diff --git a/Pawn_Unreal/.gitignore b/Pawn_Unreal/.gitignore index 9e7c1a3..cb99e31 100644 --- a/Pawn_Unreal/.gitignore +++ b/Pawn_Unreal/.gitignore @@ -98,4 +98,8 @@ Plugins/**/Binaries/* Plugins/**/Intermediate/* # End of https://www.toptal.com/developers/gitignore/api/unrealengine -vars.bat \ No newline at end of file +vars.bat + +# Wwise +!/Plugins/Wwise/ThirdParty/** +!/Plugins/Wwise/Content/* \ No newline at end of file diff --git a/Pawn_Unreal/Config/DefaultGame.ini b/Pawn_Unreal/Config/DefaultGame.ini index 7316a71..109cecd 100644 --- a/Pawn_Unreal/Config/DefaultGame.ini +++ b/Pawn_Unreal/Config/DefaultGame.ini @@ -5,3 +5,132 @@ ProjectName=Third Person BP Game Template [StartupActions] bAddPacks=True InsertPack=(PackSource="StarterContent.upack",PackName="StarterContent") + +[/Script/AkAudio.AkSettings] +MaxSimultaneousReverbVolumes=4 +WwiseProjectPath=(FilePath="../Pawn_Wwise/Pawn_Wwise.wproj") +GeneratedSoundBanksFolder=(Path="WwiseAudio/GeneratedSoundbanks") +WwiseStagingDirectory=(Path="WwiseAudio") +bSoundBanksTransfered=True +bAssetsMigrated=True +bProjectMigrated=True +DefaultOcclusionCollisionChannel=ECC_Visibility +DefaultFitToGeometryCollisionChannel=ECC_WorldStatic +AkGeometryMap=(("/Engine/EngineMaterials/DefaultDestructiblePhysicalMaterial.DefaultDestructiblePhysicalMaterial", ()),("/Engine/EngineMaterials/DefaultPhysicalMaterial.DefaultPhysicalMaterial", ()),("/Engine/EngineMaterials/LandscapeHolePhysicalMaterial.LandscapeHolePhysicalMaterial", ()),("/Engine/EngineMaterials/PhysMat_Carboard.PhysMat_Carboard", ()),("/Engine/EngineMaterials/PhysMat_Ice.PhysMat_Ice", ()),("/Engine/EngineMaterials/PhysMat_Metal.PhysMat_Metal", ()),("/Engine/EngineMaterials/PhysMat_Rubber.PhysMat_Rubber", ()),("/Engine/EngineMaterials/PhysMat_Vehicle.PhysMat_Vehicle", ()),("/Engine/EngineMaterials/PhysMat_VehicleRagdoll.PhysMat_VehicleRagdoll", ())) +GlobalDecayAbsorption=0.500000 +DefaultReverbAuxBus=None +EnvironmentDecayAuxBusMap=() +HFDampingName= +DecayEstimateName= +TimeToFirstReflectionName= +HFDampingRTPC=None +DecayEstimateRTPC=None +TimeToFirstReflectionRTPC=None +AudioInputEvent=None +AcousticTextureParamsMap=() +SplitSwitchContainerMedia=False +SplitMediaPerFolder=False +UseEventBasedPackaging=False +UnrealCultureToWwiseCulture=(("en", "English(US)")) +DefaultAssetCreationPath=/Game/WwiseAudio +InitBank=/Game/WwiseAudio/InitBank.InitBank +AudioRouting=Custom +bWwiseSoundEngineEnabled=True +bWwiseAudioLinkEnabled=False +bAkAudioMixerEnabled=False +MigratedEnableMultiCoreRendering=True +FixupRedirectorsDuringMigration=False +VisualizeRoomsAndPortals=False +bShowReverbInfo=True + +[/Script/UnrealEd.ProjectPackagingSettings] +Build=IfProjectHasCode +BuildConfiguration=PPBC_Development +BuildTarget= +FullRebuild=False +ForDistribution=False +IncludeDebugFiles=False +BlueprintNativizationMethod=Disabled +bIncludeNativizedAssetsInProjectGeneration=False +bExcludeMonolithicEngineHeadersInNativizedCode=False +UsePakFile=True +bUseIoStore=True +bUseZenStore=False +bMakeBinaryConfig=False +bGenerateChunks=False +bGenerateNoChunks=False +bChunkHardReferencesOnly=False +bForceOneChunkPerFile=False +MaxChunkSize=0 +bBuildHttpChunkInstallData=False +HttpChunkInstallDataDirectory=(Path="") +WriteBackMetadataToAssetRegistry=Disabled +bCompressed=True +PackageCompressionFormat=Oodle +bForceUseProjectCompressionFormatIgnoreHardwareOverride=False +PackageAdditionalCompressionOptions= +PackageCompressionMethod=Kraken +PackageCompressionLevel_DebugDevelopment=4 +PackageCompressionLevel_TestShipping=5 +PackageCompressionLevel_Distribution=7 +PackageCompressionMinBytesSaved=1024 +PackageCompressionMinPercentSaved=5 +bPackageCompressionEnableDDC=False +PackageCompressionMinSizeToConsiderDDC=0 +HttpChunkInstallDataVersion= +IncludePrerequisites=True +IncludeAppLocalPrerequisites=False +bShareMaterialShaderCode=True +bDeterministicShaderCodeOrder=False +bSharedMaterialNativeLibraries=True +ApplocalPrerequisitesDirectory=(Path="") +IncludeCrashReporter=False +InternationalizationPreset=English +-CulturesToStage=en ++CulturesToStage=en +LocalizationTargetCatchAllChunkId=0 +bCookAll=False +bCookMapsOnly=False +bSkipEditorContent=False +bSkipMovies=False +-IniKeyDenylist=KeyStorePassword +-IniKeyDenylist=KeyPassword +-IniKeyDenylist=rsa.privateexp +-IniKeyDenylist=rsa.modulus +-IniKeyDenylist=rsa.publicexp +-IniKeyDenylist=aes.key +-IniKeyDenylist=SigningPublicExponent +-IniKeyDenylist=SigningModulus +-IniKeyDenylist=SigningPrivateExponent +-IniKeyDenylist=EncryptionKey +-IniKeyDenylist=DevCenterUsername +-IniKeyDenylist=DevCenterPassword +-IniKeyDenylist=IOSTeamID +-IniKeyDenylist=SigningCertificate +-IniKeyDenylist=MobileProvision +-IniKeyDenylist=IniKeyDenylist +-IniKeyDenylist=IniSectionDenylist ++IniKeyDenylist=KeyStorePassword ++IniKeyDenylist=KeyPassword ++IniKeyDenylist=rsa.privateexp ++IniKeyDenylist=rsa.modulus ++IniKeyDenylist=rsa.publicexp ++IniKeyDenylist=aes.key ++IniKeyDenylist=SigningPublicExponent ++IniKeyDenylist=SigningModulus ++IniKeyDenylist=SigningPrivateExponent ++IniKeyDenylist=EncryptionKey ++IniKeyDenylist=DevCenterUsername ++IniKeyDenylist=DevCenterPassword ++IniKeyDenylist=IOSTeamID ++IniKeyDenylist=SigningCertificate ++IniKeyDenylist=MobileProvision ++IniKeyDenylist=IniKeyDenylist ++IniKeyDenylist=IniSectionDenylist +-IniSectionDenylist=HordeStorageServers +-IniSectionDenylist=StorageServers ++IniSectionDenylist=HordeStorageServers ++IniSectionDenylist=StorageServers ++DirectoriesToAlwaysCook=(Path="/Wwise/WwiseTree") ++DirectoriesToAlwaysCook=(Path="/Wwise/WwiseTypes") + diff --git a/Pawn_Unreal/Content/Developers/maxim/BP_TriggerTest.uasset b/Pawn_Unreal/Content/Developers/maxim/BP_TriggerTest.uasset index cb72b15..d8f0ad2 100644 --- a/Pawn_Unreal/Content/Developers/maxim/BP_TriggerTest.uasset +++ b/Pawn_Unreal/Content/Developers/maxim/BP_TriggerTest.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2fb54fce233f8c1dc6db6c753aa2a1b65b083a879ca3a260d2154ff2f59d866f -size 125028 +oid sha256:40247e3b2abab06ad76bce85368a0e729bd7eab161abeb138c154a0088aa5c66 +size 137488 diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/ProjectInfo.json b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/ProjectInfo.json new file mode 100644 index 0000000..963708e --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/ProjectInfo.json @@ -0,0 +1,29 @@ +{ + "ProjectInfo": { + "Project": { + "Name": "Pawn_Wwise", + "GUID": "{4CC4225E-6E29-4CFB-9AD5-8BDBBE21C951}", + "Generator": "2022.1.5.8242" + }, + "CacheRoot": "../../../../Pawn_Wwise/.cache", + "Platforms": [ + { + "Name": "Windows", + "GUID": "{A65AA759-FD18-4B50-8050-E2B5EBB888BD}", + "BasePlatform": "Windows", + "BasePlatformGUID": "{6E0CB257-C6C8-4C5C-8366-2740DFC441EB}", + "Path": "Windows" + } + ], + "Languages": [ + { + "Name": "English(US)", + "Id": "684519430", + "GUID": "{BB071A3B-DE40-4833-BE43-32DD5C317011}", + "Default": "true", + "UseAsStandIn": "true" + } + ], + "FileHash": "{D34E3E88-D55D-2954-AC8F-DD1FDAF0EC73}" + } +} \ No newline at end of file diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/ProjectInfo.xml b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/ProjectInfo.xml new file mode 100644 index 0000000..61378e6 --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/ProjectInfo.xml @@ -0,0 +1,12 @@ + + + + ../../../../Pawn_Wwise/.cache + + + + + + + {6F2A8120-A7D2-EA96-155C-FB793397F94C} + diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/10/Stop_BIP.bnk b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/10/Stop_BIP.bnk new file mode 100644 index 0000000..f2150f3 Binary files /dev/null and b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/10/Stop_BIP.bnk differ diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/10/Stop_BIP.json b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/10/Stop_BIP.json new file mode 100644 index 0000000..6b5f0e7 --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/10/Stop_BIP.json @@ -0,0 +1,33 @@ +{ + "SoundBanksInfo": { + "Platform": "Windows", + "BasePlatform": "Windows", + "SchemaVersion": "16", + "SoundBankVersion": "145", + "SoundBanks": [ + { + "Id": "1039911493", + "Type": "Event", + "GUID": "{EADC1A47-B16B-42B7-9E29-7BCD157F1C27}", + "Language": "SFX", + "Hash": "{C32B739D-90BD-A874-86F0-F05F42AB3A7B}", + "ObjectPath": "Stop_BIP", + "ShortName": "Stop_BIP", + "Path": "Event/10/Stop_BIP.bnk", + "Events": [ + { + "Id": "1039911493", + "Name": "Stop_BIP", + "ObjectPath": "\\Events\\Default Work Unit\\DOSSIER\\Stop_BIP", + "GUID": "{EADC1A47-B16B-42B7-9E29-7BCD157F1C27}", + "MaxAttenuation": "0", + "DurationType": "OneShot", + "DurationMin": "0", + "DurationMax": "0" + } + ] + } + ], + "FileHash": "{B54F2D03-7EA1-354F-5609-8D9D0128F793}" + } +} \ No newline at end of file diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/10/Stop_BIP.txt b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/10/Stop_BIP.txt new file mode 100644 index 0000000..218d327 --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/10/Stop_BIP.txt @@ -0,0 +1,3 @@ +Event ID Name Wwise Object Path Notes + 1039911493 Stop_BIP \Default Work Unit\DOSSIER\Stop_BIP + diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/10/Stop_BIP.xml b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/10/Stop_BIP.xml new file mode 100644 index 0000000..ca83727 --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/10/Stop_BIP.xml @@ -0,0 +1,14 @@ + + + + + Stop_BIP + Stop_BIP + Event/10/Stop_BIP.bnk + + + + + + {C9BBA052-BACC-EB83-7D21-FF9788B84326} + diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/30/Play_BIP.bnk b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/30/Play_BIP.bnk new file mode 100644 index 0000000..0242104 Binary files /dev/null and b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/30/Play_BIP.bnk differ diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/30/Play_BIP.json b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/30/Play_BIP.json new file mode 100644 index 0000000..a20b9f2 --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/30/Play_BIP.json @@ -0,0 +1,49 @@ +{ + "SoundBanksInfo": { + "Platform": "Windows", + "BasePlatform": "Windows", + "SchemaVersion": "16", + "SoundBankVersion": "145", + "SoundBanks": [ + { + "Id": "3026099327", + "Type": "Event", + "GUID": "{F1674A16-CA17-4824-A837-3F1D32936CE8}", + "Language": "SFX", + "Hash": "{7B1EBB1D-F8C0-4905-DF6E-CF18094DAC1E}", + "ObjectPath": "Play_BIP", + "ShortName": "Play_BIP", + "Path": "Event/30/Play_BIP.bnk", + "Plugins": { + "Custom": [ + { + "Id": "695958184", + "Name": "Synth One", + "ObjectPath": "\\Actor-Mixer Hierarchy\\Default Work Unit\\dossier\\test\\BIP\\Synth One", + "GUID": "{8E2B503A-4155-45E3-B81C-F7F6BB899CA6}", + "LibName": "Wwise Synth One", + "LibId": "9699330" + } + ] + }, + "Events": [ + { + "Id": "3026099327", + "Name": "Play_BIP", + "ObjectPath": "\\Events\\Default Work Unit\\DOSSIER\\Play_BIP", + "GUID": "{F1674A16-CA17-4824-A837-3F1D32936CE8}", + "DurationType": "Unknown", + "PluginRefs": { + "Custom": [ + { + "Id": "695958184" + } + ] + } + } + ] + } + ], + "FileHash": "{EA2AB369-8438-1D20-B186-F286AC5B60AA}" + } +} \ No newline at end of file diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/30/Play_BIP.txt b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/30/Play_BIP.txt new file mode 100644 index 0000000..e35f9a5 --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/30/Play_BIP.txt @@ -0,0 +1,9 @@ +Event ID Name Wwise Object Path Notes + 3026099327 Play_BIP \Default Work Unit\DOSSIER\Play_BIP + +Modulator Envelope ID Name Wwise Object Path Notes + 288761262 Modulator Envelope (Custom) + +Source plug-ins ID Name Type Wwise Object Path Notes + 695958184 Synth One Wwise Synth One \Actor-Mixer Hierarchy\Default Work Unit\dossier\test\BIP\Synth One + diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/30/Play_BIP.xml b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/30/Play_BIP.xml new file mode 100644 index 0000000..719c6b0 --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Event/30/Play_BIP.xml @@ -0,0 +1,25 @@ + + + + + Play_BIP + Play_BIP + Event/30/Play_BIP.bnk + + + + + + + + + + + + + + + + + {CFA376BB-60C5-C239-96B1-CDA83F87774E} + diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Init.bnk b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Init.bnk new file mode 100644 index 0000000..6f762b2 Binary files /dev/null and b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Init.bnk differ diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Init.json b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Init.json new file mode 100644 index 0000000..376e9c9 --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Init.json @@ -0,0 +1,49 @@ +{ + "SoundBanksInfo": { + "Platform": "Windows", + "BasePlatform": "Windows", + "SchemaVersion": "16", + "SoundBankVersion": "145", + "SoundBanks": [ + { + "Id": "1355168291", + "Type": "User", + "GUID": "{701ECBBD-9C7B-4030-8CDB-749EE5D1C7B9}", + "Language": "SFX", + "Hash": "{28F53A09-3ECF-7CC0-9EEE-ABA684220327}", + "ObjectPath": "Init", + "ShortName": "Init", + "Path": "Init.bnk", + "Plugins": { + "AudioDevices": [ + { + "Id": "2317455096", + "Name": "No_Output", + "ObjectPath": "\\Audio Devices\\Default Work Unit\\No_Output", + "GUID": "{15DF71CC-DC43-486E-A93A-F9E834612849}", + "LibName": "No Output", + "LibId": "11862023" + }, + { + "Id": "3859886410", + "Name": "System", + "ObjectPath": "\\Audio Devices\\Default Work Unit\\System", + "GUID": "{886A7822-C338-4EF8-9149-7E0D2A477BD3}", + "LibName": "System", + "LibId": "11403271" + } + ] + }, + "Busses": [ + { + "Id": "3803692087", + "Name": "Master Audio Bus", + "ObjectPath": "\\Master-Mixer Hierarchy\\Default Work Unit\\Master Audio Bus", + "GUID": "{1514A4D8-1DA6-412A-A17E-75CA0C2149F3}" + } + ] + } + ], + "FileHash": "{30203AB6-1ABA-3D6D-9981-31A21106ED3C}" + } +} \ No newline at end of file diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Init.txt b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Init.txt new file mode 100644 index 0000000..bb5e179 --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Init.txt @@ -0,0 +1,7 @@ +Audio Bus ID Name Wwise Object Path Notes + 3803692087 Master Audio Bus \Default Work Unit\Master Audio Bus + +Audio Devices ID Name Type Notes + 2317455096 No_Output No Output + 3859886410 System System + diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Init.xml b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Init.xml new file mode 100644 index 0000000..083d78a --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Init.xml @@ -0,0 +1,20 @@ + + + + + Init + Init + Init.bnk + + + + + + + + + + + + {4385C389-27FB-4E54-9CE2-F38127C1DE0A} + diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Pawn_SB.bnk b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Pawn_SB.bnk new file mode 100644 index 0000000..a57acfb Binary files /dev/null and b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Pawn_SB.bnk differ diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Pawn_SB.json b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Pawn_SB.json new file mode 100644 index 0000000..466cf0a --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Pawn_SB.json @@ -0,0 +1,21 @@ +{ + "SoundBanksInfo": { + "Platform": "Windows", + "BasePlatform": "Windows", + "SchemaVersion": "16", + "SoundBankVersion": "145", + "SoundBanks": [ + { + "Id": "1540204757", + "Type": "User", + "GUID": "{E9ADB960-EADA-4D0F-AE36-552B2ADE74E5}", + "Language": "SFX", + "Hash": "{DF582C47-5848-C523-62B2-3E38507AD5D5}", + "ObjectPath": "\\SoundBanks\\Default Work Unit\\Pawn_SB", + "ShortName": "Pawn_SB", + "Path": "Pawn_SB.bnk" + } + ], + "FileHash": "{E0262292-C9D9-D8B8-7E47-7C9FB4E1AA95}" + } +} \ No newline at end of file diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Pawn_SB.txt b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Pawn_SB.txt new file mode 100644 index 0000000..e69de29 diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Pawn_SB.xml b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Pawn_SB.xml new file mode 100644 index 0000000..dfab6dd --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/Pawn_SB.xml @@ -0,0 +1,11 @@ + + + + + \SoundBanks\Default Work Unit\Pawn_SB + Pawn_SB + Pawn_SB.bnk + + + {807BA9E4-B148-96AE-FB94-30918123C4C0} + diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/PlatformInfo.json b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/PlatformInfo.json new file mode 100644 index 0000000..6621030 --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/PlatformInfo.json @@ -0,0 +1,37 @@ +{ + "PlatformInfo": { + "Platform": { + "Name": "Windows", + "BasePlatform": "Windows", + "Generator": "2022.1.5.8242" + }, + "RootPaths": { + "ProjectRoot": "../../../../../Pawn_Wwise", + "SourceFilesRoot": "../../../../../Pawn_Wwise/.cache/Windows", + "SoundBanksRoot": ".", + "ExternalSourcesInputFile": "", + "ExternalSourcesOutputRoot": "../../../../../Pawn_Wwise/GeneratedSoundBanks/Windows" + }, + "DefaultAlign": "16", + "Settings": { + "AutoSoundBankDefinition": "true", + "CopyLooseStreamedMediaFiles": "true", + "SubFoldersForGeneratedFiles": "true", + "RemoveUnusedGeneratedFiles": "true", + "SourceControlGeneratedFiles": "false", + "GenerateHeaderFile": "false", + "GenerateContentTxtFile": "true", + "GenerateMetadataXML": "true", + "GenerateMetadataJSON": "true", + "GenerateAllBanksMetadata": "true", + "GeneratePerBankMetadata": "true", + "UseSoundBankNames": "true", + "AllowExceedingMaxSize": "false", + "MaxAttenuationInfo": "true", + "EstimatedDurationInfo": "true", + "PrintObjectGuid": "true", + "PrintObjectPath": "true" + }, + "FileHash": "{AFE34640-9838-E38F-BB0C-4F9C543DC99F}" + } +} \ No newline at end of file diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/PlatformInfo.xml b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/PlatformInfo.xml new file mode 100644 index 0000000..c3f389e --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/PlatformInfo.xml @@ -0,0 +1,32 @@ + + + + + ../../../../../Pawn_Wwise + ../../../../../Pawn_Wwise/.cache/Windows + . + + ../../../../../Pawn_Wwise/GeneratedSoundBanks/Windows + + 16 + + true + true + true + true + false + false + true + true + true + true + true + true + false + true + true + true + true + + {48C86647-7400-B841-5F6F-2AACB35CC1DE} + diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/PluginInfo.json b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/PluginInfo.json new file mode 100644 index 0000000..37129fc --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/PluginInfo.json @@ -0,0 +1,26 @@ +{ + "PluginInfo": { + "Platform": "Windows", + "BasePlatform": "Windows", + "PluginLibs": [ + { + "LibName": "Wwise Synth One", + "LibId": "9699330", + "Type": "Source", + "DLL": "AkSynthOne", + "StaticLib": "AkSynthOneSource" + }, + { + "LibName": "System", + "LibId": "11403271", + "Type": "AudioDevice" + }, + { + "LibName": "No Output", + "LibId": "11862023", + "Type": "AudioDevice" + } + ], + "FileHash": "{E2467BCD-FD66-8921-B109-95E8A86729CA}" + } +} \ No newline at end of file diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/PluginInfo.xml b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/PluginInfo.xml new file mode 100644 index 0000000..5926329 --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/PluginInfo.xml @@ -0,0 +1,9 @@ + + + + + + + + {48D3DF48-B305-932A-3413-98783B7E50F4} + diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/ProjectInfo.json b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/ProjectInfo.json new file mode 100644 index 0000000..0bd4d7e --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/ProjectInfo.json @@ -0,0 +1,29 @@ +{ + "ProjectInfo": { + "Project": { + "Name": "Pawn_Wwise", + "GUID": "{4CC4225E-6E29-4CFB-9AD5-8BDBBE21C951}", + "Generator": "2022.1.5.8242" + }, + "CacheRoot": "../../../../../Pawn_Wwise/.cache", + "Platforms": [ + { + "Name": "Windows", + "GUID": "{A65AA759-FD18-4B50-8050-E2B5EBB888BD}", + "BasePlatform": "Windows", + "BasePlatformGUID": "{6E0CB257-C6C8-4C5C-8366-2740DFC441EB}", + "Path": "." + } + ], + "Languages": [ + { + "Name": "English(US)", + "Id": "684519430", + "GUID": "{BB071A3B-DE40-4833-BE43-32DD5C317011}", + "Default": "true", + "UseAsStandIn": "true" + } + ], + "FileHash": "{183FADE2-4A17-FBF5-79BB-EEE1B8345771}" + } +} \ No newline at end of file diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/ProjectInfo.xml b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/ProjectInfo.xml new file mode 100644 index 0000000..e3e5a9f --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/ProjectInfo.xml @@ -0,0 +1,12 @@ + + + + ../../../../../Pawn_Wwise/.cache + + + + + + + {2FA8626A-75B7-91FF-0C53-27069DA0D2B1} + diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/SoundbanksInfo.json b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/SoundbanksInfo.json new file mode 100644 index 0000000..5f51edf --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/SoundbanksInfo.json @@ -0,0 +1,137 @@ +{ + "SoundBanksInfo": { + "Platform": "Windows", + "BasePlatform": "Windows", + "SchemaVersion": "16", + "SoundBankVersion": "145", + "RootPaths": { + "ProjectRoot": "../../../../../Pawn_Wwise", + "SourceFilesRoot": "../../../../../Pawn_Wwise/.cache/Windows", + "SoundBanksRoot": ".", + "ExternalSourcesInputFile": "", + "ExternalSourcesOutputRoot": "../../../../../Pawn_Wwise/GeneratedSoundBanks/Windows" + }, + "DialogueEvents": [], + "SoundBanks": [ + { + "Id": "1039911493", + "Type": "Event", + "GUID": "{EADC1A47-B16B-42B7-9E29-7BCD157F1C27}", + "Language": "SFX", + "Hash": "{C32B739D-90BD-A874-86F0-F05F42AB3A7B}", + "ObjectPath": "Stop_BIP", + "ShortName": "Stop_BIP", + "Path": "Event/10/Stop_BIP.bnk", + "Events": [ + { + "Id": "1039911493", + "Name": "Stop_BIP", + "ObjectPath": "\\Events\\Default Work Unit\\DOSSIER\\Stop_BIP", + "GUID": "{EADC1A47-B16B-42B7-9E29-7BCD157F1C27}", + "MaxAttenuation": "0", + "DurationType": "OneShot", + "DurationMin": "0", + "DurationMax": "0" + } + ] + }, + { + "Id": "1355168291", + "Type": "User", + "GUID": "{701ECBBD-9C7B-4030-8CDB-749EE5D1C7B9}", + "Language": "SFX", + "Hash": "{28F53A09-3ECF-7CC0-9EEE-ABA684220327}", + "ObjectPath": "Init", + "ShortName": "Init", + "Path": "Init.bnk", + "Plugins": { + "AudioDevices": [ + { + "Id": "2317455096", + "Name": "No_Output", + "ObjectPath": "\\Audio Devices\\Default Work Unit\\No_Output", + "GUID": "{15DF71CC-DC43-486E-A93A-F9E834612849}", + "LibName": "No Output", + "LibId": "11862023" + }, + { + "Id": "3859886410", + "Name": "System", + "ObjectPath": "\\Audio Devices\\Default Work Unit\\System", + "GUID": "{886A7822-C338-4EF8-9149-7E0D2A477BD3}", + "LibName": "System", + "LibId": "11403271" + } + ] + }, + "Busses": [ + { + "Id": "3803692087", + "Name": "Master Audio Bus", + "ObjectPath": "\\Master-Mixer Hierarchy\\Default Work Unit\\Master Audio Bus", + "GUID": "{1514A4D8-1DA6-412A-A17E-75CA0C2149F3}" + } + ] + }, + { + "Id": "1540204757", + "Type": "User", + "GUID": "{E9ADB960-EADA-4D0F-AE36-552B2ADE74E5}", + "Language": "SFX", + "Hash": "{DF582C47-5848-C523-62B2-3E38507AD5D5}", + "ObjectPath": "\\SoundBanks\\Default Work Unit\\Pawn_SB", + "ShortName": "Pawn_SB", + "Path": "Pawn_SB.bnk" + }, + { + "Id": "3026099327", + "Type": "Event", + "GUID": "{F1674A16-CA17-4824-A837-3F1D32936CE8}", + "Language": "SFX", + "Hash": "{7B1EBB1D-F8C0-4905-DF6E-CF18094DAC1E}", + "ObjectPath": "Play_BIP", + "ShortName": "Play_BIP", + "Path": "Event/30/Play_BIP.bnk", + "Plugins": { + "Custom": [ + { + "Id": "695958184", + "Name": "Synth One", + "ObjectPath": "\\Actor-Mixer Hierarchy\\Default Work Unit\\dossier\\test\\BIP\\Synth One", + "GUID": "{8E2B503A-4155-45E3-B81C-F7F6BB899CA6}", + "LibName": "Wwise Synth One", + "LibId": "9699330" + } + ] + }, + "Events": [ + { + "Id": "3026099327", + "Name": "Play_BIP", + "ObjectPath": "\\Events\\Default Work Unit\\DOSSIER\\Play_BIP", + "GUID": "{F1674A16-CA17-4824-A837-3F1D32936CE8}", + "DurationType": "Unknown", + "PluginRefs": { + "Custom": [ + { + "Id": "695958184" + } + ] + } + } + ] + }, + { + "Id": "3157003241", + "Type": "User", + "GUID": "{3E02EDE0-14E2-48C1-922B-7B89EAF86F5B}", + "Language": "SFX", + "Hash": "{8C6995F3-59C1-72D6-CF1A-7B0F8850DF48}", + "ObjectPath": "\\SoundBanks\\Default Work Unit\\TEST", + "ShortName": "TEST", + "Path": "TEST.bnk" + } + ], + "FileHash": "{42486F5F-2FA8-0CE1-C029-5CAC0556E0D5}" + } +} \ No newline at end of file diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/SoundbanksInfo.xml b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/SoundbanksInfo.xml new file mode 100644 index 0000000..69dd630 --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/SoundbanksInfo.xml @@ -0,0 +1,65 @@ + + + + ../../../../../Pawn_Wwise + ../../../../../Pawn_Wwise/.cache/Windows + . + + ../../../../../Pawn_Wwise/GeneratedSoundBanks/Windows + + + + + Stop_BIP + Stop_BIP + Event/10/Stop_BIP.bnk + + + + + + Init + Init + Init.bnk + + + + + + + + + + + + \SoundBanks\Default Work Unit\Pawn_SB + Pawn_SB + Pawn_SB.bnk + + + Play_BIP + Play_BIP + Event/30/Play_BIP.bnk + + + + + + + + + + + + + + + + + \SoundBanks\Default Work Unit\TEST + TEST + TEST.bnk + + + {058AD0CA-1DE0-E9F6-08B8-3A0252BB92AC} + diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/TEST.bnk b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/TEST.bnk new file mode 100644 index 0000000..a4495a3 Binary files /dev/null and b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/TEST.bnk differ diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/TEST.json b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/TEST.json new file mode 100644 index 0000000..fb80450 --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/TEST.json @@ -0,0 +1,21 @@ +{ + "SoundBanksInfo": { + "Platform": "Windows", + "BasePlatform": "Windows", + "SchemaVersion": "16", + "SoundBankVersion": "145", + "SoundBanks": [ + { + "Id": "3157003241", + "Type": "User", + "GUID": "{3E02EDE0-14E2-48C1-922B-7B89EAF86F5B}", + "Language": "SFX", + "Hash": "{8C6995F3-59C1-72D6-CF1A-7B0F8850DF48}", + "ObjectPath": "\\SoundBanks\\Default Work Unit\\TEST", + "ShortName": "TEST", + "Path": "TEST.bnk" + } + ], + "FileHash": "{5EA540BB-7786-1033-A790-810C1859DA33}" + } +} \ No newline at end of file diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/TEST.txt b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/TEST.txt new file mode 100644 index 0000000..e69de29 diff --git a/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/TEST.xml b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/TEST.xml new file mode 100644 index 0000000..3d027c3 --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/GeneratedSoundbanks/Windows/TEST.xml @@ -0,0 +1,11 @@ + + + + + \SoundBanks\Default Work Unit\TEST + TEST + TEST.bnk + + + {72213C34-6E35-7AB0-D8EF-1EEF69320458} + diff --git a/Pawn_Unreal/Content/WwiseAudio/InitBank.uasset b/Pawn_Unreal/Content/WwiseAudio/InitBank.uasset new file mode 100644 index 0000000..fd597eb --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/InitBank.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ddb166725f2a05910446e5f3f3a7621828e9a8f09dc59fd65b9363e221de56e +size 1315 diff --git a/Pawn_Unreal/Content/WwiseAudio/Play_BIP.uasset b/Pawn_Unreal/Content/WwiseAudio/Play_BIP.uasset new file mode 100644 index 0000000..d2cd496 --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/Play_BIP.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:533879a3e25a95766e5df5cf64477a913aa0d32adad7797fc166de423fed1c95 +size 1679 diff --git a/Pawn_Unreal/Content/WwiseAudio/ProjectInfo.json b/Pawn_Unreal/Content/WwiseAudio/ProjectInfo.json new file mode 100644 index 0000000..b2db247 --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/ProjectInfo.json @@ -0,0 +1,29 @@ +{ + "ProjectInfo": { + "Project": { + "Name": "Pawn_Wwise", + "GUID": "{4CC4225E-6E29-4CFB-9AD5-8BDBBE21C951}", + "Generator": "2022.1.5.8242" + }, + "CacheRoot": "../../../Pawn_Wwise/.cache", + "Platforms": [ + { + "Name": "Windows", + "GUID": "{A65AA759-FD18-4B50-8050-E2B5EBB888BD}", + "BasePlatform": "Windows", + "BasePlatformGUID": "{6E0CB257-C6C8-4C5C-8366-2740DFC441EB}", + "Path": "GeneratedSoundBanks/Windows" + } + ], + "Languages": [ + { + "Name": "English(US)", + "Id": "684519430", + "GUID": "{BB071A3B-DE40-4833-BE43-32DD5C317011}", + "Default": "true", + "UseAsStandIn": "true" + } + ], + "FileHash": "{44543EFB-4065-A087-7445-CE4EFF7B3667}" + } +} \ No newline at end of file diff --git a/Pawn_Unreal/Content/WwiseAudio/ProjectInfo.xml b/Pawn_Unreal/Content/WwiseAudio/ProjectInfo.xml new file mode 100644 index 0000000..056e1da --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/ProjectInfo.xml @@ -0,0 +1,12 @@ + + + + ../../../Pawn_Wwise/.cache + + + + + + + {17416A31-77F7-D02C-A492-7C4E6DDEDC3D} + diff --git a/Pawn_Unreal/Content/WwiseAudio/Stop_BIP.uasset b/Pawn_Unreal/Content/WwiseAudio/Stop_BIP.uasset new file mode 100644 index 0000000..2f5660f --- /dev/null +++ b/Pawn_Unreal/Content/WwiseAudio/Stop_BIP.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:869ce48312b91704d968221eb7a1d25de9bed55e018889beb8ae86d304ec9593 +size 1678 diff --git a/Pawn_Unreal/Plugins/Wwise/Config/BaseWwise.ini b/Pawn_Unreal/Plugins/Wwise/Config/BaseWwise.ini new file mode 100644 index 0000000..e9bdd84 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Config/BaseWwise.ini @@ -0,0 +1,11 @@ +[CoreRedirects] ++StructRedirects=(OldName="AkPoly",NewName="AkSurfacePoly") ++StructRedirects=(OldName="AkEdgeInfo ",NewName="AkSurfaceEdgeInfo") + ++StructRedirects=(OldName="WwiseAssetInfo",NewName="WwiseObjectInfo") ++PropertyRedirects=(OldName="WwiseObjectInfo.AssetGuid",NewName="WwiseObjectInfo.WwiseGuid") ++PropertyRedirects=(OldName="WwiseObjectInfo.AssetName",NewName="WwiseObjectInfo.WwiseName") ++PropertyRedirects=(OldName="WwiseObjectInfo.AssetShortId",NewName="WwiseObjectInfo.WwiseShortId") + ++PropertyRedirects=(OldName="AkMainOutputSettings.AudioDeviceShareset",NewName="AkMainOutputSettings.AudioDeviceShareSet") ++PropertyRedirects=(OldName="AkEffectShareSet.SharesetInfo",NewName="AkEffectShareSet.ShareSetInfo") \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Config/DefaultWwise.ini b/Pawn_Unreal/Plugins/Wwise/Config/DefaultWwise.ini new file mode 100644 index 0000000..e9bdd84 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Config/DefaultWwise.ini @@ -0,0 +1,11 @@ +[CoreRedirects] ++StructRedirects=(OldName="AkPoly",NewName="AkSurfacePoly") ++StructRedirects=(OldName="AkEdgeInfo ",NewName="AkSurfaceEdgeInfo") + ++StructRedirects=(OldName="WwiseAssetInfo",NewName="WwiseObjectInfo") ++PropertyRedirects=(OldName="WwiseObjectInfo.AssetGuid",NewName="WwiseObjectInfo.WwiseGuid") ++PropertyRedirects=(OldName="WwiseObjectInfo.AssetName",NewName="WwiseObjectInfo.WwiseName") ++PropertyRedirects=(OldName="WwiseObjectInfo.AssetShortId",NewName="WwiseObjectInfo.WwiseShortId") + ++PropertyRedirects=(OldName="AkMainOutputSettings.AudioDeviceShareset",NewName="AkMainOutputSettings.AudioDeviceShareSet") ++PropertyRedirects=(OldName="AkEffectShareSet.SharesetInfo",NewName="AkEffectShareSet.ShareSetInfo") \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Content/AkMacroLibrary.uasset b/Pawn_Unreal/Plugins/Wwise/Content/AkMacroLibrary.uasset new file mode 100644 index 0000000..c550ac1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/AkMacroLibrary.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46d3cbbb9a6dd5eec497b1eaf7540a93a824e7976d4fcf444773ea6455dca19c +size 125688 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/AnimNotify_AkEvent.uasset b/Pawn_Unreal/Plugins/Wwise/Content/AnimNotify_AkEvent.uasset new file mode 100644 index 0000000..58d0d8b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/AnimNotify_AkEvent.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46cd5ef3baffdbb3d5fd86049192f72312e2d9b60ff2190d68a4011b325f2342 +size 88976 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/DefaultForegroundTextMaterial.uasset b/Pawn_Unreal/Plugins/Wwise/Content/DefaultForegroundTextMaterial.uasset new file mode 100644 index 0000000..e7d9fde --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/DefaultForegroundTextMaterial.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45417a8050d65843445c8a7c920125b811cf35592ea4c1f7ceb3cf91b6cc8897 +size 89347 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/S_AkComponent.uasset b/Pawn_Unreal/Plugins/Wwise/Content/S_AkComponent.uasset new file mode 100644 index 0000000..4c09ea2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/S_AkComponent.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00f7bca9c135563dd783ab06caaa1d9bbc3da802ae02a1ad57b373e0ae24f729 +size 39524 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/S_AkSpotReflector.uasset b/Pawn_Unreal/Plugins/Wwise/Content/S_AkSpotReflector.uasset new file mode 100644 index 0000000..2383691 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/S_AkSpotReflector.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba3e7157c86c7082762b59a590c419f1b06c24a2baef7dd667f9678041ab1ba6 +size 40033 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/Titlebar_WwiseAppIcon.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/Titlebar_WwiseAppIcon.uasset new file mode 100644 index 0000000..07e8ffc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/Titlebar_WwiseAppIcon.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7e663a8d78ffce58bacaa3f0cd0782885c7f8313421c5aa23b951fbfd3ece3f3 +size 6520 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/acoutex_nor.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/acoutex_nor.uasset new file mode 100644 index 0000000..6096dcc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/acoutex_nor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1405278ce2bd11981232dcc1bf562ac6fd491851f490311247b38de3c05c1a63 +size 3931 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/actor_mixer_nor.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/actor_mixer_nor.uasset new file mode 100644 index 0000000..e23494f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/actor_mixer_nor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa5d3848fc692d56bd994fbf6c5bb58b5e59d46ec6fd8173777083ad8d4d440b +size 3912 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/auxbus_nor.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/auxbus_nor.uasset new file mode 100644 index 0000000..9818324 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/auxbus_nor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbfdfa807435d29538c295ffa2099b99c35453144ccb4d58d1505c8bde602cbe +size 4065 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/bus_nor.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/bus_nor.uasset new file mode 100644 index 0000000..cc431af --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/bus_nor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a5bd5c3682cfaf9f2c2a300716cf7713c07a39deef53a22dc0495e595a7e16e +size 3769 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/container_random_sequence_nor.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/container_random_sequence_nor.uasset new file mode 100644 index 0000000..6665c40 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/container_random_sequence_nor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:925ea9cd08514e7ecff6a0a99d88ef245dc55c99071a43d6a25e9014b7f6a8c6 +size 3993 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/container_switch_nor.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/container_switch_nor.uasset new file mode 100644 index 0000000..406f94c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/container_switch_nor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c53fd0077cee7d1ed6d473e632331bde9eada1ad526b9914b72be4d6d289488 +size 4119 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/effect_shareset_nor.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/effect_shareset_nor.uasset new file mode 100644 index 0000000..650e1f5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/effect_shareset_nor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:20eb326bdfd38a20320de207f631d2cac7484dbe26ff1dd49467a947aff49fd2 +size 4390 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/event_nor.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/event_nor.uasset new file mode 100644 index 0000000..1cdc039 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/event_nor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:116b907012897aaffeb3c0711d13680d519a82f6e86501700dbc5a4cb183cdd0 +size 3907 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/folder_nor.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/folder_nor.uasset new file mode 100644 index 0000000..c3b2cbf --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/folder_nor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62a893e7eed1cc2eda60905b9ce3f3ba85fa947ac508ad08f1702e87cff1e7ea +size 3789 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/gameparameter_nor.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/gameparameter_nor.uasset new file mode 100644 index 0000000..8f502e2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/gameparameter_nor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a72255c94e6dc8a6dead4609dd3f56f75e1c9cbe8cc98bc6d0060391a04208df +size 4295 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/layer_container_nor.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/layer_container_nor.uasset new file mode 100644 index 0000000..fa99c83 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/layer_container_nor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13f81d2b43b0804489e74f9794b739f42e8e1e4131b6cfcf720aba02f5885c56 +size 4388 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/motion_bus_nor.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/motion_bus_nor.uasset new file mode 100644 index 0000000..5b2f2c0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/motion_bus_nor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b12055d44e4f9aab78be3ad0b5db04b5ef8c33f64af54b51c037f6ff0a5e485 +size 4460 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/physical_folder_nor.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/physical_folder_nor.uasset new file mode 100644 index 0000000..92c40cf --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/physical_folder_nor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2cf91ecb2f5b59b3db9114b96d88344f04f6b8cdf61a791a72c70bceead7cb38 +size 3732 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/sound_fx_nor.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/sound_fx_nor.uasset new file mode 100644 index 0000000..a656b1a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/sound_fx_nor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14068072b462abc001c1452f04cd962497a28becc68d76dab28c3c2c0851b55e +size 4891 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/state_nor.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/state_nor.uasset new file mode 100644 index 0000000..2b4711e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/state_nor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ed4e3fcc78d36b7d3acedd3374623ddeeaa4f10b2f6c40ee59fd9da988b345f +size 4421 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/stategroup_nor.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/stategroup_nor.uasset new file mode 100644 index 0000000..56a86b9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/stategroup_nor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ef1040eb5c00ddfc083e402f952dd298ed386c85fc91a4a14a637aace7c106d +size 4269 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/switch_nor.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/switch_nor.uasset new file mode 100644 index 0000000..578814e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/switch_nor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf1e59cd8419b02b6e94854b171aef7cbfd0399b267f6d6398a5a7c05fa3b564 +size 3829 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/switchgroup_nor.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/switchgroup_nor.uasset new file mode 100644 index 0000000..c55cb9b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/switchgroup_nor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:98d67b58ddac762c26ea667b515c253dfba108bcbc7ff294c6d41745dd94709d +size 3919 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/trigger_nor.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/trigger_nor.uasset new file mode 100644 index 0000000..85fd930 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/trigger_nor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:405497dcfe2411744cdbc4bc0c5661217b93a3ad7c71648971ac10e5361099d0 +size 3993 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/workunit_nor.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/workunit_nor.uasset new file mode 100644 index 0000000..b2efb1c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/workunit_nor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4dc1d1cc1848b1f10fe0af35ec683fe74bedba6ce3799823d89294d5e80201c7 +size 4435 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/wproj.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/wproj.uasset new file mode 100644 index 0000000..2e01cf8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/wproj.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c7b69323d5f042a66dd478054fdc0a6165db3a8631e0967e6ba7232893ff7e5 +size 4796 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/wwise_icon_16.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/wwise_icon_16.uasset new file mode 100644 index 0000000..1b9823a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/wwise_icon_16.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c79ed942262d4490f3b21d7ae867bf86dba6ae54683784abba4d1e8618e13aa1 +size 3961 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/wwise_icon_512.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/wwise_icon_512.uasset new file mode 100644 index 0000000..c7f6931 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/wwise_icon_512.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c9751b0d2b0520a108ebb07ebb3e27509bb108a47850be51443b9d7b09f70e6b +size 64737 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/wwise_logo_32.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/wwise_logo_32.uasset new file mode 100644 index 0000000..a54a073 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTree/Icons/wwise_logo_32.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:005d0657189956b4a709a1882edfaffb58be3c7a38982c8cf1481e6590b5aff0 +size 6482 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AK_Acoustic_Portal.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AK_Acoustic_Portal.uasset new file mode 100644 index 0000000..fefdba9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AK_Acoustic_Portal.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d93971b9b71396c86a36cbefc42873f5b77bbe343be4e5be8aa76005b6904e2 +size 35783 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AK_Acoustic_Portal_Explorer.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AK_Acoustic_Portal_Explorer.uasset new file mode 100644 index 0000000..6d6fa9a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AK_Acoustic_Portal_Explorer.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:55761259beb1ea79c2f29ff078abc67c7f74aeb29d52b97bc8255a10b1e2f867 +size 3711 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AK_Reverb_Volume.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AK_Reverb_Volume.uasset new file mode 100644 index 0000000..ffb2939 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AK_Reverb_Volume.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09acd00f1c05b3878b169c7a3f7771e931786dec911c2fcbbdc8058182275f14 +size 44050 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AK_Reverb_Volume_Explorer.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AK_Reverb_Volume_Explorer.uasset new file mode 100644 index 0000000..98897e9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AK_Reverb_Volume_Explorer.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5fc02c61b6d3ce1fd27da54bdb212633052f61cb484769b30751318fd925077 +size 4115 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AK_Spatial_Audio_Volume.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AK_Spatial_Audio_Volume.uasset new file mode 100644 index 0000000..788341e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AK_Spatial_Audio_Volume.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34090948bb8e3ca6da7d44c07dc060b7cca9579f31ef61faf5ed0d8016c8d7a7 +size 55918 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AK_Spatial_Audio_Volume_Explorer.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AK_Spatial_Audio_Volume_Explorer.uasset new file mode 100644 index 0000000..234f6f9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AK_Spatial_Audio_Volume_Explorer.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b8a9aa71416ba996dd8988339527660e31a412884d074e728bedee143659614 +size 3951 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkAcousticTexture.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkAcousticTexture.uasset new file mode 100644 index 0000000..877b71a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkAcousticTexture.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17a484c295dabb87dd9faa12cbd8a8bcc38f4234ff1a8677936088ad77ed01ec +size 14582 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkAudioBank.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkAudioBank.uasset new file mode 100644 index 0000000..7a7786f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkAudioBank.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc9f1c75dbf713cb196ffbc5ea60bd55b69ed9678b5e75fba2cac59ed4ee13bb +size 42296 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkAudioEvent.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkAudioEvent.uasset new file mode 100644 index 0000000..676545c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkAudioEvent.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae702db314998f8f90ccee4d398bf5c321538163b349930b841940ccdc896797 +size 14679 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkAuxBus.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkAuxBus.uasset new file mode 100644 index 0000000..f42b998 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkAuxBus.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:35db22d30ae3c694b918a2afed29162a7ca311ace9099c107bd3eccb48d6651a +size 15077 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkEffectShareSet.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkEffectShareSet.uasset new file mode 100644 index 0000000..fc6ad8e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkEffectShareSet.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:822209dcf8821603919011db59a8d1878dc66eb3d10381c589ffc8e4a6f55469 +size 25368 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkExternalMediaAsset.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkExternalMediaAsset.uasset new file mode 100644 index 0000000..5b3b672 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkExternalMediaAsset.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:434f309d470a6346da8f957e1fa68e201f0afc52e348d8b67e5ada72ef4a0090 +size 27459 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkLocalizedMediaAsset.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkLocalizedMediaAsset.uasset new file mode 100644 index 0000000..b662682 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkLocalizedMediaAsset.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df06cf4845990edaa69fc312ddd45885a347e2e646aea81b0a498e0130cf628f +size 39608 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkMediaAsset.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkMediaAsset.uasset new file mode 100644 index 0000000..1b26712 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkMediaAsset.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5803ee3f09a42c4dfa08ec617a1f2974a75a0a455b371a536bc715dcc8f0f0f1 +size 15243 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkRtpc.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkRtpc.uasset new file mode 100644 index 0000000..dd9c140 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkRtpc.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dfbc6626da1e61061714e90caab12ba47854706ecb969e6d9d88a2692da2eb1a +size 20999 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkStateValue.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkStateValue.uasset new file mode 100644 index 0000000..c0713a0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkStateValue.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69d58d2eb99b5c7c14eea452884bfb6e78c97f5e137459c664d7ab9cc63779f5 +size 44436 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkSwitchValue.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkSwitchValue.uasset new file mode 100644 index 0000000..f8df40f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkSwitchValue.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9a1c3a5179eaf1cd052723cde91ba5b768a039c1b2eb3a32290cbccefca8780 +size 18289 diff --git a/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkTrigger.uasset b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkTrigger.uasset new file mode 100644 index 0000000..ce7dceb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Content/WwiseTypes/AkTrigger.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:276066e82586e730b0522f167567b021f003179e4f42e00bb4188d0ee4dd7e5f +size 14862 diff --git a/Pawn_Unreal/Plugins/Wwise/LauncherInfo.json b/Pawn_Unreal/Plugins/Wwise/LauncherInfo.json new file mode 100644 index 0000000..4c19139 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/LauncherInfo.json @@ -0,0 +1,52 @@ +{ + "originalSdkFolder": "C:\\Program Files (x86)\\Audiokinetic\\Wwise 2022.1.5.8242\\SDK", + "currentVersion": { + "build": 2714, + "major": 1, + "minor": 5, + "nickname": "", + "year": 2022 + }, + "installedBundleGroups": [ + { + "id": "Packages", + "valueId": "Unreal" + }, + { + "id": "Packages", + "valueId": "UnrealSwitch" + }, + { + "id": "Packages", + "valueId": "UnrealStadia" + }, + { + "id": "Packages", + "valueId": "UnrealPS4" + }, + { + "id": "Packages", + "valueId": "UnrealPS5" + }, + { + "id": "Packages", + "valueId": "UnrealXboxSeriesX" + }, + { + "id": "Packages", + "valueId": "UnrealXboxOne" + }, + { + "id": "Packages", + "valueId": "UnrealXboxOneGC" + }, + { + "id": "Packages", + "valueId": "UnrealWinGC" + }, + { + "id": "DeploymentPlatforms", + "valueId": "UE52" + } + ] +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Resources/Icon128.png b/Pawn_Unreal/Plugins/Wwise/Resources/Icon128.png new file mode 100644 index 0000000..bdd0e17 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Resources/Icon128.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2013608c596a2b8ada0ae8513010028b9dd672a9f4b54ff00ac427c3037554d5 +size 8990 diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/AkAudio.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/AkAudio.Build.cs new file mode 100644 index 0000000..01a189a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/AkAudio.Build.cs @@ -0,0 +1,123 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; +using System; +using System.IO; +using System.Collections.Generic; +using System.Reflection; + +public class AkAudio : ModuleRules +{ + public AkAudio(ReadOnlyTargetRules Target) : base(Target) + { + // If packaging as an Engine plugin, the UBT expects to already have a precompiled plugin available + // This can be set to true so long as plugin was already precompiled + bUsePrecompiled = false; + bPrecompile = false; + + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + bAllowConfidentialPlatformDefines = true; + + PrivateIncludePathModuleNames.AddRange( + new string[] + { + "Settings", + "UMG", + "TargetPlatform" + } + ); + + PublicDependencyModuleNames.AddRange( + new string[] + { + "UMG", +#if !UE_5_1_OR_LATER + "APEX", + "PhysX", +#endif + "WwiseConcurrency", + "WwiseResourceLoader", + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + + "AudioMixer", + "Chaos", + "InputCore", + "Json", + "MovieScene", + "MovieSceneTracks", + "NetworkReplayStreaming", + "PhysicsCore", + "Projects", + "Slate", + "SlateCore", + "XmlParser", + + "WwiseFileHandler", + "WwiseSoundEngine" + } + ); + + if (Target.bBuildEditor) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "DesktopPlatform", +#if UE_5_0_OR_LATER + "DeveloperToolSettings", +#endif + "EditorStyle", + + "GeometryMode", + "RenderCore", + "SharedSettingsWidgets", + "SourceControl", + "TargetPlatform", + "UnrealEd" + } + ); + } + + if (Target.bBuildWithEditorOnlyData) + { + PublicDependencyModuleNames.AddRange( + new String[] + { + "WwiseProjectDatabase" + } + ); + PrivateDependencyModuleNames.AddRange( + new string[] + { + "WwiseResourceCooker" + } + ); + } + + PrivateIncludePaths.Add("AkAudio/Private"); + PrivateIncludePaths.Add("AkAudio/Classes/GTE"); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAcousticPortal.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAcousticPortal.h new file mode 100644 index 0000000..421d04e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAcousticPortal.h @@ -0,0 +1,269 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Components/TextRenderComponent.h" +#include "GameFramework/Volume.h" +#include "ObstructionAndOcclusionService/AkPortalObstructionAndOcclusionService.h" +#include "AkGameplayTypes.h" +#if WITH_EDITOR +#include "AkSettings.h" +#endif +#include "AkAcousticPortal.generated.h" + +class UAkRoomComponent; +class UAkLateReverbComponent; + +UCLASS(ClassGroup = Audiokinetic, hidecategories = (Advanced, Attachment, Volume), BlueprintType, meta = (BlueprintSpawnableComponent)) +class AKAUDIO_API UAkPortalComponent : public USceneComponent +{ + GENERATED_BODY() + +public: + UAkPortalComponent(const class FObjectInitializer& ObjectInitializer); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkPortalComponent") + void OpenPortal(); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkPortalComponent") + void ClosePortal(); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkPortalComponent") + AkAcousticPortalState GetCurrentState() const; + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkPortalComponent") + UPrimitiveComponent* GetPrimitiveParent() const; + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkPortalComponent") + bool PortalPlacementValid() const { return GetFrontRoom() != GetBackRoom(); } + + /** If true, the room connections for this portal can change during runtime when this portal moves. For worlds containing many rooms, this can be expensive. Note that this portal's room connections may still change, even when bDynamic = false, when dynamic rooms are moved (i.e. when rooms move who have bDynamic = true). */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AkPortalComponent", meta = (DisplayName = "Is Dynamic")) + bool bDynamic = false; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AkPortalComponent") + AkAcousticPortalState InitialState = AkAcousticPortalState::Open; + + /** Time interval between obstruction checks (direct line of sight between listener and portal opening). Set to 0 to disable obstruction checks. We recommend disabling it if you want to use full Spatial Audio diffraction. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AkPortalComponent|Obstruction") + float ObstructionRefreshInterval = .0f; + + /** Collision channel for obstruction checks (between listener and portal opening). */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AkPortalComponent|Obstruction") + TEnumAsByte ObstructionCollisionChannel = ECollisionChannel::ECC_Visibility; + + void ResetPortalState(); + + FVector GetExtent() const; + AkRoomID GetFrontRoom() const; + AkRoomID GetBackRoom() const; + AkPortalID GetPortalID() const { return AkPortalID(this); } + + /** Update the room connections for the portal, given the portals current transform. + Return true if the room connections have changed. + */ + bool UpdateConnectedRooms(); + + const UAkRoomComponent* GetFrontRoomComponent() const { return FrontRoom; } + const UAkRoomComponent* GetBackRoomComponent() const { return BackRoom; } + + virtual void BeginPlay() override; +#if WITH_EDITOR + virtual void BeginDestroy() override; + virtual void InitializeComponent() override; + virtual void OnComponentCreated() override; + virtual void PostLoad() override; + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + void UpdateTextRotations() const; +#endif // WITH_EDITOR + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction * ThisTickFunction) override; + virtual void OnRegister() override; + virtual void OnUnregister() override; + virtual void OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) override; + virtual bool MoveComponentImpl( + const FVector & Delta, + const FQuat & NewRotation, + bool bSweep, + FHitResult * Hit, + EMoveComponentFlags MoveFlags, + ETeleportType Teleport) override; + +private: + class UPrimitiveComponent* Parent; + + void InitializeParent(); + void SetSpatialAudioPortal(); + + template + void FindConnectedComponents(FAkEnvironmentIndex& RoomQuery, tComponent*& out_pFront, tComponent*& out_pBack); + + AkAcousticPortalState PortalState; + +#if WITH_EDITOR + static const float RoomsRefreshIntervalEditor; +#endif + static const float RoomsRefreshIntervalGame; + static const float RoomsRefreshDistanceThreshold; + static const float RoomsRefreshMinRotationThreshold_Degrees; + float RoomsRefreshIntervalSeconds = 0.5f; + float LastRoomsUpdate = 0.0f; + FVector PreviousLocation; + FRotator PreviousRotation; + + bool PortalNeedUpdated = false; + UAkRoomComponent* FrontRoom; + UAkRoomComponent* BackRoom; + + AkPortalObstructionAndOcclusionService ObstructionService; + +#if WITH_EDITOR + void HandleObjectsReplaced(const TMap& ReplacementMap); + class UDrawPortalComponent* DrawPortalComponent = nullptr; + void RegisterVisEnabledCallback(); + void InitializeDrawComponent(); + void DestroyDrawComponent(); + FDelegateHandle ShowPortalsChangedHandle; + + bool AreTextVisualizersInitialized() const; + void InitTextVisualizers(); + void DestroyTextVisualizers(); + void UpdateRoomNames(); + void UpdateTextVisibility(); + // Updates the location, rotation and visibility of the text visualizers + void UpdateTextLocRotVis(); + bool bWasSelected = false; +#endif + +#if WITH_EDITORONLY_DATA + UPROPERTY(SkipSerialization, NonTransactional) + mutable UTextRenderComponent* FrontRoomText = nullptr; + + UPROPERTY(SkipSerialization, NonTransactional) + mutable UTextRenderComponent* BackRoomText = nullptr; +#endif +}; + +UCLASS(ClassGroup = Audiokinetic, hidecategories = (Advanced, Attachment, Volume), BlueprintType) +class AKAUDIO_API AAkAcousticPortal : public AVolume +{ + GENERATED_BODY() + +public: + AAkAcousticPortal(const class FObjectInitializer& ObjectInitializer); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkAcousticPortal") + void OpenPortal(); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkAcousticPortal") + void ClosePortal(); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkAcousticPortal") + AkAcousticPortalState GetCurrentState() const; + + AkRoomID GetFrontRoom() const; + AkRoomID GetBackRoom() const; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Portal", meta = (ShowOnlyInnerProperties)) + UAkPortalComponent* Portal = nullptr; + + virtual void PostRegisterAllComponents() override; + virtual void PostLoad() override; + virtual void Serialize(FArchive& Ar) override; + +#if WITH_EDITOR + void FitRaycast(); + void FitPortal(); + virtual void PostEditMove(bool bFinished) override; + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; + + bool GetBestHits(FVector& Start0, FVector& End0, FVector& Start1, FVector& End1) + { + if (BestFitValid) + { + Start0 = BestFit[0]; + End0 = BestFit[1]; + Start1 = BestFit[2]; + End1 = BestFit[3]; + + return true; + } + return false; + } + float GetDetectionRadius() const { return DetectionRadius; } + bool GetFitToGeometry() const { return FitToGeometry; } + bool GetIsDragging() const { return IsDragging; } + + virtual FName GetCustomIconName() const override + { + static const FName IconName("ClassIcon.AkAcousticPortal"); + return IconName; + } +#endif + +protected: + static const int kNumRaycasts = 128; + +#if WITH_EDITORONLY_DATA + void ClearBestFit(); + + /** + Automatically fit the Ak Acoustic Portal to surrounding geometry. The fitting operation is performed after enabling this property, or after moving the actor to a new location. + To find portals in surrounding geometry, rays emanating spherically outwards are cast from the origin of the actor in an attempt to detect sets of parallel surfaces. + The "best" detected parallel surfaces are indicated with yellow outline when dragging the actor to a new location. + */ + UPROPERTY(EditAnywhere, Category = "Fit to Geometry") + bool FitToGeometry = false; + + /** + Sets the collision channel for the ray traces performed to fit the portal to the surrounding geometry. When set to 'Use Integration Settings Default', the value will be taken from the DefaultFitToGeometryCollisionChannel in the Wwise Integration Settings. + */ + UPROPERTY(EditAnywhere, Category = "Fit to Geometry") + TEnumAsByte CollisionChannel = { EAkCollisionChannel::EAKCC_UseIntegrationSettingsDefault }; + +#if WITH_EDITOR + /** + Converts between EAkCollisionChannel and ECollisionChannel. Returns Wwise Integration Settings default if CollisionChannel == UseIntegrationSettingsDefault. Otherwise, casts CollisionChannel to ECollisionChannel. + */ + UFUNCTION(BlueprintCallable, Category = "Fit to Geometry") + ECollisionChannel GetCollisionChannel(); +#endif + + /** + Limits the effective portal opening size that can be detected when fitting the portal to surrounding geometry. + Increase this value to find larger openings; decrease it if large portals are erroneously detected, for example ones that span whole rooms. + The slider range can be expanded by entering a text value into this field. + */ + UPROPERTY(EditAnywhere, Category = "Fit to Geometry", meta = (ClampMin = 1.0f, ClampMax = 100000.0f, UIMin = 100.0f, UIMax = 5000.0f)) + float DetectionRadius = 500.0f; + + FVector SavedRaycastOrigin; + bool bUseSavedRaycastOrigin = false; + FVector BestFit[4]; + bool BestFitValid = false; + bool IsDragging = false; +#endif + +private: + /** As of Wwise 2020.1, the InitialState is contained in the AkPortalComponent */ + UPROPERTY() + AkAcousticPortalState InitialState; + + UPROPERTY(Transient) + bool bRequiresStateMigration = false; + bool bRequiresTransformMigration = false; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAcousticTexture.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAcousticTexture.h new file mode 100644 index 0000000..cce3ba8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAcousticTexture.h @@ -0,0 +1,57 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkAudioType.h" +#include "Wwise/CookedData/WwiseAcousticTextureCookedData.h" +#if WITH_EDITORONLY_DATA +#include "Wwise/Info/WwiseObjectInfo.h" +#endif +#include "AkAcousticTexture.generated.h" + +UCLASS(BlueprintType) +class AKAUDIO_API UAkAcousticTexture : public UAkAudioType +{ + GENERATED_BODY() + +public : + UPROPERTY(Transient, VisibleAnywhere, Category = "AkTexture") + FWwiseAcousticTextureCookedData AcousticTextureCookedData; + +#if WITH_EDITORONLY_DATA + UPROPERTY(EditAnywhere, Category="AkTexture") + FLinearColor EditColor = FLinearColor(EForceInit::ForceInitToZero); + + UPROPERTY(EditAnywhere, Category = "AkTexture") + FWwiseObjectInfo AcousticTextureInfo; +#endif + +public: + void Serialize(FArchive& Ar) override; + virtual AkUInt32 GetShortID() const override {return AcousticTextureCookedData.ShortId;} + +#if WITH_EDITORONLY_DATA + virtual void LoadData() override { GetAcousticTextureCookedData(); } + void GetAcousticTextureCookedData(); + + virtual FWwiseObjectInfo* GetInfoMutable() override {return &AcousticTextureInfo;} + virtual FWwiseObjectInfo GetInfo() const override{return AcousticTextureInfo;} + virtual bool ObjectIsInSoundBanks() override; + virtual void FillInfo() override; +#endif +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAcousticTextureSetComponent.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAcousticTextureSetComponent.h new file mode 100644 index 0000000..7719736 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAcousticTextureSetComponent.h @@ -0,0 +1,91 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#include "AkInclude.h" +#include "AkSettings.h" +#include "AkAcousticTexture.h" +#include "Components/SceneComponent.h" +#include "AkAcousticTextureSetComponent.generated.h" + +struct FAkReverbDescriptor; + +UCLASS(ClassGroup = Audiokinetic, abstract) +class AKAUDIO_API UAkAcousticTextureSetComponent : public USceneComponent +{ + GENERATED_BODY() + +public: + UAkAcousticTextureSetComponent(const class FObjectInitializer& ObjectInitializer); + + virtual void GetTexturesAndSurfaceAreas(TArray& textures, TArray& surfaceAreas) const { check(0 && "This function must be overidden"); } + + void SetReverbDescriptor(FAkReverbDescriptor* reverbDescriptor); + + virtual void OnRegister() override; + virtual void OnUnregister() override; + virtual void BeginPlay() override; + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + + virtual AkGeometrySetID GetGeometrySetID() const { return AkGeometrySetID(this); } + + virtual bool GetGeometryHasBeenSent() const { return GeometryHasBeenSent; } + virtual bool GetGeometryInstanceHasBeenSent() const { return GeometryInstanceHasBeenSent; } + +protected: + void RecalculateHFDamping(); +#if WITH_EDITOR + virtual void BeginDestroy() override; + virtual void HandleObjectsReplaced(const TMap& ReplacementMap); + void RegisterReverbRTPCChangedCallback(); + FDelegateHandle RTPCChangedHandle; + virtual void RegisterAllTextureParamCallbacks() { check(0 && "This function must be overidden"); } + void RegisterTextureParamChangeCallback(FGuid textureID); + void UnregisterTextureParamChangeCallbacks(); + /* Register param changed callbacks for any textures that have not been registered. Called when the AcousticPolys array is updated */ + TMap TextureDelegateHandles; +#endif + + FAkReverbDescriptor* ReverbDescriptor = nullptr; + bool DampingEstimationNeedsUpdate = false; + + virtual bool ShouldSendGeometry() const; + /* Add or update a geometry in Spatial Audio. It is necessary to create at least one geometry instance + * for each geometry that is to be used for diffraction and reflection simulation. See SendGeometryInstanceToWwise(). */ + void SendGeometryToWwise(const AkGeometryParams& params); + /* Add or update an instance of the geometry. A geometry instance is a unique instance of a geometry set with a specified transform (position, rotation and scale) and room association. + * It is necessary to create at least one geometry instance for each geometry set that is to be used for diffraction and reflection simulation. */ + void SendGeometryInstanceToWwise(const FRotator& rotation, const FVector& location, const FVector& scale, const AkRoomID roomID); + /* Remove a geometry and the corresponding instance from Wwise. */ + void RemoveGeometryFromWwise(); + /* Remove a geometry instance from Wwise. */ + void RemoveGeometryInstanceFromWwise(); + +private: +#if WITH_EDITOR + virtual bool ContainsTexture(const FGuid& textureID) + { + check(0 && "This function must be overidden"); + return false; + } +#endif + + float SecondsSinceDampingUpdate = 0.0f; + + bool GeometryHasBeenSent = false; + bool GeometryInstanceHasBeenSent = false; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAmbientSound.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAmbientSound.h new file mode 100644 index 0000000..7288e86 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAmbientSound.h @@ -0,0 +1,111 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkAmbientSound.h: +=============================================================================*/ +#pragma once + +#include "GameFramework/Actor.h" +#include "AkAmbientSound.generated.h" + +/*------------------------------------------------------------------------------------ + AAkAmbientSound +------------------------------------------------------------------------------------*/ +UCLASS(config=Engine, hidecategories=Audio, AutoExpandCategories=AkAmbientSound, BlueprintType) +class AKAUDIO_API AAkAmbientSound : public AActor +{ + GENERATED_BODY() + +public: + AAkAmbientSound(const class FObjectInitializer& ObjectInitializer); + + /** AkAudioEvent to play. Deprecated as UE4.7 integration: Use AkComponent->AkAudioEvent instead */ + UPROPERTY() + class UAkAudioEvent * AkAudioEvent_DEPRECATED = nullptr; + + /** AkComponent to handle playback */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=AkAmbientSound, meta=(ShowOnlyInnerProperties) ) + class UAkComponent* AkComponent = nullptr; + + /** Stop playback if the owner is destroyed */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = AkAmbientSound, SimpleDisplay) + bool StopWhenOwnerIsDestroyed = false; + + /** Automatically post the associated AkAudioEvent on BeginPlay */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = AkAmbientSound, SimpleDisplay) + bool AutoPost = false; + + /* + * Start an Ak ambient sound. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|AkAmbientSound") + void StartAmbientSound(); + + /* + * Stop an Ak ambient sound. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|AkAmbientSound") + void StopAmbientSound(); + + +#if CPP +public: + + /** + * Start the ambience playback + */ + void StartPlaying(); + + /** + * Stop the ambience playback + */ + void StopPlaying(); + + /** + * Is whether this ambient sound currently playing + * + * @return True if ambient sound is currently playing, false if not. + */ + bool IsCurrentlyPlaying(); + + +protected: + /*------------------------------------------------------------------------------------ + AActor interface. + ------------------------------------------------------------------------------------*/ + + virtual void BeginPlay() override; + +#if WITH_EDITOR + /** + * Check for errors + */ + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + + virtual void PostInitializeComponents() override; + virtual void PostLoad() override; + +#endif + +private: + /** used to update status of toggleable level placed ambient sounds on clients */ + bool CurrentlyPlaying; + + FCriticalSection PlayingCriticalSection; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAudioBank.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAudioBank.h new file mode 100644 index 0000000..1b64add --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAudioBank.h @@ -0,0 +1,36 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkAudioType.h" +#include "AkAudioBank.generated.h" + +UCLASS() +class AKAUDIO_API UAkAudioBank : public UAkAudioType +{ + GENERATED_BODY() + +#if WITH_EDITORONLY_DATA + virtual FWwiseObjectInfo* GetInfoMutable() override { return nullptr; } +#endif + +public: + UPROPERTY(meta=(Deprecated)) + bool AutoLoad_DEPRECATED = true; + +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAudioEvent.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAudioEvent.h new file mode 100644 index 0000000..d397fe3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAudioEvent.h @@ -0,0 +1,397 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkAudioType.h" +#include "AkGameplayTypes.h" +#include "Wwise/CookedData/WwiseLocalizedEventCookedData.h" +#include "Wwise/Loaded/WwiseLoadedEvent.h" + +#if WITH_EDITORONLY_DATA +#include "Wwise/Info/WwiseEventInfo.h" +#endif + +#include "AkAudioEvent.generated.h" + +class UAkGameObject; +class UAkGroupValue; +class UAkAuxBus; +class UAkAudioBank; +class UAkTrigger; + + +UCLASS(BlueprintType) +class AKAUDIO_API UAkAudioEvent : public UAkAudioType +{ + GENERATED_BODY() + + // + // Unreal properties + // +public: + UPROPERTY(Transient, VisibleAnywhere, BlueprintReadOnly, Category = "AkAudioEvent") + float MaxAttenuationRadius = .0f; + + /** Whether this event is infinite (looping) or finite (duration parameters are valid) */ + UPROPERTY(Transient, VisibleAnywhere, BlueprintReadOnly, Category = "AkAudioEvent") + bool IsInfinite = false; + + /** Minimum duration */ + UPROPERTY(Transient, VisibleAnywhere, BlueprintReadOnly, Category = "AkAudioEvent") + float MinimumDuration = .0f; + + /** Maximum duration */ + UPROPERTY(Transient, VisibleAnywhere, BlueprintReadOnly, Category = "AkAudioEvent") + float MaximumDuration = .0f; + +#if WITH_EDITORONLY_DATA + UPROPERTY(EditAnywhere, Category = "AkAudioEvent") + FWwiseEventInfo EventInfo; +#endif + + UPROPERTY(Transient, VisibleAnywhere, Category = "AkAudioEvent") + FWwiseLocalizedEventCookedData EventCookedData; + + UPROPERTY(meta = (DeprecatedProperty, DeprecationMessage = "Used for migration")) + UAkAudioBank* RequiredBank_DEPRECATED = nullptr; + +public: + /** + * @brief Posts the Wwise Event on the root component of the specified actor. + * + * @param Actor Actor on which to play the event. This actor gets followed automatically by the Event. If the Actor is left empty, + * the Event will be played as an Ambient sound. + * @param Delegate Function that gets called every time the operation defined by CallbackMask is processed. + * @param CallbackMask Bitmask defining all the operations that will call the Callback. See \ref AkCallbackType. + * @param bStopWhenAttachedObjectDestroyed Specifies whether the sound should stop playing when the owner of the attach to component + * is destroyed. This parameter modifies the AkComponent itself, you can only have one behavior per actor's root component. + * @return The Playing ID returned by the SoundEngine's PostEvent, or AK_INVALID_PLAYING_ID (0) if invalid. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|Actor", meta=(AdvancedDisplay="1", AutoCreateRefTerm = "Delegate")) + int32 PostOnActor(const AActor* Actor, + const FOnAkPostEventCallback& Delegate, + UPARAM(meta = (Bitmask, BitmaskEnum = "/Script/AkAudio.EAkCallbackType")) const int32 CallbackMask, + const bool bStopWhenAttachedObjectDestroyed); + + /** + * @brief Posts the Wwise Event on the specified component. + * + * @param Component Component on which to play the event. + * @param Delegate Function that gets called every time the operation defined by CallbackMask is processed. + * @param CallbackMask Bitmask defining all the operations that will call the Callback. See \ref AkCallbackType. + * @param bStopWhenAttachedObjectDestroyed Specifies whether the sound should stop playing when the owner of the attach to component + * is destroyed. This parameter modifies the AkComponent itself, you can only have one behavior per actor's root component. + * @return The Playing ID returned by the SoundEngine's PostEvent, or AK_INVALID_PLAYING_ID (0) if invalid. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|AkComponent", meta=(AdvancedDisplay="1", AutoCreateRefTerm = "Delegate")) + int32 PostOnComponent(UAkComponent* Component, + const FOnAkPostEventCallback& Delegate, + UPARAM(meta = (Bitmask, BitmaskEnum = "/Script/AkAudio.EAkCallbackType")) const int32 CallbackMask, + const bool bStopWhenAttachedObjectDestroyed); + + /** + * @brief Posts the Wwise Event on the specified game object. + * + * @param GameObject Game object on which to play the event. + * @param Delegate Function that gets called every time the operation defined by CallbackMask is processed. + * @param CallbackMask Bitmask defining all the operations that will call the Callback. See \ref AkCallbackType. + * @return The Playing ID returned by the SoundEngine's PostEvent, or AK_INVALID_PLAYING_ID (0) if invalid. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|AkGameObject", meta=(AdvancedDisplay="1", AutoCreateRefTerm = "Delegate")) + int32 PostOnGameObject(UAkGameObject* GameObject, + const FOnAkPostEventCallback& Delegate, + UPARAM(meta = (Bitmask, BitmaskEnum = "/Script/AkAudio.EAkCallbackType")) const int32 CallbackMask); + + /** + * @brief Posts the Wwise Event on the root component of the specified actor, and waits for the end of the event to continue execution. + * + * Additional calls made while an event is active on a particular actor's root component are ignored. + * + * @param Actor Actor on which to play the event. This actor gets followed automatically by the Event. + * @param bStopWhenAttachedObjectDestroyed Specifies whether the sound should stop playing when the owner of the attach to component is destroyed. + * This parameter modifies the AkComponent itself, you can only have one behavior per actor's root component. + * @return The Playing ID returned by the SoundEngine's PostEvent, or AK_INVALID_PLAYING_ID (0) if invalid. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|Actor", meta = (Latent, LatentInfo = "LatentActionInfo", AdvancedDisplay = "1", bStopWhenAttachedObjectDestroyed="false")) + int32 PostOnActorAndWait(const AActor* Actor, + const bool bStopWhenAttachedObjectDestroyed, + const FLatentActionInfo LatentActionInfo); + + /** + * @brief Posts the Wwise Event on the specified component, and waits for the end of the event to continue execution. + * + * Additional calls made while an event is active on a particular component are ignored. + * + * @param Component component on which to play the event. This component gets followed automatically by the Event. + * @param bStopWhenAttachedObjectDestroyed Specifies whether the sound should stop playing when the owner of the attach to component is destroyed. + * This parameter modifies the AkComponent itself, you can only have one behavior per actor's root component. + * @return The Playing ID returned by the SoundEngine's PostEvent, or AK_INVALID_PLAYING_ID (0) if invalid. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkComponent", meta = (Latent, LatentInfo = "LatentActionInfo", AdvancedDisplay = "1", bStopWhenAttachedObjectDestroyed="false")) + int32 PostOnComponentAndWait(UAkComponent* Component, + const bool bStopWhenAttachedObjectDestroyed, + const FLatentActionInfo LatentActionInfo); + + /** + * @brief Posts the Wwise Event on the specified game object, and waits for the end of the event to continue execution. + * + * Additional calls made while an event is active on a particular game object are ignored. + * + * @param GameObject Game object on which to play the event. This game object gets followed automatically by the Event. + * @return The Playing ID returned by the SoundEngine's PostEvent, or AK_INVALID_PLAYING_ID (0) if invalid. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkGameObject", meta = (Latent, LatentInfo = "LatentActionInfo", AdvancedDisplay = "1", bStopWhenAttachedObjectDestroyed="false")) + int32 PostOnGameObjectAndWait(UAkGameObject* GameObject, + const FLatentActionInfo LatentActionInfo); + + /** + * @brief Posts a Wwise Event at the specified location. + * + * This is a fire and forget sound, created on a temporary Wwise Game Object. Replication is also not handled at this point. + * + * @param Location Location from which to post the Wwise Event. + * @param Orientation Orientation of the event. + * @param Callback Function that gets called every time the operation defined by CallbackMask is processed. + * @param CallbackMask Bitmask defining all the operations that will call the Callback. See \ref AkCallbackType. + * @param WorldContextObject An object having the world we target as context. + * @return The Playing ID returned by the SoundEngine's PostEvent, or AK_INVALID_PLAYING_ID (0) if invalid. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic", meta=(WorldContext="WorldContextObject", AdvancedDisplay = "2")) + int32 PostAtLocation(const FVector Location, + const FRotator Orientation, + const FOnAkPostEventCallback& Callback, + UPARAM(meta = (Bitmask, BitmaskEnum = "/Script/AkAudio.EAkCallbackType")) const int32 CallbackMask, + const UObject* WorldContextObject); + + /** + * @brief Executes action on the different playing IDs from this event that were previously posted on the + * Actor's root component. + * + * @param ActionType What action to do. + * @param Actor The actor that initially got some event posted. + * @param PlayingID Use the return value of a Post Event to act only on this specific instance of an event. + * Use 0 for all the posted operations from this event. + * @param TransitionDuration Transition duration in milliseconds. + * @param FadeCurve The interpolation curve of the transition. + * @return AKRESULT for the operation. AK_Success (0) if successful. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|Actor", meta=(AdvancedDisplay = "2")) + int32 ExecuteAction(const AkActionOnEventType ActionType, + const AActor* Actor, + const int32 PlayingID = 0, + const int32 TransitionDuration = 0, + const EAkCurveInterpolation FadeCurve = EAkCurveInterpolation::Linear); + +public: + /** + * @brief Posts the Wwise Event on the root component of the specified actor. + * + * @param Actor Actor on which to play the event. This actor gets followed automatically by the Event. If the Actor is left empty, + * the Event will be played as an Ambient sound. + * @param Delegate Function that gets called every time the operation defined by CallbackMask is processed. + * This version is useful for Blueprint callbacks. This is mutually exclusive with AkCallback and LatentAction. + * @param Callback Function that gets called every time the operation defined by CallbackMask is processed, + * with the Cookie parameter to provide data. This is mutually exclusive with Delegate and LatentAction. + * @param Cookie Parameter provided for AkCallback to provide data. + * @param CallbackMask Bitmask defining all the operations that will call the Callback. See \ref AkCallbackType. + * @param LatentAction Function called when the posted event is done playing for latent Blueprint operation. + * This is mutually exclusive with Delegate and Callback. + * @param bStopWhenAttachedObjectDestroyed Specifies whether the sound should stop playing when the owner of the attach + * to component is destroyed. This parameter modifies the AkComponent itself, you can only have one behavior per + * actor's root component. + * @param AudioContext Context in which the Event's sounds are played. Only GamePlayAudio sounds are affected by PIE pause/stop. + * @return The Playing ID returned by the SoundEngine's PostEvent, or AK_INVALID_PLAYING_ID (0) if invalid. + */ + AkPlayingID PostOnActor(const AActor* Actor, + const FOnAkPostEventCallback* Delegate, + AkCallbackFunc Callback, + void* Cookie, + const AkCallbackType CallbackMask, + FWaitEndOfEventAction* LatentAction, + const bool bStopWhenAttachedObjectDestroyed, + const EAkAudioContext AudioContext = EAkAudioContext::GameplayAudio); + + /** + * @brief Posts the Wwise Event on the specified component. + * + * @param Component Component on which to play the event. + * @param Delegate Function that gets called every time the operation defined by CallbackMask is processed. + * This version is useful for Blueprint callbacks. This is mutually exclusive with AkCallback and LatentAction. + * @param Callback Function that gets called every time the operation defined by CallbackMask is processed, + * with the Cookie parameter to provide data. This is mutually exclusive with Delegate and LatentAction. + * @param Cookie Parameter provided for AkCallback to provide data. + * @param CallbackMask Bitmask defining all the operations that will call the Callback. See \ref AkCallbackType. + * @param LatentAction Function called when the posted event is done playing for latent Blueprint operation. + * This is mutually exclusive with Delegate and Callback. + * @param bStopWhenAttachedObjectDestroyed Specifies whether the sound should stop playing when the owner of the attach + * to component is destroyed. This parameter modifies the AkComponent itself, you can only have one behavior per + * actor's root component. + * @param AudioContext Context in which the Event's sounds are played. Only GamePlayAudio sounds are affected by PIE pause/stop. + * @return The Playing ID returned by the SoundEngine's PostEvent, or AK_INVALID_PLAYING_ID (0) if invalid. + */ + AkPlayingID PostOnComponent(UAkComponent* Component, + const FOnAkPostEventCallback* Delegate, + AkCallbackFunc Callback, + void* Cookie, + const AkCallbackType CallbackMask, + FWaitEndOfEventAction* LatentAction, + const bool bStopWhenAttachedObjectDestroyed, + const EAkAudioContext AudioContext = EAkAudioContext::GameplayAudio); + + /** + * @brief Posts a Wwise Event at the specified location. + * + * This is a fire and forget sound, created on a temporary Wwise Game Object. Replication is also not handled at this point. + * + * @param Location Location from which to post the Wwise Event. + * @param Orientation Orientation of the event. + * @param World The world that defines the Location's coordinates. + * @param Delegate Function that gets called every time the operation defined by CallbackMask is processed. + * This version is useful for Blueprint callbacks. This is mutually exclusive with AkCallback and LatentAction. + * @param Callback Function that gets called every time the operation defined by CallbackMask is processed, + * with the Cookie parameter to provide data. This is mutually exclusive with Delegate and LatentAction. + * @param Cookie Parameter provided for AkCallback to provide data. + * @param CallbackMask Bitmask defining all the operations that will call the Callback. See \ref AkCallbackType. + * @param LatentAction Function called when the posted event is done playing for latent Blueprint operation. + * This is mutually exclusive with Delegate and Callback. + * @param AudioContext Context in which the Event's sounds are played. Only GamePlayAudio sounds are affected by PIE pause/stop. + * @return The Playing ID returned by the SoundEngine's PostEvent, or AK_INVALID_PLAYING_ID (0) if invalid. + */ + AkPlayingID PostAtLocation(const FVector& Location, const FRotator& Orientation, const UWorld* World, + const FOnAkPostEventCallback* Delegate, + AkCallbackFunc Callback, + void* Cookie, + const AkCallbackType CallbackMask, + FWaitEndOfEventAction* LatentAction, + const EAkAudioContext AudioContext = EAkAudioContext::GameplayAudio); + + /** + * @brief Posts a Wwise Event onto a dummy object. + * + * @param Delegate Function that gets called every time the operation defined by CallbackMask is processed. + * This version is useful for Blueprint callbacks. This is mutually exclusive with AkCallback and LatentAction. + * @param Callback Function that gets called every time the operation defined by CallbackMask is processed, + * with the Cookie parameter to provide data. This is mutually exclusive with Delegate and LatentAction. + * @param Cookie Parameter provided for AkCallback to provide data. + * @param CallbackMask Bitmask defining all the operations that will call the Callback. See \ref AkCallbackType. + * @param LatentAction Function called when the posted event is done playing for latent Blueprint operation. + * This is mutually exclusive with Delegate and Callback. + * @param AudioContext Context in which the Event's sounds are played. Only GamePlayAudio sounds are affected by PIE pause/stop. + * @return The Playing ID returned by the SoundEngine's PostEvent, or AK_INVALID_PLAYING_ID (0) if invalid. + */ + AkPlayingID PostAmbient( + const FOnAkPostEventCallback* Delegate, + AkCallbackFunc Callback, + void* Cookie, + const AkCallbackType CallbackMask, + FWaitEndOfEventAction* LatentAction, + const EAkAudioContext AudioContext = EAkAudioContext::GameplayAudio); + + /** + * @brief Posts the Wwise Event on the specified game object. + * + * @param GameObject Game object on which to play the event. + * @param Delegate Function that gets called every time the operation defined by CallbackMask is processed. + * This version is useful for Blueprint callbacks. This is mutually exclusive with AkCallback and LatentAction. + * @param Callback Function that gets called every time the operation defined by CallbackMask is processed, + * with the Cookie parameter to provide data. This is mutually exclusive with Delegate and LatentAction. + * @param Cookie Parameter provided for AkCallback to provide data. + * @param CallbackMask Bitmask defining all the operations that will call the Callback. See \ref AkCallbackType. + * @param LatentAction Function called when the posted event is done playing for latent Blueprint operation. + * This is mutually exclusive with Delegate and Callback. + * @param AudioContext Context in which the Event's sounds are played. Only GamePlayAudio sounds are affected by PIE pause/stop. + * @return The Playing ID returned by the SoundEngine's PostEvent, or AK_INVALID_PLAYING_ID (0) if invalid. + */ + AkPlayingID PostOnGameObject(UAkGameObject* GameObject, + const FOnAkPostEventCallback* Delegate, + AkCallbackFunc Callback, + void* Cookie, + const AkCallbackType CallbackMask, + FWaitEndOfEventAction* LatentAction, + const EAkAudioContext AudioContext = EAkAudioContext::GameplayAudio); + + /** + * @brief Posts the Wwise Event on the specified game object ID. + * + * @param GameObjectID Game object's ID on which to play the event. + * @param Delegate Function that gets called every time the operation defined by CallbackMask is processed. + * This version is useful for Blueprint callbacks. This is mutually exclusive with AkCallback and LatentAction. + * @param Callback Function that gets called every time the operation defined by CallbackMask is processed, + * with the Cookie parameter to provide data. This is mutually exclusive with Delegate and LatentAction. + * @param Cookie Parameter provided for AkCallback to provide data. + * @param CallbackMask Bitmask defining all the operations that will call the Callback. See \ref AkCallbackType. + * @param LatentAction Function called when the posted event is done playing for latent Blueprint operation. + * This is mutually exclusive with Delegate and Callback. + * @param AudioContext Context in which the Event's sounds are played. Only GamePlayAudio sounds are affected by PIE pause/stop. + * @return The Playing ID returned by the SoundEngine's PostEvent, or AK_INVALID_PLAYING_ID (0) if invalid. + */ + AkPlayingID PostOnGameObjectID(const AkGameObjectID GameObjectID, + const FOnAkPostEventCallback* Delegate, + AkCallbackFunc Callback, + void* Cookie, + const AkCallbackType CallbackMask, + FWaitEndOfEventAction* LatentAction, + const EAkAudioContext AudioContext = EAkAudioContext::GameplayAudio); + +private: + template + AkPlayingID PostEvent(const AkGameObjectID GameObjectID, + FCreateCallbackPackage&& CreateCallbackPackage, + const EAkAudioContext AudioContext + ); + +public: + void Serialize(FArchive& Ar) override; + void BeginDestroy() override; + + virtual void LoadData() override {LoadEventData();} + virtual void UnloadData(bool bAsync = false) override {UnloadEventData(bAsync);} + virtual AkUInt32 GetShortID() const override {return EventCookedData.EventId;} + bool IsDataFullyLoaded() const; + bool IsLoaded() const; + +#if WITH_EDITOR + // Allow for content browser preview to work, even if asset is not auto-loaded. + // This method will load the content, and register to BeginPIE. When a PIE session + // begins, data will be unloaded, allowing to replicate in-game behaviour more + // closely. + void LoadEventDataForContentBrowserPreview(); + +private: + void OnBeginPIE(const bool bIsSimulating); + FDelegateHandle OnBeginPIEDelegateHandle; +#endif +#if WITH_EDITORONLY_DATA +public: + virtual void FillInfo() override; + virtual void FillMetadata(FWwiseProjectDatabase* ProjectDatabase) override; + void CookAdditionalFilesOverride(const TCHAR* PackageFilename, const ITargetPlatform* TargetPlatform, + TFunctionRef WriteAdditionalFile) override; + virtual FWwiseObjectInfo* GetInfoMutable() override {return &EventInfo;} + virtual FWwiseObjectInfo GetInfo() const override{return EventInfo;} + virtual bool ObjectIsInSoundBanks() override; +#endif + + TArray GetAllExternalSources() const; + +private: + void LoadEventData(); + void UnloadEventData(bool bAsync); + FWwiseLoadedEvent LoadedEvent; +}; + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAudioInputComponent.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAudioInputComponent.h new file mode 100644 index 0000000..44f8503 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAudioInputComponent.h @@ -0,0 +1,64 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= +UAkAudioInputComponent.h: +=============================================================================*/ + +#pragma once + +#include "AkAudioInputManager.h" +#include "AkInclude.h" +#include "AkComponent.h" +#include "AkAudioInputComponent.generated.h" + + +/*------------------------------------------------------------------------------------ +UAkAudioInputComponent +------------------------------------------------------------------------------------*/ +UCLASS(ClassGroup = Audiokinetic, abstract, BlueprintType, hidecategories = (Transform, Rendering, Mobility, LOD, Component, Activation), meta = (BlueprintSpawnableComponent)) +class AKAUDIO_API UAkAudioInputComponent : public UAkComponent +{ + GENERATED_BODY() + +public: + UAkAudioInputComponent(const class FObjectInitializer& ObjectInitializer); + + /** + * Posts this component's AkAudioEvent to Wwise along with associated AudioSamples callback and AudioFormat callback, using this component as the game object source + * + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|AkAudioInputComponent") + virtual int32 PostAssociatedAudioInputEvent(); + +protected: + /** This is called after the GameObject that owns this component is unregistered from the Wwise sound engine. */ + virtual void PostUnregisterGameObject() override; + /** The audio callback. This will be called continuously by the Wwise sound engine, + * and is used to provide the sound engine with audio samples. If this function returns false, the audio + * input event will be stopped and the functino will stop being called. + */ + virtual bool FillSamplesBuffer(uint32 NumChannels, uint32 NumSamples, float** BufferToFill) PURE_VIRTUAL(AkAudioInputComponent::FillSamplesBuffer, return false;); + /** This callback is used to provide the Wwise sound engine with the required audio format. */ + virtual void GetChannelConfig(AkAudioFormat& AudioFormat) PURE_VIRTUAL(UAkAudioInputComponent::GetChannelConfig,); + + TArray CurrentlyPlayingIDs; + +private: + FAkGlobalAudioInputDelegate AudioInputDelegate; + FAkGlobalAudioFormatDelegate AudioFormatDelegate; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAudioInputManager.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAudioInputManager.h new file mode 100644 index 0000000..77779a3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAudioInputManager.h @@ -0,0 +1,166 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkInclude.h" +#include "AkAudioDevice.h" +#include "Templates/Function.h" + +/*------------------------------------------------------------------------------------ +AkAudioInput Delegates +------------------------------------------------------------------------------------*/ + +DECLARE_DELEGATE_RetVal_ThreeParams(bool, FAkGlobalAudioInputDelegate, uint32, uint32, float**); +DECLARE_DELEGATE_OneParam(FAkGlobalAudioFormatDelegate, AkAudioFormat&); + +/*------------------------------------------------------------------------------------ +FAkAudioInputManager +------------------------------------------------------------------------------------*/ + +class AKAUDIO_API FAkAudioInputManager +{ +public: + + /** + * Post an Input event to Wwise SoundEngine linked to an actor + * + * @param Event Event to post + * @param Actor Actor on which to play the event + * @param AudioSamplesDelegate Callback that fills the audio samples buffer + * @param AudioFormatDelegate Callback that sets the audio format + * @param AudioContext Context where this input is used (Editor, Player, or other) + * @return ID assigned by Wwise SoundEngine + */ + static AkPlayingID PostAudioInputEvent( + UAkAudioEvent* Event, + AActor* Actor, + FAkGlobalAudioInputDelegate AudioSamplesDelegate, + FAkGlobalAudioFormatDelegate AudioFormatDelegate, + EAkAudioContext AudioContext = EAkAudioContext::Foreign + ); + + /** + * Post an Input event to Wwise SoundEngine linked to a Component + * + * @param Event Event to post + * @param Component Component on which to play the event + * @param AudioSamplesDelegate Callback that fills the audio samples buffer + * @param AudioFormatDelegate Callback that sets the audio format + * @param AudioContext Context where this input is used (Editor, Player, or other) + * @return ID assigned by Wwise SoundEngine + */ + static AkPlayingID PostAudioInputEvent( + UAkAudioEvent* Event, + UAkComponent* Component, + FAkGlobalAudioInputDelegate AudioSamplesDelegate, + FAkGlobalAudioFormatDelegate AudioFormatDelegate, + EAkAudioContext AudioContext = EAkAudioContext::Foreign + ); + + /** + * Post an Input event to Wwise SoundEngine linked to a GameObject + * + * @param Event Event to post + * @param GameObject GameObject on which to play the event + * @param AudioSamplesDelegate Callback that fills the audio samples buffer + * @param AudioFormatDelegate Callback that sets the audio format + * @param AudioContext Context where this input is used (Editor, Player, or other) + * @return ID assigned by Wwise SoundEngine + */ + static AkPlayingID PostAudioInputEvent( + UAkAudioEvent* Event, + AkGameObjectID GameObject, + FAkGlobalAudioInputDelegate AudioSamplesDelegate, + FAkGlobalAudioFormatDelegate AudioFormatDelegate, + EAkAudioContext AudioContext = EAkAudioContext::Foreign + ); + + /** + * Post an Input event to Wwise SoundEngine not linked to any GameObject + * + * @param Event Event to post + * @param AudioSamplesDelegate Callback that fills the audio samples buffer + * @param AudioFormatDelegate Callback that sets the audio format + * @param AudioContext Context where this input is used (Editor, Player, or other) + * @return ID assigned by Wwise SoundEngine + */ + static AkPlayingID PostAudioInputEvent( + UAkAudioEvent* Event, + FAkGlobalAudioInputDelegate AudioSamplesDelegate, + FAkGlobalAudioFormatDelegate AudioFormatDelegate, + EAkAudioContext AudioContext = EAkAudioContext::Foreign + ); + + /** + * Post an event to ak soundengine + * + * @param AkEvent Event UObject, if null EventName is used to determine the ShortID + * @param EventName Name of the event to post + * @param Actor Actor on which to play the event + * @param AudioSamplesDelegate Callback that fills the audio samples buffer + * @param AudioFormatDelegate Callback that sets the audio format + * @return ID assigned by ak soundengine + * + * @deprecated Use a PostAudioInputEvent without EventName. + */ + static AkPlayingID PostAudioInputEvent( + class UAkAudioEvent* AkEvent, + const FString& EventName, + AActor * Actor, + FAkGlobalAudioInputDelegate AudioSamplesDelegate, + FAkGlobalAudioFormatDelegate AudioFormatDelegate + ); + + /** + * Post an event to ak soundengine + * + * @param AkEvent Event UObject, if null EventName is used to determine the ShortID + * @param EventName Name of the event to post + * @param Component AkComponent on which to play the event + * @param AudioSamplesDelegate Callback that fills the audio samples buffer + * @param AudioFormatDelegate Callback that sets the audio format + * @return ID assigned by ak soundengine + * + * @deprecated Use a PostAudioInputEvent without EventName. + */ + static AkPlayingID PostAudioInputEvent( + class UAkAudioEvent* AkEvent, + const FString& EventName, + UAkComponent* Component, + FAkGlobalAudioInputDelegate AudioSamplesDelegate, + FAkGlobalAudioFormatDelegate AudioFormatDelegate + ); + + /** + * Post an event to ak soundengine by name + * + * @param EventName Name of the event to post + * @param GameObject AkGameObject on which to play the event + * @param AudioSamplesDelegate Callback that fills the audio samples buffer + * @param AudioFormatDelegate Callback that sets the audio format + * @return ID assigned by ak soundengine + * + * @deprecated Use a PostAudioInputEvent without EventName. + */ + static AkPlayingID PostAudioInputEvent( + const FString& EventName, + AkGameObjectID GameObject, + FAkGlobalAudioInputDelegate AudioSamplesDelegate, + FAkGlobalAudioFormatDelegate AudioFormatDelegate + ); +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAudioStyle.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAudioStyle.h new file mode 100644 index 0000000..9f2e2b8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAudioStyle.h @@ -0,0 +1,47 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "WaapiPicker/WwiseTreeItem.h" +#include "Materials/Material.h" + +/** +* Implements the visual style of Wwise Browser. +*/ +class AKAUDIO_API FAkAudioStyle +{ +public: + static const ISlateStyle& Get(); + static void Initialize(); + static void Shutdown(); + + static void DisplayEditorMessage(const FText& messageText, EWwiseItemType::Type wwiseItemType = EWwiseItemType::Type::None, float duration = 1.5f); + + static FName GetStyleSetName(); + + static const FSlateBrush* GetWwiseIcon(); + static const FSlateBrush* GetBrush(EWwiseItemType::Type ItemType); + static const FSlateBrush* GetBrush(FName PropertyName, const ANSICHAR* Specifier = NULL); + static const FSlateFontInfo GetFontStyle(FName PropertyName, const ANSICHAR* Specifier = NULL); + static UMaterial* GetAkForegroundTextMaterial(); + /** returns a color from the WwiseUnrealColorPalette (taken from the dark theme in the Wwise Authoring tool). Use a colorIndex of -1 to use the 'default' color. */ + static FLinearColor GetWwiseObjectColor(int colorIndex); +private: + static TSharedPtr< class FSlateStyleSet > StyleInstance; + static UMaterial* TextMaterial; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAudioType.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAudioType.h new file mode 100644 index 0000000..ea20400 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAudioType.h @@ -0,0 +1,128 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + + +#include "AkInclude.h" +#include "UObject/Object.h" +#include "Wwise/Info/WwiseObjectInfo.h" +#include "Wwise/WwiseResourceLoaderFuture.h" + +#include "AkAudioType.generated.h" + +class FWwiseProjectDatabase; +class FWwiseAnyRef; +UCLASS(Abstract) +class AKAUDIO_API UAkAudioType : public UObject +{ + GENERATED_BODY() + +public: + virtual ~UAkAudioType() override; + + ///< When true, SoundBanks and medias associated with this asset will be loaded in the Wwise SoundEngine when Unreal loads this asset. + UPROPERTY(EditAnywhere, Category = "AkAudioType|Behaviour") + bool bAutoLoad = true; + +// Deprecated ID properties used in migration +#if WITH_EDITORONLY_DATA + UPROPERTY(meta=(Deprecated)) + FGuid ID_DEPRECATED; + + UPROPERTY(meta=(Deprecated)) + uint32 ShortID_DEPRECATED = 0; +#endif + + UPROPERTY(EditAnywhere, Category = "AkAudioType") + TArray UserData; + +public: + void Serialize(FArchive& Ar) override; + void PostLoad() override; + void BeginDestroy() override; + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkAudioType") + virtual void LoadData() {} + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkAudioType") + virtual void UnloadData(bool bAsync = false) {} + + void LogSerializationState(const FArchive& Ar); + virtual AkUInt32 GetShortID() const; + + bool IsPostLoadThreadSafe() const override { return true; } + bool IsDestructionThreadSafe() const override { return true; } + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkAudioType") + int32 GetWwiseShortID() const { return GetShortID();} + + template + T* GetUserData() + { + for (auto Entry : UserData) + { + if (Entry && Entry->GetClass()->IsChildOf(T::StaticClass())) + { + return Entry; + } + } + + return nullptr; + } + +#if WITH_EDITORONLY_DATA + virtual FWwiseObjectInfo* GetInfoMutable(); + virtual FWwiseObjectInfo GetInfo() const {return {};} + virtual FGuid GetWwiseGuid() const { return GetInfo().WwiseGuid; } + virtual FName GetWwiseName() const { return GetInfo().WwiseName; } + virtual FName GetWwiseGroupName() { return {}; } + virtual FName GetAssetDefaultName(); + virtual FName GetAssetDefaultPackagePath(); + virtual bool ObjectIsInSoundBanks() { return false; } + virtual void BeginCacheForCookedPlatformData(const ITargetPlatform* TargetPlatform) override; + + // Checks whether the metadata for this UAkAudioType matches what is in the Project Database + virtual bool IsAssetOutOfDate(const FWwiseAnyRef& CurrentWwiseRef); + virtual void FillInfo(const FWwiseAnyRef& CurrentWwiseRef); + virtual void FillInfo() {} + virtual void FillMetadata(FWwiseProjectDatabase* ProjectDatabase) {} + virtual void CheckWwiseObjectInfo(); + virtual void MigrateWwiseObjectInfo(); + void WaitForResourceUnloaded(); + + template + InfoType GetValidatedInfo(const InfoType& InInfo) + { + InfoType TempInfo(InInfo); + ValidateShortID(TempInfo); + return TempInfo; + } + virtual void ValidateShortID(FWwiseObjectInfo& WwiseInfo) const; +#endif + +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; + virtual void GetAssetRegistryTags(TArray& OutTags) const override; +#endif + +protected: + FWwiseResourceUnloadFuture ResourceUnload; + + static bool CanLoadObjects(); + void LoadOnceSoundEngineReady(); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAuxBus.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAuxBus.h new file mode 100644 index 0000000..2a23426 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkAuxBus.h @@ -0,0 +1,73 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkAudioType.h" + +#include "Wwise/CookedData/WwiseLocalizedAuxBusCookedData.h" +#include "Wwise/Info/WwiseObjectInfo.h" +#include "Wwise/Loaded/WwiseLoadedAuxBus.h" + +#include "AkAudioBank.h" + +#include "AkAuxBus.generated.h" + +class UAkAudioBank; + +UCLASS(hidecategories=(Advanced, Attachment, Volume), BlueprintType) +class AKAUDIO_API UAkAuxBus : public UAkAudioType +{ + GENERATED_BODY() + +public: + +#if WITH_EDITORONLY_DATA + UPROPERTY(EditAnywhere, Category = "AkAuxBus") + FWwiseObjectInfo AuxBusInfo; +#endif + + UPROPERTY(Transient, EditAnywhere, Category = "AkAuxBus") + FWwiseLocalizedAuxBusCookedData AuxBusCookedData; + + UPROPERTY() + UAkAudioBank* RequiredBank_DEPRECATED = nullptr; + +public: + void Serialize(FArchive& Ar) override; + + virtual void LoadData() override {LoadAuxBus();} + virtual void UnloadData(bool bAsync = false) override {UnloadAuxBus(bAsync);} + virtual AkUInt32 GetShortID() const override {return AuxBusCookedData.AuxBusId;} + +#if WITH_EDITORONLY_DATA + virtual FWwiseObjectInfo* GetInfoMutable() override {return &AuxBusInfo;} + virtual FWwiseObjectInfo GetInfo() const override{return AuxBusInfo;} + virtual bool ObjectIsInSoundBanks() override; +#endif + +private: + void LoadAuxBus(); + void UnloadAuxBus(bool bAsync); + FWwiseLoadedAuxBus LoadedAuxBus; + +#if WITH_EDITORONLY_DATA + virtual void CookAdditionalFilesOverride(const TCHAR* PackageFilename, const ITargetPlatform* TargetPlatform, + TFunctionRef WriteAdditionalFile) override; + virtual void FillInfo() override; +#endif +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkCallbackInfoPool.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkCallbackInfoPool.h new file mode 100644 index 0000000..dad16ee --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkCallbackInfoPool.h @@ -0,0 +1,42 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Engine/EngineTypes.h" +#include "CoreUObject/Public/UObject/StrongObjectPtr.h" + +class UAkCallbackInfo; + +class AkCallbackInfoPool final +{ +public: + template + CallbackType* Acquire() + { + return static_cast(InternalAcquire(CallbackType::StaticClass())); + } + + void Release(UAkCallbackInfo* instance); + +private: + UAkCallbackInfo* InternalAcquire(UClass* type); + +private: + TMap> Pool; + TArray> gcStorage; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkComponent.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkComponent.h new file mode 100644 index 0000000..7421404 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkComponent.h @@ -0,0 +1,488 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkComponent.h: +=============================================================================*/ + +#pragma once + +#include "Runtime/Launch/Resources/Version.h" +#include "AkInclude.h" +#include "AkGameplayTypes.h" +#include "AkSettings.h" // for EAkCollisionChannel +#include "Components/SceneComponent.h" +#include "ObstructionAndOcclusionService/AkComponentObstructionAndOcclusionService.h" +#include "AkGameObject.h" +#include "AkComponent.generated.h" + + +UENUM(Meta = (Bitflags)) +enum class EReflectionFilterBits +{ + Wall, + Ceiling, + Floor +}; + +// PostEvent functions need to return the PlayingID (uint32), but Blueprints only work with int32. +// Make sure AkPlayingID is always 32 bits, or else we're gonna have a bad time. +static_assert(sizeof(AkPlayingID) == sizeof(int32), "AkPlayingID is not 32 bits anymore. Change return value of PostEvent functions!"); + +struct AkReverbFadeControl +{ +public: + uint32 AuxBusId; + bool bIsFadingOut; + void* FadeControlUniqueId; + +private: + float CurrentControlValue; + float TargetControlValue; + float FadeRate; + float Priority; + +public: + AkReverbFadeControl(const class UAkLateReverbComponent& LateReverbComponent); + void UpdateValues(const class UAkLateReverbComponent& LateReverbComponent); + bool Update(float DeltaTime); + void ForceCurrentToTargetValue() { CurrentControlValue = TargetControlValue; } + AkAuxSendValue ToAkAuxSendValue() const; + + static bool Prioritize(const AkReverbFadeControl& A, const AkReverbFadeControl& B); +}; + +/*------------------------------------------------------------------------------------ + UAkComponent +------------------------------------------------------------------------------------*/ +UCLASS(ClassGroup=Audiokinetic, BlueprintType, Blueprintable, hidecategories=(Transform,Rendering,Mobility,LOD,Component,Activation), AutoExpandCategories=AkComponent, meta=(BlueprintSpawnableComponent)) +class AKAUDIO_API UAkComponent: public UAkGameObject +{ + GENERATED_BODY() + +public: + UAkComponent(const class FObjectInitializer& ObjectInitializer); + + UPROPERTY() + bool bUseSpatialAudio_DEPRECATED = false; + + int32 ReflectionFilter_DEPRECATED; + + /** + The line trace channel to use when doing line-of-sight traces for occlusion calculations. When set to 'Use Integration Settings Default', the value will be taken from the DefaultOcclusionCollisionChannel in the Wwise Integration Settings. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AkComponent|Occlusion") + TEnumAsByte OcclusionCollisionChannel = { EAkCollisionChannel::EAKCC_UseIntegrationSettingsDefault }; + + UFUNCTION(BlueprintCallable, Category="AkComponent|Occlusion") + ECollisionChannel GetOcclusionCollisionChannel(); + + /**Enable spot reflectors for this Ak Component **/ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AkComponent|Spatial Audio") + bool EnableSpotReflectors = false; + + /** + * Define an outer radius around each sound position to simulate a radial sound source. + * If the listener is outside the outer radius, the spread is defined by the area that the sphere takes in the listener field of view. + * When the listener intersects the outer radius, the spread is exactly 50%. When the listener is in between the inner and outer radius, the spread interpolates linearly from 50% to 100%. + */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AkComponent|Spatial Audio|Radial Emitter", meta = (ClampMin = 0.0f) ) + float outerRadius = .0f; + + /** + * Define an inner radius around each sound position to simulate a radial sound source. + * If the listener is inside the inner radius, the spread is 100%. + */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AkComponent|Spatial Audio|Radial Emitter", meta = (ClampMin = 0.0f)) + float innerRadius = .0f; + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkComponent") + void SetGameObjectRadius(float in_outerRadius, float in_innerRadius); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkComponent") + void SetEnableSpotReflectors(bool in_enable); + +private: + /** + * Send to an Auxiliary Bus containing the Wwise Reflect plugin for early reflections rendering. + * Note that the Wwise Auxiliary Bus for early reflections can also be set per-sound in the Sound Property Editor in the Wwise Authoring tool. + * Setting a value here will apply only to sounds playing on the AK Component that do not have an Auxiliary Bus set in the Wwise Authoring tool. + */ + UPROPERTY(EditAnywhere, Category = "AkComponent|Spatial Audio|Reflect") + class UAkAuxBus * EarlyReflectionAuxBus = nullptr; + + /** + * Send to an Auxiliary Bus containing the Wwise Reflect plugin for early reflections rendering. + * Note that the Wwise Auxiliary Bus for early reflections can also be set per-sound in the Sound Property Editor in the Wwise Authoring tool. + * Setting a value here will apply only to sounds playing on the AK Component that do not have an Auxiliary Bus set in the Wwise Authoring tool. + */ + UPROPERTY(EditAnywhere, Category = "AkComponent|Spatial Audio|Reflect") + FString EarlyReflectionAuxBusName; + + /** As of 2019.2, the Reflection Order is set in the Spatial Audio Initialization Settings in Project Settings */ + UPROPERTY(VisibleAnywhere, Category = "AkComponent|Spatial Audio|Reflect (DEPRECATED)", meta = (ClampMin = "0", ClampMax = "4")) + int EarlyReflectionOrder = 0; + + /** + * Set the send volume for the early reflections Auxiliary Bus. + * The send volume applied to this AK Component will be applied additively to the Auxiliary Send volume defined per-sound in the Wwise Authoring tool. + */ + UPROPERTY(EditAnywhere, Category = "AkComponent|Spatial Audio|Reflect", meta = (ClampMin = "0.0", ClampMax = "1.0")) + float EarlyReflectionBusSendGain = .0f; + + /** As of 2019.2, the Reflection Max Path Length is set by the sound's Attenuation Max Distance value in the Authoring */ + UPROPERTY(VisibleAnywhere, Category = "AkComponent|Spatial Audio|Reflect (DEPRECATED)", meta = (ClampMin = "0.0")) + float EarlyReflectionMaxPathLength = .0f; + + /** As of 2019.2, the Room Reverb Aux Bus Gain is set by the Game-Defined Auxiliary Sends Volume in the Sound Property Editor in the Authoring */ + UPROPERTY(VisibleAnywhere, Category = "AkComponent|Spatial Audio|Room (DEPRECATED)", meta = (ClampMin = "0.0", ClampMax = "1.0")) + float roomReverbAuxBusGain = .0f; + + /** As of 2019.2, diffraction is enabled in the Sound Property Editor in the Authoring */ + UPROPERTY(VisibleAnywhere, Category = "AkComponent|Spatial Audio|Geometric Diffraction (DEPRECATED)", meta = (ClampMin = "0")) + int diffractionMaxEdges = .0f; + + /** As of 2019.2, diffraction is enabled in the Sound Property Editor in the Authoring */ + UPROPERTY(VisibleAnywhere, Category = "AkComponent|Spatial Audio|Geometric Diffraction (DEPRECATED)", meta = (ClampMin = "0")) + int diffractionMaxPaths = .0f; + + /** As of 2019.2, diffraction is enabled in the Sound Property Editor in the Authoring */ + UPROPERTY(VisibleAnywhere, Category = "AkComponent|Spatial Audio|Geometric Diffraction (DEPRECATED)", meta = (ClampMin = "0.0")) + float diffractionMaxPathLength = .0f; + +public: + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AkComponent|Spatial Audio|Debug Draw") + bool DrawFirstOrderReflections = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AkComponent|Spatial Audio|Debug Draw") + bool DrawSecondOrderReflections = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AkComponent|Spatial Audio|Debug Draw") + bool DrawHigherOrderReflections = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AkComponent|Spatial Audio|Debug Draw") + bool DrawDiffraction = false; + + /** Stop sound when owner is destroyed? */ + UPROPERTY() + bool StopWhenOwnerDestroyed = false; + + /** + * Posts this component's AkAudioEvent to Wwise, using this component as the game object source, and wait until the event is + * done playing to continue execution. Extra calls while the event is playing are ignored. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkComponent", meta = (AdvancedDisplay = "0", Latent, LatentInfo = "LatentInfo")) + int32 PostAssociatedAkEventAndWaitForEnd(FLatentActionInfo LatentInfo); + + /** + * @warning This function is deprecated. Use \ref PostAssociatedAkEventAndWaitForEnd. + * Async operations are deprecated. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkComponent", meta = (AdvancedDisplay = "1", Latent, LatentInfo = "LatentInfo", DeprecatedFunction, DeprecationMessage = "Use \"PostAssociatedAkEventAndWaitForEnd\".")) + void PostAssociatedAkEventAndWaitForEndAsync(int32& PlayingID, FLatentActionInfo LatentInfo); + + /** + * Posts an event to Wwise, using this component as the game object source, and wait until the event is + * done playing to continue execution. Extra calls while the event is playing are ignored. + * + * @params in_EventName Deprecated: You should ensure AkEvent is valid. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkComponent", meta = (AdvancedDisplay = "1", Latent, LatentInfo = "LatentInfo")) + int32 PostAkEventAndWaitForEnd( + class UAkAudioEvent * AkEvent, + const FString& in_EventName, + FLatentActionInfo LatentInfo + ); + + /** + * Posts an event to Wwise, using this component as the game object source, and wait until the event is + * done playing to continue execution. Extra calls while the event is playing are ignored. + * + * @warning This function is deprecated. Use \ref PostAkEventAndWaitForEnd. + * Async operations are deprecated. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkComponent", meta = (AdvancedDisplay = "2", Latent, LatentInfo = "LatentInfo", DeprecatedFunction, DeprecationMessage = "Use \"PostAkEventAndWaitForEnd\".")) + void PostAkEventAndWaitForEndAsync( + class UAkAudioEvent* AkEvent, + int32& PlayingID, + FLatentActionInfo LatentInfo + ); + + int32 PostAkEvent(UAkAudioEvent* AkEvent, int32 CallbackMask, const FOnAkPostEventCallback& PostEventCallback, const FString& InEventName) override; + AkPlayingID PostAkEvent(UAkAudioEvent* AkEvent, AkUInt32 Flags = 0, AkCallbackFunc UserCallback = nullptr, void* UserCookie = nullptr) override; + + /** + * @warning Using EventName in this function is deprecated. Use \ref PostAkEvent. + */ + AK_DEPRECATED(2022.1, "Use PostAkEvent.") + AkPlayingID PostAkEventByNameWithDelegate(UAkAudioEvent* AkEvent, const FString& in_EventName, int32 CallbackMask, const FOnAkPostEventCallback& PostEventCallback) override; + + /** + * @warning This function is deprecated. You are expected to use an UAkAudioEvent. Use \ref PostAkEvent. + */ + AK_DEPRECATED(2022.1, "Use PostAkEvent.") + AkPlayingID PostAkEventByIdWithCallback(const AkUInt32 EventShortID, AkUInt32 Flags = 0, AkCallbackFunc UserCallback = NULL, void * UserCookie = NULL, const TArray& ExternalSources = TArray()); + + /** + * Posts a trigger to wwise, using this component as the game object source + * + * @param Trigger The name of the trigger + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|AkComponent", meta = (AdvancedDisplay = "1")) + void PostTrigger(class UAkTrigger const* TriggerValue, FString Trigger); + + /** + * Sets a switch group in wwise, using this component as the game object source + * + * @param SwitchGroup The name of the switch group + * @param SwitchState The new state of the switch + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|AkComponent", meta = (AdvancedDisplay = "1")) + void SetSwitch(class UAkSwitchValue const* SwitchValue, FString SwitchGroup, FString SwitchState); + + /** + * Sets whether or not to stop sounds when the component's owner is destroyed + * + * @param bStopWhenOwnerDestroyed Whether or not to stop sounds when the component's owner is destroyed + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|AkComponent") + void SetStopWhenOwnerDestroyed( bool bStopWhenOwnerDestroyed ); + + /** + * Set a game object's listeners + * + * @param Listeners The array of components that listen to this component + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|AkComponent") + void SetListeners( const TArray& Listeners ); + + // Reverb volumes functions + + /** + * Set UseReverbVolumes flag. Set value to true to use reverb volumes on this component. + * + * @param inUseReverbVolumes Whether to use reverb volumes or not. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|AkComponent", meta = (DeprecatedFunction, DeprecationMessage = "Use the \"UseReverbVolume\" property", ScriptName="DEPRECATED_UseReverbVolumes")) + void UseReverbVolumes(bool inUseReverbVolumes); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkComponent", meta = (AdvancedDisplay = "5", DeprecatedFunction, DeprecationMessage = "This function is deprecated and will be removed in future releases.")) + void UseEarlyReflections(class UAkAuxBus* AuxBus, int Order = 1, float BusSendGain = 1.f, float MaxPathLength = 100000.f, bool SpotReflectors = false, const FString& AuxBusName = FString("")); + + + /** + * Set the early reflections aux bus for this AK Component. + * Geometrical reflection calculation inside spatial audio is enabled for a game object if any sound playing on the game object has a valid early reflections aux bus specified in the authoring tool, + * or if an aux bus is specified via SetEarlyReflectionsAuxSend. + * The AuxBusName parameter of SetEarlyReflectionsAuxSend applies to sounds playing on the Wwise game object which have not specified an early reflection bus in the authoring tool - + * the parameter specified on individual sounds' reflection bus takes priority over the value passed in to SetEarlyReflectionsAuxSend. + * @param AuxBusName - Name of aux bus in the Wwise project. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|AkComponent") + void SetEarlyReflectionsAuxBus(const FString& AuxBusName); + + /** + * Set the early reflections volume for this AK Component. The SendVolume parameter is used to control the volume of the early reflections send. It is combined with the early reflections volume specified in the authoring tool, + * and is applied to all sounds playing on the Wwise game object. + * @param SendVolume - Send volume to the early reflections aux bus. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|AkComponent") + void SetEarlyReflectionsVolume(float SendVolume); + + /** + * Set the output bus volume (direct) to be used for the specified game object. + * The control value is a number ranging from 0.0f to 1.0f. + * + * @param BusVolume - Bus volume to set + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|AkComponent") + void SetOutputBusVolume(float BusVolume); + + + /** Modifies the attenuation computations on this game object to simulate sounds with a a larger or smaller area of effect. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="AkComponent") + float AttenuationScalingFactor = .0f; + + /** Sets the attenuation scaling factor, which modifies the attenuation computations on this game object to simulate sounds with a a larger or smaller area of effect. */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|AkComponent") + void SetAttenuationScalingFactor(float Value); + + /** Set the time interval between occlusion/obstruction checks (direct line of sight between the listener and this game object). Set to 0 to disable occlusion/obstruction on this component. We recommend disabling it if you want to use full Spatial Audio diffraction. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AkComponent|Occlusion") + float OcclusionRefreshInterval = .0f; + + /** Whether to use reverb volumes or not */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AkComponent") + bool bUseReverbVolumes = true; + + + /** + * Return the real attenuation radius for this component (AttenuationScalingFactor * AkAudioEvent->MaxAttenuationRadius) + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|AkComponent") + float GetAttenuationRadius() const; + + void UpdateGameObjectPosition(); + + void GetAkGameObjectName(FString& Name) const; + + bool IsDefaultListener = false; + +#if CPP + + /*------------------------------------------------------------------------------------ + UActorComponent interface. + ------------------------------------------------------------------------------------*/ + /** + * Called after component is registered + */ + virtual void OnRegister(); + + /** + * Called after component is unregistered + */ + virtual void OnUnregister(); + + /** + * Clean up + */ + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + + /** + * Clean up after error + */ + virtual void ShutdownAfterError(); + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + + // Begin USceneComponent Interface + virtual void BeginPlay() override; + virtual void OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport = ETeleportType::None) override; + // End USceneComponent Interface + + /** Gets all AkLateReverbComponents at the AkComponent's current location, and puts them in a list + * + * @param Loc The location of the AkComponent + */ + void UpdateAkLateReverbComponentList(FVector Loc); + + /** Gets the current room the AkComponent is in. + * + * @param Location The location of the AkComponent + */ + void UpdateSpatialAudioRoom(FVector Location); + + void SetAutoDestroy(bool in_AutoDestroy) { bAutoDestroy = in_AutoDestroy; } + + bool UseDefaultListeners() const { return bUseDefaultListeners; } + + void OnListenerUnregistered(UAkComponent* in_pListener) + { + Listeners.Remove(in_pListener); + } + + void OnDefaultListenerAdded(UAkComponent* in_pListener) + { + check(bUseDefaultListeners); + Listeners.Add(in_pListener); + } + + const TSet& GetEmitters(); + + static UAkComponent* GetAkComponent(AkGameObjectID GameObjectID); + + AkRoomID GetSpatialAudioRoom() const; + + void UpdateObstructionAndOcclusion() { ObstructionService.UpdateObstructionAndOcclusion(Listeners, GetPosition(), GetOwner(), GetSpatialAudioRoom(), GetOcclusionCollisionChannel(), OcclusionRefreshInterval); } + + FVector GetPosition() const; + +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + +private: + /** + * Register the component with Wwise + */ + void RegisterGameObject(); + + /** + * Unregister the component from Wwise + */ + void UnregisterGameObject(); + + /* + * Called after registering the component with Wwise + */ + virtual void PostRegisterGameObject(); + + /* + * Called after unregistering the component from Wwise + */ + virtual void PostUnregisterGameObject(); + + // Reverb Volume features --------------------------------------------------------------------- + + /** Apply the current list of AkReverbVolumes + * + * @param DeltaTime The given time increment since last fade computation + */ + void ApplyAkReverbVolumeList(float DeltaTime); + + AkComponentObstructionAndOcclusionService ObstructionService; + + /** Array of the active AkReverbVolumes at the AkComponent's location */ + TArray ReverbFadeControls; + + /** Aux Send values sent to the SoundEngine in the previous frame */ + TArray CurrentAuxSendValues; + + /** Do we need to refresh Aux Send values? */ + bool NeedToUpdateAuxSends(const TArray& NewValues); + + /** Room the AkComponent is currently in. nullptr if none */ + class UAkRoomComponent* CurrentRoom; + + /** Whether to automatically destroy the component when the event is finished */ + bool bAutoDestroy; + + /** Previous known position. Used to avoid Spamming SetPOsition on a listener */ + AkSoundPosition CurrentSoundPosition; + bool HasMoved(); + +#endif + +#if WITH_EDITORONLY_DATA + /** Utility function that updates which texture is displayed on the sprite dependent on the properties of the Audio Component. */ + void UpdateSpriteTexture(); +#endif + + bool bUseDefaultListeners; + TSet Listeners; + + //NOTE: This set of emitters is only valid if this UAkComopnent is a listener, and it it is not a default listener. See GetEmitters(). + TSet Emitters; + + void CheckEmitterListenerConsistancy(); + + void DebugDrawReflections() const; + void _DebugDrawReflections(const AkVector64& akEmitterPos, const AkVector64& akListenerPos, const AkReflectionPathInfo* paths, AkUInt32 uNumPaths) const; + + void DebugDrawDiffraction() const; + void _DebugDrawDiffraction(const AkVector64& akEmitterPos, const AkVector64& akListenerPos, const AkDiffractionPathInfo* paths, AkUInt32 uNumPaths) const; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkComponentHelpers.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkComponentHelpers.h new file mode 100644 index 0000000..ac57881 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkComponentHelpers.h @@ -0,0 +1,94 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkComponentHelpers.h: +=============================================================================*/ +#pragma once +#include "Components/SceneComponent.h" +#include "PhysicsEngine/BodySetup.h" +#include "AkInclude.h" + +namespace AkComponentHelpers +{ + /* Return true if the bodySetup has simple collision */ + AKAUDIO_API bool HasSimpleCollisionGeometry(const UBodySetup* bodySetup); + + /* Return true if the Point lies within the Primitive component. SphereRadius provides a margin of error for containment. + If OutDistanceToPoint is non-null it will be given the distance from Point to a point on the Primitive component. */ + AKAUDIO_API bool EncompassesPoint(UPrimitiveComponent& Primitive, FVector Point, float SphereRadius = 0.f, float* OutDistanceToPoint = nullptr); + + /* Return the unreal-units-to-meters ratio being used by this component */ + AKAUDIO_API float UnrealUnitsPerMeter(const UActorComponent* component); + + /* Return the square of the unreal-units-to-meters ratio being used by this component */ + AKAUDIO_API float UnrealUnitsPerSquaredMeter(const UActorComponent* component); + + /* Return the cube of the unreal-units-to-meters ratio being used by this component */ + AKAUDIO_API float UnrealUnitsPerCubicMeter(const UActorComponent* component); + + /* Convenience function for components based on AActor::FindComponentByClass. + Return the first child component of type COMPONENT_TYPE, or nullptr if there are none. */ + template + AKAUDIO_API COMPONENT_TYPE* GetChildComponentOfType(const USceneComponent& ParentComponent) + { + const TArray parentChildren = ParentComponent.GetAttachChildren(); + + for (auto* const child : parentChildren) + { + if (child && child->IsA()) + { + return Cast(child); + } + } + return nullptr; + } + + AKAUDIO_API bool IsInGameWorld(const UActorComponent* InComponent); + + /**/ + AKAUDIO_API void GetPrimitiveTransformAndExtent(const UPrimitiveComponent& Primitive, AkWorldTransform& transform, AkExtent& extent); + AKAUDIO_API void GetPrimitiveUpAndFront(const UPrimitiveComponent& Primitive, AkVector& Up, AkVector& Front); + + /* Return true if the movement would reset the child to (0, 0, 0) locally, with respect to the parent position. Return false otherwise.*/ + AKAUDIO_API bool DoesMovementRecenterChild(USceneComponent* child, USceneComponent* parent, const FVector& Delta); + + /* Log an error for invalid parent type */ + AKAUDIO_API void LogAttachmentError(const UActorComponent* child, const UActorComponent* parent, const FString& requiredClassName); + + /* Log a warning that parent has no simple geometry, so child will use component bounds */ + AKAUDIO_API void LogSimpleGeometryWarning(const UPrimitiveComponent* parent, const UActorComponent* child); + +#if WITH_EDITOR + /* IsGameWorldBlueprintComponent() and ShouldDeferBeginPlay() are used to deal with a specific edge case concerning + Blueprint classes: For Blueprint class components, during Super::PostEditChangeProperty(), the component is + replaced with a new instance, and PostEditChangeProperty() is called on the old instance. + In our case this can lead to issues when we update Wwise from PostEditChangeProperty(). For example, duplicate + rooms or geometry could be sent. This check should be used to make sure we don't update Wwise in this scenario. + */ + AKAUDIO_API bool IsGameWorldBlueprintComponent(const UActorComponent* InComponent); + /* IsGameWorldBlueprintComponent() and ShouldDeferBeginPlay() are used to deal with a specific edge case concerning + Blueprint classes. For Blueprint class components, during Super::PostEditChangeProperty(), the component is + replaced with a new instance. + The duplicated component instance initially has default values for all UPROPERTY members. + It is only later, after BeginPlay() has been called, that the values from the previous instance's UPROPERTYs are + copied to the new instance! So in that case, we need to defer the BeginPlay logic that depends on UPROPERTY values + that have been updated via user interaction, prior to the begin play call. + */ + AKAUDIO_API bool ShouldDeferBeginPlay(const UActorComponent* InComponent); +#endif +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkDeprecated.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkDeprecated.h new file mode 100644 index 0000000..a5c54c6 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkDeprecated.h @@ -0,0 +1,143 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#include "Serialization/BulkData.h" +#include "UObject/Object.h" +#include "AkAudioType.h" + +#include "AkDeprecated.generated.h" + + +//These classes are deprecated but we use them during migration to clean up old assets + +UCLASS() +class AKAUDIO_API UAkAssetData : public UObject +{ + GENERATED_BODY() + + FByteBulkData Data; + void Serialize(FArchive& Ar) override + { + Super::Serialize(Ar); + Data.Serialize(Ar, this); + } +}; + + +UCLASS() +class AKAUDIO_API UAkAssetPlatformData : public UObject +{ + GENERATED_BODY() + +#if WITH_EDITORONLY_DATA + UPROPERTY(transient, VisibleAnywhere, Category = "UAkAssetData") + TMap AssetDataPerPlatform; +#endif + + UPROPERTY(transient) + UAkAssetData* CurrentAssetData = nullptr; + + void Serialize(FArchive& Ar) override + { + Super::Serialize(Ar); +#if WITH_EDITORONLY_DATA + Ar << AssetDataPerPlatform; +#endif + } + +}; + +struct AKAUDIO_API FAkMediaDataChunk +{ + FByteBulkData Data; + bool IsPrefetch = false; + + void Serialize(FArchive& Ar, UObject* Owner) + { + Ar << IsPrefetch; + Data.Serialize(Ar, Owner); + } +}; + + +UCLASS() +class AKAUDIO_API UAkMediaAssetData : public UObject +{ + GENERATED_BODY() + + TIndirectArray DataChunks; + + void Serialize(FArchive& Ar) override + { + Super::Serialize(Ar); + + int32 numChunks = DataChunks.Num(); + Ar << numChunks; + + if (Ar.IsLoading()) + { + DataChunks.Empty(); + for (int32 i = 0; i < numChunks; ++i) + { + DataChunks.Add(new FAkMediaDataChunk()); + } + } + + for (int32 i = 0; i < numChunks; ++i) + { + DataChunks[i].Serialize(Ar, this); + } + } +}; + +UCLASS() +class AKAUDIO_API UAkMediaAsset : public UObject +{ + GENERATED_BODY() + + UPROPERTY(transient, VisibleAnywhere, Category = "AkMediaAsset") + TMap MediaAssetDataPerPlatform; + + void Serialize(FArchive& Ar) override + { + Super::Serialize(Ar); + Ar << MediaAssetDataPerPlatform; + } +}; + +UCLASS() +class AKAUDIO_API UAkLocalizedMediaAsset : public UAkMediaAsset +{ + GENERATED_BODY() +}; + +UCLASS() +class AKAUDIO_API UAkExternalMediaAsset : public UAkMediaAsset +{ + GENERATED_BODY() + +}; + +UCLASS() +class AKAUDIO_API UAkFolder : public UAkAudioType +{ + GENERATED_BODY() +#if WITH_EDITORONLY_DATA + virtual FWwiseObjectInfo* GetInfoMutable() override { return nullptr; } +#endif +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkDrawPortalComponent.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkDrawPortalComponent.h new file mode 100644 index 0000000..e9d9fb6 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkDrawPortalComponent.h @@ -0,0 +1,53 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkDrawPortalComponent.h: +=============================================================================*/ +#pragma once + +#if WITH_EDITOR +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "AkAcousticPortal.h" +#endif // WITH_EDITOR + +#include "Components/PrimitiveComponent.h" +#include "AkDrawPortalComponent.generated.h" + +/** + * Utility component for drawing a portal in a scene. + */ + +UCLASS(collapsecategories, hidecategories = Object, editinlinenew, MinimalAPI) +class UDrawPortalComponent : public UPrimitiveComponent +{ + GENERATED_BODY() + +public: + UDrawPortalComponent(const FObjectInitializer& ObjectInitializer); + +#if WITH_EDITOR + void DrawPortalOutline(const FSceneView* View, FPrimitiveDrawInterface* PDI, FMeshElementCollector& Collector, int32 ViewIndex) const; + const UAkPortalComponent* GetPortalParent() const; + +private: + virtual class FPrimitiveSceneProxy* CreateSceneProxy() override; + virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override; + +#endif // WITH_EDITOR +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkDrawRoomComponent.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkDrawRoomComponent.h new file mode 100644 index 0000000..4e4099a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkDrawRoomComponent.h @@ -0,0 +1,53 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkDrawRoomComponent.h: +=============================================================================*/ +#pragma once + +#if WITH_EDITOR +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "AkRoomComponent.h" +#endif // WITH_EDITOR + +#include "Components/PrimitiveComponent.h" +#include "AkDrawRoomComponent.generated.h" + +/** + * Utility component for drawing a Room in a scene. + */ + +UCLASS(collapsecategories, hidecategories = Object, editinlinenew, MinimalAPI) +class UDrawRoomComponent : public UPrimitiveComponent +{ + GENERATED_BODY() + +public: + UDrawRoomComponent(const FObjectInitializer& ObjectInitializer); + +#if WITH_EDITOR + void DrawRoom(const FSceneView* View, FPrimitiveDrawInterface* PDI, FMeshElementCollector& Collector, int32 ViewIndex) const; + const UAkRoomComponent* GetRoomParent() const; + +private: + virtual class FPrimitiveSceneProxy* CreateSceneProxy() override; + virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override; + +#endif // WITH_EDITOR +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkEffectShareSet.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkEffectShareSet.h new file mode 100644 index 0000000..ab4bfea --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkEffectShareSet.h @@ -0,0 +1,63 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkAudioType.h" +#include "Wwise/CookedData/WwiseLocalizedShareSetCookedData.h" +#include "Wwise/Loaded/WwiseLoadedShareSet.h" +#if WITH_EDITORONLY_DATA +#include "Wwise/Info/WwiseObjectInfo.h" +#endif +#include "AkEffectShareSet.generated.h" + +UCLASS(BlueprintType) +class AKAUDIO_API UAkEffectShareSet : public UAkAudioType +{ + GENERATED_BODY() +public: + + UPROPERTY(Transient, VisibleAnywhere, Category = "AkEffectShareSet") + FWwiseLocalizedShareSetCookedData ShareSetCookedData; + +#if WITH_EDITORONLY_DATA + UPROPERTY(EditAnywhere, Category = "AkEffectShareSet") + FWwiseObjectInfo ShareSetInfo; +#endif + +public: + void Serialize(FArchive& Ar) override; + + virtual void LoadData() override {LoadEffectShareSet();} + virtual void UnloadData(bool bAsync = false) override {UnloadEffectShareSet(bAsync);} + virtual AkUInt32 GetShortID() const override {return ShareSetCookedData.ShareSetId;} + +#if WITH_EDITORONLY_DATA + virtual FWwiseObjectInfo* GetInfoMutable() override {return &ShareSetInfo;}; + virtual FWwiseObjectInfo GetInfo() const override{return ShareSetInfo;} + virtual bool ObjectIsInSoundBanks() override; + + void CookAdditionalFilesOverride(const TCHAR* PackageFilename, const ITargetPlatform* TargetPlatform, + TFunctionRef WriteAdditionalFile) override; + virtual void FillInfo() override; +#endif + +private : + void LoadEffectShareSet(); + void UnloadEffectShareSet(bool bAsync); + FWwiseLoadedShareSet LoadedShareSet; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkEnvironmentIndex.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkEnvironmentIndex.h new file mode 100644 index 0000000..7f3e3b2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkEnvironmentIndex.h @@ -0,0 +1,167 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Math/GenericOctree.h" +#include "WwiseDefines.h" +#include "Components/SceneComponent.h" +#include "EngineDefines.h" + +class UAkEnvironmentOctree; + +#if UE_4_26_OR_LATER +#define AK_OCTREE_TYPE TOctree2 +#define AK_OCTREE_ELEMENT_ID FOctreeElementId2 +#else +#define AK_OCTREE_TYPE TOctree +#define AK_OCTREE_ELEMENT_ID FOctreeElementId +#endif + +struct FAkEnvironmentOctreeElement +{ + USceneComponent* Component; + + FBoxCenterAndExtent BoundingBox; + + FAkEnvironmentOctreeElement(USceneComponent* in_Component) + { + Component = in_Component; + BoundingBox = FBoxCenterAndExtent(Component->Bounds.GetBox().GetCenter(), Component->Bounds.GetBox().GetExtent()); + } +}; + +struct FAkEnvironmentOctreeSemantics +{ + typedef AK_OCTREE_TYPE FOctree; + + enum { MaxElementsPerLeaf = 16 }; + enum { MinInclusiveElementsPerNode = 7 }; + enum { MaxNodeDepth = 12 }; + + typedef TInlineAllocator ElementAllocator; + + FORCEINLINE static FBoxCenterAndExtent GetBoundingBox(const FAkEnvironmentOctreeElement& Element) + { + return Element.BoundingBox; + } + + FORCEINLINE static bool AreElementsEqual(const FAkEnvironmentOctreeElement& A, const FAkEnvironmentOctreeElement& B) + { + return (A.Component == B.Component); + } + + static void SetElementId(FOctree& OctreeOwner, const FAkEnvironmentOctreeElement& Element, AK_OCTREE_ELEMENT_ID Id); +}; + +class UAkEnvironmentOctree : public AK_OCTREE_TYPE +{ +public: + + UAkEnvironmentOctree() : AK_OCTREE_TYPE(FVector::ZeroVector, HALF_WORLD_MAX) {} + + TMap ObjectToOctreeId; +}; + +/** A spatial indexing data structure used to accelerate geometric queries. + Used for fast look up of UAkRoomComponents for Spatial Audio Rooms, and for UAkLateReverbComponents for auxiliary sends in Wwise. +*/ +class FAkEnvironmentIndex +{ +public: + /** + Query a world and location for an environmental rooms or late reverb components. + Returns an array of components that overlap Location, sorted by decreasing priority. + */ + template + TArray Query(const FVector& Location, const UWorld* World) + { + TArray Result; + + TUniquePtr* Octree = Map.Find(World); + + if (Octree != nullptr) + { +#if UE_4_26_OR_LATER + FBoxCenterAndExtent BoxBounds(Location, FVector::ZeroVector); + (*Octree)->FindElementsWithBoundsTest(BoxBounds, [&Result, Location](const FAkEnvironmentOctreeElement& Element) + { + EnvironmentType* Env = Cast(Element.Component); + if (Env && + Env->bEnable && + Env->HasEffectOnLocation(Location)) + { + Result.Add(Env); + } + }); +#else + for (UAkEnvironmentOctree::TConstElementBoxIterator<> It(**Octree, FBoxCenterAndExtent(Location, FVector(ForceInitToZero))); + It.HasPendingElements(); + It.Advance()) + { + const FAkEnvironmentOctreeElement& Element = It.GetCurrentElement(); + EnvironmentType* Env = Cast(Element.Component); + if (Env && + Env->bEnable && + Env->HasEffectOnLocation(Location)) + { + Result.Add(Env); + } + } +#endif + } + + // Sort the found Volumes + if (Result.Num() > 1) + { + Result.Sort([](const EnvironmentType& A, const EnvironmentType& B) + { + return A.Priority > B.Priority; + }); + } + + return Result; + } + + /** + Add a Component to the spatial index. + */ + void Add(USceneComponent* EnvironmentToAdd); + + /** + * Remove a Component from the spatial index. + */ + bool Remove(USceneComponent* EnvironmentToRemove); + + /** + * Update the bounds of a component that is already indexed. Must be called if the transform of the component changes. + */ + void Update(USceneComponent* EnvironmentToUpdate); + + /** + * Clear all components in the given World. + */ + void Clear(const UWorld* WorldToClear); + + /** + * Is the index empty for the given World. + */ + bool IsEmpty(const UWorld* World); + +private: + TMap > Map; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkGameObject.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkGameObject.h new file mode 100644 index 0000000..c6fdf3c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkGameObject.h @@ -0,0 +1,163 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkGameObject.h: +=============================================================================*/ + +#pragma once + +#include "AkAudioDevice.h" +#include "AkGameplayTypes.h" +#include "Components/SceneComponent.h" +#include "AkGameObject.generated.h" + + +UCLASS(ClassGroup=Audiokinetic, BlueprintType, Blueprintable, hidecategories=(Transform,Rendering,Mobility,LOD,Component,Activation), AutoExpandCategories=AkComponent, meta=(BlueprintSpawnableComponent)) +class AKAUDIO_API UAkGameObject: public USceneComponent +{ + GENERATED_BODY() + +public: + UAkGameObject(const class FObjectInitializer& ObjectInitializer); + + /** Associated Wwise Event to be posted on this game object */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="AkEvent") + UAkAudioEvent* AkAudioEvent = nullptr; + + /** Associated Event name to be posted on this game object. Deprecation warning: You should always use the associated AkAudioEvent to ensure the assets are properly loaded. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay, Category = "AkEvent") + FString EventName; + + /** + * Posts this game object's AkAudioEvent to Wwise, using this as the game object source + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|AkGameObject", meta = (AdvancedDisplay = "2", AutoCreateRefTerm = "PostEventCallback,ExternalSources")) + virtual int32 PostAssociatedAkEvent( + UPARAM(meta = (Bitmask, BitmaskEnum = "/Script/AkAudio.EAkCallbackType")) int32 CallbackMask, + const FOnAkPostEventCallback& PostEventCallback); + + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|AkGameObject", meta = (AutoCreateRefTerm = "PostEventCallback,ExternalSources", Latent, LatentInfo = "LatentInfo", WorldContext = "WorldContextObject")) + virtual void PostAssociatedAkEventAsync(const UObject* WorldContextObject, + UPARAM(meta = (Bitmask, BitmaskEnum = "/Script/AkAudio.EAkCallbackType")) int32 CallbackMask, + const FOnAkPostEventCallback& PostEventCallback, + FLatentActionInfo LatentInfo, + int32& PlayingID); + + /** + * Posts an event to Wwise, using this as the game object source + * + * @param AkEvent The event to post + * @param CallbackMask Mask of desired callbacks + * @param PostEventCallback Blueprint Event to execute on callback + * @param InEventName Deprecated: If AkEvent is not set, this is used. You should ensure your AkEvent is always set. + * + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|AkGameObject", meta = (AdvancedDisplay = "1", AutoCreateRefTerm = "PostEventCallback,ExternalSources")) + virtual int32 PostAkEvent( + class UAkAudioEvent * AkEvent, + UPARAM(meta = (Bitmask, BitmaskEnum = "/Script/AkAudio.EAkCallbackType")) int32 CallbackMask, + const FOnAkPostEventCallback& PostEventCallback, + const FString& InEventName + ); + + virtual AkPlayingID PostAkEvent(UAkAudioEvent* AkEvent, AkUInt32 Flags = 0, AkCallbackFunc UserCallback = nullptr, void* UserCookie = nullptr); + + /** + * Posts an event to Wwise, using this as the game object source + * + * @param AkEvent The event to post + * @param CallbackMask Mask of desired callbacks + * @param PostEventCallback Blueprint Event to execute on callback + * + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|AkGameObject", meta = (AdvancedDisplay = "3", AutoCreateRefTerm = "PostEventCallback,ExternalSources", Latent, LatentInfo = "LatentInfo", WorldContext = "WorldContextObject")) + virtual void PostAkEventAsync(const UObject* WorldContextObject, + class UAkAudioEvent* AkEvent, + int32& PlayingID, + UPARAM(meta = (Bitmask, BitmaskEnum = "/Script/AkAudio.EAkCallbackType")) int32 CallbackMask, + const FOnAkPostEventCallback& PostEventCallback, + FLatentActionInfo LatentInfo + ); + + /** + * Stops playback using this game object as the game object to stop + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|AkComponent") + virtual void Stop(); + + /** + * @warning Using EventName in this function is deprecated. Use \ref PostAkEvent. + */ + AK_DEPRECATED(2022.1, "Use PostAkEvent.") + virtual AkPlayingID PostAkEventByNameWithDelegate( + UAkAudioEvent* AkEvent, + const FString& InEventName, + int32 CallbackMask, + const FOnAkPostEventCallback& PostEventCallback); + + /** + * @warning This function is deprecated. You are expected to use an UAkAudioEvent. Use \ref PostAkEvent. + */ + AK_DEPRECATED(2022.1, "Use PostAkEvent.") + virtual void PostAkEventAsyncByEvent(const UObject* WorldContextObject, + class UAkAudioEvent* AkEvent, + int32 CallbackMask, + const FOnAkPostEventCallback& PostEventCallback, + FLatentActionInfo LatentInfo, + int32& PlayingID + ); + + /** + * Sets an RTPC value, using this game object as the game object source + * + * @param RTPC The name of the RTPC to set + * @param Value The value of the RTPC + * @param InterpolationTimeMs - Duration during which the RTPC is interpolated towards Value (in ms) + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|AkGameObject", meta = (AdvancedDisplay = "3")) + void SetRTPCValue(class UAkRtpc const* RTPCValue, float Value, int32 InterpolationTimeMs, FString RTPC) const; + + /** + * Gets an RTPC value that was set on this game object as the game object source + * + * @param RTPC The name of the RTPC to set + * @param InputValueType The input value type + * @param Value The value of the RTPC + * @param OutputValueType The output value type + * @param PlayingID The playing ID of the posted event (Set to zero to ignore) + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|AkGameObject", meta = (AdvancedDisplay = "RTPC")) + void GetRTPCValue(class UAkRtpc const* RTPCValue, ERTPCValueType InputValueType, float& Value, ERTPCValueType& OutputValueType, FString RTPC, int32 PlayingID = 0) const; + AK_DEPRECATED(2019.1.3, "This function is deprecated and will be removed in future releases.") + void GetRTPCValue(FString RTPC, int32 PlayingID, ERTPCValueType InputValueType, float& Value, ERTPCValueType& OutputValueType) const; + +#if CPP + bool VerifyEventName(const FString& InEventName) const; + bool AllowAudioPlayback() const; + AkGameObjectID GetAkGameObjectID() const; + virtual void UpdateObstructionAndOcclusion() {}; + bool HasActiveEvents() const; +#endif + + bool HasBeenRegisteredWithWwise() const { return IsRegisteredWithWwise; } + void EventPosted() {bEventPosted = true;} +protected: + // Whether an event was posted on the game object. Never reset to false. + bool bEventPosted; + bool IsRegisteredWithWwise = false; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkGameplayStatics.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkGameplayStatics.h new file mode 100644 index 0000000..db021e0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkGameplayStatics.h @@ -0,0 +1,616 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkGameplayStatics.h: +=============================================================================*/ +#pragma once + +#include "AkAudioDevice.h" +#include "AkInclude.h" +#include "AkGameplayTypes.h" +#include "AkUnrealHelper.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "AkGameplayStatics.generated.h" + +// PostEvent functions need to return the PlayingID (uint32), but Blueprints only work with int32. +// Make sure AkPlayingID is always 32 bits, or else we're gonna have a bad time. +static_assert(sizeof(AkPlayingID) == sizeof(int32), "AkPlayingID is not 32 bits anymore. Change return value of PostEvent functions and callback info structures members!"); + +UCLASS() +class AKAUDIO_API UAkGameplayStatics : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + UAkGameplayStatics(const class FObjectInitializer& ObjectInitializer); + + /** Get an AkComponent attached to and following the specified component. + * @param AttachPointName - Optional named point within the AttachComponent to play the sound at. + */ + UFUNCTION(BlueprintCallable, Category="Audiokinetic") + static class UAkComponent * GetAkComponent( class USceneComponent* AttachToComponent, bool& ComponentCreated, FName AttachPointName = NAME_None, FVector Location = FVector(ForceInit), EAttachLocation::Type LocationType = EAttachLocation::KeepRelativeOffset ); + + UFUNCTION(BlueprintCallable, Category="Audiokinetic") + static bool IsEditor(); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic") + static bool IsGame(UObject* WorldContextObject); + + /** Posts a Wwise Event attached to and following the root component of the specified actor. + * + * @param AkEvent - Event to play. + * @param Actor - Actor on which to play the event. If the Actor is left empty, the Event will be played as an Ambient sound. + * @param bStopWhenAttachedToDestroyed - Specifies whether the sound should stop playing when the owner of the attach to component is destroyed. + * @param EventName - Deprecated: Event name in case the AkEvent is not set. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|Actor", meta=(AdvancedDisplay="2", AutoCreateRefTerm = "PostEventCallback")) + static int32 PostEvent( class UAkAudioEvent* AkEvent, + class AActor* Actor, + UPARAM(meta = (Bitmask, BitmaskEnum = "/Script/AkAudio.EAkCallbackType")) int32 CallbackMask, + const FOnAkPostEventCallback& PostEventCallback, + bool bStopWhenAttachedToDestroyed = false, + FString EventName = FString("")); + + /** + * Posts a Wwise Event attached to and following the root component of the specified actor, and waits for the end of the event to continue execution. + * Additional calls made while an event is active are ignored. + * + * @param AkEvent - Event to play. + * @param Actor - actor on which to play the event. + * @param bStopWhenAttachedToDestroyed - Specifies whether the sound should stop playing when the owner of the attach to component is destroyed. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|Actor", meta = (Latent, LatentInfo = "LatentInfo", AdvancedDisplay = "2")) + static int32 PostAndWaitForEndOfEvent(class UAkAudioEvent* AkEvent, + class AActor* Actor, + bool bStopWhenAttachedToDestroyed, + FLatentActionInfo LatentInfo); + + /** Posts a Wwise Event attached and following the root component of the specified actor, wait for the media to be loaded and waits for the end of the event to continue execution. + * Additional calls made while an event is active are ignored. + * + * @warning This function is deprecated. Use \ref PostAndWaitForEndOfEvent or \ref UAkAudioEvent::PostOnActorAndWait. + * Async operations are deprecated. + * + * @param AkEvent - ak event to play. + * @param Actor - actor on which to play the event. + * @param bStopWhenAttachedToDestroyed - Specifies whether the sound should stop playing when the owner of the attach to component is destroyed. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|Actor", meta = (Latent, LatentInfo = "LatentInfo", AdvancedDisplay = "3", bStopWhenAttachedToDestroyed = "false", AutoCreateRefTerm = "ExternalSources", DeprecatedFunction, DeprecationMessage = "Use \"UAkAudioEvent::PostOnActorAndWait\".")) + static void PostAndWaitForEndOfEventAsync(class UAkAudioEvent* AkEvent, + class AActor* Actor, + int32& PlayingID, + bool bStopWhenAttachedToDestroyed, + FLatentActionInfo LatentInfo + ); + + /** Posts a Wwise Event by name attached to and following the root component of the specified actor. + * + * @warning This function is deprecated. You are expected to use an UAkAudioEvent. Please see \ref PostEvent or \ref UAkAudioEvent::PostOnActor. + * + * @param AkEvent - ak event to play. + * @param Actor - actor on which to play the event. + * @param bStopWhenAttachedToDestroyed - Specifies whether the sound should stop playing when the owner of the attach to component is destroyed. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|Actor", meta=(DeprecatedFunction, DeprecationMessage = "Use the \"Event Name\" field of PostEvent")) + static void PostEventByName( const FString& EventName, + class AActor* Actor, + bool bStopWhenAttachedToDestroyed = false); + + /** Posts a Wwise Event at the specified location. This is a fire and forget sound, created on a temporary Wwise Game Object. Replication is also not handled at this point. + * + * @param AkEvent - Wwise Event to post. + * @param Location - Location from which to post the Wwise Event. + * @param Orientation - Orientation of the event. + * @param EventName - Deprecated: Event name in case the AkEvent is not set. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic", meta=(WorldContext="WorldContextObject", AdvancedDisplay = "3")) + static int32 PostEventAtLocation(UAkAudioEvent* AkEvent, FVector Location, FRotator Orientation, + const FString& EventName, UObject* WorldContextObject); + + /** Posts a Wwise Event by name at the specified location. This is a fire and forget sound, created on a temporary Wwise Game Object. Replication is also not handled at this point. + * + * @warning This function is deprecated. You are expected to use an UAkAudioEvent. Please see \ref UAkAudioEvent::PostAtLocation. + * + * @param AkEvent - Wwise Event to post. + * @param Location - Location from which to post the Wwise Event. + * @param Orientation - Orientation of the event. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic", meta=(WorldContext="WorldContextObject", DeprecatedFunction, DeprecationMessage = "Use \"UAkAudioEvent::PostAtLocation\".")) + static void PostEventAtLocationByName(const FString& EventName, FVector Location, FRotator Orientation, UObject* WorldContextObject ); + + /** Execute action on event attached to and following the root component of the specified actor + * + * @warning This function is deprecated. Please see \ref UAkAudioEvent::ExecuteAction. + * + * @param AkEvent - Wwise Event to act upon. + * @param ActionType - Which action to do. + * @param Actor - Which actor to use. + * @param TransitionDuration - Transition duration in milliseconds. + * @param FadeCurve - The interpolation curve of the transition. + * @param PlayingID - Use the return value of a Post Event to act only on this specific instance of an event. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|Actor", meta=(DeprecatedFunction, DeprecationMessage = "Use \"UAkAudioEvent::ExecuteAction\".")) + static void ExecuteActionOnEvent(class UAkAudioEvent* AkEvent, AkActionOnEventType ActionType, class AActor* Actor, int32 TransitionDuration = 0, EAkCurveInterpolation FadeCurve = EAkCurveInterpolation::Linear, int32 PlayingID = 0); + + /** Execute action on specific playing ID + * + * @warning This function is deprecated. You are expected to use an UAkAudioEvent. Please see \ref UAkAudioEvent::ExecuteAction. + * + * @param ActionType - Which action to do. + * @param PlayingID - Use the return value of a Post Event to act only on this specific instance of an event. + * @param TransitionDuration - Transition duration in milliseconds. + * @param FadeCurve - The interpolation curve of the transition. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|Actor", meta=(DeprecatedFunction, DeprecationMessage = "Use \"UAkAudioEvent::ExecuteAction\".")) + static void ExecuteActionOnPlayingID(AkActionOnEventType ActionType, int32 PlayingID, int32 TransitionDuration = 0, EAkCurveInterpolation FadeCurve = EAkCurveInterpolation::Linear); + + /** Spawn an AkComponent at a location. Allows, for example, to set a switch on a fire and forget sound. + * @param AkEvent - Wwise Event to post. + * @param Location - Location from which to post the Wwise Event. + * @param Orientation - Orientation of the event. + * @param AutoPost - Automatically post the event once the AkComponent is created. + * @param AutoDestroy - Automatically destroy the AkComponent once the event is finished. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic", meta=(WorldContext="WorldContextObject", AdvancedDisplay = "6")) + static class UAkComponent* SpawnAkComponentAtLocation(UObject* WorldContextObject, class UAkAudioEvent* AkEvent, FVector Location, FRotator Orientation, bool AutoPost, const FString& EventName, bool AutoDestroy = true); + + /** + * Sets the value of a Game Parameter, optionally targetting the root component of a specified actor. + * @param RTPC - The name of the Game Parameter to set + * @param Value - The value of the Game Parameter + * @param InterpolationTimeMs - Duration during which the Game Parameter is interpolated towards Value (in ms) + * @param Actor - (Optional) Actor on which to set the Game Parameter value + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic", meta = (AdvancedDisplay = "4")) + static void SetRTPCValue(class UAkRtpc const* RTPCValue, float Value, int32 InterpolationTimeMs, class AActor* Actor, FName RTPC); + + /** + * Gets the value of a Game Parameter, optionally targetting the root component of a specified actor. + * @param RTPC - The name of the Game Parameter to set + * @param Value - The value of the Game Parameter + * @param InterpolationTimeMs - Duration during which the Game Parameter is interpolated towards Value (in ms) + * @param Actor - (Optional) Actor on which to set the Game Parameter value + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic", meta = (AdvancedDisplay = "7")) + static void GetRTPCValue(class UAkRtpc const* RTPCValue, int32 PlayingID, ERTPCValueType InputValueType, float& Value, ERTPCValueType& OutputValueType, class AActor* Actor, FName RTPC); + + /** + * Resets the value of a Game Parameter to its default value, optionally targetting the root component of a specified actor. + * @param RTPCValue - The name of the Game Parameter to reset + * @param InterpolationTimeMs - Duration during which the Game Parameter is interpolated towards its default value (in ms) + * @param Actor - (Optional) Actor on which to reset the Game Parameter value + * @param RTPC - The name of the Game Parameter to reset + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic", meta = (AdvancedDisplay = "8")) + static void ResetRTPCValue(class UAkRtpc const* RTPCValue, int32 InterpolationTimeMs, class AActor* Actor, FName RTPC); + + /** + * Set the active State for a given State Group. + * @param StateGroup - Name of the State Group to be modified + * @param State - Name of the State to be made active + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic", meta = (AdvancedDisplay = "1")) + static void SetState(class UAkStateValue const* StateValue, FName StateGroup, FName State); + + /** + * Posts a Trigger, targetting the root component of a specified actor. + * @param Trigger - Name of the Trigger + * @param Actor - Actor on which to post the Trigger + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|Actor", meta = (AdvancedDisplay = "2")) + static void PostTrigger(class UAkTrigger const* TriggerValue, class AActor* Actor, FName Trigger); + + /** + * Sets the active Switch for a given Switch Group, targetting the root component of a specified actor. + * @param SwitchGroup - Name of the Switch Group to be modified + * @param SwitchState - Name of the Switch to be made active + * @param Actor - Actor on which to set the switch + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|Actor", meta = (AdvancedDisplay = "2")) + static void SetSwitch(class UAkSwitchValue const* SwitchValue, class AActor* Actor, FName SwitchGroup, FName SwitchState); + + /** Sets multiple positions to a single game object. + * Setting multiple positions on a single game object is a way to simulate multiple emission sources while using the resources of only one voice. + * This can be used to simulate wall openings, area sounds, or multiple objects emitting the same sound in the same area. + * Note: Calling SetMultiplePositions() with only one position is the same as calling SetPosition() + * @param GameObjectAkComponent AkComponent of the game object on which to set positions. + * @param Positions Array of transforms to apply. + * @param MultiPositionType Position type + * @return AK_Success when successful, AK_InvalidParameter if parameters are not valid. + * + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic") + static void SetMultiplePositions(UAkComponent* GameObjectAkComponent, TArray Positions, + AkMultiPositionType MultiPositionType = AkMultiPositionType::MultiDirections); + + /** Sets multiple positions to a single game object, with flexible assignment of input channels. + * Setting multiple positions on a single game object is a way to simulate multiple emission sources while using the resources of only one voice. + * This can be used to simulate wall openings, area sounds, or multiple objects emitting the same sound in the same area. + * Note: Calling AK::SoundEngine::SetMultiplePositions() with only one position is the same as calling AK::SoundEngine::SetPosition() + * @param GameObjectAkComponent AkComponent of the game object on which to set positions. + * @param ChannelMasks Array of channel configuration to apply for each position. + * @param Positions Array of transforms to apply. + * @param MultiPositionType Position type + * @return AK_Success when successful, AK_InvalidParameter if parameters are not valid. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic") + static void SetMultipleChannelEmitterPositions(UAkComponent* GameObjectAkComponent, + TArray ChannelMasks, + TArray Positions, + AkMultiPositionType MultiPositionType = AkMultiPositionType::MultiDirections + ); + + /** Sets multiple positions to a single game object, with flexible assignment of input channels. + * Setting multiple positions on a single game object is a way to simulate multiple emission sources while using the resources of only one voice. + * This can be used to simulate wall openings, area sounds, or multiple objects emitting the same sound in the same area. + * Note: Calling AK::SoundEngine::SetMultiplePositions() with only one position is the same as calling AK::SoundEngine::SetPosition() + * @param GameObjectAkComponent AkComponent of the game object on which to set positions. + * @param ChannelMasks Array of channel mask to apply for each position. + * @param Positions Array of transforms to apply. + * @param MultiPositionType Position type + * @return AK_Success when successful, AK_InvalidParameter if parameters are not valid. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic") + static void SetMultipleChannelMaskEmitterPositions(UAkComponent* GameObjectAkComponent, + TArray ChannelMasks, + TArray Positions, + AkMultiPositionType MultiPositionType = AkMultiPositionType::MultiDirections + ); + + /** + * Sets UseReverbVolumes flag on a specified actor. Set value to true to use reverb volumes on this component. + * + * @param inUseReverbVolumes - Whether to use reverb volumes or not. + * @param Actor - Actor on which to set the flag + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|Actor") + static void UseReverbVolumes(bool inUseReverbVolumes, class AActor* Actor); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|Actor", meta = (AdvancedDisplay = "6", DeprecatedFunction, DeprecationMessage = "This function is deprecated and will be removed in future releases.")) + static void UseEarlyReflections(class AActor* Actor, + class UAkAuxBus* AuxBus, + int Order = 1, + float BusSendGain = 1.f, + float MaxPathLength = 100000.f, + bool SpotReflectors = false, + const FString& AuxBusName = FString("")); + + /** + * Sets the Reflections Order for Spatial Audio Reflect. + * + * @param Order - The order of Reflection. Can be 0 to 4. + * @param RefreshPaths - whether the paths should be refreshed immediately. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|Spatial Audio") + static void SetReflectionsOrder(int Order, bool RefreshPaths); + + /** + * Sets the obstruction and occlusion value of sounds going through this portal. + * + * @param PortalComponent - The portal through which sound path need to pass to get obstructed and occluded. + * @param ObstructionValue - The obstruction value. Can be 0 to 1. + * @param OcclusionValue - The occlusion value. Can be 0 to 1. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|Spatial Audio") + static void SetPortalObstructionAndOcclusion(UAkPortalComponent* PortalComponent, float ObstructionValue, float OcclusionValue); + + /** + * Sets the obstruction value of sounds going from this game object through this portal. + * + * @param GameObjectAkComponent - The game object emitting the sound that we want to obstruct. + * @param PortalComponent - The portal through which the sound from the game object can go. + * @param OcclusionValue - The occlusion value. Can be 0 to 1. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|Spatial Audio") + static void SetGameObjectToPortalObstruction(UAkComponent* GameObjectAkComponent, UAkPortalComponent* PortalComponent, float ObstructionValue); + + /** + * Sets the obstruction value of sounds going from a first portal through the next portal. + * + * @param PortalComponent0 - The first portal through which a sound path goes. + * @param PortalComponent1 - The next portal throuh which the sound path goes from the first portal. + * @param OcclusionValue - The occlusion value. Can be 0 to 1. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|Spatial Audio") + static void SetPortalToPortalObstruction(UAkPortalComponent* PortalComponent0, UAkPortalComponent* PortalComponent1, float ObstructionValue); + + /** + * Set the output bus volume (direct) to be used for the specified game object. + * The control value is a number ranging from 0.0f to 1.0f. + * + * @param BusVolume - Bus volume to set + * @param Actor - Actor on which to set the flag + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|Actor") + static void SetOutputBusVolume(float BusVolume, class AActor* Actor); + + /** + * Force channel configuration for the specified bus. + * This function has unspecified behavior when changing the configuration of a bus that + * is currently playing. + * You cannot change the configuration of the master bus. + * + * @param BusName Bus Name + * @param ChannelConfiguration Desired channel configuration. + * @return Always returns AK_Success + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic") + static void SetBusConfig(const FString& BusName, AkChannelConfiguration ChannelConfiguration); + + /** + * Set the panning rule of the specified output. + * This may be changed anytime once the sound engine is initialized. + * @warning This function posts a message through the sound engine's internal message queue, whereas GetPanningRule() queries the current panning rule directly. + * + * @param PanRule Panning rule. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic") + static void SetPanningRule(PanningRule PanRule); + + /** + * Adds an output to the sound engine. Use this to add controller-attached headphones, controller speakers, DVR output, etc. + * The in_Settings parameter contains an Audio Device shareset to specify the output plugin to use and a device ID to specify the instance, if necessary (e.g. which game controller). + * + * Like most functions of AK::SoundEngine, AddOutput is asynchronous. + * A successful return code merely indicates that the request is properly queued. Error codes returned by this function indicate various invalid parameters. + * To know if this function succeeds or not, and the failure code, register an AkDeviceStatusCallbackFunc callback with RegisterAudioDeviceStatusCallback. + * + * @param in_Settings Creation parameters for this output. + * @param out_pDeviceID (Optional) Output ID to use with all other Output management functions. Leave to NULL if not required. + * @param in_pListenerIDs Specific listener(s) to attach to this device. If specified, only the sounds routed to game objects linked to those listeners will play in this device. It is necessary to have separate listeners if multiple devices of the same type can coexist (e.g. controller speakers) If not specified, sound routing simply obey the associations between Master Busses and Audio Devices setup in the Wwise Project. + */ + UFUNCTION(BlueprintCallable, meta = (AutoCreateRefTerm = "in_ListenerIDs"), Category = "Audiokinetic") + static void AddOutput(const FAkOutputSettings& in_Settings, FAkOutputDeviceID& out_DeviceID, UPARAM(ref) TArray& in_ListenerIDs); + + /** + * Removes one output added through AK::SoundEngine::AddOutput If a listener was associated with the device, you should consider unregistering the listener prior to call RemoveOutput so that Game Object/Listener routing is properly updated according to your game scenario. + * + * @param in_OutputDeviceId ID of the output to remove. Use the returned ID from AddOutput, GetOutputID, or ReplaceOutputt. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic") + static void RemoveOutput(FAkOutputDeviceID in_OutputDeviceId); + + /** + * Replaces the main output device previously created during engine initialization with a new output device. + * In addition to simply removing one output device and adding a new one, the new output device will also be used on all of the master busses + * that the old output device was associated with, and preserve all listeners that were attached to the old output device. + * + * @param MainOutputSettings Creation parameters for this output + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic") + static void ReplaceMainOutput(const FAkOutputSettings& MainOutputSettings); + + /** + * Gets speaker angles of the specified device. Speaker angles are used for 3D positioning of sounds over standard configurations. + * Note that the current version of Wwise only supports positioning on the plane. + * The speaker angles are expressed as an array of loudspeaker pairs, in degrees, relative to azimuth ]0,180]. + * Supported loudspeaker setups are always symmetric; the center speaker is always in the middle and thus not specified by angles. + * Angles must be set in ascending order. + * + * @param SpeakerAngles Returned array of loudspeaker pair angles, in degrees relative to azimuth [0,180]. + * @param HeightAngle Elevation of the height layer, in degrees relative to the plane [-90,90]. + * @param DeviceShareSet ShareSet for which to get the angles. + * + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic") + static void GetSpeakerAngles(TArray& SpeakerAngles, float& HeightAngle, const FString& DeviceShareSet = ""); + + /** + * Sets speaker angles of the specified device. Speaker angles are used for 3D positioning of sounds over standard configurations. + * Note that the current version of Wwise only supports positioning on the plane. + * The speaker angles are expressed as an array of loudspeaker pairs, in degrees, relative to azimuth ]0,180]. + * Supported loudspeaker setups are always symmetric; the center speaker is always in the middle and thus not specified by angles. + * Angles must be set in ascending order. + * + * @param SpeakerAngles Array of loudspeaker pair angles, in degrees relative to azimuth [0,180] + * @param HeightAngle Elevation of the height layer, in degrees relative to the plane [-90,90] + * @param DeviceShareSet ShareSet for which to set the angles on. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic") + static void SetSpeakerAngles(const TArray& SpeakerAngles, float HeightAngle, const FString& DeviceShareSet = ""); + + /** + * Sets the occlusion calculation refresh interval, targetting the root component of a specified actor. + * @param RefreshInterval - Value of the wanted refresh interval + * @param Actor - Actor on which to set the refresh interval + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|Actor") + static void SetOcclusionRefreshInterval(float RefreshInterval, class AActor* Actor ); + + /** + * Stop all sounds for an actor. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|Actor") + static void StopActor(class AActor* Actor); + + /** + * Stop all sounds. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic") + static void StopAll(); + + /** + * Cancels an Event callback + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic") + static void CancelEventCallback(const FOnAkPostEventCallback& PostEventCallback); + + /** + * Start all Ak ambient sounds. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|AkAmbientSound", meta=(WorldContext = "WorldContextObject")) + static void StartAllAmbientSounds(UObject* WorldContextObject); + + /** + * Stop all Ak ambient sounds. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|AkAmbientSound", meta=(WorldContext = "WorldContextObject")) + static void StopAllAmbientSounds(UObject* WorldContextObject); + + /** + * Clear all loaded banks + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|SoundBanks", meta = (DeprecatedFunction, DeprecationMessage = "Use the \"ClearSoundBanksAndMedia\" instead.")) + static void ClearBanks(); + + /** + * Clear all loaded banks + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|SoundBanks") + static void ClearSoundBanksAndMedia(); + + /** + * Loads the Init SoundBank + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|SoundBanks") + static void LoadInitBank(); + + /** + * Unloads the Init SoundBank + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|SoundBanks") + static void UnloadInitBank(); + + /** + * Loads a bank by its name. + * @param Bank - The bank to load. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|SoundBanks", meta = (DeprecatedFunction, DeprecationMessage = "To manually load and unload the bank and media resources for your Wwise Assets, use the Wwise Asset LoadData and UnloadData Blueprint functions.")) + static void LoadBankByName(const FString& BankName); + + /** + * Unloads a bank by its name. + * @param Bank - The bank to unload. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|SoundBanks", meta = (DeprecatedFunction, DeprecationMessage = "To manually load and unload the bank and media resources for your Wwise Assets, use the Wwise Asset LoadData and UnloadData Blueprint functions.")) + static void UnloadBankByName(const FString& BankName); + + /** + * Starts a Wwise output capture. The output file will be located in the same folder as the SoundBanks. + * @param Filename - The name to give to the output file. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|Debug") + static void StartOutputCapture(const FString& Filename); + + /** + * Add text marker in output capture file. + * @param MarkerText - The name text to put in the marker. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|Debug") + static void AddOutputCaptureMarker(const FString& MarkerText); + + /** + * Stops a Wwise output capture. The output file will be located in the same folder as the SoundBanks. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|Debug") + static void StopOutputCapture(); + + /** + * Starts a Wwise profiler capture. The output file will be located in the same folder as the SoundBanks. + * @param Filename - The name to give to the output file. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|Debug") + static void StartProfilerCapture(const FString& Filename); + + /** + * Stops a Wwise profiler capture. The output file will be located in the same folder as the SoundBanks. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Audiokinetic|Debug") + static void StopProfilerCapture(); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|Culture") + static FString GetCurrentAudioCulture(); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|Culture") + static TArray GetAvailableAudioCultures(); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|Culture", meta = (WorldContext = "WorldContextObject", Latent, LatentInfo = "LatentInfo")) + static void SetCurrentAudioCulture(const FString& AudioCulture, FLatentActionInfo LatentInfo, UObject* WorldContextObject); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|Culture") + static void SetCurrentAudioCultureAsync(const FString& AudioCulture, const FOnSetCurrentAudioCultureCallback& Completed); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic") + static UObject* GetAkAudioTypeUserData(const UAkAudioType* Instance, const UClass* Type); + + /** Sets an effect ShareSet on an output device + * + * @param InDeviceID Output ID, as returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + * @param InEffectIndex Effect slot index (0-3) + * @param InEffectShareSet Effect ShareSet asset + * @return Always returns True + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic") + static bool SetOutputDeviceEffect(const FAkOutputDeviceID InDeviceID, const int32 InEffectIndex, const UAkEffectShareSet* InEffectShareSet); + + /** Sets an Effect ShareSet at the specified Bus and Effect slot index. + * + * @param InBusName Bus name + * @param InEffectIndex Effect slot index (0-3) + * @param InEffectShareSet Effect ShareSet asset + * @return True when successfully posted, False otherwise. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic") + static bool SetBusEffectByName(const FString InBusName, const int32 InEffectIndex, const UAkEffectShareSet* InEffectShareSet); + + /** Sets an Effect ShareSet at the specified Bus and Effect slot index. + * + * @param InBusID Bus Short ID. + * @param InEffectIndex Effect slot index (0-3) + * @param InEffectShareSet Effect ShareSet asset + * @return True when successfully posted, False otherwise. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic") + static bool SetBusEffectByID(const FAkUniqueID InBusID, const int32 InEffectIndex, const UAkEffectShareSet* InEffectShareSet); + + /** Sets an Effect ShareSet at the specified Bus and Effect slot index. + * + * @param InAuxBus Aux Bus Asset. + * @param InEffectIndex Effect slot index (0-3) + * @param InEffectShareSet Effect ShareSet asset + * @return True when successfully posted, False otherwise. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic") + static bool SetAuxBusEffect(const UAkAuxBus* InAuxBus, const int32 InEffectIndex, const UAkEffectShareSet* InEffectShareSet); + + /** Sets an Effect ShareSet at the specified audio node and Effect slot index. + * + * @param InAudioNodeID Can be a member of the Actor-Mixer or Interactive Music Hierarchy (not a bus). + * @param InEffectIndex Effect slot index (0-3) + * @param InEffectShareSet Effect ShareSet asset + * @return Always returns True. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic") + static bool SetActorMixerEffect(const FAkUniqueID InAudioNodeID, const int32 InEffectIndex, const UAkEffectShareSet* InEffectShareSet); + + /** + * Use the position of a separate Actor for distance calculations for a specified listener. + * When called, Wwise calculates distance attenuation and filtering + * based on the distance between the AkComponent on the distance probe Actor and the sound source. + * Useful for third-person perspective applications, the distance probe may be set to the player character's position, + * and the listener position to that of the camera. In this scenario, attenuation is based on + * the distance between the character and the sound, whereas panning, spatialization, and spread and focus calculations are base on the camera. + * @param Listener - The listener that is being affected. By default, the listener is attached to the Player Camera Manager. + * @param DistanceProbe - An actor to assign as the distance probe. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|Actor") + static void SetDistanceProbe(AActor* Listener, AActor* DistanceProbe); + + static bool m_bSoundEngineRecording; + +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkGameplayTypes.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkGameplayTypes.h new file mode 100644 index 0000000..cc86f11 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkGameplayTypes.h @@ -0,0 +1,1131 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkGameplayStatics.h: +=============================================================================*/ +#pragma once + +#include "Platforms/AkUEPlatform.h" +#include "Async/Future.h" +#include "Engine/EngineBaseTypes.h" +#include "Engine/EngineTypes.h" +#include "Engine/LatentActionManager.h" +#include "HAL/ThreadSafeBool.h" +#include "LatentActions.h" +#include "AkDeprecated.h" +#include "AkGameplayTypes.generated.h" + + +UENUM(BlueprintType) +enum class EAkAudioContext : uint8 +{ + Foreign, // Sounds unrelated to gameplay or editor + GameplayAudio, // Sounds playing during gameplay, simulation, PIE, etc. + EditorAudio, // Editor sounds (e.g. UI) + AlwaysActive, // Sounds which should last for the entire runtime, and not be stopped +}; + +UENUM(BlueprintType) +enum class PanningRule : uint8 +{ + PanningRule_Speakers = 0, ///< Left and right positioned 60 degrees apart (by default - see AK::SoundEngine::GetSpeakerAngles()). + PanningRule_Headphones = 1 ///< Left and right positioned 180 degrees apart. +}; + +UENUM(BlueprintType) +enum class AkAcousticPortalState : uint8 +{ + Closed = 0, + Open = 1, +}; + +UENUM(BlueprintType) +enum class AkChannelConfiguration : uint8 +{ + Ak_Parent = 0, + Ak_MainMix, + Ak_Passthrough, + Ak_LFE, + AK_Audio_Objects, + Ak_1_0, + Ak_2_0, + Ak_2_1, + Ak_3_0, + Ak_3_1, + Ak_4_0, + Ak_4_1, + Ak_5_0, + Ak_5_1, + Ak_7_1, + Ak_5_1_2, + Ak_7_1_2, + Ak_7_1_4, + Ak_Auro_9_1, + Ak_Auro_10_1, + Ak_Auro_11_1, + Ak_Auro_13_1, + Ak_Ambisonics_1st_order, + Ak_Ambisonics_2nd_order, + Ak_Ambisonics_3rd_order, + Ak_Ambisonics_4th_order, + Ak_Ambisonics_5th_order +}; + +UENUM(meta=(Bitflags, UseEnumValuesAsMaskValuesInEditor = "true")) +enum class AkSpeakerConfiguration +{ + Ak_Speaker_Front_Left = AK_SPEAKER_FRONT_LEFT, + Ak_Speaker_Front_Right = AK_SPEAKER_FRONT_RIGHT, + Ak_Speaker_Front_Center = AK_SPEAKER_FRONT_CENTER, + Ak_Speaker_Low_Frequency = AK_SPEAKER_LOW_FREQUENCY, + Ak_Speaker_Back_Left = AK_SPEAKER_BACK_LEFT, + Ak_Speaker_Back_Right = AK_SPEAKER_BACK_RIGHT, + Ak_Speaker_Back_Center = AK_SPEAKER_BACK_CENTER, + Ak_Speaker_Side_Left = AK_SPEAKER_SIDE_LEFT, + Ak_Speaker_Side_Right = AK_SPEAKER_SIDE_RIGHT, + Ak_Speaker_Top = AK_SPEAKER_TOP, + Ak_Speaker_Height_Front_Left = AK_SPEAKER_HEIGHT_FRONT_LEFT, + Ak_Speaker_Height_Front_Center = AK_SPEAKER_HEIGHT_FRONT_CENTER, + Ak_Speaker_Height_Front_Right = AK_SPEAKER_HEIGHT_FRONT_RIGHT, + Ak_Speaker_Height_Back_Left = AK_SPEAKER_HEIGHT_BACK_LEFT, + Ak_Speaker_Height_Back_Center = AK_SPEAKER_HEIGHT_BACK_CENTER, + Ak_Speaker_Height_Back_Right = AK_SPEAKER_HEIGHT_BACK_RIGHT, +}; + +UENUM(BlueprintType) +enum class AkMultiPositionType : uint8 +{ + SingleSource = 0, //AK::SoundEngine::MultiPositionType_SingleSource, + MultiSources = 1, //AK::SoundEngine::MultiPositionType_MultiSources, + MultiDirections = 2, //AK::SoundEngine::MultiPositionType_MultiDirections, +}; + +static_assert(static_cast(AkMultiPositionType::SingleSource) == AK::SoundEngine::MultiPositionType_SingleSource, "AkMultiPositionType::SingleSource does not correspond with its internal Wwise counterpart."); +static_assert(static_cast(AkMultiPositionType::MultiSources) == AK::SoundEngine::MultiPositionType_MultiSources, "AkMultiPositionType::MultiSources does not correspond with its internal Wwise counterpart."); +static_assert(static_cast(AkMultiPositionType::MultiDirections) == AK::SoundEngine::MultiPositionType_MultiDirections, "AkMultiPositionType::MultiDirections does not correspond with its internal Wwise counterpart."); + +UENUM(BlueprintType) +enum class AkActionOnEventType : uint8 +{ + // AK::SoundEngine::AkActionOnEventType_Stop + Stop = 0, + // AK::SoundEngine::AkActionOnEventType_Pause + Pause = 1, + // AK::SoundEngine::AkActionOnEventType_Resume + Resume = 2, + // AK::SoundEngine::AkActionOnEventType_Break + Break = 3, + // AK::SoundEngine::AkActionOnEventType_ReleaseEnvelope + ReleaseEnvelope = 4 +}; + +static_assert(static_cast(AkActionOnEventType::Stop) == AK::SoundEngine::AkActionOnEventType_Stop, "AkActionOnEventType::Stop does not correspond with its internal Wwise counterpart."); +static_assert(static_cast(AkActionOnEventType::Pause) == AK::SoundEngine::AkActionOnEventType_Pause, "AkActionOnEventType::Pause does not correspond with its internal Wwise counterpart."); +static_assert(static_cast(AkActionOnEventType::Resume) == AK::SoundEngine::AkActionOnEventType_Resume, "AkActionOnEventType::Resume does not correspond with its internal Wwise counterpart."); +static_assert(static_cast(AkActionOnEventType::Break) == AK::SoundEngine::AkActionOnEventType_Break, "AkActionOnEventType::Break does not correspond with its internal Wwise counterpart."); +static_assert(static_cast(AkActionOnEventType::ReleaseEnvelope) == AK::SoundEngine::AkActionOnEventType_ReleaseEnvelope, "AkActionOnEventType::ReleaseEnvelope does not correspond with its internal Wwise counterpart."); + +UENUM(BlueprintType) +enum class EAkCurveInterpolation : uint8 +{ + // Log3 + Log3 = 0, + // Sine + Sine = 1, + // Log1 + Log1 = 2, + // Inversed S Curve + InvSCurve = 3, + // Linear (Default) + Linear = 4, + // S Curve + SCurve = 5, + // Exp1 + Exp1 = 6, + // Reciprocal of sine curve + SineRecip = 7, + // Exp3 + Exp3 = 8, + // Update this value to reflect last curve available for fades + LastFadeCurve = 8, + // Constant ( not valid for fading values ) + Constant = 9 +}; + +static_assert(static_cast(EAkCurveInterpolation::Log3) == AkCurveInterpolation_Log3, "AkCurveInterpolation::Log3 does not correspond with its internal Wwise counterpart."); +static_assert(static_cast(EAkCurveInterpolation::Sine) == AkCurveInterpolation_Sine, "AkCurveInterpolation::Sine does not correspond with its internal Wwise counterpart."); +static_assert(static_cast(EAkCurveInterpolation::Log1) == AkCurveInterpolation_Log1, "AkCurveInterpolation::Log1 does not correspond with its internal Wwise counterpart."); +static_assert(static_cast(EAkCurveInterpolation::InvSCurve) == AkCurveInterpolation_InvSCurve, "AkCurveInterpolation::InvSCurve does not correspond with its internal Wwise counterpart."); +static_assert(static_cast(EAkCurveInterpolation::Linear) == AkCurveInterpolation_Linear, "AkCurveInterpolation::Linear does not correspond with its internal Wwise counterpart."); +static_assert(static_cast(EAkCurveInterpolation::SCurve) == AkCurveInterpolation_SCurve, "AkCurveInterpolation::SCurve does not correspond with its internal Wwise counterpart."); +static_assert(static_cast(EAkCurveInterpolation::Exp1) == AkCurveInterpolation_Exp1, "AkCurveInterpolation::Exp1 does not correspond with its internal Wwise counterpart."); +static_assert(static_cast(EAkCurveInterpolation::SineRecip) == AkCurveInterpolation_SineRecip, "AkCurveInterpolation::SineRecip does not correspond with its internal Wwise counterpart."); +static_assert(static_cast(EAkCurveInterpolation::Exp3) == AkCurveInterpolation_Exp3, "AkCurveInterpolation::Exp3 does not correspond with its internal Wwise counterpart."); +static_assert(static_cast(EAkCurveInterpolation::LastFadeCurve) == AkCurveInterpolation_LastFadeCurve, "AkCurveInterpolation::LastFadeCurve does not correspond with its internal Wwise counterpart."); +static_assert(static_cast(EAkCurveInterpolation::Constant) == AkCurveInterpolation_Constant, "AkCurveInterpolation::Constant does not correspond with its internal Wwise counterpart."); + +static_assert(AK_NotImplemented == 0, "AK_NotImplemented is not equal to 0, please change the value in the EAkResult enum"); +UENUM(BlueprintType) +enum class EAkResult : uint8 +{ + NotImplemented = 0 UMETA(ToolTip = "This feature is not implemented."), + Success = AK_Success UMETA(ToolTip = "The operation was successful."), + Fail = AK_Fail UMETA(ToolTip = "The operation failed."), + PartialSuccess = AK_PartialSuccess UMETA(ToolTip = "The operation succeeded partially."), + NotCompatible = AK_NotCompatible UMETA(ToolTip = "Incompatible formats."), + AlreadyConnected = AK_AlreadyConnected UMETA(ToolTip = "The stream is already connected to another node."), + InvalidFile = AK_InvalidFile UMETA(ToolTip = "An unexpected value causes the file to be invalid."), + AudioFileHeaderTooLarge = AK_AudioFileHeaderTooLarge UMETA(ToolTip = "The file header is too large."), + MaxReached = AK_MaxReached UMETA(ToolTip = "The maximum was reached."), + InvalidID = AK_InvalidID UMETA(ToolTip = "The ID is invalid."), + IDNotFound = AK_IDNotFound UMETA(ToolTip = "The ID was not found.", DisplayName = "ID Not Found"), + InvalidInstanceID = AK_InvalidInstanceID UMETA(ToolTip = "The InstanceID is invalid."), + NoMoreData = AK_NoMoreData UMETA(ToolTip = "No more data is available from the source."), + InvalidStateGroup = AK_InvalidStateGroup UMETA(ToolTip = "The StateGroup is not a valid channel."), + ChildAlreadyHasAParent = AK_ChildAlreadyHasAParent UMETA(ToolTip = "The child already has a parent.", DisplayName = "Child Already Has A Parent"), + InvalidLanguage = AK_InvalidLanguage UMETA(ToolTip = "The language is invalid (applies to the Low-Level I/O)."), + CannotAddItseflAsAChild = AK_CannotAddItseflAsAChild UMETA(ToolTip = "It is not possible to add itself as its own child.", DisplayName = "Cannot Add Itself As A Child"), + InvalidParameter = AK_InvalidParameter UMETA(ToolTip = "Something is not within bounds."), + ElementAlreadyInList = AK_ElementAlreadyInList UMETA(ToolTip = "The item could not be added because it was already in the list."), + PathNotFound = AK_PathNotFound UMETA(ToolTip = "This path is not known."), + PathNoVertices = AK_PathNoVertices UMETA(ToolTip = "Stuff in vertices before trying to start it."), + PathNotRunning = AK_PathNotRunning UMETA(ToolTip = "Only a running path can be paused."), + PathNotPaused = AK_PathNotPaused UMETA(ToolTip = "Only a paused path can be resumed."), + PathNodeAlreadyInList = AK_PathNodeAlreadyInList UMETA(ToolTip = "This path is already there."), + PathNodeNotInList = AK_PathNodeNotInList UMETA(ToolTip = "This path is not there."), + DataNeeded = AK_DataNeeded UMETA(ToolTip = "The consumer needs more."), + NoDataNeeded = AK_NoDataNeeded UMETA(ToolTip = "The consumer does not need more."), + DataReady = AK_DataReady UMETA(ToolTip = "The provider has available data."), + NoDataReady = AK_NoDataReady UMETA(ToolTip = "The provider does not have available data."), + InsufficientMemory = AK_InsufficientMemory UMETA(ToolTip = "Memory error."), + Cancelled = AK_Cancelled UMETA(ToolTip = "The requested action was cancelled (not an error)."), + UnknownBankID = AK_UnknownBankID UMETA(ToolTip = "Trying to load a bank using an ID which is not defined."), + BankReadError = AK_BankReadError UMETA(ToolTip = "Error while reading a bank."), + InvalidSwitchType = AK_InvalidSwitchType UMETA(ToolTip = "Invalid switch type (used with the switch container)"), + FormatNotReady = AK_FormatNotReady UMETA(ToolTip = "Source format not known yet."), + WrongBankVersion = AK_WrongBankVersion UMETA(ToolTip = "The bank version is not compatible with the current bank reader."), + FileNotFound = AK_FileNotFound UMETA(ToolTip = "File not found."), + DeviceNotReady = AK_DeviceNotReady UMETA(ToolTip = "IO device not ready (may be because the tray is open)."), + BankAlreadyLoaded = AK_BankAlreadyLoaded UMETA(ToolTip = "The bank load failed because the bank is already loaded."), + RenderedFX = AK_RenderedFX UMETA(ToolTip = "The effect on the node is rendered."), + ProcessNeeded = AK_ProcessNeeded UMETA(ToolTip = "A routine needs to be executed on some CPU."), + ProcessDone = AK_ProcessDone UMETA(ToolTip = "The executed routine has finished its execution."), + MemManagerNotInitialized = AK_MemManagerNotInitialized UMETA(ToolTip = "The memory manager should have been initialized at this point."), + StreamMgrNotInitialized = AK_StreamMgrNotInitialized UMETA(ToolTip = "The stream manager should have been initialized at this point."), + SSEInstructionsNotSupported = AK_SSEInstructionsNotSupported UMETA(ToolTip = "The machine does not support SSE instructions (required on PC)."), + Busy = AK_Busy UMETA(ToolTip = "The system is busy and could not process the request."), + UnsupportedChannelConfig = AK_UnsupportedChannelConfig UMETA(ToolTip = "Channel configuration is not supported in the current execution context."), + PluginMediaNotAvailable = AK_PluginMediaNotAvailable UMETA(ToolTip = "Plugin media is not available for effect."), + MustBeVirtualized = AK_MustBeVirtualized UMETA(ToolTip = "Sound was Not Allowed to play."), + CommandTooLarge = AK_CommandTooLarge UMETA(ToolTip = "SDK command is too large to fit in the command queue."), + RejectedByFilter = AK_RejectedByFilter UMETA(ToolTip = "A play request was rejected due to the MIDI filter parameters."), + InvalidCustomPlatformName = AK_InvalidCustomPlatformName UMETA(ToolTip = "Detecting incompatibility between Custom platform of banks and custom platform of connected application."), + DLLCannotLoad = AK_DLLCannotLoad UMETA(ToolTip = "Plugin DLL could not be loaded, either because it is not found or one dependency is missing."), + DLLPathNotFound = AK_DLLPathNotFound UMETA(ToolTip = "Plugin DLL search path could not be found."), + NoJavaVM = AK_NoJavaVM UMETA(ToolTip = "No Java VM provided in AkInitSettings."), + OpenSLError = AK_OpenSLError UMETA(ToolTip = "OpenSL returned an error. Check error log for more details."), + PluginNotRegistered = AK_PluginNotRegistered UMETA(ToolTip = "Plugin is not registered. Make sure to implement a AK::PluginRegistration class for it and use AK_STATIC_LINK_PLUGIN in the game binary."), + DataAlignmentError = AK_DataAlignmentError UMETA(ToolTip = "A pointer to audio data was not aligned to the platform's required alignment (check AkTypes.h in the platform-specific folder)."), +}; + +#define CHECK_AKRESULT_VALUE(ValueName) static_assert(AK_##ValueName == (uint32)EAkResult::ValueName, #ValueName " value has changed in AKRESULT, please update the EAkResult::" #ValueName " value"); +CHECK_AKRESULT_VALUE(NotImplemented); +CHECK_AKRESULT_VALUE(Success); +CHECK_AKRESULT_VALUE(Fail); +CHECK_AKRESULT_VALUE(PartialSuccess); +CHECK_AKRESULT_VALUE(NotCompatible); +CHECK_AKRESULT_VALUE(AlreadyConnected); +CHECK_AKRESULT_VALUE(InvalidFile); +CHECK_AKRESULT_VALUE(AudioFileHeaderTooLarge); +CHECK_AKRESULT_VALUE(MaxReached); +CHECK_AKRESULT_VALUE(InvalidID); +CHECK_AKRESULT_VALUE(IDNotFound); +CHECK_AKRESULT_VALUE(InvalidInstanceID); +CHECK_AKRESULT_VALUE(NoMoreData); +CHECK_AKRESULT_VALUE(InvalidStateGroup); +CHECK_AKRESULT_VALUE(ChildAlreadyHasAParent); +CHECK_AKRESULT_VALUE(InvalidLanguage); +CHECK_AKRESULT_VALUE(CannotAddItseflAsAChild); +CHECK_AKRESULT_VALUE(InvalidParameter); +CHECK_AKRESULT_VALUE(ElementAlreadyInList); +CHECK_AKRESULT_VALUE(PathNotFound); +CHECK_AKRESULT_VALUE(PathNoVertices); +CHECK_AKRESULT_VALUE(PathNotRunning); +CHECK_AKRESULT_VALUE(PathNotPaused); +CHECK_AKRESULT_VALUE(PathNodeAlreadyInList); +CHECK_AKRESULT_VALUE(PathNodeNotInList); +CHECK_AKRESULT_VALUE(DataNeeded); +CHECK_AKRESULT_VALUE(NoDataNeeded); +CHECK_AKRESULT_VALUE(DataReady); +CHECK_AKRESULT_VALUE(NoDataReady); +CHECK_AKRESULT_VALUE(InsufficientMemory); +CHECK_AKRESULT_VALUE(Cancelled); +CHECK_AKRESULT_VALUE(UnknownBankID); +CHECK_AKRESULT_VALUE(BankReadError); +CHECK_AKRESULT_VALUE(InvalidSwitchType); +CHECK_AKRESULT_VALUE(FormatNotReady); +CHECK_AKRESULT_VALUE(WrongBankVersion); +CHECK_AKRESULT_VALUE(FileNotFound); +CHECK_AKRESULT_VALUE(DeviceNotReady); +CHECK_AKRESULT_VALUE(BankAlreadyLoaded); +CHECK_AKRESULT_VALUE(RenderedFX); +CHECK_AKRESULT_VALUE(ProcessNeeded); +CHECK_AKRESULT_VALUE(ProcessDone); +CHECK_AKRESULT_VALUE(MemManagerNotInitialized); +CHECK_AKRESULT_VALUE(StreamMgrNotInitialized); +CHECK_AKRESULT_VALUE(SSEInstructionsNotSupported); +CHECK_AKRESULT_VALUE(Busy); +CHECK_AKRESULT_VALUE(UnsupportedChannelConfig); +CHECK_AKRESULT_VALUE(PluginMediaNotAvailable); +CHECK_AKRESULT_VALUE(MustBeVirtualized); +CHECK_AKRESULT_VALUE(CommandTooLarge); +CHECK_AKRESULT_VALUE(RejectedByFilter); +CHECK_AKRESULT_VALUE(InvalidCustomPlatformName); +CHECK_AKRESULT_VALUE(DLLCannotLoad); +CHECK_AKRESULT_VALUE(DLLPathNotFound); +CHECK_AKRESULT_VALUE(NoJavaVM); +CHECK_AKRESULT_VALUE(OpenSLError); +CHECK_AKRESULT_VALUE(PluginNotRegistered); +CHECK_AKRESULT_VALUE(DataAlignmentError); + + +/*============================================================================= + +Begin - Ak Callback Blueprint classes and structures. Known limitations: +- AkDynamicSequenceItemCallbackInfo is not exposed because Dynamic sequences are not part of this integration +- AkSpeakerVolumeMatrixCallbackInfo cannot be exposed to Blueprint because it has to be executed in the audio thread +- AkMusicPlaylistCallbackInfo cannot be exposed to Blueprint because it has to be executed in the audio thread + +=============================================================================*/ + +/// Type of callback. Used as a bitfield in methods AK::SoundEngine::PostEvent() and AK::SoundEngine::DynamicSequence::Open(). +UENUM(BlueprintType, meta = (Bitmask)) +enum class EAkCallbackType : uint8 +{ + EndOfEvent = 0 UMETA(ToolTip = "Callback triggered when reaching the end of an event. AkCallbackInfo can be cast to AkEventCallbackInfo."), + Marker = 2 UMETA(ToolTip = "Callback triggered when encountering a marker during playback. AkCallbackInfo can be cast to AkMarkerCallbackInfo."), + Duration = 3 UMETA(ToolTip = "Callback triggered when the duration of the sound is known by the sound engine. AkCallbackInfo can be cast to AkDurationCallbackInfo."), + + Starvation = 5 UMETA(ToolTip = "Callback triggered when playback skips a frame due to stream starvation. AkCallbackInfo can be cast to AkEventCallbackInfo."), + + MusicPlayStarted = 7 UMETA(ToolTip = "Callback triggered when a Play or Seek command has been executed (Seek commands are issued from AK::SoundEngine::SeekOnEvent()). Applies to objects of the Interactive-Music Hierarchy only. AkCallbackInfo can be cast to AkEventCallbackInfo."), + + MusicSyncBeat = 8 UMETA(ToolTip = "Enable notifications on Music Beat. AkCallbackInfo can be cast to AkMusicSyncCallbackInfo."), + MusicSyncBar = 9 UMETA(ToolTip = "Enable notifications on Music Bar. AkCallbackInfo can be cast to AkMusicSyncCallbackInfo."), + MusicSyncEntry = 10 UMETA(ToolTip = "Enable notifications on Music Entry Cue. AkCallbackInfo can be cast to AkMusicSyncCallbackInfo."), + MusicSyncExit = 11 UMETA(ToolTip = "Enable notifications on Music Exit Cue. AkCallbackInfo can be cast to AkMusicSyncCallbackInfo."), + MusicSyncGrid = 12 UMETA(ToolTip = "Enable notifications on Music Grid. AkCallbackInfo can be cast to AkMusicSyncCallbackInfo."), + MusicSyncUserCue = 13 UMETA(ToolTip = "Enable notifications on Music Custom Cue. AkCallbackInfo can be cast to AkMusicSyncCallbackInfo."), + MusicSyncPoint = 14 UMETA(ToolTip = "Enable notifications on Music switch transition synchronization point. AkCallbackInfo can be cast to AkMusicSyncCallbackInfo."), + + MIDIEvent = 16 UMETA(ToolTip = "Enable notifications for MIDI events. AkCallbackInfo can be cast to AkMIDIEventCallbackInfo."), +}; + +#define CHECK_CALLBACK_TYPE_VALUE(ValueName) static_assert(AK_##ValueName == (1 << (uint32)EAkCallbackType::ValueName), #ValueName " value has changed in AkCallbackType, please update the EAkCallbackType::" #ValueName " value"); +CHECK_CALLBACK_TYPE_VALUE(EndOfEvent); +CHECK_CALLBACK_TYPE_VALUE(Marker); +CHECK_CALLBACK_TYPE_VALUE(Duration); +CHECK_CALLBACK_TYPE_VALUE(Starvation); +CHECK_CALLBACK_TYPE_VALUE(MusicPlayStarted); +CHECK_CALLBACK_TYPE_VALUE(MusicSyncBeat); +CHECK_CALLBACK_TYPE_VALUE(MusicSyncBar); +CHECK_CALLBACK_TYPE_VALUE(MusicSyncEntry); +CHECK_CALLBACK_TYPE_VALUE(MusicSyncExit); +CHECK_CALLBACK_TYPE_VALUE(MusicSyncGrid); +CHECK_CALLBACK_TYPE_VALUE(MusicSyncUserCue); +CHECK_CALLBACK_TYPE_VALUE(MusicSyncPoint); +CHECK_CALLBACK_TYPE_VALUE(MIDIEvent); + +static_assert(AK::SoundEngine::Query::RTPCValue_Default == 0, "AK::SoundEngine::Query::RTPCValue_Default is not equal to 0, please change the value in the ERTPCValueType enum"); +UENUM(BlueprintType) +enum class ERTPCValueType : uint8 +{ + // Need to set hard-coded 0, or else UHT complains. + Default = 0, ///< The value is the Default RTPC. + Global = AK::SoundEngine::Query::RTPCValue_Global, ///< The value is the Global RTPC. + GameObject = AK::SoundEngine::Query::RTPCValue_GameObject, ///< The value is the game object specific RTPC. + PlayingID = AK::SoundEngine::Query::RTPCValue_PlayingID, ///< The value is the playing ID specific RTPC. + Unavailable = AK::SoundEngine::Query::RTPCValue_Unavailable ///< The value is not available for the RTPC specified. +}; + +class AkCallbackTypeHelpers +{ +public: + static UAkCallbackInfo* GetBlueprintableCallbackInfo(EAkCallbackType CallbackType, AkCallbackInfo* CallbackInfo); + static AkCallbackInfo* CopyWwiseCallbackInfo(AkCallbackType CallbackType, AkCallbackInfo* SourceCallbackInfo); + static AkCallbackType GetCallbackMaskFromBlueprintMask(int32 CallbackMask); + static EAkCallbackType GetBlueprintCallbackTypeFromAkCallbackType(AkCallbackType CallbackType); +}; + +/// Callback information structure used as base for all notifications handled by \ref AkCallbackFunc. +/// \sa +/// - AK::SoundEngine::PostEvent() +/// - \ref soundengine_events +UCLASS(BlueprintType) +class AKAUDIO_API UAkCallbackInfo : public UObject +{ + GENERATED_BODY() +public: + UAkCallbackInfo( class FObjectInitializer const & ObjectInitializer); + + static UAkCallbackInfo* Create(AkGameObjectID GameObjectID); + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo") + class UAkComponent * AkComponent = nullptr; + + virtual void Reset(); +}; + +USTRUCT(BlueprintType) +struct FAkChannelMask +{ + GENERATED_BODY() +public: + UPROPERTY(EditAnywhere, Category="Channel Mask", BlueprintReadWrite, meta = (Bitmask, BitmaskEnum = "/Script/AkAudio.AkSpeakerConfiguration")) + int32 ChannelMask = 0; +}; + +USTRUCT(BlueprintType) +struct FAkOutputSettings +{ + GENERATED_BODY() +public: + UPROPERTY(EditAnywhere, Category = "Output Settings", BlueprintReadWrite) + FString AudioDeviceShareSetName; + + UPROPERTY(EditAnywhere, Category = "Output Settings", BlueprintReadWrite) + int32 IdDevice = 0; + + UPROPERTY(EditAnywhere, Category = "Output Settings", BlueprintReadWrite, meta=(DisplayName="PanningRule")) + PanningRule PanRule = PanningRule::PanningRule_Speakers; + + UPROPERTY(EditAnywhere, Category = "Output Settings", BlueprintReadWrite) + AkChannelConfiguration ChannelConfig = AkChannelConfiguration::Ak_Parent; +}; + +/// Callback information structure corresponding to \ref AK_EndOfEvent, \ref AK_MusicPlayStarted and \ref AK_Starvation. +/// \sa +/// - AK::SoundEngine::PostEvent() +/// - \ref soundengine_events +UCLASS(BlueprintType) +class AKAUDIO_API UAkEventCallbackInfo : public UAkCallbackInfo +{ + GENERATED_BODY() +public: + UAkEventCallbackInfo(class FObjectInitializer const & ObjectInitializer); + + static UAkEventCallbackInfo* Create(AkEventCallbackInfo* AkEventCallbackInfo); + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|AkEvent") + int32 PlayingID = 0; ///< Playing ID of Event, returned by PostEvent() + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|AkEvent") + int32 EventID = 0; ///< Unique ID of Event, passed to PostEvent() +}; + + +// List of MIDI event types +static_assert(AK_MIDI_EVENT_TYPE_INVALID == 0, "AK_MIDI_EVENT_TYPE_INVALID is not equal to 0, please change the value in the EAkMidiEventType enum"); +UENUM(BlueprintType) +enum class EAkMidiEventType : uint8 +{ + // Need to set hard-coded 0, or else UHT complains. + AkMidiEventTypeInvalid = 0, + AkMidiEventTypeNoteOff = AK_MIDI_EVENT_TYPE_NOTE_OFF, + AkMidiEventTypeNoteOn = AK_MIDI_EVENT_TYPE_NOTE_ON, + AkMidiEventTypeNoteAftertouch = AK_MIDI_EVENT_TYPE_NOTE_AFTERTOUCH, + AkMidiEventTypeController = AK_MIDI_EVENT_TYPE_CONTROLLER, + AkMidiEventTypeProgramChange = AK_MIDI_EVENT_TYPE_PROGRAM_CHANGE, + AkMidiEventTypeChannelAftertouch = AK_MIDI_EVENT_TYPE_CHANNEL_AFTERTOUCH, + AkMidiEventTypePitchBend = AK_MIDI_EVENT_TYPE_PITCH_BEND, + AkMidiEventTypeSysex = AK_MIDI_EVENT_TYPE_SYSEX, + AkMidiEventTypeEscape = AK_MIDI_EVENT_TYPE_ESCAPE, + AkMidiEventTypeMeta = AK_MIDI_EVENT_TYPE_META, +}; + +// List of Continuous Controller (cc) values +static_assert(AK_MIDI_CC_BANK_SELECT_COARSE == 0, "AK_MIDI_CC_BANK_SELECT_COARSE is not equal to 0, please change the value in the EAkMidiCcValues enum"); +UENUM(BlueprintType) +enum class EAkMidiCcValues : uint8 +{ + // Need to set hard-coded 0, or else UHT complains. + AkMidiCcBankSelectCoarse = 0, + AkMidiCcModWheelCoarse = AK_MIDI_CC_MOD_WHEEL_COARSE, + AkMidiCcBreathCtrlCoarse = AK_MIDI_CC_BREATH_CTRL_COARSE, + AkMidiCcCtrl3Coarse = AK_MIDI_CC_CTRL_3_COARSE, + AkMidiCcFootPedalCoarse = AK_MIDI_CC_FOOT_PEDAL_COARSE, + AkMidiCcPortamentoCoarse = AK_MIDI_CC_PORTAMENTO_COARSE, + AkMidiCcDataEntryCoarse = AK_MIDI_CC_DATA_ENTRY_COARSE, + AkMidiCcVolumeCoarse = AK_MIDI_CC_VOLUME_COARSE, + AkMidiCcBalanceCoarse = AK_MIDI_CC_BALANCE_COARSE, + AkMidiCcCtrl9Coarse = AK_MIDI_CC_CTRL_9_COARSE, + AkMidiCcPanPositionCoarse = AK_MIDI_CC_PAN_POSITION_COARSE, + AkMidiCcExpressionCoarse = AK_MIDI_CC_EXPRESSION_COARSE, + AkMidiCcEffectCtrl1Coarse = AK_MIDI_CC_EFFECT_CTRL_1_COARSE, + AkMidiCcEffectCtrl2Coarse = AK_MIDI_CC_EFFECT_CTRL_2_COARSE, + AkMidiCcCtrl14Coarse = AK_MIDI_CC_CTRL_14_COARSE, + AkMidiCcCtrl15Coarse = AK_MIDI_CC_CTRL_15_COARSE, + AkMidiCcGenSlider1 = AK_MIDI_CC_GEN_SLIDER_1, + AkMidiCcGenSlider2 = AK_MIDI_CC_GEN_SLIDER_2, + AkMidiCcGenSlider3 = AK_MIDI_CC_GEN_SLIDER_3, + AkMidiCcGenSlider4 = AK_MIDI_CC_GEN_SLIDER_4, + AkMidiCcCtrl20Coarse = AK_MIDI_CC_CTRL_20_COARSE, + AkMidiCcCtrl21Coarse = AK_MIDI_CC_CTRL_21_COARSE, + AkMidiCcCtrl22Coarse = AK_MIDI_CC_CTRL_22_COARSE, + AkMidiCcCtrl23Coarse = AK_MIDI_CC_CTRL_23_COARSE, + AkMidiCcCtrl24Coarse = AK_MIDI_CC_CTRL_24_COARSE, + AkMidiCcCtrl25Coarse = AK_MIDI_CC_CTRL_25_COARSE, + AkMidiCcCtrl26Coarse = AK_MIDI_CC_CTRL_26_COARSE, + AkMidiCcCtrl27Coarse = AK_MIDI_CC_CTRL_27_COARSE, + AkMidiCcCtrl28Coarse = AK_MIDI_CC_CTRL_28_COARSE, + AkMidiCcCtrl29Coarse = AK_MIDI_CC_CTRL_29_COARSE, + AkMidiCcCtrl30Coarse = AK_MIDI_CC_CTRL_30_COARSE, + AkMidiCcCtrl31Coarse = AK_MIDI_CC_CTRL_31_COARSE, + AkMidiCcBankSelectFine = AK_MIDI_CC_BANK_SELECT_FINE, + AkMidiCcModWheelFine = AK_MIDI_CC_MOD_WHEEL_FINE, + AkMidiCcBreathCtrlFine = AK_MIDI_CC_BREATH_CTRL_FINE, + AkMidiCcCtrl3Fine = AK_MIDI_CC_CTRL_3_FINE, + AkMidiCcFootPedalFine = AK_MIDI_CC_FOOT_PEDAL_FINE, + AkMidiCcPortamentoFine = AK_MIDI_CC_PORTAMENTO_FINE, + AkMidiCcDataEntryFine = AK_MIDI_CC_DATA_ENTRY_FINE, + AkMidiCcVolumeFine = AK_MIDI_CC_VOLUME_FINE, + AkMidiCcBalanceFine = AK_MIDI_CC_BALANCE_FINE, + AkMidiCcCtrl9Fine = AK_MIDI_CC_CTRL_9_FINE, + AkMidiCcPanPositionFine = AK_MIDI_CC_PAN_POSITION_FINE, + AkMidiCcExpressionFine = AK_MIDI_CC_EXPRESSION_FINE, + AkMidiCcEffectCtrl1Fine = AK_MIDI_CC_EFFECT_CTRL_1_FINE, + AkMidiCcEffectCtrl2Fine = AK_MIDI_CC_EFFECT_CTRL_2_FINE, + AkMidiCcCtrl14Fine = AK_MIDI_CC_CTRL_14_FINE, + AkMidiCcCtrl15Fine = AK_MIDI_CC_CTRL_15_FINE, + + AkMidiCcCtrl20Fine = AK_MIDI_CC_CTRL_20_FINE, + AkMidiCcCtrl21Fine = AK_MIDI_CC_CTRL_21_FINE, + AkMidiCcCtrl22Fine = AK_MIDI_CC_CTRL_22_FINE, + AkMidiCcCtrl23Fine = AK_MIDI_CC_CTRL_23_FINE, + AkMidiCcCtrl24Fine = AK_MIDI_CC_CTRL_24_FINE, + AkMidiCcCtrl25Fine = AK_MIDI_CC_CTRL_25_FINE, + AkMidiCcCtrl26Fine = AK_MIDI_CC_CTRL_26_FINE, + AkMidiCcCtrl27Fine = AK_MIDI_CC_CTRL_27_FINE, + AkMidiCcCtrl28Fine = AK_MIDI_CC_CTRL_28_FINE, + AkMidiCcCtrl29Fine = AK_MIDI_CC_CTRL_29_FINE, + AkMidiCcCtrl30Fine = AK_MIDI_CC_CTRL_30_FINE, + AkMidiCcCtrl31Fine = AK_MIDI_CC_CTRL_31_FINE, + + AkMidiCcHoldPedal = AK_MIDI_CC_HOLD_PEDAL, + AkMidiCcPortamentoOnOff = AK_MIDI_CC_PORTAMENTO_ON_OFF, + AkMidiCcSustenutoPedal = AK_MIDI_CC_SUSTENUTO_PEDAL, + AkMidiCcSoftPedal = AK_MIDI_CC_SOFT_PEDAL, + AkMidiCcLegatoPedal = AK_MIDI_CC_LEGATO_PEDAL, + AkMidiCcHoldPedal2 = AK_MIDI_CC_HOLD_PEDAL_2, + + AkMidiCcSoundVariation = AK_MIDI_CC_SOUND_VARIATION, + AkMidiCcSoundTimbre = AK_MIDI_CC_SOUND_TIMBRE, + AkMidiCcSoundReleaseTime = AK_MIDI_CC_SOUND_RELEASE_TIME, + AkMidiCcSoundAttackTime = AK_MIDI_CC_SOUND_ATTACK_TIME, + AkMidiCcSoundBrightness = AK_MIDI_CC_SOUND_BRIGHTNESS, + AkMidiCcSoundCtrl6 = AK_MIDI_CC_SOUND_CTRL_6, + AkMidiCcSoundCtrl7 = AK_MIDI_CC_SOUND_CTRL_7, + AkMidiCcSoundCtrl8 = AK_MIDI_CC_SOUND_CTRL_8, + AkMidiCcSoundCtrl9 = AK_MIDI_CC_SOUND_CTRL_9, + AkMidiCcSoundCtrl10 = AK_MIDI_CC_SOUND_CTRL_10, + + AkMidiCcGeneralButton1 = AK_MIDI_CC_GENERAL_BUTTON_1, + AkMidiCcGeneralButton2 = AK_MIDI_CC_GENERAL_BUTTON_2, + AkMidiCcGeneralButton3 = AK_MIDI_CC_GENERAL_BUTTON_3, + AkMidiCcGeneralButton4 = AK_MIDI_CC_GENERAL_BUTTON_4, + + AkMidiCcReverbLevel = AK_MIDI_CC_REVERB_LEVEL, + AkMidiCcTremoloLevel = AK_MIDI_CC_TREMOLO_LEVEL, + AkMidiCcChorusLevel = AK_MIDI_CC_CHORUS_LEVEL, + AkMidiCcCelesteLevel = AK_MIDI_CC_CELESTE_LEVEL, + AkMidiCcPhaserLevel = AK_MIDI_CC_PHASER_LEVEL, + AkMidiCcDataButtonP1 = AK_MIDI_CC_DATA_BUTTON_P1, + AkMidiCcDataButtonM1 = AK_MIDI_CC_DATA_BUTTON_M1, + + AkMidiCcNonRegisterCoarse = AK_MIDI_CC_NON_REGISTER_COARSE, + AkMidiCcNonRegisterFine = AK_MIDI_CC_NON_REGISTER_FINE, + + AkMidiCcAllSoundOff = AK_MIDI_CC_ALL_SOUND_OFF, + AkMidiCcAllControllersOff = AK_MIDI_CC_ALL_CONTROLLERS_OFF, + AkMidiCcLocalKeyboard = AK_MIDI_CC_LOCAL_KEYBOARD, + AkMidiCcAllNotesOff = AK_MIDI_CC_ALL_NOTES_OFF, + AkMidiCcOmniModeOff = AK_MIDI_CC_OMNI_MODE_OFF, + AkMidiCcOmniModeOn = AK_MIDI_CC_OMNI_MODE_ON, + AkMidiCcOmniMonophonicOn = AK_MIDI_CC_OMNI_MONOPHONIC_ON, + AkMidiCcOmniPolyphonicOn = AK_MIDI_CC_OMNI_POLYPHONIC_ON, +}; + +USTRUCT(BlueprintType) +struct FAkMidiEventBase +{ + GENERATED_BODY() + FAkMidiEventBase() {} + + FAkMidiEventBase(AkMIDIEvent MIDIEvent) + : Type((EAkMidiEventType)MIDIEvent.byType) + , Chan(MIDIEvent.byChan) + {} + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|MIDI") + EAkMidiEventType Type = EAkMidiEventType::AkMidiEventTypeInvalid; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|MIDI") + uint8 Chan = 0; +}; + + +USTRUCT(BlueprintType) +struct FAkMidiGeneric : public FAkMidiEventBase +{ + GENERATED_BODY() + + FAkMidiGeneric() {} + FAkMidiGeneric(AkMIDIEvent MIDIEvent) + : FAkMidiEventBase(MIDIEvent) + , Param1(MIDIEvent.Gen.byParam1) + , Param2(MIDIEvent.Gen.byParam2) + {} + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|MIDI|Generic") + uint8 Param1 = 0; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|MIDI|Generic") + uint8 Param2 = 0; +}; + +USTRUCT(BlueprintType) +struct FAkMidiNoteOnOff : public FAkMidiEventBase +{ + GENERATED_BODY() + + FAkMidiNoteOnOff() {} + FAkMidiNoteOnOff(AkMIDIEvent MIDIEvent) + : FAkMidiEventBase(MIDIEvent) + , Note(MIDIEvent.NoteOnOff.byNote) + , Velocity(MIDIEvent.NoteOnOff.byVelocity) + {} + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|MIDI|NoteOnOff") + uint8 Note = 0; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|MIDI|NoteOnOff") + uint8 Velocity = 0; +}; + +USTRUCT(BlueprintType) +struct FAkMidiCc : public FAkMidiEventBase +{ + GENERATED_BODY() + + FAkMidiCc() {} + FAkMidiCc(AkMIDIEvent MIDIEvent) + : FAkMidiEventBase(MIDIEvent) + , Cc((EAkMidiCcValues)MIDIEvent.Cc.byCc) + , Value(MIDIEvent.Cc.byValue) + {} + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|MIDI|CC") + EAkMidiCcValues Cc = EAkMidiCcValues::AkMidiCcBankSelectCoarse; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|MIDI|CC") + uint8 Value = 0; +}; + +USTRUCT(BlueprintType) +struct FAkMidiPitchBend : public FAkMidiEventBase +{ + GENERATED_BODY() + + FAkMidiPitchBend() {} + FAkMidiPitchBend(AkMIDIEvent MIDIEvent) + : FAkMidiEventBase(MIDIEvent) + , ValueLsb(MIDIEvent.PitchBend.byValueLsb) + , ValueMsb(MIDIEvent.PitchBend.byValueMsb) + , FullValue((ValueMsb & 0x7F) << 7 | (ValueLsb & 0x7F)) + {} + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|MIDI|PitchBend") + uint8 ValueLsb = 0; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|MIDI|PitchBend") + uint8 ValueMsb = 0; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|MIDI|PitchBend") + int32 FullValue = 0; +}; + +USTRUCT(BlueprintType) +struct FAkMidiNoteAftertouch : public FAkMidiEventBase +{ + GENERATED_BODY() + + FAkMidiNoteAftertouch() {} + FAkMidiNoteAftertouch(AkMIDIEvent MIDIEvent) + : FAkMidiEventBase(MIDIEvent) + , Note(MIDIEvent.NoteAftertouch.byNote) + , Value(MIDIEvent.NoteAftertouch.byValue) + {} + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|MIDI|NoteAfterTouch") + uint8 Note = 0; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|MIDI|NoteAfterTouch") + uint8 Value = 0; +}; + +USTRUCT(BlueprintType) +struct FAkMidiChannelAftertouch : public FAkMidiEventBase +{ + GENERATED_BODY() + + FAkMidiChannelAftertouch() {} + FAkMidiChannelAftertouch(AkMIDIEvent MIDIEvent) + : FAkMidiEventBase(MIDIEvent) + , Value(MIDIEvent.NoteAftertouch.byValue) + {} + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|MIDI|ChannelAfterTouch") + uint8 Value = 0; +}; + +USTRUCT(BlueprintType) +struct FAkMidiProgramChange : public FAkMidiEventBase +{ + GENERATED_BODY() + + FAkMidiProgramChange() {} + FAkMidiProgramChange(AkMIDIEvent MIDIEvent) + : FAkMidiEventBase(MIDIEvent) + , ProgramNum(MIDIEvent.ProgramChange.byProgramNum) + {} + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|MIDI|ProgramChange") + uint8 ProgramNum = 0; +}; + +/// Callback information structure corresponding to \ref AK_MidiEvent +/// \sa +/// - AK::SoundEngine::PostEvent() +/// - \ref soundengine_events +UCLASS(BlueprintType) +class UAkMIDIEventCallbackInfo : public UAkEventCallbackInfo +{ + GENERATED_BODY() + +public: + UAkMIDIEventCallbackInfo(class FObjectInitializer const & ObjectInitializer); + static UAkMIDIEventCallbackInfo* Create(AkMIDIEventCallbackInfo* akCallbackInfo); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkCallbackInfo|MIDI") + EAkMidiEventType GetType(); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkCallbackInfo|MIDI") + uint8 GetChannel(); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkCallbackInfo|MIDI") + bool GetGeneric(FAkMidiGeneric& AsGeneric); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkCallbackInfo|MIDI") + bool GetNoteOn(FAkMidiNoteOnOff& AsNoteOn); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkCallbackInfo|MIDI") + bool GetNoteOff(FAkMidiNoteOnOff& AsNoteOff); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkCallbackInfo|MIDI") + bool GetCc(FAkMidiCc& AsCc); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkCallbackInfo|MIDI") + bool GetPitchBend(FAkMidiPitchBend& AsPitchBend); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkCallbackInfo|MIDI") + bool GetNoteAftertouch(FAkMidiNoteAftertouch& AsNoteAftertouch); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkCallbackInfo|MIDI") + bool GetChannelAftertouch(FAkMidiChannelAftertouch& AsChannelAftertouch); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkCallbackInfo|MIDI") + bool GetProgramChange(FAkMidiProgramChange& AsProgramChange); + +private: + AkMIDIEvent AkMidiEvent; +}; + +/// Callback information structure corresponding to \ref AK_Marker. +/// \sa +/// - AK::SoundEngine::PostEvent() +/// - \ref soundengine_events +/// - \ref soundengine_markers +UCLASS(BlueprintType) +class UAkMarkerCallbackInfo : public UAkEventCallbackInfo +{ + GENERATED_BODY() +public: + UAkMarkerCallbackInfo(class FObjectInitializer const & ObjectInitializer); + static UAkMarkerCallbackInfo* Create(AkMarkerCallbackInfo* akCallbackInfo); + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|Marker") + int32 Identifier = 0; ///< Cue point identifier + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|Marker") + int32 Position = 0; ///< Position in the cue point (unit: sample frames) + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|Marker") + FString Label; ///< Label of the marker, read from the file +}; + +/// Callback information structure corresponding to \ref AK_Duration. +/// \sa +/// - AK::SoundEngine::PostEvent() +/// - \ref soundengine_events +UCLASS(BlueprintType) +class UAkDurationCallbackInfo : public UAkEventCallbackInfo +{ + GENERATED_BODY() +public: + UAkDurationCallbackInfo(class FObjectInitializer const & ObjectInitializer); + static UAkDurationCallbackInfo* Create(AkDurationCallbackInfo* akCallbackInfo); + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|Duration") + float Duration = 0.f; ///< Duration of the sound (unit: milliseconds) + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|Duration") + float EstimatedDuration = 0.f; ///< Estimated duration of the sound depending on source settings such as pitch. (unit: milliseconds) + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|Duration") + int32 AudioNodeID = 0; ///< Audio Node ID of playing item + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|Duration") + int32 MediaID = 0; ///< Media ID of playing item. (corresponds to 'ID' attribute of 'File' element in SoundBank metadata file) + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|Duration") + bool bStreaming = false; ///< True if source is streaming, false otherwise. +}; + +/// Structure used to query info on active playing segments. +USTRUCT(BlueprintType) +struct FAkSegmentInfo +{ + GENERATED_BODY() + + FAkSegmentInfo() {} + FAkSegmentInfo(const AkSegmentInfo& segmentInfo) + : CurrentPosition(segmentInfo.iCurrentPosition) + , PreEntryDuration(segmentInfo.iPreEntryDuration) + , ActiveDuration(segmentInfo.iActiveDuration) + , PostExitDuration(segmentInfo.iPostExitDuration) + , RemainingLookAheadTime(segmentInfo.iRemainingLookAheadTime) + , BeatDuration(segmentInfo.fBeatDuration) + , BarDuration(segmentInfo.fBarDuration) + , GridDuration(segmentInfo.fGridDuration) + , GridOffset(segmentInfo.fGridOffset) + {} + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|SegmentInfo") + int32 CurrentPosition = 0; ///< Current position of the segment, relative to the Entry Cue, in milliseconds. Range is [-iPreEntryDuration, iActiveDuration+iPostExitDuration]. + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|SegmentInfo") + int32 PreEntryDuration = 0; ///< Duration of the pre-entry region of the segment, in milliseconds. + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|SegmentInfo") + int32 ActiveDuration = 0; ///< Duration of the active region of the segment (between the Entry and Exit Cues), in milliseconds. + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|SegmentInfo") + int32 PostExitDuration = 0; ///< Duration of the post-exit region of the segment, in milliseconds. + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|SegmentInfo") + int32 RemainingLookAheadTime = 0;///< Number of milliseconds remaining in the "looking-ahead" state of the segment, when it is silent but streamed tracks are being prefetched. + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|SegmentInfo") + float BeatDuration = 0.f; ///< Beat Duration in seconds. + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|SegmentInfo") + float BarDuration = 0.f; ///< Bar Duration in seconds. + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|SegmentInfo") + float GridDuration = 0.f; ///< Grid duration in seconds. + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|SegmentInfo") + float GridOffset = 0.f; ///< Grid offset in seconds. +}; + +UCLASS(BlueprintType) +class UAkMusicSyncCallbackInfo : public UAkCallbackInfo +{ + GENERATED_BODY() + +public: + UAkMusicSyncCallbackInfo(class FObjectInitializer const & ObjectInitializer); + static UAkMusicSyncCallbackInfo* Create(AkMusicSyncCallbackInfo* akCallbackInfo); + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|Music") + int32 PlayingID = 0; ///< Playing ID of Event, returned by PostEvent() + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|Music") + FAkSegmentInfo SegmentInfo; ///< Segment information corresponding to the segment triggering this callback. + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|Music") + EAkCallbackType MusicSyncType = EAkCallbackType::EndOfEvent; ///< Would be either \ref AK_MusicSyncEntry, \ref AK_MusicSyncBeat, \ref AK_MusicSyncBar, \ref AK_MusicSyncExit, \ref AK_MusicSyncGrid, \ref AK_MusicSyncPoint or \ref AK_MusicSyncUserCue. + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Audiokinetic|AkCallbackInfo|Music") + FString UserCueName; ///< Cue name (UTF-8 string). Set for notifications AK_MusicSyncUserCue. NULL if cue has no name. +}; + +DECLARE_DYNAMIC_DELEGATE_TwoParams(FOnAkPostEventCallback, EAkCallbackType, CallbackType, UAkCallbackInfo*, CallbackInfo); +DECLARE_DYNAMIC_DELEGATE_OneParam(FOnAkBankCallback, EAkResult, Result); +DECLARE_DYNAMIC_DELEGATE_OneParam(FOnSetCurrentAudioCultureCallback, bool, Succeeded); + +struct FPendingLatentActionValidityToken +{ + bool bValid = true; +}; + +class FAkPendingLatentAction: public FPendingLatentAction +{ +public: + + // Allows objects referencing this latent action to determine if it is still valid (not deleted) before accessing it + TSharedPtr ValidityToken; + + virtual void NotifyObjectDestroyed() override + { + // When the owning object is destroyed, the latent action is about to be deleted, so flag it as invalid + if (ValidityToken.IsValid()) + { + ValidityToken->bValid = false; + } + } +}; + +// Class used for Blueprint nodes blocking on EndOfEvent +class FWaitEndOfEventAction : public FAkPendingLatentAction +{ +public: + FName ExecutionFunction; + int32 OutputLink = 0; + FWeakObjectPtr CallbackTarget; + FThreadSafeBool EventFinished; + + + FWaitEndOfEventAction(const FLatentActionInfo& LatentInfo) + : ExecutionFunction(LatentInfo.ExecutionFunction) + , OutputLink(LatentInfo.Linkage) + , CallbackTarget(LatentInfo.CallbackTarget) + , EventFinished(false) + { + } + + virtual void UpdateOperation(FLatentResponse& Response) override + { + Response.FinishAndTriggerIf(EventFinished, ExecutionFunction, OutputLink, CallbackTarget); + } + +#if WITH_EDITOR + virtual FString GetDescription() const override + { + return TEXT("Waiting for posted AkEvent to end."); + } +#endif +}; + +// Class used for Blueprint nodes blocking on Bank Load +class FWaitEndBankAction : public FAkPendingLatentAction +{ +public: + FName ExecutionFunction; + int32 OutputLink = 0; + FWeakObjectPtr CallbackTarget; + FThreadSafeBool ActionDone; + + FWaitEndBankAction(const FLatentActionInfo& LatentInfo) + : ExecutionFunction(LatentInfo.ExecutionFunction) + , OutputLink(LatentInfo.Linkage) + , CallbackTarget(LatentInfo.CallbackTarget) + , ActionDone(false) + { + } + + virtual void UpdateOperation(FLatentResponse& Response) override + { + Response.FinishAndTriggerIf(ActionDone, ExecutionFunction, OutputLink, CallbackTarget); + } + +#if WITH_EDITOR + virtual FString GetDescription() const override + { + return TEXT("Waiting for AkBank to finish loading or unloading."); + } +#endif +}; + +class FSetCurrentAudioCultureAction : public FAkPendingLatentAction +{ +public: + FName ExecutionFunction; + int32 OutputLink = 0; + FWeakObjectPtr CallbackTarget; + FThreadSafeBool ActionDone; + + FSetCurrentAudioCultureAction(const FLatentActionInfo& LatentInfo) + : ExecutionFunction(LatentInfo.ExecutionFunction) + , OutputLink(LatentInfo.Linkage) + , CallbackTarget(LatentInfo.CallbackTarget) + , ActionDone(false) + { + } + + virtual void UpdateOperation(FLatentResponse& Response) override + { + Response.FinishAndTriggerIf(ActionDone, ExecutionFunction, OutputLink, CallbackTarget); + } + +#if WITH_EDITOR + virtual FString GetDescription() const override + { + return TEXT("Waiting for SetCurrentAudioCultureAsync to finish."); + } +#endif +}; + +/*============================================================================= +End - Ak Callback Blueprint classes and structures. +=============================================================================*/ + +/*============================================================================= + +Begin - AkExternalSources enums and structures. Known limitations: + - It is not possible to set external sources from memory using Blueprint + - It is not possible to stream external sources from disk + +=============================================================================*/ + +UENUM(BlueprintType) +enum class AkCodecId : uint8 +{ + ///< None: required default. + None = 0, + + ///< PCM encoding + PCM = AKCODECID_PCM, + + ///< ADPCM encoding + ADPCM = AKCODECID_ADPCM, + + ///< XMA encoding + XMA = AKCODECID_XMA, + + ///< Vorbis encoding + Vorbis = AKCODECID_VORBIS, + + ///< ATRAC-9 encoding + ATRAC9 = AKCODECID_ATRAC9, + + ///< OpusNX encoding + OpusNX = AKCODECID_OPUSNX, + + ///< Opus encoding + AkOpus = AKCODECID_AKOPUS, + + ///< WEM Opus encoding + AkOpusWEM = AKCODECID_AKOPUS_WEM +}; + +USTRUCT(BlueprintType) +struct FAkExternalSourceInfo +{ + GENERATED_BODY() + + /// Name of the source given in the project. (The Cookie ID) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Audiokinetic|AkExternalSourceInfo") + FString ExternalSrcName; + + /// Codec ID for the file. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Audiokinetic|AkExternalSourceInfo") + AkCodecId CodecID = AkCodecId::None; + + /// File path for the source. (Relative to ExternalSources folder in your sound bank folder) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Audiokinetic|AkExternalSourceInfo") + FString FileName; + + /// Hard link to the media asset to use, it can be either streamed or not using IsStreamed + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Audiokinetic|AkExternalSourceInfo") + UAkExternalMediaAsset* ExternalSourceAsset = nullptr; + + /// Is the ExternalSourceAsset streamed or not + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Audiokinetic|AkExternalSourceInfo") + bool IsStreamed = true; +}; + +struct FAkSDKExternalSourceArray : public TSharedFromThis +{ + FAkSDKExternalSourceArray() {} + FAkSDKExternalSourceArray(const TArray& BlueprintArray); + ~FAkSDKExternalSourceArray(); + + TArray ExternalSourceArray; +}; + +class UAkAudioEvent; +class FWaitEndOfEventAsyncAction : public FWaitEndOfEventAction +{ +public: + int32* PlayingID = nullptr; + TFuture FuturePlayingID; + UAkAudioEvent* AkEvent = nullptr; + bool bStopWhenAttachedToDestroyed = true; + + FWaitEndOfEventAsyncAction(const FLatentActionInfo& LatentInfo, int32* PlayingID) + : FWaitEndOfEventAction(LatentInfo) + , PlayingID(PlayingID) + { + } + + FWaitEndOfEventAsyncAction(const FLatentActionInfo& LatentInfo, int32* PlayingID, UAkAudioEvent* Event, bool StopWhenAttachedToDestroyed) + : FWaitEndOfEventAction(LatentInfo) + , PlayingID(PlayingID) + , AkEvent(Event) + , bStopWhenAttachedToDestroyed(StopWhenAttachedToDestroyed) + { + } + + virtual void UpdateOperation(FLatentResponse& Response) override; + +#if WITH_EDITOR + virtual FString GetDescription() const override + { + return TEXT("Waiting for async posted AkEvent to end."); + } +#endif +}; + + + +/*============================================================================= +End - AkExternalSources enums and structures. +=============================================================================*/ + +struct AkDeviceAndWorld +{ + class FAkAudioDevice* AkAudioDevice = nullptr; + class UWorld* CurrentWorld = nullptr; + AkDeviceAndWorld(AActor* in_pActor); + + + AkDeviceAndWorld(const UObject* in_pWorldContextObject); + + bool IsValid() const; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkGeometryComponent.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkGeometryComponent.h new file mode 100644 index 0000000..8fc7232 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkGeometryComponent.h @@ -0,0 +1,216 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#include "Platforms/AkUEPlatform.h" +#include "AkAcousticTexture.h" +#include "Components/SceneComponent.h" +#include "PhysicalMaterials/PhysicalMaterial.h" +#include "AkAcousticTextureSetComponent.h" +#include "AkGeometryData.h" +#include "AkGeometryComponent.generated.h" + +class UAkSettings; +#if UE_5_0_OR_LATER +class UMaterialInterface; +#endif + +DECLARE_DELEGATE(FOnRefreshDetails); + +UENUM() +enum class AkMeshType : uint8 +{ + StaticMesh, + CollisionMesh UMETA(DisplayName = "Simple Collision") +}; + +USTRUCT(BlueprintType) +struct FAkGeometrySurfaceOverride +{ + GENERATED_BODY() + + /** The Acoustic Texture represents the sound absorption on the surface of the geometry when a sound bounces off of it. + * If left to None, the mesh's physical material will be used to fetch an acoustic texture. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Geometry") + UAkAcousticTexture* AcousticTexture = nullptr; + + /** Enable Transmission Loss Override */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, DisplayName = "Enable Transmission Loss Override", Category = "Geometry") + bool bEnableOcclusionOverride = false; + + /** Transmission loss value to set when modeling sound transmission through geometry. Transmission is modeled only when there is no direct line of sight from the emitter to the listener. + * If there is more than one surface between the emitter and the listener, the maximum of each surface's transmission loss value is used. If the emitter and listener are in different rooms, the room's transmission loss value is taken into account. + * Valid range : (0.0, 1.0) + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Geometry", DisplayName = "Transmission Loss", meta = (EditCondition = "bEnableOcclusionOverride", ClampMin = "0.0", ClampMax = "1.0")) + float OcclusionValue = 1.f; + + void SetSurfaceArea(float area) { SurfaceArea = area; } + + FAkGeometrySurfaceOverride() + { + AcousticTexture = nullptr; + bEnableOcclusionOverride = false; + OcclusionValue = 1.f; + } + +private: + UPROPERTY() + float SurfaceArea = 0.0f; + +}; + +UCLASS(ClassGroup = Audiokinetic, BlueprintType, hidecategories = (Transform, Rendering, Mobility, LOD, Component, Activation, Tags), meta = (BlueprintSpawnableComponent)) +class AKAUDIO_API UAkGeometryComponent : public UAkAcousticTextureSetComponent +{ + GENERATED_BODY() + +public: + UAkGeometryComponent(const class FObjectInitializer& ObjectInitializer); + + /** Convert the mesh into a local representation suited for Wwise: + * a set of vertices, triangles, surfaces, acoustic textures and transmission loss values. */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkGeometry") + void ConvertMesh(); + + /** Add or update a geometry in Spatial Audio by sending the converted mesh, as well as the rest of the AkGeometryParams to Wwise. + * It is necessary to create at least one geometry instance for each geometry set that is to be used for diffraction and reflection simulation. See UpdateGeometry(). */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkGeometry") + void SendGeometry(); + + /** Add or update an instance of the geometry by sending the transform of this component to Wwise. + * A geometry instance is a unique instance of a geometry set with a specified transform (position, rotation and scale). + * It is necessary to create at least one geometry instance for each geometry set that is to be used for diffraction and reflection simulation. */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkGeometry") + void UpdateGeometry(); + + /** Remove the geometry and the corresponding instance from Wwise. */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkGeometry") + void RemoveGeometry(); + + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Geometry") + AkMeshType MeshType = AkMeshType::CollisionMesh; + + /** The Static Mesh's LOD to use */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Geometry", meta = (ClampMin = "0.0")) + int LOD = 0; + + /** The local distance in Unreal units between two vertices to be welded together. + * Any two vertices closer than this threshold will be treated as the same unique vertex and assigned the same position. + * Increasing this threshold decreases the number of gaps between triangles, resulting in a more continuous mesh and less sound leaking though, as well as eliminating triangles that are too small to be significant. + * Increasing this threshold also helps Spatial Audio's edge-finding algorithm to find more valid diffraction edges. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Geometry", meta = (ClampMin = "0.0")) + float WeldingThreshold = .0f; + + /** Override the acoustic properties of this mesh per material.*/ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Geometry", DisplayName = "Acoustic Properties Override") + TMap StaticMeshSurfaceOverride; + + /** Override the acoustic properties of the collision mesh.*/ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Geometry", DisplayName = "Acoustic Properties Override") + FAkGeometrySurfaceOverride CollisionMeshSurfaceOverride; + + /** Enable or disable geometric diffraction for this mesh. Check this box to have Wwise Spatial Audio generate diffraction edges on the geometry. The diffraction edges will be visible in the Wwise game object viewer when connected to the game. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Geometry") + bool bEnableDiffraction = false; + + /** Enable or disable geometric diffraction on boundary edges for this Geometry. Boundary edges are edges that are connected to only one triangle. Depending on the specific shape of the geometry, boundary edges may or may not be useful and it is beneficial to reduce the total number of diffraction edges to process. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Geometry", meta = (EditCondition = "bEnableDiffraction")) + bool bEnableDiffractionOnBoundaryEdges = false; + + /** (Optional) Associate this Surface Reflector Set with a Room. + * Associating a spatial audio geometry with a particular room will limit the scope in which the geometry is visible/accessible. Leave it to None and this geometry will have a global scope. + * It is recommended to associate geometry with a room when the geometry is (1) fully contained within the room (ie. not visible to other rooms except by portals), and (2) the room does not share geometry with other rooms. Doing so reduces the search space for ray casting performed by reflection and diffraction calculations. + * Take note that once one or more geometry sets are associated with a room, that room will no longer be able to access geometry that is in the global scope. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Geometry") + AActor* AssociatedRoom = nullptr; + + float GetSurfaceAreaSquaredMeters(const int& surfaceIndex) const; + + void UpdateStaticMeshOverride(); + +#if WITH_EDITORONLY_DATA + void SetOnRefreshDetails(const FOnRefreshDetails& in_delegate) { OnRefreshDetails = in_delegate; } + void ClearOnRefreshDetails() { OnRefreshDetails.Unbind(); } + const FOnRefreshDetails* GetOnRefreshDetails() { return &OnRefreshDetails; } + + bool bMeshMaterialChanged = false; +#endif + +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; + virtual void PostEditUndo() override; + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; +#endif + + virtual void OnRegister() override; + virtual void OnUnregister() override; + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) override; + virtual bool MoveComponentImpl( + const FVector & Delta, + const FQuat & NewRotation, + bool bSweep, + FHitResult * Hit, + EMoveComponentFlags MoveFlags, + ETeleportType Teleport) override; + virtual void Serialize(FArchive& Ar) override; + + void GetTexturesAndSurfaceAreas(TArray& textures, TArray& surfaceAreas) const override; + + /** Indicates whether this component was added dynamically by a sibling room component in order to send geometry to Wwise. */ + bool bWasAddedByRoom = false; + +private: + + UPrimitiveComponent* Parent = nullptr; + void InitializeParent(); + + void CalculateSurfaceArea(UStaticMeshComponent* StaticMeshComponent); + + void ConvertStaticMesh(UStaticMeshComponent* StaticMeshComponent, const UAkSettings* AkSettings); + void ConvertCollisionMesh(UPrimitiveComponent* PrimitiveComponent, const UAkSettings* AkSettings); + void UpdateMeshAndArchetype(UStaticMeshComponent* StaticMeshComponent); + void _UpdateStaticMeshOverride(UStaticMeshComponent* StaticMeshComponent); + + UPROPERTY() + FAkGeometryData GeometryData; + + UPROPERTY() + TMap SurfaceAreas; + + TMap PreviousStaticMeshSurfaceOverride; + + void BeginPlayInternal(); +#if WITH_EDITOR + virtual void HandleObjectsReplaced(const TMap& ReplacementMap) override; + bool bRequiresDeferredBeginPlay = false; + void RegisterAllTextureParamCallbacks() override; + bool ContainsTexture(const FGuid& textureID) override; +#endif + +#if WITH_EDITORONLY_DATA + FOnRefreshDetails OnRefreshDetails; + FDelegateHandle OnMeshMaterialChangedHandle; +#endif +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkGeometryData.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkGeometryData.h new file mode 100644 index 0000000..bb16f2e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkGeometryData.h @@ -0,0 +1,95 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkGeometryData.h: +=============================================================================*/ +#pragma once +#include "AkInclude.h" + +#include "AkAcousticTexture.h" + +#include "AkGeometryData.generated.h" + +class UPhysicalMaterial; + +USTRUCT() +struct FAkAcousticSurface +{ + GENERATED_BODY() + + UPROPERTY() + uint32 Texture = AK_INVALID_UNIQUE_ID; + + UPROPERTY(DisplayName = "Transmission Loss") + float Occlusion = .0f; + + UPROPERTY() + FString Name; +}; + +USTRUCT() +struct FAkTriangle +{ + GENERATED_BODY() + + UPROPERTY() + uint16 Point0 = 0; + + UPROPERTY() + uint16 Point1 = 0; + + UPROPERTY() + uint16 Point2 = 0; + + UPROPERTY() + uint16 Surface = 0; +}; + +USTRUCT() +struct FAkGeometryData +{ + GENERATED_BODY() + + void Clear() + { + Vertices.Empty(); + Surfaces.Empty(); + Triangles.Empty(); + ToOverrideAcousticTexture.Empty(); + ToOverrideOcclusion.Empty(); + } + + UPROPERTY() + TArray Vertices; + + UPROPERTY() + TArray Surfaces; + + UPROPERTY() + TArray Triangles; + + UPROPERTY() + TArray ToOverrideAcousticTexture; + + UPROPERTY(DisplayName = "To Override Transmission Loss") + TArray ToOverrideOcclusion; + + void AddBox(AkSurfIdx surfIdx, FVector center, FVector extent, FRotator rotation); + void AddSphere(AkSurfIdx surfIdx, const FVector& Center, const float Radius, int32 NumSides, int32 NumRings); + void AddCapsule(AkSurfIdx surfIdx, const FVector& Origin, const FVector& XAxis, const FVector& YAxis, const FVector& ZAxis, float Radius, float HalfHeight, int32 NumSides); +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkGroupValue.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkGroupValue.h new file mode 100644 index 0000000..df013ef --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkGroupValue.h @@ -0,0 +1,63 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkAudioType.h" +#include "Wwise/CookedData/WwiseGroupValueCookedData.h" +#include "Wwise/Loaded/WwiseLoadedGroupValue.h" +#include "Wwise/Info/WwiseGroupValueInfo.h" +#include "AkGroupValue.generated.h" + +UCLASS(Abstract) +class AKAUDIO_API UAkGroupValue : public UAkAudioType +{ + GENERATED_BODY() + +public: + UPROPERTY(Transient, VisibleAnywhere, Category = "AkGroupValue") + FWwiseGroupValueCookedData GroupValueCookedData; + +#if WITH_EDITORONLY_DATA + UPROPERTY(EditAnywhere, Category = "AkGroupValue") + FWwiseGroupValueInfo GroupValueInfo; +#endif + + UPROPERTY(meta =(Deprecated, DeprecationMessage="Use Group ID from Load Data. Used for migration from older versions.")) + uint32 GroupShortID_DEPRECATED = 0; + +public: + virtual void LoadData() override {LoadGroupValue();} + virtual void UnloadData(bool bAsync = false) override {UnloadGroupValue(bAsync);} + virtual AkUInt32 GetShortID() const override {return GroupValueCookedData.Id;} + AkUInt32 GetGroupID() const {return GroupValueCookedData.GroupId;} + +#if WITH_EDITORONLY_DATA + virtual FWwiseObjectInfo* GetInfoMutable() override {return &GroupValueInfo;} + virtual FWwiseObjectInfo GetInfo() const override {return GroupValueInfo;} + virtual void MigrateWwiseObjectInfo() override; + virtual void ValidateShortID(FWwiseObjectInfo& WwiseInfo) const override; + virtual bool SplitAssetName(FString& OutGroupName, FString& OutValueName) const; +#endif + +protected : + virtual void LoadGroupValue(){}; + void UnloadGroupValue(bool bAsync); + FWwiseLoadedGroupValue LoadedGroupValue; + + +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkInitBank.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkInitBank.h new file mode 100644 index 0000000..66aad9b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkInitBank.h @@ -0,0 +1,72 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkAudioType.h" +#include "Wwise/CookedData/WwiseInitBankCookedData.h" +#include "Wwise/Loaded/WwiseLoadedInitBank.h" + +#if WITH_EDITORONLY_DATA +#include "Wwise/Info/WwiseObjectInfo.h" +#endif + +#include "AkInitBank.generated.h" + + +UCLASS() +class AKAUDIO_API UAkInitBank : public UAkAudioType +{ + GENERATED_BODY() + +public: + UPROPERTY(Transient) + FWwiseInitBankCookedData InitBankCookedData; + +#if WITH_EDITORONLY_DATA + void PrepareCookedData(); +#endif + + TArray GetLanguages(); + +protected : + FWwiseLoadedInitBank LoadedInitBank; + + +public: + UAkInitBank():LoadedInitBank(nullptr){} + +#if WITH_EDITORONLY_DATA + void CookAdditionalFilesOverride(const TCHAR* PackageFilename, const ITargetPlatform* TargetPlatform, + TFunctionRef WriteAdditionalFile) override; + virtual void BeginCacheForCookedPlatformData(const ITargetPlatform* TargetPlatform) override; + virtual FWwiseObjectInfo* GetInfoMutable() override; + +#endif + + virtual void UnloadData(bool bAsync = false) override; + + void LoadInitBank(); + void UnloadInitBank(bool bAsync); + +protected: + void Serialize(FArchive& Ar) override; + +#if WITH_EDITORONLY_DATA + virtual void MigrateWwiseObjectInfo() override; +#endif +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkJobWorkerScheduler.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkJobWorkerScheduler.h new file mode 100644 index 0000000..b8e835e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkJobWorkerScheduler.h @@ -0,0 +1,35 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkJobWorkerScheduler.h: Audiokinetic job worker scheduler interface. +=============================================================================*/ + +#pragma once + +#include "AkInclude.h" +#include "Async/TaskGraphInterfaces.h" + +class AKAUDIO_API FAkJobWorkerScheduler +{ +public: + FAkJobWorkerScheduler() : uMaxExecutionTime(0) {} + + void InstallJobWorkerScheduler(uint32 uMaxExecutionTime, uint32 in_uMaxWorkerCount, AkJobMgrSettings& out_settings); + + uint32 uMaxExecutionTime; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkLateReverbComponent.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkLateReverbComponent.h new file mode 100644 index 0000000..6b846fb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkLateReverbComponent.h @@ -0,0 +1,216 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#include "Components/SceneComponent.h" +#include "AkReverbDescriptor.h" + +#if WITH_EDITOR +#include "Components/TextRenderComponent.h" +#endif + +#include "AkLateReverbComponent.generated.h" + +class UAkRoomComponent; +class UAkAcousticTextureSetComponent; + +UCLASS(ClassGroup = Audiokinetic, BlueprintType, hidecategories = (Transform, Rendering, Mobility, LOD, Component, Activation, Tags), meta = (BlueprintSpawnableComponent)) +class AKAUDIO_API UAkLateReverbComponent : public USceneComponent +{ + GENERATED_BODY() + +public: + UAkLateReverbComponent(const class FObjectInitializer& ObjectInitializer); + + /** + * Enable usage of the late reverb inside a volume. Additional properties are available in the Late Reverb category. + * The number of simultaneous AkReverbVolumes is configurable in the Unreal Editor Project Settings under Plugins > Wwise + * If this Late Reverb is applied to a Spatial Audio room, it will be active even if the maximum number of simultaneous reverb volumes (see integration settings) was reached. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Toggle, meta = (DisplayName = "Enable Late Reverb")) + bool bEnable = false; + + /** Maximum send level to the Wwise Auxiliary Bus associated to this AkReverbVolume */ + UPROPERTY(EditAnywhere,BlueprintReadWrite, Category = "Late Reverb", meta = (ClampMin = 0.0f, ClampMax = 1.0f, UIMin = 0.0f, UIMax = 1.0f)) + float SendLevel = .0f; + + /** Rate at which to fade in/out the SendLevel of the current Reverb Volume when entering/exiting it, in percentage per second (0.2 will make the fade time 5 seconds) */ + UPROPERTY(EditAnywhere,BlueprintReadWrite, Category = "Late Reverb" ,meta = (ClampMin = 0.0f, UIMin = 0.0f)) + float FadeRate = .0f; + + /** + * The precedence in which the AkReverbVolumes will be applied. In the case of overlapping volumes, only the ones + * with the highest priority are chosen. If two or more overlapping AkReverbVolumes have the same + * priority, the chosen AkReverbVolume is unpredictable. + * If this Late Reverb is applied to a Spatial Audio room, the room's priority will be used instead. + * Sound emitted by game objects in a room will always be sent to the room late reverb independently of other late reverbs in the scene. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Late Reverb") + float Priority = .0f; + + /** + * When enabled, the aux bus for this reverb component will be assigned automatically. This is done by estimating the decay time of the reverb produced by the parent Primitive Component, given its volume and surface area. + * This decay value is used to select an aux bus from the reverb aux bus assignment map in the integration settings. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Late Reverb") + bool AutoAssignAuxBus = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditCondition = "!AutoAssignAuxBus"), Category = "Late Reverb") + class UAkAuxBus* AuxBus = nullptr; + + /** Wwise Auxiliary Bus associated to this AkReverbVolume */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay, meta = (EditCondition = "!AutoAssignAuxBus"), Category = "Late Reverb") + FString AuxBusName; + + /** Get the AkAuxBusId associated to AuxBusName */ + uint32 GetAuxBusId() const; + + bool HasEffectOnLocation(const FVector& Location) const; + + bool LateReverbIsActive() const { return Parent && bEnable && !IsRunningCommandlet(); } + + virtual void BeginPlay() override; + virtual void BeginDestroy() override; + virtual void OnRegister() override; + virtual void OnUnregister() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual bool MoveComponentImpl( + const FVector & Delta, + const FQuat & NewRotation, + bool bSweep, + FHitResult * Hit, + EMoveComponentFlags MoveFlags, + ETeleportType Teleport) override; + virtual void OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) override; + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + virtual void PostLoad() override; + virtual void Serialize(FArchive& Ar) override; + +#if WITH_EDITOR + virtual void PreEditChange(FProperty* PropertyAboutToChange) override; + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; + virtual void OnAttachmentChanged() override; + void UpdateHFDampingEstimation(float hfDamping); + void UpdatePredelayEstimation(float predelay); + + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + virtual void InitializeComponent() override; + virtual void OnComponentCreated() override; + + void RegisterReverbInfoEnabledCallback(); + FDelegateHandle ShowReverbInfoChangedHandle; +#endif + + void UpdateDecayEstimation(float decay, float volume, float surfaceArea); + void UpdateRTPCs(const UAkRoomComponent* room) const; + + /** Set the component that will be used to estimate the HFDamping. For example, in a Blueprint that has a static mesh component with an AkGeometry child component, this function can be called in BeginPlay to associate that AkGeometry component with this reverb component. + * If this late reverb component has a sibling geometry component (or surface reflector set component), they will be associated automatically and there is no need to call this function. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|LateReverb|Reverb Parameter Estimation") + void AssociateAkTextureSetComponent(UAkAcousticTextureSetComponent* textureSetComponent); + +private: + friend class FAkAudioDevice; + + class UPrimitiveComponent* Parent; + + /** Save the manually assigned aux bus so we can recall it if auto-assign is disabled. */ + UPROPERTY() + class UAkAuxBus* AuxBusManual = nullptr; + + /** The component that will be used to estimate the HFDamping value. This will usually be an AkGeometryComponent. + * When the owning Actor is a Volume (as is the case for SpatialAudioVolume) this will be an AkSurfaceReflectorSetComponent. + */ + UAkAcousticTextureSetComponent* TextureSetComponent = nullptr; + + // Used to estimate the reverb parameters from the Primitive parent. + FAkReverbDescriptor ReverbDescriptor; + // Used to track when the reverb parameters need updated (on register, and when the size changes). + float SecondsSinceDecayUpdate = 0.0f; + bool DecayEstimationNeedsUpdate = false; + float SecondsSincePredelayUpdate = 0.0f; + bool PredelayEstimationNeedsUpdate = false; + + // Indicates that the component was added to the spatial index in AkAudioDevice. + bool IsIndexed = false; + + void RecalculateDecay(); + void RecalculatePredelay(); + + void InitializeParent(); + void ParentChanged(); + bool EncompassesPoint(FVector Point, float SphereRadius = 0.f, float* OutDistanceToPoint = nullptr) const; + // Used to track when the parameters of an Acoustic Texture asset change. + FDelegateHandle TextureParamChangedHandle; + +#if WITH_EDITOR + void HandleObjectsReplaced(const TMap& ReplacementMap); +#endif +#if WITH_EDITORONLY_DATA + static float TextVisualizerHeightOffset; + bool bTextStatusNeedsUpdate = false; + // The text visualizers display the values of the parameter estimations directly in the level (or blueprint editor). + UPROPERTY(SkipSerialization, NonTransactional) + UTextRenderComponent* TextVisualizerLabels = nullptr; + UPROPERTY(SkipSerialization, NonTransactional) + UTextRenderComponent* TextVisualizerValues = nullptr; + void UpdateTextVisualizerStatus(); + bool TextVisualizersInitialized() const; + FText GetValuesLabels() const; + void DestroyTextVisualizers(); + void InitTextVisualizers(); + void UpdateValuesLabels(); + bool WasSelected = false; + + FVector GetTextVisualizersLocation(); + + // Used to track when the Aux bus map, or the global RTPCs in the integration settings change. + void RegisterAuxBusMapChangedCallback(); + FDelegateHandle AuxBusChangedHandle; + void RegisterReverbRTPCChangedCallback(); + FDelegateHandle RTPCChangedHandle; + + UPROPERTY(VisibleAnywhere, Category = "Late Reverb|Reverb Parameter Estimation|Primitive Geometry") + float EnvironmentVolume = 0.0f; + + UPROPERTY(VisibleAnywhere, Category = "Late Reverb|Reverb Parameter Estimation|Primitive Geometry") + float SurfaceArea = 0.0f; + + /** An estimation of the T60 (the time taken for the sound pressure level to reduce by 60dB) for the reverb's environment, based on the primitive component to which the late reverb is attached. + * This T60 value can be used to automatically assign an aux bus using the Reverb Assignment Map in the integration settings, and/or to drive the Decay Estimate RTPC, also found in the integration settings. + * In order to use the global reverb RTPCs, the reverb component must have a sibling AkRoomComponent (in other words, a room component attached to the same Primitive parent). + */ + UPROPERTY(VisibleAnywhere, Category = "Late Reverb|Reverb Parameter Estimation") + float EnvironmentDecayEstimate = 0.0f; + + /** This value is driven by the acoustic textures used in an associated AkGeometryComponent or AkSurfaceReflectorSetComponent. It measures the average frequency bias of the damping. In other words, whether there is more high-frequency damping, more low-frequency damping, or uniform damping across frequencies. + * A value of 0.0 indicates uniform damping across all frequencies. A value > 0.0 indicates more damping for higher frequencies than lower frequencies. A value < 0.0 indicates more damping for lower frequencies than high frequencies. Average absorption values are calculated using each of the textures in the collection, weighted by their corresponding surface area. + * This value can be used to drive the HFDamping RTPC, found in the integration settings. + * In order to use the global reverb RTPCs, the reverb component must have a sibling AkRoomComponent (in other words, a room component attached to the same Primitive parent). + */ + UPROPERTY(VisibleAnywhere, Category = "Late Reverb|Reverb Parameter Estimation") + float HFDamping = 0.0f; + + /** An estimation of the time taken for the first reflection to reach the listener, based on the primitive component to which the late reverb is attached. + * This is estimated based on an emitter and listener being positioned at the centre of the parent primitive component. This value can be used to drive the Time To First Reflection RTPC, found in the integration settings. + * In order to use the global reverb RTPCs, the reverb component must have a sibling AkRoomComponent (in other words, a room component attached to the same Primitive parent). + */ + UPROPERTY(VisibleAnywhere, Category = "Late Reverb|Reverb Parameter Estimation") + float TimeToFirstReflection = 0.0f; +#endif +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkReverbDescriptor.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkReverbDescriptor.h new file mode 100644 index 0000000..e3623bf --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkReverbDescriptor.h @@ -0,0 +1,75 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkGameplayTypes.h" + +#include "AkReverbDescriptor.generated.h" + +class UAkAcousticTextureSetComponent; +class UAkLateReverbComponent; +class UAkRoomComponent; + +#define PARAM_ESTIMATION_UPDATE_PERIOD 0.1f + +/** + * FAkReverbDescriptor is used to estimate the reverb parameters of a primitive component, by calculating its volume and surface area, and using the 'sabine equation' to estimate the reverb tail. + * It also estimates the Time to First Reflection and the HFDamping. + */ +USTRUCT() +struct AKAUDIO_API FAkReverbDescriptor +{ + GENERATED_BODY() +public: + static double TriangleArea(const FVector& v1, const FVector& v2, const FVector& v3); + static float SignedVolumeOfTriangle(const FVector& p1, const FVector& p2, const FVector& p3); + + float PrimitiveVolume = 0.0f; + float PrimitiveSurfaceArea = 0.0f; + float T60Decay = 0.0f; + float HFDamping = 0.0f; + float TimeToFirstReflection = 0.0f; + + bool ShouldEstimateDecay() const; + bool ShouldEstimateDamping() const; + bool ShouldEstimatePredelay() const; + bool RequiresUpdates() const; + + void CalculateT60(); + void CalculateTimeToFirstReflection(); + void CalculateHFDamping(const UAkAcousticTextureSetComponent* textureSetComponent); + + void SetPrimitive(UPrimitiveComponent* primitive); + void SetReverbComponent(UAkLateReverbComponent* reverbComp); + + void UpdateAllRTPCs(const UAkRoomComponent* room) const; + +private: + UPROPERTY(Transient) + UPrimitiveComponent* Primitive = nullptr; + UAkLateReverbComponent* ReverbComponent = nullptr; + /* Looks for a room component attached to Primitive, whose room ID has been registered with wwise, and whose world is Game or PIE. + room will be null if no such room is found, or if there is no valid AkAudioDevice. + return true if a room is found (and there is a valid AkAudioDevice). */ + bool GetRTPCRoom(UAkRoomComponent*& room) const; + bool CanSetRTPCOnRoom(const UAkRoomComponent* room) const; + void UpdateDecayRTPC() const; + void UpdateDampingRTPC() const; + void UpdatePredelaytRTPC() const; + +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkReverbVolume.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkReverbVolume.h new file mode 100644 index 0000000..0cba62a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkReverbVolume.h @@ -0,0 +1,79 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkReverbVolume.h: +=============================================================================*/ +#pragma once + +#include "GameFramework/Volume.h" +#include "AkLateReverbComponent.h" +#include "AkReverbVolume.generated.h" + +/*------------------------------------------------------------------------------------ + AAkReverbVolume +------------------------------------------------------------------------------------*/ +UCLASS(hidecategories=(Advanced, Attachment, Volume), BlueprintType) +class AKAUDIO_API AAkReverbVolume : public AVolume +{ + GENERATED_BODY() + +public: + AAkReverbVolume(const class FObjectInitializer& ObjectInitializer); + +#if WITH_EDITOR + virtual FName GetCustomIconName() const override + { + static const FName IconName("ClassIcon.AkReverbVolume"); + return IconName; + } +#endif + + /** Whether this volume is currently enabled and able to affect sounds */ + UPROPERTY() + bool bEnabled_DEPRECATED = false; + + UPROPERTY() + class UAkAuxBus * AuxBus_DEPRECATED = nullptr; + + /** Wwise Auxiliary Bus associated to this AkReverbVolume */ + UPROPERTY() + FString AuxBusName_DEPRECATED; + + /** Maximum send level to the Wwise Auxiliary Bus associated to this AkReverbVolume */ + UPROPERTY() + float SendLevel_DEPRECATED = .0f; + + /** Rate at which to fade in/out the SendLevel of the current Reverb Volume when entering/exiting it, in percentage per second (0.2 will make the fade time 5 seconds) */ + UPROPERTY() + float FadeRate_DEPRECATED = .0f; + + /** + * The precedence in which the AkReverbVolumes will be applied. In the case of overlapping volumes, only the ones + * with the highest priority are chosen (the number of simultaneous AkReverbVolumes is configurable in the Unreal + * Editor Project Settings under Plugins > Wwise). If two or more overlapping AkReverbVolumes have the same + * priority, the chosen AkReverbVolume is unpredictable. + */ + UPROPERTY() + float Priority_DEPRECATED = .0f; + + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "LateReverb", meta = (ShowOnlyInnerProperties)) + UAkLateReverbComponent* LateReverbComponent = nullptr; + + virtual void PostLoad() override; + virtual void Serialize(FArchive& Ar) override; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkRoomComponent.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkRoomComponent.h new file mode 100644 index 0000000..db1d2c3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkRoomComponent.h @@ -0,0 +1,169 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#include "AkReverbDescriptor.h" +#include "GameFramework/Volume.h" +#include "AkGameObject.h" +#include "AkRoomComponent.generated.h" + +class UAkLateReverbComponent; + +UCLASS(ClassGroup = Audiokinetic, BlueprintType, hidecategories = (Transform, Rendering, Mobility, LOD, Component, Activation, Tags), meta = (BlueprintSpawnableComponent)) +class AKAUDIO_API UAkRoomComponent : public UAkGameObject +{ + GENERATED_BODY() + +public: + UAkRoomComponent(const class FObjectInitializer& ObjectInitializer); + + /** + * Enable room transmission feature. Additional properties are available in the Room category. + * If Enable Room begins as false, changing Enable Room during runtime will only have an effect + * if Room Is Dynamic = true. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Toggle, meta = (DisplayName = "Enable Room")) + bool bEnable = false; + + /** + * If true, the portal connections for this room can change during runtime when this room moves. + * For worlds containing many portals, this can be expensive. Note that this room's portal connections + * may still change, even when Room Is Dynamic = false, when dynamic portals are moved (i.e. when portals + * move who have bDynamic = true). + * When Room Is Dynamic = true, enabling and disabling rooms will have immediate effect, without needing + * to update emitters and/or listeners directly. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Toggle, meta = (DisplayName = "Room Is Dynamic")) + bool bDynamic = false; + + /** + * The precedence in which the Rooms will be applied. In the case of overlapping rooms, only the one + * with the highest priority is chosen. If two or more overlapping rooms have the same + * priority, the chosen room is unpredictable. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Room") + float Priority = .0f; + + /** + * Used to set the transmission loss value in wwise, on emitters in the room, when no audio paths to the + * listener are found via sound propagation in Wwise Spatial Audio. This value can be thought of as + * 'thickness', as it relates to how much sound energy is transmitted through the wall. Valid range 0.0f-1.0f, + * and is mapped to the occlusion curve as defined in the Wwise project. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, DisplayName = "Transmission Loss", Category = "Room", meta = (ClampMin=0.0f, ClampMax=1.0f, UIMin=0.0f, UIMax=1.0f)) + float WallOcclusion = .0f; + + /** + * Send level for sounds that are posted on the room. Valid range: (0.f-1.f). A value of 0 disables the aux send. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AkEvent", meta = (ClampMin = "0.0", ClampMax = "1.0")) + float AuxSendLevel = .0f; + + /** Automatically post the associated AkAudioEvent on BeginPlay */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AkEvent", SimpleDisplay) + bool AutoPost = false; + + /** Posts this game object's AkAudioEvent to Wwise, using this as the game object source */ + virtual int32 PostAssociatedAkEvent( + UPARAM(meta = (Bitmask, BitmaskEnum = "/Script/AkAudio.EAkCallbackType")) int32 CallbackMask, + const FOnAkPostEventCallback& PostEventCallback + ); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkRoomComponent") + UPrimitiveComponent* GetPrimitiveParent() const; + + /** Register a room in AK Spatial Audio. */ + void AddSpatialAudioRoom(); + + /** Modify a room in AK Spatial Audio. */ + void UpdateSpatialAudioRoom(); + + /** Remove a room from AK Spatial Audio */ + void RemoveSpatialAudioRoom(); + + bool HasEffectOnLocation(const FVector& Location) const; + + bool RoomIsActive() const; + + AkRoomID GetRoomID() const { return AkRoomID(this); } + + virtual void OnRegister() override; + virtual void OnUnregister() override; +#if WITH_EDITOR + virtual void BeginDestroy() override; + virtual void OnComponentCreated() override; + virtual void InitializeComponent() override; + virtual void PostLoad() override; + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; +#endif + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction * ThisTickFunction) override; + virtual void OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) override; + virtual bool MoveComponentImpl( + const FVector & Delta, + const FQuat & NewRotation, + bool bSweep, + FHitResult * Hit, + EMoveComponentFlags MoveFlags, + ETeleportType Teleport) override; + + FName GetName() const; + + virtual AkPlayingID PostAkEventByNameWithDelegate( + UAkAudioEvent* AkEvent, + const FString& in_EventName, + int32 CallbackMask, const FOnAkPostEventCallback& PostEventCallback); + + // Begin USceneComponent Interface + virtual void BeginPlay() override; + virtual void EndPlay(EEndPlayReason::Type EndPlayReason) override; + +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + + /** Set the geometry component that will be used to send the geometry of the room to Wwise. For example, in a Blueprint that has a static mesh component with an AkGeometry child component, this function can be called in BeginPlay to associate that AkGeometry component with this room component. + * If this room component has a sibling geometry component (or surface reflector set component), they will be associated automatically and there is no need to call this function. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkRoomComponent") + void SetGeometryComponent(UAkAcousticTextureSetComponent* textureSetComponent); + + FString GetRoomName(); + UAkLateReverbComponent* GetReverbComponent(); + +private: + class UPrimitiveComponent* Parent; + + UPROPERTY(Transient) + class UAkAcousticTextureSetComponent* GeometryComponent = nullptr; + + void InitializeParent(); + void GetRoomParams(AkRoomParams& outParams); + bool EncompassesPoint(FVector Point, float SphereRadius = 0.f, float* OutDistanceToPoint = nullptr) const; + void BeginPlayInternal(); + void SendGeometry(); + void RemoveGeometry(); + float SecondsSinceMovement = 0.0f; + bool Moving = false; +#if WITH_EDITOR + void HandleObjectsReplaced(const TMap& ReplacementMap); + bool bRequiresDeferredBeginPlay = false; + class UDrawRoomComponent* DrawRoomComponent = nullptr; + void RegisterVisEnabledCallback(); + void InitializeDrawComponent(); + void DestroyDrawComponent(); + FDelegateHandle ShowRoomsChangedHandle; +#endif +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkRtpc.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkRtpc.h new file mode 100644 index 0000000..a292bde --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkRtpc.h @@ -0,0 +1,54 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkAudioType.h" +#include "Wwise/CookedData/WwiseGameParameterCookedData.h" +#if WITH_EDITORONLY_DATA +#include "Wwise/Info/WwiseObjectInfo.h" +#endif +#include "AkRtpc.generated.h" + +UCLASS(BlueprintType) +class AKAUDIO_API UAkRtpc : public UAkAudioType +{ + GENERATED_BODY() + +public: + + UPROPERTY(Transient, VisibleAnywhere, Category = "AkRtpc") + FWwiseGameParameterCookedData GameParameterCookedData; + +#if WITH_EDITORONLY_DATA + UPROPERTY(EditAnywhere, Category = "AkRtpc") + FWwiseObjectInfo RtpcInfo; +#endif + +public : + void Serialize(FArchive& Ar) override; + virtual AkUInt32 GetShortID() const override {return GameParameterCookedData.ShortId;} + +#if WITH_EDITORONLY_DATA + virtual void LoadData() override { GetGameParameterCookedData(); } + void GetGameParameterCookedData(); + virtual void FillInfo() override; + virtual FWwiseObjectInfo* GetInfoMutable() override {return &RtpcInfo;} + virtual FWwiseObjectInfo GetInfo() const override {return RtpcInfo;} + virtual bool ObjectIsInSoundBanks() override; +#endif +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSettings.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSettings.h new file mode 100644 index 0000000..f7b793d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSettings.h @@ -0,0 +1,405 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkAcousticTexture.h" +#include "Engine/EngineTypes.h" +#include "PhysicalMaterials/PhysicalMaterial.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "WwiseDefines.h" +#include "AkRtpc.h" +#include "AkSettings.generated.h" + +class UAkInitBank; +class UAkAcousticTexture; + +DECLARE_MULTICAST_DELEGATE(FOnSoundBanksPathChangedDelegate); + + +/** Custom Collision Channel enum with an option to take the value from the Wwise Integration Settings (this follows a similar approach to that of EActorUpdateOverlapsMethod in Actor.h). */ +UENUM(BlueprintType) +enum EAkCollisionChannel +{ + EAKCC_WorldStatic UMETA(DisplayName = "WorldStatic"), + EAKCC_WorldDynamic UMETA(DisplayName = "WorldDynamic"), + EAKCC_Pawn UMETA(DisplayName = "Pawn"), + EAKCC_Visibility UMETA(DisplayName = "Visibility", TraceQuery = "1"), + EAKCC_Camera UMETA(DisplayName = "Camera", TraceQuery = "1"), + EAKCC_PhysicsBody UMETA(DisplayName = "PhysicsBody"), + EAKCC_Vehicle UMETA(DisplayName = "Vehicle"), + EAKCC_Destructible UMETA(DisplayName = "Destructible"), + EAKCC_UseIntegrationSettingsDefault UMETA(DisplayName = "Use Integration Settings Default"), // Use the default value specified by Wwise Integration Settings. +}; + +UENUM() +enum class EAkUnrealAudioRouting +{ + Custom UMETA(DisplayName = "Default", ToolTip = "Custom Unreal audio settings set up by the developer"), + Separate UMETA(DisplayName = "Both Wwise and Unreal audio", ToolTip = "Use default Unreal audio at the same time than Wwise SoundEngine (might be incompatible with some platforms)"), + AudioLink UMETA(DisplayName = "Route through AudioLink [UE5.1]", ToolTip = "Use WwiseAudioLink to route all Unreal audio sources to Wwise SoundEngine Inputs (requires Unreal Engine 5.1)"), + AudioMixer UMETA(DisplayName = "Route through AkAudioMixer", ToolTip = "Use AkAudioMixer to route Unreal submixes to a Wwise SoundEngine Input"), + EnableWwiseOnly UMETA(DisplayName = "Enable Wwise SoundEngine only", ToolTip = "Only use Wwise SoundEngine, and disable Unreal audio"), + EnableUnrealOnly UMETA(DisplayName = "Enable Unreal Audio only", ToolTip = "Only use Unreal audio, and disable Wwise SoundEngine") +}; + +USTRUCT() +struct FAkGeometrySurfacePropertiesToMap +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, Category = "AkGeometry Surface Properties Map") + TSoftObjectPtr AcousticTexture = nullptr; + + UPROPERTY(EditAnywhere, DisplayName = "Transmission Loss", Category = "AkGeometry Surface Properties Map", meta = (ClampMin = "0.0", ClampMax = "1.0")) + float OcclusionValue = 1.f; + + bool operator==(const FAkGeometrySurfacePropertiesToMap& Rhs) const + { + if (OcclusionValue != Rhs.OcclusionValue) + { + return false; + } + if (!AcousticTexture.IsValid() != !Rhs.AcousticTexture.IsValid()) + { + return false; + } + if (!AcousticTexture.IsValid()) + { + return true; + } + return AcousticTexture->GetFName() == Rhs.AcousticTexture->GetFName(); + } +}; + +struct AkGeometrySurfaceProperties +{ + UAkAcousticTexture* AcousticTexture = nullptr; + float OcclusionValue = 1.f; +}; + +USTRUCT() +struct FAkAcousticTextureParams +{ + GENERATED_BODY() + UPROPERTY() + FVector4 AbsorptionValues = FVector4(FVector::ZeroVector, 0.0f); + uint32 shortID = 0; + + float AbsorptionLow() const { return AbsorptionValues[0]; } + float AbsorptionMidLow() const { return AbsorptionValues[1]; } + float AbsorptionMidHigh() const { return AbsorptionValues[2]; } + float AbsorptionHigh() const { return AbsorptionValues[3]; } + + TArray AsTArray() const { return { AbsorptionLow(), AbsorptionMidLow(), AbsorptionMidHigh(), AbsorptionHigh() }; } +}; + +#define AK_MAX_AUX_PER_OBJ 4 + +DECLARE_EVENT(UAkSettings, ActivatedNewAssetManagement); +DECLARE_EVENT(UAkSettings, ShowRoomsPortalsChanged); +DECLARE_EVENT(UAkSettings, ShowReverbInfoChanged) +DECLARE_EVENT(UAkSettings, AuxBusAssignmentMapChanged); +DECLARE_EVENT(UAkSettings, ReverbRTPCChanged); +DECLARE_EVENT_TwoParams(UAkSettings, SoundDataFolderChanged, const FString&, const FString&); +DECLARE_EVENT_OneParam(UAkSettings, AcousticTextureParamsChanged, const FGuid&) + +UCLASS(config = Game, defaultconfig) +class AKAUDIO_API UAkSettings : public UObject +{ + GENERATED_BODY() + +public: + UAkSettings(const FObjectInitializer& ObjectInitializer); + ~UAkSettings(); + + /** + Converts between EAkCollisionChannel and ECollisionChannel. Returns Wwise Integration Settings default if CollisionChannel == UseIntegrationSettingsDefault. Otherwise, casts CollisionChannel to ECollisionChannel. + */ + static ECollisionChannel ConvertFitToGeomCollisionChannel(EAkCollisionChannel CollisionChannel); + + /** + Converts between EAkCollisionChannel and ECollisionChannel. Returns Wwise Integration Settings default if CollisionChannel == UseIntegrationSettingsDefault. Otherwise, casts CollisionChannel to ECollisionChannel. + */ + static ECollisionChannel ConvertOcclusionCollisionChannel(EAkCollisionChannel CollisionChannel); + + // The maximum number of reverb auxiliary sends that will be simultaneously applied to a sound source + // Reverbs from a Spatial Audio room will be active even if this maximum is reached. + UPROPERTY(Config, EditAnywhere, DisplayName = "Max Simultaneous Reverb", Category="Reverb") + uint8 MaxSimultaneousReverbVolumes = AK_MAX_AUX_PER_OBJ; + + // Wwise Project Path + UPROPERTY(Config, EditAnywhere, Category="Installation", meta=(FilePathFilter="wproj", AbsolutePath)) + FFilePath WwiseProjectPath; + + // Where the Sound Data will be generated in the Content Folder + UPROPERTY() + FDirectoryPath WwiseSoundDataFolder; + + UPROPERTY(Config, EditAnywhere, Category="Installation", meta=( AbsolutePath)) + FDirectoryPath GeneratedSoundBanksFolder; + + //Where wwise .bnk and .wem files will be copied to when staging files during cooking + UPROPERTY(Config, EditAnywhere, Category = "Cooking", meta=(RelativeToGameContentDir)) + FDirectoryPath WwiseStagingDirectory = {TEXT("WwiseAudio")}; + + //Used to track whether SoundBanks have been transferred to Wwise after migration to 2022.1 (or later) + UPROPERTY(Config) + bool bSoundBanksTransfered = false; + + //Used after migration to track whether assets have been re-serialized after migration to 2022.1 (or later) + UPROPERTY(Config) + bool bAssetsMigrated = false; + + //Used after migration to track whether project settings have been updated after migration to 2022.1 (or later) + UPROPERTY(Config) + bool bProjectMigrated = false; + + UPROPERTY(Config) + bool bAutoConnectToWAAPI_DEPRECATED = false; + + // Default value for Occlusion Collision Channel when creating a new Ak Component. + UPROPERTY(Config, EditAnywhere, Category = "Occlusion") + TEnumAsByte DefaultOcclusionCollisionChannel = ECollisionChannel::ECC_Visibility; + + // Default value for Collision Channel when fitting Ak Acoustic Portals and Ak Spatial Audio Volumes to surrounding geometry. + UPROPERTY(Config, EditAnywhere, Category = "Fit To Geometry") + TEnumAsByte DefaultFitToGeometryCollisionChannel = ECollisionChannel::ECC_WorldStatic; + + // PhysicalMaterial to AcousticTexture and Occlusion Value Map + UPROPERTY(Config, EditAnywhere, EditFixedSize, Category = "AkGeometry Surface Properties Map") + TMap, FAkGeometrySurfacePropertiesToMap> AkGeometryMap; + + // Global surface absorption value to use when estimating environment decay value. Acts as a global scale factor for the decay estimations. Defaults to 0.5. + UPROPERTY(Config, EditAnywhere, Category = "Reverb Assignment Map", meta = (ClampMin = 0.1f, ClampMax = 1.0f, UIMin = 0.1f, UIMax = 1.0f)) + float GlobalDecayAbsorption = .5f; + + // Default reverb aux bus to apply to rooms + UPROPERTY(Config, EditAnywhere, Category = "Reverb Assignment Map") + TSoftObjectPtr DefaultReverbAuxBus = nullptr; + + // RoomDecay to AuxBusID Map. Used to automatically assign aux bus ids to rooms depending on their volume and surface area. + UPROPERTY(Config, EditAnywhere, Category = "Reverb Assignment Map") + TMap> EnvironmentDecayAuxBusMap; + + UPROPERTY(Config, EditAnywhere, Category = "Reverb Assignment Map|RTPCs") + FString HFDampingName = ""; + + UPROPERTY(Config, EditAnywhere, Category = "Reverb Assignment Map|RTPCs") + FString DecayEstimateName = ""; + + UPROPERTY(Config, EditAnywhere, Category = "Reverb Assignment Map|RTPCs") + FString TimeToFirstReflectionName = ""; + + UPROPERTY(Config, EditAnywhere, Category = "Reverb Assignment Map|RTPCs") + TSoftObjectPtr HFDampingRTPC = nullptr; + + UPROPERTY(Config, EditAnywhere, Category = "Reverb Assignment Map|RTPCs") + TSoftObjectPtr DecayEstimateRTPC = nullptr; + + UPROPERTY(Config, EditAnywhere, Category = "Reverb Assignment Map|RTPCs") + TSoftObjectPtr TimeToFirstReflectionRTPC = nullptr; + + // Input event associated with the Wwise Audio Input + UPROPERTY(Config, EditAnywhere, Category = "Initialization") + TSoftObjectPtr AudioInputEvent = nullptr; + + UPROPERTY(Config) + TMap AcousticTextureParamsMap; + + // When generating the event data, the media contained in switch containers will be split by state/switch value + // and only loaded if the state/switch value are currently loaded + UPROPERTY(Config, meta = (Deprecated, DeprecationMessage="Setting now exists for each AK Audio Event")) + bool SplitSwitchContainerMedia = false; + + //Deprecated in 2022.1 + //Used in migration from previous versions + UPROPERTY(Config) + bool SplitMediaPerFolder= false; + + // Deprecated in 2022.1 + //Used in migration from previous versions + UPROPERTY(Config) + bool UseEventBasedPackaging= false; + + // Commit message that GenerateSoundBanksCommandlet will use + UPROPERTY() + FString CommandletCommitMessage = TEXT("Unreal Wwise Sound Data auto-generation"); + + UPROPERTY(Config, EditAnywhere, Category = "Localization") + TMap UnrealCultureToWwiseCulture; + + // When an asset is dragged from the Wwise Browser, assets are created by default in this path. + UPROPERTY(Config, EditAnywhere, Category = "Asset Creation") + FString DefaultAssetCreationPath = "/Game/WwiseAudio"; + + // The unique Init Bank for the Wwise project. This contains the basic information necessary for properly setting up the SoundEngine. + UPROPERTY(Config, EditAnywhere, Category = "Initialization") + TSoftObjectPtr InitBank; + + // Routing Audio from Unreal Audio to Wwise Sound Engine + UPROPERTY(Config, EditAnywhere, Category = "Initialization", DisplayName = "Unreal Audio Routing (Experimental)", meta=(ConfigRestartRequired=true)) + EAkUnrealAudioRouting AudioRouting = EAkUnrealAudioRouting::Custom; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization", DisplayName = "Wwise SoundEngine Enabled (Experimental)", meta=(ConfigRestartRequired=true, EditCondition="AudioRouting == EAkUnrealAudioRouting::Custom")) + bool bWwiseSoundEngineEnabled = true; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization", DisplayName = "Wwise AudioLink Enabled (Experimental)", meta=(ConfigRestartRequired=true, EditCondition="AudioRouting == EAkUnrealAudioRouting::Custom")) + bool bWwiseAudioLinkEnabled = false; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization", DisplayName = "AkAudioMixer Enabled (Experimental)", meta=(ConfigRestartRequired=true, EditCondition="AudioRouting == EAkUnrealAudioRouting::Custom")) + bool bAkAudioMixerEnabled = false; + + UPROPERTY(Config) + bool AskedToUseNewAssetManagement_DEPRECATED = false; + + UPROPERTY(Config) + bool bEnableMultiCoreRendering_DEPRECATED = false; + + UPROPERTY(Config) + bool MigratedEnableMultiCoreRendering = false; + + UPROPERTY(Config) + bool FixupRedirectorsDuringMigration = false; + + UPROPERTY(Config) + FDirectoryPath WwiseWindowsInstallationPath_DEPRECATED; + + UPROPERTY(Config) + FFilePath WwiseMacInstallationPath_DEPRECATED; + + static FString DefaultSoundDataFolder; + + virtual void PostInitProperties() override; + + bool ReverbRTPCsInUse() const; + bool DecayRTPCInUse() const; + bool DampingRTPCInUse() const; + bool PredelayRTPCInUse() const; + + bool GetAssociatedAcousticTexture(const UPhysicalMaterial* physMaterial, UAkAcousticTexture*& acousticTexture) const; + bool GetAssociatedOcclusionValue(const UPhysicalMaterial* physMaterial, float& occlusionValue) const; + +#if WITH_EDITOR + bool UpdateGeneratedSoundBanksPath(); + bool UpdateGeneratedSoundBanksPath(FString Path); + bool GeneratedSoundBanksPathExists() const; + bool AreSoundBanksGenerated() const; + void RefreshAcousticTextureParams() const; +#if AK_SUPPORT_WAAPI + /** This needs to be called after the waapi client has been initialized, which happens after AkSettings is constructed. */ + void InitWaapiSync(); + /** Set the color of a UAkAcousticTexture asset using a color from the UnrealWwiseObjectColorPalette (this is the same as the 'dark theme' in Wwise Authoring). Send a colorIndex of -1 to use the 'unset' color. */ + void SetTextureColor(FGuid textureID, int colorIndex); +#endif + void RemoveSoundDataFromAlwaysStageAsUFS(const FString& SoundDataPath); + void RemoveSoundDataFromAlwaysCook(const FString& SoundDataPath); + void EnsurePluginContentIsInAlwaysCook() const; + void InitAkGeometryMap(); + void DecayAuxBusMapChanged(); + void SortDecayKeys(); + static float MinimumDecayKeyDistance; +#endif + +protected: +#if WITH_EDITOR + virtual void PostEditChangeProperty( struct FPropertyChangedEvent& PropertyChangedEvent ) override; + virtual void PreEditChange(FProperty* PropertyAboutToChange) override; +#endif + +private: +#if WITH_EDITOR + FString PreviousWwiseProjectPath; + FString PreviousWwiseGeneratedSoundBankFolder; + bool bTextureMapInitialized = false; + TMap< UPhysicalMaterial*, UAkAcousticTexture* > TextureMapInternal; + FAssetRegistryModule* AssetRegistryModule; + + void OnAssetAdded(const FAssetData& NewAssetData); + void OnAssetRemoved(const struct FAssetData& AssetData); + void FillAkGeometryMap(const TArray& PhysicalMaterials, const TArray& AcousticTextureAssets); + void UpdateAkGeometryMap(); + void SanitizeProjectPath(FString& Path, const FString& PreviousPath, const FText& DialogMessage); + void OnAudioRoutingUpdate(); + + bool bAkGeometryMapInitialized = false; + TMap< UPhysicalMaterial*, UAkAcousticTexture* > PhysicalMaterialAcousticTextureMap; + TMap< UPhysicalMaterial*, float > PhysicalMaterialOcclusionMap; + + // This is used to track which key has changed and restrict its value between the two neighbouring keys + TMap> PreviousDecayAuxBusMap; + +#if AK_SUPPORT_WAAPI + TMap> WaapiTextureSubscriptions; + TMap WaapiTextureColorSubscriptions; + TMap WaapiTextureColorOverrideSubscriptions; + FDelegateHandle WaapiProjectLoadedHandle; + FDelegateHandle WaapiConnectionLostHandle; +#endif +#endif + +public: + bool bRequestRefresh = false; + const FAkAcousticTextureParams* GetTextureParams(const uint32& shortID) const; +#if WITH_EDITOR + void ClearAkRoomDecayAuxBusMap(); + void InsertDecayKeyValue(const float& decayKey); + void SetAcousticTextureParams(const FGuid& textureID, const FAkAcousticTextureParams& params); + void ClearTextureParamsMap(); +#if AK_SUPPORT_WAAPI + void WaapiProjectLoaded(); + void WaapiDisconnected(); + void RegisterWaapiTextureCallback(const FGuid& textureID); + void UnregisterWaapiTextureCallback(const FGuid& textureID); + void ClearWaapiTextureCallbacks(); + /** Use WAAPI to query the absorption params for a given texture and store them in the texture params map. */ + void UpdateTextureParams(const FGuid& textureID); + /** Use WAAPI to query the color for a given texture and Update the corresponding UAkAcousticTexture asset. */ + void UpdateTextureColor(const FGuid& textureID); +#endif // AK_SUPPORT_WAAPI + mutable AcousticTextureParamsChanged OnTextureParamsChanged; + +#endif // WITH_EDITOR + +#if WITH_EDITORONLY_DATA + // Visualize rooms and portals in the viewport. This requires 'realtime' to be enabled in the viewport. + UPROPERTY(Config, EditAnywhere, Category = "Viewports") + bool VisualizeRoomsAndPortals = false; + // Flips the state of VisualizeRoomsAndPortals. Used for the viewport menu options. (See FAudiokineticToolsModule in AudiokineticTooslModule.cpp). + void ToggleVisualizeRoomsAndPortals(); + // When enabled, information about AkReverbComponents will be displayed in viewports, above the component's UPrimitiveComponent parent. This requires 'realtime' to be enabled in the viewport. + UPROPERTY(Config, EditAnywhere, Category = "Viewports") + bool bShowReverbInfo = true; + // Flips the state of bShowReverbInfo. Used for the viewport menu options. (See FAudiokineticToolsModule in AudiokineticTooslModule.cpp). + void ToggleShowReverbInfo(); + ShowRoomsPortalsChanged OnShowRoomsPortalsChanged; + ShowReverbInfoChanged OnShowReverbInfoChanged; + AuxBusAssignmentMapChanged OnAuxBusAssignmentMapChanged; + ReverbRTPCChanged OnReverbRTPCChanged; + + FOnSoundBanksPathChangedDelegate OnGeneratedSoundBanksPathChanged; + +#endif + + /** Get the associated AuxBus for the given environment decay value. + * Return the AuxBus associated to the next highest decay value in the EnvironmentDecayAuxBusMap, after the given value. + */ + void GetAuxBusForDecayValue(float decay, class UAkAuxBus*& auxBus); + + void GetAudioInputEvent(class UAkAudioEvent*& OutInputEvent); + +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSettingsPerUser.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSettingsPerUser.h new file mode 100644 index 0000000..8cc732f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSettingsPerUser.h @@ -0,0 +1,94 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Engine/EngineTypes.h" +#include "AkWaapiClient.h" +#include "AkSettingsPerUser.generated.h" + +DECLARE_EVENT(UAkSettingsPerUser, AutoConnectChanged); +DECLARE_EVENT(UAkSettingsPerUser, AutoSyncWaapiNamesChanged); +DECLARE_MULTICAST_DELEGATE(FOnSoundBanksPathChangedDelegate); + +UCLASS(config = EditorPerProjectUserSettings) +class AKAUDIO_API UAkSettingsPerUser : public UObject +{ + GENERATED_BODY() + +public: + UAkSettingsPerUser(const FObjectInitializer& ObjectInitializer); + + // Wwise Installation Path (Root folder containing the Authoring, SDK, etc folders) + UPROPERTY(Config, EditAnywhere, Category = "Installation") + FDirectoryPath WwiseWindowsInstallationPath; + + // Wwise Installation Path (Root folder containing the Authoring, SDK, etc folders) + UPROPERTY(Config, EditAnywhere, Category = "Installation", meta = (FilePathFilter = "app", AbsolutePath)) + FFilePath WwiseMacInstallationPath; + + //Override the Generated Soundbanks Path in the project settings + UPROPERTY(Config, EditAnywhere, Category = "Installation") + FDirectoryPath GeneratedSoundBanksFolderUserOverride; + + // IP Address used to connect to WAAPI. Changing this requires Editor restart + UPROPERTY(Config, EditAnywhere, Category = "WAAPI") + FString WaapiIPAddress = WAAPI_LOCAL_HOST_IP_STRING; + + // Network Port used to connect to WAAPI. Changing this requires Editor restart + UPROPERTY(Config, EditAnywhere, Category = "WAAPI") + uint32 WaapiPort = WAAPI_PORT; + + // Whether to connect to WAAPI or not + UPROPERTY(Config, EditAnywhere, Category = "WAAPI") + bool bAutoConnectToWAAPI = false; + + // Whether to synchronize the selection between the WAAPI picker and the Wwise Project Explorer + UPROPERTY(Config, EditAnywhere, Category = "WAAPI") + bool AutoSyncSelection = true; + + // Time out value for the waapi error message translator to translate an error message (in ms). If set to 0, disable the translator entirely + UPROPERTY(Config, EditAnywhere, Category = "Error Message Translator") + uint32 WaapiTranslatorTimeout = 0; + + UPROPERTY(Config) + bool SuppressGeneratedSoundBanksPathWarnings = false; + + UPROPERTY(Config) + bool SoundDataGenerationSkipLanguage = false; + + //Opens a notification that the user must accept before reloading Wwise Asset Data + UPROPERTY(Config, EditAnywhere, Category = "Asset Reload") + bool AskForWwiseAssetReload = false; + + +#if WITH_EDITOR + +public: + mutable AutoConnectChanged OnAutoConnectToWaapiChanged; + + FOnSoundBanksPathChangedDelegate OnGeneratedSoundBanksPathChanged; +protected: + void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; + void PreEditChange(FProperty* PropertyAboutToChange) override; + +private: + FString PreviousWwiseWindowsInstallationPath; + FString PreviousWwiseMacInstallationPath; + FString PreviousGeneratedSoundBanksFolder; +#endif +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSpatialAudioDrawUtils.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSpatialAudioDrawUtils.h new file mode 100644 index 0000000..c057337 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSpatialAudioDrawUtils.h @@ -0,0 +1,88 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkSpatialAudioDrawUtils.h: +=============================================================================*/ +#pragma once + +#if WITH_EDITOR + +#include "CoreMinimal.h" +#include "AkAudioDevice.h" + +namespace AkDrawConstants +{ + const float CullDepth = 100.0f; + const float PortalOutlineThickness = 5.0f; + const float PortalRoomConnectionThickness = 3.0f; + const float RoomIconThickness = 2.0f; + const float RoomIconRadius = 10.0f; + const int RoomIconSides = 16; + const float RoomAxisLength = 30.0f; + const float RoomAxisThickness = 1.0f; + const float RoomOutlineThickness = 1.0f; // Slightly thicker lines for spatial audio volumes when surface reflectors are disabled, since they are less visible. + const float SpatialAudioVolumeOutlineThickness = 0.2f; // Shows the outline of disabled surfaces and non-diffraction edges. + const float RadialEmitterThickness = 2.0f; + const float DiffractionEdgeThickness = 2.5f; +} + +/** A utility struct to transform the points on a local axis-aligned bounding box to world space using the given transform. + Used for drawing rotated bounding boxes around portals. +*/ +struct AKAUDIO_API AkDrawBounds +{ + AkDrawBounds(const FTransform& T, const FVector& Extent); + +private: + const FTransform& Transform; + const FVector& BoxExtent; + +public: + /** FrontRightUp */ FVector FRU() const; + /** BackLeftDown */ FVector BLD() const; + /** FrontLeftDown */ FVector FLD() const; + /** BackRightUp */ FVector BRU() const; + /** FrontLeftDown */ FVector FLU() const; + /** BackLeftUp */ FVector BLU() const; + /** FrontRightDown */ FVector FRD() const; + /** BackRightDown */ FVector BRD() const; + /** RightUp */ FVector RU() const; + /** LeftUp */ FVector LU() const; + /** RightDown */ FVector RD() const; + /** LeftDown */ FVector LD() const; +}; + +class UAkPortalComponent; +class UAkSurfaceReflectorSetComponent; +namespace AkSpatialAudioColors +{ + AKAUDIO_API void GetPortalColors(const UAkPortalComponent* Portal, FLinearColor& FrontColor, FLinearColor& BackColor); + AKAUDIO_API FLinearColor GetPortalOutlineColor(const UAkPortalComponent* Portal); + AKAUDIO_API FLinearColor GetRoomColor(); + AKAUDIO_API FLinearColor GetRadialEmitterOutlineColor(); + AKAUDIO_API FLinearColor GetRadialEmitterColor(); + AKAUDIO_API float GetRadialEmitterOuterOpacity(); + AKAUDIO_API float GetRadialEmitterInnerOpacity(); + AKAUDIO_API FLinearColor GetSurfaceReflectorColor(const UAkSurfaceReflectorSetComponent* SurfaceReflectorSet, int NodeIdx, bool IsDragging); + AKAUDIO_API FLinearColor GetSpatialAudioVolumeOutlineColor(); + AKAUDIO_API FLinearColor GetBadFitSpatialAudioVolumeOutlineColor(); + AKAUDIO_API FLinearColor GetDiffractionEdgeColor(); + AKAUDIO_API FLinearColor GetBoundaryDiffractionEdgeColor(); +} + +#endif // WITH_EDITOR diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSpatialAudioHelper.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSpatialAudioHelper.h new file mode 100644 index 0000000..286901f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSpatialAudioHelper.h @@ -0,0 +1,39 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Engine/EngineTypes.h" + +#if WITH_EDITOR +#include "Editor/EditorEngine.h" +#endif + +#include "WwiseDefines.h" +#if UE_5_1_OR_LATER +#include "Engine/HitResult.h" +#endif + +namespace AkSpatialAudioHelper +{ + AActor* GetActorFromHitResult(const FHitResult& HitResult); + bool IsAkSpatialAudioActorClass(const AActor* Actor); + +#if WITH_EDITOR + UEditorEngine::FObjectsReplacedEvent* GetObjectReplacedEvent(); +#endif +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSpatialAudioVolume.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSpatialAudioVolume.h new file mode 100644 index 0000000..25b4dc7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSpatialAudioVolume.h @@ -0,0 +1,162 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkReverbVolume.h: +=============================================================================*/ +#pragma once + +#include "GameFramework/Volume.h" +#include "AkSurfaceReflectorSetComponent.h" +#include "AkLateReverbComponent.h" +#include "AkRoomComponent.h" +#include "AkSpatialAudioVolume.generated.h" + +UENUM() +enum class EAkFitToGeometryMode : uint32 +{ + /** + Oriented Box: The Ak Spatial Audio Volume is fit to the surrounding geometry using a minimum volume oriented bounding box. + Use for shoe box shaped rooms with and arbitrary extent and rotation. + */ + OrientedBox, + + /** + Aligned Box: The Ak Spatial Audio Volume is fit to the surrounding geometry using an aligned bounding box, aligned to the local axes of the Actor. + Use for shoe box shaped rooms with an arbitrary extent, but with the rotation supplied by the user. + The actor is rotated manually in the editor to achieve desired alignment. + */ + AlignedBox, + + /** + Convex Polyhedron: The Ak Spatial Audio Volume is fit to the surrounding geometry using a convex polyhedron. Use for arbitrary-shaped convex rooms. + Will likely result in a more complex (higher poly-count) shape, and will possibly resulting in greater CPU and memory usage than oriented or aligned box shapes. + When using convex polyhedron, a room must be fully enclosed; open ceilings or walls are not permitted and will cause a failure to fit to geometry. + */ + ConvexPolyhedron +}; + +/*------------------------------------------------------------------------------------ + AAkSpatialAudioVolume +------------------------------------------------------------------------------------*/ +UCLASS(ClassGroup = Audiokinetic, BlueprintType, hidecategories = (Advanced, Attachment, Volume)) +class AKAUDIO_API AAkSpatialAudioVolume : public AVolume +{ + GENERATED_BODY() + +public: + AAkSpatialAudioVolume(const class FObjectInitializer& ObjectInitializer); + +#if WITH_EDITOR + void FitRaycast(); + void FitBox(bool bPreviewOnly = false); + bool bBrushNeedsRebuild = false; + virtual bool ShouldTickIfViewportsOnly() const override; + virtual void Tick(float DeltaSeconds) override; + virtual void PostTransacted(const FTransactionObjectEvent& TransactionEvent) override; + virtual void PostEditMove(bool bFinished) override; + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; + + const TArray& GetRaycastHits() const { return FitPoints; } + float GetFitScale() const { return FilterHitPoints; } + static const int kNumRaycasts = 32; + + virtual FName GetCustomIconName() const override + { + static const FName IconName("ClassIcon.AkSpatialAudioVolume"); + return IconName; + } + + void PostRebuildBrush(); + void ClearTextComponents(); + void UpdatePreviewTextComponents(TArray positions); + void UpdatePreviewPolys(TArray, int>> materialVotes); + TArray PreviewPolys; +#endif + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "SurfaceReflectorSet", meta = (ShowOnlyInnerProperties)) + UAkSurfaceReflectorSetComponent* SurfaceReflectorSet = nullptr; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "LateReverb", meta = (ShowOnlyInnerProperties)) + UAkLateReverbComponent* LateReverb = nullptr; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Room", meta = (ShowOnlyInnerProperties)) + UAkRoomComponent* Room = nullptr; + +#if WITH_EDITORONLY_DATA + /** + Automatically fit the Ak Spatial Audio Volume to the surrounding geometry. The fitting operation is performed after enabling this property, or after moving the actor to a new location. + The fitting operation is performed by casting rays emanating spherically outwards from the origin of the actor. + The points where the rays hit the surrounding geometry (drawn in the editor as green dots) are fit to a shape (box or convex polyhedron), and the actor is then resized appropriately. + */ + UPROPERTY(EditAnywhere, Category = "Fit to Geometry" ) + bool FitToGeometry = false; + + /** + Sets the collision channel for the ray traces performed to fit the spatial audio volume to the surrounding geometry. When set to 'Use Integration Settings Default', the value will be taken from the DefaultFitToGeometryCollisionChannel in the Wwise Integration Settings. + */ + UPROPERTY(EditAnywhere, Category = "Fit to Geometry") + TEnumAsByte CollisionChannel; + +#if WITH_EDITOR + /** + Converts between EAkCollisionChannel and ECollisionChannel. Returns Wwise Integration Settings default if CollisionChannel == UseIntegrationSettingsDefault. Otherwise, casts CollisionChannel to ECollisionChannel. + */ + UFUNCTION(BlueprintCallable, Category = "Fit to Geometry") + ECollisionChannel GetCollisionChannel(); +#endif + + /** + Choose the shape with which to fit to the surrounding geometry. + */ + UPROPERTY(EditAnywhere, Category = "Fit to Geometry" ) + EAkFitToGeometryMode Shape = EAkFitToGeometryMode::AlignedBox; + + /** + Set to a value less then 1.0 to filter out a percentage of the ray cast hits for use in fitting to surrounding geometry. + Points that have been rejected by the filter are drawn in red, and points accepted drawn in green. + Particularly useful when rays happen to escape through windows or other openings, resulting in undesirable points. + */ + UPROPERTY(EditAnywhere, Category = "Fit to Geometry", meta = (ClampMin = 0.1875f, ClampMax = 1.0f)) + float FilterHitPoints = 1.0f; + + UPROPERTY() + TArray FitPoints; + + UPROPERTY() + TArray FitNormals; + + UPROPERTY() + TArray< TWeakObjectPtr > FitMaterials; + + UPROPERTY() + FRotator SavedRotation; + + TArray< TPair< FVector, FVector> > PreviewOutline; + bool IsDragging = false; + + UPROPERTY() + bool FitFailed = false; + + // Used by the visualizer when scaling the preview text components. + float LongestEdgeLength = 0.0f; + mutable TArray PreviewTextureNameComponents; + +private: + FBoxSphereBounds PreviousBounds = FBoxSphereBounds(); +#endif +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSpotReflector.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSpotReflector.h new file mode 100644 index 0000000..83ffb88 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSpotReflector.h @@ -0,0 +1,110 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkSpotReflector.h: +=============================================================================*/ +#pragma once + +#include "AkAudioDevice.h" +#include "AkAcousticTexture.h" +#include "GameFramework/Actor.h" +#include "AkSpotReflector.generated.h" + +UCLASS(config = Engine) +class AKAUDIO_API AAkSpotReflector : public AActor +{ + GENERATED_BODY() + +public: + AAkSpotReflector(const FObjectInitializer& ObjectInitializer); + + /** + * Send to an Auxiliary Bus containing the Wwise Reflect plugin for early reflections rendering. + * Leave unassigned to use the Early Reflections Auxiliary Bus that is assigned in the Wwise Authoring Tool. + * Setting a value here will apply only to sounds playing on AK Components with EnableSpotReflectors to true. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = AkSpotReflector) + class UAkAuxBus * EarlyReflectionAuxBus = nullptr; + + /** + * Send to an Auxiliary Bus containing the Wwise Reflect plugin for early reflections rendering. + * Leave unassigned to use the Early Reflections Auxiliary Bus that is assigned in the Wwise Authoring Tool. + * Setting a value here will apply only to sounds playing on AK Components with EnableSpotReflectors to true. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay, Category = AkSpotReflector) + FString EarlyReflectionAuxBusName; + + /** + * The Acoustic Texture represents sound absorption. It is done by filtering the sound bouncing off the spot reflector. + * If left to None, no filtering will be applied to the sound. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = AkSpotReflector) + UAkAcousticTexture* AcousticTexture = nullptr; + + /** + * This number scales the distance between the listener and the actual image source, preserving orientation. + * Set to 1 to position the image source at the position of the spot reflector + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = AkSpotReflector, meta = (ClampMin = "0.0")) + float DistanceScalingFactor = .0f; + + /** Game-controlled level for the sound that will emit from the image source. Valid range: (0.0, 4.0)*/ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = AkSpotReflector, meta = (ClampMin = "0.0", ClampMax = "4.0")) + float Level = .0f; + + /** Make this spot reflector only reflect emitted sounds in the same Spatial Audio Room.*/ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = AkSpotReflector) + bool SameRoomOnly = false; + + /** Override the room this spot reflector is contained in. If disabled, a containment check will be done to find the room.*/ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = AkSpotReflector, meta = (EditCondition = "SameRoomOnly")) + bool EnableRoomOverride = false; + + /** The room in which the spot reflector will be virtually placed in. If set to None, the default "Outdoors" room will be used.*/ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = AkSpotReflector, meta = (EditCondition = "EnableRoomOverride")) + class AActor* RoomOverride = nullptr; + + AkImageSourceID GetImageSourceID() const; + AkAuxBusID GetAuxBusID() const; + + virtual void PostInitializeComponents() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + /** + * Update all spot reflectors in the world for a single ak component. + * All spot reflectors are cleared first, then they are set + * If the spot reflector is set as SameRoomOnly, it will be set only if it is in the same room as the ak component. + */ + static void UpdateSpotReflectors(UAkComponent* AkComponent); + + const FString GetSpotReflectorName() const; + +private: + void SetImageSource(UAkComponent* AkComponent); + void AddToWorld(); + void RemoveFromWorld(); + +#if WITH_EDITORONLY_DATA + /** Editor only component used to display the sprite so as to be able to see the location of the Spot Reflector */ + class UBillboardComponent* SpriteComponent; +#endif + + typedef TSet SpotReflectorSet; + typedef TMap WorldToSpotReflectorsMap; + static WorldToSpotReflectorsMap sWorldToSpotReflectors; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkStateValue.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkStateValue.h new file mode 100644 index 0000000..f30373c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkStateValue.h @@ -0,0 +1,40 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkGroupValue.h" +#include "AkStateValue.generated.h" + +UCLASS(BlueprintType) +class AKAUDIO_API UAkStateValue : public UAkGroupValue +{ + GENERATED_BODY() + + virtual void Serialize(FArchive& Ar) override; + +protected: + virtual void LoadGroupValue() override; + +#if WITH_EDITORONLY_DATA +public: + virtual void FillInfo() override; + virtual void FillInfo(const FWwiseAnyRef& CurrentWwiseRef) override; + virtual FName GetWwiseGroupName() override; + virtual bool ObjectIsInSoundBanks() override; +#endif +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSubmixInputComponent.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSubmixInputComponent.h new file mode 100644 index 0000000..d020dba --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSubmixInputComponent.h @@ -0,0 +1,67 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= +AkSubmixInputComponent.h: +=============================================================================*/ + +#pragma once + +#include "AkInclude.h" +#include "AudioDevice.h" +#include "AkAudioInputComponent.h" +#include "AkSubmixInputComponent.generated.h" + + +/*------------------------------------------------------------------------------------ +UAkSubmixInputComponent +------------------------------------------------------------------------------------*/ +UCLASS(ClassGroup = Audiokinetic, BlueprintType, hidecategories = (Transform, Rendering, Mobility, LOD, Component, Activation), meta = (BlueprintSpawnableComponent)) +class AKAUDIO_API UAkSubmixInputComponent + : public UAkAudioInputComponent + , public ISubmixBufferListener +{ + GENERATED_BODY() +public: + UAkSubmixInputComponent(const class FObjectInitializer& ObjectInitializer); + + virtual void OnNewSubmixBuffer(const USoundSubmix* OwningSubmix, float* AudioData, int32 NumSamples, int32 NumChannels, const int32 SampleRate, double AudioClock) override; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SubmixInput") + USoundSubmix* SubmixToRecord = nullptr; + + virtual int32 PostAssociatedAudioInputEvent(); + virtual void Stop(); +protected: + /** The audio callback. This will be called continuously by the Wwise sound engine, + * and is used to provide the sound engine with audio samples. If this function returns false, the audio + * input event will be stopped and the functino will stop being called. + */ + virtual bool FillSamplesBuffer(uint32 NumChannels, uint32 NumSamples, float** BufferToFill) override; + /** This callback is used to provide the Wwise sound engine with the required audio format. */ + virtual void GetChannelConfig(AkAudioFormat& AudioFormat) override; + +private: + int32 NumChannels; + int32 SampleRate; + int32 BufferLength; + Audio::TCircularAudioBuffer SampleBuffer; + TArray PoppedSamples; + + Audio::FMixerDevice* GetAudioMixerDevice(); + int32 PlayingID = AK_INVALID_PLAYING_ID; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSurfaceReflectorSetComponent.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSurfaceReflectorSetComponent.h new file mode 100644 index 0000000..bc37ebc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSurfaceReflectorSetComponent.h @@ -0,0 +1,190 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#include "AkAcousticTexture.h" +#include "AkAcousticTextureSetComponent.h" +#include "AkGameObject.h" +#include "AkSurfaceReflectorSetUtils.h" +#include "Components/SceneComponent.h" +#include "Components/TextRenderComponent.h" +#include "AkSurfaceReflectorSetComponent.generated.h" + +class UAkRoomComponent; +struct FAkReverbDescriptor; + +DECLARE_DELEGATE(FOnRefreshDetails); + +UCLASS(ClassGroup = Audiokinetic, BlueprintType, hidecategories = (Transform, Rendering, Mobility, LOD, Component, Activation, Tags), meta = (BlueprintSpawnableComponent)) +class AKAUDIO_API UAkSurfaceReflectorSetComponent : public UAkAcousticTextureSetComponent +{ + GENERATED_BODY() + +public: + UAkSurfaceReflectorSetComponent(const class FObjectInitializer& ObjectInitializer); + + /** Convert the brush to a geometry set consisting of vertices, triangles, surfaces, acoustic textures and transmission loss values. + * Send it to Wwise with the rest of the AkGeometryParams to add or update a geometry in Spatial Audio. + * It is necessary to create at least one geometry instance for each geometry set that is to be used for diffraction and reflection simulation. See UpdateSurfaceReflectorSet(). */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkSurfaceReflectorSet") + void SendSurfaceReflectorSet(); + + /** Add or update an instance of the geometry by sending the transform of this component to Wwise. + * A geometry instance is a unique instance of a geometry set with a specified transform (position, rotation and scale). + * It is necessary to create at least one geometry instance for each geometry set that is to be used for diffraction and reflection simulation. */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkSurfaceReflectorSet") + void UpdateSurfaceReflectorSet(); + + /** Remove the geometry and the corresponding instance from Wwise. */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|AkSurfaceReflectorSet") + void RemoveSurfaceReflectorSet(); + + /** Enable reflection with geometry */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Toggle") + bool bEnableSurfaceReflectors = false; + + /** The surface properties of each face on the brush geometry. */ + UPROPERTY(EditAnywhere, BlueprintSetter = UpdateAcousticProperties, Category ="Geometry Surfaces") + TArray AcousticPolys; + + /** Set AcousticPolys with an input array, compute the surface areas of each poly and notify damping needs updating. */ + UFUNCTION(BlueprintSetter, Category = "Audiokinetic|AkSurfaceReflectorSet") + void UpdateAcousticProperties(TArray in_AcousticPolys); + + /** Enable or disable geometric diffraction for this mesh. Check this box to have Wwise Spatial Audio generate diffraction edges on the geometry. The diffraction edges will be visible in the Wwise game object viewer when connected to the game. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Geometry Settings") + bool bEnableDiffraction = false; + + /** Enable or disable geometric diffraction on boundary edges for this Geometry. Boundary edges are edges that are connected to only one triangle. Depending on the specific shape of the geometry, boundary edges may or may not be useful and it is beneficial to reduce the total number of diffraction edges to process. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Geometry Settings", meta = (EditCondition = "bEnableDiffraction")) + bool bEnableDiffractionOnBoundaryEdges = false; + + /** (Optional) Associate this Surface Reflector Set with a Room. + Associating a surface reflector set with a particular room will limit the scope in which the geometry is visible/accessible. Leave it to None and this geometry will have a global scope. + It is recommended to associate geometry with a room when the geometry is (1) fully contained within the room (ie. not visible to other rooms except by portals), and (2) the room does not share geometry with other rooms. Doing so reduces the search space for ray casting performed by reflection and diffraction calculations. + Take note that once one or more geometry sets are associated with a room, that room will no longer be able to access geometry that is in the global scope.*/ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Geometry Settings") + AActor* AssociatedRoom = nullptr; + + UModel* ParentBrush; + +#if WITH_EDITORONLY_DATA + UPROPERTY(SkipSerialization, NonTransactional) + mutable TArray TextVisualizers; + + FText GetPolyText(int32 PolyIdx) const; + + void SetOnRefreshDetails(const FOnRefreshDetails& OnRefreshDetailsDelegate) { OnRefreshDetails = OnRefreshDetailsDelegate; } + void ClearOnRefreshDetails() { OnRefreshDetails.Unbind(); } + const FOnRefreshDetails* GetOnRefreshDetails() { return &OnRefreshDetails; } + + void AssignAcousticTexturesFromSamples(const TArray& Points, const TArray& Normals, const TArray< TWeakObjectPtr >& Materials, int Num); +#endif + +#if WITH_EDITOR + /** + * Check for errors + */ + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; + virtual void PostEditUndo() override; + virtual void PreEditUndo() override; + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction); + + /** Tracks whether the user is interacting with a UI element in the details panel (e.g. a slider) */ + bool UserInteractionInProgress = false; + FDelegateHandle PropertyChangedHandle; + void OnPropertyChanged(UObject* ObjectBeingModified, FPropertyChangedEvent& PropertyChangedEvent); + + void SchedulePolysUpdate(); + void UpdatePolys(bool bPreserveTextures = false); + void UpdateText(bool Visible); + /** Align all of the text components (1 for each face) along one of the edges on the face */ + void UpdateTextPositions() const; + void SurfacePropertiesChanged(); + void DestroyTextVisualizers(); + + bool WasSelected; + + TSet GetSelectedFaceIndices() const; + bool TexturesDiffer() const; + + /** Used to delay the polys update by one frame when editing geometry */ + bool PolysNeedUpdate = false; + /** Store the current acoustic properties for each face. Store them in PreviousPolys. + If bUpdateEdges == true, also store the current surface edges. + Transform the edges, normals and midpoints of the surfaces from world space to local space. */ + void CacheAcousticProperties(); + /** Store the current acoustic properties and geometry for each face. Store them in PreviousPolys + Transform the edges, normals and midpoints of the surfaces from world space to local space. */ + void CacheLocalSpaceSurfaceGeometry(); +#endif + +#if WITH_EDITORONLY_DATA + UPROPERTY() + TMap EdgeMap; +#endif + + virtual void BeginDestroy() override; + virtual void OnRegister() override; + virtual void OnUnregister() override; + virtual void OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) override; + virtual bool MoveComponentImpl( + const FVector& Delta, + const FQuat& NewRotation, + bool bSweep, + FHitResult* Hit, + EMoveComponentFlags MoveFlags, + ETeleportType Teleport) override; + + void GetTexturesAndSurfaceAreas(TArray& textures, TArray& surfaceAreas) const override; + void ComputeAcousticPolySurfaceArea(); + +private: + virtual bool ShouldSendGeometry() const override; + void InitializeParentBrush(bool fromTick = false); + +#if WITH_EDITOR + /** Used to keep track of the surfaces and their acoustic properties so that we can + restore acoustic properties to the appropriate faces when the brush geometry is changed.*/ + TArray PreviousPolys; + virtual void HandleObjectsReplaced(const TMap& ReplacementMap) override; + virtual bool ContainsTexture(const FGuid& textureID) override; + virtual void RegisterAllTextureParamCallbacks() override; + /* Sort the edges of a face such that they form a continuous loop */ + void SortFaceEdges(int FaceIndex); + /* Recalculate the normals for the face at FaceIndex, taking world scaling into account. */ + void UpdateFaceNormals(int FaceIndex); + /** Identify the edges in the brush geometry and store in EdgeMap */ + void UpdateEdgeMap(bool bUpdateTextures); + /* Compare AcousticPolys to PreviousPolys, carrying over the acoustic properties from PreviousPolys for those faces whose edges and normals have not changed. */ + void EdgeMapChanged(); + void AlignTextWithEdge(int FaceIndex) const; + /* Choose the edge upon which to align the text. The 'optimal' edge is that which aligns the + up vector of the text closest to the up vector of the view camera. */ + int ChooseAlignmentEdge(int FaceIndex) const; + /* Positions the text at the beginning of the AlignmentEdge (V0). + Shifts the text along the AlignmentEdge by a certain amount. + The amount of shift is proportional to the dot product between AlignmentEdge and the edge that connects to V0. */ + FVector GetTextAnchorPosition(int FaceIndex, const FAkSurfaceEdgeInfo& AlignmentEdge, int AlignmentEdgeIndex) const; + /* Progressively scale down the text visualizer at FaceIndex until it is completely contained within the face. */ + void SetTextScale(UTextRenderComponent* TextComp, int FaceIndex, int AlignmentEdgeIndex, const FVector& TextAnchorPosition, const struct FFacePlane& FacePlane, const FTransform& AttachTransform) const; +#endif + +#if WITH_EDITORONLY_DATA + FOnRefreshDetails OnRefreshDetails; +#endif +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSurfaceReflectorSetUtils.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSurfaceReflectorSetUtils.h new file mode 100644 index 0000000..9e5d14d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSurfaceReflectorSetUtils.h @@ -0,0 +1,115 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#include "AkAcousticTexture.h" +#include "AkSurfaceReflectorSetUtils.generated.h" + +namespace AkSurfaceReflectorUtils +{ + static float EQUALITY_THRESHOLD = 0.001f; +} + +/** An edge between two vertices */ +USTRUCT() +struct FAkSurfaceEdgeVerts +{ + GENERATED_BODY() + static bool EdgesShareVertex(const FAkSurfaceEdgeVerts& Edge1, const FAkSurfaceEdgeVerts& Edge2); + + FAkSurfaceEdgeVerts() {} + FAkSurfaceEdgeVerts(FVector InV0, FVector InV1) : V0(InV0), V1(InV1) {} + FVector V0 = FVector::ZeroVector; + FVector V1 = FVector::ZeroVector; + + FVector GetUnitVector() const { return (V1 - V0).GetSafeNormal(); } + + FAkSurfaceEdgeVerts GetTransformedEdge(const FTransform& Transform) const; + void TransformEdge(const FTransform& Transform); + void Invert(); +}; + +/** Information about an edge */ +USTRUCT() +struct FAkSurfaceEdgeInfo +{ + GENERATED_BODY() + FAkSurfaceEdgeVerts EdgeVerts; + FVector Normal; + bool IsEnabled = true; + bool IsBoundary = true; + bool IsFlat = false; + + const FVector V0() const { return EdgeVerts.V0; } + const FVector V1() const { return EdgeVerts.V1; } + + void SetV0(FVector V0) { EdgeVerts.V0 = V0; } + void SetV1(FVector V1) { EdgeVerts.V1 = V1; } + + int64 GetHash() + { + int64 H0 = GetTypeHash(V0()); + int64 H1 = GetTypeHash(V1()); + if (H1 > H0) + { + int64 temp = H0; + H0 = H1; + H1 = temp; + } + return H1 << 32 | H0; + } + + FAkSurfaceEdgeInfo(); + FAkSurfaceEdgeInfo(FVector InV0, FVector InV1); +}; + +/** Contains the properties of a face from the ParentBrush of a UAkSurfaceReflectorSetComponent. */ +USTRUCT(BlueprintType) +struct FAkSurfacePoly +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Audiokinetic|AkSurfaceReflectorSet") + UAkAcousticTexture* Texture = nullptr; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Audiokinetic|AkSurfaceReflectorSet", DisplayName = "Transmission Loss", meta = (ClampMin = "0.0", ClampMax = "1.0")) + float Occlusion = 1.f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Audiokinetic|AkSurfaceReflectorSet") + bool EnableSurface = true; + + void SetSurfaceArea(float area) { SurfaceArea = area; } + float GetSurfaceArea() const { return SurfaceArea; } + +#if WITH_EDITOR + FVector Normal; + FVector MidPoint; + /* The edges of the polygon */ + TArray Edges; + /* Keeps track of the optimal dot product between the chosen up vector and view camera up vector. + This is used to minimize the flickering of text as it switches between edges each frame. */ + mutable float OptimalEdgeDP = 0.0f; + + void ClearEdgeInfo(); + + FText GetPolyText(bool includeOcclusion) const; +#endif + +private: + UPROPERTY() + float SurfaceArea = 0.0f; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSwitchValue.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSwitchValue.h new file mode 100644 index 0000000..2593917 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkSwitchValue.h @@ -0,0 +1,39 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkGroupValue.h" +#include "AkSwitchValue.generated.h" + +UCLASS(BlueprintType) +class AKAUDIO_API UAkSwitchValue : public UAkGroupValue +{ + GENERATED_BODY() + + virtual void Serialize(FArchive& Ar) override; + +protected: + virtual void LoadGroupValue() override; +#if WITH_EDITORONLY_DATA +public: + virtual void FillInfo() override; + virtual void FillInfo(const FWwiseAnyRef& CurrentWwiseRef) override; + virtual bool ObjectIsInSoundBanks() override; + virtual FName GetWwiseGroupName() override; +#endif +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkTrigger.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkTrigger.h new file mode 100644 index 0000000..bcd9ff9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkTrigger.h @@ -0,0 +1,60 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkAudioType.h" +#include "Wwise/CookedData/WwiseTriggerCookedData.h" +#if WITH_EDITORONLY_DATA +#include "Wwise/Info/WwiseObjectInfo.h" +#endif + +#include "AkTrigger.generated.h" + +UCLASS(BlueprintType) +class AKAUDIO_API UAkTrigger : public UAkAudioType +{ + GENERATED_BODY() + + public : + UPROPERTY(Transient, VisibleAnywhere, Category = "AkTexture") + FWwiseTriggerCookedData TriggerCookedData; + +#if WITH_EDITORONLY_DATA + UPROPERTY(EditAnywhere, Category = "AkTexture") + FWwiseObjectInfo TriggerInfo; +#endif + +public: + void Serialize(FArchive& Ar) override; + + virtual AkUInt32 GetShortID() const override {return TriggerCookedData.TriggerId;} + +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + +#if WITH_EDITORONLY_DATA + void GetTriggerCookedData(); + virtual void LoadData() override { GetTriggerCookedData(); } + + virtual FWwiseObjectInfo* GetInfoMutable() override {return &TriggerInfo;} + virtual FWwiseObjectInfo GetInfo() const override {return TriggerInfo;} + virtual void FillInfo() override; + virtual bool ObjectIsInSoundBanks() override; +#endif +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiBlueprints/AkWaapiCalls.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiBlueprints/AkWaapiCalls.h new file mode 100644 index 0000000..1e0e96a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiBlueprints/AkWaapiCalls.h @@ -0,0 +1,117 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + UAkWaapiCalls.h +=============================================================================*/ +#pragma once + +#include "AkInclude.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "AkWaapiUri.h" +#include "AkWaapiJsonManager.h" +#include "AkWaapiCalls.generated.h" + +/** +* Structure for Field Names +*/ +USTRUCT(BlueprintType) +struct AKAUDIO_API FAkWaapiSubscriptionId +{ + GENERATED_BODY() + + FAkWaapiSubscriptionId() {} + FAkWaapiSubscriptionId(const uint64_t& SubscribId) : SubscriptionId(SubscribId){} + + mutable uint64 SubscriptionId = 0; +}; + +DECLARE_DYNAMIC_DELEGATE_TwoParams(FOnEventCallback, FAkWaapiSubscriptionId, SubscriptionId, FAKWaapiJsonObject, WaapiJsonObject); +DECLARE_DYNAMIC_DELEGATE(FOnWaapiProjectLoaded); +DECLARE_DYNAMIC_DELEGATE(FOnWaapiConnectionLost); + +UCLASS(Within = World, config = Engine, defaultconfig) +class AKAUDIO_API UAkWaapiCalls : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + UAkWaapiCalls(const class FObjectInitializer& ObjectInitializer); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WaapiCalls") + static void SetSubscriptionID(const FAkWaapiSubscriptionId& Subscription, int id); + + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WaapiCalls") + static int GetSubscriptionID(const FAkWaapiSubscriptionId& Subscription); + + /** + * Subscribe to WAAPI project loaded event. This event will be broadcast whenever the correct Wwise project is loaded (defined by Wwise Project Path in the Wwise Plugin Settings). This should be used to initialize any resources that use WAAPI. + * + * @param Callback the event to call when a connection is established + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WaapiCalls") + static bool RegisterWaapiProjectLoadedCallback(const FOnWaapiProjectLoaded& Callback); + /** + * Subscribe to WAAPI connection lost event. This event will be broadcast when the WAAPI connection is lost. This should be used to clean up any resources that use WAAPI. + * + * @param Callback the event to call when the connection is lost + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WaapiCalls") + static bool RegisterWaapiConnectionLostCallback(const FOnWaapiConnectionLost& Callback); + + /** + * Call Waapi and get/set information/parameters according to the uri chosen. + * + * @param WaapiUri The function that will be called when an event that we would be aware of happens. + * @param WaapiArgs The arguments referenced to the in_uri function. + * @param WaapiOptions Optional flag to get more information about the event happened. + * @return out_result A JSON object that contains useful informations about the Call process to a specific event, gets an error infos in case the Call fails. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|WaapiCalls") + static FAKWaapiJsonObject CallWaapi(const FAkWaapiUri& WaapiUri, const FAKWaapiJsonObject& WaapiArgs, const FAKWaapiJsonObject& WaapiOptions); + + /** + * Allows clients to subscribe to notifications according to the event. + * + * @param WaapiUri The reference to the event that we would be aware of when it happens. + * @param WaapiOptions Optional flag to get more information about the event happened. + * @param in_callback A delegate to be executed during the subscription event. + * @param out_subscriptionId Gets the id of this subscription. + * @param out_result A boolean to ensure that the subscription was successfully done. + * @return A JSON object that contains useful informations about the subscription process to a specific event, gets an error infos in case the subscription failed. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|WaapiCalls") + static FAKWaapiJsonObject SubscribeToWaapi(const FAkWaapiUri& WaapiUri, const FAKWaapiJsonObject& WaapiOptions, const FOnEventCallback& CallBack, FAkWaapiSubscriptionId& SubscriptionId, bool& SubscriptionDone); + + /** + * Unsubscribe from an event. + * + * @param SubscriptionId Gets the id of the current subscription to the event from which we want to be unsubscribed. + * @param UnsubscriptionDone A boolean to ensure that the unsubscription was successfully done. + * @return A JSON object that contains useful informations about the unsubscription process from a specific event, gets an error infos in case the unsubscription failed. + */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|WaapiCalls") + static FAKWaapiJsonObject Unsubscribe(const FAkWaapiSubscriptionId& SubscriptionId, bool& UnsubscriptionDone); + + /** Converts an AkWaapiSubscriptionId value to a string */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToString (FAkWaapiSubscriptionId)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|String") + static FString Conv_FAkWaapiSubscriptionIdToString(const FAkWaapiSubscriptionId& INAkWaapiSubscriptionId); + + /** Converts an AkWaapiSubscriptionId value to a localizable text */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToText (FAkWaapiSubscriptionId)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|Text") + static FText Conv_FAkWaapiSubscriptionIdToText(const FAkWaapiSubscriptionId& INAkWaapiSubscriptionId); +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiBlueprints/AkWaapiFieldNames.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiBlueprints/AkWaapiFieldNames.h new file mode 100644 index 0000000..960487d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiBlueprints/AkWaapiFieldNames.h @@ -0,0 +1,141 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ +AkWaapiFieldNames.h +------------------------------------------------------------------------------------*/ +#pragma once + +/*------------------------------------------------------------------------------------ +SAkWaapiFieldNames +------------------------------------------------------------------------------------*/ + +#include "Misc/TextFilter.h" +#include "Widgets/Views/STableRow.h" +#include "Widgets/Views/STreeView.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Widgets/Input/SSearchBox.h" +#include "AkWaapiFieldNames.generated.h" + +/** +* Structure for Field Names +*/ +USTRUCT(BlueprintType) +struct AKAUDIO_API FAkWaapiFieldNames +{ + GENERATED_BODY() + + /** + * The Field Name + */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = FieldName, meta = (DisplayName = "Field Name")) + FString FieldName; +}; + +/*------------------------------------------------------------------------------------ +USAkWaapiFieldNamesConv +------------------------------------------------------------------------------------*/ +UCLASS() +class AKAUDIO_API USAkWaapiFieldNamesConv : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + USAkWaapiFieldNamesConv(const class FObjectInitializer& ObjectInitializer); + + /** Converts an AkWaapiFieldName value to a string */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToString (FAkWaapiFieldNames)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|String") + static FString Conv_FAkWaapiFieldNamesToString(const FAkWaapiFieldNames& INAkWaapiFieldNames); + + /** Converts an AkWaapiFieldName value to a localizable text */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToText (FAkWaapiFieldNames)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|Text") + static FText Conv_FAkWaapiFieldNamesToText(const FAkWaapiFieldNames& INAkWaapiFieldNames); +}; + +typedef TTextFilter< const FString& > StringFilter; + +/*------------------------------------------------------------------------------------ +SAkWaapiFieldNames +------------------------------------------------------------------------------------*/ +class AKAUDIO_API SAkWaapiFieldNames : public SCompoundWidget +{ +public: + typedef TSlateDelegates< TSharedPtr< FString > >::FOnSelectionChanged FOnSelectionChanged; + +public: + SLATE_BEGIN_ARGS(SAkWaapiFieldNames) + : _FocusSearchBoxWhenOpened(true) + , _SelectionMode(ESelectionMode::Multi) + {} + + /** Content displayed to the left of the search bar */ + SLATE_NAMED_SLOT(FArguments, SearchContent) + + /** If true, the search box will be focus the frame after construction */ + SLATE_ARGUMENT(bool, FocusSearchBoxWhenOpened) + + /** The selection mode for the list view */ + SLATE_ARGUMENT(ESelectionMode::Type, SelectionMode) + + /** Handles the drag and drop operations */ + SLATE_EVENT(FOnDragDetected, OnDragDetected) + + /** Handles the selection operation */ + SLATE_EVENT(FOnSelectionChanged, OnSelectionChanged) + + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs); + SAkWaapiFieldNames(void); + ~SAkWaapiFieldNames(); + + /** Returns all the FieldNames currently selected in the Waapi Picker view */ + const TArray> GetSelectedFieldNames() const; + +private: + /** The tree view widget */ + TSharedPtr< SListView< TSharedPtr> > ListViewPtr; + + /** The FieldNames list search box */ + TSharedPtr< SSearchBox > SearchBoxPtr; + + /** Filter for the search box */ + TSharedPtr SearchBoxFilter; + + /** Field Names list */ + TArray< TSharedPtr > FieldNamesList; + + /** Delegate to invoke when drag drop detected. */ + FOnDragDetected OnDragDetected; + + /** Delegate to invoke when a Field Name is selected. */ + FOnSelectionChanged OnSelectionChanged; + +private: + /** One-off active timer to focus the widget post-construct */ + EActiveTimerReturnType SetFocusPostConstruct(double InCurrentTime, float InDeltaTime); + + /** Generate a row in the tree view */ + TSharedRef GenerateRow(TSharedPtr in_FieldName, const TSharedRef& OwnerTable); + + /** Used by the search filter */ + void PopulateSearchStrings(const FString& in_FieldName, OUT TArray< FString >& OutSearchStrings) const; + void FilterUpdated(); + + /** Handler for list view selection changes */ + void ListSelectionChanged(TSharedPtr< FString > in_FieldName, ESelectInfo::Type SelectInfo); +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiBlueprints/AkWaapiFieldNamesCustomization.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiBlueprints/AkWaapiFieldNamesCustomization.h new file mode 100644 index 0000000..a9a7a13 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiBlueprints/AkWaapiFieldNamesCustomization.h @@ -0,0 +1,60 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#if WITH_EDITOR + +#include "CoreMinimal.h" +#include "Input/Reply.h" +#include "IPropertyTypeCustomization.h" +#include "PropertyHandle.h" + + +class SButton; +class SComboButton; +class IMenu; + +class AKAUDIO_API FAkWaapiFieldNamesCustomization : public IPropertyTypeCustomization +{ +public: + static TSharedRef MakeInstance(); + + /** IPropertyTypeCustomization interface */ + virtual void CustomizeHeader(TSharedRef StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + + virtual void CustomizeChildren(TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + +private: + + /** Delegate used to display the Waapi Picker */ + FReply OnPickContent(TSharedRef PropertyHandle); + + /** Handler for tree view selection changes */ + void FieldNameSelectionChanged(TSharedPtr< FString > in_FieldName, ESelectInfo::Type SelectInfo); + +private: + + /** The pick button widget */ + TSharedPtr PickerButton; + + TSharedPtr FieldNameHandle; + + TSharedPtr Window; +}; + +#endif// WITH_EDITOR diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiBlueprints/AkWaapiJsonManager.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiBlueprints/AkWaapiJsonManager.h new file mode 100644 index 0000000..e0a7ce2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiBlueprints/AkWaapiJsonManager.h @@ -0,0 +1,107 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + UAkWaapiCalls.h +=============================================================================*/ +#pragma once + +#include "AkInclude.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "AkWaapiUri.h" +#include "Dom/JsonObject.h" +#include "AkWaapiUtils.h" +#include "Serialization/JsonWriter.h" +#include "Serialization/JsonSerializer.h" +#include "AkWaapiBlueprints/AkWaapiFieldNames.h" +#include "AkWaapiJsonManager.generated.h" + +USTRUCT(BlueprintType) +struct AKAUDIO_API FAKWaapiJsonObject +{ + GENERATED_BODY() + FAKWaapiJsonObject() + { + WaapiJsonObj = MakeShareable(new FJsonObject()); + } + TSharedPtr WaapiJsonObj; +}; + +UCLASS() +class AKAUDIO_API UAkWaapiJsonManager : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + UAkWaapiJsonManager(const class FObjectInitializer& ObjectInitializer); + + /** Add a String field named FieldName with value of FieldValue */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WaapiJsonManager") + static FAKWaapiJsonObject SetStringField(const FAkWaapiFieldNames& FieldName, const FString& FieldValue, FAKWaapiJsonObject target); + + /** Set a boolean field named FieldName and value of FieldValue */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WaapiJsonManager") + static FAKWaapiJsonObject SetBoolField(const FAkWaapiFieldNames& FieldName, bool FieldValue, FAKWaapiJsonObject target); + + /** Add a field named FieldName with Number as FieldValue */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WaapiJsonManager") + static FAKWaapiJsonObject SetNumberField(const FAkWaapiFieldNames& FieldName, float FieldValue, FAKWaapiJsonObject target); + + /** Set an ObjectField named FieldName and value of FieldValue */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WaapiJsonManager") + static FAKWaapiJsonObject SetObjectField(const FAkWaapiFieldNames& FieldName, FAKWaapiJsonObject FieldValue, FAKWaapiJsonObject target); + + /** Add an array of String field named FieldName with value of FieldStringValues */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WaapiJsonManager") + static FAKWaapiJsonObject SetArrayStringFields(const FAkWaapiFieldNames& FieldName, const TArray< FString >& FieldStringValues, FAKWaapiJsonObject target); + + /** Set an array of ObjectField named FieldName and value of FieldObjectValues */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WaapiJsonManager") + static FAKWaapiJsonObject SetArrayObjectFields(const FAkWaapiFieldNames& FieldName, const TArray< FAKWaapiJsonObject >& FieldObjectValues, FAKWaapiJsonObject target); + + /** Get the field named FieldName as a string. */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WaapiJsonManager") + static FString GetStringField(const FAkWaapiFieldNames& FieldName, FAKWaapiJsonObject target); + + /** Gets the field with the specified name as a boolean. */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WaapiJsonManager") + static bool GetBoolField(const FAkWaapiFieldNames& FieldName, FAKWaapiJsonObject target); + + /** Gets the field with the specified name as a number. */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WaapiJsonManager") + static float GetNumberField(const FAkWaapiFieldNames& FieldName, FAKWaapiJsonObject target); + + /** Gets a numeric field and casts to an int32 */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WaapiJsonManager") + static int32 GetIntegerField(const FAkWaapiFieldNames& FieldName, FAKWaapiJsonObject target); + + /** Gets the field with the specified name as a Json object. */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WaapiJsonManager") + static FAKWaapiJsonObject GetObjectField(const FAkWaapiFieldNames& FieldName, FAKWaapiJsonObject target); + + /** Get the field named FieldName as an array. */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WaapiJsonManager") + static const TArray GetArrayField(const FAkWaapiFieldNames& FieldName, FAKWaapiJsonObject target); + + /** Converts an AKWaapiJsonObject value to a string */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToString (FAKWaapiJsonObject)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|String") + static FString Conv_FAKWaapiJsonObjectToString(FAKWaapiJsonObject INAKWaapiJsonObject); + + /** Converts an AKWaapiJsonObject value to a localizable text */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToText (FAKWaapiJsonObject)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|Text") + static FText Conv_FAKWaapiJsonObjectToText(FAKWaapiJsonObject INAKWaapiJsonObject); +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiBlueprints/AkWaapiUri.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiBlueprints/AkWaapiUri.h new file mode 100644 index 0000000..091e03e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiBlueprints/AkWaapiUri.h @@ -0,0 +1,141 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ +AkWaapiUri.h +------------------------------------------------------------------------------------*/ +#pragma once + +/*------------------------------------------------------------------------------------ +SAkWaapiUri +------------------------------------------------------------------------------------*/ + +#include "Widgets/Views/STableRow.h" +#include "Widgets/Views/STreeView.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Widgets/Input/SSearchBox.h" +#include "Misc/TextFilter.h" +#include "AkWaapiUri.generated.h" + +/** +* Structure for Uri +*/ +USTRUCT(BlueprintType) +struct AKAUDIO_API FAkWaapiUri +{ + GENERATED_BODY() + + /** + * The Uri + */ + UPROPERTY(EditAnywhere, BlueprintReadOnly,Category = Uri, meta = (DisplayName = "Uri")) + FString Uri; +}; + +/*------------------------------------------------------------------------------------ +UAkWaapiUriConv +------------------------------------------------------------------------------------*/ +UCLASS() +class AKAUDIO_API UAkWaapiUriConv : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + UAkWaapiUriConv(const class FObjectInitializer& ObjectInitializer); + + /** Converts an AkWaapiUri value to a string */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToString (FAkWaapiUri)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|String") + static FString Conv_FAkWaapiUriToString(const FAkWaapiUri& INAkWaapiUri); + + /** Converts an AkWaapiUri value to a localizable text */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToText (FAkWaapiUri)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|Text") + static FText Conv_FAkWaapiUriToText(const FAkWaapiUri& INAkWaapiUri); +}; + +typedef TTextFilter< const FString& > StringFilter; + +/*------------------------------------------------------------------------------------ +SAkWaapiUri +------------------------------------------------------------------------------------*/ +class AKAUDIO_API SAkWaapiUri : public SCompoundWidget +{ +public: + typedef TSlateDelegates< TSharedPtr< FString > >::FOnSelectionChanged FOnSelectionChanged; + +public: + SLATE_BEGIN_ARGS(SAkWaapiUri) + : _FocusSearchBoxWhenOpened(true) + , _SelectionMode(ESelectionMode::Multi) + {} + + /** Content displayed to the left of the search bar */ + SLATE_NAMED_SLOT(FArguments, SearchContent) + + /** If true, the search box will be focus the frame after construction */ + SLATE_ARGUMENT(bool, FocusSearchBoxWhenOpened) + + /** The selection mode for the list view */ + SLATE_ARGUMENT(ESelectionMode::Type, SelectionMode) + + /** Handles the drag and drop operations */ + SLATE_EVENT(FOnDragDetected, OnDragDetected) + + /** Handles the selection operation */ + SLATE_EVENT(FOnSelectionChanged, OnSelectionChanged) + + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs); + SAkWaapiUri(void); + ~SAkWaapiUri(); + + /** Returns all the Uris currently selected in the Waapi Picker view */ + const TArray> GetSelectedUri() const; + +private: + /** The tree view widget */ + TSharedPtr< SListView< TSharedPtr> > ListViewPtr; + + /** The Uri list search box */ + TSharedPtr< SSearchBox > SearchBoxPtr; + + /** Filter for the search box */ + TSharedPtr SearchBoxFilter; + + /** Uri list */ + TArray< TSharedPtr > UriList; + + /** Delegate to invoke when drag drop detected. */ + FOnDragDetected OnDragDetected; + + /** Delegate to invoke when a Uri is selected. */ + FOnSelectionChanged OnSelectionChanged; + +private: + /** One-off active timer to focus the widget post-construct */ + EActiveTimerReturnType SetFocusPostConstruct(double InCurrentTime, float InDeltaTime); + + /** Generate a row in the tree view */ + TSharedRef GenerateRow(TSharedPtr in_Uri, const TSharedRef& OwnerTable); + + /** Used by the search filter */ + void PopulateSearchStrings(const FString& in_Uri, OUT TArray< FString >& OutSearchStrings) const; + void FilterUpdated(); + + /** Handler for list view selection changes */ + void ListSelectionChanged(TSharedPtr< FString > in_Uri, ESelectInfo::Type SelectInfo); +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiBlueprints/AkWaapiUriCustomization.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiBlueprints/AkWaapiUriCustomization.h new file mode 100644 index 0000000..f9ca437 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiBlueprints/AkWaapiUriCustomization.h @@ -0,0 +1,59 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#if WITH_EDITOR + +#include "CoreMinimal.h" +#include "Input/Reply.h" +#include "IPropertyTypeCustomization.h" +#include "PropertyHandle.h" + +class SButton; +class SComboButton; +class IMenu; + +class AKAUDIO_API FAkWaapiUriCustomization : public IPropertyTypeCustomization +{ +public: + static TSharedRef MakeInstance(); + + /** IPropertyTypeCustomization interface */ + virtual void CustomizeHeader(TSharedRef StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + + virtual void CustomizeChildren(TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + +private: + + /** Delegate used to display the Waapi Picker */ + FReply OnPickContent(TSharedRef PropertyHandle); + + /** Handler for tree view selection changes */ + void UriSelectionChanged(TSharedPtr< FString > in_Uri, ESelectInfo::Type SelectInfo); + +private: + + /** The pick button widget */ + TSharedPtr PickerButton; + + TSharedPtr UriHandle; + + TSharedPtr Window; +}; + +#endif//WITH_EDITOR \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiSlate/Widgets/Input/AkSSlider.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiSlate/Widgets/Input/AkSSlider.h new file mode 100644 index 0000000..e6cb896 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiSlate/Widgets/Input/AkSSlider.h @@ -0,0 +1,160 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "Input/Reply.h" +#include "Styling/SlateWidgetStyleAsset.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Styling/SlateTypes.h" +#include "Styling/CoreStyle.h" +#include "Framework/SlateDelegates.h" +#include "Widgets/SCompoundWidget.h" + +class SSlider; + +/** + * A Slate slider control is a linear scale and draggable handle. + */ +class AKAUDIO_API AkSSlider + : public SCompoundWidget +{ +public: + + SLATE_BEGIN_ARGS(AkSSlider) + : _Style(&FCoreStyle::Get().GetWidgetStyle("Slider")) + , _IsFocusable(true) + , _OnValueChanged() + , _OnDrop() + {} + /** The style used to draw the slider. */ + SLATE_STYLE_ARGUMENT(FSliderStyle, Style) + + /** Sometimes a slider should only be mouse-clickable and never keyboard focusable. */ + SLATE_ARGUMENT(bool, IsFocusable) + + /** Called when the value is changed by the slider. */ + SLATE_EVENT(FOnFloatValueChanged, OnValueChanged) + + /** Called when an item is dropped into the slider. */ + SLATE_EVENT(FOnDrop, OnDrop) + + /** Slot for this designers content (optional) */ + SLATE_DEFAULT_SLOT(FArguments, Content) + + SLATE_END_ARGS() + + /** + * Construct the widget. + * + * @param InDeclaration A declaration from which to construct the widget. + */ + void Construct( const AkSSlider::FArguments& InDeclaration ); + + TSharedPtr GetAkSilder() const; + + /** + * Getter to access the current value of the slider + * + * @Return the current value of the slider + */ + TOptional GetAkSliderValue() const; + + /** + * Getter to access the current property controlled by the slider + * + * @Return the current property of the slider + */ + FText GetAkSliderProperty() const; + + /** + * Getter to access the name of the current item controlled by the slider + * + * @Return the item name of the item controlled + */ + FText GetAkSliderItemControlled() const; + + void SetAkSliderItemProperty(const FString& ItemProperty); + const FString& GetAkSliderItemProperty() const; + + void SetAkSliderItemId(const FString& ItemId); + const FString& GetAkSliderItemId() const; + + /** + * Setters to update the min and max range of a property + */ + void SetAkSliderRangeMin(const double in_NewValue); + void SetAkSliderRangeMax(const double in_NewValue); + + /** + * Getters to access the min and max range of a property + */ + TOptional GetAkSliderRangeMin() const; + TOptional GetAkSliderRangeMax() const; + + + /** + * Called when the user is dropping something onto a widget; terminates drag and drop. + * + * @param MyGeometry The geometry of the widget receiving the event. + * @param DragDropEvent The drag and drop event. + * + * @return A reply that indicated whether this event was handled. + */ + FReply OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override; + + /** + * Called when the user changes the value of the slider. + * + * @param newValue The new value to set for the slider. + */ + void OnValueCommitted(double newValue, ETextCommit::Type Commit); + +private: + + /** Holds the scrubber slider. */ + TSharedPtr AkScrubberSSlider; + + /** holds the current value of the slider*/ + FText AkSliderValue; + + /** holds the property of the item to be controlled*/ + FString AkSliderItemProperty; + + /** holds the ID of the current item to control*/ + FString AkSliderItemId; + + /** holds the name of the current item to control*/ + FString ItemControlled; + + /** Executed when an item is dropped onto the slider*/ + FOnDrop OnDropDelegate; + + /** The max range of the property controlled by the current slider*/ + double UIMaxValue; + + /** The min range of the property controlled by the current slider*/ + double UIMinValue; + +private: + + /** Callback for changing the value of the 'Position' slider. */ + void HandleAkSliderValueChanged(float NewValue); +protected: + inline void UpdateRange(); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkBoolPropertyToControlCustomization.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkBoolPropertyToControlCustomization.h new file mode 100644 index 0000000..a68eb2a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkBoolPropertyToControlCustomization.h @@ -0,0 +1,59 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#if WITH_EDITOR + +#include "CoreMinimal.h" +#include "Input/Reply.h" +#include "IPropertyTypeCustomization.h" +#include "PropertyHandle.h" + +class SButton; +class SComboButton; +class IMenu; + +class AKAUDIO_API FAkBoolPropertyToControlCustomization : public IPropertyTypeCustomization +{ +public: + static TSharedRef MakeInstance(); + + /** IPropertyTypeCustomization interface */ + virtual void CustomizeHeader( TSharedRef StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils ) override; + + virtual void CustomizeChildren( TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils ) override; + +private: + + /** Delegate used to display the Waapi Picker */ + FReply OnPickContent(TSharedRef PropertyHandle) ; + + /** Handler for tree view selection changes */ + void PropertySelectionChanged(TSharedPtr< FString > ItemProperty, ESelectInfo::Type SelectInfo); + +private: + + /** The pick button widget */ + TSharedPtr PickerButton; + + TSharedPtr ItemPropertyHandle; + + TSharedPtr Window; +}; + +#endif//WITH_EDITOR diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkCheckBox.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkCheckBox.h new file mode 100644 index 0000000..6cf7ef7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkCheckBox.h @@ -0,0 +1,208 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Styling/SlateTypes.h" +#include "Widgets/SWidget.h" +#include "Components/ContentWidget.h" +#include "AkSlider.h" +#include "Widgets/SCompoundWidget.h" +#include "AkItemBoolProperties.h" +#include "AkCheckBox.generated.h" + +class SCheckBox; +class USlateBrushAsset; +class USlateWidgetStyleAsset; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam( FAkOnCheckBoxComponentStateChanged, bool, bIsChecked ); + +/** A delegate type invoked when an item is being dragged. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnWwiseItemDropDetected, FGuid, ItemDroppedID); + +/** A delegate type invoked when a property is being dragged. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnBoolPropertyDropDetected, FString, PropertyDropped); + +/** + * The checkbox widget allows you to display a toggled state of 'unchecked', 'checked' and + * 'indeterminable. You can use the checkbox for a classic checkbox, or as a toggle button, + * or as radio buttons. + * + * * Single Child + * * Toggle + */ +UCLASS(config = Editor, defaultconfig) +class AKAUDIO_API UAkCheckBox : public UContentWidget, public SCompoundWidget +{ + GENERATED_BODY() + +public: + UAkCheckBox(const FObjectInitializer& ObjectInitializer); + + /** Whether the check box is currently in a checked state */ + UPROPERTY(EditAnywhere, Category=Appearance) + ECheckBoxState CheckedState = ECheckBoxState::Undetermined; + + /** A bindable delegate for the IsChecked. */ + UPROPERTY() + FGetCheckBoxState CheckedStateDelegate; + +public: + /** The checkbox bar style */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Style", meta=( DisplayName="Style" )) + FCheckBoxStyle WidgetStyle; + + /** How the content of the toggle button should align within the given space */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Appearance) + TEnumAsByte HorizontalAlignment = EHorizontalAlignment::HAlign_Left; + + /** Sometimes a button should only be mouse-clickable and never keyboard focusable. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Interaction") + bool IsFocusable = false; + + UPROPERTY(EditAnywhere, Category = "Audiokinetic|WAAPI|Checkbox", meta = (DisplayName = "Property to control")) + FAkBoolPropertyToControl ThePropertyToControl; + + UPROPERTY(Config, VisibleAnywhere, Category = "Audiokinetic|WAAPI|Checkbox", meta = (DisplayName = "Item to control")) + FAkWwiseItemToControl ItemToControl; + +public: + + /** Called when the checked state has changed */ + UPROPERTY(BlueprintAssignable, Category="CheckBox|Event") + FAkOnCheckBoxComponentStateChanged AkOnCheckStateChanged; + + /** Called when the item selection changes. */ + UPROPERTY(BlueprintAssignable, Category = "Widget Event") + FOnWwiseItemDropDetected OnItemDropped; + + /** Called when the item selection changes. */ + UPROPERTY(BlueprintAssignable, Category = "Widget Event") + FOnBoolPropertyDropDetected OnPropertyDropped; + +public: + + /** Returns true if this button is currently pressed */ + UFUNCTION(BlueprintCallable, Category="Widget") + bool IsPressed() const; + + /** Returns true if the checkbox is currently checked */ + UFUNCTION(BlueprintCallable, Category="Widget") + bool IsChecked() const; + + /** @return the full current checked state. */ + UFUNCTION(BlueprintCallable, Category="Widget") + ECheckBoxState GetCheckedState() const; + + /** Sets the checked state. */ + UFUNCTION(BlueprintCallable, Category="Widget") + void SetIsChecked(bool InIsChecked); + + /** Sets the checked state. */ + UFUNCTION(BlueprintCallable, Category="Widget") + void SetCheckedState(ECheckBoxState InCheckedState); + + /** Set the item id to the new one. + * @param ItemId - value (new id) to set + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WAAPI|Checkbox", meta = (Keywords = "Set Item Id")) + void SetAkItemId(const FGuid& ItemId); + + /** Returns the current item id. + * + * @return an id as GUID. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WAAPI|Checkbox", meta = (Keywords = "Get Item Id")) + const FGuid GetAkItemId() const; + + /** Set the item property to the new one. + * @param ItemId - value (new id) to set + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WAAPI|Checkbox", meta = (Keywords = "Set Item Property")) + void SetAkBoolProperty(const FString& ItemProperty); + + /** Returns the current item property. + * + * @return a property as string. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WAAPI|Checkbox", meta = (Keywords = "Get Item Property")) + const FString GetAkProperty() const; + + +public: + + //~ Begin UWidget Interface + virtual void SynchronizeProperties() override; + //~ End UWidget Interface + + //~ Begin UVisual Interface + virtual void ReleaseSlateResources(bool bReleaseChildren) override; + //~ End UVisual Interface + + virtual void BeginDestroy() override; + + +#if WITH_EDITOR + virtual const FText GetPaletteCategory() override; +#endif + + /** + * Called when the user is dropping something onto a widget; terminates drag and drop. + * + * @param MyGeometry The geometry of the widget receiving the event. + * @param DragDropEvent The drag and drop event. + * + * @return A reply that indicated whether this event was handled. + */ + FReply OnDropHandler(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent); + +protected: + + // UPanelWidget + virtual void OnSlotAdded(UPanelSlot* Slot) override; + virtual void OnSlotRemoved(UPanelSlot* Slot) override; + // End UPanelWidget + +protected: + //~ Begin UWidget Interface + virtual TSharedRef RebuildWidget() override; + //~ End UWidget Interface + + void SlateOnCheckStateChangedCallback(ECheckBoxState NewState); + +private: + void SetAkItemControlled(const FString& Item); + FText GetAkItemControlled(); + + FText GetAkBoolProperty() const; + + void SynchronizePropertyWithWwise(); + +protected: + TSharedPtr MyCheckbox; + FString ItemControlled; + FString IdItemToControl; + FString BoolPropertyToControl; + uint64 SubscriptionId = 0; + uint64 SubscriptionIdNameChanged = 0; + + + PROPERTY_BINDING_IMPLEMENTATION(ECheckBoxState, CheckedState); + +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkItemBoolProperties.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkItemBoolProperties.h new file mode 100644 index 0000000..58f48a3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkItemBoolProperties.h @@ -0,0 +1,138 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Components/Widget.h" +#include "SAkItemBoolProperties.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Widgets/Text/STextBlock.h" +#include "AkItemBoolProperties.generated.h" + + +class IMenu; +class SButton; + +/** A delegate type invoked when a selection changes somewhere. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnItemBoolPropertySelectionChanged, FString, PropertySelected); + +/** A delegate type invoked when a property is being dragged. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnItemBoolPropertyDragDetected, FString, PropertyDragged); + +/*------------------------------------------------------------------------------------ +FAkBoolPropertyToControl +------------------------------------------------------------------------------------*/ + +/** +* Structure for property to be controlled by the UI. +*/ +USTRUCT(BlueprintType) +struct AKAUDIO_API FAkBoolPropertyToControl +{ + GENERATED_BODY() + + /** + * The name of the item to control + */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = ItemProperty, meta = (DisplayName = "Property")) + FString ItemProperty; +}; + +/*------------------------------------------------------------------------------------ +UAkItemBoolPropertiesConv +------------------------------------------------------------------------------------*/ + +UCLASS() +class AKAUDIO_API UAkItemBoolPropertiesConv : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + UAkItemBoolPropertiesConv(const class FObjectInitializer& ObjectInitializer); + + /** Converts an AkBoolPropertyToControl value to a string */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToString (FAkBoolPropertyToControl)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|String") + static FString Conv_FAkBoolPropertyToControlToString(const FAkBoolPropertyToControl& INAkBoolPropertyToControl); + + /** Converts an AkBoolPropertyToControl value to a localizable text */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToText (FAkBoolPropertyToControl)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|Text") + static FText Conv_FAkBoolPropertyToControlToText(const FAkBoolPropertyToControl& INAkBoolPropertyToControl); +}; + +/*------------------------------------------------------------------------------------ +UAkItemBoolProperties +------------------------------------------------------------------------------------*/ + +/** +* A widget that shows the Wwise properties list. +*/ +UCLASS(config = Editor, defaultconfig) +class AKAUDIO_API UAkItemBoolProperties : public UWidget +{ + GENERATED_BODY() +public: + UAkItemBoolProperties(const FObjectInitializer& ObjectInitializer); + + typedef TSlateDelegates< TSharedPtr< FString > >::FOnSelectionChanged FOnSelectionChanged; + + /** Called when the property selection changes. */ + UPROPERTY(BlueprintAssignable, Category = "Widget Event") + FOnItemBoolPropertySelectionChanged OnSelectionChanged; + + /** Called when a property is dragged from the property list. */ + UPROPERTY(BlueprintAssignable, Category = "Widget Event") + FOnItemBoolPropertyDragDetected OnPropertyDragged; + + /** Returns all properties currently selected in the Wwise properties list */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic") + FString GetSelectedProperty() const; + + /** returns the current text of the searchBox */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic") + FString GetSearchText() const; + + /** sets the current text of the searchBox */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic") + void SetSearchText(const FString& newText); + + // UVisual interface + virtual void ReleaseSlateResources(bool bReleaseChildren) override; + // End of UVisual interface + +#if WITH_EDITOR + virtual const FText GetPaletteCategory() override; +#endif + +protected: + + // UWidget interface + virtual TSharedRef RebuildWidget() override; + // End of UWidget interface + +private: + /** Delegate used to handle the value of the property to be controlled */ + void PropertySelectionChanged(TSharedPtr< FString > PropertySelected, ESelectInfo::Type SelectInfo); + + /** Delegate used to handle the drag detected action on the items properties. */ + FReply HandleOnDragDetected(const FGeometry& Geometry, const FPointerEvent& MouseEvent); +private: + /** The widget to display the property name */ + TSharedPtr PropertyTextBlock; + + /** The Wwise list used to pick an item property */ + TSharedPtr WwiseProperties; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkItemProperties.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkItemProperties.h new file mode 100644 index 0000000..fc0a954 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkItemProperties.h @@ -0,0 +1,136 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Components/Widget.h" +#include "SAkItemProperties.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Widgets/Text/STextBlock.h" +#include "AkItemProperties.generated.h" + +class IMenu; +class SButton; +/** A delegate type invoked when a selection changes somewhere. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnItemPropertySelectionChanged, FString, PropertySelected); + +/** A delegate type invoked when a property is being dragged. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnItemPropertyDragDetected, FString, PropertyDragged); + +/*------------------------------------------------------------------------------------ +FAkPropertyToControl +------------------------------------------------------------------------------------*/ + +/** +* Structure for property to be controlled by the UI. +*/ +USTRUCT(BlueprintType) +struct AKAUDIO_API FAkPropertyToControl +{ + GENERATED_BODY() + + /** + * The name of the item to control + */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = ItemProperty, meta = (DisplayName = "Property")) + FString ItemProperty; +}; + +/*------------------------------------------------------------------------------------ +UAkItemPropertiesConv +------------------------------------------------------------------------------------*/ + +UCLASS() +class AKAUDIO_API UAkItemPropertiesConv : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + UAkItemPropertiesConv(const class FObjectInitializer& ObjectInitializer); + + /** Converts an AkPropertyToControl value to a string */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToString (FAkPropertyToControl)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|String") + static FString Conv_FAkPropertyToControlToString(const FAkPropertyToControl& INAkPropertyToControl); + + /** Converts an AkPropertyToControl value to a localizable text */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToText (FAkPropertyToControl)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|Text") + static FText Conv_FAkPropertyToControlToText(const FAkPropertyToControl& INAkPropertyToControl); +}; + +/*------------------------------------------------------------------------------------ +UAkItemProperties +------------------------------------------------------------------------------------*/ + +/** +* A widget that shows the Wwise properties list. +*/ +UCLASS(config = Editor, defaultconfig) +class AKAUDIO_API UAkItemProperties : public UWidget +{ + GENERATED_BODY() +public: + UAkItemProperties(const FObjectInitializer& ObjectInitializer); + + typedef TSlateDelegates< TSharedPtr< FString > >::FOnSelectionChanged FOnSelectionChanged; + + /** Called when the property selection changes. */ + UPROPERTY(BlueprintAssignable, Category = "Widget Event") + FOnItemPropertySelectionChanged OnSelectionChanged; + + /** Called when a property is dragged from the property list. */ + UPROPERTY(BlueprintAssignable, Category = "Widget Event") + FOnItemPropertyDragDetected OnPropertyDragged; + + /** Returns all properties currently selected in the Wwise properties list */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic") + FString GetSelectedProperty() const; + + /** returns the current text of the searchBox */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic") + FString GetSearchText() const; + + /** sets the current text of the searchBox */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic") + void SetSearchText(const FString& newText); + + // UVisual interface + virtual void ReleaseSlateResources(bool bReleaseChildren) override; + // End of UVisual interface + +#if WITH_EDITOR + virtual const FText GetPaletteCategory() override; +#endif + +protected: + + // UWidget interface + virtual TSharedRef RebuildWidget() override; + // End of UWidget interface + +private: + /** Delegate used to handle the value of the property to be controlled */ + void PropertySelectionChanged(TSharedPtr< FString > PropertySelected, ESelectInfo::Type SelectInfo); + + /** Delegate used to handle the drag detected action on the items properties. */ + FReply HandleOnDragDetected(const FGeometry& Geometry, const FPointerEvent& MouseEvent); +private: + /** The widget to display the property name */ + TSharedPtr PropertyTextBlock; + + /** The Wwise list used to pick an item property */ + TSharedPtr WwiseProperties; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkPropertyToControlCustomization.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkPropertyToControlCustomization.h new file mode 100644 index 0000000..b875ce2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkPropertyToControlCustomization.h @@ -0,0 +1,60 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#if WITH_EDITOR + +#include "CoreMinimal.h" +#include "Input/Reply.h" +#include "IPropertyTypeCustomization.h" +#include "PropertyHandle.h" + +class SButton; +class SComboButton; +class IMenu; + +class AKAUDIO_API FAkPropertyToControlCustomization : public IPropertyTypeCustomization +{ +public: + static TSharedRef MakeInstance(); + + /** IPropertyTypeCustomization interface */ + virtual void CustomizeHeader( TSharedRef StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils ) override; + + virtual void CustomizeChildren( TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils ) override; + +private: + + + /** Delegate used to display the Waapi Picker */ + FReply OnPickContent(TSharedRef PropertyHandle) ; + + /** Handler for tree view selection changes */ + void PropertySelectionChanged(TSharedPtr< FString > ItemProperty, ESelectInfo::Type SelectInfo); + +private: + + /** The pick button widget */ + TSharedPtr PickerButton; + + TSharedPtr ItemPropertyHandle; + + TSharedPtr Window; +}; + +#endif//WITH_EDITOR diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkSlider.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkSlider.h new file mode 100644 index 0000000..cc870aa --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkSlider.h @@ -0,0 +1,253 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Components/Widget.h" +#include "AkItemProperties.h" +#include "AkSlider.generated.h" + +class AkSSlider; + +/** A delegate type invoked when the value of the slider changes. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAkOnFloatValueChangedEvent, float, Value); + +/** A delegate type invoked when an item is being dragged. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnItemDropDetected, FGuid, ItemDroppedID); + +/** A delegate type invoked when a property is being dragged. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPropertyDropDetected, FString, PropertyDropped); + +/** +* Structure for Wwise item details. +*/ +USTRUCT(BlueprintType) +struct AKAUDIO_API FAkWwiseObjectDetails +{ + GENERATED_BODY() + + /** + * The name of the item to control + */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=ItemName, meta = (DisplayName = "Name")) + FString ItemName; + + /** + * The id of the item to control + */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = ItemPath, meta = (DisplayName = "Path")) + FString ItemPath; + + /** + * The id of the item to control + */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category =ItemId, meta = (DisplayName = "Id")) + FString ItemId; +}; + +/** +* Structure for Wwise items that are displayed in the UMG. +*/ +USTRUCT(BlueprintType) +struct AKAUDIO_API FAkWwiseItemToControl //: public UObject +{ + GENERATED_BODY() + + /** + * The item to control + */ + UPROPERTY(VisibleAnywhere, Category= ItemPicked, meta = (DisplayName = "Name")) + FAkWwiseObjectDetails ItemPicked; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=ItemPath, meta = (DisplayName = "Path")) + FString ItemPath; +}; + +/*------------------------------------------------------------------------------------ +UAkSlider +------------------------------------------------------------------------------------*/ + +/** + * A simple widget that shows a sliding bar with a handle that allows you to control the value between 0..1. + * + * * No Children + */ +UCLASS(config = Editor, defaultconfig) +class AKAUDIO_API UAkSlider : public UWidget +{ + GENERATED_BODY() + +public: + UAkSlider(const FObjectInitializer& ObjectInitializer); + + /** The volume value to display. */ + UPROPERTY(EditAnywhere, Category=Appearance, meta=(ClampMin = "0", ClampMax = "1", UIMin = "0", UIMax = "1")) + float Value = .0f; + + /** A bindable delegate to allow logic to drive the value of the widget */ + UPROPERTY() + FGetFloat ValueDelegate; + +public: + + /** The progress bar style */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Style", meta=( DisplayName="Style" )) + FSliderStyle WidgetStyle; + + /** The slider's orientation. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Appearance) + TEnumAsByte Orientation = EOrientation::Orient_Horizontal; + + /** The color to draw the slider bar in. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Appearance) + FLinearColor SliderBarColor = FLinearColor(EForceInit::ForceInitToZero); + + /** The color to draw the slider handle in. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Appearance) + FLinearColor SliderHandleColor = FLinearColor(EForceInit::ForceInitToZero); + + /** Whether the slidable area should be indented to fit the handle. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Appearance, AdvancedDisplay) + bool IndentHandle = false; + + /** Whether the handle is interactive or fixed. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Appearance, AdvancedDisplay) + bool Locked = false; + + /** The amount to adjust the value by, when using a controller or keyboard */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Appearance, meta=( ClampMin="0", ClampMax="1", UIMin="0", UIMax="1")) + float StepSize = .0f; + + /** Should the slider be focusable? */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Interaction") + bool IsFocusable = false; + + UPROPERTY(VisibleAnywhere, Category = "Audiokinetic|WAAPI|Slider", meta = (DisplayName = "Property to control")) + FAkPropertyToControl ThePropertyToControl; + + UPROPERTY(Config, VisibleAnywhere, Category = "Audiokinetic|WAAPI|Slider", meta = (DisplayName = "Item to control")) + FAkWwiseItemToControl ItemToControl; + + /** Called when the value is changed by slider or typing. */ + UPROPERTY(BlueprintAssignable, Category="Widget Event") + FAkOnFloatValueChangedEvent OnValueChanged; + + /** Called when the item selection changes. */ + UPROPERTY(BlueprintAssignable, Category = "Widget Event") + FOnItemDropDetected OnItemDropped; + + /** Called when the item selection changes. */ + UPROPERTY(BlueprintAssignable, Category = "Widget Event") + FOnPropertyDropDetected OnPropertyDropped; + +public: + + /** Gets the current value of the slider. */ + UFUNCTION(BlueprintCallable, Category="Behavior") + float GetValue() const; + + /** Sets the current value of the slider. */ + UFUNCTION(BlueprintCallable, Category="Behavior") + void SetValue(float InValue); + + /** Sets if the slidable area should be indented to fit the handle */ + UFUNCTION(BlueprintCallable, Category="Behavior") + void SetIndentHandle(bool InValue); + + /** Sets the handle to be interactive or fixed */ + UFUNCTION(BlueprintCallable, Category="Behavior") + void SetLocked(bool InValue); + + /** Sets the amount to adjust the value by, when using a controller or keyboard */ + UFUNCTION(BlueprintCallable, Category="Behavior") + void SetStepSize(float InValue); + + /** Sets the color of the slider bar */ + UFUNCTION(BlueprintCallable, Category="Appearance") + void SetSliderBarColor(FLinearColor InValue); + + /** Sets the color of the handle bar */ + UFUNCTION(BlueprintCallable, Category="Appearance") + void SetSliderHandleColor(FLinearColor InValue); + + /** Set the item id to the new one. + * @param ItemId - value (new id) to set + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WAAPI|Slider", meta = (Keywords = "Set Item Id")) + void SetAkSliderItemId(const FGuid& ItemId); + + /** Returns the current item id. + * + * @return an id as GUID. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WAAPI|Slider", meta = (Keywords = "Get Item Id")) + const FGuid GetAkSliderItemId() const; + + /** Set the item property to the new one. + * @param ItemId - value (new id) to set + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WAAPI|Slider", meta = (Keywords = "Set Item Property")) + void SetAkSliderItemProperty(const FString& ItemProperty); + + /** Returns the current item property. + * + * @return a property as string. + */ + UFUNCTION(BlueprintCallable, Category = "Audiokinetic|WAAPI|Slider", meta = (Keywords = "Get Item Property")) + const FString GetAkSliderItemProperty() const; + + // UWidget interface + virtual void SynchronizeProperties() override; + // End of UWidget interface + + // UVisual interface + virtual void ReleaseSlateResources(bool bReleaseChildren) override; + // End of UVisual interface + + virtual void BeginDestroy() override; + +#if WITH_EDITOR + virtual const FText GetPaletteCategory() override; +#endif + +protected: + uint64 SubscriptionId; + + /** Native Slate Widget */ + TSharedPtr MyAkSlider; + + // UWidget interface + virtual TSharedRef RebuildWidget() override; + // End of UWidget interface + + void HandleOnValueChanged(float InValue); + + /** Handles dropping of a wwise item onto the slider */ + FReply HandleDropped(const FGeometry& DropZoneGeometry, const FDragDropEvent& DragDropEvent); + + PROPERTY_BINDING_IMPLEMENTATION(float, Value); +private: + void SynchronizePropertyWithWwise(); + void UnsubscribePropertyChangedCallback(); + + /** The minimum value the the slider could take */ + float minValue; + + /** The minimum value the the slider could take */ + float maxValue; + +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkWwiseObjectDetailsStructCustomization.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkWwiseObjectDetailsStructCustomization.h new file mode 100644 index 0000000..c30ebd9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkWwiseObjectDetailsStructCustomization.h @@ -0,0 +1,63 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#if WITH_EDITOR + +#include "CoreMinimal.h" +#include "Input/Reply.h" +#include "IPropertyTypeCustomization.h" +#include "PropertyHandle.h" +#include "WaapiPicker/SWaapiPicker.h" + +class SButton; +class SComboButton; +class IMenu; + +class AKAUDIO_API FAkWwiseObjectDetailsStructCustomization : public IPropertyTypeCustomization +{ +public: + static TSharedRef MakeInstance(); + + /** IPropertyTypeCustomization interface */ + virtual void CustomizeHeader( TSharedRef StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils ) override; + + virtual void CustomizeChildren( TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils ) override; + +private: + + + /** Delegate used to display the Waapi Picker */ + FReply OnPickContent(TSharedRef PropertyHandle) ; + + /** Handler for tree view selection changes */ + void TreeSelectionChanged(TSharedPtr< FWwiseTreeItem > TreeItem, ESelectInfo::Type SelectInfo); + +private: + + /** The pick button widget */ + TSharedPtr PickerButton; + + TSharedPtr ItemNameProperty; + TSharedPtr ItemPathProperty; + TSharedPtr ItemIdProperty; + + TSharedPtr WaapiPicker; +}; + +#endif//WITH_EDITOR diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkWwiseTree.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkWwiseTree.h new file mode 100644 index 0000000..5ba7395 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkWwiseTree.h @@ -0,0 +1,99 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Components/Widget.h" +#include "WaapiPicker/SWaapiPicker.h" +#include "AkSlider.h" +#include "AkWwiseTree.generated.h" + +class IMenu; +class SButton; +/** A delegate type invoked when a selection changes somewhere. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnItemSelectionChanged, FGuid, ItemSelectedID); + +/** A delegate type invoked when an item is being dragged. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnItemDragDetected, FGuid, ItemDraggedID, FString, ItemDraggedName); + +/*------------------------------------------------------------------------------------ +UAkSlider +------------------------------------------------------------------------------------*/ + +/** +* A widget that shows the Wwise tree items. +*/ +UCLASS(config = Editor, defaultconfig) +class AKAUDIO_API UAkWwiseTree : public UWidget +{ + GENERATED_BODY() +public: + UAkWwiseTree(const FObjectInitializer& ObjectInitializer); + + typedef TSlateDelegates< TSharedPtr< FWwiseTreeItem > >::FOnSelectionChanged FOnSelectionChanged; + + /** Called when the item selection changes. */ + UPROPERTY(BlueprintAssignable, Category = "Widget Event") + FOnItemSelectionChanged OnSelectionChanged; + + /** Called when an item is dragged from the wwise tree. */ + UPROPERTY(BlueprintAssignable, Category = "Widget Event") + FOnItemDragDetected OnItemDragged; + + /** Returns all properties currently selected in the Wwise properties list */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic") + FAkWwiseObjectDetails GetSelectedItem() const; + + /** returns the current text of the searchBox */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic") + FString GetSearchText() const; + + /** sets the current text of the searchBox */ + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic") + void SetSearchText(const FString& newText); + + // UWidget interface + virtual void SynchronizeProperties() override; + // End of UWidget interface + + // UVisual interface + virtual void ReleaseSlateResources(bool bReleaseChildren) override; + // End of UVisual interface + +#if WITH_EDITOR + virtual const FText GetPaletteCategory() override; +#endif + +protected: + + // UWidget interface + virtual TSharedRef RebuildWidget() override; + // End of UWidget interface + +private: + /** Delegate used to handle the value of the item to be controlled */ + void TreeSelectionChanged(TSharedPtr< FWwiseTreeItem > TreeItem, ESelectInfo::Type SelectInfo); + + /** Delegate used to handle the drag detected action on the Wwise items. */ + FReply HandleOnDragDetected(const FGeometry& Geometry, const FPointerEvent& MouseEvent); +private: + /** The widget to display the item name */ + TSharedPtr ItemTextBlock; + + /** The Wwise tree used to pick an item */ + TSharedPtr WaapiPicker; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkWwiseTreeSelector.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkWwiseTreeSelector.h new file mode 100644 index 0000000..4a1b681 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/AkWwiseTreeSelector.h @@ -0,0 +1,93 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Components/Widget.h" +#include "WaapiPicker/SWaapiPicker.h" +#include "AkWwiseTree.h" +#include "AkWwiseTreeSelector.generated.h" + + +class IMenu; +class SButton; + +/*------------------------------------------------------------------------------------ +UAkSlider +------------------------------------------------------------------------------------*/ + +/** +* A widget that shows the Wwise tree items. +*/ +UCLASS(config = Editor, defaultconfig) +class AKAUDIO_API UAkWwiseTreeSelector : public UWidget +{ + GENERATED_BODY() +public: + UAkWwiseTreeSelector(const FObjectInitializer& ObjectInitializer); + + typedef TSlateDelegates< TSharedPtr< FWwiseTreeItem > >::FOnSelectionChanged FOnSelectionChanged; + + /** Called when the item selection changes. */ + UPROPERTY(BlueprintAssignable, Category = "Widget Event") + FOnItemSelectionChanged OnSelectionChanged; + + /** Called when an item is dragged from the wwise tree. */ + UPROPERTY(BlueprintAssignable, Category = "Widget Event") + FOnItemDragDetected OnItemDragged; + + // UWidget interface + virtual void SynchronizeProperties() override; + // End of UWidget interface + + // UVisual interface + virtual void ReleaseSlateResources(bool bReleaseChildren) override; + // End of UVisual interface + +#if WITH_EDITOR + virtual const FText GetPaletteCategory() override; +#endif + +protected: + + // UWidget interface + virtual TSharedRef RebuildWidget() override; + // End of UWidget interface + +private: + /** Delegate used to handle the value of the item to be controlled */ + void TreeSelectionChanged(TSharedPtr< FWwiseTreeItem > TreeItem, ESelectInfo::Type SelectInfo); + + /** Delegate used to handle the drag detected action on the Wwise items. */ + FReply HandleOnDragDetected(const FGeometry& Geometry, const FPointerEvent& MouseEvent); + + /** Delegate used to display the Waapi Picker */ + FReply HandleButtonClicked(); + +private: + /** The widget to display the item name */ + TSharedPtr ItemTextBlock; + + /** The Wwise tree used to pick an item */ + TSharedPtr WaapiPicker; + + /** The pick button widget */ + TSharedPtr PickerButton; + + /** The pick button popup menu*/ + TSharedPtr PickerMenu; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/SAkItemBoolProperties.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/SAkItemBoolProperties.h new file mode 100644 index 0000000..8b11248 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/SAkItemBoolProperties.h @@ -0,0 +1,106 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ + AkItemProperties.h +------------------------------------------------------------------------------------*/ +#pragma once + +/*------------------------------------------------------------------------------------ + SAkItemBoolProperties +------------------------------------------------------------------------------------*/ + +#include "Widgets/Views/STableRow.h" +#include "Widgets/Input/SSearchBox.h" +#include "Misc/TextFilter.h" + +typedef TTextFilter< const FString& > StringFilter; + +class AKAUDIO_API SAkItemBoolProperties : public SCompoundWidget +{ +public: + typedef TSlateDelegates< TSharedPtr< FString > >::FOnSelectionChanged FOnSelectionChanged; + +public: + SLATE_BEGIN_ARGS(SAkItemBoolProperties) + : _FocusSearchBoxWhenOpened(false) + , _SelectionMode( ESelectionMode::Multi ) + {} + + /** Content displayed to the left of the search bar */ + SLATE_NAMED_SLOT( FArguments, SearchContent ) + + /** If true, the search box will be focus the frame after construction */ + SLATE_ARGUMENT( bool, FocusSearchBoxWhenOpened ) + + /** The selection mode for the list view */ + SLATE_ARGUMENT( ESelectionMode::Type, SelectionMode ) + + /** Handles the drag and drop operations */ + SLATE_EVENT(FOnDragDetected, OnDragDetected) + + /** Handles the selection operation */ + SLATE_EVENT(FOnSelectionChanged, OnSelectionChanged) + + SLATE_END_ARGS( ) + + void Construct(const FArguments& InArgs); + SAkItemBoolProperties(void); + ~SAkItemBoolProperties(); + + /** Returns all properties currently selected in the Wwise properties list */ + const TArray> GetSelectedProperties() const; + + const FString GetSearchText() const; + const void SetSearchText(const FString& newText); + +private: + /** The tree view widget */ + TSharedPtr< SListView< TSharedPtr> > ListViewPtr; + + /** The property list search box */ + TSharedPtr< SSearchBox > SearchBoxPtr; + + /** Filter for the search box */ + TSharedPtr SearchBoxFilter; + + /** Property list */ + TArray< TSharedPtr > PropertiesList; + + /** Delegate to invoke when drag drop detected. */ + FOnDragDetected OnDragDetected; + + /** Delegate to invoke when a property is selected. */ + FOnSelectionChanged OnSelectionChanged; + +private: + /** One-off active timer to focus the widget post-construct */ + EActiveTimerReturnType SetFocusPostConstruct(double InCurrentTime, float InDeltaTime); + + /** Generate a row in the tree view */ + TSharedRef GenerateRow( TSharedPtr ItemProperty, const TSharedRef& OwnerTable ); + + /** Handle Drag & Drop */ + FReply HandleOnDragDetected(const FGeometry& Geometry, const FPointerEvent& MouseEvent); + + /** Used by the search filter */ + void PopulateSearchStrings( const FString& PropertyName, OUT TArray< FString >& OutSearchStrings ) const; + void FilterUpdated(); + + /** Handler for list view selection changes */ + void ListSelectionChanged( TSharedPtr< FString > ItemProperty, ESelectInfo::Type SelectInfo ); +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/SAkItemProperties.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/SAkItemProperties.h new file mode 100644 index 0000000..3e48c9b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/SAkItemProperties.h @@ -0,0 +1,108 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ + AkItemProperties.h +------------------------------------------------------------------------------------*/ +#pragma once + +/*------------------------------------------------------------------------------------ + SAkItemProperties +------------------------------------------------------------------------------------*/ + +#include "Misc/TextFilter.h" +#include "Widgets/Views/SListView.h" +#include "Widgets/Views/STableRow.h" +#include "Widgets/Input/SSearchBox.h" + +typedef TTextFilter< const FString& > StringFilter; + +class AKAUDIO_API SAkItemProperties : public SCompoundWidget +{ +public: + typedef TSlateDelegates< TSharedPtr< FString > >::FOnSelectionChanged FOnSelectionChanged; + +public: + SLATE_BEGIN_ARGS(SAkItemProperties) + : _FocusSearchBoxWhenOpened(false) + , _SelectionMode( ESelectionMode::Multi ) + {} + + /** Content displayed to the left of the search bar */ + SLATE_NAMED_SLOT( FArguments, SearchContent ) + + /** If true, the search box will be focus the frame after construction */ + SLATE_ARGUMENT( bool, FocusSearchBoxWhenOpened ) + + /** The selection mode for the list view */ + SLATE_ARGUMENT( ESelectionMode::Type, SelectionMode ) + + /** Handles the drag and drop operations */ + SLATE_EVENT(FOnDragDetected, OnDragDetected) + + /** Handles the selection operation */ + SLATE_EVENT(FOnSelectionChanged, OnSelectionChanged) + + SLATE_END_ARGS( ) + + void Construct(const FArguments& InArgs); + SAkItemProperties(void); + ~SAkItemProperties(); + + /** Returns all the items currently selected in the Waapi Picker view */ + const TArray> GetSelectedProperties() const; + + const FString GetSearchText() const; + const void SetSearchText(const FString& newText); + +private: + /** The tree view widget */ + TSharedPtr< SListView< TSharedPtr> > ListViewPtr; + + /** The property list search box */ + TSharedPtr< SSearchBox > SearchBoxPtr; + + /** Filter for the search box */ + TSharedPtr SearchBoxFilter; + + /** Property list */ + TArray< TSharedPtr > PropertiesList; + + /** Delegate to invoke when drag drop detected. */ + FOnDragDetected OnDragDetected; + + /** Delegate to invoke when a property is selected. */ + FOnSelectionChanged OnSelectionChanged; + +private: + /** One-off active timer to focus the widget post-construct */ + EActiveTimerReturnType SetFocusPostConstruct(double InCurrentTime, float InDeltaTime); + + /** Generate a row in the tree view */ + TSharedRef GenerateRow( TSharedPtr ItemProperty, const TSharedRef& OwnerTable ); + + /** Handle Drag & Drop */ + FReply HandleOnDragDetected(const FGeometry& Geometry, const FPointerEvent& MouseEvent); + + /** Used by the search filter */ + void PopulateSearchStrings( const FString& PropertyName, OUT TArray< FString >& OutSearchStrings ) const; + void OnSearchBoxChanged( const FText& InSearchText ); + void FilterUpdated(); + + /** Handler for list view selection changes */ + void ListSelectionChanged( TSharedPtr< FString > ItemProperty, ESelectInfo::Type SelectInfo ); +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/WwiseBoolPropertyDragDropOp.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/WwiseBoolPropertyDragDropOp.h new file mode 100644 index 0000000..f7538b1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/WwiseBoolPropertyDragDropOp.h @@ -0,0 +1,93 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "Input/DragAndDrop.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Text/STextBlock.h" +#include "WaapiPicker/WwiseTreeItem.h" +#include "AkAudioStyle.h" + +class AKAUDIO_API FWwiseBoolPropertyDragDropOp : public FDragDropOperation +{ +public: + + DRAG_DROP_OPERATOR_TYPE(FWwiseBoolPropertyDragDropOp, FDragDropOperation) + + static TSharedRef New(const TArray>& in_Wwiseproperties) + { + TSharedRef Operation = MakeShareable(new FWwiseBoolPropertyDragDropOp); + + Operation->MouseCursor = EMouseCursor::GrabHandClosed; + Operation->Wwiseproperties = in_Wwiseproperties; + Operation->Construct(); + + return Operation; + } + + const TArray< TSharedPtr >& GetWiseProperties() const + { + return Wwiseproperties; + } + +public: + FText GetDecoratorText() const + { + FString Text = Wwiseproperties.Num() == 1 ? *Wwiseproperties[0].Get() : TEXT(""); + + if (Wwiseproperties.Num() > 1 ) + { + Text = FString::Printf(TEXT("Can't handle more than one Property")); + } + return FText::FromString(Text); + } + + virtual TSharedPtr GetDefaultDecorator() const override + { + return + SNew(SBorder) + .BorderImage(FAkAudioStyle::GetBrush("AudiokineticTools.AssetDragDropTooltipBackground")) + .Content() + [ + SNew(SHorizontalBox) + // slot for the item name. + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .AutoWidth() + .Padding(3,0,3,0) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(this, &FWwiseBoolPropertyDragDropOp::GetDecoratorText) + ] + ] + ]; + } + +private: + /** Data for the asset this item represents */ + TArray> Wwiseproperties; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/WwisePropertyDragDropOp.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/WwisePropertyDragDropOp.h new file mode 100644 index 0000000..5d2e549 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/WwisePropertyDragDropOp.h @@ -0,0 +1,100 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "Input/DragAndDrop.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Text/STextBlock.h" +#include "WaapiPicker/WwiseTreeItem.h" +#include "AkAudioStyle.h" + +class AKAUDIO_API FWwisePropertyDragDropOp : public FDragDropOperation +{ +public: + + DRAG_DROP_OPERATOR_TYPE(FWwisePropertyDragDropOp, FDragDropOperation) + + static TSharedRef New(const TArray>& in_Wwiseproperties) + { + TSharedRef Operation = MakeShareable(new FWwisePropertyDragDropOp); +#if WITH_EDITOR + Operation->MouseCursor = EMouseCursor::GrabHandClosed; +#else + Operation->MouseCursor = EMouseCursor::None; +#endif + Operation->Wwiseproperties = in_Wwiseproperties; + Operation->Construct(); + + return Operation; + } + + const TArray< TSharedPtr >& GetWiseProperties() const + { + return Wwiseproperties; + } + +public: + FText GetDecoratorText() const + { + FString Text = Wwiseproperties.Num() == 1 ? *Wwiseproperties[0].Get() : TEXT(""); + + if (Wwiseproperties.Num() > 1 ) + { + Text = FString::Printf(TEXT("Can't handle more than one Property")); + } + return FText::FromString(Text); + } + + virtual TSharedPtr GetDefaultDecorator() const override + { +#if WITH_EDITOR + return + SNew(SBorder) + .BorderImage(FAkAudioStyle::GetBrush("AudiokineticTools.AssetDragDropTooltipBackground")) + .Content() + [ + SNew(SHorizontalBox) + // slot for the item name. + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .AutoWidth() + .Padding(3,0,3,0) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(this, &FWwisePropertyDragDropOp::GetDecoratorText) + ] + ] + ]; +#else + return NULL; +#endif + } + +private: + /** Data for the asset this item represents */ + TArray> Wwiseproperties; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/WwiseUmgDragDropOp.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/WwiseUmgDragDropOp.h new file mode 100644 index 0000000..192ea38 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUMG/Components/WwiseUmgDragDropOp.h @@ -0,0 +1,114 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "Input/DragAndDrop.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Text/STextBlock.h" +#include "WaapiPicker/WwiseTreeItem.h" +#include "AkAudioStyle.h" + +class AKAUDIO_API FWwiseUmgDragDropOp : public FDragDropOperation +{ +public: + + DRAG_DROP_OPERATOR_TYPE(FWwiseUmgDragDropOp, FDragDropOperation) + + static TSharedRef New(const TArray>& in_WwiseAssets) + { + TSharedRef Operation = MakeShareable(new FWwiseUmgDragDropOp); + + Operation->MouseCursor = EMouseCursor::GrabHandClosed; + if (in_WwiseAssets.Num() && in_WwiseAssets[0].IsValid()) + { + Operation->WwiseAssets = in_WwiseAssets; + Operation->Icon = FAkAudioStyle::GetBrush(in_WwiseAssets[0]->ItemType); + } + Operation->Construct(); + + return Operation; + } + + const TArray< TSharedPtr >& GetWiseItems() const + { + return WwiseAssets; + } + +public: + FText GetDecoratorText() const + { + FString Text = ((WwiseAssets.Num() == 1) && WwiseAssets[0].IsValid()) ? WwiseAssets[0]->DisplayName : TEXT(""); + + if (WwiseAssets.Num() > 1 ) + { + Text = FString::Printf(TEXT("Can't handle more than one item")); + } + return FText::FromString(Text); + } + + const FSlateBrush* GetIcon() const + { + return Icon; + } + + virtual TSharedPtr GetDefaultDecorator() const override + { + return + SNew(SBorder) + .BorderImage(FAkAudioStyle::GetBrush("AudiokineticTools.AssetDragDropTooltipBackground")) + .Content() + [ + SNew(SHorizontalBox) + + // Left slot is wwise item icon. + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SImage) + .Image(this, &FWwiseUmgDragDropOp::GetIcon) + ] + + // Right slot for the item name. + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .AutoWidth() + .Padding(3,0,3,0) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(this, &FWwiseUmgDragDropOp::GetDecoratorText) + ] + ] + ]; + } + +private: + /** Data for the asset this item represents */ + TArray> WwiseAssets; + + const FSlateBrush* Icon; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUtils.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUtils.h new file mode 100644 index 0000000..c2a2b66 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AkWaapiUtils.h @@ -0,0 +1,118 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ + WwiseUtils.h +------------------------------------------------------------------------------------*/ +#pragma once + +#include "Dom/JsonObject.h" +#include "AkWaapiClient.h" + +class AKAUDIO_API WwiseWaapiHelper +{ +public: + static const FString ACTION; + static const FString ANCESTORS; + static const FString AT; + static const FString AUX_BUSSES; + static const FString BACK_SLASH; + static const FString BANK_DATA; + static const FString BANK_INFO; + static const FString CHILD; + static const FString CHILDREN; + static const FString CHILDREN_COUNT; + static const FString CLASSID; + static const FString COMMAND; + static const FString DATA; + static const FString DELETE_ITEMS; + static const FString DESCENDANTS; + static const FString DISPLAY_NAME; + static const FString DRAG_DROP_ITEMS; + static const FString EVENT; + static const FString EVENTS; + static const FString FILEPATH; + static const FString FILTER; + static const FString FIND_IN_PROJECT_EXPLORER; + static const FString FOLDER; + static const FString FROM; + static const FString ID; + static const FString INCLUSIONS; + static const FString INFO_FILE; + static const FString IS_CONNECTED; + static const FString LANGUAGE; + static const FString LANGUAGES; + static const FString MAX; + static const FString MAX_RADIUS_ATTENUATION; + static const FString MESSSAGE; + static const FString MIN; + static const FString NAME; + static const FString NAMECONTAINS; + static const FString NEW; + static const FString NEW_NAME; + static const FString NOTES; + static const FString OBJECT; + static const FString OBJECTS; + static const FString OF_TYPE; + static const FString OLD_NAME; + static const FString ON_NAME_CONFLICT; + static const FString OPERATION; + static const FString PARENT; + static const FString PATH; + static const FString PHYSICAL_FOLDER; + static const FString PLATFORM; + static const FString PLATFORMS; + static const FString PLAY; + static const FString PLAYING; + static const FString PLAYSTOP; + static const FString PLUGININFO_OPTIONS; + static const FString PLUGININFO_RESPONSE; + static const FString PROJECT; + static const FString PROPERTY; + static const FString RADIUS; + static const FString RANGE; + static const FString REBUILD; + static const FString REBUILD_INIT_BANK; + static const FString REDO; + static const FString RENAME; + static const FString RESTRICTION; + static const FString RETURN; + static const FString SEARCH; + static const FString SELECT; + static const FString SIZE; + static const FString SKIP_LANGUAGES; + static const FString SOUNDBANK_TYPE; + static const FString SOUNDBANK_FIELD; + static const FString SOUNDBANKS; + static const FString STATE; + static const FString STOP; + static const FString STOPPED; + static const FString STRUCTURE; + static const FString TRANSFORM; + static const FString TRANSPORT; + static const FString TYPE; + static const FString UI; + static const FString UNDO; + static const FString VALUE; + static const FString VOLUME; + static const FString WHERE; + static const FString WORKUNIT_TYPE; + static const FString WRITE_TO_DISK; +}; + +bool CallWappiGetPropertySate(const FString& ItemID, const FString& ItemProperty, TSharedPtr& ItemInfoResult); +bool SubscribeToPropertyStateChange(const FString& ItemID, const FString& ItemProperty, WampEventCallback CallBack, uint64& SubscriptionId, TSharedPtr& outJsonResult); \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AssetManagement/WwiseProjectInfo.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AssetManagement/WwiseProjectInfo.h new file mode 100644 index 0000000..639beda --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/AssetManagement/WwiseProjectInfo.h @@ -0,0 +1,81 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Containers/Array.h" +#include "Containers/Set.h" +#include "Containers/UnrealString.h" +#include "FastXml.h" + +struct FWwiseLanguageInfo +{ + FGuid ID; + FString Name; + uint32 ShortID; +}; + +struct FWwisePlatformInfo +{ + FGuid ID; + FString Name; +}; + +class AKAUDIO_API WwiseProjectInfo : public IFastXmlCallback +{ +public: + virtual ~WwiseProjectInfo() {} + + void Parse(); + + void ParseCacheDirectory(const FString ProjectFileString); + + static void SanitizeProjectFileString(FString& InOutProjectFileString); + + FString const& GetCacheDirectory() const { return CacheDirectory; } + + const TArray& GetSupportedPlatforms() const { return SupportedPlatforms; } + const TArray& GetSupportedLanguages() const { return SupportedLanguages; } + const FString& GetDefaultLanguage() { return DefaultLanguage; } + + bool ProcessAttribute(const TCHAR* AttributeName, const TCHAR* AttributeValue) override; + bool ProcessClose(const TCHAR* Element) override; + bool ProcessComment(const TCHAR* Comment) override; + bool ProcessElement(const TCHAR* ElementName, const TCHAR* ElementData, int32 XmlFileLineNumber) override; + bool ProcessXmlDeclaration(const TCHAR* ElementData, int32 XmlFileLineNumber) override; + + bool IsProjectInfoParsed() const {return bProjectInfoParsed;} + +private: + FString GetProjectPath() const; + +private: + TArray SupportedLanguages; + TArray SupportedPlatforms; + FString DefaultLanguage; + FString CacheDirectory; + + bool bInsidePlatformElement = false; + bool bInsideLanguageElement = false; + bool bInsidePropertyElement = false; + bool bInsideDefaultLanguage = false; + + bool bProjectInfoParsed = false ; + + FWwiseLanguageInfo CurrentLanguageInfo; + FWwisePlatformInfo CurrentPlatformInfo; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/BlueprintNodes/PostEventAsync.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/BlueprintNodes/PostEventAsync.h new file mode 100644 index 0000000..b6e7df7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/BlueprintNodes/PostEventAsync.h @@ -0,0 +1,61 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkAudioDevice.h" +#include "AkGameplayTypes.h" +#include "Kismet/BlueprintAsyncActionBase.h" +#include "PostEventAsync.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FPostEventAsyncOutputPin, int32, PlayingID); + +UCLASS() +class AKAUDIO_API UPostEventAsync : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FPostEventAsyncOutputPin Completed; + + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic|Actor", meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", AdvancedDisplay = "3", AutoCreateRefTerm = "PostEventCallback,ExternalSources")) + static UPostEventAsync* PostEventAsync(const UObject* WorldContextObject, + class UAkAudioEvent* AkEvent, + class AActor* Actor, + UPARAM(meta = (Bitmask, BitmaskEnum = "/Script/AkAudio.EAkCallbackType")) int32 CallbackMask, + const FOnAkPostEventCallback& PostEventCallback, + bool bStopWhenAttachedToDestroyed = false + ); + +public: + void Activate() override; + +private: + UFUNCTION() + void PollPostEventFuture(); + +private: + const UObject* WorldContextObject = nullptr; + UAkAudioEvent* AkEvent = nullptr; + AActor* Actor = nullptr; + int32 CallbackMask = 0; + FOnAkPostEventCallback PostEventCallback; + bool bStopWhenAttachedToDestroyed = false; + TFuture PlayingIDFuture; + FTimerHandle Timer; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/BlueprintNodes/PostEventAtLocationAsync.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/BlueprintNodes/PostEventAtLocationAsync.h new file mode 100644 index 0000000..f13a48e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/BlueprintNodes/PostEventAtLocationAsync.h @@ -0,0 +1,52 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkAudioDevice.h" +#include "Kismet/BlueprintAsyncActionBase.h" +#include "PostEventAtLocationAsync.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FPostEventAtLocationAsyncOutputPin, int32, PlayingID); + +UCLASS() +class AKAUDIO_API UPostEventAtLocationAsync : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintAssignable) + FPostEventAtLocationAsyncOutputPin Completed; + + UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Audiokinetic", meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject")) + static UPostEventAtLocationAsync* PostEventAtLocationAsync(const UObject* WorldContextObject, class UAkAudioEvent* AkEvent, FVector Location, FRotator Orientation); + +public: + void Activate() override; + +private: + UFUNCTION() + void PollPostEventFuture(); + +private: + const UObject* WorldContextObject = nullptr; + class UAkAudioEvent* AkEvent = nullptr; + FVector Location; + FRotator Orientation; + TFuture playingIDFuture; + FTimerHandle Timer; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ACosEstimate.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ACosEstimate.h new file mode 100644 index 0000000..59595af --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ACosEstimate.h @@ -0,0 +1,137 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// Approximations to acos(x) of the form f(x) = sqrt(1-x)*p(x) +// where the polynomial p(x) of degree D minimizes the quantity +// maximum{|acos(x)/sqrt(1-x) - p(x)| : x in [0,1]} over all +// polynomials of degree D. + +namespace WwiseGTE +{ + template + class ACosEstimate + { + public: + // The input constraint is x in [0,1]. For example, + // float x; // in [0,1] + // float result = ACosEstimate::Degree<3>(x); + template + inline static Real Degree(Real x) + { + return Evaluate(degree(), x); + } + + private: + // Metaprogramming and private implementation to allow specialization + // of a template member function. + template struct degree {}; + + inline static Real Evaluate(degree<1>, Real x) + { + Real poly; + poly = (Real)GTE_C_ACOS_DEG1_C1; + poly = (Real)GTE_C_ACOS_DEG1_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; + } + + inline static Real Evaluate(degree<2>, Real x) + { + Real poly; + poly = (Real)GTE_C_ACOS_DEG2_C2; + poly = (Real)GTE_C_ACOS_DEG2_C1 + poly * x; + poly = (Real)GTE_C_ACOS_DEG2_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; + } + + inline static Real Evaluate(degree<3>, Real x) + { + Real poly; + poly = (Real)GTE_C_ACOS_DEG3_C3; + poly = (Real)GTE_C_ACOS_DEG3_C2 + poly * x; + poly = (Real)GTE_C_ACOS_DEG3_C1 + poly * x; + poly = (Real)GTE_C_ACOS_DEG3_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; + } + + inline static Real Evaluate(degree<4>, Real x) + { + Real poly; + poly = (Real)GTE_C_ACOS_DEG4_C4; + poly = (Real)GTE_C_ACOS_DEG4_C3 + poly * x; + poly = (Real)GTE_C_ACOS_DEG4_C2 + poly * x; + poly = (Real)GTE_C_ACOS_DEG4_C1 + poly * x; + poly = (Real)GTE_C_ACOS_DEG4_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; + } + + inline static Real Evaluate(degree<5>, Real x) + { + Real poly; + poly = (Real)GTE_C_ACOS_DEG5_C5; + poly = (Real)GTE_C_ACOS_DEG5_C4 + poly * x; + poly = (Real)GTE_C_ACOS_DEG5_C3 + poly * x; + poly = (Real)GTE_C_ACOS_DEG5_C2 + poly * x; + poly = (Real)GTE_C_ACOS_DEG5_C1 + poly * x; + poly = (Real)GTE_C_ACOS_DEG5_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; + } + + inline static Real Evaluate(degree<6>, Real x) + { + Real poly; + poly = (Real)GTE_C_ACOS_DEG6_C6; + poly = (Real)GTE_C_ACOS_DEG6_C5 + poly * x; + poly = (Real)GTE_C_ACOS_DEG6_C4 + poly * x; + poly = (Real)GTE_C_ACOS_DEG6_C3 + poly * x; + poly = (Real)GTE_C_ACOS_DEG6_C2 + poly * x; + poly = (Real)GTE_C_ACOS_DEG6_C1 + poly * x; + poly = (Real)GTE_C_ACOS_DEG6_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; + } + + inline static Real Evaluate(degree<7>, Real x) + { + Real poly; + poly = (Real)GTE_C_ACOS_DEG7_C7; + poly = (Real)GTE_C_ACOS_DEG7_C6 + poly * x; + poly = (Real)GTE_C_ACOS_DEG7_C5 + poly * x; + poly = (Real)GTE_C_ACOS_DEG7_C4 + poly * x; + poly = (Real)GTE_C_ACOS_DEG7_C3 + poly * x; + poly = (Real)GTE_C_ACOS_DEG7_C2 + poly * x; + poly = (Real)GTE_C_ACOS_DEG7_C1 + poly * x; + poly = (Real)GTE_C_ACOS_DEG7_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; + } + + inline static Real Evaluate(degree<8>, Real x) + { + Real poly; + poly = (Real)GTE_C_ACOS_DEG8_C8; + poly = (Real)GTE_C_ACOS_DEG8_C7 + poly * x; + poly = (Real)GTE_C_ACOS_DEG8_C6 + poly * x; + poly = (Real)GTE_C_ACOS_DEG8_C5 + poly * x; + poly = (Real)GTE_C_ACOS_DEG8_C4 + poly * x; + poly = (Real)GTE_C_ACOS_DEG8_C3 + poly * x; + poly = (Real)GTE_C_ACOS_DEG8_C2 + poly * x; + poly = (Real)GTE_C_ACOS_DEG8_C1 + poly * x; + poly = (Real)GTE_C_ACOS_DEG8_C0 + poly * x; + poly = poly * std::sqrt((Real)1 - x); + return poly; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/APConversion.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/APConversion.h new file mode 100644 index 0000000..f6f2adb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/APConversion.h @@ -0,0 +1,503 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.09.26 + +#pragma once + +#include +#include +#include + +// The conversion functions here are used to obtain arbitrary-precision +// approximations to rational numbers and to quadratic field numbers. +// The arbitrary-precision arithmetic is described in +// https://www.geometrictools.com/Documentation/ArbitraryPrecision.pdf +// The quadratic field numbers and conversions are described in +// https://www.geometrictools.com/Documentation/QuadraticFields.pdf + +namespace WwiseGTE +{ + template + class APConversion + { + public: + using QFN1 = QFNumber; + using QFN2 = QFNumber; + + // Construction and destruction. + APConversion(int32_t precision, uint32_t maxIterations) + : + mZero(0), + mOne(1), + mThree(3), + mFive(5), + mPrecision(precision), + mMaxIterations(maxIterations), + mThreshold(std::ldexp(mOne, -mPrecision)) + { + LogAssert(precision > 0, "Invalid precision."); + LogAssert(maxIterations > 0, "Invalid maximum iterations."); + } + + ~APConversion() + { + } + + // Member access. + void SetPrecision(int32_t precision) + { + LogAssert(precision > 0, "Invalid precision."); + mPrecision = precision; + mThreshold = std::ldexp(mOne, -mPrecision); + } + + void SetMaxIterations(uint32_t maxIterations) + { + LogAssert(maxIterations > 0, "Invalid maximum iterations."); + mMaxIterations = maxIterations; + } + + inline int32_t GetPrecision() const + { + return mPrecision; + } + + inline uint32_t GetMaxIterations() const + { + return mMaxIterations; + } + + // Disallow copying and moving. + APConversion(APConversion const&) = delete; + APConversion(APConversion&&) = delete; + APConversion& operator=(APConversion const&) = delete; + APConversion& operator=(APConversion&&) = delete; + + // The input a^2 is rational, but a itself is usually irrational, + // although a rational value is allowed. Compute a bounding interval + // for the root, aMin <= a <= aMax, where the endpoints are both + // within the specified precision. + uint32_t EstimateSqrt(Rational const& aSqr, Rational& aMin, Rational& aMax) + { + // Factor a^2 = r^2 * 2^e, where r^2 in [1/2,1). Compute s^2 and + // the exponent used to generate the estimate of sqrt(a^2). + Rational sSqr; + int exponentA; + PreprocessSqr(aSqr, sSqr, exponentA); + + // Use the FPU to estimate s = sqrt(sSqr) to 53-bit precision with + // rounding up. Multiply by the appropriate exponent to obtain + // upper bound aMax > a. + aMax = GetMaxOfSqrt(sSqr, exponentA); + + // Compute a lower bound aMin < a. + aMin = aSqr / aMax; + + // Compute Newton iterates until convergence. The estimate closest + // to a is aMin with aMin <= a <= aMax and a - aMin <= aMax - a. + uint32_t iterate; + for (iterate = 1; iterate <= mMaxIterations; ++iterate) + { + if (aMax - aMin < mThreshold) + { + break; + } + // Compute the average aMax = (aMin + aMax) / 2. Round up + // to twice the precision to avoid quadratic growth in the + // number of bits and to ensure that aMin can increase. + aMax = std::ldexp(aMin + aMax, -1); + Convert(aMax, 2 * mPrecision, FE_UPWARD, aMax); + aMin = aSqr / aMax; + } + return iterate; + } + + // Compute an estimate of the root when you do not need a bounding + // interval. + uint32_t EstimateSqrt(Rational const& aSqr, Rational& a) + { + // Compute a bounding interval aMin <= a <= aMax. + Rational aMin, aMax; + uint32_t numIterates = EstimateSqrt(aSqr, aMin, aMax); + // Use the average of the interval endpoints as the estimate. + a = std::ldexp(aMin + aMax, -1); + return numIterates; + } + + uint32_t EstimateApB(Rational const& aSqr, Rational const& bSqr, + Rational& tMin, Rational& tMax) + { + // Factor a^2 = r^2 * 2^e, where r^2 in [1/2,1). Compute u^2 and + // the exponent used to generate the estimate of sqrt(a^2). + Rational uSqr; + int32_t exponentA; + PreprocessSqr(aSqr, uSqr, exponentA); + + // Factor b^2 = s^2 * 2^e, where s^2 in [1/2,1). Compute v^2 and + // the exponent used to generate the estimate of sqrt(b^2). + Rational vSqr; + int32_t exponentB; + PreprocessSqr(bSqr, vSqr, exponentB); + + // Use the FPU to estimate u = sqrt(u^2) and v = sqrt(v^2) to + // 53 bits of precision with rounding up. Multiply by the + // appropriate exponents to obtain upper bounds aMax > a and + // bMax > b. This ensures tMax = aMax + bMax > a + b. + Rational aMax = GetMaxOfSqrt(uSqr, exponentA); + Rational bMax = GetMaxOfSqrt(vSqr, exponentB); + tMax = aMax + bMax; + + // Compute a lower bound tMin < a + b. + Rational a2pb2 = aSqr + bSqr; + Rational a2mb2 = aSqr - bSqr; + Rational a2mb2Sqr = a2mb2 * a2mb2; + Rational tMaxSqr = tMax * tMax; + tMin = (a2pb2 * tMaxSqr - a2mb2Sqr) / (tMax * (tMaxSqr - a2pb2)); + + // Compute Newton iterates until convergence. The estimate closest + // to a + b is tMin with tMin < a + b < tMax and + // (a + b) - tMin < tMax - (a + b). + uint32_t iterate; + for (iterate = 1; iterate <= mMaxIterations; ++iterate) + { + if (tMax - tMin < mThreshold) + { + break; + } + // Compute the weighted average tMax = (3*tMin + tMax) / 4. + // Round up to twice the precision to avoid quadratic growth + // in the number of bits and to ensure that tMin can increase. + tMax = std::ldexp(mThree * tMax + tMin, -2); + Convert(tMax, 2 * mPrecision, FE_UPWARD, tMax); + tMaxSqr = tMax * tMax; + tMin = (a2pb2 * tMaxSqr - a2mb2Sqr) / (tMax * (tMaxSqr - a2pb2)); + } + return iterate; + } + + uint32_t EstimateAmB(Rational const& aSqr, Rational const& bSqr, + Rational& tMin, Rational& tMax) + { + // The return value of the function. + uint32_t iterate = 0; + + // Compute various quantities that are used later in the code. + Rational a2tb2 = aSqr * bSqr; // a^2 * b^2 + Rational a2pb2 = aSqr + bSqr; // a^2 + b^2 + Rational a2mb2 = aSqr - bSqr; // a^2 - b^2 + Rational a2mb2Sqr = a2mb2 * a2mb2; // (a^2 - b^2)^2 + Rational twoa2pb2 = std::ldexp(a2pb2, 1); // 2 * (a^2 + b^2) + + // Factor a^2 = r^2 * 2^e, where r^2 in [1/2,1). Compute u^2 and + // the exponent used to generate the estimate of sqrt(a^2). + Rational uSqr; + int32_t exponentA; + PreprocessSqr(aSqr, uSqr, exponentA); + + // Factor b^2 = s^2 * 2^e, where s^2 in [1/2,1). Compute v^2 and + // the exponent used to generate the estimate of sqrt(b^2). + Rational vSqr; + int32_t exponentB; + PreprocessSqr(bSqr, vSqr, exponentB); + + // Compute the sign of f''(a-b)/8 = a^2 - 3*a*b + b^2. It can be + // shown that Sign(a^2-3*a*b+b^2) = Sign(a^4-7*a^2*b^2+b^4) = + // Sign((a^2-b^2)^2-5*a^2*b^2). + Rational signSecDer = a2mb2Sqr - mFive * a2tb2; + + // Local variables shared by the two main blocks of code. + Rational aMin, aMax, bMin, bMax, tMinSqr, tMaxSqr, tMid, tMidSqr, f; + + if (signSecDer > mZero) + { + // Choose an initial guess tMin < a-b. Use the FPU to + // estimate u = sqrt(u^2) and v = sqrt(v^2) to 53 bits of + // precision with specified rounding. Multiply by the + // appropriate exponents to obtain tMin = aMin - bMax < a-b. + aMin = GetMinOfSqrt(uSqr, exponentA); + bMax = GetMaxOfSqrt(vSqr, exponentB); + tMin = aMin - bMax; + + // When a-b is nearly zero, it is possible the lower bound is + // negative. Clamp tMin to zero to stay on the nonnegative + // t-axis where the f"-positive basin is. + if (tMin < mZero) + { + tMin = mZero; + } + + // Test whether tMin is in the positive f"(t) basin containing + // a-b. If it is not, compute a tMin that is in the basis. The + // sign test is applied to f"(t)/4 = 3*t^2 - (a^2+b^2). + tMinSqr = tMin * tMin; + signSecDer = mThree * tMinSqr - a2pb2; + if (signSecDer < mZero) + { + // The initial guess satisfies f"(tMin) < 0. Compute an + // upper bound tMax > a-b and bisect [tMin,tMax] until + // either the t-value is an estimate to a-b within the + // specified precision or until f"(t) >= 0 and f(t) >= 0. + // In the latter case, continue on to Newton's method, + // which is then guaranteed to converge. + aMax = GetMaxOfSqrt(uSqr, exponentA); + bMin = GetMinOfSqrt(vSqr, exponentB); + tMax = aMax - bMin; + + for (iterate = 1; iterate <= mMaxIterations; ++iterate) + { + if (tMax - tMin < mThreshold) + { + return iterate; + } + + tMid = std::ldexp(tMin + tMax, -1); + tMidSqr = tMid * tMid; + signSecDer = mThree * tMidSqr - a2pb2; + if (signSecDer >= mZero) + { + f = tMidSqr * (tMidSqr - twoa2pb2) + a2mb2Sqr; + if (f >= mZero) + { + tMin = tMid; + tMinSqr = tMidSqr; + break; + } + else + { + // Round up to twice the precision to avoid + // quadratic growth in the number of bits. + tMax = tMid; + Convert(tMax, 2 * mPrecision, FE_UPWARD, tMax); + } + } + else + { + // Round down to twice the precision to avoid + // quadratic growth in the number of bits. + tMin = tMid; + Convert(tMin, 2 * mPrecision, FE_DOWNWARD, tMin); + } + } + } + + // Compute an upper bound tMax > a-b. + tMax = (a2pb2 * tMinSqr - a2mb2Sqr) / (tMin * (tMinSqr - a2pb2)); + + // Compute Newton iterates until convergence. The estimate + // closest to a-b is tMax with tMin < a-b < tMax and + // tMax - (a-b) < (a-b) - tMin. + for (iterate = 1; iterate <= mMaxIterations; ++iterate) + { + if (tMax - tMin < mThreshold) + { + break; + } + // Compute the weighted average tMin = (3*tMin+tMax)/4. + // Round down to twice the precision to avoid quadratic + // growth in the number of bits and to ensure that tMax + // can decrease. + tMin = std::ldexp(mThree * tMin + tMax, -2); + Convert(tMin, 2 * mPrecision, FE_DOWNWARD, tMin); + tMinSqr = tMin * tMin; + tMax = (a2pb2 * tMinSqr - a2mb2Sqr) / (tMin * (tMinSqr - a2pb2)); + } + return iterate; + } + + if (signSecDer < mZero) + { + // Choose an initial guess tMax > a-b. Use the FPU to + // estimate u = sqrt(u^2) and v = sqrt(v^2) to 53 bits of + // precision with specified rounding. Multiply by the + // appropriate exponents to obtain tMax = aMax - bMin > a-b. + aMax = GetMaxOfSqrt(uSqr, exponentA); + bMin = GetMinOfSqrt(vSqr, exponentB); + tMax = aMax - bMin; + + // Test whether tMax is in the negative f"(t) basin containing + // a-b. If it is not, compute a tMax that is in the basis. The + // sign test is applied to f"(t)/4 = 3*t^2 - (a^2+b^2). + tMaxSqr = tMax * tMax; + signSecDer = mThree * tMaxSqr - a2pb2; + if (signSecDer > mZero) + { + // The initial guess satisfies f"(tMax) > 0. Compute a + // lower bound tMin < a-b and bisect [tMin,tMax] until + // either the t-value is an estimate to a-b within the + // specified precision or until f"(t) <= 0 and f(t) <= 0. + // In the latter case, continue on to Newton's method, + // which is then guaranteed to converge. + aMin = GetMinOfSqrt(uSqr, exponentA); + bMax = GetMaxOfSqrt(vSqr, exponentB); + tMin = aMin - bMax; + + for (iterate = 1; iterate <= mMaxIterations; ++iterate) + { + if (tMax - tMin < mThreshold) + { + return iterate; + } + + tMid = std::ldexp(tMin + tMax, -1); + tMidSqr = tMid * tMid; + signSecDer = mThree * tMidSqr - a2pb2; + if (signSecDer <= mZero) + { + f = tMidSqr * (tMidSqr - twoa2pb2) + a2mb2Sqr; + if (f <= mZero) + { + tMax = tMid; + tMaxSqr = tMidSqr; + break; + } + else + { + // Round down to twice the precision to avoid + // quadratic growth in the number of bits. + tMin = tMid; + Convert(tMin, 2 * mPrecision, FE_DOWNWARD, tMin); + } + } + else + { + // Round up to twice the precision to avoid + // quadratic growth in the number of bits. + tMax = tMid; + Convert(tMax, 2 * mPrecision, FE_UPWARD, tMax); + } + } + } + + // Compute a lower bound tMin < a-b. + tMin = (a2pb2 * tMaxSqr - a2mb2Sqr) / (tMax * (tMaxSqr - a2pb2)); + + // Compute Newton iterates until convergence. The estimate + // closest to a-b is tMin with tMin < a - b < tMax and + // (a-b) - tMin < tMax - (a-b). + for (iterate = 1; iterate <= mMaxIterations; ++iterate) + { + if (tMax - tMin < mThreshold) + { + break; + } + // Compute the weighted average tMax = (3*tMax+tMin)/4. + // Round up to twice the precision to avoid quadratic + // growth in the number of bits and to ensure that tMin + // can increase. + tMax = std::ldexp(mThree * tMax + tMin, -2); + Convert(tMax, 2 * mPrecision, FE_UPWARD, tMax); + tMaxSqr = tMax * tMax; + tMin = (a2pb2 * tMaxSqr - a2mb2Sqr) / (tMax * (tMaxSqr - a2pb2)); + } + return iterate; + } + + // The sign of the second derivative is Sign(a^4-7*a^2*b^2+b^4) + // and cannot be zero. Define rational r = a^2/b^2 so that + // a^4-7*a^2*b^2+b^4 = 0. This implies r^2 - 7*r^2 + 1 = 0. The + // irrational roots are r = (7 +- sqrt(45))/2, which is a + // contradiction. + LogError("This second derivative cannot be zero at a-b."); + } + + // Compute a bounding interval for the root, qMin <= q <= qMax, where + // the endpoints are both within the specified precision. + uint32_t Estimate(QFN1 const& q, Rational& qMin, Rational& qMax) + { + Rational const& x = q.x[0]; + Rational const& y = q.x[1]; + Rational const& d = q.d; + + uint32_t numIterates; + if (d != mZero && y != mZero) + { + Rational aSqr = y * y * d; + numIterates = EstimateSqrt(aSqr, qMin, qMax); + if (y > mZero) + { + qMin = x + qMin; + qMax = x + qMax; + } + else + { + Rational diff = x - qMax; + qMax = x - qMin; + qMin = diff; + } + } + else + { + numIterates = 0; + qMin = x; + qMax = x; + } + + return numIterates; + } + + // Compute an estimate of the root when you do not need a bounding + // interval. + uint32_t Estimate(QFN1 const& q, Rational& qEstimate) + { + // Compute a bounding interval qMin <= q <= qMax. + Rational qMin, qMax; + uint32_t numIterates = Estimate(q, qMin, qMax); + // Use the average of the interval endpoints as the estimate. + qEstimate = std::ldexp(qMin + qMax, -1); + return numIterates; + } + + private: + void PreprocessSqr(Rational const& aSqr, Rational& rSqr, int& exponentA) + { + // Factor a^2 = r^2 * 2^e, where r^2 in [1/2,1). + int32_t exponentASqr; + rSqr = std::frexp(aSqr, &exponentASqr); + if (exponentASqr & 1) // odd exponent + { + // a = sqrt(2*r^2) * 2^{(e-1)/2} + exponentA = (exponentASqr - 1) / 2; + rSqr = std::ldexp(rSqr, 1); // = 2*rSqr + // rSqr in [1,2) + } + else // even exponent + { + // a = sqrt(r^2) * 2^{e/2} + exponentA = exponentASqr / 2; + // rSqr in [1/2,1) + } + } + + Rational GetMinOfSqrt(Rational const& rSqr, int exponent) + { + double lowerRSqr; + Convert(rSqr, FE_DOWNWARD, lowerRSqr); + int saveRoundingMode = std::fegetround(); + std::fesetround(FE_DOWNWARD); + Rational aMin = std::sqrt(lowerRSqr); + std::fesetround(saveRoundingMode); + aMin = std::ldexp(aMin, exponent); + return aMin; + } + + Rational GetMaxOfSqrt(Rational const& rSqr, int exponent) + { + double upperRSqr; + Convert(rSqr, FE_UPWARD, upperRSqr); + int saveRoundingMode = std::fegetround(); + std::fesetround(FE_UPWARD); + Rational aMax = std::sqrt(upperRSqr); + std::fesetround(saveRoundingMode); + aMax = std::ldexp(aMax, exponent); + return aMax; + } + + Rational const mZero, mOne, mThree, mFive; + int32_t mPrecision; + uint32_t mMaxIterations; + Rational mThreshold; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/APInterval.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/APInterval.h new file mode 100644 index 0000000..dfd95aa --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/APInterval.h @@ -0,0 +1,434 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.1.2019.10.17 + +#pragma once + +#include +#include + +// The interval [e0,e1] must satisfy e0 <= e1. Expose this define to trap +// invalid construction where e0 > e1. +#define GTE_THROW_ON_INVALID_APINTERVAL + +namespace WwiseGTE +{ + // The APType must be an arbitrary-precision type. + template + class APInterval + { + public: + // Construction. This is the only way to create an interval. All such + // intervals are immutable once created. The constructor + // APInterval(APType) is used to create the degenerate interval [e,e]. + APInterval() + : + mEndpoints{ static_cast(0), static_cast(0) } + { + static_assert(WwiseGTE::is_arbitrary_precision::value, "Invalid type."); + } + + APInterval(APInterval const& other) + : + mEndpoints(other.mEndpoints) + { + static_assert(WwiseGTE::is_arbitrary_precision::value, "Invalid type."); + } + + explicit APInterval(APType e) + : + mEndpoints{ e, e } + { + static_assert(WwiseGTE::is_arbitrary_precision::value, "Invalid type."); + } + + APInterval(APType e0, APType e1) + : + mEndpoints{ e0, e1 } + { + static_assert(WwiseGTE::is_arbitrary_precision::value, "Invalid type."); +#if defined(GTE_THROW_ON_INVALID_APINTERVAL) + LogAssert(mEndpoints[0] <= mEndpoints[1], "Invalid interval."); +#endif + } + + APInterval(std::array const& endpoint) + : + mEndpoints(endpoint) + { + static_assert(WwiseGTE::is_arbitrary_precision::value, "Invalid type."); +#if defined(GTE_THROW_ON_INVALID_APINTERVAL) + LogAssert(mEndpoints[0] <= mEndpoints[1], "Invalid interval."); +#endif + } + + APInterval& operator=(APInterval const& other) + { + static_assert(WwiseGTE::is_arbitrary_precision::value, "Invalid type."); + mEndpoints = other.mEndpoints; + return *this; + } + + // Member access. It is only possible to read the endpoints. You + // cannot modify the endpoints outside the arithmetic operations. + inline APType operator[](size_t i) const + { + return mEndpoints[i]; + } + + inline std::array GetEndpoints() const + { + return mEndpoints; + } + + // Arithmetic operations to compute intervals at the leaf nodes of + // an expression tree. Such nodes correspond to the raw floating-point + // variables of the expression. The non-class operators defined after + // the class definition are used to compute intervals at the interior + // nodes of the expression tree. + inline static APInterval Add(APType u, APType v) + { + APInterval w; + w.mEndpoints[0] = u + v; + w.mEndpoints[1] = w.mEndpoints[0]; + return w; + } + + inline static APInterval Sub(APType u, APType v) + { + APInterval w; + w.mEndpoints[0] = u - v; + w.mEndpoints[1] = w.mEndpoints[0]; + return w; + } + + inline static APInterval Mul(APType u, APType v) + { + APInterval w; + w.mEndpoints[0] = u * v; + w.mEndpoints[1] = w.mEndpoints[0]; + return w; + } + + template + inline static + typename std::enable_if::value, APInterval>::type + Div(APType u, APType v) + { + APType const zero = static_cast(0); + if (v != zero) + { + APInterval w; + w.mEndpoints[0] = u / v; + w.mEndpoints[1] = w.mEndpoints[0]; + return w; + } + else + { + // Division by zero does not lead to a determinate interval. + // Just return the entire set of real numbers. + return Reals(); + } + } + + private: + std::array mEndpoints; + + public: + // FOR INTERNAL USE ONLY. These are used by the non-class operators + // defined after the class definition. + inline static APInterval Add(APType u0, APType u1, APType v0, APType v1) + { + APInterval w; + w.mEndpoints[0] = u0 + v0; + w.mEndpoints[1] = u1 + v1; + return w; + } + + inline static APInterval Sub(APType u0, APType u1, APType v0, APType v1) + { + APInterval w; + w.mEndpoints[0] = u0 - v1; + w.mEndpoints[1] = u1 - v0; + return w; + } + + inline static APInterval Mul(APType u0, APType u1, APType v0, APType v1) + { + APInterval w; + w.mEndpoints[0] = u0 * v0; + w.mEndpoints[1] = u1 * v1; + return w; + } + + inline static APInterval Mul2(APType u0, APType u1, APType v0, APType v1) + { + APType u0mv1 = u0 * v1; + APType u1mv0 = u1 * v0; + APType u0mv0 = u0 * v0; + APType u1mv1 = u1 * v1; + return APInterval(std::min(u0mv1, u1mv0), std::max(u0mv0, u1mv1)); + } + + template + inline static + typename std::enable_if::value, APInterval>::type + Div(APType u0, APType u1, APType v0, APType v1) + { + APInterval w; + w.mEndpoints[0] = u0 / v1; + w.mEndpoints[1] = u1 / v0; + return w; + } + + template + inline static + typename std::enable_if::value, APInterval>::type + Reciprocal(APType v0, APType v1) + { + APType const one = static_cast(1); + APInterval w; + w.mEndpoints[0] = one / v1; + w.mEndpoints[1] = one / v0; + return w; + } + + template + inline static + typename std::enable_if::value, APInterval>::type + ReciprocalDown(APType v) + { + APType recpv = static_cast(1) / v; + APType posinf(0); + posinf.SetSign(+2); + return APInterval(recpv, posinf); + } + + template + inline static + typename std::enable_if::value, APInterval>::type + ReciprocalUp(APType v) + { + APType recpv = static_cast(1) / v; + APType neginf(0); + neginf.SetSign(-2); + return APInterval(neginf, recpv); + } + + inline static APInterval Reals() + { + APType posinf(0), neginf(0); + posinf.SetSign(+2); + neginf.SetSign(-2); + return APInterval(neginf, posinf); + } + }; + + // Unary operations. Negation of [e0,e1] produces [-e1,-e0]. This + // operation needs to be supported in the sense of negating a + // "number" in an arithmetic expression. + template + APInterval operator+(APInterval const& u) + { + return u; + } + + template + APInterval operator-(APInterval const& u) + { + return APInterval(-u[1], -u[0]); + } + + // Addition operations. + template + APInterval operator+(APType u, APInterval const& v) + { + return APInterval::Add(u, u, v[0], v[1]); + } + + template + APInterval operator+(APInterval const& u, APType v) + { + return APInterval::Add(u[0], u[1], v, v); + } + + template + APInterval operator+(APInterval const& u, APInterval const& v) + { + return APInterval::Add(u[0], u[1], v[0], v[1]); + } + + // Subtraction operations. + template + APInterval operator-(APType u, APInterval const& v) + { + return APInterval::Sub(u, u, v[0], v[1]); + } + + template + APInterval operator-(APInterval const& u, APType v) + { + return APInterval::Sub(u[0], u[1], v, v); + } + + template + APInterval operator-(APInterval const& u, APInterval const& v) + { + return APInterval::Sub(u[0], u[1], v[0], v[1]); + } + + // Multiplication operations. + template + APInterval operator*(APType u, APInterval const& v) + { + APType const zero = static_cast(0); + if (u >= zero) + { + return APInterval::Mul(u, u, v[0], v[1]); + } + else + { + return APInterval::Mul(u, u, v[1], v[0]); + } + } + + template + APInterval operator*(APInterval const& u, APType v) + { + APType const zero = static_cast(0); + if (v >= zero) + { + return APInterval::Mul(u[0], u[1], v, v); + } + else + { + return APInterval::Mul(u[1], u[0], v, v); + } + } + + template + APInterval operator*(APInterval const& u, APInterval const& v) + { + APType const zero = static_cast(0); + if (u[0] >= zero) + { + if (v[0] >= zero) + { + return APInterval::Mul(u[0], u[1], v[0], v[1]); + } + else if (v[1] <= zero) + { + return APInterval::Mul(u[1], u[0], v[0], v[1]); + } + else // v[0] < 0 < v[1] + { + return APInterval::Mul(u[1], u[1], v[0], v[1]); + } + } + else if (u[1] <= zero) + { + if (v[0] >= zero) + { + return APInterval::Mul(u[0], u[1], v[1], v[0]); + } + else if (v[1] <= zero) + { + return APInterval::Mul(u[1], u[0], v[1], v[0]); + } + else // v[0] < 0 < v[1] + { + return APInterval::Mul(u[0], u[0], v[1], v[0]); + } + } + else // u[0] < 0 < u[1] + { + if (v[0] >= zero) + { + return APInterval::Mul(u[0], u[1], v[1], v[1]); + } + else if (v[1] <= zero) + { + return APInterval::Mul(u[1], u[0], v[0], v[0]); + } + else // v[0] < 0 < v[1] + { + return APInterval::Mul2(u[0], u[1], v[0], v[1]); + } + } + } + + // Division operations. If the divisor interval is [v0,v1] with + // v0 < 0 < v1, then the returned interval is (-infinity,+infinity) + // instead of Union((-infinity,1/v0),(1/v1,+infinity)). An application + // should try to avoid this case by branching based on [v0,0] and [0,v1]. + template + APInterval operator/(APType u, APInterval const& v) + { + APType const zero = static_cast(0); + if (v[0] > zero || v[1] < zero) + { + return u * APInterval::Reciprocal(v[0], v[1]); + } + else + { + if (v[0] == zero) + { + return u * APInterval::ReciprocalDown(v[1]); + } + else if (v[1] == zero) + { + return u * APInterval::ReciprocalUp(v[0]); + } + else // v[0] < 0 < v[1] + { + return APInterval::Reals(); + } + } + } + + template + APInterval operator/(APInterval const& u, APType v) + { + APType const zero = static_cast(0); + if (v > zero) + { + return APInterval::Div(u[0], u[1], v, v); + } + else if (v < zero) + { + return APInterval::Div(u[1], u[0], v, v); + } + else // v = 0 + { + return APInterval::Reals(); + } + } + + template + APInterval operator/(APInterval const& u, APInterval const& v) + { + APType const zero = static_cast(0); + if (v[0] > zero || v[1] < zero) + { + return u * APInterval::Reciprocal(v[0], v[1]); + } + else + { + if (v[0] == zero) + { + return u * APInterval::ReciprocalDown(v[1]); + } + else if (v[1] == zero) + { + return u * APInterval::ReciprocalUp(v[0]); + } + else // v[0] < 0 < v[1] + { + return APInterval::Reals(); + } + } + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ASinEstimate.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ASinEstimate.h new file mode 100644 index 0000000..5cdb0b7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ASinEstimate.h @@ -0,0 +1,32 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// Approximations to asin(x) of the form f(x) = pi/2 - sqrt(1-x)*p(x) +// where the polynomial p(x) of degree D minimizes the quantity +// maximum{|acos(x)/sqrt(1-x) - p(x)| : x in [0,1]} over all +// polynomials of degree D. We use the identity asin(x) = pi/2 - acos(x). + +namespace WwiseGTE +{ + template + class ASinEstimate + { + public: + // The input constraint is x in [0,1]. For example, + // float x; // in [0,1] + // float result = ASinEstimate::Degree<3>(x); + template + inline static Real Degree(Real x) + { + return (Real)GTE_C_HALF_PI - ACosEstimate::Degree(x); + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ATanEstimate.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ATanEstimate.h new file mode 100644 index 0000000..f28a403 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ATanEstimate.h @@ -0,0 +1,135 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// Minimax polynomial approximations to atan(x). The polynomial p(x) of +// degree D has only odd-power terms, is required to have linear term x, +// and p(1) = atan(1) = pi/4. It minimizes the quantity +// maximum{|atan(x) - p(x)| : x in [-1,1]} over all polynomials of +// degree D subject to the constraints mentioned. + +namespace WwiseGTE +{ + template + class ATanEstimate + { + public: + // The input constraint is x in [-1,1]. For example, + // float x; // in [-1,1] + // float result = ATanEstimate::Degree<3>(x); + template + inline static Real Degree(Real x) + { + return Evaluate(degree(), x); + } + + // The input x can be any real number. Range reduction is used via + // the identities atan(x) = pi/2 - atan(1/x) for x > 0, and + // atan(x) = -pi/2 - atan(1/x) for x < 0. For example, + // float x; // x any real number + // float result = ATanEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x) + { + if (std::fabs(x) <= (Real)1) + { + return Degree(x); + } + else if (x > (Real)1) + { + return (Real)GTE_C_HALF_PI - Degree((Real)1 / x); + } + else + { + return (Real)-GTE_C_HALF_PI - Degree((Real)1 / x); + } + } + + private: + // Metaprogramming and private implementation to allow specialization + // of a template member function. + template struct degree {}; + + inline static Real Evaluate(degree<3>, Real x) + { + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_ATAN_DEG3_C1; + poly = (Real)GTE_C_ATAN_DEG3_C0 + poly * xsqr; + poly = poly * x; + return poly; + } + + inline static Real Evaluate(degree<5>, Real x) + { + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_ATAN_DEG5_C2; + poly = (Real)GTE_C_ATAN_DEG5_C1 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG5_C0 + poly * xsqr; + poly = poly * x; + return poly; + } + + inline static Real Evaluate(degree<7>, Real x) + { + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_ATAN_DEG7_C3; + poly = (Real)GTE_C_ATAN_DEG7_C2 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG7_C1 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG7_C0 + poly * xsqr; + poly = poly * x; + return poly; + } + + inline static Real Evaluate(degree<9>, Real x) + { + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_ATAN_DEG9_C4; + poly = (Real)GTE_C_ATAN_DEG9_C3 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG9_C2 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG9_C1 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG9_C0 + poly * xsqr; + poly = poly * x; + return poly; + } + + inline static Real Evaluate(degree<11>, Real x) + { + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_ATAN_DEG11_C5; + poly = (Real)GTE_C_ATAN_DEG11_C4 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG11_C3 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG11_C2 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG11_C1 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG11_C0 + poly * xsqr; + poly = poly * x; + return poly; + } + + inline static Real Evaluate(degree<13>, Real x) + { + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_ATAN_DEG13_C6; + poly = (Real)GTE_C_ATAN_DEG13_C5 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG13_C4 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG13_C3 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG13_C2 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG13_C1 + poly * xsqr; + poly = (Real)GTE_C_ATAN_DEG13_C0 + poly * xsqr; + poly = poly * x; + return poly; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/AdaptiveSkeletonClimbing2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/AdaptiveSkeletonClimbing2.h new file mode 100644 index 0000000..120f333 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/AdaptiveSkeletonClimbing2.h @@ -0,0 +1,936 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// Extract level surfaces using an adaptive approach to reduce the triangle +// count. The implementation is for the algorithm described in the paper +// Multiresolution Isosurface Extraction with Adaptive Skeleton Climbing +// Tim Poston, Tien-Tsin Wong and Pheng-Ann Heng +// Computer Graphics forum, volume 17, issue 3, September 1998 +// pages 137-147 +// https://onlinelibrary.wiley.com/doi/abs/10.1111/1467-8659.00261 + +namespace WwiseGTE +{ + // The image type T must be one of the integer types: int8_t, int16_t, + // int32_t, uint8_t, uint16_t or uint32_t. Internal integer computations + // are performed using int64_t. The type Real is for extraction to + // floating-point vertices. + template + class AdaptiveSkeletonClimbing2 + { + public: + // Construction and destruction. The input image is assumed to + // contain (2^N+1)-by-(2^N+1) elements where N >= 0. The organization + // is row-major order for (x,y). + AdaptiveSkeletonClimbing2(int N, T const* inputPixels) + : + mTwoPowerN(1 << N), + mSize(mTwoPowerN + 1), + mInputPixels(inputPixels), + mXMerge(mSize), + mYMerge(mSize) + { + static_assert(std::is_integral::value && sizeof(T) <= 4, + "Type T must be int{8,16,32}_t or uint{8,16,32}_t."); + if (N <= 0 || mInputPixels == nullptr) + { + LogError("Invalid input."); + } + + for (int i = 0; i < mSize; ++i) + { + mXMerge[i] = std::make_shared(N); + mYMerge[i] = std::make_shared(N); + } + + mXYMerge = std::make_unique(N, mXMerge, mYMerge); + } + + // TODO: Refactor this class to have base class CurveExtractor. + typedef std::array Vertex; + typedef std::array Edge; + + void Extract(Real level, int depth, + std::vector& vertices, std::vector& edges) + { + std::vector rectangles; + std::vector localVertices; + std::vector localEdges; + + SetLevel(level, depth); + GetRectangles(rectangles); + for (auto& rectangle : rectangles) + { + if (rectangle.type > 0) + { + GetComponents(level, rectangle, localVertices, localEdges); + } + } + + vertices = std::move(localVertices); + edges = std::move(localEdges); + } + + void MakeUnique(std::vector& vertices, std::vector& edges) + { + size_t numVertices = vertices.size(); + size_t numEdges = edges.size(); + if (numVertices == 0 || numEdges == 0) + { + return; + } + + // Compute the map of unique vertices and assign to them new and + // unique indices. + std::map vmap; + int nextVertex = 0; + for (size_t v = 0; v < numVertices; ++v) + { + // Keep only unique vertices. + auto result = vmap.insert(std::make_pair(vertices[v], nextVertex)); + if (result.second) + { + ++nextVertex; + } + } + + // Compute the map of unique edges and assign to them new and + // unique indices. + std::map emap; + int nextEdge = 0; + for (size_t e = 0; e < numEdges; ++e) + { + // Replace old vertex indices by new vertex indices. + Edge& edge = edges[e]; + for (int i = 0; i < 2; ++i) + { + auto iter = vmap.find(vertices[edge[i]]); + LogAssert(iter != vmap.end(), "Expecting the vertex to be in the vmap."); + edge[i] = iter->second; + } + + // Keep only unique edges. + auto result = emap.insert(std::make_pair(edge, nextEdge)); + if (result.second) + { + ++nextEdge; + } + } + + // Pack the vertices into an array. + vertices.resize(vmap.size()); + for (auto const& element : vmap) + { + vertices[element.second] = element.first; + } + + // Pack the edges into an array. + edges.resize(emap.size()); + for (auto const& element : emap) + { + edges[element.second] = element.first; + } + } + + private: + // Helper classes for the skeleton climbing. + struct QuadRectangle + { + QuadRectangle() + : + xOrigin(0), + yOrigin(0), + xStride(0), + yStride(0), + valid(false) + { + } + + QuadRectangle(int inXOrigin, int inYOrigin, int inXStride, int inYStride) + { + Initialize(inXOrigin, inYOrigin, inXStride, inYStride); + } + + void Initialize(int inXOrigin, int inYOrigin, int inXStride, int inYStride) + { + xOrigin = inXOrigin; + yOrigin = inYOrigin; + xStride = inXStride; + yStride = inYStride; + valid = true; + } + + int xOrigin, yOrigin, xStride, yStride; + bool valid; + }; + + struct QuadNode + { + QuadNode() + { + // The members are uninitialized. + } + + QuadNode(int xOrigin, int yOrigin, int xNext, int yNext, int stride) + : + r00(xOrigin, yOrigin, stride, stride), + r10(xNext, yOrigin, stride, stride), + r01(xOrigin, yNext, stride, stride), + r11(xNext, yNext, stride, stride) + { + + } + + void Initialize(int xOrigin, int yOrigin, int xNext, int yNext, int stride) + { + r00.Initialize(xOrigin, yOrigin, stride, stride); + r10.Initialize(xNext, yOrigin, stride, stride); + r01.Initialize(xOrigin, yNext, stride, stride); + r11.Initialize(xNext, yNext, stride, stride); + } + + bool IsMono() const + { + return !r10.valid && !r01.valid && !r11.valid; + } + + int GetQuantity() const + { + int quantity = 0; + + if (r00.valid) + { + ++quantity; + } + + if (r10.valid) + { + ++quantity; + } + + if (r01.valid) + { + ++quantity; + } + + if (r11.valid) + { + ++quantity; + } + + return quantity; + } + + QuadRectangle r00, r10, r01, r11; + }; + + class LinearMergeTree + { + public: + LinearMergeTree(int N) + : + mTwoPowerN(1 << N), + mNodes(2 * mTwoPowerN - 1) + { + } + + enum + { + CFG_NONE, + CFG_INCR, + CFG_DECR, + CFG_MULT + }; + + // Member access. + int GetQuantity() const + { + return 2 * mTwoPowerN - 1; + } + + int GetNode(int i) const + { + return mNodes[i]; + } + + int GetEdge(int i) const + { + // assert: mNodes[i] == CFG_INCR || mNodes[i] == CFG_DECR + + // Traverse binary tree looking for incr or decr leaf node. + int const firstLeaf = mTwoPowerN - 1; + while (i < firstLeaf) + { + i = 2 * i + 1; + if (mNodes[i] == CFG_NONE) + { + ++i; + } + } + + return i - firstLeaf; + } + + void SetLevel(Real level, T const* data, int offset, int stride) + { + // Assert: The 'level' is not an image value. Because T is + // an integer type, choose 'level' to be a Real-valued number + // that does not represent an integer. + + // Determine the sign changes between pairs of consecutive + // samples. + int const firstLeaf = mTwoPowerN - 1; + for (int i = 0, leaf = firstLeaf; i < mTwoPowerN; ++i, ++leaf) + { + int base = offset + stride * i; + Real value0 = static_cast(data[base]); + Real value1 = static_cast(data[base + stride]); + + if (value0 > level) + { + if (value1 > level) + { + mNodes[leaf] = CFG_NONE; + } + else + { + mNodes[leaf] = CFG_DECR; + } + } + else // value0 < level + { + if (value1 > level) + { + mNodes[leaf] = CFG_INCR; + } + else + { + mNodes[leaf] = CFG_NONE; + } + } + } + + // Propagate the sign change information up the binary tree. + for (int i = firstLeaf - 1; i >= 0; --i) + { + int twoIp1 = 2 * i + 1; + int child0 = mNodes[twoIp1]; + int child1 = mNodes[twoIp1 + 1]; + mNodes[i] = (child0 | child1); + } + } + + private: + int mTwoPowerN; + std::vector mNodes; + }; + + struct Rectangle + { + Rectangle(int inXOrigin, int inYOrigin, int inXStride, int inYStride) + : + xOrigin(inXOrigin), + yOrigin(inYOrigin), + xStride(inXStride), + yStride(inYStride), + yOfXMin(-1), + yOfXMax(-1), + xOfYMin(-1), + xOfYMax(-1), + type(0) + { + + } + + int xOrigin, yOrigin, xStride, yStride; + int yOfXMin, yOfXMax, xOfYMin, xOfYMax; + + // A 4-bit flag for how the level set intersects the rectangle + // boundary. + // bit 0 = xmin edge + // bit 1 = xmax edge + // bit 2 = ymin edge + // bit 3 = ymax edge + // A bit is set if the corresponding edge is intersected by the + // level set. This information is known from the CFG flags for + // LinearMergeTree. Intersection occurs whenever the flag is + // CFG_INCR or CFG_DECR. + unsigned int type; + }; + + class AreaMergeTree + { + public: + AreaMergeTree(int N, + std::vector> const& xMerge, + std::vector> const& yMerge) + : + mXMerge(xMerge), + mYMerge(yMerge), + mNodes(((1 << 2 * (N + 1)) - 1) / 3) + { + } + + void ConstructMono(int A, int LX, int LY, int xOrigin, int yOrigin, + int stride, int depth) + { + if (stride > 1) // internal nodes + { + int hStride = stride / 2; + + int ABase = 4 * A; + int A00 = ++ABase; + int A10 = ++ABase; + int A01 = ++ABase; + int A11 = ++ABase; + + int LXBase = 2 * LX; + int LX0 = ++LXBase; + int LX1 = ++LXBase; + + int LYBase = 2 * LY; + int LY0 = ++LYBase; + int LY1 = ++LYBase; + + int xNext = xOrigin + hStride; + int yNext = yOrigin + hStride; + + int depthM1 = depth - 1; + ConstructMono(A00, LX0, LY0, xOrigin, yOrigin, hStride, depthM1); + ConstructMono(A10, LX1, LY0, xNext, yOrigin, hStride, depthM1); + ConstructMono(A01, LX0, LY1, xOrigin, yNext, hStride, depthM1); + ConstructMono(A11, LX1, LY1, xNext, yNext, hStride, depthM1); + + if (depth >= 0) + { + // Merging is prevented above the specified depth in + // the tree. This allows a single object to produce + // any resolution isocontour rather than using + // multiple objects to do so. + mNodes[A].Initialize(xOrigin, yOrigin, xNext, yNext, hStride); + return; + } + + bool mono00 = mNodes[A00].IsMono(); + bool mono10 = mNodes[A10].IsMono(); + bool mono01 = mNodes[A01].IsMono(); + bool mono11 = mNodes[A11].IsMono(); + + QuadNode node0(xOrigin, yOrigin, xNext, yNext, hStride); + QuadNode node1 = node0; + + // Merge x first, y second. + if (mono00 && mono10) + { + DoXMerge(node0.r00, node0.r10, LX, yOrigin); + } + if (mono01 && mono11) + { + DoXMerge(node0.r01, node0.r11, LX, yNext); + } + if (mono00 && mono01) + { + DoYMerge(node0.r00, node0.r01, xOrigin, LY); + } + if (mono10 && mono11) + { + DoYMerge(node0.r10, node0.r11, xNext, LY); + } + + // Merge y first, x second. + if (mono00 && mono01) + { + DoYMerge(node1.r00, node1.r01, xOrigin, LY); + } + if (mono10 && mono11) + { + DoYMerge(node1.r10, node1.r11, xNext, LY); + } + if (mono00 && mono10) + { + DoXMerge(node1.r00, node1.r10, LX, yOrigin); + } + if (mono01 && mono11) + { + DoXMerge(node1.r01, node1.r11, LX, yNext); + } + + // Choose the merge that produced the smallest number of + // rectangles. + if (node0.GetQuantity() <= node1.GetQuantity()) + { + mNodes[A] = node0; + } + else + { + mNodes[A] = node1; + } + } + else // leaf nodes + { + mNodes[A].r00.Initialize(xOrigin, yOrigin, 1, 1); + } + } + + void GetRectangles(int A, int LX, int LY, int xOrigin, int yOrigin, + int stride, std::vector& rectangles) + { + int hStride = stride / 2; + int ABase = 4 * A; + int A00 = ++ABase; + int A10 = ++ABase; + int A01 = ++ABase; + int A11 = ++ABase; + int LXBase = 2 * LX; + int LX0 = ++LXBase; + int LX1 = ++LXBase; + int LYBase = 2 * LY; + int LY0 = ++LYBase; + int LY1 = ++LYBase; + int xNext = xOrigin + hStride; + int yNext = yOrigin + hStride; + + QuadRectangle const& r00 = mNodes[A].r00; + if (r00.valid) + { + if (r00.xStride == stride) + { + if (r00.yStride == stride) + { + rectangles.push_back(GetRectangle(r00, LX, LY)); + } + else + { + rectangles.push_back(GetRectangle(r00, LX, LY0)); + } + } + else + { + if (r00.yStride == stride) + { + rectangles.push_back(GetRectangle(r00, LX0, LY)); + } + else + { + GetRectangles(A00, LX0, LY0, xOrigin, yOrigin, hStride, rectangles); + } + } + } + + QuadRectangle const& r10 = mNodes[A].r10; + if (r10.valid) + { + if (r10.yStride == stride) + { + rectangles.push_back(GetRectangle(r10, LX1, LY)); + } + else + { + GetRectangles(A10, LX1, LY0, xNext, yOrigin, hStride, rectangles); + } + } + + QuadRectangle const& r01 = mNodes[A].r01; + if (r01.valid) + { + if (r01.xStride == stride) + { + rectangles.push_back(GetRectangle(r01, LX, LY1)); + } + else + { + GetRectangles(A01, LX0, LY1, xOrigin, yNext, hStride, rectangles); + } + } + + QuadRectangle const& r11 = mNodes[A].r11; + if (r11.valid) + { + GetRectangles(A11, LX1, LY1, xNext, yNext, hStride, rectangles); + } + } + + private: + void DoXMerge(QuadRectangle& r0, QuadRectangle& r1, int LX, int yOrigin) + { + if (r0.valid && r1.valid && r0.yStride == r1.yStride) + { + // Rectangles are x-mergeable. + int incr = 0, decr = 0; + for (int y = 0; y <= r0.yStride; ++y) + { + switch (mXMerge[yOrigin + y]->GetNode(LX)) + { + case LinearMergeTree::CFG_MULT: + return; + case LinearMergeTree::CFG_INCR: + ++incr; + break; + case LinearMergeTree::CFG_DECR: + ++decr; + break; + } + } + + if (incr == 0 || decr == 0) + { + // Strongly mono, x-merge the rectangles. + r0.xStride *= 2; + r1.valid = false; + } + } + } + + void DoYMerge(QuadRectangle& r0, QuadRectangle& r1, int xOrigin, int LY) + { + if (r0.valid && r1.valid && r0.xStride == r1.xStride) + { + // Rectangles are y-mergeable. + int incr = 0, decr = 0; + for (int x = 0; x <= r0.xStride; ++x) + { + switch (mYMerge[xOrigin + x]->GetNode(LY)) + { + case LinearMergeTree::CFG_MULT: + return; + case LinearMergeTree::CFG_INCR: + ++incr; + break; + case LinearMergeTree::CFG_DECR: + ++decr; + break; + } + } + + if (incr == 0 || decr == 0) + { + // Strongly mono, y-merge the rectangles. + r0.yStride *= 2; + r1.valid = false; + } + } + } + + Rectangle GetRectangle(QuadRectangle const& qrect, int LX, int LY) + { + Rectangle rect(qrect.xOrigin, qrect.yOrigin, qrect.xStride, qrect.yStride); + + // xmin edge + auto merge = mYMerge[qrect.xOrigin]; + if (merge->GetNode(LY) != LinearMergeTree::CFG_NONE) + { + rect.yOfXMin = merge->GetEdge(LY); + if (rect.yOfXMin != -1) + { + rect.type |= 0x01; + } + } + + // xmax edge + merge = mYMerge[qrect.xOrigin + qrect.xStride]; + if (merge->GetNode(LY) != LinearMergeTree::CFG_NONE) + { + rect.yOfXMax = merge->GetEdge(LY); + if (rect.yOfXMax != -1) + { + rect.type |= 0x02; + } + } + + // ymin edge + merge = mXMerge[qrect.yOrigin]; + if (merge->GetNode(LX) != LinearMergeTree::CFG_NONE) + { + rect.xOfYMin = merge->GetEdge(LX); + if (rect.xOfYMin != -1) + { + rect.type |= 0x04; + } + } + + // ymax edge + merge = mXMerge[qrect.yOrigin + qrect.yStride]; + if (merge->GetNode(LX) != LinearMergeTree::CFG_NONE) + { + rect.xOfYMax = merge->GetEdge(LX); + if (rect.xOfYMax != -1) + { + rect.type |= 0x08; + } + } + + return rect; + } + + std::vector> mXMerge; + std::vector> mYMerge; + std::vector mNodes; + }; + + private: + // Support for extraction of level sets. + Real GetInterp(Real level, int base, int index, int increment) + { + Real f0 = static_cast(mInputPixels[index]); + index += increment; + Real f1 = static_cast(mInputPixels[index]); + LogAssert((f0 - level) * (f1 - level) < (Real)0, "Unexpected condition."); + return static_cast(base) + (level - f0) / (f1 - f0); + } + + void AddVertex(std::vector& vertices, Real x, Real y) + { + Vertex vertex = { x, y }; + vertices.push_back(vertex); + } + + void AddEdge(std::vector& vertices, + std::vector& edges, Real x0, Real y0, Real x1, Real y1) + { + int v0 = static_cast(vertices.size()); + int v1 = v0 + 1; + Edge edge = { v0, v1 }; + edges.push_back(edge); + Vertex vertex0 = { x0, y0 }; + Vertex vertex1 = { x1, y1 }; + vertices.push_back(vertex0); + vertices.push_back(vertex1); + } + + void SetLevel(Real level, int depth) + { + int offset, stride; + + for (int y = 0; y < mSize; ++y) + { + offset = mSize * y; + stride = 1; + mXMerge[y]->SetLevel(level, mInputPixels, offset, stride); + } + + for (int x = 0; x < mSize; ++x) + { + offset = x; + stride = mSize; + mYMerge[x]->SetLevel(level, mInputPixels, offset, stride); + } + + mXYMerge->ConstructMono(0, 0, 0, 0, 0, mTwoPowerN, depth); + } + + void GetRectangles(std::vector& rectangles) + { + mXYMerge->GetRectangles(0, 0, 0, 0, 0, mTwoPowerN, rectangles); + } + + void GetComponents(Real level, Rectangle const& rectangle, + std::vector& vertices, std::vector& edges) + { + int x, y; + Real x0, y0, x1, y1; + + switch (rectangle.type) + { + case 3: // two vertices, on xmin and xmax + LogAssert(rectangle.yOfXMin != -1, "Unexpected condition."); + x = rectangle.xOrigin; + y = rectangle.yOfXMin; + x0 = static_cast(x); + y0 = GetInterp(level, y, x + mSize * y, mSize); + + LogAssert(rectangle.yOfXMax != -1, "Unexpected condition."); + x = rectangle.xOrigin + rectangle.xStride; + y = rectangle.yOfXMax; + x1 = static_cast(x); + y1 = GetInterp(level, y, x + mSize * y, mSize); + + AddEdge(vertices, edges, x0, y0, x1, y1); + break; + case 5: // two vertices, on xmin and ymin + LogAssert(rectangle.yOfXMin != -1, "Unexpected condition."); + x = rectangle.xOrigin; + y = rectangle.yOfXMin; + x0 = static_cast(x); + y0 = GetInterp(level, y, x + mSize * y, mSize); + + LogAssert(rectangle.xOfYMin != -1, "Unexpected condition."); + x = rectangle.xOfYMin; + y = rectangle.yOrigin; + x1 = GetInterp(level, x, x + mSize * y, 1); + y1 = static_cast(y); + + AddEdge(vertices, edges, x0, y0, x1, y1); + break; + case 6: // two vertices, on xmax and ymin + LogAssert(rectangle.yOfXMax != -1, "Unexpected condition."); + x = rectangle.xOrigin + rectangle.xStride; + y = rectangle.yOfXMax; + x0 = static_cast(x); + y0 = GetInterp(level, y, x + mSize * y, mSize); + + LogAssert(rectangle.xOfYMin != -1, "Unexpected condition."); + x = rectangle.xOfYMin; + y = rectangle.yOrigin; + x1 = GetInterp(level, x, x + mSize * y, 1); + y1 = static_cast(y); + + AddEdge(vertices, edges, x0, y0, x1, y1); + break; + case 9: // two vertices, on xmin and ymax + LogAssert(rectangle.yOfXMin != -1, "Unexpected condition."); + x = rectangle.xOrigin; + y = rectangle.yOfXMin; + x0 = static_cast(x); + y0 = GetInterp(level, y, x + mSize * y, mSize); + + LogAssert(rectangle.xOfYMax != -1, "Unexpected condition."); + x = rectangle.xOfYMax; + y = rectangle.yOrigin + rectangle.yStride; + x1 = GetInterp(level, x, x + mSize * y, 1); + y1 = static_cast(y); + + AddEdge(vertices, edges, x0, y0, x1, y1); + break; + case 10: // two vertices, on xmax and ymax + LogAssert(rectangle.yOfXMax != -1, "Unexpected condition."); + x = rectangle.xOrigin + rectangle.xStride; + y = rectangle.yOfXMax; + x0 = static_cast(x); + y0 = GetInterp(level, y, x + mSize * y, mSize); + + LogAssert(rectangle.xOfYMax != -1, "Unexpected condition."); + x = rectangle.xOfYMax; + y = rectangle.yOrigin + rectangle.yStride; + x1 = GetInterp(level, x, x + mSize * y, 1); + y1 = static_cast(y); + + AddEdge(vertices, edges, x0, y0, x1, y1); + break; + case 12: // two vertices, on ymin and ymax + LogAssert(rectangle.xOfYMin != -1, "Unexpected condition."); + x = rectangle.xOfYMin; + y = rectangle.yOrigin; + x0 = GetInterp(level, x, x + mSize * y, 1); + y0 = static_cast(y); + + LogAssert(rectangle.xOfYMax != -1, "Unexpected condition."); + x = rectangle.xOfYMax; + y = rectangle.yOrigin + rectangle.yStride; + x1 = GetInterp(level, x, x + mSize * y, 1); + y1 = static_cast(y); + + AddEdge(vertices, edges, x0, y0, x1, y1); + break; + case 15: // four vertices, one per edge, need to disambiguate + { + LogAssert(rectangle.xStride == 1 && rectangle.yStride == 1, + "Unexpected condition."); + + LogAssert(rectangle.yOfXMin != -1, "Unexpected condition."); + x = rectangle.xOrigin; + y = rectangle.yOfXMin; + x0 = static_cast(x); + y0 = GetInterp(level, y, x + mSize * y, mSize); + + LogAssert(rectangle.yOfXMax != -1, "Unexpected condition."); + x = rectangle.xOrigin + rectangle.xStride; + y = rectangle.yOfXMax; + x1 = static_cast(x); + y1 = GetInterp(level, y, x + mSize * y, mSize); + + LogAssert(rectangle.xOfYMin != -1, "Unexpected condition."); + x = rectangle.xOfYMin; + y = rectangle.yOrigin; + Real fx2 = GetInterp(level, x, x + mSize * y, 1); + Real fy2 = static_cast(y); + + LogAssert(rectangle.xOfYMax != -1, "Unexpected condition."); + x = rectangle.xOfYMax; + y = rectangle.yOrigin + rectangle.yStride; + Real fx3 = GetInterp(level, x, x + mSize * y, 1); + Real fy3 = static_cast(y); + + int index = rectangle.xOrigin + mSize * rectangle.yOrigin; + int64_t i00 = static_cast(mInputPixels[index]); + ++index; + int64_t i10 = static_cast(mInputPixels[index]); + index += mSize; + int64_t i11 = static_cast(mInputPixels[index]); + --index; + int64_t i01 = static_cast(mInputPixels[index]); + + int64_t det = i00 * i11 - i01 * i10; + if (det > 0) + { + // Disjoint hyperbolic segments, pair and . + AddEdge(vertices, edges, x0, y0, fx2, fy2); + AddEdge(vertices, edges, x1, y1, fx3, fy3); + } + else if (det < 0) + { + // Disjoint hyperbolic segments, pair and . + AddEdge(vertices, edges, x0, y0, fx3, fy3); + AddEdge(vertices, edges, x1, y1, fx2, fy2); + } + else + { + // Plus-sign configuration, add branch point to + // tessellation. + Real fx4 = fx2, fy4 = y0; + AddEdge(vertices, edges, x0, y0, fx4, fy4); + AddEdge(vertices, edges, x1, y1, fx4, fy4); + AddEdge(vertices, edges, fx2, fy2, fx4, fy4); + AddEdge(vertices, edges, fx3, fy3, fx4, fy4); + } + break; + } + default: + LogError("Unexpected condition."); + } + } + + // Support for debugging. + void PrintRectangles(std::ostream& output, std::vector const& rectangles) + { + for (size_t i = 0; i < rectangles.size(); ++i) + { + auto const& rectangle = rectangles[i]; + output << "rectangle " << i << std::endl; + output << " x origin = " << rectangle.xOrigin << std::endl; + output << " y origin = " << rectangle.yOrigin << std::endl; + output << " x stride = " << rectangle.xStride << std::endl; + output << " y stride = " << rectangle.yStride << std::endl; + output << " flag = " << rectangle.type << std::endl; + output << " y of xmin = " << rectangle.yOfXMin << std::endl; + output << " y of xmax = " << rectangle.yOfXMax << std::endl; + output << " x of ymin = " << rectangle.xOfYMin << std::endl; + output << " x of ymax = " << rectangle.xOfYMax << std::endl; + output << std::endl; + } + } + + // Storage of image data. + int mTwoPowerN, mSize; + T const* mInputPixels; + + // Trees for linear merging. + std::vector> mXMerge, mYMerge; + + // Tree for area merging. + std::unique_ptr mXYMerge; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/AdaptiveSkeletonClimbing3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/AdaptiveSkeletonClimbing3.h new file mode 100644 index 0000000..efd4be6 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/AdaptiveSkeletonClimbing3.h @@ -0,0 +1,2872 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2020.01.08 + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// Extract level surfaces using an adaptive approach to reduce the triangle +// count. The implementation is for the algorithm described in the paper +// Multiresolution Isosurface Extraction with Adaptive Skeleton Climbing +// Tim Poston, Tien-Tsin Wong and Pheng-Ann Heng +// Computer Graphics forum, volume 17, issue 3, September 1998 +// pages 137-147 +// https://onlinelibrary.wiley.com/doi/abs/10.1111/1467-8659.00261 + +namespace WwiseGTE +{ + // The image type T must be one of the integer types: int8_t, int16_t, + // int32_t, uint8_t, uint16_t or uint32_t. Internal integer computations + // are performed using int64_t. The type Real is for extraction to + // floating-point vertices. + template + class AdaptiveSkeletonClimbing3 + { + public: + // Construction and destruction. The input image is assumed to + // contain (2^N+1)-by-(2^N+1)-by-(2^N+1) elements where N >= 0. + // The organization is lexicographic order for (x,y,z). When + // 'fixBoundary' is set to 'true', image boundary voxels are not + // allowed to merge with any other voxels. This forces highest + // level of detail on the boundary. The idea is that an image too + // large to process by itself can be partitioned into smaller + // subimages and the adaptive skeleton climbing applied to each + // subimage. By forcing highest resolution on the boundary, + // adjacent subimages will not have any cracking problems. + AdaptiveSkeletonClimbing3(int N, T const* inputVoxels, + bool fixBoundary = false) + : + mTwoPowerN(1 << N), + mSize(mTwoPowerN + 1), + mSizeSqr(mSize * mSize), + mInputVoxels(inputVoxels), + mLevel((Real)0), + mFixBoundary(fixBoundary), + mXMerge(mSize, mSize), + mYMerge(mSize, mSize), + mZMerge(mSize, mSize) + { + static_assert(std::is_integral::value && sizeof(T) <= 4, + "Type T must be int{8,16,32}_t or uint{8,16,32}_t."); + if (N <= 0 || mInputVoxels == nullptr) + { + LogError("Invalid input."); + } + + for (int i = 0; i < mSize; ++i) + { + for (int j = 0; j < mSize; ++j) + { + mXMerge[i][j] = std::make_shared(N); + mYMerge[i][j] = std::make_shared(N); + mZMerge[i][j] = std::make_shared(N); + } + } + } + + // TODO: Refactor this class to have base class SurfaceExtractor. + typedef std::array Vertex; + typedef std::array Edge; + typedef TriangleKey Triangle; + + void Extract(Real level, int depth, + std::vector& vertices, std::vector& triangles) + { + std::vector localVertices; + std::vector localTriangles; + mBoxes.clear(); + + mLevel = level; + Merge(depth); + Tessellate(localVertices, localTriangles); + + vertices = std::move(localVertices); + triangles = std::move(localTriangles); + } + + void MakeUnique(std::vector& vertices, + std::vector& triangles) + { + size_t numVertices = vertices.size(); + size_t numTriangles = triangles.size(); + if (numVertices == 0 || numTriangles == 0) + { + return; + } + + // Compute the map of unique vertices and assign to them new and + // unique indices. + std::map vmap; + int nextVertex = 0; + for (size_t v = 0; v < numVertices; ++v) + { + // Keep only unique vertices. + auto result = vmap.insert(std::make_pair(vertices[v], nextVertex)); + if (result.second) + { + ++nextVertex; + } + } + + // Compute the map of unique triangles and assign to them new and + // unique indices. + std::map tmap; + int nextTriangle = 0; + for (size_t t = 0; t < numTriangles; ++t) + { + Triangle& triangle = triangles[t]; + for (int i = 0; i < 3; ++i) + { + auto iter = vmap.find(vertices[triangle.V[i]]); + LogAssert(iter != vmap.end(), "Expecting the vertex to be in the vmap."); + triangle.V[i] = iter->second; + } + + // Keep only unique triangles. + auto result = tmap.insert(std::make_pair(triangle, nextTriangle)); + if (result.second) + { + ++nextTriangle; + } + } + + // Pack the vertices into an array. + vertices.resize(vmap.size()); + for (auto const& element : vmap) + { + vertices[element.second] = element.first; + } + + // Pack the triangles into an array. + triangles.resize(tmap.size()); + for (auto const& element : tmap) + { + triangles[element.second] = element.first; + } + } + + void OrientTriangles(std::vector& vertices, + std::vector& triangles, bool sameDir) + { + for (auto& triangle : triangles) + { + // Get the triangle vertices. + std::array v0 = vertices[triangle.V[0]]; + std::array v1 = vertices[triangle.V[1]]; + std::array v2 = vertices[triangle.V[2]]; + + // Construct the triangle normal based on the current + // orientation. + std::array edge1, edge2, normal; + for (int i = 0; i < 3; ++i) + { + edge1[i] = v1[i] - v0[i]; + edge2[i] = v2[i] - v0[i]; + } + normal[0] = edge1[1] * edge2[2] - edge1[2] * edge2[1]; + normal[1] = edge1[2] * edge2[0] - edge1[0] * edge2[2]; + normal[2] = edge1[0] * edge2[1] - edge1[1] * edge2[0]; + + // Get the image gradient at the vertices. + std::array grad0 = GetGradient(v0); + std::array grad1 = GetGradient(v1); + std::array grad2 = GetGradient(v2); + + // Compute the average gradient. + std::array gradAvr; + for (int i = 0; i < 3; ++i) + { + gradAvr[i] = (grad0[i] + grad1[i] + grad2[i]) / (Real)3; + } + + // Compute the dot product of normal and average gradient. + Real dot = gradAvr[0] * normal[0] + gradAvr[1] * normal[1] + gradAvr[2] * normal[2]; + + // Choose triangle orientation based on gradient direction. + if (sameDir) + { + if (dot < (Real)0) + { + // Wrong orientation, reorder it. + std::swap(triangle.V[1], triangle.V[2]); + } + } + else + { + if (dot > (Real)0) + { + // Wrong orientation, reorder it. + std::swap(triangle.V[1], triangle.V[2]); + } + } + } + } + + void ComputeNormals(std::vector const& vertices, + std::vector const& triangles, + std::vector>& normals) + { + // Compute a vertex normal to be area-weighted sums of the normals + // to the triangles that share that vertex. + std::array const zero{ (Real)0, (Real)0, (Real)0 }; + normals.resize(vertices.size()); + std::fill(normals.begin(), normals.end(), zero); + + for (auto const& triangle : triangles) + { + // Get the triangle vertices. + std::array v0 = vertices[triangle.V[0]]; + std::array v1 = vertices[triangle.V[1]]; + std::array v2 = vertices[triangle.V[2]]; + + // Construct the triangle normal. + std::array edge1, edge2, normal; + for (int i = 0; i < 3; ++i) + { + edge1[i] = v1[i] - v0[i]; + edge2[i] = v2[i] - v0[i]; + } + normal[0] = edge1[1] * edge2[2] - edge1[2] * edge2[1]; + normal[1] = edge1[2] * edge2[0] - edge1[0] * edge2[2]; + normal[2] = edge1[0] * edge2[1] - edge1[1] * edge2[0]; + + // Maintain the sum of normals at each vertex. + for (int i = 0; i < 3; ++i) + { + for (int j = 0; j < 3; ++j) + { + normals[triangle.V[i]][j] += normal[j]; + } + } + } + + // The normal vector storage was used to accumulate the sum of + // triangle normals. Now these vectors must be rescaled to be + // unit length. + for (auto& normal : normals) + { + Real sqrLength = normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]; + Real length = std::sqrt(sqrLength); + if (length > (Real)0) + { + for (int i = 0; i < 3; ++i) + { + normal[i] /= length; + } + } + else + { + for (int i = 0; i < 3; ++i) + { + normal[i] = (Real)0; + } + } + } + } + + // Support for debugging. + void PrintBoxes(std::ostream& output) + { + output << mBoxes.size() << std::endl; + for (size_t i = 0; i < mBoxes.size(); ++i) + { + OctBox const& box = mBoxes[i]; + output << "box " << i << ": "; + output << "x0 = " << box.x0 << ", "; + output << "y0 = " << box.y0 << ", "; + output << "z0 = " << box.z0 << ", "; + output << "dx = " << box.dx << ", "; + output << "dy = " << box.dy << ", "; + output << "dz = " << box.dz << std::endl; + } + } + + private: + // Helper classes for the skeleton climbing. + struct OctBox + { + OctBox(int inX0, int inY0, int inZ0, int inDX, int inDY, int inDZ, + int inLX, int inLY, int inLZ) + : + x0(inX0), y0(inY0), z0(inZ0), + x1(inX0 + inDX), y1(inY0 + inDY), z1(inZ0 + inDZ), + dx(inDX), dy(inDY), dz(inDZ), + LX(inLX), LY(inLY), LZ(inLZ) + { + } + + int x0, y0, z0, x1, y1, z1, dx, dy, dz, LX, LY, LZ; + }; + + struct MergeBox + { + MergeBox(int stride) + : + xStride(stride), yStride(stride), zStride(stride), + valid(true) + { + } + + int xStride, yStride, zStride; + bool valid; + }; + + class LinearMergeTree + { + public: + LinearMergeTree(int N) + : + mTwoPowerN(1 << N), + mNodes(2 * mTwoPowerN - 1), + mZeroBases(2 * mTwoPowerN - 1) + { + } + + enum + { + CFG_NONE = 0, + CFG_INCR = 1, + CFG_DECR = 2, + CFG_MULT = 3, + CFG_ROOT_MASK = 3, + CFG_EDGE = 4, + CFG_ZERO_SUBEDGE = 8 + }; + + bool IsNone(int i) const + { + return (mNodes[i] & CFG_ROOT_MASK) == CFG_NONE; + } + + int GetRootType(int i) const + { + return mNodes[i] & CFG_ROOT_MASK; + } + + int GetZeroBase(int i) const + { + return mZeroBases[i]; + } + + void SetEdge(int i) + { + mNodes[i] |= CFG_EDGE; + + // Inform all predecessors whether they have a zero subedge. + if (mZeroBases[i] >= 0) + { + while (i > 0) + { + i = (i - 1) / 2; + mNodes[i] |= CFG_ZERO_SUBEDGE; + } + } + } + + bool IsZeroEdge(int i) const + { + return mNodes[i] == (CFG_EDGE | CFG_INCR) + || mNodes[i] == (CFG_EDGE | CFG_DECR); + } + + bool HasZeroSubedge(int i) const + { + return (mNodes[i] & CFG_ZERO_SUBEDGE) != 0; + } + + void SetLevel(Real level, T const* data, int offset, int stride) + { + // Assert: The 'level' is not an image value. Because T is + // an integer type, choose 'level' to be a Real-valued number + // that does not represent an integer. + + // Determine the sign changes between pairs of consecutive + // samples. + int firstLeaf = mTwoPowerN - 1; + for (int i = 0, leaf = firstLeaf; i < mTwoPowerN; ++i, ++leaf) + { + int base = offset + stride * i; + Real value0 = static_cast(data[base]); + Real value1 = static_cast(data[base + stride]); + + if (value0 > level) + { + if (value1 > level) + { + mNodes[leaf] = CFG_NONE; + mZeroBases[leaf] = -1; + } + else + { + mNodes[leaf] = CFG_DECR; + mZeroBases[leaf] = i; + } + } + else // value0 < level + { + if (value1 > level) + { + mNodes[leaf] = CFG_INCR; + mZeroBases[leaf] = i; + } + else + { + mNodes[leaf] = CFG_NONE; + mZeroBases[leaf] = -1; + } + } + } + + // Propagate the sign change information up the binary tree. + for (int i = firstLeaf - 1; i >= 0; --i) + { + int twoIp1 = 2 * i + 1, twoIp2 = twoIp1 + 1; + int value0 = mNodes[twoIp1]; + int value1 = mNodes[twoIp2]; + int combine = (value0 | value1); + mNodes[i] = combine; + if (combine == CFG_INCR) + { + if (value0 == CFG_INCR) + { + mZeroBases[i] = mZeroBases[twoIp1]; + } + else + { + mZeroBases[i] = mZeroBases[twoIp2]; + } + } + else if (combine == CFG_DECR) + { + if (value0 == CFG_DECR) + { + mZeroBases[i] = mZeroBases[twoIp1]; + } + else + { + mZeroBases[i] = mZeroBases[twoIp2]; + } + } + else + { + mZeroBases[i] = -1; + } + } + } + + private: + int mTwoPowerN; + std::vector mNodes; + std::vector mZeroBases; + }; + + class VETable + { + public: + VETable() + : + mVertices(18) + { + } + + bool IsValidVertex(int i) const + { + return mVertices[i].valid; + } + + int GetNumVertices() const + { + return static_cast(mVertices.size()); + } + + Vertex const& GetVertex(int i) const + { + return mVertices[i].position; + } + + void Insert(int i, Real x, Real y, Real z) + { + TVertex& vertex = mVertices[i]; + vertex.position = Vertex{ x, y, z }; + vertex.valid = true; + } + + void Insert(Vertex const& position) + { + mVertices.push_back(TVertex(position)); + } + + void InsertEdge(int v0, int v1) + { + TVertex& vertex0 = mVertices[v0]; + TVertex& vertex1 = mVertices[v1]; + vertex0.adjacent[vertex0.adjQuantity] = v1; + ++vertex0.adjQuantity; + vertex1.adjacent[vertex1.adjQuantity] = v0; + ++vertex1.adjQuantity; + } + + void RemoveTrianglesEC(std::vector& positions, + std::vector& triangles) + { + // Ear-clip the wireframe to get the triangles. + Triangle triangle; + while (RemoveEC(triangle)) + { + int v0 = static_cast(positions.size()); + int v1 = v0 + 1; + int v2 = v1 + 1; + triangles.push_back(Triangle(v0, v1, v2)); + positions.push_back(mVertices[triangle.V[0]].position); + positions.push_back(mVertices[triangle.V[1]].position); + positions.push_back(mVertices[triangle.V[2]].position); + } + } + + void RemoveTrianglesSE(std::vector& positions, + std::vector& triangles) + { + // Compute centroid of vertices. + Vertex centroid = { (Real)0, (Real)0, (Real)0 }; + int const vmax = static_cast(mVertices.size()); + int i, j, quantity = 0; + for (i = 0; i < vmax; i++) + { + TVertex const& vertex = mVertices[i]; + if (vertex.valid) + { + for (j = 0; j < 3; ++j) + { + centroid[j] += vertex.position[j]; + } + ++quantity; + } + } + for (j = 0; j < 3; ++j) + { + centroid[j] /= static_cast(quantity); + } + + int v0 = static_cast(positions.size()); + positions.push_back(centroid); + + int i1 = 18; + int v1 = v0 + 1; + positions.push_back(mVertices[i1].position); + + int i2 = mVertices[i1].adjacent[1], v2; + for (i = 0; i < quantity - 1; ++i) + { + v2 = v1 + 1; + positions.push_back(mVertices[i2].position); + triangles.push_back(Triangle(v0, v1, v2)); + if (mVertices[i2].adjacent[1] != i1) + { + i1 = i2; + i2 = mVertices[i2].adjacent[1]; + } + else + { + i1 = i2; + i2 = mVertices[i2].adjacent[0]; + } + v1 = v2; + } + + v2 = v0 + 1; + triangles.push_back(Triangle(v0, v1, v2)); + } + + protected: + void RemoveVertex(int i) + { + TVertex& vertex0 = mVertices[i]; + int a0 = vertex0.adjacent[0]; + int a1 = vertex0.adjacent[1]; + TVertex& adjVertex0 = mVertices[a0]; + TVertex& adjVertex1 = mVertices[a1]; + + int j; + for (j = 0; j < adjVertex0.adjQuantity; j++) + { + if (adjVertex0.adjacent[j] == i) + { + adjVertex0.adjacent[j] = a1; + break; + } + } + + for (j = 0; j < adjVertex1.adjQuantity; j++) + { + if (adjVertex1.adjacent[j] == i) + { + adjVertex1.adjacent[j] = a0; + break; + } + } + + vertex0.valid = false; + + if (adjVertex0.adjQuantity == 2) + { + if (adjVertex0.adjacent[0] == adjVertex0.adjacent[1]) + { + adjVertex0.valid = false; + } + } + + if (adjVertex1.adjQuantity == 2) + { + if (adjVertex1.adjacent[0] == adjVertex1.adjacent[1]) + { + adjVertex1.valid = false; + } + } + } + + // ear clipping + bool RemoveEC(Triangle& triangle) + { + int numVertices = static_cast(mVertices.size()); + for (int i = 0; i < numVertices; ++i) + { + TVertex const& vertex = mVertices[i]; + if (vertex.valid && vertex.adjQuantity == 2) + { + triangle.V[0] = i; + triangle.V[1] = vertex.adjacent[0]; + triangle.V[2] = vertex.adjacent[1]; + RemoveVertex(i); + return true; + } + } + + return false; + } + + class TVertex + { + public: + TVertex() + : + adjQuantity(0), + valid(false) + { + } + + TVertex(Vertex const& inPosition) + : + position(inPosition), + adjQuantity(0), + valid(true) + { + + } + + Vertex position; + int adjQuantity; + std::array adjacent; + bool valid; + }; + + std::vector mVertices; + }; + + private: + // Support for merging monoboxes. + void Merge(int depth) + { + int x, y, z, offset, stride; + + for (y = 0; y < mSize; ++y) + { + for (z = 0; z < mSize; ++z) + { + offset = mSize * (y + mSize * z); + stride = 1; + mXMerge[y][z]->SetLevel(mLevel, mInputVoxels, offset, stride); + } + } + + for (x = 0; x < mSize; ++x) + { + for (z = 0; z < mSize; ++z) + { + offset = x + mSizeSqr * z; + stride = mSize; + mYMerge[x][z]->SetLevel(mLevel, mInputVoxels, offset, stride); + } + } + + for (x = 0; x < mSize; ++x) + { + for (y = 0; y < mSize; ++y) + { + offset = x + mSize * y; + stride = mSizeSqr; + mZMerge[x][y]->SetLevel(mLevel, mInputVoxels, offset, stride); + } + } + + Merge(0, 0, 0, 0, 0, 0, 0, mTwoPowerN, depth); + } + + bool Merge(int v, int LX, int LY, int LZ, int x0, int y0, int z0, int stride, int depth) + { + if (stride > 1) // internal nodes + { + int hStride = stride / 2; + int vBase = 8 * v; + int v000 = vBase + 1; + int v100 = vBase + 2; + int v010 = vBase + 3; + int v110 = vBase + 4; + int v001 = vBase + 5; + int v101 = vBase + 6; + int v011 = vBase + 7; + int v111 = vBase + 8; + int LX0 = 2 * LX + 1, LX1 = LX0 + 1; + int LY0 = 2 * LY + 1, LY1 = LY0 + 1; + int LZ0 = 2 * LZ + 1, LZ1 = LZ0 + 1; + int x1 = x0 + hStride, y1 = y0 + hStride, z1 = z0 + hStride; + + int dm1 = depth - 1; + bool m000 = Merge(v000, LX0, LY0, LZ0, x0, y0, z0, hStride, dm1); + bool m100 = Merge(v100, LX1, LY0, LZ0, x1, y0, z0, hStride, dm1); + bool m010 = Merge(v010, LX0, LY1, LZ0, x0, y1, z0, hStride, dm1); + bool m110 = Merge(v110, LX1, LY1, LZ0, x1, y1, z0, hStride, dm1); + bool m001 = Merge(v001, LX0, LY0, LZ1, x0, y0, z1, hStride, dm1); + bool m101 = Merge(v101, LX1, LY0, LZ1, x1, y0, z1, hStride, dm1); + bool m011 = Merge(v011, LX0, LY1, LZ1, x0, y1, z1, hStride, dm1); + bool m111 = Merge(v111, LX1, LY1, LZ1, x1, y1, z1, hStride, dm1); + + MergeBox r000(hStride), r100(hStride), r010(hStride), r110(hStride); + MergeBox r001(hStride), r101(hStride), r011(hStride), r111(hStride); + + if (depth <= 0) + { + if (m000 && m001) + { + DoZMerge(r000, r001, x0, y0, LZ); + } + + if (m100 && m101) + { + DoZMerge(r100, r101, x1, y0, LZ); + } + + if (m010 && m011) + { + DoZMerge(r010, r011, x0, y1, LZ); + } + + if (m110 && m111) + { + DoZMerge(r110, r111, x1, y1, LZ); + } + + if (m000 && m010) + { + DoYMerge(r000, r010, x0, LY, z0); + } + + if (m100 && m110) + { + DoYMerge(r100, r110, x1, LY, z0); + } + + if (m001 && m011) + { + DoYMerge(r001, r011, x0, LY, z1); + } + + if (m101 && m111) + { + DoYMerge(r101, r111, x1, LY, z1); + } + + if (m000 && m100) + { + DoXMerge(r000, r100, LX, y0, z0); + } + + if (m010 && m110) + { + DoXMerge(r010, r110, LX, y1, z0); + } + + if (m001 && m101) + { + DoXMerge(r001, r101, LX, y0, z1); + } + + if (m011 && m111) + { + DoXMerge(r011, r111, LX, y1, z1); + } + } + + if (depth <= 1) + { + if (r000.valid) + { + if (r000.xStride == stride) + { + if (r000.yStride == stride) + { + if (r000.zStride == stride) + { + return true; + } + else + { + AddBox(x0, y0, z0, stride, stride, hStride, LX, LY, LZ0); + } + } + else + { + if (r000.zStride == stride) + { + AddBox(x0, y0, z0, stride, hStride, stride, LX, LY0, LZ); + } + else + { + AddBox(x0, y0, z0, stride, hStride, hStride, LX, LY0, LZ0); + } + } + } + else + { + if (r000.yStride == stride) + { + if (r000.zStride == stride) + { + AddBox(x0, y0, z0, hStride, stride, stride, LX0, LY, LZ); + } + else + { + AddBox(x0, y0, z0, hStride, stride, hStride, LX0, LY, LZ0); + } + } + else + { + if (r000.zStride == stride) + { + AddBox(x0, y0, z0, hStride, hStride, stride, LX0, LY0, LZ); + } + else if (m000) + { + AddBox(x0, y0, z0, hStride, hStride, hStride, LX0, LY0, LZ0); + } + } + } + } + + if (r100.valid) + { + if (r100.yStride == stride) + { + if (r100.zStride == stride) + { + AddBox(x1, y0, z0, hStride, stride, stride, LX1, LY, LZ); + } + else + { + AddBox(x1, y0, z0, hStride, stride, hStride, LX1, LY, LZ0); + } + } + else + { + if (r100.zStride == stride) + { + AddBox(x1, y0, z0, hStride, hStride, stride, LX1, LY0, LZ); + } + else if (m100) + { + AddBox(x1, y0, z0, hStride, hStride, hStride, LX1, LY0, LZ0); + } + } + } + + if (r010.valid) + { + if (r010.xStride == stride) + { + if (r010.zStride == stride) + { + AddBox(x0, y1, z0, stride, hStride, stride, LX, LY1, LZ); + } + else + { + AddBox(x0, y1, z0, stride, hStride, hStride, LX, LY1, LZ0); + } + } + else + { + if (r010.zStride == stride) + { + AddBox(x0, y1, z0, hStride, hStride, stride, LX0, LY1, LZ); + } + else if (m010) + { + AddBox(x0, y1, z0, hStride, hStride, hStride, LX0, LY1, LZ0); + } + } + } + + if (r001.valid) + { + if (r001.xStride == stride) + { + if (r001.yStride == stride) + { + AddBox(x0, y0, z1, stride, stride, hStride, LX, LY, LZ1); + } + else + { + AddBox(x0, y0, z1, stride, hStride, hStride, LX, LY0, LZ1); + } + } + else + { + if (r001.yStride == stride) + { + AddBox(x0, y0, z1, hStride, stride, hStride, LX0, LY, LZ1); + } + else if (m001) + { + AddBox(x0, y0, z1, hStride, hStride, hStride, LX0, LY0, LZ1); + } + } + } + + if (r110.valid) + { + if (r110.zStride == stride) + { + AddBox(x1, y1, z0, hStride, hStride, stride, LX1, LY1, LZ); + } + else if (m110) + { + AddBox(x1, y1, z0, hStride, hStride, hStride, LX1, LY1, LZ0); + } + } + + if (r101.valid) + { + if (r101.yStride == stride) + { + AddBox(x1, y0, z1, hStride, stride, hStride, LX1, LY, LZ1); + } + else if (m101) + { + AddBox(x1, y0, z1, hStride, hStride, hStride, LX1, LY0, LZ1); + } + } + + if (r011.valid) + { + if (r011.xStride == stride) + { + AddBox(x0, y1, z1, stride, hStride, hStride, LX, LY1, LZ1); + } + else if (m011) + { + AddBox(x0, y1, z1, hStride, hStride, hStride, LX0, LY1, LZ1); + } + } + + if (r111.valid && m111) + { + AddBox(x1, y1, z1, hStride, hStride, hStride, LX1, LY1, LZ1); + } + } + return false; + } + else // leaf nodes + { + if (mFixBoundary) + { + // Do not allow boundary voxels to merge with any other + // voxels. + AddBox(x0, y0, z0, 1, 1, 1, LX, LY, LZ); + return false; + } + + // A leaf box is mergeable with neighbors as long as all its + // faces have 0 or 2 sign changes on the edges. That is, a + // face may not have sign changes on all four edges. If it + // does, the resulting box for tessellating is 1x1x1 and is + // handled separately from boxes of larger dimensions. + + // xmin face + int z1 = z0 + 1; + int rt0 = mYMerge[x0][z0]->GetRootType(LY); + int rt1 = mYMerge[x0][z1]->GetRootType(LY); + if ((rt0 | rt1) == LinearMergeTree::CFG_MULT) + { + AddBox(x0, y0, z0, 1, 1, 1, LX, LY, LZ); + return false; + } + + // xmax face + int x1 = x0 + 1; + rt0 = mYMerge[x1][z0]->GetRootType(LY); + rt1 = mYMerge[x1][z1]->GetRootType(LY); + if ((rt0 | rt1) == LinearMergeTree::CFG_MULT) + { + AddBox(x0, y0, z0, 1, 1, 1, LX, LY, LZ); + return false; + } + + // ymin face + rt0 = mZMerge[x0][y0]->GetRootType(LZ); + rt1 = mZMerge[x1][y0]->GetRootType(LZ); + if ((rt0 | rt1) == LinearMergeTree::CFG_MULT) + { + AddBox(x0, y0, z0, 1, 1, 1, LX, LY, LZ); + return false; + } + + // ymax face + int y1 = y0 + 1; + rt0 = mZMerge[x0][y1]->GetRootType(LZ); + rt1 = mZMerge[x1][y1]->GetRootType(LZ); + if ((rt0 | rt1) == LinearMergeTree::CFG_MULT) + { + AddBox(x0, y0, z0, 1, 1, 1, LX, LY, LZ); + return false; + } + + // zmin face + rt0 = mXMerge[y0][z0]->GetRootType(LX); + rt1 = mXMerge[y1][z0]->GetRootType(LX); + if ((rt0 | rt1) == LinearMergeTree::CFG_MULT) + { + AddBox(x0, y0, z0, 1, 1, 1, LX, LY, LZ); + return false; + } + + // zmax face + rt0 = mXMerge[y0][z1]->GetRootType(LX); + rt1 = mXMerge[y1][z1]->GetRootType(LX); + if ((rt0 | rt1) == LinearMergeTree::CFG_MULT) + { + AddBox(x0, y0, z0, 1, 1, 1, LX, LY, LZ); + return false; + } + + return true; + } + } + + bool DoXMerge(MergeBox& r0, MergeBox& r1, int LX, int y0, int z0) + { + if (!r0.valid || !r1.valid || r0.yStride != r1.yStride || r0.zStride != r1.zStride) + { + return false; + } + + // Boxes are potentially x-mergeable. + int y1 = y0 + r0.yStride, z1 = z0 + r0.zStride; + int incr = 0, decr = 0; + for (int y = y0; y <= y1; ++y) + { + for (int z = z0; z <= z1; ++z) + { + switch (mXMerge[y][z]->GetRootType(LX)) + { + case LinearMergeTree::CFG_MULT: + return false; + case LinearMergeTree::CFG_INCR: + ++incr; + break; + case LinearMergeTree::CFG_DECR: + ++decr; + break; + } + } + } + + if (incr != 0 && decr != 0) + { + return false; + } + + // Strongly mono, x-merge the boxes. + r0.xStride *= 2; + r1.valid = false; + return true; + } + + bool DoYMerge(MergeBox& r0, MergeBox& r1, int x0, int LY, int z0) + { + if (!r0.valid || !r1.valid || r0.xStride != r1.xStride || r0.zStride != r1.zStride) + { + return false; + } + + // Boxes are potentially y-mergeable. + int x1 = x0 + r0.xStride, z1 = z0 + r0.zStride; + int incr = 0, decr = 0; + for (int x = x0; x <= x1; ++x) + { + for (int z = z0; z <= z1; ++z) + { + switch (mYMerge[x][z]->GetRootType(LY)) + { + case LinearMergeTree::CFG_MULT: + return false; + case LinearMergeTree::CFG_INCR: + ++incr; + break; + case LinearMergeTree::CFG_DECR: + ++decr; + break; + } + } + } + + if (incr != 0 && decr != 0) + { + return false; + } + + // Strongly mono, y-merge the boxes. + r0.yStride *= 2; + r1.valid = false; + return true; + } + + bool DoZMerge(MergeBox& r0, MergeBox& r1, int x0, int y0, int LZ) + { + if (!r0.valid || !r1.valid || r0.xStride != r1.xStride || r0.yStride != r1.yStride) + { + return false; + } + + // Boxes are potentially z-mergeable. + int x1 = x0 + r0.xStride, y1 = y0 + r0.yStride; + int incr = 0, decr = 0; + for (int x = x0; x <= x1; ++x) + { + for (int y = y0; y <= y1; ++y) + { + switch (mZMerge[x][y]->GetRootType(LZ)) + { + case LinearMergeTree::CFG_MULT: + return false; + case LinearMergeTree::CFG_INCR: + ++incr; + break; + case LinearMergeTree::CFG_DECR: + ++decr; + break; + } + } + } + + if (incr != 0 && decr != 0) + { + return false; + } + + // Strongly mono, z-merge the boxes. + r0.zStride *= 2; + r1.valid = false; + return true; + } + + void AddBox(int x0, int y0, int z0, int dx, int dy, int dz, int LX, int LY, int LZ) + { + OctBox box(x0, y0, z0, dx, dy, dz, LX, LY, LZ); + mBoxes.push_back(box); + + // Mark box edges in linear merge trees. This information will be + // used later for extraction. + mXMerge[box.y0][box.z0]->SetEdge(box.LX); + mXMerge[box.y0][box.z1]->SetEdge(box.LX); + mXMerge[box.y1][box.z0]->SetEdge(box.LX); + mXMerge[box.y1][box.z1]->SetEdge(box.LX); + mYMerge[box.x0][box.z0]->SetEdge(box.LY); + mYMerge[box.x0][box.z1]->SetEdge(box.LY); + mYMerge[box.x1][box.z0]->SetEdge(box.LY); + mYMerge[box.x1][box.z1]->SetEdge(box.LY); + mZMerge[box.x0][box.y0]->SetEdge(box.LZ); + mZMerge[box.x0][box.y1]->SetEdge(box.LZ); + mZMerge[box.x1][box.y0]->SetEdge(box.LZ); + mZMerge[box.x1][box.y1]->SetEdge(box.LZ); + } + + // Support for tessellating monoboxes. + void Tessellate(std::vector& positions, std::vector& triangles) + { + for (size_t i = 0; i < mBoxes.size(); ++i) + { + OctBox const& box = mBoxes[i]; + + // Get vertices on edges of box. + VETable table; + unsigned int type; + GetVertices(box, type, table); + if (type == 0) + { + continue; + } + + // Add wireframe edges to table, add face-vertices if + // necessary. + if (box.dx > 1 || box.dy > 1 || box.dz > 1) + { + // Box is larger than voxel, each face has at most one + // edge. + GetXMinEdgesM(box, type, table); + GetXMaxEdgesM(box, type, table); + GetYMinEdgesM(box, type, table); + GetYMaxEdgesM(box, type, table); + GetZMinEdgesM(box, type, table); + GetZMaxEdgesM(box, type, table); + + if (table.GetNumVertices() > 18) + { + table.RemoveTrianglesSE(positions, triangles); + } + else + { + table.RemoveTrianglesEC(positions, triangles); + } + } + else + { + // The box is a 1x1x1 voxel. Do the full edge analysis + // but no splitting is required. + GetXMinEdgesS(box, type, table); + GetXMaxEdgesS(box, type, table); + GetYMinEdgesS(box, type, table); + GetYMaxEdgesS(box, type, table); + GetZMinEdgesS(box, type, table); + GetZMaxEdgesS(box, type, table); + table.RemoveTrianglesEC(positions, triangles); + } + } + } + + Real GetXInterp(int x, int y, int z) const + { + int index = x + mSize * (y + mSize * z); + Real f0 = static_cast(mInputVoxels[index]); + ++index; + Real f1 = static_cast(mInputVoxels[index]); + return static_cast(x) + (mLevel - f0) / (f1 - f0); + } + + Real GetYInterp(int x, int y, int z) const + { + int index = x + mSize * (y + mSize * z); + Real f0 = static_cast(mInputVoxels[index]); + index += mSize; + Real f1 = static_cast(mInputVoxels[index]); + return static_cast(y) + (mLevel - f0) / (f1 - f0); + } + + Real GetZInterp(int x, int y, int z) const + { + int index = x + mSize * (y + mSize * z); + Real f0 = static_cast(mInputVoxels[index]); + index += mSizeSqr; + Real f1 = static_cast(mInputVoxels[index]); + return static_cast(z) + (mLevel - f0) / (f1 - f0); + } + + void GetVertices(OctBox const& box, unsigned int& type, VETable& table) + { + int root; + type = 0; + + // xmin-ymin edge + root = mZMerge[box.x0][box.y0]->GetZeroBase(box.LZ); + if (root != -1) + { + type |= EB_XMIN_YMIN; + table.Insert(EI_XMIN_YMIN, + static_cast(box.x0), + static_cast(box.y0), + GetZInterp(box.x0, box.y0, root)); + } + + // xmin-ymax edge + root = mZMerge[box.x0][box.y1]->GetZeroBase(box.LZ); + if (root != -1) + { + type |= EB_XMIN_YMAX; + table.Insert(EI_XMIN_YMAX, + static_cast(box.x0), + static_cast(box.y1), + GetZInterp(box.x0, box.y1, root)); + } + + // xmax-ymin edge + root = mZMerge[box.x1][box.y0]->GetZeroBase(box.LZ); + if (root != -1) + { + type |= EB_XMAX_YMIN; + table.Insert(EI_XMAX_YMIN, + static_cast(box.x1), + static_cast(box.y0), + GetZInterp(box.x1, box.y0, root)); + } + + // xmax-ymax edge + root = mZMerge[box.x1][box.y1]->GetZeroBase(box.LZ); + if (root != -1) + { + type |= EB_XMAX_YMAX; + table.Insert(EI_XMAX_YMAX, + static_cast(box.x1), + static_cast(box.y1), + GetZInterp(box.x1, box.y1, root)); + } + + // xmin-zmin edge + root = mYMerge[box.x0][box.z0]->GetZeroBase(box.LY); + if (root != -1) + { + type |= EB_XMIN_ZMIN; + table.Insert(EI_XMIN_ZMIN, + static_cast(box.x0), + GetYInterp(box.x0, root, box.z0), + static_cast(box.z0)); + } + + // xmin-zmax edge + root = mYMerge[box.x0][box.z1]->GetZeroBase(box.LY); + if (root != -1) + { + type |= EB_XMIN_ZMAX; + table.Insert(EI_XMIN_ZMAX, + static_cast(box.x0), + GetYInterp(box.x0, root, box.z1), + static_cast(box.z1)); + } + + // xmax-zmin edge + root = mYMerge[box.x1][box.z0]->GetZeroBase(box.LY); + if (root != -1) + { + type |= EB_XMAX_ZMIN; + table.Insert(EI_XMAX_ZMIN, + static_cast(box.x1), + GetYInterp(box.x1, root, box.z0), + static_cast(box.z0)); + } + + // xmax-zmax edge + root = mYMerge[box.x1][box.z1]->GetZeroBase(box.LY); + if (root != -1) + { + type |= EB_XMAX_ZMAX; + table.Insert(EI_XMAX_ZMAX, + static_cast(box.x1), + GetYInterp(box.x1, root, box.z1), + static_cast(box.z1)); + } + + // ymin-zmin edge + root = mXMerge[box.y0][box.z0]->GetZeroBase(box.LX); + if (root != -1) + { + type |= EB_YMIN_ZMIN; + table.Insert(EI_YMIN_ZMIN, + GetXInterp(root, box.y0, box.z0), + static_cast(box.y0), + static_cast(box.z0)); + } + + // ymin-zmax edge + root = mXMerge[box.y0][box.z1]->GetZeroBase(box.LX); + if (root != -1) + { + type |= EB_YMIN_ZMAX; + table.Insert(EI_YMIN_ZMAX, + GetXInterp(root, box.y0, box.z1), + static_cast(box.y0), + static_cast(box.z1)); + } + + // ymax-zmin edge + root = mXMerge[box.y1][box.z0]->GetZeroBase(box.LX); + if (root != -1) + { + type |= EB_YMAX_ZMIN; + table.Insert(EI_YMAX_ZMIN, + GetXInterp(root, box.y1, box.z0), + static_cast(box.y1), + static_cast(box.z0)); + } + + // ymax-zmax edge + root = mXMerge[box.y1][box.z1]->GetZeroBase(box.LX); + if (root != -1) + { + type |= EB_YMAX_ZMAX; + table.Insert(EI_YMAX_ZMAX, + GetXInterp(root, box.y1, box.z1), + static_cast(box.y1), + static_cast(box.z1)); + } + } + + // Edge extraction for single boxes (1x1x1). + void GetXMinEdgesS(OctBox const& box, unsigned int type, VETable& table) + { + unsigned int faceType = 0; + if (type & EB_XMIN_YMIN) + { + faceType |= 0x01; + } + if (type & EB_XMIN_YMAX) + { + faceType |= 0x02; + } + if (type & EB_XMIN_ZMIN) + { + faceType |= 0x04; + } + if (type & EB_XMIN_ZMAX) + { + faceType |= 0x08; + } + + switch (faceType) + { + case 0: + return; + case 3: + table.InsertEdge(EI_XMIN_YMIN, EI_XMIN_YMAX); + break; + case 5: + table.InsertEdge(EI_XMIN_YMIN, EI_XMIN_ZMIN); + break; + case 6: + table.InsertEdge(EI_XMIN_YMAX, EI_XMIN_ZMIN); + break; + case 9: + table.InsertEdge(EI_XMIN_YMIN, EI_XMIN_ZMAX); + break; + case 10: + table.InsertEdge(EI_XMIN_YMAX, EI_XMIN_ZMAX); + break; + case 12: + table.InsertEdge(EI_XMIN_ZMIN, EI_XMIN_ZMAX); + break; + case 15: + { + // Four vertices, one per edge, need to disambiguate. + int i = box.x0 + mSize * (box.y0 + mSize * box.z0); + // F(x,y,z) + int64_t f00 = static_cast(mInputVoxels[i]); + i += mSize; + // F(x,y+1,z) + int64_t f10 = static_cast(mInputVoxels[i]); + i += mSizeSqr; + // F(x,y+1,z+1) + int64_t f11 = static_cast(mInputVoxels[i]); + i -= mSize; + // F(x,y,z+1) + int64_t f01 = static_cast(mInputVoxels[i]); + int64_t det = f00 * f11 - f01 * f10; + + if (det > 0) + { + // Disjoint hyperbolic segments, pair , . + table.InsertEdge(EI_XMIN_YMIN, EI_XMIN_ZMIN); + table.InsertEdge(EI_XMIN_YMAX, EI_XMIN_ZMAX); + } + else if (det < 0) + { + // Disjoint hyperbolic segments, pair , . + table.InsertEdge(EI_XMIN_YMIN, EI_XMIN_ZMAX); + table.InsertEdge(EI_XMIN_YMAX, EI_XMIN_ZMIN); + } + else + { + // Plus-sign configuration, add branch point to + // tessellation. + table.Insert(FI_XMIN, + table.GetVertex(EI_XMIN_ZMIN)[0], + table.GetVertex(EI_XMIN_ZMIN)[1], + table.GetVertex(EI_XMIN_YMIN)[2]); + + // Add edges sharing the branch point. + table.InsertEdge(EI_XMIN_YMIN, FI_XMIN); + table.InsertEdge(EI_XMIN_YMAX, FI_XMIN); + table.InsertEdge(EI_XMIN_ZMIN, FI_XMIN); + table.InsertEdge(EI_XMIN_ZMAX, FI_XMIN); + } + break; + } + default: + LogError("The level value cannot be an exact integer."); + } + } + + void GetXMaxEdgesS(OctBox const& box, unsigned int type, VETable& table) + { + unsigned int faceType = 0; + if (type & EB_XMAX_YMIN) + { + faceType |= 0x01; + } + if (type & EB_XMAX_YMAX) + { + faceType |= 0x02; + } + if (type & EB_XMAX_ZMIN) + { + faceType |= 0x04; + } + if (type & EB_XMAX_ZMAX) + { + faceType |= 0x08; + } + + switch (faceType) + { + case 0: + return; + case 3: + table.InsertEdge(EI_XMAX_YMIN, EI_XMAX_YMAX); + break; + case 5: + table.InsertEdge(EI_XMAX_YMIN, EI_XMAX_ZMIN); + break; + case 6: + table.InsertEdge(EI_XMAX_YMAX, EI_XMAX_ZMIN); + break; + case 9: + table.InsertEdge(EI_XMAX_YMIN, EI_XMAX_ZMAX); + break; + case 10: + table.InsertEdge(EI_XMAX_YMAX, EI_XMAX_ZMAX); + break; + case 12: + table.InsertEdge(EI_XMAX_ZMIN, EI_XMAX_ZMAX); + break; + case 15: + { + // Four vertices, one per edge, need to disambiguate. + int i = box.x1 + mSize * (box.y0 + mSize * box.z0); + // F(x,y,z) + int64_t f00 = static_cast(mInputVoxels[i]); + i += mSize; + // F(x,y+1,z) + int64_t f10 = static_cast(mInputVoxels[i]); + i += mSizeSqr; + // F(x,y+1,z+1) + int64_t f11 = static_cast(mInputVoxels[i]); + i -= mSize; + // F(x,y,z+1) + int64_t f01 = static_cast(mInputVoxels[i]); + int64_t det = f00 * f11 - f01 * f10; + + if (det > 0) + { + // Disjoint hyperbolic segments, pair , . + table.InsertEdge(EI_XMAX_YMIN, EI_XMAX_ZMIN); + table.InsertEdge(EI_XMAX_YMAX, EI_XMAX_ZMAX); + } + else if (det < 0) + { + // Disjoint hyperbolic segments, pair , . + table.InsertEdge(EI_XMAX_YMIN, EI_XMAX_ZMAX); + table.InsertEdge(EI_XMAX_YMAX, EI_XMAX_ZMIN); + } + else + { + // Plus-sign configuration, add branch point to + // tessellation. + table.Insert(FI_XMAX, + table.GetVertex(EI_XMAX_ZMIN)[0], + table.GetVertex(EI_XMAX_ZMIN)[1], + table.GetVertex(EI_XMAX_YMIN)[2]); + + // Add edges sharing the branch point. + table.InsertEdge(EI_XMAX_YMIN, FI_XMAX); + table.InsertEdge(EI_XMAX_YMAX, FI_XMAX); + table.InsertEdge(EI_XMAX_ZMIN, FI_XMAX); + table.InsertEdge(EI_XMAX_ZMAX, FI_XMAX); + } + break; + } + default: + LogError("The level value cannot be an exact integer."); + } + } + + void GetYMinEdgesS(OctBox const& box, unsigned int type, VETable& table) + { + unsigned int faceType = 0; + if (type & EB_XMIN_YMIN) + { + faceType |= 0x01; + } + if (type & EB_XMAX_YMIN) + { + faceType |= 0x02; + } + if (type & EB_YMIN_ZMIN) + { + faceType |= 0x04; + } + if (type & EB_YMIN_ZMAX) + { + faceType |= 0x08; + } + + switch (faceType) + { + case 0: + return; + case 3: + table.InsertEdge(EI_XMIN_YMIN, EI_XMAX_YMIN); + break; + case 5: + table.InsertEdge(EI_XMIN_YMIN, EI_YMIN_ZMIN); + break; + case 6: + table.InsertEdge(EI_XMAX_YMIN, EI_YMIN_ZMIN); + break; + case 9: + table.InsertEdge(EI_XMIN_YMIN, EI_YMIN_ZMAX); + break; + case 10: + table.InsertEdge(EI_XMAX_YMIN, EI_YMIN_ZMAX); + break; + case 12: + table.InsertEdge(EI_YMIN_ZMIN, EI_YMIN_ZMAX); + break; + case 15: + { + // Four vertices, one per edge, need to disambiguate. + int i = box.x0 + mSize * (box.y0 + mSize * box.z0); + // F(x,y,z) + int64_t f00 = static_cast(mInputVoxels[i]); + ++i; + // F(x+1,y,z) + int64_t f10 = static_cast(mInputVoxels[i]); + i += mSizeSqr; + // F(x+1,y,z+1) + int64_t f11 = static_cast(mInputVoxels[i]); + --i; + // F(x,y,z+1) + int64_t f01 = static_cast(mInputVoxels[i]); + int64_t det = f00 * f11 - f01 * f10; + + if (det > 0) + { + // Disjoint hyperbolic segments, pair , . + table.InsertEdge(EI_XMIN_YMIN, EI_YMIN_ZMIN); + table.InsertEdge(EI_XMAX_YMIN, EI_YMIN_ZMAX); + } + else if (det < 0) + { + // Disjoint hyperbolic segments, pair , . + table.InsertEdge(EI_XMIN_YMIN, EI_YMIN_ZMAX); + table.InsertEdge(EI_XMAX_YMIN, EI_YMIN_ZMIN); + } + else + { + // Plus-sign configuration, add branch point to + // tessellation. + table.Insert(FI_YMIN, + table.GetVertex(EI_YMIN_ZMIN)[0], + table.GetVertex(EI_XMIN_YMIN)[1], + table.GetVertex(EI_XMIN_YMIN)[2]); + + // Add edges sharing the branch point. + table.InsertEdge(EI_XMIN_YMIN, FI_YMIN); + table.InsertEdge(EI_XMAX_YMIN, FI_YMIN); + table.InsertEdge(EI_YMIN_ZMIN, FI_YMIN); + table.InsertEdge(EI_YMIN_ZMAX, FI_YMIN); + } + break; + } + default: + LogError("The level value cannot be an exact integer."); + } + } + + void GetYMaxEdgesS(OctBox const& box, unsigned int type, VETable& table) + { + unsigned int faceType = 0; + if (type & EB_XMIN_YMAX) + { + faceType |= 0x01; + } + if (type & EB_XMAX_YMAX) + { + faceType |= 0x02; + } + if (type & EB_YMAX_ZMIN) + { + faceType |= 0x04; + } + if (type & EB_YMAX_ZMAX) + { + faceType |= 0x08; + } + + switch (faceType) + { + case 0: + return; + case 3: + table.InsertEdge(EI_XMIN_YMAX, EI_XMAX_YMAX); + break; + case 5: + table.InsertEdge(EI_XMIN_YMAX, EI_YMAX_ZMIN); + break; + case 6: + table.InsertEdge(EI_XMAX_YMAX, EI_YMAX_ZMIN); + break; + case 9: + table.InsertEdge(EI_XMIN_YMAX, EI_YMAX_ZMAX); + break; + case 10: + table.InsertEdge(EI_XMAX_YMAX, EI_YMAX_ZMAX); + break; + case 12: + table.InsertEdge(EI_YMAX_ZMIN, EI_YMAX_ZMAX); + break; + case 15: + { + // Four vertices, one per edge, need to disambiguate. + int i = box.x0 + mSize * (box.y1 + mSize * box.z0); + // F(x,y,z) + int64_t f00 = static_cast(mInputVoxels[i]); + ++i; + // F(x+1,y,z) + int64_t f10 = static_cast(mInputVoxels[i]); + i += mSizeSqr; + // F(x+1,y,z+1) + int64_t f11 = static_cast(mInputVoxels[i]); + --i; + // F(x,y,z+1) + int64_t f01 = static_cast(mInputVoxels[i]); + int64_t det = f00 * f11 - f01 * f10; + + if (det > 0) + { + // Disjoint hyperbolic segments, pair , . + table.InsertEdge(EI_XMIN_YMAX, EI_YMAX_ZMIN); + table.InsertEdge(EI_XMAX_YMAX, EI_YMAX_ZMAX); + } + else if (det < 0) + { + // Disjoint hyperbolic segments, pair , . + table.InsertEdge(EI_XMIN_YMAX, EI_YMAX_ZMAX); + table.InsertEdge(EI_XMAX_YMAX, EI_YMAX_ZMIN); + } + else + { + // Plus-sign configuration, add branch point to + // tessellation. + table.Insert(FI_YMAX, + table.GetVertex(EI_YMAX_ZMIN)[0], + table.GetVertex(EI_XMIN_YMAX)[1], + table.GetVertex(EI_XMIN_YMAX)[2]); + + // Add edges sharing the branch point. + table.InsertEdge(EI_XMIN_YMAX, FI_YMAX); + table.InsertEdge(EI_XMAX_YMAX, FI_YMAX); + table.InsertEdge(EI_YMAX_ZMIN, FI_YMAX); + table.InsertEdge(EI_YMAX_ZMAX, FI_YMAX); + } + break; + } + default: + LogError("The level value cannot be an exact integer."); + } + } + + void GetZMinEdgesS(OctBox const& box, unsigned int type, VETable& table) + { + unsigned int faceType = 0; + if (type & EB_XMIN_ZMIN) + { + faceType |= 0x01; + } + if (type & EB_XMAX_ZMIN) + { + faceType |= 0x02; + } + if (type & EB_YMIN_ZMIN) + { + faceType |= 0x04; + } + if (type & EB_YMAX_ZMIN) + { + faceType |= 0x08; + } + + switch (faceType) + { + case 0: + return; + case 3: + table.InsertEdge(EI_XMIN_ZMIN, EI_XMAX_ZMIN); + break; + case 5: + table.InsertEdge(EI_XMIN_ZMIN, EI_YMIN_ZMIN); + break; + case 6: + table.InsertEdge(EI_XMAX_ZMIN, EI_YMIN_ZMIN); + break; + case 9: + table.InsertEdge(EI_XMIN_ZMIN, EI_YMAX_ZMIN); + break; + case 10: + table.InsertEdge(EI_XMAX_ZMIN, EI_YMAX_ZMIN); + break; + case 12: + table.InsertEdge(EI_YMIN_ZMIN, EI_YMAX_ZMIN); + break; + case 15: + { + // Four vertices, one per edge, need to disambiguate. + int i = box.x0 + mSize * (box.y0 + mSize * box.z0); + // F(x,y,z) + int64_t f00 = static_cast(mInputVoxels[i]); + ++i; + // F(x+1,y,z) + int64_t f10 = static_cast(mInputVoxels[i]); + i += mSize; + // F(x+1,y+1,z) + int64_t f11 = static_cast(mInputVoxels[i]); + --i; + // F(x,y+1,z) + int64_t f01 = static_cast(mInputVoxels[i]); + int64_t det = f00 * f11 - f01 * f10; + + if (det > 0) + { + // Disjoint hyperbolic segments, pair , . + table.InsertEdge(EI_XMIN_ZMIN, EI_YMIN_ZMIN); + table.InsertEdge(EI_XMAX_ZMIN, EI_YMAX_ZMIN); + } + else if (det < 0) + { + // Disjoint hyperbolic segments, pair , . + table.InsertEdge(EI_XMIN_ZMIN, EI_YMAX_ZMIN); + table.InsertEdge(EI_XMAX_ZMIN, EI_YMIN_ZMIN); + } + else + { + // Plus-sign configuration, add branch point to + // tessellation. + table.Insert(FI_ZMIN, + table.GetVertex(EI_YMIN_ZMIN)[0], + table.GetVertex(EI_XMIN_ZMIN)[1], + table.GetVertex(EI_XMIN_ZMIN)[2]); + + // Add edges sharing the branch point. + table.InsertEdge(EI_XMIN_ZMIN, FI_ZMIN); + table.InsertEdge(EI_XMAX_ZMIN, FI_ZMIN); + table.InsertEdge(EI_YMIN_ZMIN, FI_ZMIN); + table.InsertEdge(EI_YMAX_ZMIN, FI_ZMIN); + } + break; + } + default: + LogError("The level value cannot be an exact integer."); + } + } + + void GetZMaxEdgesS(const OctBox& box, unsigned int type, VETable& table) + { + unsigned int faceType = 0; + if (type & EB_XMIN_ZMAX) + { + faceType |= 0x01; + } + if (type & EB_XMAX_ZMAX) + { + faceType |= 0x02; + } + if (type & EB_YMIN_ZMAX) + { + faceType |= 0x04; + } + if (type & EB_YMAX_ZMAX) + { + faceType |= 0x08; + } + + switch (faceType) + { + case 0: + return; + case 3: + table.InsertEdge(EI_XMIN_ZMAX, EI_XMAX_ZMAX); + break; + case 5: + table.InsertEdge(EI_XMIN_ZMAX, EI_YMIN_ZMAX); + break; + case 6: + table.InsertEdge(EI_XMAX_ZMAX, EI_YMIN_ZMAX); + break; + case 9: + table.InsertEdge(EI_XMIN_ZMAX, EI_YMAX_ZMAX); + break; + case 10: + table.InsertEdge(EI_XMAX_ZMAX, EI_YMAX_ZMAX); + break; + case 12: + table.InsertEdge(EI_YMIN_ZMAX, EI_YMAX_ZMAX); + break; + case 15: + { + // Four vertices, one per edge, need to disambiguate. + int i = box.x0 + mSize * (box.y0 + mSize * box.z1); + // F(x,y,z) + int64_t f00 = static_cast(mInputVoxels[i]); + i++; + // F(x+1,y,z) + int64_t f10 = static_cast(mInputVoxels[i]); + i += mSize; + // F(x+1,y+1,z) + int64_t f11 = static_cast(mInputVoxels[i]); + i--; + // F(x,y+1,z) + int64_t f01 = static_cast(mInputVoxels[i]); + int64_t det = f00 * f11 - f01 * f10; + + if (det > 0) + { + // Disjoint hyperbolic segments, pair , . + table.InsertEdge(EI_XMIN_ZMAX, EI_YMIN_ZMAX); + table.InsertEdge(EI_XMAX_ZMAX, EI_YMAX_ZMAX); + } + else if (det < 0) + { + // Disjoint hyperbolic segments, pair , . + table.InsertEdge(EI_XMIN_ZMAX, EI_YMAX_ZMAX); + table.InsertEdge(EI_XMAX_ZMAX, EI_YMIN_ZMAX); + } + else + { + // Plus-sign configuration, add branch point to + // tessellation. + table.Insert(FI_ZMAX, + table.GetVertex(EI_YMIN_ZMAX)[0], + table.GetVertex(EI_XMIN_ZMAX)[1], + table.GetVertex(EI_XMIN_ZMAX)[2]); + + // Add edges sharing the branch point. + table.InsertEdge(EI_XMIN_ZMAX, FI_ZMAX); + table.InsertEdge(EI_XMAX_ZMAX, FI_ZMAX); + table.InsertEdge(EI_YMIN_ZMAX, FI_ZMAX); + table.InsertEdge(EI_YMAX_ZMAX, FI_ZMAX); + } + break; + } + default: + LogError("The level value cannot be an exact integer."); + } + } + + // Edge extraction for merged boxes. + class Sort0 + { + public: + bool operator()(Vertex const& arg0, Vertex const& arg1) const + { + if (arg0[0] < arg1[0]) + { + return true; + } + if (arg0[0] > arg1[0]) + { + return false; + } + if (arg0[1] < arg1[1]) + { + return true; + } + if (arg0[1] > arg1[1]) + { + return false; + } + return arg0[2] < arg1[2]; + } + }; + + class Sort1 + { + public: + bool operator()(Vertex const& arg0, Vertex const& arg1) const + { + if (arg0[2] < arg1[2]) + { + return true; + } + if (arg0[2] > arg1[2]) + { + return false; + } + if (arg0[0] < arg1[0]) + { + return true; + } + if (arg0[0] > arg1[0]) + { + return false; + } + return arg0[1] < arg1[1]; + } + }; + + class Sort2 + { + public: + bool operator()(Vertex const& arg0, Vertex const& arg1) const + { + if (arg0[1] < arg1[1]) + { + return true; + } + if (arg0[1] > arg1[1]) + { + return false; + } + if (arg0[2] < arg1[2]) + { + return true; + } + if (arg0[2] > arg1[2]) + { + return false; + } + return arg0[0] < arg1[0]; + } + }; + + void GetZMinEdgesM(OctBox const& box, unsigned int type, VETable& table) + { + unsigned int faceType = 0; + if (type & EB_XMIN_ZMIN) + { + faceType |= 0x01; + } + if (type & EB_XMAX_ZMIN) + { + faceType |= 0x02; + } + if (type & EB_YMIN_ZMIN) + { + faceType |= 0x04; + } + if (type & EB_YMAX_ZMIN) + { + faceType |= 0x08; + } + + int end0 = 0, end1 = 0; + switch (faceType) + { + case 0: + return; + case 3: + end0 = EI_XMIN_ZMIN; + end1 = EI_XMAX_ZMIN; + break; + case 5: + end0 = EI_XMIN_ZMIN; + end1 = EI_YMIN_ZMIN; + break; + case 6: + end0 = EI_YMIN_ZMIN; + end1 = EI_XMAX_ZMIN; + break; + case 9: + end0 = EI_XMIN_ZMIN; + end1 = EI_YMAX_ZMIN; + break; + case 10: + end0 = EI_YMAX_ZMIN; + end1 = EI_XMAX_ZMIN; + break; + case 12: + end0 = EI_YMIN_ZMIN; + end1 = EI_YMAX_ZMIN; + break; + default: + LogError("The level value cannot be an exact integer."); + } + + std::set vSet; + + for (int x = box.x0 + 1; x < box.x1; ++x) + { + auto const& merge = mYMerge[x][box.z0]; + if (merge->IsZeroEdge(box.LY) || merge->HasZeroSubedge(box.LY)) + { + int root = merge->GetZeroBase(box.LY); + vSet.insert(Vertex{ + static_cast(x), + GetYInterp(x, root, box.z0), + static_cast(box.z0) }); + } + } + + for (int y = box.y0 + 1; y < box.y1; ++y) + { + auto const& merge = mXMerge[y][box.z0]; + if (merge->IsZeroEdge(box.LX) || merge->HasZeroSubedge(box.LX)) + { + int root = merge->GetZeroBase(box.LX); + vSet.insert(Vertex{ + GetXInterp(root, y, box.z0), + static_cast(y), + static_cast(box.z0) }); + } + } + + // Add subdivision. + if (vSet.size() == 0) + { + table.InsertEdge(end0, end1); + return; + } + + Real v0x = std::floor((*vSet.begin())[0]); + Real v1x = std::floor((*vSet.rbegin())[0]); + Real e0x = std::floor(table.GetVertex(end0)[0]); + Real e1x = std::floor(table.GetVertex(end1)[0]); + if (e1x <= v0x && v1x <= e0x) + { + std::swap(end0, end1); + } + + // Add vertices. + int v0 = table.GetNumVertices(), v1 = v0; + for (auto const& position : vSet) + { + table.Insert(position); + } + + // Add edges. + table.InsertEdge(end0, v1); + ++v1; + int const imax = static_cast(vSet.size()); + for (int i = 1; i < imax; ++i, ++v0, ++v1) + { + table.InsertEdge(v0, v1); + } + table.InsertEdge(v0, end1); + } + + void GetZMaxEdgesM(OctBox const& box, unsigned int type, VETable& table) + { + unsigned int faceType = 0; + if (type & EB_XMIN_ZMAX) + { + faceType |= 0x01; + } + if (type & EB_XMAX_ZMAX) + { + faceType |= 0x02; + } + if (type & EB_YMIN_ZMAX) + { + faceType |= 0x04; + } + if (type & EB_YMAX_ZMAX) + { + faceType |= 0x08; + } + + int end0 = 0, end1 = 0; + switch (faceType) + { + case 0: + return; + case 3: + end0 = EI_XMIN_ZMAX; + end1 = EI_XMAX_ZMAX; + break; + case 5: + end0 = EI_XMIN_ZMAX; + end1 = EI_YMIN_ZMAX; + break; + case 6: + end0 = EI_YMIN_ZMAX; + end1 = EI_XMAX_ZMAX; + break; + case 9: + end0 = EI_XMIN_ZMAX; + end1 = EI_YMAX_ZMAX; + break; + case 10: + end0 = EI_YMAX_ZMAX; + end1 = EI_XMAX_ZMAX; + break; + case 12: + end0 = EI_YMIN_ZMAX; + end1 = EI_YMAX_ZMAX; + break; + default: + LogError("The level value cannot be an exact integer."); + } + + std::set vSet; + + for (int x = box.x0 + 1; x < box.x1; ++x) + { + auto const& merge = mYMerge[x][box.z1]; + if (merge->IsZeroEdge(box.LY) || merge->HasZeroSubedge(box.LY)) + { + int root = merge->GetZeroBase(box.LY); + vSet.insert(Vertex{ + static_cast(x), + GetYInterp(x, root, box.z1), + static_cast(box.z1) }); + } + } + + for (int y = box.y0 + 1; y < box.y1; ++y) + { + auto const& merge = mXMerge[y][box.z1]; + if (merge->IsZeroEdge(box.LX) || merge->HasZeroSubedge(box.LX)) + { + int root = merge->GetZeroBase(box.LX); + vSet.insert(Vertex{ + GetXInterp(root, y, box.z1), + static_cast(y), + static_cast(box.z1) }); + } + } + + // Add subdivision. + int v0 = table.GetNumVertices(), v1 = v0; + if (vSet.size() == 0) + { + table.InsertEdge(end0, end1); + return; + } + + Real v0x = std::floor((*vSet.begin())[0]); + Real v1x = std::floor((*vSet.rbegin())[0]); + Real e0x = std::floor(table.GetVertex(end0)[0]); + Real e1x = std::floor(table.GetVertex(end1)[0]); + if (e1x <= v0x && v1x <= e0x) + { + std::swap(end0, end1); + } + + // Add vertices. + for (auto const& position : vSet) + { + table.Insert(position); + } + + // Add edges. + table.InsertEdge(end0, v1); + ++v1; + int const imax = static_cast(vSet.size()); + for (int i = 1; i < imax; ++i, ++v0, ++v1) + { + table.InsertEdge(v0, v1); + } + table.InsertEdge(v0, end1); + } + + void GetYMinEdgesM(OctBox const& box, unsigned int type, VETable& table) + { + unsigned int faceType = 0; + if (type & EB_XMIN_YMIN) + { + faceType |= 0x01; + } + if (type & EB_XMAX_YMIN) + { + faceType |= 0x02; + } + if (type & EB_YMIN_ZMIN) + { + faceType |= 0x04; + } + if (type & EB_YMIN_ZMAX) + { + faceType |= 0x08; + } + + int end0 = 0, end1 = 0; + switch (faceType) + { + case 0: + return; + case 3: + end0 = EI_XMIN_YMIN; + end1 = EI_XMAX_YMIN; + break; + case 5: + end0 = EI_XMIN_YMIN; + end1 = EI_YMIN_ZMIN; + break; + case 6: + end0 = EI_YMIN_ZMIN; + end1 = EI_XMAX_YMIN; + break; + case 9: + end0 = EI_XMIN_YMIN; + end1 = EI_YMIN_ZMAX; + break; + case 10: + end0 = EI_YMIN_ZMAX; + end1 = EI_XMAX_YMIN; + break; + case 12: + end0 = EI_YMIN_ZMIN; + end1 = EI_YMIN_ZMAX; + break; + default: + LogError("The level value cannot be an exact integer."); + } + + std::set vSet; + + for (int x = box.x0 + 1; x < box.x1; ++x) + { + auto const& merge = mZMerge[x][box.y0]; + if (merge->IsZeroEdge(box.LZ) || merge->HasZeroSubedge(box.LZ)) + { + int root = merge->GetZeroBase(box.LZ); + vSet.insert(Vertex{ + static_cast(x), + static_cast(box.y0), + GetZInterp(x, box.y0, root) }); + } + } + + for (int z = box.z0 + 1; z < box.z1; ++z) + { + auto const& merge = mXMerge[box.y0][z]; + if (merge->IsZeroEdge(box.LX) || merge->HasZeroSubedge(box.LX)) + { + int root = merge->GetZeroBase(box.LX); + vSet.insert(Vertex{ + GetXInterp(root, box.y0, z), + static_cast(box.y0), + static_cast(z) }); + } + } + + // Add subdivision. + int v0 = table.GetNumVertices(), v1 = v0; + if (vSet.size() == 0) + { + table.InsertEdge(end0, end1); + return; + } + + Real v0z = std::floor((*vSet.begin())[2]); + Real v1z = std::floor((*vSet.rbegin())[2]); + Real e0z = std::floor(table.GetVertex(end0)[2]); + Real e1z = std::floor(table.GetVertex(end1)[2]); + if (e1z <= v0z && v1z <= e0z) + { + std::swap(end0, end1); + } + + // Add vertices. + for (auto const& position : vSet) + { + table.Insert(position); + } + + // Add edges. + table.InsertEdge(end0, v1); + ++v1; + int const imax = static_cast(vSet.size()); + for (int i = 1; i < imax; ++i, ++v0, ++v1) + { + table.InsertEdge(v0, v1); + } + table.InsertEdge(v0, end1); + } + + void GetYMaxEdgesM(OctBox const& box, unsigned int type, VETable& table) + { + unsigned int faceType = 0; + if (type & EB_XMIN_YMAX) + { + faceType |= 0x01; + } + if (type & EB_XMAX_YMAX) + { + faceType |= 0x02; + } + if (type & EB_YMAX_ZMIN) + { + faceType |= 0x04; + } + if (type & EB_YMAX_ZMAX) + { + faceType |= 0x08; + } + + int end0 = 0, end1 = 0; + switch (faceType) + { + case 0: + return; + case 3: + end0 = EI_XMIN_YMAX; + end1 = EI_XMAX_YMAX; + break; + case 5: + end0 = EI_XMIN_YMAX; + end1 = EI_YMAX_ZMIN; + break; + case 6: + end0 = EI_YMAX_ZMIN; + end1 = EI_XMAX_YMAX; + break; + case 9: + end0 = EI_XMIN_YMAX; + end1 = EI_YMAX_ZMAX; + break; + case 10: + end0 = EI_YMAX_ZMAX; + end1 = EI_XMAX_YMAX; + break; + case 12: + end0 = EI_YMAX_ZMIN; + end1 = EI_YMAX_ZMAX; + break; + default: + LogError("The level value cannot be an exact integer."); + } + + std::set vSet; + + for (int x = box.x0 + 1; x < box.x1; ++x) + { + auto const& merge = mZMerge[x][box.y1]; + if (merge->IsZeroEdge(box.LZ) || merge->HasZeroSubedge(box.LZ)) + { + int root = merge->GetZeroBase(box.LZ); + vSet.insert(Vertex{ + static_cast(x), + static_cast(box.y1), + GetZInterp(x, box.y1, root) }); + } + } + + for (int z = box.z0 + 1; z < box.z1; ++z) + { + auto const& merge = mXMerge[box.y1][z]; + if (merge->IsZeroEdge(box.LX) || merge->HasZeroSubedge(box.LX)) + { + int root = merge->GetZeroBase(box.LX); + vSet.insert(Vertex{ + GetXInterp(root, box.y1, z), + static_cast(box.y1), + static_cast(z) }); + } + } + + // Add subdivision. + if (vSet.size() == 0) + { + table.InsertEdge(end0, end1); + return; + } + + Real v0z = std::floor((*vSet.begin())[2]); + Real v1z = std::floor((*vSet.rbegin())[2]); + Real e0z = std::floor(table.GetVertex(end0)[2]); + Real e1z = std::floor(table.GetVertex(end1)[2]); + if (e1z <= v0z && v1z <= e0z) + { + std::swap(end0, end1); + } + + // Add vertices. + int v0 = table.GetNumVertices(), v1 = v0; + for (auto const& position : vSet) + { + table.Insert(position); + } + + // Add edges. + table.InsertEdge(end0, v1); + ++v1; + int const imax = static_cast(vSet.size()); + for (int i = 1; i < imax; ++i, ++v0, ++v1) + { + table.InsertEdge(v0, v1); + } + table.InsertEdge(v0, end1); + } + + void GetXMinEdgesM(OctBox const& box, unsigned int type, VETable& table) + { + unsigned int faceType = 0; + if (type & EB_XMIN_YMIN) + { + faceType |= 0x01; + } + if (type & EB_XMIN_YMAX) + { + faceType |= 0x02; + } + if (type & EB_XMIN_ZMIN) + { + faceType |= 0x04; + } + if (type & EB_XMIN_ZMAX) + { + faceType |= 0x08; + } + + int end0 = 0, end1 = 0; + switch (faceType) + { + case 0: + return; + case 3: + end0 = EI_XMIN_YMIN; + end1 = EI_XMIN_YMAX; + break; + case 5: + end0 = EI_XMIN_YMIN; + end1 = EI_XMIN_ZMIN; + break; + case 6: + end0 = EI_XMIN_ZMIN; + end1 = EI_XMIN_YMAX; + break; + case 9: + end0 = EI_XMIN_YMIN; + end1 = EI_XMIN_ZMAX; + break; + case 10: + end0 = EI_XMIN_ZMAX; + end1 = EI_XMIN_YMAX; + break; + case 12: + end0 = EI_XMIN_ZMIN; + end1 = EI_XMIN_ZMAX; + break; + default: + LogError("The level value cannot be an exact integer."); + } + + std::set vSet; + + for (int z = box.z0 + 1; z < box.z1; ++z) + { + auto const& merge = mYMerge[box.x0][z]; + if (merge->IsZeroEdge(box.LY) || merge->HasZeroSubedge(box.LY)) + { + int root = merge->GetZeroBase(box.LY); + vSet.insert(Vertex{ + static_cast(box.x0), + GetYInterp(box.x0, root, z), + static_cast(z) }); + } + } + + for (int y = box.y0 + 1; y < box.y1; ++y) + { + auto const& merge = mZMerge[box.x0][y]; + if (merge->IsZeroEdge(box.LZ) || merge->HasZeroSubedge(box.LZ)) + { + int root = merge->GetZeroBase(box.LZ); + vSet.insert(Vertex{ + static_cast(box.x0), + static_cast(y), + GetZInterp(box.x0, y, root) }); + } + } + + // Add subdivision. + int v0 = table.GetNumVertices(), v1 = v0; + if (vSet.size() == 0) + { + table.InsertEdge(end0, end1); + return; + } + + Real v0y = std::floor((*vSet.begin())[1]); + Real v1y = std::floor((*vSet.rbegin())[1]); + Real e0y = std::floor(table.GetVertex(end0)[1]); + Real e1y = std::floor(table.GetVertex(end1)[1]); + if (e1y <= v0y && v1y <= e0y) + { + std::swap(end0, end1); + } + + // Add vertices. + for (auto const& position : vSet) + { + table.Insert(position); + } + + // Add edges. + table.InsertEdge(end0, v1); + ++v1; + int const imax = static_cast(vSet.size()); + for (int i = 1; i < imax; ++i, ++v0, ++v1) + { + table.InsertEdge(v0, v1); + } + table.InsertEdge(v0, end1); + } + + void GetXMaxEdgesM(OctBox const& box, unsigned int type, VETable& table) + { + unsigned int faceType = 0; + if (type & EB_XMAX_YMIN) + { + faceType |= 0x01; + } + if (type & EB_XMAX_YMAX) + { + faceType |= 0x02; + } + if (type & EB_XMAX_ZMIN) + { + faceType |= 0x04; + } + if (type & EB_XMAX_ZMAX) + { + faceType |= 0x08; + } + + int end0 = 0, end1 = 0; + switch (faceType) + { + case 0: + return; + case 3: + end0 = EI_XMAX_YMIN; + end1 = EI_XMAX_YMAX; + break; + case 5: + end0 = EI_XMAX_YMIN; + end1 = EI_XMAX_ZMIN; + break; + case 6: + end0 = EI_XMAX_ZMIN; + end1 = EI_XMAX_YMAX; + break; + case 9: + end0 = EI_XMAX_YMIN; + end1 = EI_XMAX_ZMAX; + break; + case 10: + end0 = EI_XMAX_ZMAX; + end1 = EI_XMAX_YMAX; + break; + case 12: + end0 = EI_XMAX_ZMIN; + end1 = EI_XMAX_ZMAX; + break; + default: + LogError("The level value cannot be an exact integer."); + } + + std::set vSet; + + for (int z = box.z0 + 1; z < box.z1; ++z) + { + auto const& merge = mYMerge[box.x1][z]; + if (merge->IsZeroEdge(box.LY) || merge->HasZeroSubedge(box.LY)) + { + int root = merge->GetZeroBase(box.LY); + vSet.insert(Vertex{ + static_cast(box.x1), + GetYInterp(box.x1, root, z), + static_cast(z) }); + } + } + + for (int y = box.y0 + 1; y < box.y1; y++) + { + auto const& merge = mZMerge[box.x1][y]; + if (merge->IsZeroEdge(box.LZ) || merge->HasZeroSubedge(box.LZ)) + { + int root = merge->GetZeroBase(box.LZ); + vSet.insert(Vertex{ + static_cast(box.x1), + static_cast(y), + GetZInterp(box.x1, y, root) }); + } + } + + // Add subdivision. + if (vSet.size() == 0) + { + table.InsertEdge(end0, end1); + return; + } + + Real v0y = std::floor((*vSet.begin())[1]); + Real v1y = std::floor((*vSet.rbegin())[1]); + Real e0y = std::floor(table.GetVertex(end0)[1]); + Real e1y = std::floor(table.GetVertex(end1)[1]); + if (e1y <= v0y && v1y <= e0y) + { + std::swap(end0, end1); + } + + // Add vertices. + int v0 = table.GetNumVertices(), v1 = v0; + for (auto const& position : vSet) + { + table.Insert(position); + } + + // Add edges. + table.InsertEdge(end0, v1); + ++v1; + int const imax = static_cast(vSet.size()); + for (int i = 1; i < imax; ++i, ++v0, ++v1) + { + table.InsertEdge(v0, v1); + } + table.InsertEdge(v0, end1); + } + + // Support for normal vector calculations. + Vertex GetGradient(Vertex const& position) const + { + Vertex vzero = { (Real)0, (Real)0, (Real)0 }; + int x = static_cast(position[0]); + if (x < 0 || x >= mTwoPowerN) + { + return vzero; + } + + int y = static_cast(position[1]); + if (y < 0 || y >= mTwoPowerN) + { + return vzero; + } + + int z = static_cast(position[2]); + if (z < 0 || z >= mTwoPowerN) + { + return vzero; + } + + int i000 = x + mSize * (y + mSize * z); + int i100 = i000 + 1; + int i010 = i000 + mSize; + int i110 = i100 + mSize; + int i001 = i000 + mSizeSqr; + int i101 = i100 + mSizeSqr; + int i011 = i010 + mSizeSqr; + int i111 = i110 + mSizeSqr; + Real f000 = static_cast(mInputVoxels[i000]); + Real f100 = static_cast(mInputVoxels[i100]); + Real f010 = static_cast(mInputVoxels[i010]); + Real f110 = static_cast(mInputVoxels[i110]); + Real f001 = static_cast(mInputVoxels[i001]); + Real f101 = static_cast(mInputVoxels[i101]); + Real f011 = static_cast(mInputVoxels[i011]); + Real f111 = static_cast(mInputVoxels[i111]); + + Real fx = position[0] - static_cast(x); + Real fy = position[1] - static_cast(y); + Real fz = position[2] - static_cast(z); + Real oneMinusX = (Real)1 - fx; + Real oneMinusY = (Real)1 - fy; + Real oneMinusZ = (Real)1 - fz; + Real tmp0, tmp1; + Vertex gradient; + + tmp0 = oneMinusY * (f100 - f000) + fy * (f110 - f010); + tmp1 = oneMinusY * (f101 - f001) + fy * (f111 - f011); + gradient[0] = oneMinusZ * tmp0 + fz * tmp1; + + tmp0 = oneMinusX * (f010 - f000) + fx * (f110 - f100); + tmp1 = oneMinusX * (f011 - f001) + fx * (f111 - f101); + gradient[1] = oneMinusZ * tmp0 + fz * tmp1; + + tmp0 = oneMinusX * (f001 - f000) + fx * (f101 - f100); + tmp1 = oneMinusX * (f011 - f010) + fx * (f111 - f110); + gradient[2] = oneMinusY * tmp0 + oneMinusY * tmp1; + + return gradient; + } + + enum + { + EI_XMIN_YMIN = 0, + EI_XMIN_YMAX = 1, + EI_XMAX_YMIN = 2, + EI_XMAX_YMAX = 3, + EI_XMIN_ZMIN = 4, + EI_XMIN_ZMAX = 5, + EI_XMAX_ZMIN = 6, + EI_XMAX_ZMAX = 7, + EI_YMIN_ZMIN = 8, + EI_YMIN_ZMAX = 9, + EI_YMAX_ZMIN = 10, + EI_YMAX_ZMAX = 11, + FI_XMIN = 12, + FI_XMAX = 13, + FI_YMIN = 14, + FI_YMAX = 15, + FI_ZMIN = 16, + FI_ZMAX = 17, + I_QUANTITY = 18, + + EB_XMIN_YMIN = 1 << EI_XMIN_YMIN, + EB_XMIN_YMAX = 1 << EI_XMIN_YMAX, + EB_XMAX_YMIN = 1 << EI_XMAX_YMIN, + EB_XMAX_YMAX = 1 << EI_XMAX_YMAX, + EB_XMIN_ZMIN = 1 << EI_XMIN_ZMIN, + EB_XMIN_ZMAX = 1 << EI_XMIN_ZMAX, + EB_XMAX_ZMIN = 1 << EI_XMAX_ZMIN, + EB_XMAX_ZMAX = 1 << EI_XMAX_ZMAX, + EB_YMIN_ZMIN = 1 << EI_YMIN_ZMIN, + EB_YMIN_ZMAX = 1 << EI_YMIN_ZMAX, + EB_YMAX_ZMIN = 1 << EI_YMAX_ZMIN, + EB_YMAX_ZMAX = 1 << EI_YMAX_ZMAX, + FB_XMIN = 1 << FI_XMIN, + FB_XMAX = 1 << FI_XMAX, + FB_YMIN = 1 << FI_YMIN, + FB_YMAX = 1 << FI_YMAX, + FB_ZMIN = 1 << FI_ZMIN, + FB_ZMAX = 1 << FI_ZMAX + }; + + // image data + int mTwoPowerN, mSize, mSizeSqr; + T const* mInputVoxels; + Real mLevel; + + bool mFixBoundary; + + // Trees for linear merging. + Array2> mXMerge, mYMerge, mZMerge; + + // monoboxes + std::vector mBoxes; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/AlignedBox.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/AlignedBox.h new file mode 100644 index 0000000..d0c9f25 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/AlignedBox.h @@ -0,0 +1,107 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The box is aligned with the standard coordinate axes, which allows us to +// represent it using minimum and maximum values along each axis. Some +// algorithms prefer the centered representation that is used for oriented +// boxes. The center is C and the extents are the half-lengths in each +// coordinate-axis direction. + +namespace WwiseGTE +{ + template + class AlignedBox + { + public: + // Construction and destruction. The default constructor sets the + // minimum values to -1 and the maximum values to +1. + AlignedBox() + { + for (int i = 0; i < N; ++i) + { + min[i] = (Real)-1; + max[i] = (Real)+1; + } + } + + // Please ensure that inMin[i] <= inMax[i] for all i. + AlignedBox(Vector const& inMin, Vector const& inMax) + { + for (int i = 0; i < N; ++i) + { + min[i] = inMin[i]; + max[i] = inMax[i]; + } + } + + // Compute the centered representation. NOTE: If you set the minimum + // and maximum values, compute C and extents, and then recompute the + // minimum and maximum values, the numerical round-off errors can lead + // to results different from what you started with. + void GetCenteredForm(Vector& center, Vector& extent) const + { + center = (max + min) * (Real)0.5; + extent = (max - min) * (Real)0.5; + } + + // Public member access. It is required that min[i] <= max[i]. + Vector min, max; + + public: + // Comparisons to support sorted containers. + bool operator==(AlignedBox const& box) const + { + return min == box.min && max == box.max; + } + + bool operator!=(AlignedBox const& box) const + { + return !operator==(box); + } + + bool operator< (AlignedBox const& box) const + { + if (min < box.min) + { + return true; + } + + if (min > box.min) + { + return false; + } + + return max < box.max; + } + + bool operator<=(AlignedBox const& box) const + { + return !box.operator<(*this); + } + + bool operator> (AlignedBox const& box) const + { + return box.operator<(*this); + } + + bool operator>=(AlignedBox const& box) const + { + return !operator<(box); + } + }; + + // Template aliases for convenience. + template + using AlignedBox2 = AlignedBox<2, Real>; + + template + using AlignedBox3 = AlignedBox<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprCircle2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprCircle2.h new file mode 100644 index 0000000..7a22d25 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprCircle2.h @@ -0,0 +1,157 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// Least-squares fit of a circle to a set of points. The algorithms are +// described in Section 5 of +// https://www.geometrictools.com/Documentation/LeastSquaresFitting.pdf +// FitUsingLengths uses the algorithm of Section 5.1. +// FitUsingSquaredLengths uses the algorithm of Section 5.2. + +namespace WwiseGTE +{ + template + class ApprCircle2 + { + public: + // The return value is 'true' when the linear system of the algorithm + // is solvable, 'false' otherwise. If 'false' is returned, the circle + // center and radius are set to zero values. + bool FitUsingSquaredLengths(int numPoints, Vector2 const* points, Circle2& circle) + { + // Compute the average of the data points. + Real const zero(0); + Vector2 A = { zero, zero }; + for (int i = 0; i < numPoints; ++i) + { + A += points[i]; + } + Real invNumPoints = ((Real)1) / static_cast(numPoints); + A *= invNumPoints; + + // Compute the covariance matrix M of the Y[i] = X[i]-A and the + // right-hand side R of the linear system M*(C-A) = R. + Real M00 = zero, M01 = zero, M11 = zero; + Vector2 R = { zero, zero }; + for (int i = 0; i < numPoints; ++i) + { + Vector2 Y = points[i] - A; + Real Y0Y0 = Y[0] * Y[0]; + Real Y0Y1 = Y[0] * Y[1]; + Real Y1Y1 = Y[1] * Y[1]; + M00 += Y0Y0; + M01 += Y0Y1; + M11 += Y1Y1; + R += (Y0Y0 + Y1Y1) * Y; + } + R *= (Real)0.5; + + // Solve the linear system M*(C-A) = R for the center C. + Real det = M00 * M11 - M01 * M01; + if (det != zero) + { + circle.center[0] = A[0] + (M11 * R[0] - M01 * R[1]) / det; + circle.center[1] = A[1] + (M00 * R[1] - M01 * R[0]) / det; + Real rsqr = zero; + for (int i = 0; i < numPoints; ++i) + { + Vector2 delta = points[i] - circle.center; + rsqr += Dot(delta, delta); + } + rsqr *= invNumPoints; + circle.radius = std::sqrt(rsqr); + return true; + } + else + { + circle.center = { zero, zero }; + circle.radius = zero; + return false; + } + } + + // Fit the points using lengths to drive the least-squares algorithm. + // If initialCenterIsAverage is set to 'false', the initial guess for + // the initial circle center is computed as the average of the data + // points. If the data points are clustered along a small arc, the + // algorithm is slow to converge. If initialCenterIsAverage is set to + // 'true', the incoming circle center is used as-is to start the + // iterative algorithm. This approach tends to converge more rapidly + // than when using the average of points but can be much slower than + // FitUsingSquaredLengths. + // + // The value epsilon may be chosen as a positive number for the + // comparison of consecutive estimated circle centers, terminating the + // iterations when the center difference has length less than or equal + // to epsilon. + // + // The return value is the number of iterations used. If is is the + // input maxIterations, you can either accept the result or polish the + // result by calling the function again with initialCenterIsAverage + // set to 'true'. + unsigned int FitUsingLengths(int numPoints, Vector2 const* points, + unsigned int maxIterations, bool initialCenterIsAverage, + Circle2& circle, Real epsilon = (Real)0) + { + // Compute the average of the data points. + Vector2 average = points[0]; + for (int i = 1; i < numPoints; ++i) + { + average += points[i]; + } + Real invNumPoints = ((Real)1) / static_cast(numPoints); + average *= invNumPoints; + + // The initial guess for the center. + if (initialCenterIsAverage) + { + circle.center = average; + } + + Real epsilonSqr = epsilon * epsilon; + unsigned int iteration; + for (iteration = 0; iteration < maxIterations; ++iteration) + { + // Update the iterates. + Vector2 current = circle.center; + + // Compute average L, dL/da, dL/db. + Real lenAverage = (Real)0; + Vector2 derLenAverage = Vector2::Zero(); + for (int i = 0; i < numPoints; ++i) + { + Vector2 diff = points[i] - circle.center; + Real length = Length(diff); + if (length > (Real)0) + { + lenAverage += length; + Real invLength = ((Real)1) / length; + derLenAverage -= invLength * diff; + } + } + lenAverage *= invNumPoints; + derLenAverage *= invNumPoints; + + circle.center = average + lenAverage * derLenAverage; + circle.radius = lenAverage; + + Vector2 diff = circle.center - current; + Real diffSqrLen = Dot(diff, diff); + if (diffSqrLen <= epsilonSqr) + { + break; + } + } + + return ++iteration; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprCone3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprCone3.h new file mode 100644 index 0000000..cf35d62 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprCone3.h @@ -0,0 +1,339 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The cone vertex is V, the unit-length axis direction is U and the +// cone angle is A in (0,pi/2). The cone is defined algebraically by +// those points X for which +// Dot(U,X-V)/Length(X-V) = cos(A) +// This can be written as a quadratic equation +// (V-X)^T * (cos(A)^2 - U * U^T) * (V-X) = 0 +// with the implicit constraint that Dot(U, X-V) > 0 (X is on the +// "positive" cone). Define W = U/cos(A), so Length(W) > 1 and +// F(X;V,W) = (V-X)^T * (I - W * W^T) * (V-X) = 0 +// The nonlinear least squares fitting of points {X[i]}_{i=0}^{n-1} +// computes V and W to minimize the error function +// E(V,W) = sum_{i=0}^{n-1} F(X[i];V,W)^2 +// I recommend using the Gauss-Newton minimizer when your cone points +// are truly nearly a cone; otherwise, try the Levenberg-Marquardt +// minimizer. +// +// The mathematics used in this implementation are found in +// https://www.geometrictools.com/Documentation/LeastSquaresFitting.pdf +// In particular, the details for choosing an initial cone for fitting +// are somewhat complicated. + +namespace WwiseGTE +{ + template + class ApprCone3 + { + public: + ApprCone3() + : + mNumPoints(0), + mPoints(nullptr) + { + // F[i](V,W) = D^T * (I - W * W^T) * D, D = V - X[i], P = (V,W) + mFFunction = [this](GVector const& P, GVector& F) + { + Vector<3, Real> V = { P[0], P[1], P[2] }; + Vector<3, Real> W = { P[3], P[4], P[5] }; + for (int i = 0; i < mNumPoints; ++i) + { + Vector<3, Real> delta = V - mPoints[i]; + Real deltaDotW = Dot(delta, W); + F[i] = Dot(delta, delta) - deltaDotW * deltaDotW; + } + }; + + // dF[i]/dV = 2 * (D - Dot(W, D) * W) + // dF[i]/dW = -2 * Dot(W, D) * D + mJFunction = [this](GVector const& P, GMatrix& J) + { + Vector<3, Real> V = { P[0], P[1], P[2] }; + Vector<3, Real> W = { P[3], P[4], P[5] }; + for (int row = 0; row < mNumPoints; ++row) + { + Vector<3, Real> delta = V - mPoints[row]; + Real deltaDotW = Dot(delta, W); + Vector<3, Real> temp0 = delta - deltaDotW * W; + Vector<3, Real> temp1 = deltaDotW * delta; + for (int col = 0; col < 3; ++col) + { + J(row, col) = (Real)2 * temp0[col]; + J(row, col + 3) = (Real)-2 * temp1[col]; + } + } + }; + } + + // If you want to specify that coneVertex, coneAxis and coneAngle + // are the initial guesses for the minimizer, set the parameter + // useConeInputAsInitialGuess to 'true'. If you want the function + // to compute initial guesses, set that parameter to 'false'. + // A Gauss-Newton minimizer is used to fit a cone using nonlinear + // least-squares. The fitted cone is returned in coneVertex, + // coneAxis and coneAngle. See GaussNewtonMinimizer.h for a + // description of the least-squares algorithm and the parameters + // that it requires. + typename GaussNewtonMinimizer::Result + operator()(int numPoints, Vector<3, Real> const* points, + size_t maxIterations, Real updateLengthTolerance, Real errorDifferenceTolerance, + bool useConeInputAsInitialGuess, + Vector<3, Real>& coneVertex, Vector<3, Real>& coneAxis, Real& coneAngle) + { + mNumPoints = numPoints; + mPoints = points; + GaussNewtonMinimizer minimizer(6, mNumPoints, mFFunction, mJFunction); + + Real coneCosAngle; + if (useConeInputAsInitialGuess) + { + Normalize(coneAxis); + coneCosAngle = std::cos(coneAngle); + } + else + { + ComputeInitialCone(coneVertex, coneAxis, coneCosAngle); + } + + // The initial guess for the cone vertex. + GVector initial(6); + initial[0] = coneVertex[0]; + initial[1] = coneVertex[1]; + initial[2] = coneVertex[2]; + + // The initial guess for the weighted cone axis. + initial[3] = coneAxis[0] / coneCosAngle; + initial[4] = coneAxis[1] / coneCosAngle; + initial[5] = coneAxis[2] / coneCosAngle; + + auto result = minimizer(initial, maxIterations, updateLengthTolerance, + errorDifferenceTolerance); + + // No test is made for result.converged so that we return some + // estimates of the cone. The caller can decide how to respond + // when result.converged is false. + for (int i = 0; i < 3; ++i) + { + coneVertex[i] = result.minLocation[i]; + coneAxis[i] = result.minLocation[i + 3]; + } + + // We know that coneCosAngle will be nonnegative. The std::min + // call guards against rounding errors leading to a number + // slightly larger than 1. The clamping ensures std::acos will + // not return a NaN. + coneCosAngle = std::min((Real)1 / Normalize(coneAxis), (Real)1); + coneAngle = std::acos(coneCosAngle); + + mNumPoints = 0; + mPoints = nullptr; + return result; + } + + // The parameters coneVertex, coneAxis and coneAngle are in/out + // variables. The caller must provide initial guesses for these. + // The function estimates the cone parameters and returns them. See + // GteGaussNewtonMinimizer.h for a description of the least-squares + // algorithm and the parameters that it requires. + typename LevenbergMarquardtMinimizer::Result + operator()(int numPoints, Vector<3, Real> const* points, + size_t maxIterations, Real updateLengthTolerance, Real errorDifferenceTolerance, + Real lambdaFactor, Real lambdaAdjust, size_t maxAdjustments, + bool useConeInputAsInitialGuess, + Vector<3, Real>& coneVertex, Vector<3, Real>& coneAxis, Real& coneAngle) + { + mNumPoints = numPoints; + mPoints = points; + LevenbergMarquardtMinimizer minimizer(6, mNumPoints, mFFunction, mJFunction); + + Real coneCosAngle; + if (useConeInputAsInitialGuess) + { + Normalize(coneAxis); + coneCosAngle = std::cos(coneAngle); + } + else + { + ComputeInitialCone(coneVertex, coneAxis, coneCosAngle); + } + + // The initial guess for the cone vertex. + GVector initial(6); + initial[0] = coneVertex[0]; + initial[1] = coneVertex[1]; + initial[2] = coneVertex[2]; + + // The initial guess for the weighted cone axis. + initial[3] = coneAxis[0] / coneCosAngle; + initial[4] = coneAxis[1] / coneCosAngle; + initial[5] = coneAxis[2] / coneCosAngle; + + auto result = minimizer(initial, maxIterations, updateLengthTolerance, + errorDifferenceTolerance, lambdaFactor, lambdaAdjust, maxAdjustments); + + // No test is made for result.converged so that we return some + // estimates of the cone. The caller can decide how to respond + // when result.converged is false. + for (int i = 0; i < 3; ++i) + { + coneVertex[i] = result.minLocation[i]; + coneAxis[i] = result.minLocation[i + 3]; + } + + // We know that coneCosAngle will be nonnegative. The std::min + // call guards against rounding errors leading to a number + // slightly larger than 1. The clamping ensures std::acos will + // not return a NaN. + coneCosAngle = std::min((Real)1 / Normalize(coneAxis), (Real)1); + coneAngle = std::acos(coneCosAngle); + + mNumPoints = 0; + mPoints = nullptr; + return result; + } + + private: + void ComputeInitialCone(Vector<3, Real>& coneVertex, Vector<3, Real>& coneAxis, Real& coneCosAngle) + { + // Compute the average of the sample points. + Vector<3, Real> center{ (Real)0, (Real)0, (Real)0 }; + Real const invNumPoints = (Real)1 / (Real)mNumPoints; + for (int i = 0; i < mNumPoints; ++i) + { + center += mPoints[i]; + } + center *= invNumPoints; + + // The cone axis is estimated from ZZTZ (see the PDF). + coneAxis = { (Real)0, (Real)0, (Real)0 }; + for (int i = 0; i < mNumPoints; ++i) + { + Vector<3, Real> diff = mPoints[i] - center; + coneAxis += Dot(diff, diff) * diff; + } + coneAxis *= invNumPoints; + Normalize(coneAxis); + + // Compute the averages of powers and products of powers of + // a[i] = Dot(U,X[i]-C) and b[i] = Dot(X[i]-C,X[i]-C). + Real c10 = (Real)0, c20 = (Real)0, c30 = (Real)0, c01 = (Real)0; + Real c02 = (Real)0, c11 = (Real)0, c21 = (Real)0; + for (int i = 0; i < mNumPoints; ++i) + { + Vector<3, Real> diff = mPoints[i] - center; + Real ai = Dot(coneAxis, diff); + Real aisqr = ai * ai; + Real bi = Dot(diff, diff); + c10 += ai; + c20 += aisqr; + c30 += aisqr * ai; + c01 += bi; + c02 += bi * bi; + c11 += ai * bi; + c21 += aisqr * bi; + } + c10 *= invNumPoints; + c20 *= invNumPoints; + c30 *= invNumPoints; + c01 *= invNumPoints; + c02 *= invNumPoints; + c11 *= invNumPoints; + c21 *= invNumPoints; + + // Compute the coefficients of p3(t) and q3(t). + Real e0 = (Real)3 * c10; + Real e1 = (Real)2 * c20 + c01; + Real e2 = c11; + Real e3 = (Real)3 * c20; + Real e4 = c30; + + // Compute the coefficients of g(t). + Real g0 = c11 * c21 - c02 * c30; + Real g1 = c01 * c21 - (Real)3 * c02 * c20 + (Real)2 * (c20 * c21 - c11 * (c30 - c11)); + Real g2 = (Real)3 * (c11 * (c01 - c20) + c10 * (c21 - c02)); + Real g3 = c21 - c02 + c01 * (c01 + c20) + (Real)2 * (c10 * (c30 - c11) - c20 * c20); + Real g4 = c30 - c11 + c10 * (c01 - c20); + + // Compute the roots of g(t) = 0. + std::map rmMap; + RootsPolynomial::SolveQuartic(g0, g1, g2, g3, g4, rmMap); + + // Locate the positive root t that leads to an s = cos(theta) + // in (0,1) and that has minimum least-squares error. In theory, + // there must always be such a root, but floating-point rounding + // errors might lead to no such root. The implementation returns + // the center as the estimate of V and pi/4 as the estimate of + // the angle (s = 1/2). + std::vector> info; + Real s, t; + for (auto const& element : rmMap) + { + t = element.first; + if (t > (Real)0) + { + Real p3 = e2 + t * (e1 + t * (e0 + t)); + if (p3 != (Real)0) + { + Real q3 = e4 + t * (e3 + t * (e0 + t)); + s = q3 / p3; + if ((Real)0 < s && s < (Real)1) + { + Real error(0); + for (int i = 0; i < mNumPoints; ++i) + { + Vector<3, Real> diff = mPoints[i] - center; + Real ai = Dot(coneAxis, diff); + Real bi = Dot(diff, diff); + Real tpai = t + ai; + Real Fi = s * (bi + t * ((Real)2 * ai + t)) - tpai * tpai; + error += Fi * Fi; + } + error *= invNumPoints; + std::array item = { s, t, error }; + info.push_back(item); + } + } + } + } + + Real minError = std::numeric_limits::max(); + std::array minItem = { (Real)0, (Real)0, minError }; + for (auto const& item : info) + { + if (item[2] < minError) + { + minItem = item; + } + } + + if (minItem[2] < std::numeric_limits::max()) + { + // minItem = { minS, minT, minError } + coneVertex = center - minItem[1] * coneAxis; + coneCosAngle = std::sqrt(minItem[0]); + } + else + { + coneVertex = center; + coneCosAngle = (Real)GTE_C_INV_SQRT_2; + } + } + + int mNumPoints; + Vector<3, Real> const* mPoints; + std::function const&, GVector&)> mFFunction; + std::function const&, GMatrix&)> mJFunction; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprCylinder3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprCylinder3.h new file mode 100644 index 0000000..094bf3d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprCylinder3.h @@ -0,0 +1,466 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// The algorithm for least-squares fitting of a point set by a cylinder is +// described in +// https://www.geometrictools.com/Documentation/CylinderFitting.pdf +// This document shows how to compute the cylinder radius r and the cylinder +// axis as a line C+t*W with origin C, unit-length direction W, and any +// real-valued t. The implementation here adds one addition step. It +// projects the point set onto the cylinder axis, computes the bounding +// t-interval [tmin,tmax] for the projections, and sets the cylinder center +// to C + 0.5*(tmin+tmax)*W and the cylinder height to tmax-tmin. + +namespace WwiseGTE +{ + template + class ApprCylinder3 + { + public: + // Search the hemisphere for a minimum, choose numThetaSamples and + // numPhiSamples to be positive (and preferably large). These are + // used to generate a hemispherical grid of samples to be evaluated + // to find the cylinder axis-direction W. If the grid samples is + // quite large and the number of points to be fitted is large, you + // most likely will want to run multithreaded. Set numThreads to 0 + // to run single-threaded in the main process. Set numThreads > 0 to + // run multithreaded. If either of numThetaSamples or numPhiSamples + // is zero, the operator() sets the cylinder origin and axis to the + // zero vectors, the radius and height to zero, and returns + // std::numeric_limits::max(). + ApprCylinder3(unsigned int numThreads, unsigned int numThetaSamples, unsigned int numPhiSamples) + : + mConstructorType(FIT_BY_HEMISPHERE_SEARCH), + mNumThreads(numThreads), + mNumThetaSamples(numThetaSamples), + mNumPhiSamples(numPhiSamples), + mEigenIndex(0), + mInvNumPoints((Real)0) + { + mCylinderAxis = { (Real)0, (Real)0, (Real)0 }; + } + + // Choose one of the eigenvectors for the covariance matrix as the + // cylinder axis direction. If eigenIndex is 0, the eigenvector + // associated with the smallest eigenvalue is chosen. If eigenIndex + // is 2, the eigenvector associated with the largest eigenvalue is + // chosen. If eigenIndex is 1, the eigenvector associated with the + // median eigenvalue is chosen; keep in mind that this could be the + // minimum or maximum eigenvalue if the eigenspace has dimension 2 + // or 3. If eigenIndex is 3 or larger, the operator() sets the + // cylinder origin and axis to the zero vectors, the radius and height + // to zero, and returns std::numeric_limits::max(). + ApprCylinder3(unsigned int eigenIndex) + : + mConstructorType(FIT_USING_COVARIANCE_EIGENVECTOR), + mNumThreads(0), + mNumThetaSamples(0), + mNumPhiSamples(0), + mEigenIndex(eigenIndex), + mInvNumPoints((Real)0) + { + mCylinderAxis = { (Real)0, (Real)0, (Real)0 }; + } + + // Choose the cylinder axis. If cylinderAxis is not the zero vector, + // the constructor will normalize it. If cylinderAxis is the zero + // vector, the operator() sets the cylinder origin and axis to the + // zero vectors, the radius and height to zero, and returns + // std::numeric_limits::max(). + ApprCylinder3(Vector3 const& cylinderAxis) + : + mConstructorType(FIT_USING_SPECIFIED_AXIS), + mNumThreads(0), + mNumThetaSamples(0), + mNumPhiSamples(0), + mEigenIndex(0), + mCylinderAxis(cylinderAxis), + mInvNumPoints((Real)0) + { + Normalize(mCylinderAxis, true); + } + + // The algorithm must estimate 6 parameters, so the number of points + // must be at least 6 but preferably larger. The returned value is + // the root-mean-square of the least-squares error. If numPoints is + // less than 6 or if points is a null pointer, the operator() sets the + // cylinder origin and axis to the zero vectors, the radius and height + // to zero, and returns std::numeric_limits::max(). + Real operator()(unsigned int numPoints, Vector3 const* points, Cylinder3& cylinder) + { + mX.clear(); + mInvNumPoints = (Real)0; + cylinder.axis.origin = Vector3::Zero(); + cylinder.axis.direction = Vector3::Zero(); + cylinder.radius = (Real)0; + cylinder.height = (Real)0; + + // Validate the input parameters. + if (numPoints < 6 || !points) + { + return std::numeric_limits::max(); + } + + Vector3 average; + Preprocess(numPoints, points, average); + + // Fit the points based on which constructor the caller used. The + // direction is either estimated or selected directly or + // indirectly by the caller. The center and squared radius are + // estimated. + Vector3 minPC, minW; + Real minRSqr, minError; + + if (mConstructorType == FIT_BY_HEMISPHERE_SEARCH) + { + // Validate the relevant internal parameters. + if (mNumThetaSamples == 0 || mNumPhiSamples == 0) + { + return std::numeric_limits::max(); + } + + // Search the hemisphere for the vector that leads to minimum + // error and use it for the cylinder axis. + if (mNumThreads == 0) + { + // Execute the algorithm in the main process. + minError = ComputeSingleThreaded(minPC, minW, minRSqr); + } + else + { + // Execute the algorithm in multiple threads. + minError = ComputeMultiThreaded(minPC, minW, minRSqr); + } + } + else if (mConstructorType == FIT_USING_COVARIANCE_EIGENVECTOR) + { + // Validate the relevant internal parameters. + if (mEigenIndex >= 3) + { + return std::numeric_limits::max(); + } + + // Use the eigenvector corresponding to the mEigenIndex of the + // eigenvectors of the covariance matrix as the cylinder axis + // direction. The eigenvectors are sorted from smallest + // eigenvalue (mEigenIndex = 0) to largest eigenvalue + // (mEigenIndex = 2). + minError = ComputeUsingCovariance(minPC, minW, minRSqr); + } + else // mConstructorType == FIT_USING_SPECIFIED_AXIS + { + // Validate the relevant internal parameters. + if (mCylinderAxis == Vector3::Zero()) + { + return std::numeric_limits::max(); + } + + minError = ComputeUsingDirection(minPC, minW, minRSqr); + } + + // Translate back to the original space by the average of the + // points. + cylinder.axis.origin = minPC + average; + cylinder.axis.direction = minW; + + // Compute the cylinder radius. + cylinder.radius = std::sqrt(minRSqr); + + // Project the points onto the cylinder axis and choose the + // cylinder center and cylinder height as described in the + // comments at the top of this header file. + Real tmin = (Real)0, tmax = (Real)0; + for (unsigned int i = 0; i < numPoints; ++i) + { + Real t = Dot(cylinder.axis.direction, points[i] - cylinder.axis.origin); + tmin = std::min(t, tmin); + tmax = std::max(t, tmax); + } + + cylinder.axis.origin += ((tmin + tmax) * (Real)0.5) * cylinder.axis.direction; + cylinder.height = tmax - tmin; + return minError; + } + + private: + enum ConstructorType + { + FIT_BY_HEMISPHERE_SEARCH, + FIT_USING_COVARIANCE_EIGENVECTOR, + FIT_USING_SPECIFIED_AXIS + }; + + void Preprocess(unsigned int numPoints, Vector3 const* points, Vector3& average) + { + mX.resize(numPoints); + mInvNumPoints = (Real)1 / (Real)numPoints; + + // Copy the points and translate by the average for numerical + // robustness. + average.MakeZero(); + for (unsigned int i = 0; i < numPoints; ++i) + { + average += points[i]; + } + average *= mInvNumPoints; + for (unsigned int i = 0; i < numPoints; ++i) + { + mX[i] = points[i] - average; + } + + Vector<6, Real> zero{ (Real)0 }; + std::vector> products(mX.size(), zero); + mMu = zero; + for (size_t i = 0; i < mX.size(); ++i) + { + products[i][0] = mX[i][0] * mX[i][0]; + products[i][1] = mX[i][0] * mX[i][1]; + products[i][2] = mX[i][0] * mX[i][2]; + products[i][3] = mX[i][1] * mX[i][1]; + products[i][4] = mX[i][1] * mX[i][2]; + products[i][5] = mX[i][2] * mX[i][2]; + mMu[0] += products[i][0]; + mMu[1] += (Real)2 * products[i][1]; + mMu[2] += (Real)2 * products[i][2]; + mMu[3] += products[i][3]; + mMu[4] += (Real)2 * products[i][4]; + mMu[5] += products[i][5]; + } + mMu *= mInvNumPoints; + + mF0.MakeZero(); + mF1.MakeZero(); + mF2.MakeZero(); + for (size_t i = 0; i < mX.size(); ++i) + { + Vector<6, Real> delta; + delta[0] = products[i][0] - mMu[0]; + delta[1] = (Real)2 * products[i][1] - mMu[1]; + delta[2] = (Real)2 * products[i][2] - mMu[2]; + delta[3] = products[i][3] - mMu[3]; + delta[4] = (Real)2 * products[i][4] - mMu[4]; + delta[5] = products[i][5] - mMu[5]; + mF0(0, 0) += products[i][0]; + mF0(0, 1) += products[i][1]; + mF0(0, 2) += products[i][2]; + mF0(1, 1) += products[i][3]; + mF0(1, 2) += products[i][4]; + mF0(2, 2) += products[i][5]; + mF1 += OuterProduct(mX[i], delta); + mF2 += OuterProduct(delta, delta); + } + mF0 *= mInvNumPoints; + mF0(1, 0) = mF0(0, 1); + mF0(2, 0) = mF0(0, 2); + mF0(2, 1) = mF0(1, 2); + mF1 *= mInvNumPoints; + mF2 *= mInvNumPoints; + } + + Real ComputeUsingDirection(Vector3& minPC, Vector3& minW, Real& minRSqr) + { + minW = mCylinderAxis; + return G(minW, minPC, minRSqr); + } + + Real ComputeUsingCovariance(Vector3& minPC, Vector3& minW, Real& minRSqr) + { + Matrix3x3 covar = Matrix3x3::Zero(); + for (auto const& X : mX) + { + covar += OuterProduct(X, X); + } + covar *= mInvNumPoints; + std::array eval; + std::array, 3> evec; + SymmetricEigensolver3x3()( + covar(0, 0), covar(0, 1), covar(0, 2), covar(1, 1), covar(1, 2), covar(2, 2), + true, +1, eval, evec); + minW = evec[mEigenIndex]; + return G(minW, minPC, minRSqr); + } + + Real ComputeSingleThreaded(Vector3& minPC, Vector3& minW, Real& minRSqr) + { + Real const iMultiplier = (Real)GTE_C_TWO_PI / (Real)mNumThetaSamples; + Real const jMultiplier = (Real)GTE_C_HALF_PI / (Real)mNumPhiSamples; + + // Handle the north pole (0,0,1) separately. + minW = { (Real)0, (Real)0, (Real)1 }; + Real minError = G(minW, minPC, minRSqr); + + for (unsigned int j = 1; j <= mNumPhiSamples; ++j) + { + Real phi = jMultiplier * static_cast(j); // in [0,pi/2] + Real csphi = std::cos(phi); + Real snphi = std::sin(phi); + for (unsigned int i = 0; i < mNumThetaSamples; ++i) + { + Real theta = iMultiplier * static_cast(i); // in [0,2*pi) + Real cstheta = std::cos(theta); + Real sntheta = std::sin(theta); + Vector3 W{ cstheta * snphi, sntheta * snphi, csphi }; + Vector3 PC; + Real rsqr; + Real error = G(W, PC, rsqr); + if (error < minError) + { + minError = error; + minRSqr = rsqr; + minW = W; + minPC = PC; + } + } + } + + return minError; + } + + Real ComputeMultiThreaded(Vector3& minPC, Vector3& minW, Real& minRSqr) + { + Real const iMultiplier = (Real)GTE_C_TWO_PI / (Real)mNumThetaSamples; + Real const jMultiplier = (Real)GTE_C_HALF_PI / (Real)mNumPhiSamples; + + // Handle the north pole (0,0,1) separately. + minW = { (Real)0, (Real)0, (Real)1 }; + Real minError = G(minW, minPC, minRSqr); + + struct Local + { + Real error; + Real rsqr; + Vector3 W; + Vector3 PC; + unsigned int jmin; + unsigned int jmax; + }; + + std::vector local(mNumThreads); + unsigned int numPhiSamplesPerThread = mNumPhiSamples / mNumThreads; + for (unsigned int t = 0; t < mNumThreads; ++t) + { + local[t].error = std::numeric_limits::max(); + local[t].rsqr = (Real)0; + local[t].W = Vector3::Zero(); + local[t].PC = Vector3::Zero(); + local[t].jmin = numPhiSamplesPerThread * t; + local[t].jmax = numPhiSamplesPerThread * (t + 1); + } + local[mNumThreads - 1].jmax = mNumPhiSamples + 1; + + std::vector process(mNumThreads); + for (unsigned int t = 0; t < mNumThreads; ++t) + { + process[t] = std::thread + ( + [this, t, iMultiplier, jMultiplier, &local]() + { + for (unsigned int j = local[t].jmin; j < local[t].jmax; ++j) + { + // phi in [0,pi/2] + Real phi = jMultiplier * static_cast(j); + Real csphi = std::cos(phi); + Real snphi = std::sin(phi); + for (unsigned int i = 0; i < mNumThetaSamples; ++i) + { + // theta in [0,2*pi) + Real theta = iMultiplier * static_cast(i); + Real cstheta = std::cos(theta); + Real sntheta = std::sin(theta); + Vector3 W{ cstheta * snphi, sntheta * snphi, csphi }; + Vector3 PC; + Real rsqr; + Real error = G(W, PC, rsqr); + if (error < local[t].error) + { + local[t].error = error; + local[t].rsqr = rsqr; + local[t].W = W; + local[t].PC = PC; + } + } + } + } + ); + } + + for (unsigned int t = 0; t < mNumThreads; ++t) + { + process[t].join(); + + if (local[t].error < minError) + { + minError = local[t].error; + minRSqr = local[t].rsqr; + minW = local[t].W; + minPC = local[t].PC; + } + } + + return minError; + } + + Real G(Vector3 const& W, Vector3& PC, Real& rsqr) + { + Matrix3x3 P = Matrix3x3::Identity() - OuterProduct(W, W); + Matrix3x3 S + { + (Real)0, -W[2], W[1], + W[2], (Real)0, -W[0], + -W[1], W[0], (Real)0 + }; + + Matrix<3, 3, Real> A = P * mF0 * P; + Matrix<3, 3, Real> hatA = -(S * A * S); + Matrix<3, 3, Real> hatAA = hatA * A; + Real trace = Trace(hatAA); + Matrix<3, 3, Real> Q = hatA / trace; + Vector<6, Real> pVec{ P(0, 0), P(0, 1), P(0, 2), P(1, 1), P(1, 2), P(2, 2) }; + Vector<3, Real> alpha = mF1 * pVec; + Vector<3, Real> beta = Q * alpha; + Real G = (Dot(pVec, mF2 * pVec) - (Real)4 * Dot(alpha, beta) + (Real)4 * Dot(beta, mF0 * beta)) / (Real)mX.size(); + + PC = beta; + rsqr = Dot(pVec, mMu) + Dot(PC, PC); + return G; + } + + ConstructorType mConstructorType; + + // Parameters for the hemisphere-search constructor. + unsigned int mNumThreads; + unsigned int mNumThetaSamples; + unsigned int mNumPhiSamples; + + // Parameters for the eigenvector-index constructor. + unsigned int mEigenIndex; + + // Parameters for the specified-axis constructor. + Vector3 mCylinderAxis; + + // A copy of the input points but translated by their average for + // numerical robustness. + std::vector> mX; + Real mInvNumPoints; + + // Preprocessed information that depends only on the sample points. + // This allows precomputed summations so that G(...) can be evaluated + // extremely fast. + Vector<6, Real> mMu; + Matrix<3, 3, Real> mF0; + Matrix<3, 6, Real> mF1; + Matrix<6, 6, Real> mF2; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprEllipse2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprEllipse2.h new file mode 100644 index 0000000..0ac18d1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprEllipse2.h @@ -0,0 +1,128 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +// The ellipse in general form is X^t A X + B^t X + C = 0 where A is a +// positive definite 2x2 matrix, B is a 2x1 vector, C is a scalar, and X is +// a 2x1 vector X. Completing the square, (X-U)^t A (X-U) = U^t A U - C +// where U = -0.5 A^{-1} B. Define M = A/(U^t A U - C). The ellipse is +// (X-U)^t M (X-U) = 1. Factor M = R^t D R where R is orthonormal and D is +// diagonal with positive diagonal terms. The ellipse in factored form is +// (X-U)^t R^t D^t R (X-U) = 1. Find the least squares fit of a set of N +// points P[0] through P[N-1]. The return value is the least-squares energy +// function at (U,R,D). + +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class ApprEllipse2 + { + public: + Real operator()(int numPoints, Vector2 const* points, + Vector2& center, Matrix2x2& rotate, Real diagonal[2]) const + { + // Energy function is E : R^5 -> R where + // V = (V0, V1, V2, V3, V4) + // = (D[0], D[1], U.x, U.y, atan2(R(0,1),R(0,0))). + std::function energy = + [numPoints, points](Real const* input) + { + return Energy(numPoints, points, input); + }; + + MinimizeN minimizer(5, energy, 8, 8, 32); + + // The initial guess for the minimizer is based on an oriented box + // that contains the points. + OrientedBox2 box; + GetContainer(numPoints, points, box); + center = box.center; + for (int i = 0; i < 2; ++i) + { + rotate.SetRow(i, box.axis[i]); + diagonal[i] = box.extent[i]; + } + + Real angle = std::atan2(rotate(0, 1), rotate(0, 0)); + Real e0 = + diagonal[0] * std::fabs(rotate(0, 0)) + + diagonal[1] * std::fabs(rotate(1, 0)); + Real e1 = + diagonal[0] * std::fabs(rotate(0, 1)) + + diagonal[1] * std::fabs(rotate(1, 1)); + + Real v0[5] = + { + (Real)0.5 * diagonal[0], + (Real)0.5 * diagonal[1], + center[0] - e0, + center[1] - e1, + -(Real)GTE_C_PI + }; + + Real v1[5] = + { + (Real)2 * diagonal[0], + (Real)2 * diagonal[1], + center[0] + e0, + center[1] + e1, + (Real)GTE_C_PI + }; + + Real vInitial[5] = + { + diagonal[0], + diagonal[1], + center[0], + center[1], + angle + }; + + Real vMin[5], error; + minimizer.GetMinimum(v0, v1, vInitial, vMin, error); + + diagonal[0] = vMin[0]; + diagonal[1] = vMin[1]; + center[0] = vMin[2]; + center[1] = vMin[3]; + MakeRotation(-vMin[4], rotate); + + return error; + } + + private: + static Real Energy(int numPoints, Vector2 const* points, Real const* input) + { + // Build rotation matrix. + Matrix2x2 rotate; + MakeRotation(-input[4], rotate); + + Ellipse2 ellipse(Vector2::Zero(), { Vector2::Unit(0), + Vector2::Unit(1) }, { input[0], input[1] }); + + // Transform the points to the coordinate system of center C and + // columns of rotation R. + DCPQuery, Ellipse2> peQuery; + Real energy = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector2 diff = points[i] - Vector2{ input[2], input[3] }; + Vector2 prod = rotate * diff; + Real dist = peQuery(prod, ellipse).distance; + energy += dist; + } + + return energy; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprEllipseByArcs.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprEllipseByArcs.h new file mode 100644 index 0000000..f1fac16 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprEllipseByArcs.h @@ -0,0 +1,119 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The ellipse is (x/a)^2 + (y/b)^2 = 1, but only the portion in the first +// quadrant (x >= 0 and y >= 0) is approximated. Generate numArcs >= 2 arcs +// by constructing points corresponding to the weighted averages of the +// curvatures at the ellipse points (a,0) and (0,b). The returned input point +// array has numArcs+1 elements and the returned input center and radius +// arrays each have numArc elements. The arc associated with points[i] and +// points[i+1] has center centers[i] and radius radii[i]. The algorithm +// is described in +// https://www.geometrictools.com/Documentation/ApproximateEllipse.pdf + +namespace WwiseGTE +{ + // The function returns 'true' when the approximation succeeded, in which + // case the output arrays are nonempty. If the 'numArcs' is smaller than + // 2 or a == b or one of the calls to Circumscribe fails, the function + // returns 'false'. + + template + bool ApproximateEllipseByArcs(Real a, Real b, int numArcs, + std::vector>& points, std::vector>& centers, + std::vector& radii) + { + if (numArcs < 2 || a == b) + { + // At least 2 arcs are required. The ellipse cannot already be a + // circle. + points.clear(); + centers.clear(); + radii.clear(); + return false; + } + + points.resize(numArcs + 1); + centers.resize(numArcs); + radii.resize(numArcs); + + // Compute intermediate ellipse quantities. + Real a2 = a * a, b2 = b * b, ab = a * b; + Real invB2mA2 = (Real)1 / (b2 - a2); + + // Compute the endpoints of the ellipse in the first quadrant. The + // points are generated in counterclockwise order. + points[0] = { a, (Real)0 }; + points[numArcs] = { (Real)0, b }; + + // Compute the curvature at the endpoints. These are used when + // computing the arcs. + Real curv0 = a / b2; + Real curv1 = b / a2; + + // Select the ellipse points based on curvature properties. + Real invNumArcs = (Real)1 / numArcs; + for (int i = 1; i < numArcs; ++i) + { + // The curvature at a new point is a weighted average of curvature + // at the endpoints. + Real weight1 = static_cast(i) * invNumArcs; + Real weight0 = (Real)1 - weight1; + Real curv = weight0 * curv0 + weight1 * curv1; + + // Compute point having this curvature. + Real tmp = std::pow(ab / curv, (Real)2 / (Real)3); + points[i][0] = a * std::sqrt(std::fabs((tmp - a2) * invB2mA2)); + points[i][1] = b * std::sqrt(std::fabs((tmp - b2) * invB2mA2)); + } + + // Compute the arc at (a,0). + Circle2 circle; + Vector2 const& p0 = points[0]; + Vector2 const& p1 = points[1]; + if (!Circumscribe(Vector2{ p1[0], -p1[1] }, p0, p1, circle)) + { + // This should not happen for the arc-fitting algorithm. + points.clear(); + centers.clear(); + radii.clear(); + return false; + } + centers[0] = circle.center; + radii[0] = circle.radius; + + // Compute arc at (0,b). + int last = numArcs - 1; + Vector2 const& pNm1 = points[last]; + Vector2 const& pN = points[numArcs]; + if (!Circumscribe(Vector2{ -pNm1[0], pNm1[1] }, pN, pNm1, circle)) + { + // This should not happen for the arc-fitting algorithm. + points.clear(); + centers.clear(); + radii.clear(); + return false; + } + + centers[last] = circle.center; + radii[last] = circle.radius; + + // Compute arcs at intermediate points between (a,0) and (0,b). + for (int iM = 0, i = 1, iP = 2; i < last; ++iM, ++i, ++iP) + { + Circumscribe(points[iM], points[i], points[iP], circle); + centers[i] = circle.center; + radii[i] = circle.radius; + } + return true; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprEllipsoid3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprEllipsoid3.h new file mode 100644 index 0000000..9c852e6 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprEllipsoid3.h @@ -0,0 +1,204 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +// The ellipsoid in general form is X^t A X + B^t X + C = 0 where A is a +// positive definite 3x3 matrix, B is a 3x1 vector, C is a scalar, and X is a +// 3x1 vector. Completing the square, (X-U)^t A (X-U) = U^t A U - C where +// U = -0.5 A^{-1} B. Define M = A/(U^t A U - C). The ellipsoid is +// (X-U)^t M (X-U) = 1. Factor M = R^t D R where R is orthonormal and D is +// diagonal with positive diagonal terms. The ellipsoid in factored form is +// (X-U)^t R^t D^t R (X-U) = 1. Find the least squares fit of a set of N +// points P[0] through P[N-1]. The error return value is the least-squares +// energy function at (U,R,D). + +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class ApprEllipsoid3 + { + public: + Real operator()(int numPoints, Vector3 const* points, + Vector3& center, Matrix3x3& rotate, Real diagonal[3]) const + { + // Energy function is E : R^9 -> R where + // V = (V0,V1,V2,V3,V4,V5,V6,V7,V8) + // = (D[0],D[1],D[2],U[0],U,y,U[2],A0,A1,A2). + std::function energy = + [numPoints, points](Real const* input) + { + return Energy(numPoints, points, input); + }; + + MinimizeN minimizer(9, energy, 8, 8, 32); + + // The initial guess for the minimizer is based on an oriented box + // that contains the points. + OrientedBox3 box; + GetContainer(numPoints, points, box); + center = box.center; + for (int i = 0; i < 3; ++i) + { + rotate.SetRow(i, box.axis[i]); + diagonal[i] = box.extent[i]; + } + + Real angle[3]; + MatrixToAngles(rotate, angle); + + Real extent[3] = + { + diagonal[0] * std::fabs(rotate(0, 0)) + + diagonal[1] * std::fabs(rotate(0, 1)) + + diagonal[2] * std::fabs(rotate(0, 2)), + + diagonal[0] * std::fabs(rotate(1, 0)) + + diagonal[1] * std::fabs(rotate(1, 1)) + + diagonal[2] * std::fabs(rotate(1, 2)), + + diagonal[0] * std::fabs(rotate(2, 0)) + + diagonal[1] * std::fabs(rotate(2, 1)) + + diagonal[2] * std::fabs(rotate(2, 2)) + }; + + Real v0[9] = + { + (Real)0.5 * diagonal[0], + (Real)0.5 * diagonal[1], + (Real)0.5 * diagonal[2], + center[0] - extent[0], + center[1] - extent[1], + center[2] - extent[2], + -(Real)GTE_C_PI, + (Real)0, + (Real)0 + }; + + Real v1[9] = + { + (Real)2 * diagonal[0], + (Real)2 * diagonal[1], + (Real)2 * diagonal[2], + center[0] + extent[0], + center[1] + extent[1], + center[2] + extent[2], + (Real)GTE_C_PI, + (Real)GTE_C_PI, + (Real)GTE_C_PI + }; + + Real vInitial[9] = + { + diagonal[0], + diagonal[1], + diagonal[2], + center[0], + center[1], + center[2], + angle[0], + angle[1], + angle[2] + }; + + Real vMin[9], error; + minimizer.GetMinimum(v0, v1, vInitial, vMin, error); + + diagonal[0] = vMin[0]; + diagonal[1] = vMin[1]; + diagonal[2] = vMin[2]; + center[0] = vMin[3]; + center[1] = vMin[4]; + center[2] = vMin[5]; + AnglesToMatrix(&vMin[6], rotate); + + return error; + } + + private: + static void MatrixToAngles(Matrix3x3 const& rotate, Real angle[3]) + { + // rotation axis = (cos(a0)sin(a1),sin(a0)sin(a1),cos(a1)) + // a0 in [-pi,pi], a1 in [0,pi], a2 in [0,pi] + + Real const zero = (Real)0; + Real const one = (Real)1; + AxisAngle<3, Real> aa = Rotation<3, Real>(rotate); + + if (-one < aa.axis[2]) + { + if (aa.axis[2] < one) + { + angle[0] = std::atan2(aa.axis[1], aa.axis[0]); + angle[1] = std::acos(aa.axis[2]); + } + else + { + angle[0] = zero; + angle[1] = zero; + } + } + else + { + angle[0] = zero; + angle[1] = (Real)GTE_C_PI; + } + } + + static void AnglesToMatrix(Real const angle[3], Matrix3x3& rotate) + { + // rotation axis = (cos(a0)sin(a1),sin(a0)sin(a1),cos(a1)) + // a0 in [-pi,pi], a1 in [0,pi], a2 in [0,pi] + + Real cs0 = std::cos(angle[0]); + Real sn0 = std::sin(angle[0]); + Real cs1 = std::cos(angle[1]); + Real sn1 = std::sin(angle[1]); + AxisAngle<3, Real> aa; + aa.axis = { cs0 * sn1, sn0 * sn1, cs1 }; + aa.angle = angle[2]; + rotate = Rotation<3, Real>(aa); + } + + static Real Energy(int numPoints, Vector3 const* points, Real const* input) + { + // Build rotation matrix. + Matrix3x3 rotate; + AnglesToMatrix(&input[6], rotate); + + // Uniformly scale the extents to keep reasonable floating point values + // in the distance calculations. + Real maxValue = std::max(std::max(input[0], input[1]), input[2]); + Real invMax = (Real)1 / maxValue; + Ellipsoid3 ellipsoid(Vector3::Zero(), { Vector3::Unit(0), + Vector3::Unit(1), Vector3::Unit(2) }, { invMax * input[0], + invMax * input[1], invMax * input[2] }); + + // Transform the points to the coordinate system of center C and columns + // of rotation R. + DCPQuery, Ellipsoid3> peQuery; + Real energy = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - + Vector3{ input[3], input[4], input[5] }; + + Vector3 prod = invMax * (diff * rotate); + Real dist = peQuery(prod, ellipsoid).distance; + energy += maxValue * dist; + } + + return energy; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprGaussian2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprGaussian2.h new file mode 100644 index 0000000..406861f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprGaussian2.h @@ -0,0 +1,128 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +// Fit points with a Gaussian distribution. The center is the mean of the +// points, the axes are the eigenvectors of the covariance matrix and the +// extents are the eigenvalues of the covariance matrix and are returned in +// increasing order. An oriented box is used to store the mean, axes and +// extents. + +namespace WwiseGTE +{ + template + class ApprGaussian2 : public ApprQuery> + { + public: + // Initialize the model parameters to zero. + ApprGaussian2() + { + mParameters.center = Vector2::Zero(); + mParameters.axis[0] = Vector2::Zero(); + mParameters.axis[1] = Vector2::Zero(); + mParameters.extent = Vector2::Zero(); + } + + // Basic fitting algorithm. See ApprQuery.h for the various Fit(...) + // functions that you can call. + virtual bool FitIndexed( + size_t numPoints, Vector2 const* points, + size_t numIndices, int const* indices) override + { + if (this->ValidIndices(numPoints, points, numIndices, indices)) + { + // Compute the mean of the points. + Vector2 mean = Vector2::Zero(); + int const* currentIndex = indices; + for (size_t i = 0; i < numIndices; ++i) + { + mean += points[*currentIndex++]; + } + Real invSize = (Real)1 / (Real)numIndices; + mean *= invSize; + + if (std::isfinite(mean[0]) && std::isfinite(mean[1])) + { + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar11 = (Real)0; + currentIndex = indices; + for (size_t i = 0; i < numIndices; ++i) + { + Vector2 diff = points[*currentIndex++] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar11 += diff[1] * diff[1]; + } + covar00 *= invSize; + covar01 *= invSize; + covar11 *= invSize; + + // Solve the eigensystem. + SymmetricEigensolver2x2 es; + std::array eval; + std::array, 2> evec; + es(covar00, covar01, covar11, +1, eval, evec); + mParameters.center = mean; + mParameters.axis[0] = evec[0]; + mParameters.axis[1] = evec[1]; + mParameters.extent = eval; + return true; + } + } + + mParameters.center = Vector2::Zero(); + mParameters.axis[0] = Vector2::Zero(); + mParameters.axis[1] = Vector2::Zero(); + mParameters.extent = Vector2::Zero(); + return false; + } + + // Get the parameters for the best fit. + OrientedBox2 const& GetParameters() const + { + return mParameters; + } + + virtual size_t GetMinimumRequired() const override + { + return 2; + } + + virtual Real Error(Vector2 const& point) const override + { + Vector2 diff = point - mParameters.center; + Real error = (Real)0; + for (int i = 0; i < 2; ++i) + { + if (mParameters.extent[i] > (Real)0) + { + Real ratio = Dot(diff, mParameters.axis[i]) / mParameters.extent[i]; + error += ratio * ratio; + } + } + return error; + } + + virtual void CopyParameters(ApprQuery> const* input) override + { + auto source = dynamic_cast(input); + if (source) + { + *this = *source; + } + } + + private: + OrientedBox2 mParameters; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprGaussian3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprGaussian3.h new file mode 100644 index 0000000..b65c92c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprGaussian3.h @@ -0,0 +1,139 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +// Fit points with a Gaussian distribution. The center is the mean of the +// points, the axes are the eigenvectors of the covariance matrix and the +// extents are the eigenvalues of the covariance matrix and are returned in +// increasing order. An oriented box is used to store the mean, axes and +// extents. + +namespace WwiseGTE +{ + template + class ApprGaussian3 : public ApprQuery> + { + public: + // Initialize the model parameters to zero. + ApprGaussian3() + { + mParameters.center = Vector3::Zero(); + mParameters.axis[0] = Vector3::Zero(); + mParameters.axis[1] = Vector3::Zero(); + mParameters.axis[2] = Vector3::Zero(); + mParameters.extent = Vector3::Zero(); + } + + // Basic fitting algorithm. See ApprQuery.h for the various Fit(...) + // functions that you can call. + virtual bool FitIndexed( + size_t numPoints, Vector3 const* points, + size_t numIndices, int const* indices) override + { + if (this->ValidIndices(numPoints, points, numIndices, indices)) + { + // Compute the mean of the points. + Vector3 mean = Vector3::Zero(); + int const* currentIndex = indices; + for (size_t i = 0; i < numIndices; ++i) + { + mean += points[*currentIndex++]; + } + Real invSize = (Real)1 / (Real)numIndices; + mean *= invSize; + + if (std::isfinite(mean[0]) && std::isfinite(mean[1])) + { + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0, covar22 = (Real)0; + currentIndex = indices; + for (size_t i = 0; i < numIndices; ++i) + { + Vector3 diff = points[*currentIndex++] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + covar22 += diff[2] * diff[2]; + } + covar00 *= invSize; + covar01 *= invSize; + covar02 *= invSize; + covar11 *= invSize; + covar12 *= invSize; + covar22 *= invSize; + + // Solve the eigensystem. + SymmetricEigensolver3x3 es; + std::array eval; + std::array, 3> evec; + es(covar00, covar01, covar02, covar11, covar12, covar22, + false, +1, eval, evec); + mParameters.center = mean; + mParameters.axis[0] = evec[0]; + mParameters.axis[1] = evec[1]; + mParameters.axis[2] = evec[2]; + mParameters.extent = eval; + return true; + } + } + + mParameters.center = Vector3::Zero(); + mParameters.axis[0] = Vector3::Zero(); + mParameters.axis[1] = Vector3::Zero(); + mParameters.axis[2] = Vector3::Zero(); + mParameters.extent = Vector3::Zero(); + return false; + } + + // Get the parameters for the best fit. + OrientedBox3 const& GetParameters() const + { + return mParameters; + } + + virtual size_t GetMinimumRequired() const override + { + return 2; + } + + virtual Real Error(Vector3 const& point) const override + { + Vector3 diff = point - mParameters.center; + Real error = (Real)0; + for (int i = 0; i < 3; ++i) + { + if (mParameters.extent[i] > (Real)0) + { + Real ratio = Dot(diff, mParameters.axis[i]) / mParameters.extent[i]; + error += ratio * ratio; + } + } + return error; + } + + virtual void CopyParameters(ApprQuery> const* input) override + { + auto source = dynamic_cast(input); + if (source) + { + *this = *source; + } + } + + private: + OrientedBox3 mParameters; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprGreatCircle3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprGreatCircle3.h new file mode 100644 index 0000000..8f43bd4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprGreatCircle3.h @@ -0,0 +1,129 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + // Least-squares fit of a great circle to unit-length vectors (x,y,z) by + // using distance measurements orthogonal (and measured along great + // circles) to the proposed great circle. The inputs akPoint[] are unit + // length. The returned value is unit length, call it N. The fitted + // great circle is defined by Dot(N,X) = 0, where X is a unit-length + // vector on the great circle. + template + class ApprGreatCircle3 + { + public: + void operator()(int numPoints, Vector3 const* points, Vector3& normal) const + { + // Compute the covariance matrix of the vectors. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0, covar22 = (Real)0; + for (int i = 0; i < numPoints; i++) + { + Vector3 diff = points[i]; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + covar22 += diff[2] * diff[2]; + } + + Real invNumPoints = (Real)1 / static_cast(numPoints); + covar00 *= invNumPoints; + covar01 *= invNumPoints; + covar02 *= invNumPoints; + covar11 *= invNumPoints; + covar12 *= invNumPoints; + covar22 *= invNumPoints; + + // Solve the eigensystem. + SymmetricEigensolver3x3 es; + std::array eval; + std::array, 3> evec; + es(covar00, covar01, covar02, covar11, covar12, covar22, false, +1, + eval, evec); + normal = evec[0]; + } + }; + + + // In addition to the least-squares fit of a great circle, the input + // vectors are projected onto that circle. The sector of smallest angle + // (possibly obtuse) that contains the points is computed. The endpoints + // of the arc of the sector are returned. The returned endpoints A0 and + // A1 are perpendicular to the returned normal N. Moreover, when you view + // the arc by looking at the plane of the great circle with a view + // direction of -N, the arc is traversed counterclockwise starting at A0 + // and ending at A1. + template + class ApprGreatArc3 + { + public: + void operator()(int numPoints, Vector3 const* points, + Vector3& normal, Vector3& arcEnd0, + Vector3& arcEnd1) const + { + // Get the least-squares great circle for the vectors. The circle + // is on the plane Dot(N,X) = 0. Generate a basis from N. + Vector3 basis[3]; // { N, U, V } + ApprGreatCircle3()(numPoints, points, basis[0]); + ComputeOrthogonalComplement(1, basis); + + // The vectors are X[i] = u[i]*U + v[i]*V + w[i]*N. The + // projections are + // P[i] = (u[i]*U + v[i]*V)/sqrt(u[i]*u[i] + v[i]*v[i]) + // The great circle is parameterized by + // C(t) = cos(t)*U + sin(t)*V + // Compute the angles t in [-pi,pi] for the projections onto the + // great circle. It is not necesarily to normalize (u[i],v[i]), + // instead computing t = atan2(v[i],u[i]). The items[] represents + // (u, v, angle). + std::vector> items(numPoints); + for (int i = 0; i < numPoints; ++i) + { + items[i][0] = Dot(basis[1], points[i]); + items[i][1] = Dot(basis[2], points[i]); + items[i][2] = std::atan2(items[i][1], items[i][0]); + } + std::sort(items.begin(), items.end(), + [](std::array const& item0, std::array const& item1) + { + return item0[2] < item1[2]; + } + ); + + // Locate the pair of consecutive angles whose difference is a + // maximum. Effectively, we are constructing a cone of minimum + // angle that contains the unit-length vectors. + int numPointsM1 = numPoints - 1; + Real maxDiff = (Real)GTE_C_TWO_PI + items[0][2] - items[numPointsM1][2]; + int end0 = 0, end1 = numPointsM1; + for (int i0 = 0, i1 = 1; i0 < numPointsM1; i0 = i1++) + { + Real diff = items[i1][2] - items[i0][2]; + if (diff > maxDiff) + { + maxDiff = diff; + end0 = i1; + end1 = i0; + } + } + + normal = basis[0]; + arcEnd0 = items[end0][0] * basis[1] + items[end0][1] * basis[2]; + arcEnd1 = items[end1][0] * basis[1] + items[end1][1] * basis[2]; + Normalize(arcEnd0); + Normalize(arcEnd1); + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprHeightLine2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprHeightLine2.h new file mode 100644 index 0000000..c7b5c64 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprHeightLine2.h @@ -0,0 +1,108 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// Least-squares fit of a line to height data (x,f(x)). The line is of the +// form: (y - yAvr) = a*(x - xAvr), where (xAvr,yAvr) is the average of the +// sample points. The return value of Fit is 'true' if and only if the fit is +// successful (the input points are not degenerate to a single point). The +// mParameters values are ((xAvr,yAvr),(a,-1)) on success and ((0,0),(0,0)) on +// failure. The error for (x0,y0) is [a*(x0-xAvr)-(y0-yAvr)]^2. + +namespace WwiseGTE +{ + template + class ApprHeightLine2 : public ApprQuery> + { + public: + // Initialize the model parameters to zero. + ApprHeightLine2() + { + mParameters.first = Vector2::Zero(); + mParameters.second = Vector2::Zero(); + } + + // Basic fitting algorithm. See ApprQuery.h for the various Fit(...) + // functions that you can call. + virtual bool FitIndexed( + size_t numPoints, Vector2 const* points, + size_t numIndices, int const* indices) override + { + if (this->ValidIndices(numPoints, points, numIndices, indices)) + { + // Compute the mean of the points. + Vector2 mean = Vector2::Zero(); + int const* currentIndex = indices; + for (size_t i = 0; i < numIndices; ++i) + { + mean += points[*currentIndex++]; + } + mean /= (Real)numIndices; + + if (std::isfinite(mean[0]) && std::isfinite(mean[1])) + { + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0; + currentIndex = indices; + for (size_t i = 0; i < numIndices; ++i) + { + Vector2 diff = points[*currentIndex++] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + } + + // Decompose the covariance matrix. + if (covar00 > (Real)0) + { + mParameters.first = mean; + mParameters.second[0] = covar01 / covar00; + mParameters.second[1] = (Real)-1; + return true; + } + } + } + + mParameters.first = Vector2::Zero(); + mParameters.second = Vector2::Zero(); + return false; + } + + // Get the parameters for the best fit. + std::pair, Vector2> const& GetParameters() const + { + return mParameters; + } + + virtual size_t GetMinimumRequired() const override + { + return 2; + } + + virtual Real Error(Vector2 const& point) const override + { + Real d = Dot(point - mParameters.first, mParameters.second); + Real error = d * d; + return error; + } + + virtual void CopyParameters(ApprQuery> const* input) override + { + auto source = dynamic_cast const*>(input); + if (source) + { + *this = *source; + } + } + + private: + std::pair, Vector2> mParameters; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprHeightPlane3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprHeightPlane3.h new file mode 100644 index 0000000..01a1737 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprHeightPlane3.h @@ -0,0 +1,116 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// Least-squares fit of a plane to height data (x,y,f(x,y)). The plane is of +// the form (z - zAvr) = a*(x - xAvr) + b*(y - yAvr), where (xAvr,yAvr,zAvr) +// is the average of the sample points. The return value is 'true' if and +// only the if fit is successful (the input points are noncollinear). The +// mParameters values are ((xAvr,yAvr,zAvr),(a,b,-1)) on success and +// ((0,0,0),(0,0,0)) on failure. The error for (x0,y0,z0) is +// [a*(x0-xAvr)+b*(y0-yAvr)-(z0-zAvr)]^2. + +namespace WwiseGTE +{ + template + class ApprHeightPlane3 : public ApprQuery> + { + public: + // Initialize the model parameters to zero. + ApprHeightPlane3() + { + mParameters.first = Vector3::Zero(); + mParameters.second = Vector3::Zero(); + } + + // Basic fitting algorithm. See ApprQuery.h for the various Fit(...) + // functions that you can call. + virtual bool FitIndexed( + size_t numPoints, Vector3 const* points, + size_t numIndices, int const* indices) override + { + if (this->ValidIndices(numPoints, points, numIndices, indices)) + { + // Compute the mean of the points. + Vector3 mean = Vector3::Zero(); + int const* currentIndex = indices; + for (size_t i = 0; i < numIndices; ++i) + { + mean += points[*currentIndex++]; + } + mean /= (Real)numIndices; + + if (std::isfinite(mean[0]) && std::isfinite(mean[1])) + { + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0; + currentIndex = indices; + for (size_t i = 0; i < numIndices; ++i) + { + Vector3 diff = points[*currentIndex++] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + } + + // Decompose the covariance matrix. + Real det = covar00 * covar11 - covar01 * covar01; + if (det != (Real)0) + { + Real invDet = (Real)1 / det; + mParameters.first = mean; + mParameters.second[0] = (covar11 * covar02 - covar01 * covar12) * invDet; + mParameters.second[1] = (covar00 * covar12 - covar01 * covar02) * invDet; + mParameters.second[2] = (Real)-1; + return true; + } + } + } + + mParameters.first = Vector3::Zero(); + mParameters.second = Vector3::Zero(); + return false; + } + + // Get the parameters for the best fit. + std::pair, Vector3> const& GetParameters() const + { + return mParameters; + } + + virtual size_t GetMinimumRequired() const override + { + return 3; + } + + virtual Real Error(Vector3 const& point) const override + { + Real d = Dot(point - mParameters.first, mParameters.second); + Real error = d * d; + return error; + } + + virtual void CopyParameters(ApprQuery> const* input) override + { + auto source = dynamic_cast const*>(input); + if (source) + { + *this = *source; + } + } + + private: + std::pair, Vector3> mParameters; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprOrthogonalLine2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprOrthogonalLine2.h new file mode 100644 index 0000000..cacbbec --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprOrthogonalLine2.h @@ -0,0 +1,117 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +// Least-squares fit of a line to (x,y) data by using distance measurements +// orthogonal to the proposed line. The return value is 'true' if and only +// if the fit is unique (always successful, 'true' when a minimum eigenvalue +// is unique). The mParameters value is a line with (P,D) = +// (origin,direction). The error for S = (x0,y0) is (S-P)^T*(I - D*D^T)*(S-P). + +namespace WwiseGTE +{ + template + class ApprOrthogonalLine2 : public ApprQuery> + { + public: + // Initialize the model parameters to zero. + ApprOrthogonalLine2() + : + mParameters(Vector2::Zero(), Vector2::Zero()) + { + } + + // Basic fitting algorithm. See ApprQuery.h for the various Fit(...) + // functions that you can call. + virtual bool FitIndexed( + size_t numPoints, Vector2 const* points, + size_t numIndices, int const* indices) override + { + if (this->ValidIndices(numPoints, points, numIndices, indices)) + { + // Compute the mean of the points. + Vector2 mean = Vector2::Zero(); + int const* currentIndex = indices; + for (size_t i = 0; i < numIndices; ++i) + { + mean += points[*currentIndex++]; + } + mean /= (Real)numIndices; + + if (std::isfinite(mean[0]) && std::isfinite(mean[1])) + { + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar11 = (Real)0; + currentIndex = indices; + for (size_t i = 0; i < numIndices; ++i) + { + Vector2 diff = points[*currentIndex++] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar11 += diff[1] * diff[1]; + } + + // Solve the eigensystem. + SymmetricEigensolver2x2 es; + std::array eval; + std::array, 2> evec; + es(covar00, covar01, covar11, +1, eval, evec); + + // The line direction is the eigenvector in the direction + // of largest variance of the points. + mParameters.origin = mean; + mParameters.direction = evec[1]; + + // The fitted line is unique when the maximum eigenvalue + // has multiplicity 1. + return eval[0] < eval[1]; + } + } + + mParameters = Line2(Vector2::Zero(), Vector2::Zero()); + return false; + } + + // Get the parameters for the best fit. + Line2 const& GetParameters() const + { + return mParameters; + } + + virtual size_t GetMinimumRequired() const override + { + return 2; + } + + virtual Real Error(Vector2 const& point) const override + { + Vector2 diff = point - mParameters.origin; + Real sqrlen = Dot(diff, diff); + Real dot = Dot(diff, mParameters.direction); + Real error = std::fabs(sqrlen - dot * dot); + return error; + } + + virtual void CopyParameters(ApprQuery> const* input) override + { + auto source = dynamic_cast const*>(input); + if (source) + { + *this = *source; + } + } + + private: + Line2 mParameters; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprOrthogonalLine3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprOrthogonalLine3.h new file mode 100644 index 0000000..caf8a0d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprOrthogonalLine3.h @@ -0,0 +1,129 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +// Least-squares fit of a line to (x,y,z) data by using distance measurements +// orthogonal to the proposed line. The return value is 'true' if and only if +// the fit is unique (always successful, 'true' when a minimum eigenvalue is +// unique). The mParameters value is a line with (P,D) = (origin,direction). +// The error for S = (x0,y0,z0) is (S-P)^T*(I - D*D^T)*(S-P). + +namespace WwiseGTE +{ + template + class ApprOrthogonalLine3 : public ApprQuery> + { + public: + // Initialize the model parameters to zero. + ApprOrthogonalLine3() + : + mParameters(Vector3::Zero(), Vector3::Zero()) + { + } + + // Basic fitting algorithm. See ApprQuery.h for the various Fit(...) + // functions that you can call. + virtual bool FitIndexed( + size_t numPoints, Vector3 const* points, + size_t numIndices, int const* indices) override + { + if (this->ValidIndices(numPoints, points, numIndices, indices)) + { + // Compute the mean of the points. + Vector3 mean = Vector3::Zero(); + int const* currentIndex = indices; + for (size_t i = 0; i < numIndices; ++i) + { + mean += points[*currentIndex++]; + } + Real invSize = (Real)1 / (Real)numIndices; + mean *= invSize; + + if (std::isfinite(mean[0]) && std::isfinite(mean[1])) + { + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0, covar22 = (Real)0; + currentIndex = indices; + for (size_t i = 0; i < numIndices; ++i) + { + Vector3 diff = points[*currentIndex++] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + covar22 += diff[2] * diff[2]; + } + covar00 *= invSize; + covar01 *= invSize; + covar02 *= invSize; + covar11 *= invSize; + covar12 *= invSize; + covar22 *= invSize; + + // Solve the eigensystem. + SymmetricEigensolver3x3 es; + std::array eval; + std::array, 3> evec; + es(covar00, covar01, covar02, covar11, covar12, covar22, + false, +1, eval, evec); + + // The line direction is the eigenvector in the direction + // of largest variance of the points. + mParameters.origin = mean; + mParameters.direction = evec[2]; + + // The fitted line is unique when the maximum eigenvalue + // has multiplicity 1. + return eval[1] < eval[2]; + } + } + + mParameters = Line3(Vector3::Zero(), Vector3::Zero()); + return false; + } + + // Get the parameters for the best fit. + Line3 const& GetParameters() const + { + return mParameters; + } + + virtual size_t GetMinimumRequired() const override + { + return 2; + } + + virtual Real Error(Vector3 const& point) const override + { + Vector3 diff = point - mParameters.origin; + Real sqrlen = Dot(diff, diff); + Real dot = Dot(diff, mParameters.direction); + Real error = std::fabs(sqrlen - dot * dot); + return error; + } + + virtual void CopyParameters(ApprQuery> const* input) override + { + auto source = dynamic_cast const*>(input); + if (source) + { + *this = *source; + } + } + + private: + Line3 mParameters; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprOrthogonalPlane3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprOrthogonalPlane3.h new file mode 100644 index 0000000..9ff9aef --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprOrthogonalPlane3.h @@ -0,0 +1,129 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// Least-squares fit of a plane to (x,y,z) data by using distance measurements +// orthogonal to the proposed plane. The return value is 'true' if and only if +// the fit is unique (always successful, 'true' when a minimum eigenvalue is +// unique). The mParameters value is (P,N) = (origin,normal). The error for +// S = (x0,y0,z0) is (S-P)^T*(I - N*N^T)*(S-P). + +namespace WwiseGTE +{ + template + class ApprOrthogonalPlane3 : public ApprQuery> + { + public: + // Initialize the model parameters to zero. + ApprOrthogonalPlane3() + { + mParameters.first = Vector3::Zero(); + mParameters.second = Vector3::Zero(); + } + + // Basic fitting algorithm. See ApprQuery.h for the various Fit(...) + // functions that you can call. + virtual bool FitIndexed( + size_t numPoints, Vector3 const* points, + size_t numIndices, int const* indices) override + { + if (this->ValidIndices(numPoints, points, numIndices, indices)) + { + // Compute the mean of the points. + Vector3 mean = Vector3::Zero(); + int const* currentIndex = indices; + for (size_t i = 0; i < numIndices; ++i) + { + mean += points[*currentIndex++]; + } + Real invSize = (Real)1 / (Real)numIndices; + mean *= invSize; + + if (std::isfinite(mean[0]) && std::isfinite(mean[1])) + { + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0, covar22 = (Real)0; + currentIndex = indices; + for (size_t i = 0; i < numIndices; ++i) + { + Vector3 diff = points[*currentIndex++] - mean; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + covar22 += diff[2] * diff[2]; + } + covar00 *= invSize; + covar01 *= invSize; + covar02 *= invSize; + covar11 *= invSize; + covar12 *= invSize; + covar22 *= invSize; + + // Solve the eigensystem. + SymmetricEigensolver3x3 es; + std::array eval; + std::array, 3> evec; + es(covar00, covar01, covar02, covar11, covar12, covar22, + false, +1, eval, evec); + + // The plane normal is the eigenvector in the direction of + // smallest variance of the points. + mParameters.first = mean; + mParameters.second = evec[0]; + + // The fitted plane is unique when the minimum eigenvalue + // has multiplicity 1. + return eval[0] < eval[1]; + } + } + + mParameters.first = Vector3::Zero(); + mParameters.second = Vector3::Zero(); + return false; + } + + // Get the parameters for the best fit. + std::pair, Vector3> const& GetParameters() const + { + return mParameters; + } + + virtual size_t GetMinimumRequired() const override + { + return 3; + } + + virtual Real Error(Vector3 const& point) const override + { + Vector3 diff = point - mParameters.first; + Real sqrlen = Dot(diff, diff); + Real dot = Dot(diff, mParameters.second); + Real error = std::fabs(sqrlen - dot * dot); + return error; + } + + virtual void CopyParameters(ApprQuery> const* input) override + { + auto source = dynamic_cast const*>(input); + if (source) + { + *this = *source; + } + } + + private: + std::pair, Vector3> mParameters; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprParaboloid3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprParaboloid3.h new file mode 100644 index 0000000..dbb4ee0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprParaboloid3.h @@ -0,0 +1,129 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// Least-squares fit of a paraboloid to a set of point. The paraboloid is +// of the form z = c0*x^2+c1*x*y+c2*y^2+c3*x+c4*y+c5. A successful fit is +// indicated by return value of 'true'. +// +// Given a set of samples (x_i,y_i,z_i) for 0 <= i < N, and assuming +// that the true values lie on a paraboloid +// z = p0*x*x + p1*x*y + p2*y*y + p3*x + p4*y + p5 = Dot(P,Q(x,y)) +// where P = (p0,p1,p2,p3,p4,p5) and Q(x,y) = (x*x,x*y,y*y,x,y,1), +// select P to minimize the sum of squared errors +// E(P) = sum_{i=0}^{N-1} [Dot(P,Q_i)-z_i]^2 +// where Q_i = Q(x_i,y_i). +// +// The minimum occurs when the gradient of E is the zero vector, +// grad(E) = 2 sum_{i=0}^{N-1} [Dot(P,Q_i)-z_i] Q_i = 0 +// Some algebra converts this to a system of 6 equations in 6 unknowns: +// [(sum_{i=0}^{N-1} Q_i Q_i^t] P = sum_{i=0}^{N-1} z_i Q_i +// The product Q_i Q_i^t is a product of the 6x1 matrix Q_i with the +// 1x6 matrix Q_i^t, the result being a 6x6 matrix. +// +// Define the 6x6 symmetric matrix A = sum_{i=0}^{N-1} Q_i Q_i^t and the 6x1 +// vector B = sum_{i=0}^{N-1} z_i Q_i. The choice for P is the solution to +// the linear system of equations A*P = B. The entries of A and B indicate +// summations over the appropriate product of variables. For example, +// s(x^3 y) = sum_{i=0}^{N-1} x_i^3 y_i. +// +// +- -++ + +- -+ +// | s(x^4) s(x^3 y) s(x^2 y^2) s(x^3) s(x^2 y) s(x^2) ||p0| |s(z x^2)| +// | s(x^2 y^2) s(x y^3) s(x^2 y) s(x y^2) s(x y) ||p1| |s(z x y)| +// | s(y^4) s(x y^2) s(y^3) s(y^2) ||p2| = |s(z y^2)| +// | s(x^2) s(x y) s(x) ||p3| |s(z x) | +// | s(y^2) s(y) ||p4| |s(z y) | +// | s(1) ||p5| |s(z) | +// +- -++ + +- -+ + +namespace WwiseGTE +{ + template + class ApprParaboloid3 + { + public: + bool operator()(int numPoints, Vector3 const* points, Real coefficients[6]) const + { + Matrix<6, 6, Real> A; + Vector<6, Real> B; + B.MakeZero(); + + for (int i = 0; i < numPoints; i++) + { + Real x2 = points[i][0] * points[i][0]; + Real xy = points[i][0] * points[i][1]; + Real y2 = points[i][1] * points[i][1]; + Real zx = points[i][2] * points[i][0]; + Real zy = points[i][2] * points[i][1]; + Real x3 = points[i][0] * x2; + Real x2y = x2 * points[i][1]; + Real xy2 = points[i][0] * y2; + Real y3 = points[i][1] * y2; + Real zx2 = points[i][2] * x2; + Real zxy = points[i][2] * xy; + Real zy2 = points[i][2] * y2; + Real x4 = x2 * x2; + Real x3y = x3 * points[i][1]; + Real x2y2 = x2 * y2; + Real xy3 = points[i][0] * y3; + Real y4 = y2 * y2; + + A(0, 0) += x4; + A(0, 1) += x3y; + A(0, 2) += x2y2; + A(0, 3) += x3; + A(0, 4) += x2y; + A(0, 5) += x2; + A(1, 2) += xy3; + A(1, 4) += xy2; + A(1, 5) += xy; + A(2, 2) += y4; + A(2, 4) += y3; + A(2, 5) += y2; + A(3, 3) += x2; + A(3, 5) += points[i][0]; + A(4, 5) += points[i][1]; + + B[0] += zx2; + B[1] += zxy; + B[2] += zy2; + B[3] += zx; + B[4] += zy; + B[5] += points[i][2]; + } + + A(1, 0) = A(0, 1); + A(1, 1) = A(0, 2); + A(1, 3) = A(0, 4); + A(2, 0) = A(0, 2); + A(2, 1) = A(1, 2); + A(2, 3) = A(1, 4); + A(3, 0) = A(0, 3); + A(3, 1) = A(1, 3); + A(3, 2) = A(2, 3); + A(3, 4) = A(1, 5); + A(4, 0) = A(0, 4); + A(4, 1) = A(1, 4); + A(4, 2) = A(2, 4); + A(4, 3) = A(3, 4); + A(4, 4) = A(2, 5); + A(5, 0) = A(0, 5); + A(5, 1) = A(1, 5); + A(5, 2) = A(2, 5); + A(5, 3) = A(3, 5); + A(5, 4) = A(4, 5); + A(5, 5) = static_cast(numPoints); + + return LinearSystem().Solve(6, &A[0], &B[0], &coefficients[0]); + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprParallelLines2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprParallelLines2.h new file mode 100644 index 0000000..95cb96a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprParallelLines2.h @@ -0,0 +1,333 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.3.2019.12.27 + +#pragma once + +// Least-squares fit of two parallel lines to points that presumably are +// clustered on the lines. The algorithm is described in +// https://www.geometrictools.com/Documentation/FitParallelLinesToPoints2D.pdf + +#include +#include +#include + +namespace WwiseGTE +{ + template + class ApprParallelLines2 + { + public: + ApprParallelLines2() + : + mR0(0), mR1(1), mR2(2), mR3(3), mR4(4), mR5(5), mR6(6) + { + // The constants are set here in case Real is a rational type, + // which avoids construction costs for those types. + } + + void Fit(std::vector> const& P, unsigned int maxIterations, + Vector2& C, Vector2& V, Real& radius) + { + // Compute the average of the samples. + size_t const n = P.size(); + Real const invN = static_cast(1) / static_cast(n); + std::vector> PAdjust = P; + Vector2 A{ mR0, mR0 }; + for (auto const& sample : PAdjust) + { + A += sample; + } + A *= invN; + + // Subtract the average from the samples so that the replacement + // points have zero average. + for (auto& sample : PAdjust) + { + sample -= A; + } + + // Compute the Zpq terms. + ZValues data(PAdjust); + + // Compute F(sigma,gamma) = f0(sigma) + gamma * f1(sigma). + Polynomial1 f0, f1; + ComputeF(data, f0, f1); + Polynomial1 freduced0(4), freduced1(3); + for (int i = 0; i <= 4; ++i) + { + freduced0[i] = f0[2 * i]; + } + for (int i = 0; i <= 3; ++i) + { + freduced1[i] = f1[2 * i + 1]; + } + + // Evaluate the error function at any (sigma,gamma). Choose (0,1) + // so that we do not have to process a root sigma=0 later. + Real minSigma = mR0, minGamma = mR1; + Real minK = data.Z03 / (mR2 * data.Z02); + Real minKSqr = minK * minK; + Real minRSqr = minKSqr + data.Z02; + Real minError = data.Z04 - mR4 * minK * data.Z03 + (mR4 * minKSqr - data.Z02) * data.Z02; + + if (f1 != Polynomial1{ mR0 }) + { + Polynomial1 sigmaSqrPoly{ mR0, mR0, mR1 }; + Polynomial1 f0Sqr = f0 * f0, f1Sqr = f1 * f1; + Polynomial1 h = sigmaSqrPoly * f1Sqr + (f0Sqr - f1Sqr); + Polynomial1 hreduced(8); + for (int i = 0; i <= 8; ++i) + { + hreduced[i] = h[2 * i]; + } + + std::array roots; + int numRoots = RootsPolynomial::Find(8, &hreduced[0], + maxIterations, roots.data()); + for (int i = 0; i < numRoots; ++i) + { + Real sigmaSqr = roots[i]; + if (sigmaSqr > mR0) + { + Real sigma = std::sqrt(sigmaSqr); + Real gamma = -freduced0(sigmaSqr) / (sigma * freduced1(sigmaSqr)); + UpdateParameters(data, sigma, sigmaSqr, gamma, + minSigma, minGamma, minK, minRSqr, minError); + } + } + } + else + { + Polynomial1 hreduced(4); + for (int i = 0; i <= 4; ++i) + { + hreduced[i] = f0[2 * i]; + } + + std::array roots; + int numRoots = RootsPolynomial::Find(4, &hreduced[0], + maxIterations, roots.data()); + for (int i = 0; i < numRoots; ++i) + { + Real sigmaSqr = roots[i]; + if (sigmaSqr > mR0) + { + Real sigma = std::sqrt(sigmaSqr); + Real gamma = std::sqrt(sigma); + UpdateParameters(data, sigma, sigmaSqr, gamma, + minSigma, minGamma, minK, minRSqr, minError); + + gamma = -gamma; + UpdateParameters(data, sigma, sigmaSqr, gamma, + minSigma, minGamma, minK, minRSqr, minError); + } + } + } + + // Compute the minimizers V, C and radius. The center minK*U must have + // A added to it because the inputs P had A subtracted from them. The + // addition no longer guarantees that Dot(V,C) = 0, so the V-component + // of A+minK*U is projected out so that the returned center has only a + // U-component. + V = Vector2{ minGamma, minSigma }; + C = A + minK * Vector2{ -minSigma, minGamma }; + C -= Dot(C, V) * V; + radius = std::sqrt(minRSqr); + } + private: + struct ZValues + { + ZValues(std::vector> const& P) + : + Z20(0), Z11(0), Z02(0), + Z30(0), Z21(0), Z12(0), Z03(0), + Z40(0), Z31(0), Z22(0), Z13(0), Z04(0) + { + Real const invN = static_cast(1) / static_cast(P.size()); + for (auto const& sample : P) + { + Real xx = sample[0] * sample[0]; + Real xy = sample[0] * sample[1]; + Real yy = sample[1] * sample[1]; + Real xxx = xx * sample[0]; + Real xxy = xy * sample[0]; + Real xyy = xy * sample[1]; + Real yyy = yy * sample[1]; + Real xxxx = xxx * sample[0]; + Real xxxy = xxx * sample[1]; + Real xxyy = xx * yy; + Real xyyy = yyy * sample[0]; + Real yyyy = yyy * sample[1]; + Z20 += xx; + Z11 += xy; + Z02 += yy; + Z30 += xxx; + Z21 += xxy; + Z12 += xyy; + Z03 += yyy; + Z40 += xxxx; + Z31 += xxxy; + Z22 += xxyy; + Z13 += xyyy; + Z04 += yyyy; + } + Z20 *= invN; + Z11 *= invN; + Z02 *= invN; + Z30 *= invN; + Z21 *= invN; + Z12 *= invN; + Z03 *= invN; + Z40 *= invN; + Z31 *= invN; + Z22 *= invN; + Z13 *= invN; + Z04 *= invN; + } + + Real Z20, Z11, Z02; + Real Z30, Z21, Z12, Z03; + Real Z40, Z31, Z22, Z13, Z04; + }; + + // Given two polynomials A0+gamma*B0 and A1+gamma*B1, the product is + // [A0*A1+(1-sigma^2)*B0*B1] + gamma*[A0*B1+B0*A1] = A2+gamma*B2. + void ComputeProduct( + Polynomial1 const& A0, Polynomial1 const& B0, + Polynomial1 const& A1, Polynomial1 const& B1, + Polynomial1& A2, Polynomial1& B2) + { + Polynomial1 gammaSqr{ mR1, mR0, -mR1 }; + A2 = A0 * A1 + gammaSqr * B0 * B1; + B2 = A0 * B1 + B0 * A1; + } + + void ComputeF(ZValues const& data, Polynomial1& f0, Polynomial1& f1) + { + // Compute the apq and bpq terms. + Polynomial1 a11(2); + a11[0] = data.Z11; + a11[2] = -mR2 * data.Z11; + + Polynomial1 b11(1); + b11[1] = data.Z02 - data.Z20; + + Polynomial1 a20(2); + a20[0] = data.Z02; + a20[2] = data.Z20 - data.Z02; + + Polynomial1 b20(1); + b20[1] = -mR2 * data.Z11; + + Polynomial1 a30(3); + a30[1] = -mR3; + a30[3] = mR3 * data.Z12 - data.Z30; + + Polynomial1 b30(2); + b30[0] = data.Z03; + b30[2] = mR3 * data.Z21 - data.Z03; + + Polynomial1 a21(3); + a21[1] = data.Z03 - mR2 * data.Z21; + a21[3] = mR3 * data.Z21 - data.Z03; + + Polynomial1 b21(2); + b21[0] = data.Z12; + b21[2] = data.Z30 - mR3 * data.Z12; + + Polynomial1 a40(4); + a40[0] = data.Z04; + a40[2] = mR6 * data.Z22 - mR2 * data.Z04; + a40[4] = data.Z40 - mR6 * data.Z22 + data.Z04; + + Polynomial1 b40(3); + b40[1] = -mR4 * data.Z13; + b40[3] = mR4 * (data.Z13 - data.Z31); + + Polynomial1 a31(4); + a31[0] = data.Z13; + a31[2] = mR3 * data.Z31 - mR5 * data.Z13; + a31[4] = mR4 * (data.Z13 - data.Z31); + + Polynomial1 b31(3); + b31[1] = data.Z04 - mR3 * data.Z22; + b31[3] = mR6 * data.Z22 - data.Z40 - data.Z04; + + // Compute S20^2 = c0 + gamma*d0. + Polynomial1 c0, d0; + ComputeProduct(a20, b20, a20, b20, c0, d0); + + // Compute S31 * S20^2 = c1 + gamma*d1. + Polynomial1 c1, d1; + ComputeProduct(a31, b31, c0, d0, c1, d1); + + // Compute S21 * S20 = c2 + gamma*d2. + Polynomial1 c2, d2; + ComputeProduct(a21, b21, a20, b20, c2, d2); + + // Compute S30 * (S21 * S20) = c3 + gamma*d3. + Polynomial1 c3, d3; + ComputeProduct(a30, b30, c2, d2, c3, d3); + + // Compute S30 * S11 = c4 + gamma*d4. + Polynomial1 c4, d4; + ComputeProduct(a30, b30, a11, b11, c4, d4); + + // Compute S30 * (S30 * S11) = c5 + gamma*d5. + Polynomial1 c5, d5; + ComputeProduct(a30, b30, c4, d4, c5, d5); + + // Compute S20^2 * S11 = c6 + gamma*d6. + Polynomial1 c6, d6; + ComputeProduct(c0, d0, a11, b11, c6, d6); + + // Compute S20 * (S20^2 * S11) = c7 + gamma*d7. + Polynomial1 c7, d7; + ComputeProduct(a20, b20, c6, d6, c7, d7); + + // Compute F = 2*S31*S20^2 - 3*S30*S21*S20 + S30^2*S11 + // - 2*S20^3*S11 = f0 + gamma*f1, where f0 is even of degree 8 + // and f1 is odd of degree 7. + f0 = mR2 * (c1 - c7) - mR3 * c3 + c5; + f1 = mR2 * (d1 - d7) - mR3 * d3 + d5; + } + + void UpdateParameters(ZValues const& data, Real const& sigma, Real const& sigmaSqr, + Real const& gamma, Real& minSigma, Real& minGamma, Real& minK, + Real& minRSqr, Real& minError) + { + // Rather than evaluate apq(sigma) and bpq(sigma), the + // polynomials are evaluated at sigmaSqr to avoid the + // rounding errors that are inherent by computing + // s = sqrt(ssqr); ssqr = s * s; + Real A20 = data.Z02 + (data.Z20 - data.Z02) * sigmaSqr; + Real B20 = -mR2 * data.Z11 * sigma; + Real S20 = A20 + gamma * B20; + Real A30 = -sigma * (mR3 * data.Z12 + (data.Z30 - mR3 * data.Z12) * sigmaSqr); + Real B30 = data.Z03 + (mR3 * data.Z21 - data.Z03) * sigmaSqr; + Real S30 = A30 + gamma * B30; + Real A40 = data.Z04 + ((mR6 * data.Z22 - mR2 * data.Z04) + + (data.Z40 - mR6 * data.Z22 + data.Z04) * sigmaSqr) * sigmaSqr; + Real B40 = -mR4 * sigma * (data.Z13 + (data.Z31 - data.Z13) * sigmaSqr); + Real S40 = A40 + gamma * B40; + Real k = S30 / (mR2 * S20); + Real ksqr = k * k; + Real rsqr = ksqr + S20; + Real error = S40 - mR4 * k * S30 + (mR4 * ksqr - S20) * S20; + if (error < minError) + { + minSigma = sigma; + minGamma = gamma; + minK = k; + minRSqr = rsqr; + minError = error; + } + } + + Real const mR0, mR1, mR2, mR3, mR4, mR5, mR6; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprPolynomial2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprPolynomial2.h new file mode 100644 index 0000000..b001619 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprPolynomial2.h @@ -0,0 +1,178 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +// The samples are (x[i],w[i]) for 0 <= i < S. Think of w as a function of +// x, say w = f(x). The function fits the samples with a polynomial of +// degree d, say w = sum_{i=0}^d c[i]*x^i. The method is a least-squares +// fitting algorithm. The mParameters stores the coefficients c[i] for +// 0 <= i <= d. The observation type is std::array, which represents +// a pair (x,w). +// +// WARNING. The fitting algorithm for polynomial terms +// (1,x,x^2,...,x^d) +// is known to be nonrobust for large degrees and for large magnitude data. +// One alternative is to use orthogonal polynomials +// (f[0](x),...,f[d](x)) +// and apply the least-squares algorithm to these. Another alternative is to +// transform +// (x',w') = ((x-xcen)/rng, w/rng) +// where xmin = min(x[i]), xmax = max(x[i]), xcen = (xmin+xmax)/2, and +// rng = xmax-xmin. Fit the (x',w') points, +// w' = sum_{i=0}^d c'[i]*(x')^i. +// The original polynomial is evaluated as +// w = rng*sum_{i=0}^d c'[i]*((x-xcen)/rng)^i + +namespace WwiseGTE +{ + template + class ApprPolynomial2 : public ApprQuery> + { + public: + // Initialize the model parameters to zero. + ApprPolynomial2(int degree) + : + mDegree(degree), + mSize(degree + 1), + mParameters(mSize, (Real)0) + { + mXDomain[0] = std::numeric_limits::max(); + mXDomain[1] = -mXDomain[0]; + } + + // Basic fitting algorithm. See ApprQuery.h for the various Fit(...) + // functions that you can call. + virtual bool FitIndexed( + size_t numObservations, std::array const* observations, + size_t numIndices, int const* indices) override + { + if (this->ValidIndices(numObservations, observations, numIndices, indices)) + { + int s, i0, i1; + + // Compute the powers of x. + int numSamples = static_cast(numIndices); + int twoDegree = 2 * mDegree; + Array2 xPower(twoDegree + 1, numSamples); + for (s = 0; s < numSamples; ++s) + { + Real x = observations[indices[s]][0]; + mXDomain[0] = std::min(x, mXDomain[0]); + mXDomain[1] = std::max(x, mXDomain[1]); + + xPower[s][0] = (Real)1; + for (i0 = 1; i0 <= twoDegree; ++i0) + { + xPower[s][i0] = x * xPower[s][i0 - 1]; + } + } + + // Matrix A is the Vandermonde matrix and vector B is the + // right-hand side of the linear system A*X = B. + GMatrix A(mSize, mSize); + GVector B(mSize); + for (i0 = 0; i0 <= mDegree; ++i0) + { + Real sum = (Real)0; + for (s = 0; s < numSamples; ++s) + { + Real w = observations[indices[s]][1]; + sum += w * xPower[s][i0]; + } + + B[i0] = sum; + + for (i1 = 0; i1 <= mDegree; ++i1) + { + sum = (Real)0; + for (s = 0; s < numSamples; ++s) + { + sum += xPower[s][i0 + i1]; + } + + A(i0, i1) = sum; + } + } + + // Solve for the polynomial coefficients. + GVector coefficients = Inverse(A) * B; + bool hasNonzero = false; + for (int i = 0; i < mSize; ++i) + { + mParameters[i] = coefficients[i]; + if (coefficients[i] != (Real)0) + { + hasNonzero = true; + } + } + return hasNonzero; + } + + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + return false; + } + + // Get the parameters for the best fit. + std::vector const& GetParameters() const + { + return mParameters; + } + + virtual size_t GetMinimumRequired() const override + { + return static_cast(mSize); + } + + // Compute the model error for the specified observation for the + // current model parameters. The returned value for observation + // (x0,w0) is |w(x0) - w0|, where w(x) is the fitted polynomial. + virtual Real Error(std::array const& observation) const override + { + Real w = Evaluate(observation[0]); + Real error = std::fabs(w - observation[1]); + return error; + } + + virtual void CopyParameters(ApprQuery> const* input) override + { + auto source = dynamic_cast(input); + if (source) + { + *this = *source; + } + } + + // Evaluate the polynomial. The domain interval is provided so you can + // interpolate (x in domain) or extrapolate (x not in domain). + std::array const& GetXDomain() const + { + return mXDomain; + } + + Real Evaluate(Real x) const + { + int i = mDegree; + Real w = mParameters[i]; + while (--i >= 0) + { + w = mParameters[i] + w * x; + } + return w; + } + + private: + int mDegree, mSize; + std::array mXDomain; + std::vector mParameters; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprPolynomial3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprPolynomial3.h new file mode 100644 index 0000000..d38d3e6 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprPolynomial3.h @@ -0,0 +1,235 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +// The samples are (x[i],y[i],w[i]) for 0 <= i < S. Think of w as a function +// of x and y, say w = f(x,y). The function fits the samples with a +// polynomial of degree d0 in x and degree d1 in y, say +// w = sum_{i=0}^{d0} sum_{j=0}^{d1} c[i][j]*x^i*y^j +// The method is a least-squares fitting algorithm. The mParameters stores +// c[i][j] = mParameters[i+(d0+1)*j] for a total of (d0+1)*(d1+1) +// coefficients. The observation type is std::array, which represents +// a triple (x,y,w). +// +// WARNING. The fitting algorithm for polynomial terms +// (1,x,x^2,...,x^d0), (1,y,y^2,...,y^d1) +// is known to be nonrobust for large degrees and for large magnitude data. +// One alternative is to use orthogonal polynomials +// (f[0](x),...,f[d0](x)), (g[0](y),...,g[d1](y)) +// and apply the least-squares algorithm to these. Another alternative is to +// transform +// (x',y',w') = ((x-xcen)/rng, (y-ycen)/rng, w/rng) +// where xmin = min(x[i]), xmax = max(x[i]), xcen = (xmin+xmax)/2, +// ymin = min(y[i]), ymax = max(y[i]), ycen = (ymin+ymax)/2, and +// rng = max(xmax-xmin,ymax-ymin). Fit the (x',y',w') points, +// w' = sum_{i=0}^{d0} sum_{j=0}^{d1} c'[i][j]*(x')^i*(y')^j +// The original polynomial is evaluated as +// w = rng * sum_{i=0}^{d0} sum_{j=0}^{d1} c'[i][j] * +// ((x-xcen)/rng)^i * ((y-ycen)/rng)^j + +namespace WwiseGTE +{ + template + class ApprPolynomial3 : public ApprQuery> + { + public: + // Initialize the model parameters to zero. + ApprPolynomial3(int xDegree, int yDegree) + : + mXDegree(xDegree), + mYDegree(yDegree), + mXDegreeP1(xDegree + 1), + mYDegreeP1(yDegree + 1), + mSize(mXDegreeP1 * mYDegreeP1), + mParameters(mSize, (Real)0), + mYCoefficient(mYDegreeP1, (Real)0) + { + mXDomain[0] = std::numeric_limits::max(); + mXDomain[1] = -mXDomain[0]; + mYDomain[0] = std::numeric_limits::max(); + mYDomain[1] = -mYDomain[0]; + } + + // Basic fitting algorithm. See ApprQuery.h for the various Fit(...) + // functions that you can call. + virtual bool FitIndexed( + size_t numObservations, std::array const* observations, + size_t numIndices, int const* indices) override + { + if (this->ValidIndices(numObservations, observations, numIndices, indices)) + { + int s, i0, j0, k0, i1, j1, k1; + + // Compute the powers of x and y. + int numSamples = static_cast(numIndices); + int twoXDegree = 2 * mXDegree; + int twoYDegree = 2 * mYDegree; + Array2 xPower(twoXDegree + 1, numSamples); + Array2 yPower(twoYDegree + 1, numSamples); + for (s = 0; s < numSamples; ++s) + { + Real x = observations[indices[s]][0]; + Real y = observations[indices[s]][1]; + mXDomain[0] = std::min(x, mXDomain[0]); + mXDomain[1] = std::max(x, mXDomain[1]); + mYDomain[0] = std::min(y, mYDomain[0]); + mYDomain[1] = std::max(y, mYDomain[1]); + + xPower[s][0] = (Real)1; + for (i0 = 1; i0 <= twoXDegree; ++i0) + { + xPower[s][i0] = x * xPower[s][i0 - 1]; + } + + yPower[s][0] = (Real)1; + for (j0 = 1; j0 <= twoYDegree; ++j0) + { + yPower[s][j0] = y * yPower[s][j0 - 1]; + } + } + + // Matrix A is the Vandermonde matrix and vector B is the + // right-hand side of the linear system A*X = B. + GMatrix A(mSize, mSize); + GVector B(mSize); + for (j0 = 0; j0 <= mYDegree; ++j0) + { + for (i0 = 0; i0 <= mXDegree; ++i0) + { + Real sum = (Real)0; + k0 = i0 + mXDegreeP1 * j0; + for (s = 0; s < numSamples; ++s) + { + Real w = observations[indices[s]][2]; + sum += w * xPower[s][i0] * yPower[s][j0]; + } + + B[k0] = sum; + + for (j1 = 0; j1 <= mYDegree; ++j1) + { + for (i1 = 0; i1 <= mXDegree; ++i1) + { + sum = (Real)0; + k1 = i1 + mXDegreeP1 * j1; + for (s = 0; s < numSamples; ++s) + { + sum += xPower[s][i0 + i1] * yPower[s][j0 + j1]; + } + + A(k0, k1) = sum; + } + } + } + } + + // Solve for the polynomial coefficients. + GVector coefficients = Inverse(A) * B; + bool hasNonzero = false; + for (int i = 0; i < mSize; ++i) + { + mParameters[i] = coefficients[i]; + if (coefficients[i] != (Real)0) + { + hasNonzero = true; + } + } + return hasNonzero; + } + + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + return false; + } + + // Get the parameters for the best fit. + std::vector const& GetParameters() const + { + return mParameters; + } + + virtual size_t GetMinimumRequired() const override + { + return static_cast(mSize); + } + + // Compute the model error for the specified observation for the + // current model parameters. The returned value for observation + // (x0,y0,w0) is |w(x0,y0) - w0|, where w(x,y) is the fitted + // polynomial. + virtual Real Error(std::array const& observation) const override + { + Real w = Evaluate(observation[0], observation[1]); + Real error = std::fabs(w - observation[2]); + return error; + } + + virtual void CopyParameters(ApprQuery> const* input) override + { + auto source = dynamic_cast(input); + if (source) + { + *this = *source; + } + } + + // Evaluate the polynomial. The domain intervals are provided so you + // can interpolate ((x,y) in domain) or extrapolate ((x,y) not in + // domain). + std::array const& GetXDomain() const + { + return mXDomain; + } + + std::array const& GetYDomain() const + { + return mYDomain; + } + + Real Evaluate(Real x, Real y) const + { + int i0, i1; + Real w; + + for (i1 = 0; i1 <= mYDegree; ++i1) + { + i0 = mXDegree; + w = mParameters[i0 + mXDegreeP1 * i1]; + while (--i0 >= 0) + { + w = mParameters[i0 + mXDegreeP1 * i1] + w * x; + } + mYCoefficient[i1] = w; + } + + i1 = mYDegree; + w = mYCoefficient[i1]; + while (--i1 >= 0) + { + w = mYCoefficient[i1] + w * y; + } + + return w; + } + + private: + int mXDegree, mYDegree, mXDegreeP1, mYDegreeP1, mSize; + std::array mXDomain, mYDomain; + std::vector mParameters; + + // This array is used by Evaluate() to avoid reallocation of the + // 'vector' for each call. The member is mutable because, to the + // user, the call to Evaluate does not modify the polynomial. + mutable std::vector mYCoefficient; + }; + +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprPolynomial4.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprPolynomial4.h new file mode 100644 index 0000000..212a62b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprPolynomial4.h @@ -0,0 +1,280 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.12.05 + +#pragma once + +#include +#include +#include +#include + +// The samples are (x[i],y[i],z[i],w[i]) for 0 <= i < S. Think of w as a +// function of x, y, and z, say w = f(x,y,z). The function fits the samples +// with a polynomial of degree d0 in x, degree d1 in y, and degree d2 in z, +// say +// w = sum_{i=0}^{d0} sum_{j=0}^{d1} sum_{k=0}^{d2} c[i][j][k]*x^i*y^j*z^k +// The method is a least-squares fitting algorithm. The mParameters stores +// c[i][j][k] = mParameters[i+(d0+1)*(j+(d1+1)*k)] for a total of +// (d0+1)*(d1+1)*(d2+1) coefficients. The observation type is +// std::array, which represents a tuple (x,y,z,w). +// +// WARNING. The fitting algorithm for polynomial terms +// (1,x,x^2,...,x^d0), (1,y,y^2,...,y^d1), (1,z,z^2,...,z^d2) +// is known to be nonrobust for large degrees and for large magnitude data. +// One alternative is to use orthogonal polynomials +// (f[0](x),...,f[d0](x)), (g[0](y),...,g[d1](y)), (h[0](z),...,h[d2](z)) +// and apply the least-squares algorithm to these. Another alternative is to +// transform +// (x',y',z',w') = ((x-xcen)/rng, (y-ycen)/rng, (z-zcen)/rng, w/rng) +// where xmin = min(x[i]), xmax = max(x[i]), xcen = (xmin+xmax)/2, +// ymin = min(y[i]), ymax = max(y[i]), ycen = (ymin+ymax)/2, zmin = min(z[i]), +// zmax = max(z[i]), zcen = (zmin+zmax)/2, and +// rng = max(xmax-xmin,ymax-ymin,zmax-zmin). Fit the (x',y',z',w') points, +// w' = sum_{i=0}^{d0} sum_{j=0}^{d1} sum_{k=0}^{d2} c'[i][j][k] * +// (x')^i*(y')^j*(z')^k +// The original polynomial is evaluated as +// w = rng * sum_{i=0}^{d0} sum_{j=0}^{d1} sum_{k=0}^{d2} c'[i][j][k] * +// ((x-xcen)/rng)^i * ((y-ycen)/rng)^j * ((z-zcen)/rng)^k + +namespace WwiseGTE +{ + template + class ApprPolynomial4 : public ApprQuery> + { + public: + // Initialize the model parameters to zero. + ApprPolynomial4(int xDegree, int yDegree, int zDegree) + : + mXDegree(xDegree), + mYDegree(yDegree), + mZDegree(zDegree), + mXDegreeP1(xDegree + 1), + mYDegreeP1(yDegree + 1), + mZDegreeP1(zDegree + 1), + mSize(mXDegreeP1* mYDegreeP1* mZDegreeP1), + mParameters(mSize, (Real)0), + mYZCoefficient(mYDegreeP1 * mZDegreeP1, (Real)0), + mZCoefficient(mZDegreeP1, (Real)0) + { + mXDomain[0] = std::numeric_limits::max(); + mXDomain[1] = -mXDomain[0]; + mYDomain[0] = std::numeric_limits::max(); + mYDomain[1] = -mYDomain[0]; + mZDomain[0] = std::numeric_limits::max(); + mZDomain[1] = -mZDomain[0]; + } + + // Basic fitting algorithm. See ApprQuery.h for the various Fit(...) + // functions that you can call. + virtual bool FitIndexed( + size_t numObservations, std::array const* observations, + size_t numIndices, int const* indices) override + { + if (this->ValidIndices(numObservations, observations, numIndices, indices)) + { + int s, i0, j0, k0, n0, i1, j1, k1, n1; + + // Compute the powers of x, y, and z. + int numSamples = static_cast(numIndices); + int twoXDegree = 2 * mXDegree; + int twoYDegree = 2 * mYDegree; + int twoZDegree = 2 * mZDegree; + Array2 xPower(twoXDegree + 1, numSamples); + Array2 yPower(twoYDegree + 1, numSamples); + Array2 zPower(twoZDegree + 1, numSamples); + for (s = 0; s < numSamples; ++s) + { + Real x = observations[indices[s]][0]; + Real y = observations[indices[s]][1]; + Real z = observations[indices[s]][2]; + mXDomain[0] = std::min(x, mXDomain[0]); + mXDomain[1] = std::max(x, mXDomain[1]); + mYDomain[0] = std::min(y, mYDomain[0]); + mYDomain[1] = std::max(y, mYDomain[1]); + mZDomain[0] = std::min(z, mZDomain[0]); + mZDomain[1] = std::max(z, mZDomain[1]); + + xPower[s][0] = (Real)1; + for (i0 = 1; i0 <= twoXDegree; ++i0) + { + xPower[s][i0] = x * xPower[s][i0 - 1]; + } + + yPower[s][0] = (Real)1; + for (j0 = 1; j0 <= twoYDegree; ++j0) + { + yPower[s][j0] = y * yPower[s][j0 - 1]; + } + + zPower[s][0] = (Real)1; + for (k0 = 1; k0 <= twoZDegree; ++k0) + { + zPower[s][k0] = z * zPower[s][k0 - 1]; + } + } + + // Matrix A is the Vandermonde matrix and vector B is the + // right-hand side of the linear system A*X = B. + GMatrix A(mSize, mSize); + GVector B(mSize); + for (k0 = 0; k0 <= mZDegree; ++k0) + { + for (j0 = 0; j0 <= mYDegree; ++j0) + { + for (i0 = 0; i0 <= mXDegree; ++i0) + { + Real sum = (Real)0; + n0 = i0 + mXDegreeP1 * (j0 + mYDegreeP1 * k0); + for (s = 0; s < numSamples; ++s) + { + Real w = observations[indices[s]][3]; + sum += w * xPower[s][i0] * yPower[s][j0] * zPower[s][k0]; + } + + B[n0] = sum; + + for (k1 = 0; k1 <= mZDegree; ++k1) + { + for (j1 = 0; j1 <= mYDegree; ++j1) + { + for (i1 = 0; i1 <= mXDegree; ++i1) + { + sum = (Real)0; + n1 = i1 + mXDegreeP1 * (j1 + mYDegreeP1 * k1); + for (s = 0; s < numSamples; ++s) + { + sum += xPower[s][i0 + i1] * yPower[s][j0 + j1] * zPower[s][k0 + k1]; + } + + A(n0, n1) = sum; + } + } + } + } + } + } + + // Solve for the polynomial coefficients. + GVector coefficients = Inverse(A) * B; + bool hasNonzero = false; + for (int i = 0; i < mSize; ++i) + { + mParameters[i] = coefficients[i]; + if (coefficients[i] != (Real)0) + { + hasNonzero = true; + } + } + return hasNonzero; + } + + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + return false; + } + + // Get the parameters for the best fit. + std::vector const& GetParameters() const + { + return mParameters; + } + + virtual size_t GetMinimumRequired() const override + { + return static_cast(mSize); + } + + // Compute the model error for the specified observation for the + // current model parameters. The returned value for observation + // (x0,y0,z0,w0) is |w(x0,y0,z0) - w0|, where w(x,y,z) is the fitted + // polynomial. + virtual Real Error(std::array const& observation) const override + { + Real w = Evaluate(observation[0], observation[1], observation[2]); + Real error = std::fabs(w - observation[3]); + return error; + } + + virtual void CopyParameters(ApprQuery> const* input) override + { + auto source = dynamic_cast(input); + if (source) + { + *this = *source; + } + } + + // Evaluate the polynomial. The domain intervals are provided so you + // can interpolate ((x,y,z) in domain) or extrapolate ((x,y,z) not in + // domain). + std::array const& GetXDomain() const + { + return mXDomain; + } + + std::array const& GetYDomain() const + { + return mYDomain; + } + + std::array const& GetZDomain() const + { + return mZDomain; + } + + Real Evaluate(Real x, Real y, Real z) const + { + int i0, i1, i2; + Real w; + + for (i2 = 0; i2 <= mZDegree; ++i2) + { + for (i1 = 0; i1 <= mYDegree; ++i1) + { + i0 = mXDegree; + w = mParameters[i0 + mXDegreeP1 * (i1 + mYDegreeP1 * i2)]; + while (--i0 >= 0) + { + w = mParameters[i0 + mXDegreeP1 * (i1 + mYDegreeP1 * i2)] + w * x; + } + mYZCoefficient[i1 + mYDegree * i2] = w; + } + } + + for (i2 = 0; i2 <= mZDegree; ++i2) + { + i1 = mYDegree; + w = mYZCoefficient[i1 + mYDegreeP1 * i2]; + while (--i1 >= 0) + { + w = mParameters[i1 + mYDegreeP1 * i2] + w * y; + } + mZCoefficient[i2] = w; + } + + i2 = mZDegree; + w = mZCoefficient[i2]; + while (--i2 >= 0) + { + w = mZCoefficient[i2] + w * z; + } + + return w; + } + + private: + int mXDegree, mYDegree, mZDegree; + int mXDegreeP1, mYDegreeP1, mZDegreeP1, mSize; + std::array mXDomain, mYDomain, mZDomain; + std::vector mParameters; + + // These arrays are used by Evaluate() to avoid reallocation of the + // 'vector's for each call. The member is mutable because, to the + // user, the call to Evaluate does not modify the polynomial. + mutable std::vector mYZCoefficient; + mutable std::vector mZCoefficient; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprPolynomialSpecial2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprPolynomialSpecial2.h new file mode 100644 index 0000000..c639ff1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprPolynomialSpecial2.h @@ -0,0 +1,272 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// Fit the data with a polynomial of the form +// w = sum_{i=0}^{n-1} c[i]*x^{p[i]} +// where p[i] are distinct nonnegative powers provided by the caller. A +// least-squares fitting algorithm is used, but the input data is first +// mapped to (x,w) in [-1,1]^2 for numerical robustness. + +namespace WwiseGTE +{ + template + class ApprPolynomialSpecial2 : public ApprQuery> + { + public: + // Initialize the model parameters to zero. The degrees must be + // nonnegative and strictly increasing. + ApprPolynomialSpecial2(std::vector const& degrees) + : + mDegrees(degrees), + mParameters(degrees.size(), (Real)0) + { +#if !defined(GTE_NO_LOGGER) + LogAssert(mDegrees.size() > 0, "The input array must have elements."); + int lastDegree = -1; + for (auto degree : mDegrees) + { + LogAssert(degree > lastDegree, "Degrees must be increasing."); + lastDegree = degree; + } +#endif + + mXDomain[0] = std::numeric_limits::max(); + mXDomain[1] = -mXDomain[0]; + mWDomain[0] = std::numeric_limits::max(); + mWDomain[1] = -mWDomain[0]; + + mScale[0] = (Real)0; + mScale[1] = (Real)0; + mInvTwoWScale = (Real)0; + + // Powers of x are computed up to twice the powers when + // constructing the fitted polynomial. Powers of x are computed + // up to the powers for the evaluation of the fitted polynomial. + mXPowers.resize(2 * mDegrees.back() + 1); + mXPowers[0] = (Real)1; + } + + // Basic fitting algorithm. See ApprQuery.h for the various Fit(...) + // functions that you can call. + virtual bool FitIndexed( + size_t numObservations, std::array const* observations, + size_t numIndices, int const* indices) override + { + if (this->ValidIndices(numObservations, observations, numIndices, indices)) + { + // Transform the observations to [-1,1]^2 for numerical + // robustness. + std::vector> transformed; + Transform(observations, numIndices, indices, transformed); + + // Fit the transformed data using a least-squares algorithm. + return DoLeastSquares(transformed); + } + + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + return false; + } + + // Get the parameters for the best fit. + std::vector const& GetParameters() const + { + return mParameters; + } + + virtual size_t GetMinimumRequired() const override + { + return mParameters.size(); + } + + // Compute the model error for the specified observation for the + // current model parameters. The returned value for observation + // (x0,w0) is |w(x0) - w0|, where w(x) is the fitted polynomial. + virtual Real Error(std::array const& observation) const override + { + Real w = Evaluate(observation[0]); + Real error = std::fabs(w - observation[1]); + return error; + } + + virtual void CopyParameters(ApprQuery> const* input) override + { + auto source = dynamic_cast(input); + if (source) + { + *this = *source; + } + } + + // Evaluate the polynomial. The domain interval is provided so you can + // interpolate (x in domain) or extrapolate (x not in domain). + std::array const& GetXDomain() const + { + return mXDomain; + } + + Real Evaluate(Real x) const + { + // Transform x to x' in [-1,1]. + x = (Real)-1 + (Real)2 * mScale[0] * (x - mXDomain[0]); + + // Compute relevant powers of x. + int jmax = mDegrees.back(); + for (int j = 1; j <= jmax; ++j) + { + mXPowers[j] = mXPowers[j - 1] * x; + } + + Real w = (Real)0; + int isup = static_cast(mDegrees.size()); + for (int i = 0; i < isup; ++i) + { + Real xp = mXPowers[mDegrees[i]]; + w += mParameters[i] * xp; + } + + // Transform w from [-1,1] back to the original space. + w = (w + (Real)1) * mInvTwoWScale + mWDomain[0]; + return w; + } + + private: + // Transform the (x,w) values to (x',w') in [-1,1]^2. + void Transform(std::array const* observations, size_t numIndices, + int const* indices, std::vector>& transformed) + { + int numSamples = static_cast(numIndices); + transformed.resize(numSamples); + + std::array omin = observations[indices[0]]; + std::array omax = omin; + std::array obs; + int s, i; + for (s = 1; s < numSamples; ++s) + { + obs = observations[indices[s]]; + for (i = 0; i < 2; ++i) + { + if (obs[i] < omin[i]) + { + omin[i] = obs[i]; + } + else if (obs[i] > omax[i]) + { + omax[i] = obs[i]; + } + } + } + + mXDomain[0] = omin[0]; + mXDomain[1] = omax[0]; + mWDomain[0] = omin[1]; + mWDomain[1] = omax[1]; + for (i = 0; i < 2; ++i) + { + mScale[i] = (Real)1 / (omax[i] - omin[i]); + } + + for (s = 0; s < numSamples; ++s) + { + obs = observations[indices[s]]; + for (i = 0; i < 2; ++i) + { + transformed[s][i] = (Real)-1 + (Real)2 * mScale[i] * (obs[i] - omin[i]); + } + } + mInvTwoWScale = (Real)0.5 / mScale[1]; + } + + // The least-squares fitting algorithm for the transformed data. + bool DoLeastSquares(std::vector> & transformed) + { + // Set up a linear system A*X = B, where X are the polynomial + // coefficients. + int size = static_cast(mDegrees.size()); + GMatrix A(size, size); + A.MakeZero(); + GVector B(size); + B.MakeZero(); + + int numSamples = static_cast(transformed.size()); + int twoMaxXDegree = 2 * mDegrees.back(); + int row, col; + for (int i = 0; i < numSamples; ++i) + { + // Compute relevant powers of x. + Real x = transformed[i][0]; + Real w = transformed[i][1]; + for (int j = 0; j <= twoMaxXDegree; ++j) + { + mXPowers[j] = mXPowers[j - 1] * x; + } + + for (row = 0; row < size; ++row) + { + // Update the upper-triangular portion of the symmetric + // matrix. + for (col = row; col < size; ++col) + { + A(row, col) += mXPowers[mDegrees[row] + mDegrees[col]]; + } + + // Update the right-hand side of the system. + B[row] += mXPowers[mDegrees[row]] * w; + } + } + + // Copy the upper-triangular portion of the symmetric matrix to + // the lower-triangular portion. + for (row = 0; row < size; ++row) + { + for (col = 0; col < row; ++col) + { + A(row, col) = A(col, row); + } + } + + // Precondition by normalizing the sums. + Real invNumSamples = (Real)1 / (Real)numSamples; + A *= invNumSamples; + B *= invNumSamples; + + // Solve for the polynomial coefficients. + GVector coefficients = Inverse(A) * B; + bool hasNonzero = false; + for (int i = 0; i < size; ++i) + { + mParameters[i] = coefficients[i]; + if (coefficients[i] != (Real)0) + { + hasNonzero = true; + } + } + return hasNonzero; + } + + std::vector mDegrees; + std::vector mParameters; + + // Support for evaluation. The coefficients were generated for the + // samples mapped to [-1,1]^2. The Evaluate() function must transform + // x to x' in [-1,1], compute w' in [-1,1], then transform w' to w. + std::array mXDomain, mWDomain; + std::array mScale; + Real mInvTwoWScale; + + // This array is used by Evaluate() to avoid reallocation of the + // 'vector' for each call. The member is mutable because, to the + // user, the call to Evaluate does not modify the polynomial. + mutable std::vector mXPowers; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprPolynomialSpecial3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprPolynomialSpecial3.h new file mode 100644 index 0000000..1136cee --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprPolynomialSpecial3.h @@ -0,0 +1,319 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// Fit the data with a polynomial of the form +// w = sum_{i=0}^{n-1} c[i]*x^{p[i]}*y^{q[i]} +// where are distinct pairs of nonnegative powers provided by the +// caller. A least-squares fitting algorithm is used, but the input data is +// first mapped to (x,y,w) in [-1,1]^3 for numerical robustness. + +namespace WwiseGTE +{ + template + class ApprPolynomialSpecial3 : public ApprQuery> + { + public: + // Initialize the model parameters to zero. The degrees must be + // nonnegative and strictly increasing. + ApprPolynomialSpecial3(std::vector const& xDegrees, + std::vector const& yDegrees) + : + mXDegrees(xDegrees), + mYDegrees(yDegrees), + mParameters(mXDegrees.size() * mYDegrees.size(), (Real)0) + { +#if !defined(GTE_NO_LOGGER) + LogAssert(mXDegrees.size() == mYDegrees.size(), + "The input arrays must have the same size."); + + LogAssert(mXDegrees.size() > 0, "The input array must have elements."); + int lastDegree = -1; + for (auto degree : mXDegrees) + { + LogAssert(degree > lastDegree, "Degrees must be increasing."); + lastDegree = degree; + } + + LogAssert(mYDegrees.size() > 0, "The input array must have elements."); + lastDegree = -1; + for (auto degree : mYDegrees) + { + LogAssert(degree > lastDegree, "Degrees must be increasing."); + lastDegree = degree; + } +#endif + + mXDomain[0] = std::numeric_limits::max(); + mXDomain[1] = -mXDomain[0]; + mYDomain[0] = std::numeric_limits::max(); + mYDomain[1] = -mYDomain[0]; + mWDomain[0] = std::numeric_limits::max(); + mWDomain[1] = -mWDomain[0]; + + mScale[0] = (Real)0; + mScale[1] = (Real)0; + mScale[2] = (Real)0; + mInvTwoWScale = (Real)0; + + // Powers of x and y are computed up to twice the powers when + // constructing the fitted polynomial. Powers of x and y are + // computed up to the powers for the evaluation of the fitted + // polynomial. + mXPowers.resize(2 * mXDegrees.back() + 1); + mXPowers[0] = (Real)1; + mYPowers.resize(2 * mYDegrees.back() + 1); + mYPowers[0] = (Real)1; + } + + // Basic fitting algorithm. See ApprQuery.h for the various Fit(...) + // functions that you can call. + virtual bool FitIndexed( + size_t numObservations, std::array const* observations, + size_t numIndices, int const* indices) override + { + if (this->ValidIndices(numObservations, observations, numIndices, indices)) + { + // Transform the observations to [-1,1]^3 for numerical + // robustness. + std::vector> transformed; + Transform(observations, numIndices, indices, transformed); + + // Fit the transformed data using a least-squares algorithm. + return DoLeastSquares(transformed); + } + + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + return false; + } + + // Get the parameters for the best fit. + std::vector const& GetParameters() const + { + return mParameters; + } + + virtual size_t GetMinimumRequired() const override + { + return mParameters.size(); + } + + // Compute the model error for the specified observation for the + // current model parameters. The returned value for observation + // (x0,y0,w0) is |w(x0,y0) - w0|, where w(x,y) is the fitted + // polynomial. + virtual Real Error(std::array const& observation) const override + { + Real w = Evaluate(observation[0], observation[1]); + Real error = std::fabs(w - observation[2]); + return error; + } + + virtual void CopyParameters(ApprQuery> const* input) override + { + auto source = dynamic_cast(input); + if (source) + { + *this = *source; + } + } + + // Evaluate the polynomial. The domain interval is provided so you can + // interpolate ((x,y) in domain) or extrapolate ((x,y) not in domain). + std::array const& GetXDomain() const + { + return mXDomain; + } + + std::array const& GetYDomain() const + { + return mYDomain; + } + + Real Evaluate(Real x, Real y) const + { + // Transform (x,y) to (x',y') in [-1,1]^2. + x = (Real)-1 + (Real)2 * mScale[0] * (x - mXDomain[0]); + y = (Real)-1 + (Real)2 * mScale[1] * (y - mYDomain[0]); + + // Compute relevant powers of x and y. + int jmax = mXDegrees.back(); + for (int j = 1; j <= jmax; ++j) + { + mXPowers[j] = mXPowers[j - 1] * x; + } + + jmax = mYDegrees.back(); + for (int j = 1; j <= jmax; ++j) + { + mYPowers[j] = mYPowers[j - 1] * y; + } + + Real w = (Real)0; + int isup = static_cast(mXDegrees.size()); + for (int i = 0; i < isup; ++i) + { + Real xp = mXPowers[mXDegrees[i]]; + Real yp = mYPowers[mYDegrees[i]]; + w += mParameters[i] * xp * yp; + } + + // Transform w from [-1,1] back to the original space. + w = (w + (Real)1) * mInvTwoWScale + mWDomain[0]; + return w; + } + + private: + // Transform the (x,y,w) values to (x',y',w') in [-1,1]^3. + void Transform(std::array const* observations, size_t numIndices, + int const* indices, std::vector> & transformed) + { + int numSamples = static_cast(numIndices); + transformed.resize(numSamples); + + std::array omin = observations[indices[0]]; + std::array omax = omin; + std::array obs; + int s, i; + for (s = 1; s < numSamples; ++s) + { + obs = observations[indices[s]]; + for (i = 0; i < 3; ++i) + { + if (obs[i] < omin[i]) + { + omin[i] = obs[i]; + } + else if (obs[i] > omax[i]) + { + omax[i] = obs[i]; + } + } + } + + mXDomain[0] = omin[0]; + mXDomain[1] = omax[0]; + mYDomain[0] = omin[1]; + mYDomain[1] = omax[1]; + mWDomain[0] = omin[2]; + mWDomain[1] = omax[2]; + for (i = 0; i < 3; ++i) + { + mScale[i] = (Real)1 / (omax[i] - omin[i]); + } + + for (s = 0; s < numSamples; ++s) + { + obs = observations[indices[s]]; + for (i = 0; i < 3; ++i) + { + transformed[s][i] = (Real)-1 + (Real)2 * mScale[i] * (obs[i] - omin[i]); + } + } + mInvTwoWScale = (Real)0.5 / mScale[2]; + } + + // The least-squares fitting algorithm for the transformed data. + bool DoLeastSquares(std::vector> & transformed) + { + // Set up a linear system A*X = B, where X are the polynomial + // coefficients. + int size = static_cast(mXDegrees.size()); + GMatrix A(size, size); + A.MakeZero(); + GVector B(size); + B.MakeZero(); + + int numSamples = static_cast(transformed.size()); + int twoMaxXDegree = 2 * mXDegrees.back(); + int twoMaxYDegree = 2 * mYDegrees.back(); + int row, col; + for (int i = 0; i < numSamples; ++i) + { + // Compute relevant powers of x and y. + Real x = transformed[i][0]; + Real y = transformed[i][1]; + Real w = transformed[i][2]; + for (int j = 1; j <= 2 * twoMaxXDegree; ++j) + { + mXPowers[j] = mXPowers[j - 1] * x; + } + for (int j = 1; j <= 2 * twoMaxYDegree; ++j) + { + mYPowers[j] = mYPowers[j - 1] * y; + } + + for (row = 0; row < size; ++row) + { + // Update the upper-triangular portion of the symmetric + // matrix. + Real xp, yp; + for (col = row; col < size; ++col) + { + xp = mXPowers[mXDegrees[row] + mXDegrees[col]]; + yp = mYPowers[mYDegrees[row] + mYDegrees[col]]; + A(row, col) += xp * yp; + } + + // Update the right-hand side of the system. + xp = mXPowers[mXDegrees[row]]; + yp = mYPowers[mYDegrees[row]]; + B[row] += xp * yp * w; + } + } + + // Copy the upper-triangular portion of the symmetric matrix to + // the lower-triangular portion. + for (row = 0; row < size; ++row) + { + for (col = 0; col < row; ++col) + { + A(row, col) = A(col, row); + } + } + + // Precondition by normalizing the sums. + Real invNumSamples = (Real)1 / (Real)numSamples; + A *= invNumSamples; + B *= invNumSamples; + + // Solve for the polynomial coefficients. + GVector coefficients = Inverse(A) * B; + bool hasNonzero = false; + for (int i = 0; i < size; ++i) + { + mParameters[i] = coefficients[i]; + if (coefficients[i] != (Real)0) + { + hasNonzero = true; + } + } + return hasNonzero; + } + + std::vector mXDegrees, mYDegrees; + std::vector mParameters; + + // Support for evaluation. The coefficients were generated for the + // samples mapped to [-1,1]^3. The Evaluate() function must + // transform (x,y) to (x',y') in [-1,1]^2, compute w' in [-1,1], then + // transform w' to w. + std::array mXDomain, mYDomain, mWDomain; + std::array mScale; + Real mInvTwoWScale; + + // This array is used by Evaluate() to avoid reallocation of the + // 'vector's for each call. The members are mutable because, to the + // user, the call to Evaluate does not modify the polynomial. + mutable std::vector mXPowers, mYPowers; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprPolynomialSpecial4.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprPolynomialSpecial4.h new file mode 100644 index 0000000..1c1268f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprPolynomialSpecial4.h @@ -0,0 +1,358 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// Fit the data with a polynomial of the form +// w = sum_{i=0}^{n-1} c[i]*x^{p[i]}*y^{q[i]}*z^{r[i]} +// where are distinct triples of nonnegative powers provided +// by the caller. A least-squares fitting algorithm is used, but the input +// data is first mapped to (x,y,z,w) in [-1,1]^4 for numerical robustness. + +namespace WwiseGTE +{ + template + class ApprPolynomialSpecial4 : public ApprQuery> + { + public: + // Initialize the model parameters to zero. The degrees must be + // nonnegative and strictly increasing. + ApprPolynomialSpecial4(std::vector const& xDegrees, + std::vector const& yDegrees, std::vector const& zDegrees) + : + mXDegrees(xDegrees), + mYDegrees(yDegrees), + mZDegrees(zDegrees), + mParameters(mXDegrees.size() * mYDegrees.size() * mZDegrees.size(), (Real)0) + { +#if !defined(GTE_NO_LOGGER) + LogAssert(mXDegrees.size() == mYDegrees.size() + && mXDegrees.size() == mZDegrees.size(), + "The input arrays must have the same size."); + + LogAssert(mXDegrees.size() > 0, "The input array must have elements."); + int lastDegree = -1; + for (auto degree : mXDegrees) + { + LogAssert(degree > lastDegree, "Degrees must be increasing."); + lastDegree = degree; + } + + LogAssert(mYDegrees.size() > 0, "The input array must have elements."); + lastDegree = -1; + for (auto degree : mYDegrees) + { + LogAssert(degree > lastDegree, "Degrees must be increasing."); + lastDegree = degree; + } + + LogAssert(mZDegrees.size() > 0, "The input array must have elements."); + lastDegree = -1; + for (auto degree : mZDegrees) + { + LogAssert(degree > lastDegree, "Degrees must be increasing."); + lastDegree = degree; + } +#endif + + mXDomain[0] = std::numeric_limits::max(); + mXDomain[1] = -mXDomain[0]; + mYDomain[0] = std::numeric_limits::max(); + mYDomain[1] = -mYDomain[0]; + mZDomain[0] = std::numeric_limits::max(); + mZDomain[1] = -mZDomain[0]; + mWDomain[0] = std::numeric_limits::max(); + mWDomain[1] = -mWDomain[0]; + + mScale[0] = (Real)0; + mScale[1] = (Real)0; + mScale[2] = (Real)0; + mScale[3] = (Real)0; + mInvTwoWScale = (Real)0; + + // Powers of x, y, and z are computed up to twice the powers when + // constructing the fitted polynomial. Powers of x, y, and z are + // computed up to the powers for the evaluation of the fitted + // polynomial. + mXPowers.resize(2 * mXDegrees.back() + 1); + mXPowers[0] = (Real)1; + mYPowers.resize(2 * mYDegrees.back() + 1); + mYPowers[0] = (Real)1; + mZPowers.resize(2 * mZDegrees.back() + 1); + mZPowers[0] = (Real)1; + } + + // Basic fitting algorithm. See ApprQuery.h for the various Fit(...) + // functions that you can call. + virtual bool FitIndexed( + size_t numObservations, std::array const* observations, + size_t numIndices, int const* indices) override + { + if (this->ValidIndices(numObservations, observations, numIndices, indices)) + { + // Transform the observations to [-1,1]^4 for numerical + // robustness. + std::vector> transformed; + Transform(observations, numIndices, indices, transformed); + + // Fit the transformed data using a least-squares algorithm. + return DoLeastSquares(transformed); + } + + std::fill(mParameters.begin(), mParameters.end(), (Real)0); + return false; + } + + // Get the parameters for the best fit. + std::vector const& GetParameters() const + { + return mParameters; + } + + virtual size_t GetMinimumRequired() const override + { + return mParameters.size(); + } + + // Compute the model error for the specified observation for the + // current model parameters. The returned value for observation + // (x0,y0,z0,w0) is |w(x0,y0,z0) - w0|, where w(x,y,z) is the fitted + // polynomial. + virtual Real Error(std::array const& observation) const override + { + Real w = Evaluate(observation[0], observation[1], observation[2]); + Real error = std::fabs(w - observation[3]); + return error; + } + + virtual void CopyParameters(ApprQuery> const* input) override + { + auto source = dynamic_cast(input); + if (source) + { + *this = *source; + } + } + + // Evaluate the polynomial. The domain interval is provided so you can + // interpolate ((x,y,z) in domain) or extrapolate ((x,y,z) not in + // domain). + std::array const& GetXDomain() const + { + return mXDomain; + } + + std::array const& GetYDomain() const + { + return mYDomain; + } + + std::array const& GetZDomain() const + { + return mZDomain; + } + + Real Evaluate(Real x, Real y, Real z) const + { + // Transform (x,y,z) to (x',y',z') in [-1,1]^3. + x = (Real)-1 + (Real)2 * mScale[0] * (x - mXDomain[0]); + y = (Real)-1 + (Real)2 * mScale[1] * (y - mYDomain[0]); + z = (Real)-1 + (Real)2 * mScale[2] * (z - mZDomain[0]); + + // Compute relevant powers of x, y, and z. + int jmax = mXDegrees.back();; + for (int j = 1; j <= jmax; ++j) + { + mXPowers[j] = mXPowers[j - 1] * x; + } + + jmax = mYDegrees.back();; + for (int j = 1; j <= jmax; ++j) + { + mYPowers[j] = mYPowers[j - 1] * y; + } + + jmax = mZDegrees.back();; + for (int j = 1; j <= jmax; ++j) + { + mZPowers[j] = mZPowers[j - 1] * z; + } + + Real w = (Real)0; + int isup = static_cast(mXDegrees.size()); + for (int i = 0; i < isup; ++i) + { + Real xp = mXPowers[mXDegrees[i]]; + Real yp = mYPowers[mYDegrees[i]]; + Real zp = mYPowers[mZDegrees[i]]; + w += mParameters[i] * xp * yp * zp; + } + + // Transform w from [-1,1] back to the original space. + w = (w + (Real)1) * mInvTwoWScale + mWDomain[0]; + return w; + } + + private: + // Transform the (x,y,z,w) values to (x',y',z',w') in [-1,1]^4. + void Transform(std::array const* observations, size_t numIndices, + int const* indices, std::vector> & transformed) + { + int numSamples = static_cast(numIndices); + transformed.resize(numSamples); + + std::array omin = observations[indices[0]]; + std::array omax = omin; + std::array obs; + int s, i; + for (s = 1; s < numSamples; ++s) + { + obs = observations[indices[s]]; + for (i = 0; i < 4; ++i) + { + if (obs[i] < omin[i]) + { + omin[i] = obs[i]; + } + else if (obs[i] > omax[i]) + { + omax[i] = obs[i]; + } + } + } + + mXDomain[0] = omin[0]; + mXDomain[1] = omax[0]; + mYDomain[0] = omin[1]; + mYDomain[1] = omax[1]; + mZDomain[0] = omin[2]; + mZDomain[1] = omax[2]; + mWDomain[0] = omin[3]; + mWDomain[1] = omax[3]; + for (i = 0; i < 4; ++i) + { + mScale[i] = (Real)1 / (omax[i] - omin[i]); + } + + for (s = 0; s < numSamples; ++s) + { + obs = observations[indices[s]]; + for (i = 0; i < 4; ++i) + { + transformed[s][i] = (Real)-1 + (Real)2 * mScale[i] * (obs[i] - omin[i]); + } + } + mInvTwoWScale = (Real)0.5 / mScale[3]; + } + + // The least-squares fitting algorithm for the transformed data. + bool DoLeastSquares(std::vector> & transformed) + { + // Set up a linear system A*X = B, where X are the polynomial + // coefficients. + int size = static_cast(mXDegrees.size()); + GMatrix A(size, size); + A.MakeZero(); + GVector B(size); + B.MakeZero(); + + int numSamples = static_cast(transformed.size()); + int twoMaxXDegree = 2 * mXDegrees.back(); + int twoMaxYDegree = 2 * mYDegrees.back(); + int twoMaxZDegree = 2 * mZDegrees.back(); + int row, col; + for (int i = 0; i < numSamples; ++i) + { + // Compute relevant powers of x, y, and z. + Real x = transformed[i][0]; + Real y = transformed[i][1]; + Real z = transformed[i][2]; + Real w = transformed[i][3]; + for (int j = 1; j <= twoMaxXDegree; ++j) + { + mXPowers[j] = mXPowers[j - 1] * x; + } + for (int j = 1; j <= twoMaxYDegree; ++j) + { + mYPowers[j] = mYPowers[j - 1] * y; + } + for (int j = 1; j <= twoMaxZDegree; ++j) + { + mZPowers[j] = mZPowers[j - 1] * z; + } + + for (row = 0; row < size; ++row) + { + // Update the upper-triangular portion of the symmetric + // matrix. + Real xp, yp, zp; + for (col = row; col < size; ++col) + { + xp = mXPowers[mXDegrees[row] + mXDegrees[col]]; + yp = mYPowers[mYDegrees[row] + mYDegrees[col]]; + zp = mZPowers[mZDegrees[row] + mZDegrees[col]]; + A(row, col) += xp * yp * zp; + } + + // Update the right-hand side of the system. + xp = mXPowers[mXDegrees[row]]; + yp = mYPowers[mYDegrees[row]]; + zp = mZPowers[mZDegrees[row]]; + B[row] += xp * yp * zp * w; + } + } + + // Copy the upper-triangular portion of the symmetric matrix to + // the lower-triangular portion. + for (row = 0; row < size; ++row) + { + for (col = 0; col < row; ++col) + { + A(row, col) = A(col, row); + } + } + + // Precondition by normalizing the sums. + Real invNumSamples = (Real)1 / (Real)numSamples; + A *= invNumSamples; + B *= invNumSamples; + + // Solve for the polynomial coefficients. + GVector coefficients = Inverse(A) * B; + bool hasNonzero = false; + for (int i = 0; i < size; ++i) + { + mParameters[i] = coefficients[i]; + if (coefficients[i] != (Real)0) + { + hasNonzero = true; + } + } + return hasNonzero; + } + + std::vector mXDegrees, mYDegrees, mZDegrees; + std::vector mParameters; + + // Support for evaluation. The coefficients were generated for the + // samples mapped to [-1,1]^4. The Evaluate() function must transform + // (x,y,z) to (x',y',z') in [-1,1]^3, compute w' in [-1,1], then + // transform w' to w. + std::array mXDomain, mYDomain, mZDomain, mWDomain; + std::array mScale; + Real mInvTwoWScale; + + // This array is used by Evaluate() to avoid reallocation of the + // 'vector's for each call. The members are mutable because, to the + // user, the call to Evaluate does not modify the polynomial. + mutable std::vector mXPowers, mYPowers, mZPowers; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprQuadratic2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprQuadratic2.h new file mode 100644 index 0000000..a674c75 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprQuadratic2.h @@ -0,0 +1,203 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + // The quadratic fit is + // 0 = C[0] + C[1]*X + C[2]*Y + C[3]*X^2 + C[4]*Y^2 + C[5]*X*Y + // subject to Length(C) = 1. Minimize E(C) = C^t M C with Length(C) = 1 + // and M = (sum_i V_i)(sum_i V_i)^t where + // V = (1, X, Y, X^2, Y^2, X*Y) + // The minimum value is the smallest eigenvalue of M and C is a + // corresponding unit length eigenvector. + // + // Input: + // n = number of points to fit + // p[0..n-1] = array of points to fit + // + // Output: + // c[0..5] = coefficients of quadratic fit (the eigenvector) + // return value of function is nonnegative and a measure of the fit + // (the minimum eigenvalue; 0 = exact fit, positive otherwise) + // + // Canonical forms. The quadratic equation can be factored into + // P^T A P + B^T P + K = 0 where P = (X,Y,Z), K = C[0], + // B = (C[1],C[2],C[3]), and A is a 3x3 symmetric matrix with + // A00 = C[4], A11 = C[5], A22 = C[6], A01 = C[7]/2, A02 = C[8]/2, + // and A12 = C[9]/2. Matrix A = R^T D R where R is orthogonal and + // D is diagonal (using an eigendecomposition). Define + // V = R P = (v0,v1,v2), E = R B = (e0,e1,e2), D = diag(d0,d1,d2) + // and f = K to obtain + // d0 v0^2 + d1 v1^2 + d2 v^2 + e0 v0 + e1 v1 + e2 v2 + f = 0 + // The characterization depends on the signs of the d_i. + + template + class ApprQuadratic2 + { + public: + Real operator()(int numPoints, Vector2 const* points, Real coefficients[6]) + { + Matrix<6, 6, Real> A; // constructor sets A to zero + for (int i = 0; i < numPoints; ++i) + { + Real x = points[i][0]; + Real y = points[i][1]; + Real x2 = x * x; + Real y2 = y * y; + Real xy = x * y; + Real x3 = x * x2; + Real xy2 = x * y2; + Real x2y = x * xy; + Real y3 = y * y2; + Real x4 = x * x3; + Real x2y2 = x * xy2; + Real x3y = x * x2y; + Real y4 = y * y3; + Real xy3 = x * y3; + + A(0, 1) += x; + A(0, 2) += y; + A(0, 3) += x2; + A(0, 4) += y2; + A(0, 5) += xy; + A(1, 3) += x3; + A(1, 4) += xy2; + A(1, 5) += x2y; + A(2, 4) += y3; + A(3, 3) += x4; + A(3, 4) += x2y2; + A(3, 5) += x3y; + A(4, 4) += y4; + A(4, 5) += xy3; + } + + A(0, 0) = static_cast(numPoints); + A(1, 1) = A(0, 3); + A(1, 2) = A(0, 5); + A(2, 2) = A(0, 4); + A(2, 3) = A(1, 5); + A(2, 5) = A(1, 4); + A(5, 5) = A(3, 4); + + for (int row = 0; row < 6; ++row) + { + for (int col = 0; col < row; ++col) + { + A(row, col) = A(col, row); + } + } + + Real invNumPoints = (Real)1 / static_cast(numPoints); + for (int row = 0; row < 6; ++row) + { + for (int col = 0; col < 6; ++col) + { + A(row, col) *= invNumPoints; + } + } + + SymmetricEigensolver es(6, 1024); + es.Solve(&A[0], +1); + es.GetEigenvector(0, &coefficients[0]); + + // For an exact fit, numeric round-off errors might make the + // minimum eigenvalue just slightly negative. Return the + // absolute value because the application might rely on the + // return value being nonnegative. + return std::fabs(es.GetEigenvalue(0)); + } + }; + + + // If you think your points are nearly circular, use this. The circle is + // of the form C'[0]+C'[1]*X+C'[2]*Y+C'[3]*(X^2+Y^2), where + // Length(C') = 1. The function returns + // C = (C'[0]/C'[3],C'[1]/C'[3],C'[2]/C'[3]), so the fitted circle is + // C[0]+C[1]*X+C[2]*Y+X^2+Y^2. The center is (xc,yc) = -0.5*(C[1],C[2]) + // and the radius is r = sqrt(xc*xc+yc*yc-C[0]). + + template + class ApprQuadraticCircle2 + { + public: + Real operator()(int numPoints, Vector2 const* points, Circle2& circle) + { + Matrix<4, 4, Real> A; // constructor sets A to zero + for (int i = 0; i < numPoints; ++i) + { + Real x = points[i][0]; + Real y = points[i][1]; + Real x2 = x * x; + Real y2 = y * y; + Real xy = x * y; + Real r2 = x2 + y2; + Real xr2 = x * r2; + Real yr2 = y * r2; + Real r4 = r2 * r2; + + A(0, 1) += x; + A(0, 2) += y; + A(0, 3) += r2; + A(1, 1) += x2; + A(1, 2) += xy; + A(1, 3) += xr2; + A(2, 2) += y2; + A(2, 3) += yr2; + A(3, 3) += r4; + } + + A(0, 0) = static_cast(numPoints); + + for (int row = 0; row < 4; ++row) + { + for (int col = 0; col < row; ++col) + { + A(row, col) = A(col, row); + } + } + + Real invNumPoints = (Real)1 / static_cast(numPoints); + for (int row = 0; row < 4; ++row) + { + for (int col = 0; col < 4; ++col) + { + A(row, col) *= invNumPoints; + } + } + + SymmetricEigensolver es(4, 1024); + es.Solve(&A[0], +1); + Vector<4, Real> evector; + es.GetEigenvector(0, &evector[0]); + + // TODO: Guard against zero divide? + Real inv = (Real)1 / evector[3]; + Real coefficients[3]; + for (int row = 0; row < 3; ++row) + { + coefficients[row] = inv * evector[row]; + } + + circle.center[0] = (Real)-0.5 * coefficients[1]; + circle.center[1] = (Real)-0.5 * coefficients[2]; + circle.radius = std::sqrt(std::fabs(Dot(circle.center, circle.center) - coefficients[0])); + + // For an exact fit, numeric round-off errors might make the + // minimum eigenvalue just slightly negative. Return the + // absolute value because the application might rely on the + // return value being nonnegative. + return std::fabs(es.GetEigenvalue(0)); + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprQuadratic3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprQuadratic3.h new file mode 100644 index 0000000..11adfc0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprQuadratic3.h @@ -0,0 +1,269 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + // The quadratic fit is + // 0 = C[0] + C[1]*X + C[2]*Y + C[3]*Z + C[4]*X^2 + C[5]*Y^2 + // + C[6]*Z^2 + C[7]*X*Y + C[8]*X*Z + C[9]*Y*Z + // subject to Length(C) = 1. Minimize E(C) = C^t M C with Length(C) = 1 + // and M = (sum_i V_i)(sum_i V_i)^t where + // V = (1, X, Y, Z, X^2, Y^2, Z^2, X*Y, X*Z, Y*Z) + // The minimum value is the smallest eigenvalue of M and C is a + // corresponding unit length eigenvector. + // + // Input: + // n = number of points to fit + // p[0..n-1] = array of points to fit + // + // Output: + // c[0..9] = coefficients of quadratic fit (the eigenvector) + // return value of function is nonnegative and a measure of the fit + // (the minimum eigenvalue; 0 = exact fit, positive otherwise) + // + // Canonical forms. The quadratic equation can be factored into + // P^T A P + B^T P + K = 0 where P = (X,Y,Z), K = C[0], + // B = (C[1],C[2],C[3]), and A is a 3x3 symmetric matrix with + // A00 = C[4], A11 = C[5], A22 = C[6], A01 = C[7]/2, A02 = C[8]/2, and + // A12 = C[9]/2. Matrix A = R^T D R where R is orthogonal and D is + // diagonal (using an eigendecomposition). Define V = R P = (v0,v1,v2), + // E = R B = (e0,e1,e2), D = diag(d0,d1,d2), and f = K to obtain + // d0 v0^2 + d1 v1^2 + d2 v^2 + e0 v0 + e1 v1 + e2 v2 + f = 0 + // The characterization depends on the signs of the d_i. + + template + class ApprQuadratic3 + { + public: + Real operator()(int numPoints, Vector3 const* points, Real coefficients[10]) + { + Matrix<10, 10, Real> A; // constructor sets A to zero + for (int i = 0; i < numPoints; ++i) + { + Real x = points[i][0]; + Real y = points[i][1]; + Real z = points[i][2]; + Real x2 = x * x; + Real y2 = y * y; + Real z2 = z * z; + Real xy = x * y; + Real xz = x * z; + Real yz = y * z; + Real x3 = x * x2; + Real xy2 = x * y2; + Real xz2 = x * z2; + Real x2y = x * xy; + Real x2z = x * xz; + Real xyz = x * y * z; + Real y3 = y * y2; + Real yz2 = y * z2; + Real y2z = y * yz; + Real z3 = z * z2; + Real x4 = x * x3; + Real x2y2 = x * xy2; + Real x2z2 = x * xz2; + Real x3y = x * x2y; + Real x3z = x * x2z; + Real x2yz = x * xyz; + Real y4 = y * y3; + Real y2z2 = y * yz2; + Real xy3 = x * y3; + Real xy2z = x * y2z; + Real y3z = y * y2z; + Real z4 = z * z3; + Real xyz2 = x * yz2; + Real xz3 = x * z3; + Real yz3 = y * z3; + + A(0, 1) += x; + A(0, 2) += y; + A(0, 3) += z; + A(0, 4) += x2; + A(0, 5) += y2; + A(0, 6) += z2; + A(0, 7) += xy; + A(0, 8) += xz; + A(0, 9) += yz; + A(1, 4) += x3; + A(1, 5) += xy2; + A(1, 6) += xz2; + A(1, 7) += x2y; + A(1, 8) += x2z; + A(1, 9) += xyz; + A(2, 5) += y3; + A(2, 6) += yz2; + A(2, 9) += y2z; + A(3, 6) += z3; + A(4, 4) += x4; + A(4, 5) += x2y2; + A(4, 6) += x2z2; + A(4, 7) += x3y; + A(4, 8) += x3z; + A(4, 9) += x2yz; + A(5, 5) += y4; + A(5, 6) += y2z2; + A(5, 7) += xy3; + A(5, 8) += xy2z; + A(5, 9) += y3z; + A(6, 6) += z4; + A(6, 7) += xyz2; + A(6, 8) += xz3; + A(6, 9) += yz3; + A(9, 9) += y2z2; + } + + A(0, 0) = static_cast(numPoints); + A(1, 1) = A(0, 4); + A(1, 2) = A(0, 7); + A(1, 3) = A(0, 8); + A(2, 2) = A(0, 5); + A(2, 3) = A(0, 9); + A(2, 4) = A(1, 7); + A(2, 7) = A(1, 5); + A(2, 8) = A(1, 9); + A(3, 3) = A(0, 6); + A(3, 4) = A(1, 8); + A(3, 5) = A(2, 9); + A(3, 7) = A(1, 9); + A(3, 8) = A(1, 6); + A(3, 9) = A(2, 6); + A(7, 7) = A(4, 5); + A(7, 8) = A(4, 9); + A(7, 9) = A(5, 8); + A(8, 8) = A(4, 6); + A(8, 9) = A(6, 7); + A(9, 9) = A(5, 6); + + for (int row = 0; row < 10; ++row) + { + for (int col = 0; col < row; ++col) + { + A(row, col) = A(col, row); + } + } + + Real invNumPoints = (Real)1 / static_cast(numPoints); + for (int row = 0; row < 10; ++row) + { + for (int col = 0; col < 10; ++col) + { + A(row, col) *= invNumPoints; + } + } + + SymmetricEigensolver es(10, 1024); + es.Solve(&A[0], +1); + es.GetEigenvector(0, &coefficients[0]); + + // For an exact fit, numeric round-off errors might make the + // minimum eigenvalue just slightly negative. Return the absolute + // value because the application might rely on the return value + // being nonnegative. + return std::fabs(es.GetEigenvalue(0)); + } + }; + + + // If you think your points are nearly spherical, use this. The sphere is + // of form C'[0]+C'[1]*X+C'[2]*Y+C'[3]*Z+C'[4]*(X^2+Y^2+Z^2) where + // Length(C') = 1. The function returns + // C = (C'[0]/C'[4],C'[1]/C'[4],C'[2]/C'[4],C'[3]/C'[4]), so the fitted + // sphere is C[0]+C[1]*X+C[2]*Y+C[3]*Z+X^2+Y^2+Z^2. The center is + // (xc,yc,zc) = -0.5*(C[1],C[2],C[3]) and the radius is + // r = sqrt(xc*xc+yc*yc+zc*zc-C[0]). + template + class ApprQuadraticSphere3 + { + public: + Real operator()(int numPoints, Vector3 const* points, Sphere3& sphere) + { + Matrix<5, 5, Real> A; // constructor sets A to zero + for (int i = 0; i < numPoints; ++i) + { + Real x = points[i][0]; + Real y = points[i][1]; + Real z = points[i][2]; + Real x2 = x * x; + Real y2 = y * y; + Real z2 = z * z; + Real xy = x * y; + Real xz = x * z; + Real yz = y * z; + Real r2 = x2 + y2 + z2; + Real xr2 = x * r2; + Real yr2 = y * r2; + Real zr2 = z * r2; + Real r4 = r2 * r2; + + A(0, 1) += x; + A(0, 2) += y; + A(0, 3) += z; + A(0, 4) += r2; + A(1, 1) += x2; + A(1, 2) += xy; + A(1, 3) += xz; + A(1, 4) += xr2; + A(2, 2) += y2; + A(2, 3) += yz; + A(2, 4) += yr2; + A(3, 3) += z2; + A(3, 4) += zr2; + A(4, 4) += r4; + } + + A(0, 0) = static_cast(numPoints); + + for (int row = 0; row < 5; ++row) + { + for (int col = 0; col < row; ++col) + { + A(row, col) = A(col, row); + } + } + + Real invNumPoints = (Real)1 / static_cast(numPoints); + for (int row = 0; row < 5; ++row) + { + for (int col = 0; col < 5; ++col) + { + A(row, col) *= invNumPoints; + } + } + + SymmetricEigensolver es(5, 1024); + es.Solve(&A[0], +1); + Vector<5, Real> evector; + es.GetEigenvector(0, &evector[0]); + + // TODO: Guard against zero divide? + Real inv = (Real)1 / evector[4]; + Real coefficients[4]; + for (int row = 0; row < 4; ++row) + { + coefficients[row] = inv * evector[row]; + } + + sphere.center[0] = (Real)-0.5 * coefficients[1]; + sphere.center[1] = (Real)-0.5 * coefficients[2]; + sphere.center[2] = (Real)-0.5 * coefficients[3]; + sphere.radius = std::sqrt(std::fabs(Dot(sphere.center, sphere.center) - coefficients[0])); + + // For an exact fit, numeric round-off errors might make the + // minimum eigenvalue just slightly negative. Return the + // absolute value because the application might rely on the + // return value being nonnegative. + return std::fabs(es.GetEigenvalue(0)); + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprQuery.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprQuery.h new file mode 100644 index 0000000..10542d3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprQuery.h @@ -0,0 +1,215 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +// Base class support for least-squares fitting algorithms and for RANSAC +// algorithms. + +// Expose this define if you want the code to verify that the incoming +// indices to the fitting functions are valid. +#define GTE_APPR_QUERY_VALIDATE_INDICES + +namespace WwiseGTE +{ + template + class ApprQuery + { + public: + // Construction and destruction. + ApprQuery() = default; + virtual ~ApprQuery() = default; + + // The base-class Fit* functions are generic but need to call the + // indexed fitting function for the specific derived class. + virtual bool FitIndexed( + size_t numObservations, ObservationType const* observations, + size_t numIndices, int const* indices) = 0; + + bool ValidIndices( + size_t numObservations, ObservationType const* observations, + size_t numIndices, int const* indices) + { +#if defined(GTE_APPR_QUERY_VALIDATE_INDICES) + if (observations && indices && + GetMinimumRequired() <= numIndices && numIndices <= numObservations) + { + int const* currentIndex = indices; + for (size_t i = 0; i < numIndices; ++i) + { + if (*currentIndex++ >= static_cast(numObservations)) + { + return false; + } + } + return true; + } + return false; +#else + // The caller is responsible for passing correctly formed data. + (void)numObservations; + (void)observations; + (void)numIndices; + (void)indices; + return true; +#endif + } + + // Estimate the model parameters for all observations passed in via + // raw pointers. + bool Fit(size_t numObservations, ObservationType const* observations) + { + std::vector indices(numObservations); + std::iota(indices.begin(), indices.end(), 0); + return FitIndexed(numObservations, observations, indices.size(), indices.data()); + } + + // Estimate the model parameters for all observations passed in via + // std::vector. + bool Fit(std::vector const& observations) + { + std::vector indices(observations.size()); + std::iota(indices.begin(), indices.end(), 0); + return FitIndexed(observations.size(), observations.data(), indices.size(), indices.data()); + } + + // Estimate the model parameters for a contiguous subset of + // observations. + bool Fit(std::vector const& observations, size_t imin, size_t imax) + { + if (imin <= imax) + { + size_t numIndices = static_cast(imax - imin + 1); + std::vector indices(numIndices); + std::iota(indices.begin(), indices.end(), static_cast(imin)); + return FitIndexed(observations.size(), observations.data(), indices.size(), indices.data()); + } + else + { + return false; + } + } + + // Estimate the model parameters for an indexed subset of observations. + virtual bool Fit(std::vector const& observations, + std::vector const& indices) + { + return FitIndexed(observations.size(), observations.data(), indices.size(), indices.data()); + } + + // Estimate the model parameters for the subset of observations + // specified by the indices and the number of indices that is possibly + // smaller than indices.size(). + bool Fit(std::vector const& observations, + std::vector const& indices, size_t numIndices) + { + size_t imax = std::min(numIndices, indices.size()); + std::vector localindices(imax); + std::copy(indices.begin(), indices.begin() + imax, localindices.begin()); + return FitIndexed(observations.size(), observations.data(), localindices.size(), localindices.data()); + } + + + // Apply the RANdom SAmple Consensus algorithm for fitting a model to + // observations. The algorithm requires three virtual functions to be + // implemented by the derived classes. + + // The minimum number of observations required to fit the model. + virtual size_t GetMinimumRequired() const = 0; + + // Compute the model error for the specified observation for the + // current model parameters. + virtual Real Error(ObservationType const& observation) const = 0; + + // Copy the parameters between two models. This is used to copy the + // candidate-model parameters to the current best-fit model. + virtual void CopyParameters(ApprQuery const* input) = 0; + + static bool RANSAC(ApprQuery& candidateModel, std::vector const& observations, + size_t numRequiredForGoodFit, Real maxErrorForGoodFit, size_t numIterations, + std::vector& bestConsensus, ApprQuery& bestModel) + { + size_t const numObservations = observations.size(); + size_t const minRequired = candidateModel.GetMinimumRequired(); + if (numObservations < minRequired) + { + // Too few observations for model fitting. + return false; + } + + // The first part of the array will store the consensus set, + // initially filled with the minimum number of indices that + // correspond to the candidate inliers. The last part will store + // the remaining indices. These points are tested against the + // model and are added to the consensus set when they fit. All + // the index manipulation is done in place. Initially, the + // candidates are the identity permutation. + std::vector candidates(numObservations); + std::iota(candidates.begin(), candidates.end(), 0); + + if (numObservations == minRequired) + { + // We have the minimum number of observations to generate the + // model, so RANSAC cannot be used. Compute the model with the + // entire set of observations. + bestConsensus = candidates; + return bestModel.Fit(observations); + } + + size_t bestNumFittedObservations = minRequired; + + for (size_t i = 0; i < numIterations; ++i) + { + // Randomly permute the previous candidates, partitioning the + // array into GetMinimumRequired() indices (the candidate + // inliers) followed by the remaining indices (candidates for + // testing against the model). + std::shuffle(candidates.begin(), candidates.end(), std::default_random_engine()); + + // Fit the model to the inliers. + if (candidateModel.Fit(observations, candidates, minRequired)) + { + // Test each remaining observation whether it fits the + // model. If it does, include it in the consensus set. + size_t numFittedObservations = minRequired; + for (size_t j = minRequired; j < numObservations; ++j) + { + Real error = candidateModel.Error(observations[candidates[j]]); + if (error <= maxErrorForGoodFit) + { + std::swap(candidates[j], candidates[numFittedObservations]); + ++numFittedObservations; + } + } + + if (numFittedObservations >= numRequiredForGoodFit) + { + // We have observations that fit the model. Update the + // best model using the consensus set. + candidateModel.Fit(observations, candidates, numFittedObservations); + if (numFittedObservations > bestNumFittedObservations) + { + // The consensus set is larger than the previous + // consensus set, so its model becomes the best one. + bestModel.CopyParameters(&candidateModel); + bestConsensus.resize(numFittedObservations); + std::copy(candidates.begin(), candidates.begin() + numFittedObservations, bestConsensus.begin()); + bestNumFittedObservations = numFittedObservations; + } + } + } + } + + return bestNumFittedObservations >= numRequiredForGoodFit; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprSphere3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprSphere3.h new file mode 100644 index 0000000..3d06d10 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprSphere3.h @@ -0,0 +1,170 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// Least-squares fit of a sphere to a set of points. The algorithms are +// described in Section 5 of +// https://www.geometrictools.com/Documentation/LeastSquaresFitting.pdf +// FitUsingLengths uses the algorithm of Section 5.1. +// FitUsingSquaredLengths uses the algorithm of Section 5.2. + +namespace WwiseGTE +{ + template + class ApprSphere3 + { + public: + // The return value is 'true' when the linear system of the algorithm + // is solvable, 'false' otherwise. If 'false' is returned, the sphere + // center and radius are set to zero values. + bool FitUsingSquaredLengths(int numPoints, Vector3 const* points, Sphere3& sphere) + { + // Compute the average of the data points. + Real const zero(0); + Vector3 A = { zero, zero, zero }; + for (int i = 0; i < numPoints; ++i) + { + A += points[i]; + } + Real invNumPoints = ((Real)1) / static_cast(numPoints); + A *= invNumPoints; + + // Compute the covariance matrix M of the Y[i] = X[i]-A and the + // right-hand side R of the linear system M*(C-A) = R. + Real M00 = zero, M01 = zero, M02 = zero, M11 = zero, M12 = zero, M22 = zero; + Vector3 R = { zero, zero, zero }; + for (int i = 0; i < numPoints; ++i) + { + Vector3 Y = points[i] - A; + Real Y0Y0 = Y[0] * Y[0]; + Real Y0Y1 = Y[0] * Y[1]; + Real Y0Y2 = Y[0] * Y[2]; + Real Y1Y1 = Y[1] * Y[1]; + Real Y1Y2 = Y[1] * Y[2]; + Real Y2Y2 = Y[2] * Y[2]; + M00 += Y0Y0; + M01 += Y0Y1; + M02 += Y0Y2; + M11 += Y1Y1; + M12 += Y1Y2; + M22 += Y2Y2; + R += (Y0Y0 + Y1Y1 + Y2Y2) * Y; + } + R *= (Real)0.5; + + // Solve the linear system M*(C-A) = R for the center C. + Real cof00 = M11 * M22 - M12 * M12; + Real cof01 = M02 * M12 - M01 * M22; + Real cof02 = M01 * M12 - M02 * M11; + Real det = M00 * cof00 + M01 * cof01 + M02 * cof02; + if (det != zero) + { + Real cof11 = M00 * M22 - M02 * M02; + Real cof12 = M01 * M02 - M00 * M12; + Real cof22 = M00 * M11 - M01 * M01; + sphere.center[0] = A[0] + (cof00 * R[0] + cof01 * R[1] + cof02 * R[2]) / det; + sphere.center[1] = A[1] + (cof01 * R[0] + cof11 * R[1] + cof12 * R[2]) / det; + sphere.center[2] = A[2] + (cof02 * R[0] + cof12 * R[1] + cof22 * R[2]) / det; + Real rsqr = zero; + for (int i = 0; i < numPoints; ++i) + { + Vector3 delta = points[i] - sphere.center; + rsqr += Dot(delta, delta); + } + rsqr *= invNumPoints; + sphere.radius = std::sqrt(rsqr); + return true; + } + else + { + sphere.center = { zero, zero, zero }; + sphere.radius = zero; + return false; + } + } + + // Fit the points using lengths to drive the least-squares algorithm. + // If initialCenterIsAverage is set to 'false', the initial guess for + // the initial sphere center is computed as the average of the data + // points. If the data points are clustered along a small solid angle, + // the algorithm is slow to converge. If initialCenterIsAverage is set + // to 'true', the incoming sphere center is used as-is to start the + // iterative algorithm. This approach tends to converge more rapidly + // than when using the average of points but can be much slower than + // FitUsingSquaredLengths. + // + // The value epsilon may be chosen as a positive number for the + // comparison of consecutive estimated sphere centers, terminating the + // iterations when the center difference has length less than or equal + // to epsilon. + // + // The return value is the number of iterations used. If is is the + // input maxIterations, you can either accept the result or polish the + // result by calling the function again with initialCenterIsAverage + // set to 'true'. + unsigned int FitUsingLengths(int numPoints, Vector3 const* points, + unsigned int maxIterations, bool initialCenterIsAverage, + Sphere3& sphere, Real epsilon = (Real)0) + { + // Compute the average of the data points. + Vector3 average = points[0]; + for (int i = 1; i < numPoints; ++i) + { + average += points[i]; + } + Real invNumPoints = ((Real)1) / static_cast(numPoints); + average *= invNumPoints; + + // The initial guess for the center. + if (initialCenterIsAverage) + { + sphere.center = average; + } + + Real epsilonSqr = epsilon * epsilon; + unsigned int iteration; + for (iteration = 0; iteration < maxIterations; ++iteration) + { + // Update the iterates. + Vector3 current = sphere.center; + + // Compute average L, dL/da, dL/db, dL/dc. + Real lenAverage = (Real)0; + Vector3 derLenAverage = Vector3::Zero(); + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - sphere.center; + Real length = Length(diff); + if (length > (Real)0) + { + lenAverage += length; + Real invLength = ((Real)1) / length; + derLenAverage -= invLength * diff; + } + } + lenAverage *= invNumPoints; + derLenAverage *= invNumPoints; + + sphere.center = average + lenAverage * derLenAverage; + sphere.radius = lenAverage; + + Vector3 diff = sphere.center - current; + Real diffSqrLen = Dot(diff, diff); + if (diffSqrLen <= epsilonSqr) + { + break; + } + } + + return ++iteration; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprTorus3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprTorus3.h new file mode 100644 index 0000000..a503d80 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ApprTorus3.h @@ -0,0 +1,396 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// Let the torus center be C with plane of symmetry containing C and having +// directions D0 and D1. The axis of symmetry is the line containing C and +// having direction N (the plane normal). The radius from the center of the +// torus is r0 and the radius of the tube of the torus is r1. A point P may +// be written as P = C + x*D0 + y*D1 + z*N, where matrix [D0 D1 N] is +// orthogonal and has determinant 1. Thus, x = Dot(D0,P-C), y = Dot(D1,P-C) +// and z = Dot(N,P-C). The implicit equation defining the torus is +// (|P-C|^2 + r0^2 - r1^2)^2 - 4*r0^2*(|P-C|^2 - (Dot(N,P-C))^2) = 0 +// Observe that D0 and D1 are not present in the equation, which is to be +// expected by the symmetry. +// +// Define u = r0^2 and v = r0^2 - r1^2. Define +// F(X;C,N,u,v) = (|P-C|^2 + v)^2 - 4*u*(|P-C|^2 - (Dot(N,P-C))^2) +// The nonlinear least-squares fitting of points {X[i]}_{i=0}^{n-1} computes +// C, N, u and v to minimize the error function +// E(C,N,u,v) = sum_{i=0}^{n-1} F(X[i];C,N,u,v)^2 +// When the sample points are distributed so that there is large coverage +// by a purported fitted torus, a variation on fitting is the following. +// Compute the least-squares plane with origin C and normal N that fits the +// points. Define G(X;u,v) = F(X;C,N,u,v); the only variables now are u and +// v. Define L[i] = |X[i]-C|^2 and S[i] = 4 * (L[i] - (Dot(N,X[i]-C))^2). +// Define the error function +// H(u,v) = sum_{i=0}^{n-1} G(X[i];u,v)^2 +// = sum_{i=0}^{n-1} ((v + L[i])^2 - S[i]*u)^2 +// The first-order partial derivatives are +// dH/du = -2 sum_{i=0}^{n-1} ((v + L[i])^2 - S[i]*u) * S[i] +// dH/dv = 4 sum_{i=0}^{n-1} ((v + L[i])^2 - S[i]*u) * (v + L[i]) +// Setting these to zero and expanding the terms, we have +// 0 = a2 * v^2 + a1 * v + a0 - b0 * u +// 0 = c3 * v^3 + c2 * v^2 + c1 * v + c0 - u * (d1 * v + d0) +// where a2 = sum(S[i]), a1 = 2*sum(S[i]*L[i]), a2 = sum(S[i]*L[i]^2), +// b0 = sum(S[i]^2), c3 = sum(1) = n, c2 = 3*sum(L[i]), c1 = 3*sum(L[i]^2), +// c0 = sum(L[i]^3), d1 = sum(S[i]) = a2 and d0 = sum(S[i]*L[i]) = a1/2. +// The first equation is solved for +// u = (a2 * v^2 + a1 * v + a0) / b0 = e2 * v^2 + e1 * v + e0 +// and substituted into the second equation to obtain a cubic polynomial +// equation +// 0 = f3 * v^3 + f2 * v^2 + f1 * v + f0 +// where f3 = c3 - d1 * e2, f2 = c2 - d1 * e1 - d0 * e2, +// f1 = c1 - d1 * e0 - d0 * e1 and f0 = c0 - d0 * e0. The positive v-roots +// are computed. For each root compute the corresponding u. For all pairs +// (u,v) with u > v > 0, evaluate H(u,v) and choose the pair that minimizes +// H(u,v). The torus radii are r0 = sqrt(u) and r1 = sqrt(u - v). + +namespace WwiseGTE +{ + template + class ApprTorus3 + { + public: + ApprTorus3() + { + // The unit-length normal is + // N = (cos(theta)*sin(phi), sin(theta)*sin(phi), cos(phi) + // for theta in [0,2*pi) and phi in [0,*pi). The radii are + // encoded as + // u = r0^2, v = r0^2 - r1^2 + // with 0 < v < u. Let D = C - X[i] where X[i] is a sample point. + // The parameters P = (C0,C1,C2,theta,phi,u,v). + + // F[i](C,theta,phi,u,v) = + // (|D|^2 + v)^2 - 4*u*(|D|^2 - Dot(N,D)^2) + mFFunction = [this](GVector const& P, GVector& F) + { + Real csTheta = std::cos(P[3]); + Real snTheta = std::sin(P[3]); + Real csPhi = std::cos(P[4]); + Real snPhi = std::sin(P[4]); + Vector<3, Real> C = { P[0], P[1], P[2] }; + Vector<3, Real> N = { csTheta * snPhi, snTheta * snPhi, csPhi }; + Real u = P[5]; + Real v = P[6]; + for (int i = 0; i < mNumPoints; ++i) + { + Vector<3, Real> D = C - mPoints[i]; + Real DdotD = Dot(D, D), NdotD = Dot(N, D); + Real sum = DdotD + v; + F[i] = sum * sum - (Real)4 * u * (DdotD - NdotD * NdotD); + } + }; + + // dF[i]/dC = 4 * (|D|^2 + v) * D - 8 * u * (I - N*N^T) * D + // dF[i]/dTheta = 8 * u * Dot(dN/dTheta, D) + // dF[i]/dPhi = 8 * u * Dot(dN/dPhi, D) + // dF[i]/du = -4 * u * (|D|^2 - Dot(N,D)^2) + // dF[i]/dv = 2 * (|D|^2 + v) + mJFunction = [this](GVector const& P, GMatrix& J) + { + Real const r2(2), r4(4), r8(8); + Real csTheta = std::cos(P[3]); + Real snTheta = std::sin(P[3]); + Real csPhi = std::cos(P[4]); + Real snPhi = std::sin(P[4]); + Vector<3, Real> C = { P[0], P[1], P[2] }; + Vector<3, Real> N = { csTheta * snPhi, snTheta * snPhi, csPhi }; + Real u = P[5]; + Real v = P[6]; + for (int row = 0; row < mNumPoints; ++row) + { + Vector<3, Real> D = C - mPoints[row]; + Real DdotD = Dot(D, D), NdotD = Dot(N, D); + Real sum = DdotD + v; + Vector<3, Real> dNdTheta{ -snTheta * snPhi, csTheta * snPhi, (Real)0 }; + Vector<3, Real> dNdPhi{ csTheta * csPhi, snTheta * csPhi, -snPhi }; + Vector<3, Real> temp = r4 * sum * D - r8 * u * (D - NdotD * N); + J(row, 0) = temp[0]; + J(row, 1) = temp[1]; + J(row, 2) = temp[2]; + J(row, 3) = r8 * u * Dot(dNdTheta, D); + J(row, 4) = r8 * u * Dot(dNdPhi, D); + J(row, 5) = -r4 * u * (DdotD - NdotD * NdotD); + J(row, 6) = r2 * sum; + } + }; + } + + // When the samples are distributed approximately uniformly near a + // torus, use this method. For example, if the purported torus has + // center (0,0,0) and normal (0,0,1), you want the (x,y,z) samples + // to occur in all 8 octants. If the samples occur, say, only in + // one octant, this method will estimate a C and N that are nowhere + // near (0,0,0) and (0,0,1). The function sets the output variables + // C, N, r0 and r1 as the fitted torus. + // + // The return value is a pair . The first element is + // 'true' when the estimate is valid, in which case the second + // element is the least-squares error for that estimate. If any + // unexpected condition occurs that prevents computing an estimate, + // the first element is 'false' and the second element is + // std::numeric_limits::max(). + std::pair + operator()(int numPoints, Vector<3, Real> const* points, + Vector<3, Real>& C, Vector<3, Real>& N, Real& r0, Real& r1) const + { + ApprOrthogonalPlane3 fitter; + if (!fitter.Fit(numPoints, points)) + { + return std::make_pair(false, std::numeric_limits::max()); + } + C = fitter.GetParameters().first; + N = fitter.GetParameters().second; + + Real const zero(0); + Real a0 = zero, a1 = zero, a2 = zero, b0 = zero; + Real c0 = zero, c1 = zero, c2 = zero, c3 = (Real)numPoints; + for (int i = 0; i < numPoints; ++i) + { + Vector<3, Real> delta = points[i] - C; + Real dot = Dot(N, delta); + Real L = Dot(delta, delta), L2 = L * L, L3 = L * L2; + Real S = (Real)4 * (L - dot * dot), S2 = S * S; + a2 += S; + a1 += S * L; + a0 += S * L2; + b0 += S2; + c2 += L; + c1 += L2; + c0 += L3; + } + Real d1 = a2; + Real d0 = a1; + a1 *= (Real)2; + c2 *= (Real)3; + c1 *= (Real)3; + Real invB0 = (Real)1 / b0; + Real e0 = a0 * invB0; + Real e1 = a1 * invB0; + Real e2 = a2 * invB0; + + Rational f0 = c0 - d0 * e0; + Rational f1 = c1 - d1 * e0 - d0 * e1; + Rational f2 = c2 - d1 * e1 - d0 * e2; + Rational f3 = c3 - d1 * e2; + std::map rmMap; + RootsPolynomial::SolveCubic(f0, f1, f2, f3, rmMap); + + Real hmin = std::numeric_limits::max(); + Real umin = zero, vmin = zero; + for (auto const& element : rmMap) + { + Real v = element.first; + if (v > zero) + { + Real u = e0 + v * (e1 + v * e2); + if (u > v) + { + Real h = zero; + for (int i = 0; i < numPoints; ++i) + { + Vector<3, Real> delta = points[i] - C; + Real dot = Dot(N, delta); + Real L = Dot(delta, delta); + Real S = (Real)4 * (L - dot * dot); + Real sum = v + L; + Real term = sum * sum - S * u; + h += term * term; + } + if (h < hmin) + { + hmin = h; + umin = u; + vmin = v; + } + } + } + } + + if (hmin == std::numeric_limits::max()) + { + return std::make_pair(false, std::numeric_limits::max()); + } + + r0 = std::sqrt(umin); + r1 = std::sqrt(umin - vmin); + return std::make_pair(true, hmin); + } + + // If you want to specify that C, N, r0 and r1 are the initial guesses + // for the minimizer, set the parameter useTorusInputAsInitialGuess to + // 'true'. If you want the function to compute initial guesses, set + // useTorusInputAsInitialGuess to 'false'. A Gauss-Newton minimizer + // is used to fit a torus using nonlinear least-squares. The fitted + // torus is returned in C, N, r0 and r1. See GteGaussNewtonMinimizer.h + // for a description of the least-squares algorithm and the parameters + // that it requires. + typename GaussNewtonMinimizer::Result + operator()(int numPoints, Vector<3, Real> const* points, + size_t maxIterations, Real updateLengthTolerance, Real errorDifferenceTolerance, + bool useTorusInputAsInitialGuess, + Vector<3, Real>& C, Vector<3, Real>& N, Real& r0, Real& r1) const + { + mNumPoints = numPoints; + mPoints = points; + GaussNewtonMinimizer minimizer(7, mNumPoints, mFFunction, mJFunction); + + if (!useTorusInputAsInitialGuess) + { + operator()(numPoints, points, C, N, r0, r1); + } + + GVector initial(7); + + // The initial guess for the plane origin. + initial[0] = C[0]; + initial[1] = C[1]; + initial[2] = C[2]; + + // The initial guess for the plane normal. The angles must be + // extracted for spherical coordinates. + if (std::fabs(N[2]) < (Real)1) + { + initial[3] = std::atan2(N[1], N[0]); + initial[4] = std::acos(N[2]); + } + else + { + initial[3] = (Real)0; + initial[4] = (Real)0; + } + + // The initial guess for the radii-related parameters. + initial[5] = r0 * r0; + initial[6] = initial[5] - r1 * r1; + + auto result = minimizer(initial, maxIterations, updateLengthTolerance, + errorDifferenceTolerance); + + // No test is made for result.converged so that we return some + // estimates of the torus. The caller can decide how to respond + // when result.converged is false. + C[0] = result.minLocation[0]; + C[1] = result.minLocation[1]; + C[2] = result.minLocation[2]; + + Real theta = result.minLocation[3]; + Real phi = result.minLocation[4]; + Real csTheta = std::cos(theta); + Real snTheta = std::sin(theta); + Real csPhi = std::cos(phi); + Real snPhi = std::sin(phi); + N[0] = csTheta * snPhi; + N[1] = snTheta * snPhi; + N[2] = csPhi; + + Real u = result.minLocation[5]; + Real v = result.minLocation[6]; + r0 = std::sqrt(u); + r1 = std::sqrt(u - v); + + mNumPoints = 0; + mPoints = nullptr; + return result; + } + + // If you want to specify that C, N, r0 and r1 are the initial guesses + // for the minimizer, set the parameter useTorusInputAsInitialGuess to + // 'true'. If you want the function to compute initial guesses, set + // useTorusInputAsInitialGuess to 'false'. A Gauss-Newton minimizer + // is used to fit a torus using nonlinear least-squares. The fitted + // torus is returned in C, N, r0 and r1. See GteGaussNewtonMinimizer.h + // for a description of the least-squares algorithm and the parameters + // that it requires. + typename LevenbergMarquardtMinimizer::Result + operator()(int numPoints, Vector<3, Real> const* points, + size_t maxIterations, Real updateLengthTolerance, Real errorDifferenceTolerance, + Real lambdaFactor, Real lambdaAdjust, size_t maxAdjustments, + bool useTorusInputAsInitialGuess, + Vector<3, Real>& C, Vector<3, Real>& N, Real& r0, Real& r1) const + { + mNumPoints = numPoints; + mPoints = points; + LevenbergMarquardtMinimizer minimizer(7, mNumPoints, mFFunction, mJFunction); + + if (!useTorusInputAsInitialGuess) + { + operator()(numPoints, points, C, N, r0, r1); + } + + GVector initial(7); + + // The initial guess for the plane origin. + initial[0] = C[0]; + initial[1] = C[1]; + initial[2] = C[2]; + + // The initial guess for the plane normal. The angles must be + // extracted for spherical coordinates. + if (std::fabs(N[2]) < (Real)1) + { + initial[3] = std::atan2(N[1], N[0]); + initial[4] = std::acos(N[2]); + } + else + { + initial[3] = (Real)0; + initial[4] = (Real)0; + } + + // The initial guess for the radii-related parameters. + initial[5] = r0 * r0; + initial[6] = initial[5] - r1 * r1; + + auto result = minimizer(initial, maxIterations, updateLengthTolerance, + errorDifferenceTolerance, lambdaFactor, lambdaAdjust, maxAdjustments); + + // No test is made for result.converged so that we return some + // estimates of the torus. The caller can decide how to respond + // when result.converged is false. + C[0] = result.minLocation[0]; + C[1] = result.minLocation[1]; + C[2] = result.minLocation[2]; + + Real theta = result.minLocation[3]; + Real phi = result.minLocation[4]; + Real csTheta = std::cos(theta); + Real snTheta = std::sin(theta); + Real csPhi = std::cos(phi); + Real snPhi = std::sin(phi); + N[0] = csTheta * snPhi; + N[1] = snTheta * snPhi; + N[2] = csPhi; + + Real u = result.minLocation[5]; + Real v = result.minLocation[6]; + r0 = std::sqrt(u); + r1 = std::sqrt(u - v); + + mNumPoints = 0; + mPoints = nullptr; + return result; + } + + private: + typedef BSRational Rational; + + mutable int mNumPoints; + mutable Vector<3, Real> const* mPoints; + std::function const&, GVector&)> mFFunction; + std::function const&, GMatrix&)> mJFunction; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ArbitraryPrecision.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ArbitraryPrecision.h new file mode 100644 index 0000000..72a699d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ArbitraryPrecision.h @@ -0,0 +1,15 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Arc2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Arc2.h new file mode 100644 index 0000000..70f692e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Arc2.h @@ -0,0 +1,129 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The circle containing the arc is represented as |X-C| = R where C is the +// center and R is the radius. The arc is defined by two points end0 and +// end1 on the circle so that end1 is obtained from end0 by traversing +// counterclockwise. The application is responsible for ensuring that end0 +// and end1 are on the circle and that they are properly ordered. + +namespace WwiseGTE +{ + template + class Arc2 + { + public: + // Construction and destruction. The default constructor sets the + // center to (0,0), radius to 1, end0 to (1,0), and end1 to (0,1). + Arc2() + : + center(Vector2::Zero()), + radius((Real)1) + { + end[0] = Vector2::Unit(0); + end[1] = Vector2::Unit(1); + } + + Arc2(Vector2 const& inCenter, Real inRadius, + Vector2const& inEnd0, Vector2const& inEnd1) + : + center(inCenter), + radius(inRadius) + { + end[0] = inEnd0; + end[1] = inEnd1; + } + + // Test whether P is on the arc. The application must ensure that P + // is on the circle; that is, |P-C| = R. This test works for any + // angle between B-C and A-C, not just those between 0 and pi + // radians. + bool Contains(Vector2 const& p) const + { + // Assert: |P-C| = R where P is the input point, C is the circle + // center and R is the circle radius. For P to be on the arc from + // A to B, it must be on the side of the plane containing A with + // normal N = Perp(B-A) where Perp(u,v) = (v,-u). + + Vector2 diffPE0 = p - end[0]; + Vector2 diffE1E0 = end[1] - end[0]; + Real dotPerp = DotPerp(diffPE0, diffE1E0); + return dotPerp >= (Real)0; + } + + Vector2 center; + Real radius; + std::array, 2> end; + + public: + // Comparisons to support sorted containers. + bool operator==(Arc2 const& arc) const + { + return center == arc.center && radius == arc.radius + && end[0] == arc.end[0] && end[1] == arc.end[1]; + } + + bool operator!=(Arc2 const& arc) const + { + return !operator==(arc); + } + + bool operator< (Arc2 const& arc) const + { + if (center < arc.center) + { + return true; + } + + if (center > arc.center) + { + return false; + } + + if (radius < arc.radius) + { + return true; + } + + if (radius > arc.radius) + { + return false; + } + + if (end[0] < arc.end[0]) + { + return true; + } + + if (end[0] > arc.end[0]) + { + return false; + } + + return end[1] < arc.end[1]; + } + + bool operator<=(Arc2 const& arc) const + { + return !arc.operator<(*this); + } + + bool operator> (Arc2 const& arc) const + { + return arc.operator<(*this); + } + + bool operator>=(Arc2 const& arc) const + { + return !operator<(arc); + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Array2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Array2.h new file mode 100644 index 0000000..310d05a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Array2.h @@ -0,0 +1,144 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The Array2 class represents a 2-dimensional array that minimizes the number +// of new and delete calls. The T objects are stored in a contiguous array. + +namespace WwiseGTE +{ + template + class Array2 + { + public: + // Construction. The first constructor generates an array of objects + // that are owned by Array2. The second constructor is given an array + // of objects that are owned by the caller. The array has bound0 + // columns and bound1 rows. + Array2(size_t bound0, size_t bound1) + : + mBound0(bound0), + mBound1(bound1), + mObjects(bound0 * bound1), + mIndirect1(bound1) + { + SetPointers(mObjects.data()); + } + + Array2(size_t bound0, size_t bound1, T* objects) + : + mBound0(bound0), + mBound1(bound1), + mIndirect1(bound1) + { + SetPointers(objects); + } + + // Support for dynamic resizing, copying, or moving. If 'other' does + // not own the original 'objects', they are not copied by the + // assignment operator. + Array2() + : + mBound0(0), + mBound1(0) + { + } + + Array2(Array2 const& other) + : + mBound0(other.mBound0), + mBound1(other.mBound1) + { + *this = other; + } + + Array2& operator=(Array2 const& other) + { + // The copy is valid whether or not other.mObjects has elements. + mObjects = other.mObjects; + SetPointers(other); + return *this; + } + + Array2(Array2&& other) noexcept + : + mBound0(other.mBound0), + mBound1(other.mBound1) + { + *this = std::move(other); + } + + Array2& operator=(Array2&& other) noexcept + { + // The move is valid whether or not other.mObjects has elements. + mObjects = std::move(other.mObjects); + SetPointers(other); + return *this; + } + + // Access to the array. Sample usage is + // Array2 myArray(3, 2); + // T* row1 = myArray[1]; + // T row1Col2 = myArray[1][2]; + inline size_t GetBound0() const + { + return mBound0; + } + + inline size_t GetBound1() const + { + return mBound1; + } + + inline T const* operator[](int row) const + { + return mIndirect1[row]; + } + + inline T* operator[](int row) + { + return mIndirect1[row]; + } + + private: + void SetPointers(T* objects) + { + for (size_t i1 = 0; i1 < mBound1; ++i1) + { + size_t j0 = mBound0 * i1; // = bound0*(i1 + j1) where j1 = 0 + mIndirect1[i1] = &objects[j0]; + } + } + + void SetPointers(Array2 const& other) + { + mBound0 = other.mBound0; + mBound1 = other.mBound1; + mIndirect1.resize(mBound1); + + if (mBound0 > 0) + { + // The objects are owned. + SetPointers(mObjects.data()); + } + else if (mIndirect1.size() > 0) + { + // The objects are not owned. + SetPointers(other.mIndirect1[0]); + } + // else 'other' is an empty Array2. + } + + size_t mBound0, mBound1; + std::vector mObjects; + std::vector mIndirect1; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Array3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Array3.h new file mode 100644 index 0000000..18dd850 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Array3.h @@ -0,0 +1,165 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The Array3 class represents a 3-dimensional array that minimizes the number +// of new and delete calls. The T objects are stored in a contiguous array. + +namespace WwiseGTE +{ + template + class Array3 + { + public: + // Construction. The first constructor generates an array of objects + // that are owned by Array3. The second constructor is given an array + // of objects that are owned by the caller. The array has bound0 + // columns, bound1 rows, and bound2 slices. + Array3(size_t bound0, size_t bound1, size_t bound2) + : + mBound0(bound0), + mBound1(bound1), + mBound2(bound2), + mObjects(bound0 * bound1 * bound2), + mIndirect1(bound1 * bound2), + mIndirect2(bound2) + { + SetPointers(mObjects.data()); + } + + Array3(size_t bound0, size_t bound1, size_t bound2, T* objects) + : + mBound0(bound0), + mBound1(bound1), + mBound2(bound2), + mIndirect1(bound1 * bound2), + mIndirect2(bound2) + { + SetPointers(objects); + } + + // Support for dynamic resizing, copying, or moving. If 'other' does + // not own the original 'objects', they are not copied by the + // assignment operator. + Array3() + : + mBound0(0), + mBound1(0), + mBound2(0) + { + } + + Array3(Array3 const& other) + : + mBound0(other.mBound0), + mBound1(other.mBound1), + mBound2(other.mBound2) + { + *this = other; + } + + Array3& operator=(Array3 const& other) + { + // The copy is valid whether or not other.mObjects has elements. + mObjects = other.mObjects; + SetPointers(other); + return *this; + } + + Array3(Array3&& other) noexcept + : + mBound0(other.mBound0), + mBound1(other.mBound1), + mBound2(other.mBound2) + { + *this = std::move(other); + } + + Array3& operator=(Array3&& other) noexcept + { + // The move is valid whether or not other.mObjects has elements. + mObjects = std::move(other.mObjects); + SetPointers(other); + return *this; + } + + // Access to the array. Sample usage is + // Array3 myArray(4, 3, 2); + // T** slice1 = myArray[1]; + // T* slice1row2 = myArray[1][2]; + // T slice1Row2Col3 = myArray[1][2][3]; + inline size_t GetBound0() const + { + return mBound0; + } + + inline size_t GetBound1() const + { + return mBound1; + } + + inline size_t GetBound2() const + { + return mBound2; + } + + inline T* const* operator[](int slice) const + { + return mIndirect2[slice]; + } + + inline T** operator[](int slice) + { + return mIndirect2[slice]; + } + + private: + void SetPointers(T* objects) + { + for (size_t i2 = 0; i2 < mBound2; ++i2) + { + size_t j1 = mBound1 * i2; // = bound1*(i2 + j2) where j2 = 0 + mIndirect2[i2] = &mIndirect1[j1]; + for (size_t i1 = 0; i1 < mBound1; ++i1) + { + size_t j0 = mBound0 * (i1 + j1); + mIndirect2[i2][i1] = &objects[j0]; + } + } + } + + void SetPointers(Array3 const& other) + { + mBound0 = other.mBound0; + mBound1 = other.mBound1; + mBound2 = other.mBound2; + mIndirect1.resize(mBound1 * mBound2); + mIndirect2.resize(mBound2); + + if (mBound0 > 0) + { + // The objects are owned. + SetPointers(mObjects.data()); + } + else if (mIndirect1.size() > 0) + { + // The objects are not owned. + SetPointers(other.mIndirect2[0][0]); + } + // else 'other' is an empty Array3. + } + + size_t mBound0, mBound1, mBound2; + std::vector mObjects; + std::vector mIndirect1; + std::vector mIndirect2; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Array4.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Array4.h new file mode 100644 index 0000000..771baea --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Array4.h @@ -0,0 +1,186 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The Array4 class represents a 4-dimensional array that minimizes the number +// of new and delete calls. The T objects are stored in a contiguous array. + +namespace WwiseGTE +{ + template + class Array4 + { + public: + // Construction. The first constructor generates an array of objects + // that are owned by Array4. The second constructor is given an array + // of objects that are owned by the caller. The array has bound0 + // columns, bound1 rows, bound2 slices, and bound3 cuboids. + Array4(size_t bound0, size_t bound1, size_t bound2, size_t bound3) + : + mBound0(bound0), + mBound1(bound1), + mBound2(bound2), + mBound3(bound3), + mObjects(bound0 * bound1 * bound2 * bound3), + mIndirect1(bound1 * bound2 * bound3), + mIndirect2(bound2 * bound3), + mIndirect3(bound3) + { + SetPointers(mObjects.data()); + } + + Array4(size_t bound0, size_t bound1, size_t bound2, size_t bound3, T* objects) + : + mBound0(bound0), + mBound1(bound1), + mBound2(bound2), + mBound3(bound3), + mIndirect1(bound1 * bound2 * bound3), + mIndirect2(bound2 * bound3), + mIndirect3(bound3) + { + SetPointers(objects); + } + + // Support for dynamic resizing, copying, or moving. If 'other' does + // not own the original 'objects', they are not copied by the + // assignment operator. + Array4() + : + mBound0(0), + mBound1(0), + mBound2(0), + mBound3(0) + { + } + + Array4(Array4 const& other) + : + mBound0(other.mBound0), + mBound1(other.mBound1), + mBound2(other.mBound2), + mBound3(other.mBound3) + { + *this = other; + } + + Array4& operator=(Array4 const& other) + { + // The copy is valid whether or not other.mObjects has elements. + mObjects = other.mObjects; + SetPointers(other); + return *this; + } + + Array4(Array4&& other) noexcept + : + mBound0(other.mBound0), + mBound1(other.mBound1), + mBound2(other.mBound2), + mBound3(other.mBound3) + { + *this = std::move(other); + } + + Array4& operator=(Array4&& other) noexcept + { + // The move is valid whether or not other.mObjects has elements. + mObjects = std::move(other.mObjects); + SetPointers(other); + return *this; + } + + // Access to the array. Sample usage is + // Array4 myArray(5, 4, 3, 2); + // T*** cuboid1 = myArray[1]; + // T** cuboid1Slice2 = myArray[1][2]; + // T* cuboid1Slice2Row3 = myArray[1][2][3]; + // T cuboid1Slice2Row3Col4 = myArray[1][2][3][4]; + inline size_t GetBound0() const + { + return mBound0; + } + + inline size_t GetBound1() const + { + return mBound1; + } + + inline size_t GetBound2() const + { + return mBound2; + } + + inline size_t GetBound3() const + { + return mBound3; + } + + inline T** const* operator[](int cuboid) const + { + return mIndirect3[cuboid]; + } + + inline T*** operator[](int cuboid) + { + return mIndirect3[cuboid]; + } + + private: + void SetPointers(T* objects) + { + for (size_t i3 = 0; i3 < mBound3; ++i3) + { + size_t j2 = mBound2 * i3; // = bound2*(i3 + j3) where j3 = 0 + mIndirect3[i3] = &mIndirect2[j2]; + for (size_t i2 = 0; i2 < mBound2; ++i2) + { + size_t j1 = mBound1 * (i2 + j2); + mIndirect3[i3][i2] = &mIndirect1[j1]; + for (size_t i1 = 0; i1 < mBound1; ++i1) + { + size_t j0 = mBound0 * (i1 + j1); + mIndirect3[i3][i2][i1] = &objects[j0]; + } + } + } + } + + void SetPointers(Array4 const& other) + { + mBound0 = other.mBound0; + mBound1 = other.mBound1; + mBound2 = other.mBound2; + mBound3 = other.mBound3; + mIndirect1.resize(mBound1 * mBound2 * mBound3); + mIndirect2.resize(mBound2 * mBound3); + mIndirect3.resize(mBound3); + + if (mBound0 > 0) + { + // The objects are owned. + SetPointers(mObjects.data()); + } + else if (mIndirect1.size() > 0) + { + // The objects are not owned. + SetPointers(other.mIndirect3[0][0][0]); + } + // else 'other' is an empty Array3. + } + + size_t mBound0, mBound1, mBound2, mBound3; + std::vector mObjects; + std::vector mIndirect1; + std::vector mIndirect2; + std::vector mIndirect3; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/AtomicMinMax.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/AtomicMinMax.h new file mode 100644 index 0000000..8756d8c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/AtomicMinMax.h @@ -0,0 +1,43 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// Implementations of atomic minimum and atomic maximum computations. These +// are based on std::atomic_compare_exchange_strong. + +namespace WwiseGTE +{ + template + T AtomicMin(std::atomic& v0, T const& v1) + { + T vInitial, vMin; + do + { + vInitial = v0; + vMin = std::min(vInitial, v1); + } + while (!std::atomic_compare_exchange_strong(&v0, &vInitial, vMin)); + return vInitial; + } + + template + T AtomicMax(std::atomic& v0, T const& v1) + { + T vInitial, vMax; + do + { + vInitial = v0; + vMax = std::max(vInitial, v1); + } + while (!std::atomic_compare_exchange_strong(&v0, &vInitial, vMax)); + return vInitial; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/AxisAngle.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/AxisAngle.h new file mode 100644 index 0000000..aa5e9a8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/AxisAngle.h @@ -0,0 +1,41 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + // Axis-angle representation for N = 3 or N = 4. When N = 4, the axis + // must be a vector of the form (x,y,z,0) [affine representation of the + // 3-tuple direction]. + + template + class AxisAngle + { + public: + AxisAngle() + : + axis(Vector::Zero()), + angle((Real)0) + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + } + + AxisAngle(Vector const& inAxis, Real inAngle) + : + axis(inAxis), + angle(inAngle) + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + } + + Vector axis; + Real angle; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSNumber.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSNumber.h new file mode 100644 index 0000000..1eeb10c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSNumber.h @@ -0,0 +1,1335 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.11.03 + +#pragma once + +#include +#include +#include + +// The class BSNumber (binary scientific number) is designed to provide exact +// arithmetic for robust algorithms, typically those for which we need to know +// the exact sign of determinants. The template parameter UInteger must +// have support for at least the following public interface. The fstream +// objects for Write/Read must be created using std::ios::binary. The return +// value of Write/Read is 'true' iff the operation was successful. +// +// class UInteger +// { +// public: +// UInteger(); +// UInteger(UInteger const& number); +// UInteger(uint32_t number); +// UInteger(uint64_t number); +// UInteger& operator=(UInteger const& number); +// UInteger(UInteger&& number); +// UInteger& operator=(UInteger&& number); +// void SetNumBits(int numBits); +// int32_t GetNumBits() const; +// bool operator==(UInteger const& number) const; +// bool operator< (UInteger const& number) const; +// void Add(UInteger const& n0, UInteger const& n1); +// void Sub(UInteger const& n0, UInteger const& n1); +// void Mul(UInteger const& n0, UInteger const& n1); +// void ShiftLeft(UInteger const& number, int shift); +// int32_t ShiftRightToOdd(UInteger const& number); +// int32_t RoundUp(); +// uint64_t GetPrefix(int numRequested) const; +// bool Write(std::ofstream& output) const; +// bool Read(std::ifstream& input); +// }; +// +// GTEngine currently has 32-bits-per-word storage for UInteger. See the +// classes UIntegerAP32 (arbitrary precision), UIntegerFP32 (fixed +// precision), and UIntegerALU32 (arithmetic logic unit shared by the previous +// two classes). The document at the following link describes the design, +// implementation, and use of BSNumber and BSRational. +// https://www.geometrictools.com/Documentation/ArbitraryPrecision.pdf +// +// Support for debugging algorithms that use exact rational arithmetic. Each +// BSNumber and BSRational has a double-precision member that is exposed when +// the conditional define is enabled. Be aware that this can be very slow +// because of the conversion to double-precision whenever new objects are +// created by arithmetic operations. As a faster alternative, you can add +// temporary code in your algorithms that explicitly convert specific rational +// numbers to double precision. +// +//#define GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE +// +// Enable this to throw exceptions when NaNs are generated by conversion +// from BSNumber to float or double. +#define GTE_THROW_ON_BSNUMBER_ERRORS + +namespace WwiseGTE +{ + template class BSRational; + + template + class BSNumber + { + public: + // Construction. The default constructor generates the zero BSNumber. + BSNumber() + : + mSign(0), + mBiasedExponent(0) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSNumber(BSNumber const& number) + { + *this = number; + } + + BSNumber(float number) + { + ConvertFrom(number); +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSNumber(double number) + { + ConvertFrom(number); +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSNumber(int32_t number) + { + if (number == 0) + { + mSign = 0; + mBiasedExponent = 0; + } + else + { + if (number < 0) + { + mSign = -1; + number = -number; + } + else + { + mSign = 1; + } + + mBiasedExponent = BitHacks::GetTrailingBit(number); + mUInteger = (uint32_t)number; + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSNumber(uint32_t number) + { + if (number == 0) + { + mSign = 0; + mBiasedExponent = 0; + } + else + { + mSign = 1; + mBiasedExponent = BitHacks::GetTrailingBit(number); + mUInteger = number; + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSNumber(int64_t number) + { + if (number == 0) + { + mSign = 0; + mBiasedExponent = 0; + } + else + { + if (number < 0) + { + mSign = -1; + number = -number; + } + else + { + mSign = 1; + } + + mBiasedExponent = BitHacks::GetTrailingBit(number); + mUInteger = (uint64_t)number; + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSNumber(uint64_t number) + { + if (number == 0) + { + mSign = 0; + mBiasedExponent = 0; + } + else + { + mSign = 1; + mBiasedExponent = BitHacks::GetTrailingBit(number); + mUInteger = number; + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + // The number must be of the form "x" or "+x" or "-x", where x is a + // positive integer with nonzero leading digit. + BSNumber(std::string const& number) + { + LogAssert(number.size() > 0, "A number must be specified."); + + // Get the leading '+' or '-' if it exists. + std::string intNumber; + int sign; + if (number[0] == '+') + { + intNumber = number.substr(1); + sign = +1; + LogAssert(intNumber.size() > 1, "Invalid number format."); + } + else if (number[0] == '-') + { + intNumber = number.substr(1); + sign = -1; + LogAssert(intNumber.size() > 1, "Invalid number format."); + } + else + { + intNumber = number; + sign = +1; + } + + *this = ConvertToInteger(intNumber); + mSign = sign; + } + + BSNumber(char const* number) + : + BSNumber(std::string(number)) + { + } + + // Implicit conversions. These always use the default rounding mode, + // round-to-nearest-ties-to-even. + inline operator float() const + { + return ConvertTo(); + } + + inline operator double() const + { + return ConvertTo(); + } + + // Assignment. + BSNumber& operator=(BSNumber const& number) + { + mSign = number.mSign; + mBiasedExponent = number.mBiasedExponent; + mUInteger = number.mUInteger; +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = number.mValue; +#endif + return *this; + } + + // Support for std::move. + BSNumber(BSNumber&& number) + { + *this = std::move(number); + } + + BSNumber& operator=(BSNumber&& number) + { + mSign = number.mSign; + mBiasedExponent = number.mBiasedExponent; + mUInteger = std::move(number.mUInteger); + number.mSign = 0; + number.mBiasedExponent = 0; +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = number.mValue; +#endif + return *this; + } + + // Member access. + inline void SetSign(int32_t sign) + { + mSign = sign; + } + + inline int32_t GetSign() const + { + return mSign; + } + + inline void SetBiasedExponent(int32_t biasedExponent) + { + mBiasedExponent = biasedExponent; + } + + inline int32_t GetBiasedExponent() const + { + return mBiasedExponent; + } + + inline void SetExponent(int32_t exponent) + { + mBiasedExponent = exponent - mUInteger.GetNumBits() + 1; + } + + inline int32_t GetExponent() const + { + return mBiasedExponent + mUInteger.GetNumBits() - 1; + } + + inline UInteger const& GetUInteger() const + { + return mUInteger; + } + + inline UInteger& GetUInteger() + { + return mUInteger; + } + + // Comparisons. + bool operator==(BSNumber const& number) const + { + return (mSign == number.mSign ? EqualIgnoreSign(*this, number) : false); + } + + bool operator!=(BSNumber const& number) const + { + return !operator==(number); + } + + bool operator< (BSNumber const& number) const + { + if (mSign > 0) + { + if (number.mSign <= 0) + { + return false; + } + + // Both numbers are positive. + return LessThanIgnoreSign(*this, number); + } + else if (mSign < 0) + { + if (number.mSign >= 0) + { + return true; + } + + // Both numbers are negative. + return LessThanIgnoreSign(number, *this); + } + else + { + return number.mSign > 0; + } + } + + bool operator<=(BSNumber const& number) const + { + return !number.operator<(*this); + } + + bool operator> (BSNumber const& number) const + { + return number.operator<(*this); + } + + bool operator>=(BSNumber const& number) const + { + return !operator<(number); + } + + // Unary operations. + BSNumber operator+() const + { + return *this; + } + + BSNumber operator-() const + { + BSNumber result = *this; + result.mSign = -result.mSign; +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + result.mValue = (double)result; +#endif + return result; + } + + // Arithmetic. + BSNumber operator+(BSNumber const& n1) const + { + BSNumber const& n0 = *this; + + if (n0.mSign == 0) + { + return n1; + } + + if (n1.mSign == 0) + { + return n0; + } + + if (n0.mSign > 0) + { + if (n1.mSign > 0) + { + // n0 + n1 = |n0| + |n1| + return AddIgnoreSign(n0, n1, +1); + } + else // n1.mSign < 0 + { + if (!EqualIgnoreSign(n0, n1)) + { + if (LessThanIgnoreSign(n1, n0)) + { + // n0 + n1 = |n0| - |n1| > 0 + return SubIgnoreSign(n0, n1, +1); + } + else + { + // n0 + n1 = -(|n1| - |n0|) < 0 + return SubIgnoreSign(n1, n0, -1); + } + } + // else n0 + n1 = 0 + } + } + else // n0.mSign < 0 + { + if (n1.mSign < 0) + { + // n0 + n1 = -(|n0| + |n1|) + return AddIgnoreSign(n0, n1, -1); + } + else // n1.mSign > 0 + { + if (!EqualIgnoreSign(n0, n1)) + { + if (LessThanIgnoreSign(n1, n0)) + { + // n0 + n1 = -(|n0| - |n1|) < 0 + return SubIgnoreSign(n0, n1, -1); + } + else + { + // n0 + n1 = |n1| - |n0| > 0 + return SubIgnoreSign(n1, n0, +1); + } + } + // else n0 + n1 = 0 + } + } + + return BSNumber(); // = 0 + } + + BSNumber operator-(BSNumber const& n1) const + { + BSNumber const& n0 = *this; + + if (n0.mSign == 0) + { + return -n1; + } + + if (n1.mSign == 0) + { + return n0; + } + + if (n0.mSign > 0) + { + if (n1.mSign < 0) + { + // n0 - n1 = |n0| + |n1| + return AddIgnoreSign(n0, n1, +1); + } + else // n1.mSign > 0 + { + if (!EqualIgnoreSign(n0, n1)) + { + if (LessThanIgnoreSign(n1, n0)) + { + // n0 - n1 = |n0| - |n1| > 0 + return SubIgnoreSign(n0, n1, +1); + } + else + { + // n0 - n1 = -(|n1| - |n0|) < 0 + return SubIgnoreSign(n1, n0, -1); + } + } + // else n0 - n1 = 0 + } + } + else // n0.mSign < 0 + { + if (n1.mSign > 0) + { + // n0 - n1 = -(|n0| + |n1|) + return AddIgnoreSign(n0, n1, -1); + } + else // n1.mSign < 0 + { + if (!EqualIgnoreSign(n0, n1)) + { + if (LessThanIgnoreSign(n1, n0)) + { + // n0 - n1 = -(|n0| - |n1|) < 0 + return SubIgnoreSign(n0, n1, -1); + } + else + { + // n0 - n1 = |n1| - |n0| > 0 + return SubIgnoreSign(n1, n0, +1); + } + } + // else n0 - n1 = 0 + } + } + + return BSNumber(); // = 0 + } + + BSNumber operator*(BSNumber const& number) const + { + BSNumber result; // = 0 + int sign = mSign * number.mSign; + if (sign != 0) + { + result.mSign = sign; + result.mBiasedExponent = mBiasedExponent + number.mBiasedExponent; + result.mUInteger.Mul(mUInteger, number.mUInteger); + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + result.mValue = (double)result; +#endif + return result; + } + + BSNumber& operator+=(BSNumber const& number) + { + *this = operator+(number); + return *this; + } + + BSNumber& operator-=(BSNumber const& number) + { + *this = operator-(number); + return *this; + } + + BSNumber& operator*=(BSNumber const& number) + { + *this = operator*(number); + return *this; + } + + // Disk input/output. The fstream objects should be created using + // std::ios::binary. The return value is 'true' iff the operation + // was successful. + bool Write(std::ostream& output) const + { + if (output.write((char const*)&mSign, sizeof(mSign)).bad()) + { + return false; + } + + if (output.write((char const*)&mBiasedExponent, + sizeof(mBiasedExponent)).bad()) + { + return false; + } + + return mUInteger.Write(output); + } + + bool Read(std::istream& input) + { + if (input.read((char*)&mSign, sizeof(mSign)).bad()) + { + return false; + } + + if (input.read((char*)&mBiasedExponent, sizeof(mBiasedExponent)).bad()) + { + return false; + } + + return mUInteger.Read(input); + } + + private: + // Helper for converting a string to a BSNumber. The string must be + // valid for a nonnegative integer without a leading '+' sign. + static BSNumber ConvertToInteger(std::string const& number) + { + int digit = static_cast(number.back()) - static_cast('0'); + BSNumber x(digit); + if (number.size() > 1) + { + LogAssert(number.find_first_of("123456789") == 0, "Invalid number format."); + LogAssert(number.find_first_not_of("0123456789") == std::string::npos, "Invalid number format."); + BSNumber ten(10), pow10(10); + for (size_t i = 1, j = number.size() - 2; i < number.size(); ++i, --j) + { + digit = static_cast(number[j]) - static_cast('0'); + if (digit > 0) + { + x += BSNumber(digit) * pow10; + } + pow10 *= ten; + } + } + return x; + } + + // Helpers for operator==, operator<, operator+, operator-. + static bool EqualIgnoreSign(BSNumber const& n0, BSNumber const& n1) + { + return n0.mBiasedExponent == n1.mBiasedExponent && n0.mUInteger == n1.mUInteger; + } + + static bool LessThanIgnoreSign(BSNumber const& n0, BSNumber const& n1) + { + int32_t e0 = n0.GetExponent(), e1 = n1.GetExponent(); + if (e0 < e1) + { + return true; + } + if (e0 > e1) + { + return false; + } + return n0.mUInteger < n1.mUInteger; + } + + // Add two positive numbers. + static BSNumber AddIgnoreSign(BSNumber const& n0, BSNumber const& n1, int32_t resultSign) + { + BSNumber result, temp; + + int32_t diff = n0.mBiasedExponent - n1.mBiasedExponent; + if (diff > 0) + { + temp.mUInteger.ShiftLeft(n0.mUInteger, diff); + result.mUInteger.Add(temp.mUInteger, n1.mUInteger); + result.mBiasedExponent = n1.mBiasedExponent; + } + else if (diff < 0) + { + temp.mUInteger.ShiftLeft(n1.mUInteger, -diff); + result.mUInteger.Add(n0.mUInteger, temp.mUInteger); + result.mBiasedExponent = n0.mBiasedExponent; + } + else + { + temp.mUInteger.Add(n0.mUInteger, n1.mUInteger); + int32_t shift = result.mUInteger.ShiftRightToOdd(temp.mUInteger); + result.mBiasedExponent = n0.mBiasedExponent + shift; + } + + result.mSign = resultSign; +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + result.mValue = (double)result; +#endif + return result; + } + + // Subtract two positive numbers where n0 > n1. + static BSNumber SubIgnoreSign(BSNumber const& n0, BSNumber const& n1, int32_t resultSign) + { + BSNumber result, temp; + + int32_t diff = n0.mBiasedExponent - n1.mBiasedExponent; + if (diff > 0) + { + temp.mUInteger.ShiftLeft(n0.mUInteger, diff); + result.mUInteger.Sub(temp.mUInteger, n1.mUInteger); + result.mBiasedExponent = n1.mBiasedExponent; + } + else if (diff < 0) + { + temp.mUInteger.ShiftLeft(n1.mUInteger, -diff); + result.mUInteger.Sub(n0.mUInteger, temp.mUInteger); + result.mBiasedExponent = n0.mBiasedExponent; + } + else + { + temp.mUInteger.Sub(n0.mUInteger, n1.mUInteger); + int32_t shift = result.mUInteger.ShiftRightToOdd(temp.mUInteger); + result.mBiasedExponent = n0.mBiasedExponent + shift; + } + + result.mSign = resultSign; +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + result.mValue = (double)result; +#endif + return result; + } + + // Support for conversions from floating-point numbers to BSNumber. + template + void ConvertFrom(typename IEEE::FloatType number) + { + IEEE x(number); + + // Extract sign s, biased exponent e, and trailing significand t. + typename IEEE::UIntType s = x.GetSign(); + typename IEEE::UIntType e = x.GetBiased(); + typename IEEE::UIntType t = x.GetTrailing(); + + if (e == 0) + { + if (t == 0) // zeros + { + // x = (-1)^s * 0 + mSign = 0; + mBiasedExponent = 0; + } + else // subnormal numbers + { + // x = (-1)^s * 0.t * 2^{1-EXPONENT_BIAS} + int32_t last = BitHacks::GetTrailingBit(t); + int32_t diff = IEEE::NUM_TRAILING_BITS - last; + mSign = (s > 0 ? -1 : 1); + mBiasedExponent = IEEE::MIN_SUB_EXPONENT - diff; + mUInteger = (t >> last); + } + } + else if (e < IEEE::MAX_BIASED_EXPONENT) // normal numbers + { + // x = (-1)^s * 1.t * 2^{e-EXPONENT_BIAS} + if (t > 0) + { + int32_t last = BitHacks::GetTrailingBit(t); + int32_t diff = IEEE::NUM_TRAILING_BITS - last; + mSign = (s > 0 ? -1 : 1); + mBiasedExponent = + static_cast(e) - IEEE::EXPONENT_BIAS - diff; + mUInteger = ((t | IEEE::SUP_TRAILING) >> last); + } + else + { + mSign = (s > 0 ? -1 : 1); + mBiasedExponent = static_cast(e) - IEEE::EXPONENT_BIAS; + mUInteger = (typename IEEE::UIntType)1; + } + } + else // e == MAX_BIASED_EXPONENT, special numbers + { + if (t == 0) // infinities + { + // x = (-1)^s * infinity +#if defined(GTE_THROW_ON_BSNUMBER_ERRORS) + // TODO: This should not be a warning, but BSNumber does + // not have a representation for +infinity or -infinity. + // Consider doing so with mSign in {-2,2} and all other + // members zero-valued. + LogWarning("Input is " + std::string(s > 0 ? "-" : "+") + "infinity."); +#else + // Return (-1)^s * 2^{1+EXPONENT_BIAS} for a graceful + // exit. + mSign = (s > 0 ? -1 : 1); + mBiasedExponent = 1 + IEEE::EXPONENT_BIAS; + mUInteger = (typename IEEE::UIntType)1; +#endif + } + else // not-a-number (NaN) + { +#if defined(GTE_THROW_ON_BSNUMBER_ERRORS) + // TODO: BSNumber does not have a representation for + // NaNs. Consider doing so with mSign in {-3,3} and a + // payload stored in mBits. + LogError("Input is a " + + std::string(t & IEEE::NAN_QUIET_MASK ? + "quiet" : "signaling") + " NaN with payload " + + std::to_string(t & IEEE::NAN_PAYLOAD_MASK) + "."); +#else + // Return 0 for a graceful exit. + mSign = 0; + mBiasedExponent = 0; +#endif + } + } + } + + // Support for conversions from BSNumber to floating-point numbers. + template + typename IEEE::FloatType ConvertTo() const + { + typename IEEE::UIntType s = (mSign < 0 ? 1 : 0); + typename IEEE::UIntType e, t; + + if (mSign != 0) + { + // The conversions use round-to-nearest-ties-to-even + // semantics. + int32_t exponent = GetExponent(); + if (exponent < IEEE::MIN_EXPONENT) + { + if (exponent < IEEE::MIN_EXPONENT - 1 + || mUInteger.GetNumBits() == 1) + { + // x = 1.0*2^{MIN_EXPONENT-1}, round to zero. + e = 0; + t = 0; + } + else + { + // Round to min subnormal. + e = 0; + t = 1; + } + } + else if (exponent < IEEE::MIN_SUB_EXPONENT) + { + // The second input is in {0, ..., NUM_TRAILING_BITS-1}. + t = GetTrailing(0, IEEE::MIN_SUB_EXPONENT - exponent - 1); + if (t & IEEE::SUP_TRAILING) + { + // Leading NUM_SIGNIFICAND_BITS bits were all 1, so + // round to min normal. + e = 1; + t = 0; + } + else + { + e = 0; + } + } + else if (exponent <= IEEE::EXPONENT_BIAS) + { + e = static_cast(exponent + IEEE::EXPONENT_BIAS); + t = GetTrailing(1, 0); + if (t & (IEEE::SUP_TRAILING << 1)) + { + // Carry-out occurred, so increase exponent by 1 and + // shift right to compensate. + ++e; + t >>= 1; + } + // Eliminate the leading 1 (implied for normals). + t &= ~IEEE::SUP_TRAILING; + } + else + { + // Set to infinity. + e = IEEE::MAX_BIASED_EXPONENT; + t = 0; + } + } + else + { + // The input is zero. + e = 0; + t = 0; + } + + IEEE x(s, e, t); + return x.number; + } + + template + typename IEEE::UIntType GetTrailing(int32_t normal, int32_t sigma) const + { + int32_t const numRequested = IEEE::NUM_SIGNIFICAND_BITS + normal; + + // We need numRequested bits to determine rounding direction. + // These are stored in the high-order bits of 'prefix'. + uint64_t prefix = mUInteger.GetPrefix(numRequested); + + // The first bit index after the implied binary point for + // rounding. + int32_t diff = numRequested - sigma; + int32_t roundBitIndex = 64 - diff; + + // Determine value based on round-to-nearest-ties-to-even. + uint64_t mask = (1ull << roundBitIndex); + uint64_t round; + if (prefix & mask) + { + // The first bit of the remainder is 1. + if (mUInteger.GetNumBits() == diff) + { + // The first bit of the remainder is the lowest-order bit + // of mBits[0]. Apply the ties-to-even rule. + if (prefix & (mask << 1)) + { + // The last bit of the trailing significand is odd, + // so round up. + round = 1; + } + else + { + // The last bit of the trailing significand is even, + // so round down. + round = 0; + } + } + else + { + // The first bit of the remainder is not the lowest-order + // bit of mBits[0]. The remainder as a fraction is larger + // than 1/2, so round up. + round = 1; + } + } + else + { + // The first bit of the remainder is 0, so round down. + round = 0; + } + + // Get the unrounded trailing significand. + uint64_t trailing = prefix >> (roundBitIndex + 1); + + // Apply the rounding. + trailing += round; + return static_cast(trailing); + } + +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + public: + // List this first so that it shows up first in the debugger watch + // window. + double mValue; + private: +#endif + + // The number 0 is represented by: mSign = 0, mBiasedExponent = 0 and + // mUInteger = 0. For nonzero numbers, mSign != 0 and mUInteger > 0. + int32_t mSign; + int32_t mBiasedExponent; + UInteger mUInteger; + + // BSRational depends on the design of BSNumber, so allow it to have + // full access to the implementation. + friend class BSRational; + }; + + + // Explicit conversion to a user-specified precision. The rounding + // mode is one of the flags provided in . The modes are + // FE_TONEAREST: round to nearest ties to even + // FE_DOWNWARD: round towards negative infinity + // FE_TOWARDZERO: round towards zero + // FE_UPWARD: round towards positive infinity + template + void Convert(BSNumber const& input, int32_t precision, + int32_t roundingMode, BSNumber& output) + { + if (precision <= 0) + { + LogError("Precision must be positive."); + } + + int64_t const maxSize = static_cast(UInteger::GetMaxSize()); + int64_t const excess = 32LL * maxSize - static_cast(precision); + if (excess <= 0) + { + LogError("The maximum precision has been exceeded."); + } + + if (input.GetSign() == 0) + { + output = BSNumber(0); + return; + } + + // Let p = precision and n+1 be the number of bits of the input. + // Compute n+1-p. If it is nonpositive, then the requested precision + // is already satisfied by the input. + int32_t np1mp = input.GetUInteger().GetNumBits() - precision; + if (np1mp <= 0) + { + output = input; + return; + } + + // At this point, the requested number of bits is smaller than the + // number of bits in the input. Round the input to the smaller number + // of bits using the specified rounding mode. + UInteger& outW = output.GetUInteger(); + outW.SetNumBits(precision); + outW.SetAllBitsToZero(); + int32_t const outSize = outW.GetSize(); + int32_t const precisionM1 = precision - 1; + int32_t const outLeading = precisionM1 % 32; + uint32_t outMask = (1 << outLeading); + auto& outBits = outW.GetBits(); + int32_t outCurrent = outSize - 1; + + UInteger const& inW = input.GetUInteger(); + int32_t const inSize = inW.GetSize(); + int32_t const inLeading = (inW.GetNumBits() - 1) % 32; + uint32_t inMask = (1 << inLeading); + auto const& inBits = inW.GetBits(); + int32_t inCurrent = inSize - 1; + + int32_t lastBit = -1; + for (int i = precisionM1; i >= 0; --i) + { + if (inBits[inCurrent] & inMask) + { + outBits[outCurrent] |= outMask; + lastBit = 1; + } + else + { + lastBit = 0; + } + + if (inMask == 0x00000001u) + { + --inCurrent; + inMask = 0x80000000u; + } + else + { + inMask >>= 1; + } + + if (outMask == 0x00000001u) + { + --outCurrent; + outMask = 0x80000000u; + } + else + { + outMask >>= 1; + } + } + + // At this point as a sequence of bits, r = u_{n-p} ... u_0. + int32_t sign = input.GetSign(); + int32_t outExponent = input.GetExponent(); + if (roundingMode == FE_TONEAREST) + { + // Determine whether u_{n-p} is positive. + uint32_t positive = (inBits[inCurrent] & inMask) != 0u; + if (positive && (np1mp > 1 || lastBit == 1)) + { + // round up + outExponent += outW.RoundUp(); + } + // else round down, equivalent to truncating the r bits + } + else if (roundingMode == FE_UPWARD) + { + // The remainder r must be positive because n-p >= 0 and u_0 = 1. + if (sign > 0) + { + // round up + outExponent += outW.RoundUp(); + } + // else round down, equivalent to truncating the r bits + } + else if (roundingMode == FE_DOWNWARD) + { + // The remainder r must be positive because n-p >= 0 and u_0 = 1. + if (sign < 0) + { + // Round down. This is the round-up operation applied to + // w, but the final sign is negative which amounts to + // rounding down. + outExponent += outW.RoundUp(); + } + // else round down, equivalent to truncating the r bits + } + else if (roundingMode != FE_TOWARDZERO) + { + // Currently, no additional implementation-dependent modes + // are supported for rounding. + LogError("Implementation-dependent rounding mode not supported."); + } + // else roundingMode == FE_TOWARDZERO. Truncate the r bits, which + // requires no additional work. + + output.SetSign(sign); + output.SetBiasedExponent(outExponent - precisionM1); + } +} + +namespace std +{ + // TODO: Allow for implementations of the math functions in which a + // specified precision is used when computing the result. + + template + inline WwiseGTE::BSNumber acos(WwiseGTE::BSNumber const& x) + { + return (WwiseGTE::BSNumber)std::acos((double)x); + } + + template + inline WwiseGTE::BSNumber acosh(WwiseGTE::BSNumber const& x) + { + return (WwiseGTE::BSNumber)std::acosh((double)x); + } + + template + inline WwiseGTE::BSNumber asin(WwiseGTE::BSNumber const& x) + { + return (WwiseGTE::BSNumber)std::asin((double)x); + } + + template + inline WwiseGTE::BSNumber asinh(WwiseGTE::BSNumber const& x) + { + return (WwiseGTE::BSNumber)std::asinh((double)x); + } + + template + inline WwiseGTE::BSNumber atan(WwiseGTE::BSNumber const& x) + { + return (WwiseGTE::BSNumber)std::atan((double)x); + } + + template + inline WwiseGTE::BSNumber atanh(WwiseGTE::BSNumber const& x) + { + return (WwiseGTE::BSNumber)std::atanh((double)x); + } + + template + inline WwiseGTE::BSNumber atan2(WwiseGTE::BSNumber const& y, WwiseGTE::BSNumber const& x) + { + return (WwiseGTE::BSNumber)std::atan2((double)y, (double)x); + } + + template + inline WwiseGTE::BSNumber ceil(WwiseGTE::BSNumber const& x) + { + return (WwiseGTE::BSNumber)std::ceil((double)x); + } + + template + inline WwiseGTE::BSNumber cos(WwiseGTE::BSNumber const& x) + { + return (WwiseGTE::BSNumber)std::cos((double)x); + } + + template + inline WwiseGTE::BSNumber cosh(WwiseGTE::BSNumber const& x) + { + return (WwiseGTE::BSNumber)std::cosh((double)x); + } + + template + inline WwiseGTE::BSNumber exp(WwiseGTE::BSNumber const& x) + { + return (WwiseGTE::BSNumber)std::exp((double)x); + } + + template + inline WwiseGTE::BSNumber exp2(WwiseGTE::BSNumber const& x) + { + return (WwiseGTE::BSNumber)std::exp2((double)x); + } + + template + inline WwiseGTE::BSNumber fabs(WwiseGTE::BSNumber const& x) + { + return (x.GetSign() >= 0 ? x : -x); + } + + template + inline WwiseGTE::BSNumber floor(WwiseGTE::BSNumber const& x) + { + return (WwiseGTE::BSNumber)std::floor((double)x); + } + + template + inline WwiseGTE::BSNumber fmod(WwiseGTE::BSNumber const& x, WwiseGTE::BSNumber const& y) + { + return (WwiseGTE::BSNumber)std::fmod((double)x, (double)y); + } + + template + inline WwiseGTE::BSNumber frexp(WwiseGTE::BSNumber const& x, int* exponent) + { + if (x.GetSign() != 0) + { + WwiseGTE::BSNumber result = x; + *exponent = result.GetExponent() + 1; + result.SetExponent(-1); + return result; + } + else + { + *exponent = 0; + return WwiseGTE::BSNumber(0); + } + } + + template + inline WwiseGTE::BSNumber ldexp(WwiseGTE::BSNumber const& x, int exponent) + { + WwiseGTE::BSNumber result = x; + result.SetBiasedExponent(result.GetBiasedExponent() + exponent); + return result; + } + + template + inline WwiseGTE::BSNumber log(WwiseGTE::BSNumber const& x) + { + return (WwiseGTE::BSNumber)std::log((double)x); + } + + template + inline WwiseGTE::BSNumber log2(WwiseGTE::BSNumber const& x) + { + return (WwiseGTE::BSNumber)std::log2((double)x); + } + + template + inline WwiseGTE::BSNumber log10(WwiseGTE::BSNumber const& x) + { + return (WwiseGTE::BSNumber)std::log10((double)x); + } + + template + inline WwiseGTE::BSNumber pow(WwiseGTE::BSNumber const& x, WwiseGTE::BSNumber const& y) + { + return (WwiseGTE::BSNumber)std::pow((double)x, (double)y); + } + + template + inline WwiseGTE::BSNumber sin(WwiseGTE::BSNumber const& x) + { + return (WwiseGTE::BSNumber)std::sin((double)x); + } + + template + inline WwiseGTE::BSNumber sinh(WwiseGTE::BSNumber const& x) + { + return (WwiseGTE::BSNumber)std::sinh((double)x); + } + + template + inline WwiseGTE::BSNumber sqrt(WwiseGTE::BSNumber const& x) + { + return (WwiseGTE::BSNumber)std::sqrt((double)x); + } + + template + inline WwiseGTE::BSNumber tan(WwiseGTE::BSNumber const& x) + { + return (WwiseGTE::BSNumber)std::tan((double)x); + } + + template + inline WwiseGTE::BSNumber tanh(WwiseGTE::BSNumber const& x) + { + return (WwiseGTE::BSNumber)std::tanh((double)x); + } + + // Type trait that says BSNumber is a signed type. + template + struct is_signed> : true_type {}; +} + +namespace WwiseGTE +{ + template + inline BSNumber atandivpi(BSNumber const& x) + { + return (BSNumber)atandivpi((double)x); + } + + template + inline BSNumber atan2divpi(BSNumber const& y, BSNumber const& x) + { + return (BSNumber)atan2divpi((double)y, (double)x); + } + + template + inline BSNumber clamp(BSNumber const& x, BSNumber const& xmin, BSNumber const& xmax) + { + return (BSNumber)clamp((double)x, (double)xmin, (double)xmax); + } + + template + inline BSNumber cospi(BSNumber const& x) + { + return (BSNumber)cospi((double)x); + } + + template + inline BSNumber exp10(BSNumber const& x) + { + return (BSNumber)exp10((double)x); + } + + template + inline BSNumber invsqrt(BSNumber const& x) + { + return (BSNumber)invsqrt((double)x); + } + + template + inline int isign(BSNumber const& x) + { + return isign((double)x); + } + + template + inline BSNumber saturate(BSNumber const& x) + { + return (BSNumber)saturate((double)x); + } + + template + inline BSNumber sign(BSNumber const& x) + { + return (BSNumber)sign((double)x); + } + + template + inline BSNumber sinpi(BSNumber const& x) + { + return (BSNumber)sinpi((double)x); + } + + template + inline BSNumber sqr(BSNumber const& x) + { + return (BSNumber)sqr((double)x); + } + + // See the comments in GteMath.h about trait is_arbitrary_precision. + template + struct is_arbitrary_precision_internal> : std::true_type {}; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSPPolygon2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSPPolygon2.h new file mode 100644 index 0000000..4ae855b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSPPolygon2.h @@ -0,0 +1,848 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#define GTE_BSPPOLYGON2_ENABLE_DEBUG_PRINT +#if defined(GTE_BSPPOLYGON2_ENABLE_DEBUG_PRINT) +#include +#endif + +namespace WwiseGTE +{ + template + class BSPPolygon2 + { + public: + // vertices + typedef Vector2 Vertex; + typedef std::map VMap; + typedef std::vector VArray; + + // edges + typedef EdgeKey Edge; + typedef std::map EMap; + typedef std::vector EArray; + + // Construction and destruction. + BSPPolygon2(Real epsilon) + : + mEpsilon(epsilon >= (Real)0 ? epsilon : (Real)0) + { + } + + ~BSPPolygon2() + { + } + + // Copy semantics. + BSPPolygon2(BSPPolygon2 const& polygon) + { + *this = polygon; + } + + BSPPolygon2& operator=(BSPPolygon2 const& polygon) + { + mEpsilon = polygon.mEpsilon; + mVMap = polygon.mVMap; + mVArray = polygon.mVArray; + mEMap = polygon.mEMap; + mEArray = polygon.mEArray; + mTree = (polygon.mTree ? polygon.mTree->GetCopy() : nullptr); + return *this; + } + + // Move semantics. + BSPPolygon2(BSPPolygon2&& polygon) + { + *this = std::move(polygon); + } + + BSPPolygon2& operator=(BSPPolygon2&& polygon) + { + mEpsilon = polygon.mEpsilon; + mVMap = std::move(polygon.mVMap); + mVArray = std::move(polygon.mVArray); + mEMap = std::move(polygon.mEMap); + mEArray = std::move(polygon.mEArray); + mTree = polygon.mTree; + polygon.mTree = nullptr; + return *this; + } + + // Support for deferred construction. + int InsertVertex(Vertex const& vertex) + { + auto iter = mVMap.find(vertex); + if (iter != mVMap.end()) + { + // Vertex already in map, just return its unique index. + return iter->second; + } + + // Vertex not in map, insert it and assign it a unique index. + int i = static_cast(mVArray.size()); + mVMap.insert(std::make_pair(vertex, i)); + mVArray.push_back(vertex); + return i; + } + + int InsertEdge(Edge const& edge) + { + LogAssert(edge.V[0] != edge.V[1], "Degenerate edges not allowed."); + + auto iter = mEMap.find(edge); + if (iter != mEMap.end()) + { + // Edge already in map, just return its unique index. + return iter->second; + } + + // Edge not in map, insert it and assign it a unique index. + int i = static_cast(mEArray.size()); + mEMap.insert(std::make_pair(edge, i)); + mEArray.push_back(edge); + return i; + } + + void Finalize() + { + mTree = std::make_shared(*this, mEArray, mEpsilon); + } + + // Member access. + inline int GetNumVertices() const + { + return static_cast(mVMap.size()); + } + + inline Vertex const& GetVertex(int i) const + { + return mVArray[i]; + } + + inline int GetNumEdges() const + { + return static_cast(mEMap.size()); + } + + inline Edge const& GetEdge(int i) const + { + return mEArray[i]; + } + + // negation + BSPPolygon2 operator~() const + { + LogAssert(mTree != nullptr, "Tree must exist."); + + BSPPolygon2 neg(mEpsilon); + neg.mVMap = mVMap; + neg.mVArray = mVArray; + + for (auto const& element : mEMap) + { + auto const& edge = element.first; + neg.InsertEdge(Edge(edge.V[1], edge.V[0])); + } + + neg.mTree = mTree->GetCopy(); + neg.mTree->Negate(); + return neg; + } + + // intersection + BSPPolygon2 operator&(BSPPolygon2 const& polygon) const + { + LogAssert(mTree != nullptr, "Tree must exist."); + + BSPPolygon2 intersect(mEpsilon); + GetInsideEdgesFrom(polygon, intersect); + polygon.GetInsideEdgesFrom(*this, intersect); + intersect.Finalize(); + return intersect; + } + + // union + BSPPolygon2 operator|(BSPPolygon2 const& polygon) const + { + return ~(~*this & ~polygon); + } + + // difference + BSPPolygon2 operator-(BSPPolygon2 const& polygon) const + { + return *this & ~polygon; + } + + // exclusive or + BSPPolygon2 operator^(BSPPolygon2 const& polygon) const + { + return (*this - polygon) | (polygon - *this); + } + + // point location (-1 inside, 0 on polygon, 1 outside) + int PointLocation(Vertex const& vertex) const + { + LogAssert(mTree != nullptr, "Tree must exist."); + return mTree->PointLocation(*this, vertex); + } + +#if defined(GTE_BSPPOLYGON2_ENABLE_DEBUG_PRINT) + // debugging support + void Print(std::string const& filename) const + { + std::ofstream output(filename); + + int const numVertices = GetNumVertices(); + output << "vquantity = " << numVertices << std::endl; + for (int i = 0; i < numVertices; ++i) + { + output << i << " (" << mVArray[i][0] << ',' << mVArray[i][1] << ')' << std::endl; + } + output << std::endl; + + int const numEdges = GetNumEdges(); + output << "equantity = " << numEdges << std::endl; + for (int i = 0; i < numEdges; ++i) + { + output << " <" << mEArray[i].V[0] << ',' << mEArray[i].V[1] << '>' << std::endl; + } + output << std::endl; + + output << "bsp tree" << std::endl; + if (mTree) + { + mTree->Print(output, 0, 'r'); + } + output << std::endl; + output.close(); + } +#endif + + private: + // Binary space partitioning tree support for polygon Boolean + // operations. + class BSPTree2 + { + public: + // Construction and destruction. + BSPTree2(Real epsilon) + : + mEpsilon(epsilon >= (Real)0 ? epsilon : (Real)0) + { + } + + BSPTree2(BSPPolygon2& polygon, EArray const& edges, Real epsilon) + : + mEpsilon(epsilon >= (Real)0 ? epsilon : (Real)0) + { + LogAssert(edges.size() > 0, "Invalid input."); + + // Construct splitting line from first edge. + Vertex end0 = polygon.GetVertex(edges[0].V[0]); + Vertex end1 = polygon.GetVertex(edges[0].V[1]); + + // Add edge to coincident list. + mCoincident.push_back(edges[0]); + + // Split remaining edges. + EArray posArray, negArray; + for (size_t i = 1; i < edges.size(); ++i) + { + int v0 = edges[i].V[0]; + int v1 = edges[i].V[1]; + Vertex vertex0 = polygon.GetVertex(v0); + Vertex vertex1 = polygon.GetVertex(v1); + + Vertex intr; + int vmid; + + switch (Classify(end0, end1, vertex0, vertex1, intr)) + { + case TRANSVERSE_POSITIVE: + // modify edge to , add new edge + vmid = polygon.InsertVertex(intr); + polygon.SplitEdge(v0, v1, vmid); + posArray.push_back(Edge(vmid, v1)); + negArray.push_back(Edge(v0, vmid)); + break; + case TRANSVERSE_NEGATIVE: + // modify edge to , add new edge + vmid = polygon.InsertVertex(intr); + polygon.SplitEdge(v0, v1, vmid); + posArray.push_back(Edge(v0, vmid)); + negArray.push_back(Edge(vmid, v1)); + break; + case ALL_POSITIVE: + posArray.push_back(edges[i]); + break; + case ALL_NEGATIVE: + negArray.push_back(edges[i]); + break; + default: // COINCIDENT + mCoincident.push_back(edges[i]); + break; + } + } + + if (posArray.size() > 0) + { + mPosChild = std::make_shared(polygon, posArray, mEpsilon); + } + + if (negArray.size() > 0) + { + mNegChild = std::make_shared(polygon, negArray, mEpsilon); + } + } + + ~BSPTree2() + { + } + + // Disallow copying and assignment. Use GetCopy() instead to + // obtain a deep copy of the BSP tree. + BSPTree2(const BSPTree2&) = delete; + BSPTree2& operator= (const BSPTree2&) = delete; + + std::shared_ptr GetCopy() const + { + auto tree = std::make_shared(mEpsilon); + + tree->mCoincident = mCoincident; + + if (mPosChild) + { + tree->mPosChild = mPosChild->GetCopy(); + } + + if (mNegChild) + { + tree->mNegChild = mNegChild->GetCopy(); + } + + return tree; + } + + // Polygon Boolean operation support. + void Negate() + { + // Reverse coincident edge directions. + for (auto& edge : mCoincident) + { + std::swap(edge.V[0], edge.V[1]); + } + + // Swap positive and negative subtrees. + std::swap(mPosChild, mNegChild); + + if (mPosChild) + { + mPosChild->Negate(); + } + + if (mNegChild) + { + mNegChild->Negate(); + } + } + + void GetPartition(BSPPolygon2 const& polygon, Vertex const& v0, + Vertex const& v1, BSPPolygon2& pos, BSPPolygon2& neg, + BSPPolygon2& coSame, BSPPolygon2& coDiff) const + { + // Construct splitting line from first coincident edge. + Vertex end0 = polygon.GetVertex(mCoincident[0].V[0]); + Vertex end1 = polygon.GetVertex(mCoincident[0].V[1]); + + Vertex intr; + + switch (Classify(end0, end1, v0, v1, intr)) + { + case TRANSVERSE_POSITIVE: + GetPosPartition(polygon, intr, v1, pos, neg, coSame, coDiff); + GetNegPartition(polygon, v0, intr, pos, neg, coSame, coDiff); + break; + case TRANSVERSE_NEGATIVE: + GetPosPartition(polygon, v0, intr, pos, neg, coSame, coDiff); + GetNegPartition(polygon, intr, v1, pos, neg, coSame, coDiff); + break; + case ALL_POSITIVE: + GetPosPartition(polygon, v0, v1, pos, neg, coSame, coDiff); + break; + case ALL_NEGATIVE: + GetNegPartition(polygon, v0, v1, pos, neg, coSame, coDiff); + break; + default: // COINCIDENT + GetCoPartition(polygon, v0, v1, pos, neg, coSame, coDiff); + break; + } + } + + // Point-in-polygon support (-1 outside, 0 on polygon, +1 inside). + int PointLocation(BSPPolygon2 const& polygon, Vertex const& vertex) const + { + // Construct splitting line from first coincident edge. + Vertex end0 = polygon.GetVertex(mCoincident[0].V[0]); + Vertex end1 = polygon.GetVertex(mCoincident[0].V[1]); + + switch (Classify(end0, end1, vertex)) + { + case ALL_POSITIVE: + if (mPosChild) + { + return mPosChild->PointLocation(polygon, vertex); + } + else + { + return 1; + } + case ALL_NEGATIVE: + if (mNegChild) + { + return mNegChild->PointLocation(polygon, vertex); + } + else + { + return -1; + } + default: // COINCIDENT + return CoPointLocation(polygon, vertex); + } + } + +#if defined(GTE_BSPPOLYGON2_ENABLE_DEBUG_PRINT) + void Print(std::ofstream& outFile, int level, char type) const + { + for (size_t i = 0; i < mCoincident.size(); ++i) + { + for (int j = 0; j < 4 * level; ++j) + { + outFile << ' '; + } + + outFile << type << " <" << mCoincident[i].V[0] << ',' << + mCoincident[i].V[1] << ">" << std::endl; + } + + if (mPosChild) + { + mPosChild->Print(outFile, level + 1, 'p'); + } + + if (mNegChild) + { + mNegChild->Print(outFile, level + 1, 'n'); + } + } +#endif + + private: + enum + { + TRANSVERSE_POSITIVE, + TRANSVERSE_NEGATIVE, + ALL_POSITIVE, + ALL_NEGATIVE, + COINCIDENT + }; + + int Classify(Vertex const& end0, Vertex const& end1, + Vertex const& v0, Vertex const& v1, Vertex& intr) const + { + Vertex dir = end1 - end0; + Vertex nor = Perp(dir); + Vertex diff0 = v0 - end0; + Vertex diff1 = v1 - end0; + + Real d0 = Dot(nor, diff0); + Real d1 = Dot(nor, diff1); + + if (d0 * d1 < (Real)0) + { + // Edge transversely crosses line. Compute point + // of intersection I = V0 + t*(V1 - V0). + Real t = d0 / (d0 - d1); + if (t > mEpsilon) + { + if (t < (Real)1 - mEpsilon) + { + intr = v0 + t * (v1 - v0); + if (d1 > (Real)0) + { + return TRANSVERSE_POSITIVE; + } + else + { + return TRANSVERSE_NEGATIVE; + } + } + else + { + // T is effectively 1 (numerical round-off issue), + // so set d1 = 0 and go on to other cases. + d1 = (Real)0; + } + } + else + { + // T is effectively 0 (numerical round-off issue), so + // set d0 = 0 and go on to other cases. + d0 = (Real)0; + } + } + + if (d0 > (Real)0 || d1 > (Real)0) + { + // edge on positive side of line + return ALL_POSITIVE; + } + + if (d0 < (Real)0 || d1 < (Real)0) + { + // edge on negative side of line + return ALL_NEGATIVE; + } + + return COINCIDENT; + } + + void GetPosPartition(BSPPolygon2 const& polygon, Vertex const& v0, + Vertex const& v1, BSPPolygon2& pos, BSPPolygon2& neg, + BSPPolygon2& coSame, BSPPolygon2& coDiff) const + { + if (mPosChild) + { + mPosChild->GetPartition(polygon, v0, v1, pos, neg, coSame, coDiff); + } + else + { + int i0 = pos.InsertVertex(v0); + int i1 = pos.InsertVertex(v1); + pos.InsertEdge(Edge(i0, i1)); + } + } + + void GetNegPartition(BSPPolygon2 const& polygon, Vertex const& v0, + Vertex const& v1, BSPPolygon2& pos, BSPPolygon2& neg, + BSPPolygon2& coSame, BSPPolygon2& coDiff) const + { + if (mNegChild) + { + mNegChild->GetPartition(polygon, v0, v1, pos, neg, coSame, coDiff); + } + else + { + int i0 = neg.InsertVertex(v0); + int i1 = neg.InsertVertex(v1); + neg.InsertEdge(Edge(i0, i1)); + } + } + + class Interval + { + public: + Interval(Real inT0, Real inT1, bool inSameDir, bool inTouching) + : + t0(inT0), + t1(inT1), + sameDir(inSameDir), + touching(inTouching) + { + } + + Real t0, t1; + bool sameDir, touching; + }; + + void GetCoPartition(BSPPolygon2 const& polygon, Vertex const& v0, + Vertex const& v1, BSPPolygon2& pos, BSPPolygon2& neg, + BSPPolygon2& coSame, BSPPolygon2& coDiff) const + { + // Segment the line containing V0 and V1 by the coincident + // intervals that intersect . + Vertex dir = v1 - v0; + Real tmax = Dot(dir, dir); + + Vertex end0, end1; + Real t0, t1; + bool sameDir; + + std::list intervals; + typename std::list::iterator iter; + + for (auto const& edge : mCoincident) + { + end0 = polygon.GetVertex(edge.V[0]); + end1 = polygon.GetVertex(edge.V[1]); + + t0 = Dot(dir, end0 - v0); + if (std::fabs(t0) <= mEpsilon) + { + t0 = (Real)0; + } + else if (std::fabs(t0 - tmax) <= mEpsilon) + { + t0 = tmax; + } + + t1 = Dot(dir, end1 - v0); + if (std::fabs(t1) <= mEpsilon) + { + t1 = (Real)0; + } + else if (std::fabs(t1 - tmax) <= mEpsilon) + { + t1 = tmax; + } + + sameDir = (t1 > t0); + if (!sameDir) + { + Real save = t0; + t0 = t1; + t1 = save; + } + + if (t1 > (Real)0 && t0 < tmax) + { + if (intervals.empty()) + { + intervals.push_front(Interval(t0, t1, sameDir, true)); + } + else + { + for (iter = intervals.begin(); iter != intervals.end(); ++iter) + { + if (std::fabs(t1 - iter->t0) <= mEpsilon) + { + t1 = iter->t0; + } + + if (t1 <= iter->t0) + { + // [t0,t1] is on the left of [I.t0,I.t1] + intervals.insert(iter, Interval(t0, t1, sameDir, true)); + break; + } + + // Theoretically, the intervals are disjoint + // or intersect only at an end point. The + // assert makes sure that [t0,t1] is to the + // right of [I.t0,I.t1]. + if (std::fabs(t0 - iter->t1) <= mEpsilon) + { + t0 = iter->t1; + } + + LogAssert(t0 >= iter->t1, "Invalid ordering in BSPTree2::GetCoPartition."); + + auto last = std::prev(intervals.end()); + if (iter == last) + { + intervals.push_back(Interval(t0, t1, sameDir, true)); + break; + } + } + } + } + } + + if (intervals.empty()) + { + GetPosPartition(polygon, v0, v1, pos, neg, coSame, coDiff); + GetNegPartition(polygon, v0, v1, pos, neg, coSame, coDiff); + return; + } + + // Insert outside intervals between the touching intervals. + // It is possible that two touching intervals are adjacent, + // so this is not just a simple alternation of touching and + // outside intervals. + Interval& front = intervals.front(); + if (front.t0 > (Real)0) + { + intervals.push_front(Interval((Real)0, front.t0, front.sameDir, false)); + } + else + { + front.t0 = (Real)0; + } + + Interval& back = intervals.back(); + if (back.t1 < tmax) + { + intervals.push_back(Interval(back.t1, tmax, back.sameDir, false)); + } + else + { + back.t1 = tmax; + } + + typename std::list::iterator iter0 = intervals.begin(); + typename std::list::iterator iter1 = intervals.begin(); + for (++iter1; iter1 != intervals.end(); ++iter0, ++iter1) + { + t0 = iter0->t1; + t1 = iter1->t0; + if (t1 - t0 > mEpsilon) + { + iter0 = intervals.insert(iter1, Interval(t0, t1, true, false)); + } + } + + // Process the segmentation. + Real invTMax = (Real)1 / tmax; + t0 = intervals.front().t0 * invTMax; + end1 = v0 + (intervals.front().t0 * invTMax) * dir; + for (iter = intervals.begin(); iter != intervals.end(); ++iter) + { + end0 = end1; + t1 = iter->t1 * invTMax; + end1 = v0 + (iter->t1 * invTMax) * dir; + + if (iter->touching) + { + Edge edge; + if (iter->sameDir) + { + edge.V[0] = coSame.InsertVertex(end0); + edge.V[1] = coSame.InsertVertex(end1); + if (edge.V[0] != edge.V[1]) + { + coSame.InsertEdge(edge); + } + } + else + { + edge.V[0] = coDiff.InsertVertex(end1); + edge.V[1] = coDiff.InsertVertex(end0); + if (edge.V[0] != edge.V[1]) + { + coDiff.InsertEdge(edge); + } + } + } + else + { + GetPosPartition(polygon, end0, end1, pos, neg, coSame, coDiff); + GetNegPartition(polygon, end0, end1, pos, neg, coSame, coDiff); + } + } + } + + // point-in-polygon support + int Classify(Vertex const& end0, Vertex const& end1, Vertex const& vertex) const + { + Vertex dir = end1 - end0; + Vertex nor = Perp(dir); + Vertex diff = vertex - end0; + Real c = Dot(nor, diff); + + if (c > mEpsilon) + { + return ALL_POSITIVE; + } + + if (c < -mEpsilon) + { + return ALL_NEGATIVE; + } + + return COINCIDENT; + } + + int CoPointLocation(BSPPolygon2 const& polygon, Vertex const& vertex) const + { + for (auto const& edge : mCoincident) + { + Vertex end0 = polygon.GetVertex(edge.V[0]); + Vertex end1 = polygon.GetVertex(edge.V[1]); + Vertex dir = end1 - end0; + Vertex diff = vertex - end0; + Real tmax = Dot(dir, dir); + Real t = Dot(dir, diff); + + if (-mEpsilon <= t && t <= tmax + mEpsilon) + { + return 0; + } + } + + // It does not matter which subtree you use. + if (mPosChild) + { + return mPosChild->PointLocation(polygon, vertex); + } + + if (mNegChild) + { + return mNegChild->PointLocation(polygon, vertex); + } + + return 0; + } + + Real mEpsilon; + EArray mCoincident; + std::shared_ptr mPosChild; + std::shared_ptr mNegChild; + }; + + private: + void SplitEdge(int v0, int v1, int vmid) + { + // Find the edge in the map to get the edge-array index. + auto iter = mEMap.find(Edge(v0, v1)); + LogAssert(iter != mEMap.end(), "Edge does not exist."); + + int eIndex = iter->second; + + // Delete edge . + mEMap.erase(iter); + + // Insert edge . + mEArray[eIndex].V[1] = vmid; + mEMap.insert(std::make_pair(mEArray[eIndex], eIndex)); + + // Insert edge . + InsertEdge(Edge(vmid, v1)); + } + + void GetInsideEdgesFrom(BSPPolygon2 const& polygon, BSPPolygon2& inside) const + { + LogAssert(mTree != nullptr, "Tree must exist."); + + BSPPolygon2 ignore(mEpsilon); + const int numEdges = polygon.GetNumEdges(); + for (int i = 0; i < numEdges; ++i) + { + int v0 = polygon.mEArray[i].V[0]; + int v1 = polygon.mEArray[i].V[1]; + Vertex vertex0 = polygon.mVArray[v0]; + Vertex vertex1 = polygon.mVArray[v1]; + mTree->GetPartition(*this, vertex0, vertex1, ignore, inside, inside, ignore); + } + } + + Real mEpsilon; + VMap mVMap; + VArray mVArray; + EMap mEMap; + EArray mEArray; + std::shared_ptr mTree; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSPrecision.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSPrecision.h new file mode 100644 index 0000000..3b42d13 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSPrecision.h @@ -0,0 +1,239 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.09.25 + +#pragma once + +#include +#include +#include + +// Support for determining the number of bits of precision required to compute +// an expression using BSNumber or BSRational. + +namespace WwiseGTE +{ + class BSPrecision + { + public: + enum Type + { + IS_FLOAT, + IS_DOUBLE, + IS_INT32, + IS_INT64, + IS_UINT32, + IS_UINT64 + }; + + struct Parameters + { + Parameters() + : + minExponent(0), + maxExponent(0), + maxBits(0), + maxWords(0) + { + } + + Parameters(int inMinExponent, int inMaxExponent, int inMaxBits) + : + minExponent(inMinExponent), + maxExponent(inMaxExponent), + maxBits(inMaxBits), + maxWords(GetMaxWords()) + { + } + + inline int GetMaxWords() const + { + return maxBits / 32 + ((maxBits % 32) > 0 ? 1 : 0); + } + + int minExponent, maxExponent, maxBits, maxWords; + }; + + Parameters bsn, bsr; + + BSPrecision() = default; + + BSPrecision(Type type) + { + switch (type) + { + case IS_FLOAT: + bsn = Parameters(-149, 127, 24); + break; + case IS_DOUBLE: + bsn = Parameters(-1074, 1023, 53); + break; + case IS_INT32: + bsn = Parameters(0, 30, 31); + break; + case IS_INT64: + bsn = Parameters(0, 62, 63); + break; + case IS_UINT32: + bsn = Parameters(0, 31, 32); + break; + case IS_UINT64: + bsn = Parameters(0, 63, 64); + break; + } + bsr = bsn; + } + + BSPrecision(int minExponent, int maxExponent, int maxBits) + : + bsn(minExponent, maxExponent, maxBits), + bsr(minExponent, maxExponent, maxBits) + { + } + }; + + inline BSPrecision operator+(BSPrecision const& bsp0, BSPrecision const& bsp1) + { + BSPrecision result; + + result.bsn.minExponent = std::min(bsp0.bsn.minExponent, bsp1.bsn.minExponent); + if (bsp0.bsn.maxExponent >= bsp1.bsn.maxExponent) + { + result.bsn.maxExponent = bsp0.bsn.maxExponent; + if (bsp0.bsn.maxExponent - bsp0.bsn.maxBits + 1 <= bsp1.bsn.maxExponent) + { + ++result.bsn.maxExponent; + } + + result.bsn.maxBits = bsp0.bsn.maxExponent - bsp1.bsn.minExponent + 1; + if (result.bsn.maxBits <= bsp0.bsn.maxBits + bsp1.bsn.maxBits - 1) + { + ++result.bsn.maxBits; + } + } + else + { + result.bsn.maxExponent = bsp1.bsn.maxExponent; + if (bsp1.bsn.maxExponent - bsp1.bsn.maxBits + 1 <= bsp0.bsn.maxExponent) + { + ++result.bsn.maxExponent; + } + + result.bsn.maxBits = bsp1.bsn.maxExponent - bsp0.bsn.minExponent + 1; + if (result.bsn.maxBits <= bsp0.bsn.maxBits + bsp1.bsn.maxBits - 1) + { + ++result.bsn.maxBits; + } + } + result.bsn.maxWords = result.bsn.GetMaxWords(); + + // Addition is n0/d0 + n1/d1 = (n0*d1 + n1*d0)/(d0*d1). The numerator + // and denominator of a number are assumed to have the same + // parameters, so for the addition, the numerator is used for the + // parameter computations. + + // Compute the parameters for the multiplication. + int mulMinExponent = bsp0.bsr.minExponent + bsp1.bsr.minExponent; + int mulMaxExponent = bsp0.bsr.maxExponent + bsp1.bsr.maxExponent + 1; + int mulMaxBits = bsp0.bsr.maxBits + bsp1.bsr.maxBits; + + // Compute the parameters for the addition. The number n0*d1 and n1*d0 + // are in the same arbitrary-precision set. + result.bsr.minExponent = mulMinExponent; + result.bsr.maxExponent = mulMaxExponent + 1; // Always a carry-out. + result.bsr.maxBits = mulMaxExponent - mulMinExponent + 1; + if (result.bsr.maxBits <= 2 * mulMaxBits - 1) + { + ++result.bsr.maxBits; + } + result.bsr.maxWords = result.bsr.GetMaxWords(); + + return result; + } + + inline BSPrecision operator-(BSPrecision const& bsp0, BSPrecision const& bsp1) + { + return bsp0 + bsp1; + } + + inline BSPrecision operator*(BSPrecision const& bsp0, BSPrecision const& bsp1) + { + BSPrecision result; + + result.bsn.minExponent = bsp0.bsn.minExponent + bsp1.bsn.minExponent; + result.bsn.maxExponent = bsp0.bsn.maxExponent + bsp1.bsn.maxExponent + 1; + result.bsn.maxBits = bsp0.bsn.maxBits + bsp1.bsn.maxBits; + result.bsn.maxWords = result.bsn.GetMaxWords(); + + // Multiplication is (n0/d0) * (n1/d1) = (n0 * n1) / (d0 * d1). The + // parameters are the same as for bsn. + result.bsr = result.bsn; + + return result; + } + + inline BSPrecision operator/(BSPrecision const& bsp0, BSPrecision const& bsp1) + { + BSPrecision result; + + // BSNumber does not support division, so result.bsr has all members + // set to zero. + + // Division is (n0/d0) / (n1/d1) = (n0 * d1) / (n1 * d0). The + // parameters are the same as for multiplication. + result.bsr.minExponent = bsp0.bsr.minExponent + bsp1.bsr.minExponent; + result.bsr.maxExponent = bsp0.bsr.maxExponent + bsp1.bsr.maxExponent + 1; + result.bsr.maxBits = bsp0.bsr.maxBits + bsp1.bsr.maxBits; + result.bsr.maxWords = result.bsr.GetMaxWords(); + + return result; + } + + // Comparisons for BSNumber do not involve dynamic allocations, so + // the results are the extremes of the inputs. Comparisons for BSRational + // involve multiplications of numerators and denominators. + inline BSPrecision operator==(BSPrecision const& bsp0, BSPrecision const& bsp1) + { + BSPrecision result; + + result.bsn.minExponent = std::min(bsp0.bsn.minExponent, bsp1.bsn.minExponent); + result.bsn.maxExponent = std::max(bsp0.bsn.maxExponent, bsp1.bsn.maxExponent); + result.bsn.maxBits = std::max(bsp0.bsn.maxBits, bsp1.bsn.maxBits); + result.bsn.maxWords = result.bsn.GetMaxWords(); + + result.bsr.minExponent = bsp0.bsr.minExponent + bsp1.bsr.minExponent; + result.bsr.maxExponent = bsp0.bsr.maxExponent + bsp1.bsr.maxExponent + 1; + result.bsr.maxBits = bsp0.bsr.maxBits + bsp1.bsr.maxBits; + result.bsr.maxWords = result.bsr.GetMaxWords(); + + return result; + } + + inline BSPrecision operator!=(BSPrecision const& bsp0, BSPrecision const& bsp1) + { + return operator==(bsp0, bsp1); + } + + inline BSPrecision operator<(BSPrecision const& bsp0, BSPrecision const& bsp1) + { + return operator==(bsp0, bsp1); + } + + inline BSPrecision operator<=(BSPrecision const& bsp0, BSPrecision const& bsp1) + { + return operator==(bsp0, bsp1); + } + + inline BSPrecision operator>(BSPrecision const& bsp0, BSPrecision const& bsp1) + { + return operator==(bsp0, bsp1); + } + + inline BSPrecision operator>=(BSPrecision const& bsp0, BSPrecision const& bsp1) + { + return operator==(bsp0, bsp1); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSRational.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSRational.h new file mode 100644 index 0000000..6de7842 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSRational.h @@ -0,0 +1,1013 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.11.03 + +#pragma once + +#include + +// See the comments in BSNumber.h about the UInteger requirements. The +// denominator of a BSRational is chosen to be positive, which allows some +// simplification of comparisons. Also see the comments about exposing the +// GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE conditional define. + +namespace WwiseGTE +{ + template + class BSRational + { + public: + // Construction. The default constructor generates the zero + // BSRational. The constructors that take only numerators set the + // denominators to one. + BSRational() + : + mNumerator(0), + mDenominator(1) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(BSRational const& r) + { + *this = r; + } + + BSRational(float numerator) + : + mNumerator(numerator), + mDenominator(1.0f) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(double numerator) + : + mNumerator(numerator), + mDenominator(1.0) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(int32_t numerator) + : + mNumerator(numerator), + mDenominator(1) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(uint32_t numerator) + : + mNumerator(numerator), + mDenominator(1) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(int64_t numerator) + : + mNumerator(numerator), + mDenominator(1) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(uint64_t numerator) + : + mNumerator(numerator), + mDenominator(1) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(BSNumber const& numerator) + : + mNumerator(numerator), + mDenominator(1) + { +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(float numerator, float denominator) + : + mNumerator(numerator), + mDenominator(denominator) + { + LogAssert(mDenominator.mSign != 0, "Division by zero."); + if (mDenominator.mSign < 0) + { + mNumerator.mSign = -mNumerator.mSign; + mDenominator.mSign = 1; + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(double numerator, double denominator) + : + mNumerator(numerator), + mDenominator(denominator) + { + LogAssert(mDenominator.mSign != 0, "Division by zero."); + if (mDenominator.mSign < 0) + { + mNumerator.mSign = -mNumerator.mSign; + mDenominator.mSign = 1; + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(int32_t numerator, int32_t denominator) + : + mNumerator(numerator), + mDenominator(denominator) + { + LogAssert(mDenominator.mSign != 0, "Division by zero."); + if (mDenominator.mSign < 0) + { + mNumerator.mSign = -mNumerator.mSign; + mDenominator.mSign = 1; + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(uint32_t numerator, uint32_t denominator) + : + mNumerator(numerator), + mDenominator(denominator) + { + LogAssert(mDenominator.mSign != 0, "Division by zero."); + if (mDenominator.mSign < 0) + { + mNumerator.mSign = -mNumerator.mSign; + mDenominator.mSign = 1; + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(int64_t numerator, int64_t denominator) + : + mNumerator(numerator), + mDenominator(denominator) + { + LogAssert(mDenominator.mSign != 0, "Division by zero."); + if (mDenominator.mSign < 0) + { + mNumerator.mSign = -mNumerator.mSign; + mDenominator.mSign = 1; + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(uint64_t numerator, uint64_t denominator) + : + mNumerator(numerator), + mDenominator(denominator) + { + LogAssert(mDenominator.mSign != 0, "Division by zero."); + if (mDenominator.mSign < 0) + { + mNumerator.mSign = -mNumerator.mSign; + mDenominator.mSign = 1; + } +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(BSNumber const& numerator, BSNumber const& denominator) + : + mNumerator(numerator), + mDenominator(denominator) + { + LogAssert(mDenominator.mSign != 0, "Division by zero."); + if (mDenominator.mSign < 0) + { + mNumerator.mSign = -mNumerator.mSign; + mDenominator.mSign = 1; + } + + // Set the exponent of the denominator to zero, but you can do so + // only by modifying the biased exponent. Adjust the numerator + // accordingly. This prevents large growth of the exponents in + // both numerator and denominator simultaneously. + mNumerator.mBiasedExponent -= mDenominator.GetExponent(); + mDenominator.mBiasedExponent = -(mDenominator.GetUInteger().GetNumBits() - 1); + +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + } + + BSRational(std::string const& number) + { + LogAssert(number.size() > 0, "A number must be specified."); + + // Get the leading '+' or '-' if it exists. + std::string fpNumber; + int sign; + if (number[0] == '+') + { + fpNumber = number.substr(1); + sign = +1; + LogAssert(fpNumber.size() > 1, "Invalid number format."); + } + else if (number[0] == '-') + { + fpNumber = number.substr(1); + sign = -1; + LogAssert(fpNumber.size() > 1, "Invalid number format."); + } + else + { + fpNumber = number; + sign = +1; + } + + size_t decimal = fpNumber.find('.'); + if (decimal != std::string::npos) + { + if (decimal > 0) + { + if (decimal < fpNumber.size()) + { + // The number is "x.y". + BSNumber intPart = BSNumber::ConvertToInteger(fpNumber.substr(0, decimal)); + BSRational frcPart = ConvertToFraction(fpNumber.substr(decimal + 1)); + mNumerator = intPart * frcPart.mDenominator + frcPart.mNumerator; + mDenominator = frcPart.mDenominator; + } + else + { + // The number is "x.". + mNumerator = BSNumber::ConvertToInteger(fpNumber.substr(0,fpNumber.size()-1)); + mDenominator = 1; + } + } + else + { + // The number is ".y". + BSRational frcPart = ConvertToFraction(fpNumber.substr(1)); + mNumerator = frcPart.mNumerator; + mDenominator = frcPart.mDenominator; + } + } + else + { + // The number is "x". + mNumerator = BSNumber::ConvertToInteger(fpNumber); + mDenominator = 1; + } + mNumerator.SetSign(sign); + } + + BSRational(const char* number) + : + BSRational(std::string(number)) + { + } + + // Implicit conversions. These always use the default rounding mode, + // round-to-nearest-ties-to-even. + operator float() const + { + float output; + Convert(*this, FE_TONEAREST, output); + return output; + } + + operator double() const + { + double output; + Convert(*this, FE_TONEAREST, output); + return output; + } + + // Assignment. + BSRational& operator=(BSRational const& r) + { + mNumerator = r.mNumerator; + mDenominator = r.mDenominator; +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + return *this; + } + + // Support for move semantics. + BSRational(BSRational&& r) + { + *this = std::move(r); + } + + BSRational& operator=(BSRational&& r) + { + mNumerator = std::move(r.mNumerator); + mDenominator = std::move(r.mDenominator); +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + mValue = (double)*this; +#endif + return *this; + } + + // Member access. + inline void SetSign(int sign) + { + mNumerator.SetSign(sign); + mDenominator.SetSign(1); + } + + inline int GetSign() const + { + return mNumerator.GetSign() * mDenominator.GetSign(); + } + + inline BSNumber const& GetNumerator() const + { + return mNumerator; + } + + inline BSNumber& GetNumerator() + { + return mNumerator; + } + + inline BSNumber const& GetDenominator() const + { + return mDenominator; + } + + inline BSNumber& GetDenominator() + { + return mDenominator; + } + + // Comparisons. + bool operator==(BSRational const& r) const + { + // Do inexpensive sign tests first for optimum performance. + if (mNumerator.mSign != r.mNumerator.mSign) + { + return false; + } + if (mNumerator.mSign == 0) + { + // The numbers are both zero. + return true; + } + + return mNumerator * r.mDenominator == mDenominator * r.mNumerator; + } + + bool operator!=(BSRational const& r) const + { + return !operator==(r); + } + + bool operator< (BSRational const& r) const + { + // Do inexpensive sign tests first for optimum performance. + if (mNumerator.mSign > 0) + { + if (r.mNumerator.mSign <= 0) + { + return false; + } + } + else if (mNumerator.mSign == 0) + { + return r.mNumerator.mSign > 0; + } + else if (mNumerator.mSign < 0) + { + if (r.mNumerator.mSign >= 0) + { + return true; + } + } + + return mNumerator * r.mDenominator < mDenominator * r.mNumerator; + } + + bool operator<=(BSRational const& r) const + { + return !r.operator<(*this); + } + + bool operator> (BSRational const& r) const + { + return r.operator<(*this); + } + + bool operator>=(BSRational const& r) const + { + return !operator<(r); + } + + // Unary operations. + BSRational operator+() const + { + return *this; + } + + BSRational operator-() const + { + return BSRational(-mNumerator, mDenominator); + } + + // Arithmetic. + BSRational operator+(BSRational const& r) const + { + BSNumber product0 = mNumerator * r.mDenominator; + BSNumber product1 = mDenominator * r.mNumerator; + BSNumber numerator = product0 + product1; + + // Complex expressions can lead to 0/denom, where denom is not 1. + if (numerator.mSign != 0) + { + BSNumber denominator = mDenominator * r.mDenominator; + return BSRational(numerator, denominator); + } + else + { + return BSRational(0); + } + } + + BSRational operator-(BSRational const& r) const + { + BSNumber product0 = mNumerator * r.mDenominator; + BSNumber product1 = mDenominator * r.mNumerator; + BSNumber numerator = product0 - product1; + + // Complex expressions can lead to 0/denom, where denom is not 1. + if (numerator.mSign != 0) + { + BSNumber denominator = mDenominator * r.mDenominator; + return BSRational(numerator, denominator); + } + else + { + return BSRational(0); + } + } + + BSRational operator*(BSRational const& r) const + { + BSNumber numerator = mNumerator * r.mNumerator; + + // Complex expressions can lead to 0/denom, where denom is not 1. + if (numerator.mSign != 0) + { + BSNumber denominator = mDenominator * r.mDenominator; + return BSRational(numerator, denominator); + } + else + { + return BSRational(0); + } + } + + BSRational operator/(BSRational const& r) const + { + LogAssert(r.mNumerator.mSign != 0, "Division by zero in BSRational::operator/."); + + BSNumber numerator = mNumerator * r.mDenominator; + + // Complex expressions can lead to 0/denom, where denom is not 1. + if (numerator.mSign != 0) + { + BSNumber denominator = mDenominator * r.mNumerator; + if (denominator.mSign < 0) + { + numerator.mSign = -numerator.mSign; + denominator.mSign = 1; + } + return BSRational(numerator, denominator); + } + else + { + return BSRational(0); + } + } + + BSRational& operator+=(BSRational const& r) + { + *this = operator+(r); + return *this; + } + + BSRational& operator-=(BSRational const& r) + { + *this = operator-(r); + return *this; + } + + BSRational& operator*=(BSRational const& r) + { + *this = operator*(r); + return *this; + } + + BSRational& operator/=(BSRational const& r) + { + *this = operator/(r); + return *this; + } + + // Disk input/output. The fstream objects should be created using + // std::ios::binary. The return value is 'true' iff the operation + // was successful. + bool Write(std::ostream& output) const + { + return mNumerator.Write(output) && mDenominator.Write(output); + } + + bool Read(std::istream& input) + { + return mNumerator.Read(input) && mDenominator.Read(input); + } + + private: + // Helper for converting a string to a BSRational, where the string + // is the fractional part "y" of the string "x.y". + static BSRational ConvertToFraction(std::string const& number) + { + LogAssert(number.find_first_not_of("0123456789") == std::string::npos, "Invalid number format."); + BSRational y(0), ten(10), pow10(10); + for (size_t i = 0; i < number.size(); ++i) + { + int digit = static_cast(number[i]) - static_cast('0'); + if (digit > 0) + { + y += BSRational(digit) / pow10; + } + pow10 *= ten; + } + return y; + } + +#if defined(GTE_BINARY_SCIENTIFIC_SHOW_DOUBLE) + public: + // List this first so that it shows up first in the debugger watch + // window. + double mValue; + private: +#endif + + BSNumber mNumerator, mDenominator; + }; + + + // Explicit conversion to a user-specified precision. The rounding + // mode is one of the flags provided in . The modes are + // FE_TONEAREST: round to nearest ties to even + // FE_DOWNWARD: round towards negative infinity + // FE_TOWARDZERO: round towards zero + // FE_UPWARD: round towards positive infinity + template + void Convert(BSRational const& input, int32_t precision, + int32_t roundingMode, BSNumber& output) + { + if (precision <= 0) + { + LogError("Precision must be positive."); + } + + int64_t const maxSize = static_cast(UInteger::GetMaxSize()); + int64_t const excess = 32LL * maxSize - static_cast(precision); + if (excess <= 0) + { + LogError("The maximum precision has been exceeded."); + } + + if (input.GetSign() == 0) + { + output = BSNumber(0); + return; + } + + BSNumber n = input.GetNumerator(); + BSNumber d = input.GetDenominator(); + + // The ratio is abstractly of the form n/d = (1.u*2^p)/(1.v*2^q). + // Convert to the form + // (1.u/1.v)*2^{p-q}, if 1.u >= 1.v + // 2*(1.u/1.v)*2^{p-q-1} if 1.u < 1.v + // which are in the interval [1,2). + int32_t sign = n.GetSign() * d.GetSign(); + n.SetSign(1); + d.SetSign(1); + int32_t pmq = n.GetExponent() - d.GetExponent(); + n.SetExponent(0); + d.SetExponent(0); + if (n < d) + { + n.SetExponent(n.GetExponent() + 1); + --pmq; + } + + // Let p = precision. At this time, n/d = 1.c in [1,2). Define the + // sequence of bits w = 1c = w_{p-1} w_{p-2} ... w_0 r, where + // w_{p-1} = 1. The bits r after w_0 are used for rounding based on + // the user-specified rounding mode. + + // Compute p bits for w, the leading bit guaranteed to be 1 and + // occurring at index (1 << (precision-1)). + BSNumber one(1), two(2); + UInteger& w = output.GetUInteger(); + w.SetNumBits(precision); + w.SetAllBitsToZero(); + int32_t const size = w.GetSize(); + int32_t const precisionM1 = precision - 1; + int32_t const leading = precisionM1 % 32; + uint32_t mask = (1 << leading); + auto& bits = w.GetBits(); + int32_t current = size - 1; + int32_t lastBit = -1; + for (int i = precisionM1; i >= 0; --i) + { + if (n < d) + { + n = two * n; + lastBit = 0; + } + else + { + n = two * (n - d); + bits[current] |= mask; + lastBit = 1; + } + + if (mask == 0x00000001u) + { + --current; + mask = 0x80000000u; + } + else + { + mask >>= 1; + } + } + + // At this point as a sequence of bits, r = n/d = r0 r1 ... + if (roundingMode == FE_TONEAREST) + { + n = n - d; + if (n.GetSign() > 0 || (n.GetSign() == 0 && lastBit == 1)) + { + // round up + pmq += w.RoundUp(); + } + // else round down, equivalent to truncating the r bits + } + else if (roundingMode == FE_UPWARD) + { + if (n.GetSign() > 0 && sign > 0) + { + // round up + pmq += w.RoundUp(); + } + // else round down, equivalent to truncating the r bits + } + else if (roundingMode == FE_DOWNWARD) + { + if (n.GetSign() > 0 && sign < 0) + { + // Round down. This is the round-up operation applied to + // w, but the final sign is negative which amounts to + // rounding down. + pmq += w.RoundUp(); + } + // else round down, equivalent to truncating the r bits + } + else if (roundingMode != FE_TOWARDZERO) + { + // Currently, no additional implementation-dependent modes + // are supported for rounding. + LogError("Implementation-dependent rounding mode not supported."); + } + // else roundingMode == FE_TOWARDZERO. Truncate the r bits, which + // requires no additional work. + + // Do not use SetExponent(pmq) at this step. The number of + // requested bits is 'precision' but w.GetNumBits() will be + // different when round-up occurs, and SetExponent accesses + // w.GetNumBits(). + output.SetSign(sign); + output.SetBiasedExponent(pmq - precisionM1); + } + + // This conversion is used to avoid having to expose BSNumber in the + // APConversion class as well as other places where BSRational + // is passed via a template parameter named Rational. + template + void Convert(BSRational const& input, int32_t precision, + int32_t roundingMode, BSRational& output) + { + BSNumber numerator; + Convert(input, precision, roundingMode, numerator); + output = BSRational(numerator); + } + + // Convert to 'float' or 'double' using the specified rounding mode. + template + void Convert(BSRational const& input, int32_t roundingMode, FPType& output) + { + static_assert(std::is_floating_point::value, "Invalid floating-point type."); + BSNumber number; + Convert(input, std::numeric_limits::digits, roundingMode, number); + output = static_cast(number); + } +} + +namespace std +{ + // TODO: Allow for implementations of the math functions in which a + // specified precision is used when computing the result. + + template + inline WwiseGTE::BSRational acos(WwiseGTE::BSRational const& x) + { + return (WwiseGTE::BSRational)std::acos((double)x); + } + + template + inline WwiseGTE::BSRational acosh(WwiseGTE::BSRational const& x) + { + return (WwiseGTE::BSRational)std::acosh((double)x); + } + + template + inline WwiseGTE::BSRational asin(WwiseGTE::BSRational const& x) + { + return (WwiseGTE::BSRational)std::asin((double)x); + } + + template + inline WwiseGTE::BSRational asinh(WwiseGTE::BSRational const& x) + { + return (WwiseGTE::BSRational)std::asinh((double)x); + } + + template + inline WwiseGTE::BSRational atan(WwiseGTE::BSRational const& x) + { + return (WwiseGTE::BSRational)std::atan((double)x); + } + + template + inline WwiseGTE::BSRational atanh(WwiseGTE::BSRational const& x) + { + return (WwiseGTE::BSRational)std::atanh((double)x); + } + + template + inline WwiseGTE::BSRational atan2(WwiseGTE::BSRational const& y, WwiseGTE::BSRational const& x) + { + return (WwiseGTE::BSRational)std::atan2((double)y, (double)x); + } + + template + inline WwiseGTE::BSRational ceil(WwiseGTE::BSRational const& x) + { + return (WwiseGTE::BSRational)std::ceil((double)x); + } + + template + inline WwiseGTE::BSRational cos(WwiseGTE::BSRational const& x) + { + return (WwiseGTE::BSRational)std::cos((double)x); + } + + template + inline WwiseGTE::BSRational cosh(WwiseGTE::BSRational const& x) + { + return (WwiseGTE::BSRational)std::cosh((double)x); + } + + template + inline WwiseGTE::BSRational exp(WwiseGTE::BSRational const& x) + { + return (WwiseGTE::BSRational)std::exp((double)x); + } + + template + inline WwiseGTE::BSRational exp2(WwiseGTE::BSRational const& x) + { + return (WwiseGTE::BSRational)std::exp2((double)x); + } + + template + inline WwiseGTE::BSRational fabs(WwiseGTE::BSRational const& x) + { + return (x.GetSign() >= 0 ? x : -x); + } + + template + inline WwiseGTE::BSRational floor(WwiseGTE::BSRational const& x) + { + return (WwiseGTE::BSRational)std::floor((double)x); + } + + template + inline WwiseGTE::BSRational fmod(WwiseGTE::BSRational const& x, WwiseGTE::BSRational const& y) + { + return (WwiseGTE::BSRational)std::fmod((double)x, (double)y); + } + + template + inline WwiseGTE::BSRational frexp(WwiseGTE::BSRational const& x, int* exponent) + { + WwiseGTE::BSRational result = x; + auto& numer = result.GetNumerator(); + auto& denom = result.GetDenominator(); + int32_t e = numer.GetExponent() - denom.GetExponent(); + numer.SetExponent(0); + denom.SetExponent(0); + int32_t saveSign = numer.GetSign(); + numer.SetSign(1); + if (numer >= denom) + { + ++e; + numer.SetExponent(-1); + } + numer.SetSign(saveSign); + *exponent = e; + return result; + } + + template + inline WwiseGTE::BSRational ldexp(WwiseGTE::BSRational const& x, int exponent) + { + WwiseGTE::BSRational result = x; + int biasedExponent = result.GetNumerator().GetBiasedExponent(); + biasedExponent += exponent; + result.GetNumerator().SetBiasedExponent(biasedExponent); + return result; + } + + template + inline WwiseGTE::BSRational log(WwiseGTE::BSRational const& x) + { + return (WwiseGTE::BSRational)std::log((double)x); + } + + template + inline WwiseGTE::BSRational log2(WwiseGTE::BSRational const& x) + { + return (WwiseGTE::BSRational)std::log2((double)x); + } + + template + inline WwiseGTE::BSRational log10(WwiseGTE::BSRational const& x) + { + return (WwiseGTE::BSRational)std::log10((double)x); + } + + template + inline WwiseGTE::BSRational pow(WwiseGTE::BSRational const& x, WwiseGTE::BSRational const& y) + { + return (WwiseGTE::BSRational)std::pow((double)x, (double)y); + } + + template + inline WwiseGTE::BSRational sin(WwiseGTE::BSRational const& x) + { + return (WwiseGTE::BSRational)std::sin((double)x); + } + + template + inline WwiseGTE::BSRational sinh(WwiseGTE::BSRational const& x) + { + return (WwiseGTE::BSRational)std::sinh((double)x); + } + + template + inline WwiseGTE::BSRational sqrt(WwiseGTE::BSRational const& x) + { + return (WwiseGTE::BSRational)std::sqrt((double)x); + } + + template + inline WwiseGTE::BSRational tan(WwiseGTE::BSRational const& x) + { + return (WwiseGTE::BSRational)std::tan((double)x); + } + + template + inline WwiseGTE::BSRational tanh(WwiseGTE::BSRational const& x) + { + return (WwiseGTE::BSRational)std::tanh((double)x); + } + + // Type trait that says BSRational is a signed type. + template + struct is_signed> : true_type {}; +} + +namespace WwiseGTE +{ + template + inline BSRational atandivpi(BSRational const& x) + { + return (BSRational)atandivpi((double)x); + } + + template + inline BSRational atan2divpi(BSRational const& y, BSRational const& x) + { + return (BSRational)atan2divpi((double)y, (double)x); + } + + template + inline BSRational clamp(BSRational const& x, BSRational const& xmin, BSRational const& xmax) + { + return (BSRational)clamp((double)x, (double)xmin, (double)xmax); + } + + template + inline BSRational cospi(BSRational const& x) + { + return (BSRational)cospi((double)x); + } + + template + inline BSRational exp10(BSRational const& x) + { + return (BSRational)exp10((double)x); + } + + template + inline BSRational invsqrt(BSRational const& x) + { + return (BSRational)invsqrt((double)x); + } + + template + inline int isign(BSRational const& x) + { + return isign((double)x); + } + + template + inline BSRational saturate(BSRational const& x) + { + return (BSRational)saturate((double)x); + } + + template + inline BSRational sign(BSRational const& x) + { + return (BSRational)sign((double)x); + } + + template + inline BSRational sinpi(BSRational const& x) + { + return (BSRational)sinpi((double)x); + } + + template + inline BSRational sqr(BSRational const& x) + { + return (BSRational)sqr((double)x); + } + + // See the comments in Math.h about traits is_arbitrary_precision + // and has_division_operator. + template + struct is_arbitrary_precision_internal> : std::true_type {}; + + template + struct has_division_operator_internal> : std::true_type {}; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSplineCurve.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSplineCurve.h new file mode 100644 index 0000000..afb9066 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSplineCurve.h @@ -0,0 +1,155 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class BSplineCurve : public ParametricCurve + { + public: + // Construction. If the input controls is non-null, a copy is made of + // the controls. To defer setting the control points, pass a null + // pointer and later access the control points via GetControls() or + // SetControl() member functions. The domain is t in [t[d],t[n]], + // where t[d] and t[n] are knots with d the degree and n the number of + // control points. + BSplineCurve(BasisFunctionInput const& input, Vector const* controls) + : + ParametricCurve((Real)0, (Real)1), + mBasisFunction(input) + { + // The mBasisFunction stores the domain but so does + // ParametricCurve. + this->mTime.front() = mBasisFunction.GetMinDomain(); + this->mTime.back() = mBasisFunction.GetMaxDomain(); + + // The replication of control points for periodic splines is + // avoided by wrapping the i-loop index in Evaluate. + mControls.resize(input.numControls); + if (controls) + { + std::copy(controls, controls + input.numControls, mControls.begin()); + } + else + { + Vector zero{ (Real)0 }; + std::fill(mControls.begin(), mControls.end(), zero); + } + this->mConstructed = true; + } + + // Member access. + inline BasisFunction const& GetBasisFunction() const + { + return mBasisFunction; + } + + inline int GetNumControls() const + { + return static_cast(mControls.size()); + } + + inline Vector const* GetControls() const + { + return mControls.data(); + } + + inline Vector* GetControls() + { + return mControls.data(); + } + + void SetControl(int i, Vector const& control) + { + if (0 <= i && i < GetNumControls()) + { + mControls[i] = control; + } + } + + Vector const& GetControl(int i) const + { + if (0 <= i && i < GetNumControls()) + { + return mControls[i]; + } + else + { + return mControls[0]; + } + } + + // Evaluation of the curve. The function supports derivative + // calculation through order 3; that is, order <= 3 is required. If + // you want/ only the position, pass in order of 0. If you want the + // position and first derivative, pass in order of 1, and so on. The + // output array 'jet' must have enough storage to support the maximum + // order. The values are ordered as: position, first derivative, + // second derivative, third derivative. + virtual void Evaluate(Real t, unsigned int order, Vector* jet) const override + { + unsigned int const supOrder = ParametricCurve::SUP_ORDER; + if (!this->mConstructed || order >= supOrder) + { + // Return a zero-valued jet for invalid state. + for (unsigned int i = 0; i < supOrder; ++i) + { + jet[i].MakeZero(); + } + return; + } + + int imin, imax; + mBasisFunction.Evaluate(t, order, imin, imax); + + // Compute position. + jet[0] = Compute(0, imin, imax); + if (order >= 1) + { + // Compute first derivative. + jet[1] = Compute(1, imin, imax); + if (order >= 2) + { + // Compute second derivative. + jet[2] = Compute(2, imin, imax); + if (order == 3) + { + jet[3] = Compute(3, imin, imax); + } + } + } + } + + private: + // Support for Evaluate(...). + Vector Compute(unsigned int order, int imin, int imax) const + { + // The j-index introduces a tiny amount of overhead in order to handle + // both aperiodic and periodic splines. For aperiodic splines, j = i + // always. + + int numControls = GetNumControls(); + Vector result; + result.MakeZero(); + for (int i = imin; i <= imax; ++i) + { + Real tmp = mBasisFunction.GetValue(order, i); + int j = (i >= numControls ? i - numControls : i); + result += tmp * mControls[j]; + } + return result; + } + + BasisFunction mBasisFunction; + std::vector> mControls; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSplineCurveFit.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSplineCurveFit.h new file mode 100644 index 0000000..d32fff5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSplineCurveFit.h @@ -0,0 +1,238 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The algorithm implemented here is based on the document +// https://www.geometrictools.com/Documentation/BSplineCurveLeastSquaresFit.pdf + +namespace WwiseGTE +{ + template + class BSplineCurveFit + { + public: + // Construction. The preconditions for calling the constructor are + // 1 <= degree && degree < numControls <= numSamples + // The samples points are contiguous blocks of 'dimension' real values + // stored in sampleData. + BSplineCurveFit(int dimension, int numSamples, Real const* sampleData, + int degree, int numControls) + : + mDimension(dimension), + mNumSamples(numSamples), + mSampleData(sampleData), + mDegree(degree), + mNumControls(numControls), + mControlData(dimension * numControls) + { + LogAssert(dimension >= 1, "Invalid dimension."); + LogAssert(1 <= degree && degree < numControls, "Invalid degree."); + LogAssert(sampleData, "Invalid sample data."); + LogAssert(numControls <= numSamples, "Invalid number of controls."); + + BasisFunctionInput input; + input.numControls = numControls; + input.degree = degree; + input.uniform = true; + input.periodic = false; + input.numUniqueKnots = numControls - degree + 1; + input.uniqueKnots.resize(input.numUniqueKnots); + input.uniqueKnots[0].t = (Real)0; + input.uniqueKnots[0].multiplicity = degree + 1; + int last = input.numUniqueKnots - 1; + Real factor = ((Real)1) / (Real)last; + for (int i = 1; i < last; ++i) + { + input.uniqueKnots[i].t = factor * (Real)i; + input.uniqueKnots[i].multiplicity = 1; + } + input.uniqueKnots[last].t = (Real)1; + input.uniqueKnots[last].multiplicity = degree + 1; + mBasis.Create(input); + + // Fit the data points with a B-spline curve using a least-squares + // error metric. The problem is of the form A^T*A*Q = A^T*P, + // where A^T*A is a banded matrix, P contains the sample data, and + // Q is the unknown vector of control points. + Real tMultiplier = ((Real)1) / (Real)(mNumSamples - 1); + Real t; + int i0, i1, i2, imin, imax, j; + + // Construct the matrix A^T*A. + int degp1 = mDegree + 1; + int numBands = (mNumControls > degp1 ? degp1 : mDegree); + BandedMatrix ATAMat(mNumControls, numBands, numBands); + for (i0 = 0; i0 < mNumControls; ++i0) + { + for (i1 = 0; i1 < i0; ++i1) + { + ATAMat(i0, i1) = ATAMat(i1, i0); + } + + int i1Max = i0 + mDegree; + if (i1Max >= mNumControls) + { + i1Max = mNumControls - 1; + } + + for (i1 = i0; i1 <= i1Max; ++i1) + { + Real value = (Real)0; + for (i2 = 0; i2 < mNumSamples; ++i2) + { + t = tMultiplier * (Real)i2; + mBasis.Evaluate(t, 0, imin, imax); + if (imin <= i0 && i0 <= imax && imin <= i1 && i1 <= imax) + { + Real b0 = mBasis.GetValue(0, i0); + Real b1 = mBasis.GetValue(0, i1); + value += b0 * b1; + } + } + ATAMat(i0, i1) = value; + } + } + + // Construct the matrix A^T. + Array2 ATMat(mNumSamples, mNumControls); + std::memset(ATMat[0], 0, mNumControls * mNumSamples * sizeof(Real)); + for (i0 = 0; i0 < mNumControls; ++i0) + { + for (i1 = 0; i1 < mNumSamples; ++i1) + { + t = tMultiplier * (Real)i1; + mBasis.Evaluate(t, 0, imin, imax); + if (imin <= i0 && i0 <= imax) + { + ATMat[i0][i1] = mBasis.GetValue(0, i0); + } + } + } + + // Compute X0 = (A^T*A)^{-1}*A^T by solving the linear system + // A^T*A*X = A^T. + bool solved = ATAMat.template SolveSystem(ATMat[0], mNumSamples); + LogAssert(solved, "Failed to solve linear system."); + + // The control points for the fitted curve are stored in the + // vector Q = X0*P, where P is the vector of sample data. + std::fill(mControlData.begin(), mControlData.end(), (Real)0); + for (i0 = 0; i0 < mNumControls; ++i0) + { + Real* Q = &mControlData[i0 * mDimension]; + for (i1 = 0; i1 < mNumSamples; ++i1) + { + Real const* P = mSampleData + i1 * mDimension; + Real xValue = ATMat[i0][i1]; + for (j = 0; j < mDimension; ++j) + { + Q[j] += xValue * P[j]; + } + } + } + + // Set the first and last output control points to match the first + // and last input samples. This supports the application of + // fitting keyframe data with B-spline curves. The user expects + // that the curve passes through the first and last positions in + // order to support matching two consecutive keyframe sequences. + Real* cEnd0 = &mControlData[0]; + Real const* sEnd0 = mSampleData; + Real* cEnd1 = &mControlData[mDimension * (mNumControls - 1)]; + Real const* sEnd1 = &mSampleData[mDimension * (mNumSamples - 1)]; + for (j = 0; j < mDimension; ++j) + { + *cEnd0++ = *sEnd0++; + *cEnd1++ = *sEnd1++; + } + } + + // Access to input sample information. + inline int GetDimension() const + { + return mDimension; + } + + inline int GetNumSamples() const + { + return mNumSamples; + } + + inline Real const* GetSampleData() const + { + return mSampleData; + } + + // Access to output control point and curve information. + inline int GetDegree() const + { + return mDegree; + } + + inline int GetNumControls() const + { + return mNumControls; + } + + inline Real const* GetControlData() const + { + return &mControlData[0]; + } + + inline BasisFunction const& GetBasis() const + { + return mBasis; + } + + // Evaluation of the B-spline curve. It is defined for 0 <= t <= 1. + // If a t-value is outside [0,1], an open spline clamps it to [0,1]. + // The caller must ensure that position[] has at least 'dimension' + // elements. + void Evaluate(Real t, unsigned int order, Real* value) const + { + int imin, imax; + mBasis.Evaluate(t, order, imin, imax); + + Real const* source = &mControlData[mDimension * imin]; + Real basisValue = mBasis.GetValue(order, imin); + for (int j = 0; j < mDimension; ++j) + { + value[j] = basisValue * (*source++); + } + + for (int i = imin + 1; i <= imax; ++i) + { + basisValue = mBasis.GetValue(order, i); + for (int j = 0; j < mDimension; ++j) + { + value[j] += basisValue * (*source++); + } + } + } + + void GetPosition(Real t, Real* position) const + { + Evaluate(t, 0, position); + } + + private: + // Input sample information. + int mDimension; + int mNumSamples; + Real const* mSampleData; + + // The fitted B-spline curve, open and with uniform knots. + int mDegree; + int mNumControls; + std::vector mControlData; + BasisFunction mBasis; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSplineGeodesic.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSplineGeodesic.h new file mode 100644 index 0000000..1cd9268 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSplineGeodesic.h @@ -0,0 +1,70 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class BSplineGeodesic : public RiemannianGeodesic + { + public: + BSplineGeodesic(BSplineSurface<3, Real> const& spline) + : + RiemannianGeodesic(2), + mSpline(&spline) + { + } + + virtual ~BSplineGeodesic() + { + } + + private: + virtual void ComputeMetric(const GVector& point) override + { + mSpline->Evaluate(point[0], point[1], 2, mJet); + Vector<3, Real> const& der0 = mJet[1]; + Vector<3, Real> const& der1 = mJet[2]; + + this->mMetric(0, 0) = Dot(der0, der0); + this->mMetric(0, 1) = Dot(der0, der1); + this->mMetric(1, 0) = this->mMetric(0, 1); + this->mMetric(1, 1) = Dot(der1, der1); + } + + virtual void ComputeChristoffel1(const GVector&) override + { + Vector<3, Real> const& der0 = mJet[1]; + Vector<3, Real> const& der1 = mJet[2]; + Vector<3, Real> const& der00 = mJet[3]; + Vector<3, Real> const& der01 = mJet[4]; + Vector<3, Real> const& der11 = mJet[5]; + + this->mChristoffel1[0](0, 0) = Dot(der00, der0); + this->mChristoffel1[0](0, 1) = Dot(der01, der0); + this->mChristoffel1[0](1, 0) = this->mChristoffel1[0](0, 1); + this->mChristoffel1[0](1, 1) = Dot(der11, der0); + + this->mChristoffel1[1](0, 0) = Dot(der00, der1); + this->mChristoffel1[1](0, 1) = Dot(der01, der1); + this->mChristoffel1[1](1, 0) = this->mChristoffel1[1](0, 1); + this->mChristoffel1[1](1, 1) = Dot(der11, der1); + } + + BSplineSurface<3, Real> const* mSpline; + + // We are guaranteed that RiemannianGeodesic calls ComputeMetric + // before ComputeChristoffel1. Thus, we can compute the B-spline + // first- and second-order derivatives in ComputeMetric and cache + // the results for use in ComputeChristoffel1. + Vector<3, Real> mJet[6]; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSplineReduction.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSplineReduction.h new file mode 100644 index 0000000..1a616d5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSplineReduction.h @@ -0,0 +1,226 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// The BSplineReduction class is an implementation of the algorithm in +// https://www.geometrictools.com/Documentation/BSplineReduction.pdf +// for least-squares fitting of points in the continuous sense by +// an L2 integral norm. The least-squares fitting implemented in the +// file GteBSplineCurveFit.h is in the discrete sense by an L2 summation. +// The intended use for this class is to take an open B-spline curve, +// defined by its control points and degree, and reducing the number of +// control points dramatically to obtain another curve that is close to +// the original one. + +namespace WwiseGTE +{ + // The input numCtrlPoints must be 2 or larger. The input degree must + // satisfy the condition 1 <= degree <= inControls.size()-1. The degree + // of the output curve is the same as that of the input curve. The input + // fraction must be in [0,1]. If the fraction is 1, the output curve + // is identical to the input curve. If the fraction is too small to + // produce a valid number of control points, outControls.size() is chosen + // to be degree+1. + template + class BSplineReduction + { + public: + void operator()(std::vector> const& inControls, + int degree, Real fraction, std::vector>& outControls) + { + int numInControls = static_cast(inControls.size()); + LogAssert(numInControls >= 2 && 1 <= degree && degree < numInControls, "Invalid input."); + + // Clamp the number of control points to [degree+1,quantity-1]. + int numOutControls = static_cast(fraction * numInControls); + if (numOutControls >= numInControls) + { + outControls = inControls; + return; + } + if (numOutControls < degree + 1) + { + numOutControls = degree + 1; + } + + // Allocate output control points. + outControls.resize(numOutControls); + + // Set up basis function parameters. Function 0 corresponds to + // the output curve. Function 1 corresponds to the input curve. + mDegree = degree; + mQuantity[0] = numOutControls; + mQuantity[1] = numInControls; + + for (int j = 0; j <= 1; ++j) + { + mNumKnots[j] = mQuantity[j] + mDegree + 1; + mKnot[j].resize(mNumKnots[j]); + + int i; + for (i = 0; i <= mDegree; ++i) + { + mKnot[j][i] = (Real)0; + } + + Real factor = (Real)1 / static_cast(mQuantity[j] - mDegree); + for (/**/; i < mQuantity[j]; ++i) + { + mKnot[j][i] = (i - mDegree) * factor; + } + + for (/**/; i < mNumKnots[j]; ++i) + { + mKnot[j][i] = (Real)1; + } + } + + // Construct matrix A (depends only on the output basis function). + Real value, tmin, tmax; + int i0, i1; + + mBasis[0] = 0; + mBasis[1] = 0; + + std::function integrand = [this](Real t) + { + Real value0 = F(mBasis[0], mIndex[0], mDegree, t); + Real value1 = F(mBasis[1], mIndex[1], mDegree, t); + Real result = value0 * value1; + return result; + }; + + BandedMatrix A(mQuantity[0], mDegree, mDegree); + for (i0 = 0; i0 < mQuantity[0]; ++i0) + { + mIndex[0] = i0; + tmax = MaxSupport(0, i0); + + for (i1 = i0; i1 <= i0 + mDegree && i1 < mQuantity[0]; ++i1) + { + mIndex[1] = i1; + tmin = MinSupport(0, i1); + + value = Integration::Romberg(8, tmin, tmax, integrand); + A(i0, i1) = value; + A(i1, i0) = value; + } + } + + // Construct A^{-1}. TODO: This is inefficient. Use an iterative + // scheme to invert A? + GMatrix invA(mQuantity[0], mQuantity[0]); + bool invertible = A.template ComputeInverse(&invA[0]); + LogAssert(invertible, "Failed to invert matrix."); + + // Construct B (depends on both input and output basis functions). + mBasis[1] = 1; + GMatrix B(mQuantity[0], mQuantity[1]); + FIQuery, std::array> query; + for (i0 = 0; i0 < mQuantity[0]; ++i0) + { + mIndex[0] = i0; + Real tmin0 = MinSupport(0, i0); + Real tmax0 = MaxSupport(0, i0); + + for (i1 = 0; i1 < mQuantity[1]; ++i1) + { + mIndex[1] = i1; + Real tmin1 = MinSupport(1, i1); + Real tmax1 = MaxSupport(1, i1); + + std::array interval0 = { tmin0, tmax0 }; + std::array interval1 = { tmin1, tmax1 }; + auto result = query(interval0, interval1); + if (result.numIntersections == 2) + { + value = Integration::Romberg(8, result.overlap[0], + result.overlap[1], integrand); + + B(i0, i1) = value; + } + else + { + B(i0, i1) = (Real)0; + } + } + } + + // Construct A^{-1}*B. + GMatrix prod = invA * B; + + // Construct the control points for the least-squares curve. + std::fill(outControls.begin(), outControls.end(), Vector::Zero()); + for (i0 = 0; i0 < mQuantity[0]; ++i0) + { + for (i1 = 0; i1 < mQuantity[1]; ++i1) + { + outControls[i0] += inControls[i1] * prod(i0, i1); + } + } + } + + private: + inline Real MinSupport(int basis, int i) const + { + return mKnot[basis][i]; + } + + inline Real MaxSupport(int basis, int i) const + { + return mKnot[basis][i + 1 + mDegree]; + } + + Real F(int basis, int i, int j, Real t) + { + if (j > 0) + { + Real result = (Real)0; + + Real denom = mKnot[basis][i + j] - mKnot[basis][i]; + if (denom > (Real)0) + { + result += (t - mKnot[basis][i]) * + F(basis, i, j - 1, t) / denom; + } + + denom = mKnot[basis][i + j + 1] - mKnot[basis][i + 1]; + if (denom > (Real)0) + { + result += (mKnot[basis][i + j + 1] - t) * + F(basis, i + 1, j - 1, t) / denom; + } + + return result; + } + + if (mKnot[basis][i] <= t && t < mKnot[basis][i + 1]) + { + return (Real)1; + } + else + { + return (Real)0; + } + } + + int mDegree; + std::array mQuantity; + std::array mNumKnots; // N+D+2 + std::array, 2> mKnot; + + // For the integration-based least-squares fitting. + std::array mBasis, mIndex; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSplineSurface.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSplineSurface.h new file mode 100644 index 0000000..a1d875b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSplineSurface.h @@ -0,0 +1,173 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class BSplineSurface : public ParametricSurface + { + public: + // Construction. If the input controls is non-null, a copy is made of + // the controls. To defer setting the control points, pass a null + // pointer and later access the control points via GetControls() or + // SetControl() member functions. The input 'controls' must be stored + // in row-major order, control[i0 + numControls0*i1]. As a 2D array, + // this corresponds to control2D[i1][i0]. + BSplineSurface(BasisFunctionInput const input[2], Vector const* controls) + : + ParametricSurface((Real)0, (Real)1, (Real)0, (Real)1, true) + { + for (int i = 0; i < 2; ++i) + { + mNumControls[i] = input[i].numControls; + mBasisFunction[i].Create(input[i]); + } + + // The mBasisFunction stores the domain but so does + // ParametricCurve. + this->mUMin = mBasisFunction[0].GetMinDomain(); + this->mUMax = mBasisFunction[0].GetMaxDomain(); + this->mVMin = mBasisFunction[1].GetMinDomain(); + this->mVMax = mBasisFunction[1].GetMaxDomain(); + + // The replication of control points for periodic splines is + // avoided by wrapping the i-loop index in Evaluate. + int numControls = mNumControls[0] * mNumControls[1]; + mControls.resize(numControls); + if (controls) + { + std::copy(controls, controls + numControls, mControls.begin()); + } + else + { + Vector zero{ (Real)0 }; + std::fill(mControls.begin(), mControls.end(), zero); + } + this->mConstructed = true; + } + + // Member access. The index 'dim' must be in {0,1}. + inline BasisFunction const& GetBasisFunction(int dim) const + { + return mBasisFunction[dim]; + } + + inline int GetNumControls(int dim) const + { + return mNumControls[dim]; + } + + inline Vector* GetControls() + { + return mControls.data(); + } + + inline Vector const* GetControls() const + { + return mControls.data(); + } + + void SetControl(int i0, int i1, Vector const& control) + { + if (0 <= i0 && i0 < GetNumControls(0) + && 0 <= i1 && i1 < GetNumControls(1)) + { + mControls[i0 + mNumControls[0] * i1] = control; + } + } + + Vector const& GetControl(int i0, int i1) const + { + if (0 <= i0 && i0 < GetNumControls(0) && 0 <= i1 && i1 < GetNumControls(1)) + { + return mControls[i0 + mNumControls[0] * i1]; + } + else + { + return mControls[0]; + } + } + + // Evaluation of the surface. The function supports derivative + // calculation through order 2; that is, order <= 2 is required. If + // you want only the position, pass in order of 0. If you want the + // position and first-order derivatives, pass in order of 1, and so + // on. The output array 'jet' must have enough storage to support the + // maximum order. The values are ordered as: position X; first-order + // derivatives dX/du, dX/dv; second-order derivatives d2X/du2, + // d2X/dudv, d2X/dv2. + virtual void Evaluate(Real u, Real v, unsigned int order, Vector* jet) const override + { + unsigned int const supOrder = ParametricSurface::SUP_ORDER; + if (!this->mConstructed || order >= supOrder) + { + // Return a zero-valued jet for invalid state. + for (unsigned int i = 0; i < supOrder; ++i) + { + jet[i].MakeZero(); + } + return; + } + + int iumin, iumax, ivmin, ivmax; + mBasisFunction[0].Evaluate(u, order, iumin, iumax); + mBasisFunction[1].Evaluate(v, order, ivmin, ivmax); + + // Compute position. + jet[0] = Compute(0, 0, iumin, iumax, ivmin, ivmax); + if (order >= 1) + { + // Compute first-order derivatives. + jet[1] = Compute(1, 0, iumin, iumax, ivmin, ivmax); + jet[2] = Compute(0, 1, iumin, iumax, ivmin, ivmax); + if (order >= 2) + { + // Compute second-order derivatives. + jet[3] = Compute(2, 0, iumin, iumax, ivmin, ivmax); + jet[4] = Compute(1, 1, iumin, iumax, ivmin, ivmax); + jet[5] = Compute(0, 2, iumin, iumax, ivmin, ivmax); + } + } + } + + private: + // Support for Evaluate(...). + Vector Compute(unsigned int uOrder, unsigned int vOrder, + int iumin, int iumax, int ivmin, int ivmax) const + { + // The j*-indices introduce a tiny amount of overhead in order to + // handle both aperiodic and periodic splines. For aperiodic + // splines, j* = i* always. + + int const numControls0 = mNumControls[0]; + int const numControls1 = mNumControls[1]; + Vector result; + result.MakeZero(); + for (int iv = ivmin; iv <= ivmax; ++iv) + { + Real tmpv = mBasisFunction[1].GetValue(vOrder, iv); + int jv = (iv >= numControls1 ? iv - numControls1 : iv); + for (int iu = iumin; iu <= iumax; ++iu) + { + Real tmpu = mBasisFunction[0].GetValue(uOrder, iu); + int ju = (iu >= numControls0 ? iu - numControls0 : iu); + result += (tmpu * tmpv) * mControls[ju + numControls0 * jv]; + } + } + return result; + } + + std::array, 2> mBasisFunction; + std::array mNumControls; + std::vector> mControls; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSplineSurfaceFit.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSplineSurfaceFit.h new file mode 100644 index 0000000..19e5e03 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSplineSurfaceFit.h @@ -0,0 +1,243 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The algorithm implemented here is based on the document +// https://www.geometrictools.com/Documentation/BSplineSurfaceLeastSquaresFit.pdf + +namespace WwiseGTE +{ + template + class BSplineSurfaceFit + { + public: + // Construction. The preconditions for calling the constructor are + // 1 <= degree0 && degree0 + 1 < numControls0 <= numSamples0 + // 1 <= degree1 && degree1 + 1 < numControls1 <= numSamples1 + // The sample data must be in row-major order. The control data is + // also stored in row-major order. + BSplineSurfaceFit(int degree0, int numControls0, int numSamples0, + int degree1, int numControls1, int numSamples1, Vector3 const* sampleData) + : + mSampleData(sampleData), + mControlData(numControls0 * numControls1) + { + LogAssert(1 <= degree0 && degree0 + 1 < numControls0, "Invalid degree."); + LogAssert(numControls0 <= numSamples0, "Invalid number of controls."); + LogAssert(1 <= degree1 && degree1 + 1 < numControls1, "Invalid degree."); + LogAssert(numControls1 <= numSamples1, "Invalid number of controls."); + LogAssert(sampleData, "Invalid sample data."); + + mDegree[0] = degree0; + mNumSamples[0] = numSamples0; + mNumControls[0] = numControls0; + mDegree[1] = degree1; + mNumSamples[1] = numSamples1; + mNumControls[1] = numControls1; + + BasisFunctionInput input; + Real tMultiplier[2]; + int dim; + for (dim = 0; dim < 2; ++dim) + { + input.numControls = mNumControls[dim]; + input.degree = mDegree[dim]; + input.uniform = true; + input.periodic = false; + input.numUniqueKnots = mNumControls[dim] - mDegree[dim] + 1; + input.uniqueKnots.resize(input.numUniqueKnots); + input.uniqueKnots[0].t = (Real)0; + input.uniqueKnots[0].multiplicity = mDegree[dim] + 1; + int last = input.numUniqueKnots - 1; + Real factor = (Real)1 / (Real)last; + for (int i = 1; i < last; ++i) + { + input.uniqueKnots[i].t = factor * (Real)i; + input.uniqueKnots[i].multiplicity = 1; + } + input.uniqueKnots[last].t = (Real)1; + input.uniqueKnots[last].multiplicity = mDegree[dim] + 1; + mBasis[dim].Create(input); + + tMultiplier[dim] = ((Real)1) / (Real)(mNumSamples[dim] - 1); + } + + // Fit the data points with a B-spline surface using a + // least-squares error metric. The problem is of the form + // A0^T*A0*Q*A1^T*A1 = A0^T*P*A1, where A0^T*A0 and A1^T*A1 are + // banded matrices, P contains the sample data, and Q is the + // unknown matrix of control points. + Real t; + int i0, i1, i2, imin, imax; + + // Construct the matrices A0^T*A0 and A1^T*A1. + BandedMatrix ATAMat[2] = + { + BandedMatrix(mNumControls[0], mDegree[0] + 1, mDegree[0] + 1), + BandedMatrix(mNumControls[1], mDegree[1] + 1, mDegree[1] + 1) + }; + + for (dim = 0; dim < 2; ++dim) + { + for (i0 = 0; i0 < mNumControls[dim]; ++i0) + { + for (i1 = 0; i1 < i0; ++i1) + { + ATAMat[dim](i0, i1) = ATAMat[dim](i1, i0); + } + + int i1Max = i0 + mDegree[dim]; + if (i1Max >= mNumControls[dim]) + { + i1Max = mNumControls[dim] - 1; + } + + for (i1 = i0; i1 <= i1Max; ++i1) + { + Real value = (Real)0; + for (i2 = 0; i2 < mNumSamples[dim]; ++i2) + { + t = tMultiplier[dim] * (Real)i2; + mBasis[dim].Evaluate(t, 0, imin, imax); + if (imin <= i0 && i0 <= imax && imin <= i1 && i1 <= imax) + { + Real b0 = mBasis[dim].GetValue(0, i0); + Real b1 = mBasis[dim].GetValue(0, i1); + value += b0 * b1; + } + } + ATAMat[dim](i0, i1) = value; + } + } + } + + // Construct the matrices A0^T and A1^T. A[d]^T has + // mNumControls[d] rows and mNumSamples[d] columns. + Array2 ATMat[2]; + for (dim = 0; dim < 2; dim++) + { + ATMat[dim] = Array2(mNumSamples[dim], mNumControls[dim]); + size_t numBytes = mNumControls[dim] * mNumSamples[dim] * sizeof(Real); + std::memset(ATMat[dim][0], 0, numBytes); + for (i0 = 0; i0 < mNumControls[dim]; ++i0) + { + for (i1 = 0; i1 < mNumSamples[dim]; ++i1) + { + t = tMultiplier[dim] * (Real)i1; + mBasis[dim].Evaluate(t, 0, imin, imax); + if (imin <= i0 && i0 <= imax) + { + ATMat[dim][i0][i1] = mBasis[dim].GetValue(0, i0); + } + } + } + } + + // Compute X0 = (A0^T*A0)^{-1}*A0^T and X1 = (A1^T*A1)^{-1}*A1^T + // by solving the linear systems A0^T*A0*X0 = A0^T and + // A1^T*A1*X1 = A1^T. + for (dim = 0; dim < 2; ++dim) + { + bool solved = ATAMat[dim].template SolveSystem(ATMat[dim][0], mNumSamples[dim]); + LogAssert(solved, "Failed to solve linear system in BSplineSurfaceFit constructor."); + } + + // The control points for the fitted surface are stored in the matrix + // Q = X0*P*X1^T, where P is the matrix of sample data. + for (i1 = 0; i1 < mNumControls[1]; ++i1) + { + for (i0 = 0; i0 < mNumControls[0]; ++i0) + { + Vector3 sum = Vector3::Zero(); + for (int j1 = 0; j1 < mNumSamples[1]; ++j1) + { + Real x1Value = ATMat[1][i1][j1]; + for (int j0 = 0; j0 < mNumSamples[0]; ++j0) + { + Real x0Value = ATMat[0][i0][j0]; + Vector3 sample = + mSampleData[j0 + mNumSamples[0] * j1]; + sum += (x0Value * x1Value) * sample; + } + } + mControlData[i0 + mNumControls[0] * i1] = sum; + } + } + } + + // Access to input sample information. + inline int GetNumSamples(int dimension) const + { + return mNumSamples[dimension]; + } + + inline Vector3 const* GetSampleData() const + { + return mSampleData; + } + + // Access to output control point and surface information. + inline int GetDegree(int dimension) const + { + return mDegree[dimension]; + } + + inline int GetNumControls(int dimension) const + { + return mNumControls[dimension]; + } + + inline Vector3 const* GetControlData() const + { + return &mControlData[0]; + } + + inline BasisFunction const& GetBasis(int dimension) const + { + return mBasis[dimension]; + } + + // Evaluation of the B-spline surface. It is defined for + // 0 <= u <= 1 and 0 <= v <= 1. If a parameter value is outside + // [0,1], it is clamped to [0,1]. + Vector3 GetPosition(Real u, Real v) const + { + int iumin, iumax, ivmin, ivmax; + mBasis[0].Evaluate(u, 0, iumin, iumax); + mBasis[1].Evaluate(v, 0, ivmin, ivmax); + + Vector3 position = Vector3::Zero(); + for (int iv = ivmin; iv <= ivmax; ++iv) + { + Real value1 = mBasis[1].GetValue(0, iv); + for (int iu = iumin; iu <= iumax; ++iu) + { + Real value0 = mBasis[0].GetValue(0, iu); + Vector3 control = mControlData[iu + mNumControls[0] * iv]; + position += (value0 * value1) * control; + } + } + return position; + } + + private: + // Input sample information. + int mNumSamples[2]; + Vector3 const* mSampleData; + + // The fitted B-spline surface, open and with uniform knots. + int mDegree[2]; + int mNumControls[2]; + std::vector> mControlData; + BasisFunction mBasis[2]; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSplineVolume.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSplineVolume.h new file mode 100644 index 0000000..b68cd29 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BSplineVolume.h @@ -0,0 +1,204 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class BSplineVolume + { + public: + // Construction. If the input controls is non-null, a copy is made of + // the controls. To defer setting the control points, pass a null + // pointer and later access the control points via GetControls() or + // SetControl() member functions. The input 'controls' must be stored + // in lexicographical order, + // control[i0+numControls0*(i1+numControls1*i2)]. As a 3D array, this + // corresponds to control3D[i2][i1][i0]. + BSplineVolume(BasisFunctionInput const input[3], Vector const* controls) + : + mConstructed(false) + { + for (int i = 0; i < 3; ++i) + { + mNumControls[i] = input[i].numControls; + mBasisFunction[i].Create(input[i]); + } + + // The replication of control points for periodic splines is + // avoided by wrapping the i-loop index in Evaluate. + int numControls = mNumControls[0] * mNumControls[1] * mNumControls[2]; + mControls.resize(numControls); + if (controls) + { + std::copy(controls, controls + numControls, mControls.begin()); + } + else + { + Vector zero{ (Real)0 }; + std::fill(mControls.begin(), mControls.end(), zero); + } + mConstructed = true; + } + + // To validate construction, create an object as shown: + // BSplineVolume volume(parameters); + // if (!volume) { ; } + inline operator bool() const + { + return mConstructed; + } + + // Member access. The index 'dim' must be in {0,1,2}. + inline BasisFunction const& GetBasisFunction(int dim) const + { + return mBasisFunction[dim]; + } + + inline Real GetMinDomain(int dim) const + { + return mBasisFunction[dim].GetMinDomain(); + } + + inline Real GetMaxDomain(int dim) const + { + return mBasisFunction[dim].GetMaxDomain(); + } + + inline int GetNumControls(int dim) const + { + return mNumControls[dim]; + } + + inline Vector const* GetControls() const + { + return mControls.data(); + } + + inline Vector* GetControls() + { + return mControls.data(); + } + + void SetControl(int i0, int i1, int i2, Vector const& control) + { + if (0 <= i0 && i0 < GetNumControls(0) + && 0 <= i1 && i1 < GetNumControls(1) + && 0 <= i2 && i2 < GetNumControls(2)) + { + mControls[i0 + mNumControls[0] * (i1 + mNumControls[1] * i2)] = control; + } + } + + Vector const& GetControl(int i0, int i1, int i2) const + { + if (0 <= i0 && i0 < GetNumControls(0) + && 0 <= i1 && i1 < GetNumControls(1) + && 0 <= i2 && i2 < GetNumControls(2)) + { + return mControls[i0 + mNumControls[0] * (i1 + mNumControls[1] * i2)]; + } + else + { + return mControls[0]; + } + } + + // Evaluation of the volume. The function supports derivative + // calculation through order 2; that is, order <= 2 is required. If + // you want only the position, pass in order of 0. If you want the + // position and first-order derivatives, pass in order of 1, and so + // on. The output array 'jet' muist have enough storage to support + // the maximum order. The values are ordered as: position X; + // first-order derivatives dX/du, dX/dv, dX/dw; second-order + // derivatives d2X/du2, d2X/dv2, d2X/dw2, d2X/dudv, d2X/dudw, + // d2X/dvdw. + enum { SUP_ORDER = 10 }; + void Evaluate(Real u, Real v, Real w, unsigned int order, Vector* jet) const + { + if (!mConstructed || order >= SUP_ORDER) + { + // Return a zero-valued jet for invalid state. + for (unsigned int i = 0; i < SUP_ORDER; ++i) + { + jet[i].MakeZero(); + } + return; + } + + int iumin, iumax, ivmin, ivmax, iwmin, iwmax; + mBasisFunction[0].Evaluate(u, order, iumin, iumax); + mBasisFunction[1].Evaluate(v, order, ivmin, ivmax); + mBasisFunction[2].Evaluate(w, order, iwmin, iwmax); + + // Compute position. + jet[0] = Compute(0, 0, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + if (order >= 1) + { + // Compute first-order derivatives. + jet[1] = Compute(1, 0, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + jet[2] = Compute(0, 1, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + jet[3] = Compute(0, 0, 1, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + if (order >= 2) + { + // Compute second-order derivatives. + jet[4] = Compute(2, 0, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + jet[5] = Compute(0, 2, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + jet[6] = Compute(0, 0, 2, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + jet[7] = Compute(1, 1, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + jet[8] = Compute(1, 0, 1, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + jet[9] = Compute(0, 1, 1, iumin, iumax, ivmin, ivmax, iwmin, iwmax); + } + } + } + + private: + // Support for Evaluate(...). + Vector Compute(unsigned int uOrder, unsigned int vOrder, + unsigned int wOrder, int iumin, int iumax, int ivmin, int ivmax, + int iwmin, int iwmax) const + { + // The j*-indices introduce a tiny amount of overhead in order to + // handle both aperiodic and periodic splines. For aperiodic + // splines, j* = i* always. + + int const numControls0 = mNumControls[0]; + int const numControls1 = mNumControls[1]; + int const numControls2 = mNumControls[2]; + Vector result; + result.MakeZero(); + for (int iw = iwmin; iw <= iwmax; ++iw) + { + Real tmpw = mBasisFunction[2].GetValue(wOrder, iw); + int jw = (iw >= numControls2 ? iw - numControls2 : iw); + for (int iv = ivmin; iv <= ivmax; ++iv) + { + Real tmpv = mBasisFunction[1].GetValue(vOrder, iv); + Real tmpvw = tmpv * tmpw; + int jv = (iv >= numControls1 ? iv - numControls1 : iv); + for (int iu = iumin; iu <= iumax; ++iu) + { + Real tmpu = mBasisFunction[0].GetValue(uOrder, iu); + int ju = (iu >= numControls0 ? iu - numControls0 : iu); + result += (tmpu * tmpvw) * + mControls[ju + numControls0 * (jv + numControls1 * jw)]; + } + } + } + return result; + } + + std::array, 3> mBasisFunction; + std::array mNumControls; + std::vector> mControls; + bool mConstructed; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BandedMatrix.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BandedMatrix.h new file mode 100644 index 0000000..5e242bb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BandedMatrix.h @@ -0,0 +1,520 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class BandedMatrix + { + public: + // Construction and destruction. + BandedMatrix(int size, int numLBands, int numUBands) + : + mSize(size), + mZero((Real)0) + { + if (size > 0 + && 0 <= numLBands && numLBands < size + && 0 <= numUBands && numUBands < size) + { + mDBand.resize(size); + std::fill(mDBand.begin(), mDBand.end(), (Real)0); + + if (numLBands > 0) + { + mLBands.resize(numLBands); + int numElements = size - 1; + for (auto& band : mLBands) + { + band.resize(numElements--); + std::fill(band.begin(), band.end(), (Real)0); + } + } + + if (numUBands > 0) + { + mUBands.resize(numUBands); + int numElements = size - 1; + for (auto& band : mUBands) + { + band.resize(numElements--); + std::fill(band.begin(), band.end(), (Real)0); + } + } + } + else + { + // Invalid argument to BandedMatrix constructor. + mSize = 0; + } + } + + ~BandedMatrix() + { + } + + // Member access. + inline int GetSize() const + { + return mSize; + } + + inline std::vector& GetDBand() + { + return mDBand; + } + + inline std::vector const& GetDBand() const + { + return mDBand; + } + + inline std::vector>& GetLBands() + { + return mLBands; + } + + inline std::vector> const& GetLBands() const + { + return mLBands; + } + + inline std::vector>& GetUBands() + { + return mUBands; + } + + inline std::vector> const& GetUBands() const + { + return mUBands; + } + + Real& operator()(int r, int c) + { + if (0 <= r && r < mSize && 0 <= c && c < mSize) + { + int band = c - r; + if (band > 0) + { + int const numUBands = static_cast(mUBands.size()); + if (--band < numUBands && r < mSize - 1 - band) + { + return mUBands[band][r]; + } + } + else if (band < 0) + { + band = -band; + int const numLBands = static_cast(mLBands.size()); + if (--band < numLBands && c < mSize - 1 - band) + { + return mLBands[band][c]; + } + } + else + { + return mDBand[r]; + } + } + // else invalid index + + + // Set the value to zero in case someone unknowingly modified mZero on a + // previous call to operator(int,int). + mZero = (Real)0; + return mZero; + } + + Real const& operator()(int r, int c) const + { + if (0 <= r && r < mSize && 0 <= c && c < mSize) + { + int band = c - r; + if (band > 0) + { + int const numUBands = static_cast(mUBands.size()); + if (--band < numUBands && r < mSize - 1 - band) + { + return mUBands[band][r]; + } + } + else if (band < 0) + { + band = -band; + int const numLBands = static_cast(mLBands.size()); + if (--band < numLBands && c < mSize - 1 - band) + { + return mLBands[band][c]; + } + } + else + { + return mDBand[r]; + } + } + // else invalid index + + + // Set the value to zero in case someone unknowingly modified + // mZero on a previous call to operator(int,int). + mZero = (Real)0; + return mZero; + } + + // Factor the square banded matrix A into A = L*L^T, where L is a + // lower-triangular matrix (L^T is an upper-triangular matrix). This + // is an LU decomposition that allows for stable inversion of A to + // solve A*X = B. The return value is 'true' iff the factorizing is + // successful (L is invertible). If successful, A contains the + // Cholesky factorization: L in the lower-triangular part of A and + // L^T in the upper-triangular part of A. + bool CholeskyFactor() + { + if (mDBand.size() == 0 || mLBands.size() != mUBands.size()) + { + // Invalid number of bands. + return false; + } + + int const sizeM1 = mSize - 1; + int const numBands = static_cast(mLBands.size()); + + int k, kMax; + for (int i = 0; i < mSize; ++i) + { + int jMin = i - numBands; + if (jMin < 0) + { + jMin = 0; + } + + int j; + for (j = jMin; j < i; ++j) + { + kMax = j + numBands; + if (kMax > sizeM1) + { + kMax = sizeM1; + } + + for (k = i; k <= kMax; ++k) + { + operator()(k, i) -= operator()(i, j) * operator()(k, j); + } + } + + kMax = j + numBands; + if (kMax > sizeM1) + { + kMax = sizeM1; + } + + for (k = 0; k < i; ++k) + { + operator()(k, i) = operator()(i, k); + } + + Real diagonal = operator()(i, i); + if (diagonal <= (Real)0) + { + return false; + } + Real invSqrt = ((Real)1) / std::sqrt(diagonal); + for (k = i; k <= kMax; ++k) + { + operator()(k, i) *= invSqrt; + } + } + + return true; + } + + // Solve the linear system A*X = B, where A is an NxN banded matrix + // and B is an Nx1 vector. The unknown X is also Nx1. The input to + // this function is B. The output X is computed and stored in B. The + // return value is 'true' iff the system has a solution. The matrix A + // and the vector B are both modified by this function. If + // successful, A contains the Cholesky factorization: L in the + // lower-triangular part of A and L^T in the upper-triangular part + // of A. + bool SolveSystem(Real* bVector) + { + return CholeskyFactor() + && SolveLower(bVector) + && SolveUpper(bVector); + } + + // Solve the linear system A*X = B, where A is an NxN banded matrix + // and B is an NxM matrix. The unknown X is also NxM. The input to + // this function is B. The output X is computed and stored in B. The + // return value is 'true' iff the system has a solution. The matrix A + // and the vector B are both modified by this function. If + // successful, A contains the Cholesky factorization: L in the + // lower-triangular part of A and L^T in the upper-triangular part + // of A. + // + // 'bMatrix' must have the storage order specified by the template + // parameter. + template + bool SolveSystem(Real* bMatrix, int numBColumns) + { + return CholeskyFactor() + && SolveLower(bMatrix, numBColumns) + && SolveUpper(bMatrix, numBColumns); + } + + // Compute the inverse of the banded matrix. The return value is + // 'true' when the matrix is invertible, in which case the 'inverse' + // output is valid. The return value is 'false' when the matrix is + // not invertible, in which case 'inverse' is invalid and should not + // be used. The input matrix 'inverse' must be the same size as + // 'this'. + // + // 'bMatrix' must have the storage order specified by the template + // parameter. + template + bool ComputeInverse(Real* inverse) const + { + LexicoArray2 invA(mSize, mSize, inverse); + + BandedMatrix tmpA = *this; + for (int row = 0; row < mSize; ++row) + { + for (int col = 0; col < mSize; ++col) + { + if (row != col) + { + invA(row, col) = (Real)0; + } + else + { + invA(row, row) = (Real)1; + } + } + } + + // Forward elimination. + for (int row = 0; row < mSize; ++row) + { + // The pivot must be nonzero in order to proceed. + Real diag = tmpA(row, row); + if (diag == (Real)0) + { + return false; + } + + Real invDiag = ((Real)1) / diag; + tmpA(row, row) = (Real)1; + + // Multiply the row to be consistent with diagonal term of 1. + int colMin = row + 1; + int colMax = colMin + static_cast(mUBands.size()); + if (colMax > mSize) + { + colMax = mSize; + } + + int c; + for (c = colMin; c < colMax; ++c) + { + tmpA(row, c) *= invDiag; + } + for (c = 0; c <= row; ++c) + { + invA(row, c) *= invDiag; + } + + // Reduce the remaining rows. + int rowMin = row + 1; + int rowMax = rowMin + static_cast(mLBands.size()); + if (rowMax > mSize) + { + rowMax = mSize; + } + + for (int r = rowMin; r < rowMax; ++r) + { + Real mult = tmpA(r, row); + tmpA(r, row) = (Real)0; + for (c = colMin; c < colMax; ++c) + { + tmpA(r, c) -= mult * tmpA(row, c); + } + for (c = 0; c <= row; ++c) + { + invA(r, c) -= mult * invA(row, c); + } + } + } + + // Backward elimination. + for (int row = mSize - 1; row >= 1; --row) + { + int rowMax = row - 1; + int rowMin = row - static_cast(mUBands.size()); + if (rowMin < 0) + { + rowMin = 0; + } + + for (int r = rowMax; r >= rowMin; --r) + { + Real mult = tmpA(r, row); + tmpA(r, row) = (Real)0; + for (int c = 0; c < mSize; ++c) + { + invA(r, c) -= mult * invA(row, c); + } + } + } + + return true; + } + + private: + // The linear system is L*U*X = B, where A = L*U and U = L^T, Reduce + // this to U*X = L^{-1}*B. The return value is 'true' iff the + // operation is successful. + bool SolveLower(Real* dataVector) const + { + int const size = static_cast(mDBand.size()); + for (int r = 0; r < size; ++r) + { + Real lowerRR = operator()(r, r); + if (lowerRR > (Real)0) + { + for (int c = 0; c < r; ++c) + { + Real lowerRC = operator()(r, c); + dataVector[r] -= lowerRC * dataVector[c]; + } + dataVector[r] /= lowerRR; + } + else + { + return false; + } + } + return true; + } + + // The linear system is U*X = L^{-1}*B. Reduce this to + // X = U^{-1}*L^{-1}*B. The return value is 'true' iff the operation + // is successful. + bool SolveUpper(Real* dataVector) const + { + int const size = static_cast(mDBand.size()); + for (int r = size - 1; r >= 0; --r) + { + Real upperRR = operator()(r, r); + if (upperRR > (Real)0) + { + for (int c = r + 1; c < size; ++c) + { + Real upperRC = operator()(r, c); + dataVector[r] -= upperRC * dataVector[c]; + } + + dataVector[r] /= upperRR; + } + else + { + return false; + } + } + return true; + } + + // The linear system is L*U*X = B, where A = L*U and U = L^T, Reduce + // this to U*X = L^{-1}*B. The return value is 'true' iff the + // operation is successful. See the comments for + // SolveSystem(Real*,int) about the storage for dataMatrix. + template + bool SolveLower(Real* dataMatrix, int numColumns) const + { + LexicoArray2 data(mSize, numColumns, dataMatrix); + + for (int r = 0; r < mSize; ++r) + { + Real lowerRR = operator()(r, r); + if (lowerRR > (Real)0) + { + for (int c = 0; c < r; ++c) + { + Real lowerRC = operator()(r, c); + for (int bCol = 0; bCol < numColumns; ++bCol) + { + data(r, bCol) -= lowerRC * data(c, bCol); + } + } + + Real inverse = ((Real)1) / lowerRR; + for (int bCol = 0; bCol < numColumns; ++bCol) + { + data(r, bCol) *= inverse; + } + } + else + { + return false; + } + } + return true; + } + + // The linear system is U*X = L^{-1}*B. Reduce this to + // X = U^{-1}*L^{-1}*B. The return value is 'true' iff the operation + // is successful. See the comments for SolveSystem(Real*,int) about + // the storage for dataMatrix. + template + bool SolveUpper(Real* dataMatrix, int numColumns) const + { + LexicoArray2 data(mSize, numColumns, dataMatrix); + + for (int r = mSize - 1; r >= 0; --r) + { + Real upperRR = operator()(r, r); + if (upperRR > (Real)0) + { + for (int c = r + 1; c < mSize; ++c) + { + Real upperRC = operator()(r, c); + for (int bCol = 0; bCol < numColumns; ++bCol) + { + data(r, bCol) -= upperRC * data(c, bCol); + } + } + + Real inverse = ((Real)1) / upperRR; + for (int bCol = 0; bCol < numColumns; ++bCol) + { + data(r, bCol) *= inverse; + } + } + else + { + return false; + } + } + return true; + } + + int mSize; + std::vector mDBand; + std::vector> mLBands, mUBands; + + // For return by operator()(int,int) for valid indices not in the + // bands, in which case the matrix entries are zero, + mutable Real mZero; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BasisFunction.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BasisFunction.h new file mode 100644 index 0000000..c3016a6 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BasisFunction.h @@ -0,0 +1,474 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + struct UniqueKnot + { + Real t; + int multiplicity; + }; + + template + struct BasisFunctionInput + { + // The members are uninitialized. + BasisFunctionInput() + { + } + + // Construct an open uniform curve with t in [0,1]. + BasisFunctionInput(int inNumControls, int inDegree) + : + numControls(inNumControls), + degree(inDegree), + uniform(true), + periodic(false), + numUniqueKnots(numControls - degree + 1), + uniqueKnots(numUniqueKnots) + { + uniqueKnots.front().t = (Real)0; + uniqueKnots.front().multiplicity = degree + 1; + for (int i = 1; i <= numUniqueKnots - 2; ++i) + { + uniqueKnots[i].t = i / (numUniqueKnots - (Real)1); + uniqueKnots[i].multiplicity = 1; + } + uniqueKnots.back().t = (Real)1; + uniqueKnots.back().multiplicity = degree + 1; + } + + int numControls; + int degree; + bool uniform; + bool periodic; + int numUniqueKnots; + std::vector> uniqueKnots; + }; + + template + class BasisFunction + { + public: + // Let n be the number of control points. Let d be the degree, where + // 1 <= d <= n-1. The number of knots is k = n + d + 1. The knots + // are t[i] for 0 <= i < k and must be nondecreasing, t[i] <= t[i+1], + // but a knot value can be repeated. Let s be the number of distinct + // knots. Let the distinct knots be u[j] for 0 <= j < s, so u[j] < + // u[j+1] for all j. The set of u[j] is called a 'breakpoint + // sequence'. Let m[j] >= 1 be the multiplicity; that is, if t[i] is + // the first occurrence of u[j], then t[i+r] = t[i] for 1 <= r < m[j]. + // The multiplicities have the constraints m[0] <= d+1, m[s-1] <= d+1, + // and m[j] <= d for 1 <= j <= s-2. Also, k = sum_{j=0}^{s-1} m[j], + // which says the multiplicities account for all k knots. + // + // Given a knot vector (t[0],...,t[n+d]), the domain of the + // corresponding B-spline curve is the interval [t[d],t[n]]. + // + // The corresponding B-spline or NURBS curve is characterized as + // follows. See "Geometric Modeling with Splines: An Introduction" by + // Elaine Cohen, Richard F. Riesenfeld and Gershon Elber, AK Peters, + // 2001, Natick MA. The curve is 'open' when m[0] = m[s-1] = d+1; + // otherwise, it is 'floating'. An open curve is uniform when the + // knots t[d] through t[n] are equally spaced; that is, t[i+1] - t[i] + // are a common value for d <= i <= n-1. By implication, s = n-d+1 + // and m[j] = 1 for 1 <= j <= s-2. An open curve that does not + // satisfy these conditions is said to be nonuniform. A floating + // curve is uniform when m[j] = 1 for 0 <= j <= s-1 and t[i+1] - t[i] + // are a common value for 0 <= i <= k-2; otherwise, the floating curve + // is nonuniform. + // + // A special case of a floating curve is a periodic curve. The intent + // is that the curve is closed, so the first and last control points + // should be the same, which ensures C^{0} continuity. Higher-order + // continuity is obtained by repeating more control points. If the + // control points are P[0] through P[n-1], append the points P[0] + // through P[d-1] to ensure C^{d-1} continuity. Additionally, the + // knots must be chosen properly. You may choose t[d] through t[n] as + // you wish. The other knots are defined by + // t[i] - t[i-1] = t[n-d+i] - t[n-d+i-1] + // t[n+i] - t[n+i-1] = t[d+i] - t[d+i-1] + // for 1 <= i <= d. + + + // Construction and destruction. The determination that the curve is + // open or floating is based on the multiplicities. The 'uniform' + // input is used to avoid misclassifications due to floating-point + // rounding errors. Specifically, the breakpoints might be equally + // spaced (uniform) as real numbers, but the floating-point + // representations can have rounding errors that cause the knot + // differences not to be exactly the same constant. A periodic curve + // can have uniform or nonuniform knots. This object makes copies of + // the input arrays. + BasisFunction() + : + mNumControls(0), + mDegree(0), + mTMin((Real)0), + mTMax((Real)0), + mTLength((Real)0), + mOpen(false), + mUniform(false), + mPeriodic(false) + { + } + + BasisFunction(BasisFunctionInput const& input) + : + mNumControls(0), + mDegree(0), + mTMin((Real)0), + mTMax((Real)0), + mTLength((Real)0), + mOpen(false), + mUniform(false), + mPeriodic(false) + { + Create(input); + } + + + ~BasisFunction() + { + } + + // No copying is allowed. + BasisFunction(BasisFunction const&) = delete; + BasisFunction& operator=(BasisFunction const&) = delete; + + // Support for explicit creation in classes that have std::array + // members involving BasisFunction. This is a call-once function. + void Create(BasisFunctionInput const& input) + { + LogAssert(mNumControls == 0 && mDegree == 0, "Object already created."); + LogAssert(input.numControls >= 2, "Invalid number of control points."); + LogAssert(1 <= input.degree && input.degree < input.numControls, "Invalid degree."); + LogAssert(input.numUniqueKnots >= 2, "Invalid number of unique knots."); + + mNumControls = (input.periodic ? input.numControls + input.degree : input.numControls); + mDegree = input.degree; + mTMin = (Real)0; + mTMax = (Real)0; + mTLength = (Real)0; + mOpen = false; + mUniform = input.uniform; + mPeriodic = input.periodic; + for (int i = 0; i < 4; ++i) + { + mJet[i] = Array2(); + } + + mUniqueKnots.resize(input.numUniqueKnots); + std::copy(input.uniqueKnots.begin(), + input.uniqueKnots.begin() + input.numUniqueKnots, + mUniqueKnots.begin()); + + Real u = mUniqueKnots.front().t; + for (int i = 1; i < input.numUniqueKnots - 1; ++i) + { + Real uNext = mUniqueKnots[i].t; + LogAssert(u < uNext, "Unique knots are not strictly increasing."); + u = uNext; + } + + int mult0 = mUniqueKnots.front().multiplicity; + LogAssert(mult0 >= 1 && mult0 <= mDegree + 1, "Invalid first multiplicity."); + + int mult1 = mUniqueKnots.back().multiplicity; + LogAssert(mult1 >= 1 && mult1 <= mDegree + 1, "Invalid last multiplicity."); + + for (int i = 1; i <= input.numUniqueKnots - 2; ++i) + { + int mult = mUniqueKnots[i].multiplicity; + LogAssert(mult >= 1 && mult <= mDegree + 1, "Invalid interior multiplicity."); + } + + mOpen = (mult0 == mult1 && mult0 == mDegree + 1); + + mKnots.resize(mNumControls + mDegree + 1); + mKeys.resize(input.numUniqueKnots); + int sum = 0; + for (int i = 0, j = 0; i < input.numUniqueKnots; ++i) + { + Real tCommon = mUniqueKnots[i].t; + int mult = mUniqueKnots[i].multiplicity; + for (int k = 0; k < mult; ++k, ++j) + { + mKnots[j] = tCommon; + } + + mKeys[i].first = tCommon; + mKeys[i].second = sum - 1; + sum += mult; + } + + mTMin = mKnots[mDegree]; + mTMax = mKnots[mNumControls]; + mTLength = mTMax - mTMin; + + size_t numRows = mDegree + 1; + size_t numCols = mNumControls + mDegree; + size_t numBytes = numRows * numCols * sizeof(Real); + for (int i = 0; i < 4; ++i) + { + mJet[i] = Array2(numCols, numRows); + std::memset(mJet[i][0], 0, numBytes); + } + } + + // Member access. + inline int GetNumControls() const + { + return mNumControls; + } + + inline int GetDegree() const + { + return mDegree; + } + + inline int GetNumUniqueKnots() const + { + return static_cast(mUniqueKnots.size()); + } + + inline int GetNumKnots() const + { + return static_cast(mKnots.size()); + } + + inline Real GetMinDomain() const + { + return mTMin; + } + + inline Real GetMaxDomain() const + { + return mTMax; + } + + inline bool IsOpen() const + { + return mOpen; + } + + inline bool IsUniform() const + { + return mUniform; + } + + inline bool IsPeriodic() const + { + return mPeriodic; + } + + inline UniqueKnot const* GetUniqueKnots() const + { + return &mUniqueKnots[0]; + } + + inline Real const* GetKnots() const + { + return &mKnots[0]; + } + + // Evaluation of the basis function and its derivatives through + // order 3. For the function value only, pass order 0. For the + // function and first derivative, pass order 1, and so on. + void Evaluate(Real t, unsigned int order, int& minIndex, int& maxIndex) const + { + LogAssert(order <= 3, "Invalid order."); + + int i = GetIndex(t); + mJet[0][0][i] = (Real)1; + + if (order >= 1) + { + mJet[1][0][i] = (Real)0; + if (order >= 2) + { + mJet[2][0][i] = (Real)0; + if (order >= 3) + { + mJet[3][0][i] = (Real)0; + } + } + } + + Real n0 = t - mKnots[i], n1 = mKnots[i + 1] - t; + Real e0, e1, d0, d1, invD0, invD1; + int j; + for (j = 1; j <= mDegree; j++) + { + d0 = mKnots[i + j] - mKnots[i]; + d1 = mKnots[i + 1] - mKnots[i - j + 1]; + invD0 = (d0 > (Real)0 ? (Real)1 / d0 : (Real)0); + invD1 = (d1 > (Real)0 ? (Real)1 / d1 : (Real)0); + + e0 = n0 * mJet[0][j - 1][i]; + mJet[0][j][i] = e0 * invD0; + e1 = n1 * mJet[0][j - 1][i - j + 1]; + mJet[0][j][i - j] = e1 * invD1; + + if (order >= 1) + { + e0 = n0 * mJet[1][j - 1][i] + mJet[0][j - 1][i]; + mJet[1][j][i] = e0 * invD0; + e1 = n1 * mJet[1][j - 1][i - j + 1] - mJet[0][j - 1][i - j + 1]; + mJet[1][j][i - j] = e1 * invD1; + + if (order >= 2) + { + e0 = n0 * mJet[2][j - 1][i] + ((Real)2) * mJet[1][j - 1][i]; + mJet[2][j][i] = e0 * invD0; + e1 = n1 * mJet[2][j - 1][i - j + 1] - ((Real)2) * mJet[1][j - 1][i - j + 1]; + mJet[2][j][i - j] = e1 * invD1; + + if (order >= 3) + { + e0 = n0 * mJet[3][j - 1][i] + ((Real)3) * mJet[2][j - 1][i]; + mJet[3][j][i] = e0 * invD0; + e1 = n1 * mJet[3][j - 1][i - j + 1] - ((Real)3) * mJet[2][j - 1][i - j + 1]; + mJet[3][j][i - j] = e1 * invD1; + } + } + } + } + + for (j = 2; j <= mDegree; ++j) + { + for (int k = i - j + 1; k < i; ++k) + { + n0 = t - mKnots[k]; + n1 = mKnots[k + j + 1] - t; + d0 = mKnots[k + j] - mKnots[k]; + d1 = mKnots[k + j + 1] - mKnots[k + 1]; + invD0 = (d0 > (Real)0 ? (Real)1 / d0 : (Real)0); + invD1 = (d1 > (Real)0 ? (Real)1 / d1 : (Real)0); + + e0 = n0 * mJet[0][j - 1][k]; + e1 = n1 * mJet[0][j - 1][k + 1]; + mJet[0][j][k] = e0 * invD0 + e1 * invD1; + + if (order >= 1) + { + e0 = n0 * mJet[1][j - 1][k] + mJet[0][j - 1][k]; + e1 = n1 * mJet[1][j - 1][k + 1] - mJet[0][j - 1][k + 1]; + mJet[1][j][k] = e0 * invD0 + e1 * invD1; + + if (order >= 2) + { + e0 = n0 * mJet[2][j - 1][k] + ((Real)2) * mJet[1][j - 1][k]; + e1 = n1 * mJet[2][j - 1][k + 1] - ((Real)2) * mJet[1][j - 1][k + 1]; + mJet[2][j][k] = e0 * invD0 + e1 * invD1; + + if (order >= 3) + { + e0 = n0 * mJet[3][j - 1][k] + ((Real)3) * mJet[2][j - 1][k]; + e1 = n1 * mJet[3][j - 1][k + 1] - ((Real)3) * mJet[2][j - 1][k + 1]; + mJet[3][j][k] = e0 * invD0 + e1 * invD1; + } + } + } + } + } + + minIndex = i - mDegree; + maxIndex = i; + } + + // Access the results of the call to Evaluate(...). The index i must + // satisfy minIndex <= i <= maxIndex. If it is not, the function + // returns zero. The separation of evaluation and access is based on + // local control of the basis function; that is, only the accessible + // values are (potentially) not zero. + Real GetValue(unsigned int order, int i) const + { + if (order < 4) + { + if (0 <= i && i < mNumControls + mDegree) + { + return mJet[order][mDegree][i]; + } + LogError("Invalid index."); + } + LogError("Invalid order."); + } + + private: + // Determine the index i for which knot[i] <= t < knot[i+1]. The + // t-value is modified (wrapped for periodic splines, clamped for + // nonperiodic splines). + int GetIndex(Real& t) const + { + // Find the index i for which knot[i] <= t < knot[i+1]. + if (mPeriodic) + { + // Wrap to [tmin,tmax]. + Real r = std::fmod(t - mTMin, mTLength); + if (r < (Real)0) + { + r += mTLength; + } + t = mTMin + r; + } + + // Clamp to [tmin,tmax]. For the periodic case, this handles + // small numerical rounding errors near the domain endpoints. + if (t <= mTMin) + { + t = mTMin; + return mDegree; + } + if (t >= mTMax) + { + t = mTMax; + return mNumControls - 1; + } + + // At this point, tmin < t < tmax. + for (auto const& key : mKeys) + { + if (t < key.first) + { + return key.second; + } + } + + // We should not reach this code. + LogError("Unexpected condition."); + } + + // Constructor inputs and values derived from them. + int mNumControls; + int mDegree; + Real mTMin, mTMax, mTLength; + bool mOpen; + bool mUniform; + bool mPeriodic; + std::vector> mUniqueKnots; + std::vector mKnots; + + // Lookup information for the GetIndex() function. The first element + // of the pair is a unique knot value. The second element is the + // index in mKnots[] for the last occurrence of that knot value. + std::vector> mKeys; + + // Storage for the basis functions and their first three derivatives; + // mJet[i] is array[d+1][n+d]. + mutable std::array, 4> mJet; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BezierCurve.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BezierCurve.h new file mode 100644 index 0000000..0c1f13f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BezierCurve.h @@ -0,0 +1,178 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class BezierCurve : public ParametricCurve + { + public: + // Construction and destruction. The number of control points must be + // degree + 1. This object copies the input array. The domain is t + // in [0,1]. To validate construction, create an object as shown: + // BezierCurve curve(parameters); + // if (!curve) { ; } + BezierCurve(int degree, Vector const* controls) + : + ParametricCurve((Real)0, (Real)1), + mDegree(degree), + mNumControls(degree + 1), + mChoose(mNumControls, mNumControls) + { + LogAssert(degree >= 2 && controls != nullptr, "Invalid input."); + + // Copy the controls. + mControls[0].resize(mNumControls); + std::copy(controls, controls + mNumControls, mControls[0].begin()); + + // Compute first-order differences. + mControls[1].resize(mNumControls - 1); + for (int i = 0; i < mNumControls - 1; ++i) + { + mControls[1][i] = mControls[0][i + 1] - mControls[0][i]; + } + + // Compute second-order differences. + mControls[2].resize(mNumControls - 2); + for (int i = 0; i < mNumControls - 2; ++i) + { + mControls[2][i] = mControls[1][i + 1] - mControls[1][i]; + } + + // Compute third-order differences. + if (degree >= 3) + { + mControls[3].resize(mNumControls - 3); + for (int i = 0; i < mNumControls - 3; ++i) + { + mControls[3][i] = mControls[2][i + 1] - mControls[2][i]; + } + } + + // Compute combinatorial values Choose(n,k) and store in mChoose[n][k]. + // The values mChoose[r][c] are invalid for r < c; that is, we use only + // the entries for r >= c. + mChoose[0][0] = (Real)1; + mChoose[1][0] = (Real)1; + mChoose[1][1] = (Real)1; + for (int i = 2; i <= mDegree; ++i) + { + mChoose[i][0] = (Real)1; + mChoose[i][i] = (Real)1; + for (int j = 1; j < i; ++j) + { + mChoose[i][j] = mChoose[i - 1][j - 1] + mChoose[i - 1][j]; + } + } + + this->mConstructed = true; + } + + virtual ~BezierCurve() + { + } + + // Member access. + inline int GetDegree() const + { + return mDegree; + } + + inline int GetNumControls() const + { + return mNumControls; + } + + inline Vector const* GetControls() const + { + return &mControls[0][0]; + } + + // Evaluation of the curve. The function supports derivative + // calculation through order 3; that is, order <= 3 is required. If + // you want/ only the position, pass in order of 0. If you want the + // position and first derivative, pass in order of 1, and so on. The + // output array 'jet' must have enough storage to support the maximum + // order. The values are ordered as: position, first derivative, + // second derivative, third derivative. + virtual void Evaluate(Real t, unsigned int order, Vector* jet) const override + { + unsigned int const supOrder = ParametricCurve::SUP_ORDER; + if (!this->mConstructed || order >= supOrder) + { + // Return a zero-valued jet for invalid state. + for (unsigned int i = 0; i < supOrder; ++i) + { + jet[i].MakeZero(); + } + return; + } + + // Compute position. + Real omt = (Real)1 - t; + jet[0] = Compute(t, omt, 0); + if (order >= 1) + { + // Compute first derivative. + jet[1] = Compute(t, omt, 1); + if (order >= 2) + { + // Compute second derivative. + jet[2] = Compute(t, omt, 2); + if (order >= 3) + { + // Compute third derivative. + if (mDegree >= 3) + { + jet[3] = Compute(t, omt, 3); + } + else + { + jet[3].MakeZero(); + } + } + } + } + } + + protected: + // Support for Evaluate(...). + Vector Compute(Real t, Real omt, int order) const + { + Vector result = omt * mControls[order][0]; + + Real tpow = t; + int isup = mDegree - order; + for (int i = 1; i < isup; ++i) + { + Real c = mChoose[isup][i] * tpow; + result = (result + c * mControls[order][i]) * omt; + tpow *= t; + } + result = (result + tpow * mControls[order][isup]); + + int multiplier = 1; + for (int i = 0; i < order; ++i) + { + multiplier *= mDegree - i; + } + result *= (Real)multiplier; + + return result; + } + + int mDegree, mNumControls; + std::array>, ParametricCurve::SUP_ORDER> mControls; + Array2 mChoose; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BitHacks.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BitHacks.h new file mode 100644 index 0000000..65656be --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BitHacks.h @@ -0,0 +1,209 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The leadingBit table in GetLeadingBit and the trailingBit table in +// GetTrailingBit are based on De Bruijn sequences. The leadingBit table +// is taken from +// https://stackoverflow.com/questions/17027878/algorithm-to-find-the-most-significant-bit +// The trailingBit table is taken from +// https://www.dotnetperls.com/trailing-bits + +// The int32_t inputs to the bit-hack functions are required to be +// nonnegative. Expose this if you want exceptions thrown when the +// int32_t inputs are negative. +#define GTE_THROW_ON_BITHACKS_ERROR + +namespace WwiseGTE +{ + class BitHacks + { + public: + static bool IsPowerOfTwo(uint32_t value) + { + return (value > 0) && ((value & (value - 1)) == 0); + } + + static bool IsPowerOfTwo(int32_t value) + { +#if defined(GTE_THROW_ON_BITHACKS_ERROR) + LogAssert(value >= 0, "Invalid input."); +#endif + return IsPowerOfTwo(static_cast(value)); + } + + static uint32_t Log2OfPowerOfTwo(uint32_t powerOfTwo) + { + uint32_t log2 = (powerOfTwo & 0xAAAAAAAAu) != 0; + log2 |= ((powerOfTwo & 0xFFFF0000u) != 0) << 4; + log2 |= ((powerOfTwo & 0xFF00FF00u) != 0) << 3; + log2 |= ((powerOfTwo & 0xF0F0F0F0u) != 0) << 2; + log2 |= ((powerOfTwo & 0xCCCCCCCCu) != 0) << 1; + return log2; + } + + static int32_t Log2OfPowerOfTwo(int32_t powerOfTwo) + { +#if defined(GTE_THROW_ON_BITHACKS_ERROR) + LogAssert(powerOfTwo >= 0, "Invalid input."); +#endif + return static_cast(Log2OfPowerOfTwo(static_cast(powerOfTwo))); + } + + // The return value of the function is the index into the 32-bit value. + // For example, GetLeadingBit(10) = 3 and GetTrailingBit(10) = 2. The + // value in binary is 0x0000000000001010. The bit locations start at 0 + // on the right of the pattern and end at 31 on the left of the pattern. + // If the input value is zero, there is no leading bit and no trailing + // bit. However, the functions return 0, which is considered invalid. + // Try to call these functions only for positive inputs. + static int32_t GetLeadingBit(uint32_t value) + { + static std::array const leadingBitTable = + { + 0, 9, 1, 10, 13, 21, 2, 29, + 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, + 19, 27, 23, 6, 26, 5, 4, 31 + }; + + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + uint32_t key = (value * 0x07C4ACDDu) >> 27; + return leadingBitTable[key]; + } + + static int32_t GetLeadingBit(int32_t value) + { +#if defined(GTE_THROW_ON_BITHACKS_ERROR) + LogAssert(value != 0, "Invalid input."); +#endif + return GetLeadingBit(static_cast(value)); + } + + static int32_t GetLeadingBit(uint64_t value) + { + uint32_t v1 = static_cast((value >> 32) & 0x00000000FFFFFFFFull); + if (v1 != 0) + { + return GetLeadingBit(v1) + 32; + } + + uint32_t v0 = static_cast(value & 0x00000000FFFFFFFFull); + return GetLeadingBit(v0); + } + + static int32_t GetLeadingBit(int64_t value) + { +#if defined(GTE_THROW_ON_BITHACKS_ERROR) + LogAssert(value != 0, "Invalid input."); +#endif + return GetLeadingBit(static_cast(value)); + } + + static int32_t GetTrailingBit(int32_t value) + { + static std::array const trailingBitTable = + { + 0, 1, 28, 2, 29, 14, 24, 3, + 30, 22, 20, 15, 25, 17, 4, 8, + 31, 27, 13, 23, 21, 19, 16, 7, + 26, 12, 18, 6, 11, 5, 10, 9 + }; + +#if defined(GTE_THROW_ON_BITHACKS_ERROR) + LogAssert(value != 0, "Invalid input."); +#endif + + uint32_t key = (static_cast((value & -value) * 0x077CB531u)) >> 27; + return trailingBitTable[key]; + } + + static int32_t GetTrailingBit(uint32_t value) + { + // The GetTrailingBit(int32_t) function contains the actual + // implementation. If the uint32_t-based function were to be + // implemented, the (value & -value) statement generates a compiler + // warning about negating an unsigned integer, which requires + // additional logic to avoid. + return GetTrailingBit(static_cast(value)); + } + + static int32_t GetTrailingBit(uint64_t value) + { + uint32_t v0 = static_cast(value & 0x00000000FFFFFFFFull); + if (v0 != 0) + { + return GetTrailingBit(v0); + } + + uint32_t v1 = static_cast((value >> 32) & 0x00000000FFFFFFFFull); + if (v1 != 0) + { + return GetTrailingBit(v1) + 32; + } + return 0; + } + + static int32_t GetTrailingBit(int64_t value) + { +#if defined(GTE_THROW_ON_BITHACKS_ERROR) + LogAssert(value != 0, "Invalid input."); +#endif + return GetTrailingBit(static_cast(value)); + } + + // Round up to a power of two. If input is zero, the return is 1. If + // input is larger than 2^{31}, the return is 2^{32}. + static uint64_t RoundUpToPowerOfTwo(uint32_t value) + { + if (value > 0) + { + int32_t leading = GetLeadingBit(value); + uint32_t mask = (1 << leading); + if ((value & ~mask) == 0) + { + // value is a power of two + return static_cast(value); + } + else + { + // round up to a power of two + return (static_cast(mask) << 1); + } + + } + else + { + return 1ull; + } + } + + // Round down to a power of two. If input is zero, the return is 0. + static uint32_t RoundDownToPowerOfTwo(uint32_t value) + { + if (value > 0) + { + int32_t leading = GetLeadingBit(value); + uint32_t mask = (1 << leading); + return mask; + } + else + { + return 0; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BoxManager.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BoxManager.h new file mode 100644 index 0000000..afaf628 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/BoxManager.h @@ -0,0 +1,279 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class BoxManager + { + public: + // Construction. + BoxManager(std::vector>& boxes) + : + mBoxes(boxes) + { + Initialize(); + } + + // No default construction, copy construction, or assignment are + // allowed. + BoxManager() = delete; + BoxManager(BoxManager const&) = delete; + BoxManager& operator=(BoxManager const&) = delete; + + // This function is called by the constructor and does the + // sort-and-sweep to initialize the update system. However, if you + // add or remove items from the array of boxes after the constructor + // call, you will need to call this function once before you start the + // multiple calls of the update function. + void Initialize() + { + // Get the box endpoints. + int intrSize = static_cast(mBoxes.size()), endpSize = 2 * intrSize; + mXEndpoints.resize(endpSize); + mYEndpoints.resize(endpSize); + mZEndpoints.resize(endpSize); + for (int i = 0, j = 0; i < intrSize; ++i) + { + mXEndpoints[j].type = 0; + mXEndpoints[j].value = mBoxes[i].min[0]; + mXEndpoints[j].index = i; + mYEndpoints[j].type = 0; + mYEndpoints[j].value = mBoxes[i].min[1]; + mYEndpoints[j].index = i; + mZEndpoints[j].type = 0; + mZEndpoints[j].value = mBoxes[i].min[2]; + mZEndpoints[j].index = i; + ++j; + + mXEndpoints[j].type = 1; + mXEndpoints[j].value = mBoxes[i].max[0]; + mXEndpoints[j].index = i; + mYEndpoints[j].type = 1; + mYEndpoints[j].value = mBoxes[i].max[1]; + mYEndpoints[j].index = i; + mZEndpoints[j].type = 1; + mZEndpoints[j].value = mBoxes[i].max[2]; + mZEndpoints[j].index = i; + ++j; + } + + // Sort the rectangle endpoints. + std::sort(mXEndpoints.begin(), mXEndpoints.end()); + std::sort(mYEndpoints.begin(), mYEndpoints.end()); + std::sort(mZEndpoints.begin(), mZEndpoints.end()); + + // Create the interval-to-endpoint lookup tables. + mXLookup.resize(endpSize); + mYLookup.resize(endpSize); + mZLookup.resize(endpSize); + for (int j = 0; j < endpSize; ++j) + { + mXLookup[2 * mXEndpoints[j].index + mXEndpoints[j].type] = j; + mYLookup[2 * mYEndpoints[j].index + mYEndpoints[j].type] = j; + mZLookup[2 * mZEndpoints[j].index + mZEndpoints[j].type] = j; + } + + // Active set of rectangles (stored by index in array). + std::set active; + + // Set of overlapping rectangles (stored by pairs of indices in + // array). + mOverlap.clear(); + + // Sweep through the endpoints to determine overlapping + // x-intervals. + for (int i = 0; i < endpSize; ++i) + { + Endpoint& endpoint = mXEndpoints[i]; + int index = endpoint.index; + if (endpoint.type == 0) // an interval 'begin' value + { + // In the 1D problem, the current interval overlaps with + // all the active intervals. In 3D we also need to check + // for y-overlap and z-overlap. + for (auto activeIndex : active) + { + // Rectangles activeIndex and index overlap in the + // x-dimension. Test for overlap in the y-dimension + // and z-dimension. + AlignedBox3 const& b0 = mBoxes[activeIndex]; + AlignedBox3 const& b1 = mBoxes[index]; + if (b0.max[1] >= b1.min[1] && b0.min[1] <= b1.max[1] + && b0.max[2] >= b1.min[2] && b0.min[2] <= b1.max[2]) + { + if (activeIndex < index) + { + mOverlap.insert(EdgeKey(activeIndex, index)); + } + else + { + mOverlap.insert(EdgeKey(index, activeIndex)); + } + } + } + active.insert(index); + } + else // an interval 'end' value + { + active.erase(index); + } + } + } + + // After the system is initialized, you can move the boxes using this + // function. It is not enough to modify the input array of boxes + // because the endpoint values stored internally by this class must + // also change. You can also retrieve the current rectangles + // information. + void SetBox(int i, AlignedBox3 const& box) + { + mBoxes[i] = box; + mXEndpoints[mXLookup[2 * i]].value = box.min[0]; + mXEndpoints[mXLookup[2 * i + 1]].value = box.max[0]; + mYEndpoints[mYLookup[2 * i]].value = box.min[1]; + mYEndpoints[mYLookup[2 * i + 1]].value = box.max[1]; + mZEndpoints[mZLookup[2 * i]].value = box.min[2]; + mZEndpoints[mZLookup[2 * i + 1]].value = box.max[2]; + } + + inline void GetBox(int i, AlignedBox3& box) const + { + box = mBoxes[i]; + } + + // When you are finished moving boxes, call this function to determine + // the overlapping boxes. An incremental update is applied to + // determine the new set of overlapping boxes. + void Update() + { + InsertionSort(mXEndpoints, mXLookup); + InsertionSort(mYEndpoints, mYLookup); + InsertionSort(mZEndpoints, mZLookup); + } + + // If (i,j) is in the overlap set, then box i and box j are + // overlapping. The indices are those for the the input array. The + // set elements (i,j) are stored so that i < j. + inline std::set> const& GetOverlap() const + { + return mOverlap; + } + + private: + class Endpoint + { + public: + Real value; // endpoint value + int type; // '0' if interval min, '1' if interval max. + int index; // index of interval containing this endpoint + + // Support for sorting of endpoints. + bool operator<(Endpoint const& endpoint) const + { + if (value < endpoint.value) + { + return true; + } + if (value > endpoint.value) + { + return false; + } + return type < endpoint.type; + } + }; + + void InsertionSort(std::vector& endpoint, std::vector& lookup) + { + // Apply an insertion sort. Under the assumption that the + // rectangles have not changed much since the last call, the + // endpoints are nearly sorted. The insertion sort should be very + // fast in this case. + + TIQuery, AlignedBox3> query; + int endpSize = static_cast(endpoint.size()); + for (int j = 1; j < endpSize; ++j) + { + Endpoint key = endpoint[j]; + int i = j - 1; + while (i >= 0 && key < endpoint[i]) + { + Endpoint e0 = endpoint[i]; + Endpoint e1 = endpoint[i + 1]; + + // Update the overlap status. + if (e0.type == 0) + { + if (e1.type == 1) + { + // The 'b' of interval E0.mIndex was smaller than + // the 'e' of interval E1.mIndex, and the + // intervals *might have been* overlapping. Now + // 'b' and 'e' are swapped, and the intervals + // cannot overlap. Remove the pair from the + // overlap set. The removal operation needs to + // find the pair and erase it if it exists. + // Finding the pair is the expensive part of the + // operation, so there is no real time savings in + // testing for existence first, then deleting if + // it does. + mOverlap.erase(EdgeKey(e0.index, e1.index)); + } + } + else + { + if (e1.type == 0) + { + // The 'b' of interval E1.index was larger than + // the 'e' of interval E0.index, and the intervals + // were not overlapping. Now 'b' and 'e' are + // swapped, and the intervals *might be* + // overlapping. Determine if they are overlapping + // and then insert. + if (query(mBoxes[e0.index], mBoxes[e1.index]).intersect) + { + mOverlap.insert(EdgeKey(e0.index, e1.index)); + } + } + } + + // Reorder the items to maintain the sorted list. + endpoint[i] = e1; + endpoint[i + 1] = e0; + lookup[2 * e1.index + e1.type] = i; + lookup[2 * e0.index + e0.type] = i + 1; + --i; + } + endpoint[i + 1] = key; + lookup[2 * key.index + key.type] = i + 1; + } + } + + std::vector>& mBoxes; + std::vector mXEndpoints, mYEndpoints, mZEndpoints; + std::set> mOverlap; + + // The intervals are indexed 0 <= i < n. The endpoint array has 2*n + // entries. The original 2*n interval values are ordered as + // b[0], e[0], b[1], e[1], ..., b[n-1], e[n-1] + // When the endpoint array is sorted, the mapping between interval + // values and endpoints is lost. In order to modify interval values + // that are stored in the endpoint array, we need to maintain the + // mapping. This is done by the following lookup table of 2*n + // entries. The value mLookup[2*i] is the index of b[i] in the + // endpoint array. The value mLookup[2*i+1] is the index of e[i] + // in the endpoint array. + std::vector mXLookup, mYLookup, mZLookup; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CLODPolyline.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CLODPolyline.h new file mode 100644 index 0000000..f5cb320 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CLODPolyline.h @@ -0,0 +1,336 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +// The continuous level of detail (CLOD) algorithm implemented here is +// described in +// https://www.geometrictools.com/Documentation/PolylineReduction.pdf +// It is an algorithm to reduce incrementally the number of vertices in a +// polyline (open or closed). The sequence of vertex collapses is based on +// vertex weights associated with distance from vertices to polyline segments. + +namespace WwiseGTE +{ + template + class CLODPolyline + { + public: + // Constructor and destructor. The number of vertices must be 2 or + // larger. The vertices are assumed to be ordered. The segments are + // for 0 <= i <= numVertices-2 for an open polyline. + // If the polyline is closed, an additional segment is + // . + CLODPolyline(std::vector> const& vertices, bool closed) + : + mNumVertices(static_cast(vertices.size())), + mVertices(vertices), + mClosed(closed), + mNumEdges(0), + mVMin(mClosed ? 3 : 2), + mVMax(mNumVertices) + { + LogAssert(closed ? mNumVertices >= 3 : mNumVertices >= 2, "Invalid inputs."); + + // Compute the sequence of vertex collapses. The polyline starts + // out at the full level of detail (mNumVertices equals mVMax). + VertexCollapse collapser; + collapser(mVertices, mClosed, mIndices, mNumEdges, mEdges); + } + + ~CLODPolyline() = default; + + // Member access. + inline int GetNumVertices() const + { + return mNumVertices; + } + + inline std::vector> const& GetVertices() const + { + return mVertices; + } + + inline bool GetClosed() const + { + return mClosed; + } + + inline int GetNumEdges() const + { + return mNumEdges; + } + + inline std::vector const& GetEdges() const + { + return mEdges; + } + + // Accessors to level of detail (MinLOD <= LOD <= MaxLOD is required). + inline int GetMinLevelOfDetail() const + { + return mVMin; + } + + inline int GetMaxLevelOfDetail() const + { + return mVMax; + } + + inline int GetLevelOfDetail() const + { + return mNumVertices; + } + + void SetLevelOfDetail(int numVertices) + { + if (numVertices < mVMin || numVertices > mVMax) + { + return; + } + + // Decrease the level of detail. + while (mNumVertices > numVertices) + { + --mNumVertices; + mEdges[mIndices[mNumVertices]] = mEdges[2 * mNumEdges - 1]; + --mNumEdges; + } + + // Increase the level of detail. + while (mNumVertices < numVertices) + { + ++mNumEdges; + mEdges[mIndices[mNumVertices]] = mNumVertices; + ++mNumVertices; + } + } + + private: + // Support for vertex collapses in a polyline. + class VertexCollapse + { + public: + void operator()(std::vector>& vertices, + bool closed, std::vector& indices, int& numEdges, + std::vector& edges) + { + int numVertices = static_cast(vertices.size()); + indices.resize(vertices.size()); + + if (closed) + { + numEdges = numVertices; + edges.resize(2 * numEdges); + + if (numVertices == 3) + { + indices[0] = 0; + indices[1] = 1; + indices[2] = 3; + edges[0] = 0; edges[1] = 1; + edges[2] = 1; edges[3] = 2; + edges[4] = 2; edges[5] = 0; + return; + } + } + else + { + numEdges = numVertices - 1; + edges.resize(2 * numEdges); + + if (numVertices == 2) + { + indices[0] = 0; + indices[1] = 1; + edges[0] = 0; edges[1] = 1; + return; + } + } + + // Create the heap of weights. + MinHeap heap(numVertices); + int qm1 = numVertices - 1; + if (closed) + { + int qm2 = numVertices - 2; + heap.Insert(0, GetWeight(qm1, 0, 1, vertices)); + heap.Insert(qm1, GetWeight(qm2, qm1, 0, vertices)); + } + else + { + heap.Insert(0, std::numeric_limits::max()); + heap.Insert(qm1, std::numeric_limits::max()); + } + for (int m = 0, z = 1, p = 2; z < qm1; ++m, ++z, ++p) + { + heap.Insert(z, GetWeight(m, z, p, vertices)); + } + + // Create the level of detail information for the polyline. + std::vector collapses(numVertices); + CollapseVertices(heap, numVertices, collapses); + ComputeEdges(numVertices, closed, collapses, indices, numEdges, edges); + ReorderVertices(numVertices, vertices, collapses, numEdges, edges); + } + + protected: + // Weight calculation for vertex triple . + Real GetWeight(int m, int z, int p, std::vector>& vertices) + { + Vector direction = vertices[p] - vertices[m]; + Real length = Normalize(direction); + if (length > (Real)0) + { + Segment segment(vertices[m], vertices[p]); + DCPQuery, Segment> query; + Real distance = query(vertices[z], segment).distance; + return distance / length; + } + else + { + return std::numeric_limits::max(); + } + } + + // Create data structures for the polyline. + void CollapseVertices(MinHeap& heap, + int numVertices, std::vector& collapses) + { + for (int i = numVertices - 1; i >= 0; --i) + { + Real weight; + heap.Remove(collapses[i], weight); + } + } + + void ComputeEdges(int numVertices, bool closed, std::vector& collapses, + std::vector& indices, int numEdges, std::vector& edges) + { + // Compute the edges (first to collapse is last in array). Do + // not collapse the last line segment of an open polyline. Do + // not collapse further when a closed polyline becomes a + // triangle. + int i, vIndex, eIndex = 2 * numEdges - 1; + if (closed) + { + for (i = numVertices - 1; i >= 0; --i) + { + vIndex = collapses[i]; + edges[eIndex--] = (vIndex + 1) % numVertices; + edges[eIndex--] = vIndex; + } + } + else + { + for (i = numVertices - 1; i >= 2; --i) + { + vIndex = collapses[i]; + edges[eIndex--] = vIndex + 1; + edges[eIndex--] = vIndex; + } + + vIndex = collapses[0]; + edges[0] = vIndex; + edges[1] = vIndex + 1; + } + + // In the given edge order, find the index in the edge array + // that corresponds to a collapse vertex and save the index + // for the dynamic change in level of detail. This relies on + // the assumption that a vertex is shared by at most two + // edges. + eIndex = 2 * numEdges - 1; + for (i = numVertices - 1; i >= 0; --i) + { + vIndex = collapses[i]; + for (int e = 0; e < 2 * numEdges; ++e) + { + if (vIndex == edges[e]) + { + indices[i] = e; + edges[e] = edges[eIndex]; + break; + } + } + eIndex -= 2; + + if (closed) + { + if (eIndex == 5) + { + break; + } + } + else + { + if (eIndex == 1) + { + break; + } + } + } + + // Restore the edge array to full level of detail. + if (closed) + { + for (i = 3; i < numVertices; ++i) + { + edges[indices[i]] = collapses[i]; + } + } + else + { + for (i = 2; i < numVertices; ++i) + { + edges[indices[i]] = collapses[i]; + } + } + } + + void ReorderVertices(int numVertices, std::vector>& vertices, + std::vector& collapses, int numEdges, std::vector& edges) + { + std::vector permute(numVertices); + std::vector> permutedVertex(numVertices); + + for (int i = 0; i < numVertices; ++i) + { + int vIndex = collapses[i]; + permute[vIndex] = i; + permutedVertex[i] = vertices[vIndex]; + } + + for (int i = 0; i < 2 * numEdges; ++i) + { + edges[i] = permute[edges[i]]; + } + + vertices = permutedVertex; + } + }; + + private: + // The polyline vertices. + int mNumVertices; + std::vector> mVertices; + bool mClosed; + + // The polyline edges. + int mNumEdges; + std::vector mEdges; + + // The level of detail information. + int mVMin, mVMax; + std::vector mIndices; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Capsule.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Capsule.h new file mode 100644 index 0000000..af1147e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Capsule.h @@ -0,0 +1,87 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// A capsule is the set of points that are equidistant from a segment, the +// common distance called the radius. + +namespace WwiseGTE +{ + template + class Capsule + { + public: + // Construction and destruction. The default constructor sets the + // segment to have endpoints p0 = (-1,0,...,0) and p1 = (1,0,...,0), + // and the radius is 1. + Capsule() + : + radius((Real)1) + { + } + + Capsule(Segment const& inSegment, Real inRadius) + : + segment(inSegment), + radius(inRadius) + { + } + + // Public member access. + Segment segment; + Real radius; + + public: + // Comparisons to support sorted containers. + bool operator==(Capsule const& capsule) const + { + return segment == capsule.segment && radius == capsule.radius; + } + + bool operator!=(Capsule const& capsule) const + { + return !operator==(capsule); + } + + bool operator< (Capsule const& capsule) const + { + if (segment < capsule.segment) + { + return true; + } + + if (segment > capsule.segment) + { + return false; + } + + return radius < capsule.radius; + } + + bool operator<=(Capsule const& capsule) const + { + return !capsule.operator<(*this); + } + + bool operator> (Capsule const& capsule) const + { + return capsule.operator<(*this); + } + + bool operator>=(Capsule const& capsule) const + { + return !operator<(capsule); + } + }; + + // Template alias for convenience. + template + using Capsule3 = Capsule<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ChebyshevRatio.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ChebyshevRatio.h new file mode 100644 index 0000000..16f4882 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ChebyshevRatio.h @@ -0,0 +1,155 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// Let f(t,A) = sin(t*A)/sin(A). The slerp of quaternions q0 and q1 is +// slerp(t,q0,q1) = f(1-t,A)*q0 + f(t,A)*q1. +// Let y = 1-cos(A); we allow A in [0,pi], so y in [0,1]. As a function of y, +// a series representation for f(t,y) is +// f(t,y) = sum_{i=0}^{infinity} c_{i}(t) y^{i} +// where c_0(t) = t, c_{i}(t) = c_{i-1}(t)*(i^2 - t^2)/(i*(2*i+1)) for i >= 1. +// The c_{i}(t) are polynomials in t of degree 2*i+1. The document +// https://www.geometrictools/com/Documentation/FastAndAccurateSlerp.pdf +// derives an approximation +// g(t,y) = sum_{i=0}^{n-1} c_{i}(t) y^{i} + (1+u_n) c_{n}(t) y^n +// which has degree 2*n+1 in t and degree n in y. +// +// Given q0 and q1 such that cos(A) = dot(q0,q1) in [0,1], in which case +// A in [0,pi/2], let qh = (q0+q1)/|q0 + q1| = slerp(1/2,q0,q1). Note that +// |q0 + q1| = 2*cos(A/2) because +// sin(A/2)/sin(A) = sin(A/2)/(2*sin(A/2)*cos(A/2)) = 1/(2*cos(A/2)) +// The angle between q0 and qh is the same as the angle between qh and q1, +// namely, A/2 in [0,pi/4]. It may be shown that +// +-- +// slerp(t,q0,q1) = | slerp(2*t,q0,qh), 0 <= t <= 1/2 +// | slerp(2*t-1,qh,q1), 1/2 <= t <= 1 +// +-- +// The slerp functions on the right-hand side involve angles in [0,pi/4], so +// the approximation is more accurate for those evaluations than using the +// original. +// +// TODO: The constants in GetEstimate are those of the published paper. +// Modify these to match the aforementioned GT document. + +namespace WwiseGTE +{ + template + class ChebyshevRatio + { + public: + // Compute f(t,A) = sin(t*A)/sin(A), where t in [0,1], A in [0,pi/2], + // cosA = cos(A), f0 = f(1-t,A), and f1 = f(t,A). + static void Get(Real t, Real cosA, Real& f0, Real& f1) + { + if (cosA < (Real)1) + { + // The angle A is in (0,pi/2]. + Real A = std::acos(cosA); + Real invSinA = (Real)1 / std::sin(A); + f0 = std::sin(((Real)1 - t) * A) * invSinA; + f1 = std::sin(t * A) * invSinA; + } + else + { + // The angle theta is 0. + f0 = (Real)1 - t; + f1 = (Real)t; + } + } + + // Compute estimates for f(t,y) = sin(t*A)/sin(A), where t in [0,1], + // A in [0,pi/2], y = 1 - cos(A), f0 is the estimate for f(1-t,y), and + // f1 is the estimate for f(t,y). The approximating function is a + // polynomial of two variables. The template parameter N must be in + // {1..16}. The degree in t is 2*N+1 and the degree in Y is N. + template + static void GetEstimate(Real t, Real y, Real & f0, Real & f1) + { + static_assert(1 <= N && N <= 16, "Invalid degree."); + + // The ASM output shows that the constants/ in these arrays are + // loaded to XMM registers as literal values, and only those + // constants required for the specified degree D are loaded. + // That is, the compiler does a good job of optimizing the code. + + Real const onePlusMu[16] = + { + (Real)1.62943436108234530, + (Real)1.73965850021313961, + (Real)1.79701067629566813, + (Real)1.83291820510335812, + (Real)1.85772477879039977, + (Real)1.87596835698904785, + (Real)1.88998444919711206, + (Real)1.90110745351730037, + (Real)1.91015881189952352, + (Real)1.91767344933047190, + (Real)1.92401541194159076, + (Real)1.92944142668012797, + (Real)1.93413793373091059, + (Real)1.93824371262559758, + (Real)1.94186426368404708, + (Real)1.94508125972497303 + }; + + Real const a[16] = + { + (N != 1 ? (Real)1 : onePlusMu[0]) / ((Real)1 * (Real)3), + (N != 2 ? (Real)1 : onePlusMu[1]) / ((Real)2 * (Real)5), + (N != 3 ? (Real)1 : onePlusMu[2]) / ((Real)3 * (Real)7), + (N != 4 ? (Real)1 : onePlusMu[3]) / ((Real)4 * (Real)9), + (N != 5 ? (Real)1 : onePlusMu[4]) / ((Real)5 * (Real)11), + (N != 6 ? (Real)1 : onePlusMu[5]) / ((Real)6 * (Real)13), + (N != 7 ? (Real)1 : onePlusMu[6]) / ((Real)7 * (Real)15), + (N != 8 ? (Real)1 : onePlusMu[7]) / ((Real)8 * (Real)17), + (N != 9 ? (Real)1 : onePlusMu[8]) / ((Real)9 * (Real)19), + (N != 10 ? (Real)1 : onePlusMu[9]) / ((Real)10 * (Real)21), + (N != 11 ? (Real)1 : onePlusMu[10]) / ((Real)11 * (Real)23), + (N != 12 ? (Real)1 : onePlusMu[11]) / ((Real)12 * (Real)25), + (N != 13 ? (Real)1 : onePlusMu[12]) / ((Real)13 * (Real)27), + (N != 14 ? (Real)1 : onePlusMu[13]) / ((Real)14 * (Real)29), + (N != 15 ? (Real)1 : onePlusMu[14]) / ((Real)15 * (Real)31), + (N != 16 ? (Real)1 : onePlusMu[15]) / ((Real)16 * (Real)33) + }; + + Real const b[16] = + { + (N != 1 ? (Real)1 : onePlusMu[0]) * (Real)1 / (Real)3, + (N != 2 ? (Real)1 : onePlusMu[1]) * (Real)2 / (Real)5, + (N != 3 ? (Real)1 : onePlusMu[2]) * (Real)3 / (Real)7, + (N != 4 ? (Real)1 : onePlusMu[3]) * (Real)4 / (Real)9, + (N != 5 ? (Real)1 : onePlusMu[4]) * (Real)5 / (Real)11, + (N != 6 ? (Real)1 : onePlusMu[5]) * (Real)6 / (Real)13, + (N != 7 ? (Real)1 : onePlusMu[6]) * (Real)7 / (Real)15, + (N != 8 ? (Real)1 : onePlusMu[7]) * (Real)8 / (Real)17, + (N != 9 ? (Real)1 : onePlusMu[8]) * (Real)9 / (Real)19, + (N != 10 ? (Real)1 : onePlusMu[9]) * (Real)10 / (Real)21, + (N != 11 ? (Real)1 : onePlusMu[10]) * (Real)11 / (Real)23, + (N != 12 ? (Real)1 : onePlusMu[11]) * (Real)12 / (Real)25, + (N != 13 ? (Real)1 : onePlusMu[12]) * (Real)13 / (Real)27, + (N != 14 ? (Real)1 : onePlusMu[13]) * (Real)14 / (Real)29, + (N != 15 ? (Real)1 : onePlusMu[14]) * (Real)15 / (Real)31, + (N != 16 ? (Real)1 : onePlusMu[15]) * (Real)16 / (Real)33 + }; + + Real term0 = (Real)1 - t, term1 = t; + Real sqr0 = term0 * term0, sqr1 = term1 * term1; + f0 = term0; + f1 = term1; + for (int i = 0; i < N; ++i) + { + term0 *= (b[i] - a[i] * sqr0) * y; + term1 *= (b[i] - a[i] * sqr1) * y; + f0 += term0; + f1 += term1; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CholeskyDecomposition.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CholeskyDecomposition.h new file mode 100644 index 0000000..66c572f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CholeskyDecomposition.h @@ -0,0 +1,551 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + // Implementation for size known at compile time. + template + class CholeskyDecomposition + { + public: + // Ensure that N > 0 at compile time. + CholeskyDecomposition() + { + static_assert(N > 0, "Invalid size in CholeskyDecomposition constructor."); + } + + // Disallow copies and moves. + CholeskyDecomposition(CholeskyDecomposition const&) = delete; + CholeskyDecomposition& operator=(CholeskyDecomposition const&) = delete; + CholeskyDecomposition(CholeskyDecomposition&&) = delete; + CholeskyDecomposition& operator=(CholeskyDecomposition&&) = delete; + + // On input, A is symmetric. Only the lower-triangular portion is + // modified. On output, the lower-triangular portion is L where + // A = L * L^T. + bool Factor(Matrix& A) + { + for (int c = 0; c < N; ++c) + { + if (A(c, c) <= (Real)0) + { + return false; + } + A(c, c) = std::sqrt(A(c, c)); + + for (int r = c + 1; r < N; ++r) + { + A(r, c) /= A(c, c); + } + + for (int k = c + 1; k < N; ++k) + { + for (int r = k; r < N; ++r) + { + A(r, k) -= A(r, c) * A(k, c); + } + } + } + return true; + } + + // Solve L*Y = B, where L is lower triangular and invertible. The + // input value of Y is B. On output, Y is the solution. + void SolveLower(Matrix const& L, Vector& Y) + { + for (int r = 0; r < N; ++r) + { + for (int c = 0; c < r; ++c) + { + Y[r] -= L(r, c) * Y[c]; + } + Y[r] /= L(r, r); + } + } + + // Solve L^T*X = Y, where L is lower triangular (L^T is upper + // triangular) and invertible. The input value of X is Y. On + // output, X is the solution. + void SolveUpper(Matrix const& L, Vector& X) + { + for (int r = N - 1; r >= 0; --r) + { + for (int c = r + 1; c < N; ++c) + { + X[r] -= L(c, r) * X[c]; + } + X[r] /= L(r, r); + } + } + }; + + // Implementation for size known only at run time. + template + class CholeskyDecomposition + { + public: + int const N; + + // Ensure that N > 0 at run time. + CholeskyDecomposition(int n) + : + N(n) + { + } + + // Disallow copies and moves. This is required to avoid compiler + // complaints about the 'int const N' member. + CholeskyDecomposition(CholeskyDecomposition const&) = delete; + CholeskyDecomposition& operator=(CholeskyDecomposition const&) = delete; + CholeskyDecomposition(CholeskyDecomposition&&) = delete; + CholeskyDecomposition& operator=(CholeskyDecomposition&&) = delete; + + // On input, A is symmetric. Only the lower-triangular portion is + // modified. On output, the lower-triangular portion is L where + // A = L * L^T. + bool Factor(GMatrix& A) + { + if (A.GetNumRows() == N && A.GetNumCols() == N) + { + for (int c = 0; c < N; ++c) + { + if (A(c, c) <= (Real)0) + { + return false; + } + A(c, c) = std::sqrt(A(c, c)); + + for (int r = c + 1; r < N; ++r) + { + A(r, c) /= A(c, c); + } + + for (int k = c + 1; k < N; ++k) + { + for (int r = k; r < N; ++r) + { + A(r, k) -= A(r, c) * A(k, c); + } + } + } + return true; + } + LogError("Matrix must be square."); + } + + // Solve L*Y = B, where L is lower triangular and invertible. The + // input value of Y is B. On output, Y is the solution. + void SolveLower(GMatrix const& L, GVector& Y) + { + if (L.GetNumRows() == N && L.GetNumCols() == N && Y.GetSize() == N) + { + for (int r = 0; r < N; ++r) + { + for (int c = 0; c < r; ++c) + { + Y[r] -= L(r, c) * Y[c]; + } + Y[r] /= L(r, r); + } + return; + } + LogError("Invalid size."); + } + + // Solve L^T*X = Y, where L is lower triangular (L^T is upper + // triangular) and invertible. The input value of X is Y. On + // output, X is the solution. + void SolveUpper(GMatrix const& L, GVector& X) + { + if (L.GetNumRows() == N && L.GetNumCols() == N && X.GetSize() == N) + { + for (int r = N - 1; r >= 0; --r) + { + for (int c = r + 1; c < N; ++c) + { + X[r] -= L(c, r) * X[c]; + } + X[r] /= L(r, r); + } + } + else + { + LogError("Invalid size."); + } + } + }; + + + // Implementation for sizes known at compile time. + template + class BlockCholeskyDecomposition + { + public: + // Let B represent the block size and N represent the number of + // blocks. The matrix A is (N*B)-by-(N*B) but partitioned into an + // N-by-N matrix of blocks, each block of size B-by-B. The value + // N*B is NumDimensions. + enum + { + NumDimensions = NumBlocks * BlockSize + }; + + typedef std::array, NumBlocks> BlockVector; + typedef std::array, NumBlocks>, NumBlocks> BlockMatrix; + + // Ensure that BlockSize > 0 and NumBlocks > 0 at compile time. + BlockCholeskyDecomposition() + { + static_assert(BlockSize > 0 && NumBlocks > 0, "Invalid size in BlockCholeskyDecomposition constructor."); + } + + // Disallow copies and moves. + BlockCholeskyDecomposition(BlockCholeskyDecomposition const&) = delete; + BlockCholeskyDecomposition& operator=(BlockCholeskyDecomposition const&) = delete; + BlockCholeskyDecomposition(BlockCholeskyDecomposition&&) = delete; + BlockCholeskyDecomposition& operator=(BlockCholeskyDecomposition&&) = delete; + + // Treating the matrix as a 2D table of scalars with NUM_DIMENSIONS + // rows and NUM_DIMENSIONS columns, look up the correct block that + // stores the requested element and return a reference. + Real Get(BlockMatrix const& M, int row, int col) + { + int b0 = col / BlockSize, b1 = row / BlockSize; + int i0 = col % BlockSize, i1 = row % BlockSize; + auto const& block = M[b1][b0]; + return block(i1, i0); + } + + void Set(BlockMatrix& M, int row, int col, Real value) + { + int b0 = col / BlockSize, b1 = row / BlockSize; + int i0 = col % BlockSize, i1 = row % BlockSize; + auto& block = M[b1][b0]; + block(i1, i0) = value; + } + + bool Factor(BlockMatrix& A) + { + for (int c = 0; c < NumBlocks; ++c) + { + if (!mDecomposer.Factor(A[c][c])) + { + return false; + } + + for (int r = c + 1; r < NumBlocks; ++r) + { + LowerTriangularSolver(r, c, A); + } + + for (int k = c + 1; k < NumBlocks; ++k) + { + for (int r = k; r < NumBlocks; ++r) + { + SubtractiveUpdate(r, k, c, A); + } + } + } + return true; + } + + // Solve L*Y = B, where L is an invertible lower-triangular block + // matrix whose diagonal blocks are lower-triangular matrices. + // The input B is a block vector of commensurate size. The input + // value of Y is B. On output, Y is the solution. + void SolveLower(BlockMatrix const& L, BlockVector& Y) + { + for (int r = 0; r < NumBlocks; ++r) + { + auto& Yr = Y[r]; + for (int c = 0; c < r; ++c) + { + auto const& Lrc = L[r][c]; + auto const& Yc = Y[c]; + for (int i = 0; i < BlockSize; ++i) + { + for (int j = 0; j < BlockSize; ++j) + { + Yr[i] -= Lrc(i, j) * Yc[j]; + } + } + } + mDecomposer.SolveLower(L[r][r], Yr); + } + } + + // Solve L^T*X = Y, where L is an invertible lower-triangular block + // matrix (L^T is an upper-triangular block matrix) whose diagonal + // blocks are lower-triangular matrices. The input value of X is Y. + // On output, X is the solution. + void SolveUpper(BlockMatrix const& L, BlockVector& X) + { + for (int r = NumBlocks - 1; r >= 0; --r) + { + auto& Xr = X[r]; + for (int c = r + 1; c < NumBlocks; ++c) + { + auto const& Lcr = L[c][r]; + auto const& Xc = X[c]; + for (int i = 0; i < BlockSize; ++i) + { + for (int j = 0; j < BlockSize; ++j) + { + Xr[i] -= Lcr(j, i) * Xc[j]; + } + } + } + mDecomposer.SolveUpper(L[r][r], Xr); + } + } + + private: + // Solve G(c,c)*G(r,c)^T = A(r,c)^T for G(r,c). The matrices + // G(c,c) and A(r,c) are known quantities, and G(c,c) occupies + // the lower triangular portion of A(c,c). The solver stores + // its results in-place, so A(r,c) stores the G(r,c) result. + void LowerTriangularSolver(int r, int c, BlockMatrix& A) + { + auto const& Acc = A[c][c]; + auto& Arc = A[r][c]; + for (int j = 0; j < BlockSize; ++j) + { + for (int i = 0; i < j; ++i) + { + Real Lji = Acc(j, i); + for (int k = 0; k < BlockSize; ++k) + { + Arc(k, j) -= Lji * Arc(k, i); + } + } + + Real Ljj = Acc(j, j); + for (int k = 0; k < BlockSize; ++k) + { + Arc(k, j) /= Ljj; + } + } + } + + void SubtractiveUpdate(int r, int k, int c, BlockMatrix& A) + { + auto const& Arc = A[r][c]; + auto const& Akc = A[k][c]; + auto& Ark = A[r][k]; + for (int j = 0; j < BlockSize; ++j) + { + for (int i = 0; i < BlockSize; ++i) + { + for (int m = 0; m < BlockSize; ++m) + { + Ark(j, i) -= Arc(j, m) * Akc(i, m); + } + } + } + } + + CholeskyDecomposition mDecomposer; + }; + + // Implementation for sizes known only at run time. + template + class BlockCholeskyDecomposition + { + public: + // Let B represent the block size and N represent the number of + // blocks. The matrix A is (N*B)-by-(N*B) but partitioned into an + // N-by-N matrix of blocks, each block of size B-by-B. The value + // N*B is NumDimensions. + int const BlockSize; + int const NumBlocks; + int const NumDimensions; + + // The number of elements in a BlockVector object must be NumBlocks + // and each GVector element has BlockSize components. + typedef std::vector> BlockVector; + + // The BlockMatrix is an array of NumBlocks-by-NumBlocks matrices. + // Each block matrix is stored in row-major order. The BlockMatrix + // elements themselves are stored in row-major order. The block + // matrix element M = BlockMatrix[col + NumBlocks * row] is of size + // BlockSize-by-BlockSize (in row-major order) and is in the (row,col) + // location of the full matrix of blocks. + typedef std::vector> BlockMatrix; + + // Ensure that BlockSize > 0 and NumDimensions > 0 at run time. + BlockCholeskyDecomposition(int blockSize, int numBlocks) + : + BlockSize(blockSize), + NumBlocks(numBlocks), + NumDimensions(numBlocks * blockSize), + mDecomposer(blockSize) + { + LogAssert(blockSize > 0 && numBlocks > 0, "Invalid input."); + } + + // Disallow copies and moves. This is required to avoid compiler + // complaints about the 'int const' members. + BlockCholeskyDecomposition(BlockCholeskyDecomposition const&) = delete; + BlockCholeskyDecomposition& operator=(BlockCholeskyDecomposition const&) = delete; + BlockCholeskyDecomposition(BlockCholeskyDecomposition&&) = delete; + BlockCholeskyDecomposition& operator=(BlockCholeskyDecomposition&&) = delete; + + // Treating the matrix as a 2D table of scalars with NumDimensions + // rows and NumDimensions columns, look up the correct block that + // stores the requested element and return a reference. + Real Get(BlockMatrix const& M, int row, int col) + { + int b0 = col / BlockSize, b1 = row / BlockSize; + int i0 = col % BlockSize, i1 = row % BlockSize; + auto const& block = M[GetIndex(b1, b0)]; + return block(i1, i0); + } + + void Set(BlockMatrix& M, int row, int col, Real value) + { + int b0 = col / BlockSize, b1 = row / BlockSize; + int i0 = col % BlockSize, i1 = row % BlockSize; + auto& block = M[GetIndex(b1, b0)]; + block(i1, i0) = value; + } + + bool Factor(BlockMatrix& A) + { + for (int c = 0; c < NumBlocks; ++c) + { + if (!mDecomposer.Factor(A[GetIndex(c, c)])) + { + return false; + } + + for (int r = c + 1; r < NumBlocks; ++r) + { + LowerTriangularSolver(r, c, A); + } + + for (int k = c + 1; k < NumBlocks; ++k) + { + for (int r = k; r < NumBlocks; ++r) + { + SubtractiveUpdate(r, k, c, A); + } + } + } + return true; + } + + // Solve L*Y = B, where L is an invertible lower-triangular block + // matrix whose diagonal blocks are lower-triangular matrices. + // The input B is a block vector of commensurate size. The input + // value of Y is B. On output, Y is the solution. + void SolveLower(BlockMatrix const& L, BlockVector& Y) + { + for (int r = 0; r < NumBlocks; ++r) + { + auto& Yr = Y[r]; + for (int c = 0; c < r; ++c) + { + auto const& Lrc = L[GetIndex(r, c)]; + auto const& Yc = Y[c]; + for (int i = 0; i < NumBlocks; ++i) + { + for (int j = 0; j < NumBlocks; ++j) + { + Yr[i] -= Lrc[GetIndex(i, j)] * Yc[j]; + } + } + } + mDecomposer.SolveLower(L[GetIndex(r, r)], Yr); + } + } + + // Solve L^T*X = Y, where L is an invertible lower-triangular block + // matrix (L^T is an upper-triangular block matrix) whose diagonal + // blocks are lower-triangular matrices. The input value of X is Y. + // On output, X is the solution. + void SolveUpper(BlockMatrix const& L, BlockVector& X) + { + for (int r = NumBlocks - 1; r >= 0; --r) + { + auto& Xr = X[r]; + for (int c = r + 1; c < NumBlocks; ++c) + { + auto const& Lcr = L[GetIndex(c, r)]; + auto const& Xc = X[c]; + for (int i = 0; i < BlockSize; ++i) + { + for (int j = 0; j < BlockSize; ++j) + { + Xr[i] -= Lcr[GetIndex(j, i)] * Xc[j]; + } + } + } + mDecomposer.SolveUpper(L[GetIndex(r, r)], Xr); + } + } + + private: + // Compute the 1-dimensional index of the block matrix in a + // 2-dimensional BlockMatrix object. + inline int GetIndex(int row, int col) const + { + return col + row * NumBlocks; + } + + // Solve G(c,c)*G(r,c)^T = A(r,c)^T for G(r,c). The matrices + // G(c,c) and A(r,c) are known quantities, and G(c,c) occupies + // the lower triangular portion of A(c,c). The solver stores + // its results in-place, so A(r,c) stores the G(r,c) result. + void LowerTriangularSolver(int r, int c, BlockMatrix& A) + { + auto const& Acc = A[GetIndex(c, c)]; + auto& Arc = A[GetIndex(r, c)]; + for (int j = 0; j < BlockSize; ++j) + { + for (int i = 0; i < j; ++i) + { + Real Lji = Acc[GetIndex(j, i)]; + for (int k = 0; k < BlockSize; ++k) + { + Arc[GetIndex(k, j)] -= Lji * Arc[GetIndex(k, i)]; + } + } + + Real Ljj = Acc[GetIndex(j, j)]; + for (int k = 0; k < BlockSize; ++k) + { + Arc[GetIndex(k, j)] /= Ljj; + } + } + } + + void SubtractiveUpdate(int r, int k, int c, BlockMatrix& A) + { + auto const& Arc = A[GetIndex(r, c)]; + auto const& Akc = A[GetIndex(k, c)]; + auto& Ark = A[GetIndex(r, k)]; + for (int j = 0; j < BlockSize; ++j) + { + for (int i = 0; i < BlockSize; ++i) + { + for (int m = 0; m < BlockSize; ++m) + { + Ark[GetIndex(j, i)] -= Arc[GetIndex(j, m)] * Akc[GetIndex(i, m)]; + } + } + } + } + + // The decomposer has size BlockSize. + CholeskyDecomposition mDecomposer; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Circle3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Circle3.h new file mode 100644 index 0000000..e0d2e55 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Circle3.h @@ -0,0 +1,99 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The circle is the intersection of the sphere |X-C|^2 = r^2 and the +// plane Dot(N,X-C) = 0, where C is the circle center, r is the radius, +// and N is a unit-length plane normal. + +namespace WwiseGTE +{ + template + class Circle3 + { + public: + + // Construction and destruction. The default constructor sets center + // to (0,0,0), normal to (0,0,1), and radius to 1. + Circle3() + : + center(Vector3::Zero()), + normal(Vector3::Unit(2)), + radius((Real)1) + { + } + + Circle3(Vector3 const& inCenter, Vector3 const& inNormal, Real inRadius) + : + center(inCenter), + normal(inNormal), + radius(inRadius) + { + } + + // Public member access. + Vector3 center, normal; + Real radius; + + public: + // Comparisons to support sorted containers. + bool operator==(Circle3 const& circle) const + { + return center == circle.center + && normal == circle.normal + && radius == circle.radius; + } + + bool operator!=(Circle3 const& circle) const + { + return !operator==(circle); + } + + bool operator< (Circle3 const& circle) const + { + if (center < circle.center) + { + return true; + } + + if (center > circle.center) + { + return false; + } + + if (normal < circle.normal) + { + return true; + } + + if (normal > circle.normal) + { + return false; + } + + return radius < circle.radius; + } + + bool operator<=(Circle3 const& circle) const + { + return !circle.operator<(*this); + } + + bool operator> (Circle3 const& circle) const + { + return circle.operator<(*this); + } + + bool operator>=(Circle3 const& circle) const + { + return !operator<(circle); + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Cone.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Cone.h new file mode 100644 index 0000000..95f04c5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Cone.h @@ -0,0 +1,333 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// An infinite cone is defined by a vertex V, a unit-length direction D and an +// angle A with 0 < A < pi/2. A point X is on the cone when +// Dot(D, X - V) = |X - V| * cos(A) +// A solid cone includes points on the cone and in the region that contains +// the cone ray V + h * D for h >= 0. It is defined by +// Dot(D, X - V) >= |X - V| * cos(A) +// The height of any point Y in space relative to the cone is defined by +// h = Dot(D, Y - V), which is the signed length of the projection of X - V +// onto the cone axis. Observe that we have restricted the cone definition to +// an acute angle A, so |X - V| * cos(A) >= 0; therefore, points on or inside +// the cone have nonnegative heights: Dot(D, X - V) >= 0. I will refer to the +// infinite solid cone as the "positive cone," which means that the non-vertex +// points inside the cone have positive heights. Although rare in computer +// graphics, one might also want to consider the "negative cone," which is +// defined by +// -Dot(D, X - V) <= -|X - V| * cos(A) +// The non-vertex points inside this cone have negative heights. +// +// For many of the geometric queries involving cones, we can avoid the square +// root computation implied by |X - V|. The positive cone is defined by +// Dot(D, X - V)^2 >= |X - V|^2 * cos(A)^2, +// which is a quadratic inequality, but the squaring of the terms leads to an +// inequality that includes points X in the negative cone. When using the +// quadratic inequality for the positive cone, we need to include also the +// constraint Dot(D, X - V) >= 0. +// +// I define four different types of cones. They all involve V, D and A. The +// differences are based on restrictions to the heights of the cone points. +// The height range is defined to be the interval of possible heights, say, +// [hmin,hmax] with 0 <= hmin < hmax <= +infinity. +// 1. infinite cone: hmin = 0, hmax = +infinity +// 2. infinite truncated cone: hmin > 0, hmax = +infinity +// 3. finite cone: hmin >= 0, hmax < +infinity +// 4. frustum of a cone: hmin > 0, hmax < +infinity +// The infinite truncated cone is truncated for h-minimum; the radius of the +// disk at h-minimum is rmin = hmin * tan(A). The finite cone is truncated for +// h-maximum; the radius of the disk at h-maximum is rmax = hmax * tan(A). +// The frustum of a cone is truncated both for h-minimum and h-maximum. +// +// A technical problem when creating a data structure to represent a cone is +// deciding how to represent +infinity in the height range. When the template +// type Real is 'float' or 'double', we could represent it as +// std::numeric_limits::infinity(). The geometric queries must be +// structured properly to conform to the semantics associated with the +// floating-point infinity. We could also use the largest finite +// floating-point number, std::numeric_limits::max(). Either choice is +// problematic when instead Real is an arbitrary precision type that does not +// have a representation for infinity; this is the case for the types +// BSNumber and BSRational, where T is UIntegerAP or UIntegerFP. +// +// The introduction of representations of infinities for the arbitrary +// precision types would require modifying the arithmetic operations to test +// whether the number is finite or infinite. This leads to a greater +// computational cost for all queries, even when those queries do not require +// manipulating infinities. In the case of a cone, the height manipulations +// are nearly always for comparisons of heights. I choose to represent +// +infinity by setting the maxHeight member to -1. The member functions +// IsFinite() and IsInfinite() compare maxHeight to -1 and report the correct +// state. +// +// My choice of representation has the main consequence that comparisons +// between heights requires extra logic. This can make geometric queries +// cumbersome to implement. For example, the point-in-cone test using the +// quadratic inequality is shown in the pseudocode +// Vector point = ; +// Cone cone = ; +// Vector delta = point - cone.V; +// Real h = Dot(cone.D, delta); +// bool pointInCone = +// cone.hmin <= h && +// h <= cone.hmax && +// h * h >= Dot(delta, delta) * cone.cosAngleSqr; +// In the event the cone is infinite and we choose cone.hmax = -1 to +// represent this, the test 'h <= cone.hmax' must be revised, +// bool pointInCone = +// cone.hmin <= h && +// (cone.hmax == -1 ? true : (h <= cone.hmax)) && +// h * h >= Dot(delta, delta) * cone.cosAngleSqr; +// To encapsulate the comparisons against height extremes, use the member +// function HeightInRange(h); that is +// bool pointInCone = +// cone.HeightInRange(h) && +// h * h >= Dot(delta, delta) * cone.cosAngleSqr; +// The modification is not that complicated here, but consider a more +// sophisticated query such as determining the interval of intersection +// of two height intervals [h0,h1] and [cone.hmin,cone.hmax]. The file +// GteIntrIntervals.h provides implementations for computing the +// intersection of two intervals, where either or both intervals are +// semi-infinite. + +namespace WwiseGTE +{ + template + class Cone + { + public: + // Create an infinite cone with + // vertex = (0,...,0) + // axis = (0,...,0,1) + // angle = pi/4 + // minimum height = 0 + // maximum height = +infinity + Cone() + { + ray.origin.MakeZero(); + ray.direction.MakeUnit(N - 1); + SetAngle((Real)GTE_C_QUARTER_PI); + MakeInfiniteCone(); + } + + // Create an infinite cone with the specified vertex, axis direction, + // angle and with heights + // minimum height = 0 + // maximum height = +infinity + Cone(Ray const& inRay, Real const& inAngle) + : + ray(inRay) + { + SetAngle(inAngle); + MakeInfiniteCone(); + } + + // Create an infinite truncated cone with the specified vertex, axis + // direction, angle and positive minimum height. The maximum height + // is +infinity. If you specify a minimum height of 0, you get the + // equivalent of calling the constructor for an infinite cone. + Cone(Ray const& inRay, Real const& inAngle, Real const& inMinHeight) + : + ray(inRay) + { + SetAngle(inAngle); + MakeInfiniteTruncatedCone(inMinHeight); + } + + // Create a finite cone or a frustum of a cone with all parameters + // specified. If you specify a minimum height of 0, you get a finite + // cone. If you specify a positive minimum height, you get a frustum + // of a cone. + Cone(Ray const& inRay, Real inAngle, Real inMinHeight, Real inMaxHeight) + : + ray(inRay) + { + SetAngle(inAngle); + MakeConeFrustum(inMinHeight, inMaxHeight); + } + + // The angle must be in (0,pi/2). The function sets 'angle' and + // computes 'cosAngle', 'sinAngle', 'tanAngle', 'cosAngleSqr', + // 'sinAngleSqr' and 'invSinAngle'. + void SetAngle(Real const& inAngle) + { + LogAssert((Real)0 < inAngle && inAngle < (Real)GTE_C_HALF_PI, "Invalid angle."); + angle = inAngle; + cosAngle = std::cos(angle); + sinAngle = std::sin(angle); + tanAngle = std::tan(angle); + cosAngleSqr = cosAngle * cosAngle; + sinAngleSqr = sinAngle * sinAngle; + invSinAngle = (Real)1 / sinAngle; + } + + // Set the heights to obtain one of the four types of cones. Be aware + // that an infinite cone has maxHeight set to -1. Be careful not to + // use maxHeight without understanding this interpretation. + void MakeInfiniteCone() + { + mMinHeight = (Real)0; + mMaxHeight = (Real)-1; + } + + void MakeInfiniteTruncatedCone(Real const& inMinHeight) + { + LogAssert(inMinHeight >= (Real)0, "Invalid minimum height."); + mMinHeight = inMinHeight; + mMaxHeight = (Real)-1; + } + + void MakeFiniteCone(Real const& inMaxHeight) + { + LogAssert(inMaxHeight > (Real)0, "Invalid maximum height."); + mMinHeight = (Real)0; + mMaxHeight = inMaxHeight; + } + + void MakeConeFrustum(Real const& inMinHeight, Real const& inMaxHeight) + { + LogAssert(inMinHeight >= (Real)0 && inMaxHeight > inMinHeight, + "Invalid minimum or maximum height."); + mMinHeight = inMinHeight; + mMaxHeight = inMaxHeight; + } + + // Get the height extremes. For an infinite cone, maxHeight is set + // to -1. For a finite cone, maxHeight is set to a positive number. + // Be careful not to use maxHeight without understanding this + // interpretation. + inline Real GetMinHeight() const + { + return mMinHeight; + } + + inline Real GetMaxHeight() const + { + return mMaxHeight; + } + + inline bool HeightInRange(Real const& h) const + { + return mMinHeight <= h && (mMaxHeight != (Real)-1 ? h <= mMaxHeight : true); + } + + inline bool HeightLessThanMin(Real const& h) const + { + return h < mMinHeight; + } + + inline bool HeightGreaterThanMax(Real const& h) const + { + return (mMaxHeight != (Real)-1 ? h > mMaxHeight : false); + } + + inline bool IsFinite() const + { + return mMaxHeight != (Real)-1; + } + + inline bool IsInfinite() const + { + return mMaxHeight == (Real)-1; + } + + // The cone axis direction (ray.direction) must be unit length. + Ray ray; + + // The angle must be in (0,pi/2). The other members are derived from + // angle to avoid calling trigonometric functions in geometric queries + // (for speed). You may set the angle and compute these by calling + // SetAngle(inAngle). + Real angle; + Real cosAngle, sinAngle, tanAngle; + Real cosAngleSqr, sinAngleSqr, invSinAngle; + + private: + // The heights must satisfy 0 <= minHeight < maxHeight <= +infinity. + // For an infinite cone, maxHeight is set to -1. For a finite cone, + // maxHeight is set to a positive number. Be careful not to use + // maxHeight without understanding this interpretation. + Real mMinHeight, mMaxHeight; + + public: + // Comparisons to support sorted containers. These based only on + // 'ray', 'angle', 'minHeight' and 'maxHeight'. + bool operator==(Cone const& cone) const + { + return ray == cone.ray + && angle == cone.angle + && mMinHeight == cone.mMinHeight + && mMaxHeight == cone.mMaxHeight; + } + + bool operator!=(Cone const& cone) const + { + return !operator==(cone); + } + + bool operator< (Cone const& cone) const + { + if (ray < cone.ray) + { + return true; + } + + if (ray > cone.ray) + { + return false; + } + + if (angle < cone.angle) + { + return true; + } + + if (angle > cone.angle) + { + return false; + } + + if (mMinHeight < cone.mMinHeight) + { + return true; + } + + if (mMinHeight > cone.mMinHeight) + { + return false; + } + + return mMaxHeight < cone.mMaxHeight; + } + + bool operator<=(Cone const& cone) const + { + return !cone.operator<(*this); + } + + bool operator> (Cone const& cone) const + { + return cone.operator<(*this); + } + + bool operator>=(Cone const& cone) const + { + return !operator<(cone); + } + }; + + // Template alias for convenience. + template + using Cone3 = Cone<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ConformalMapGenus0.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ConformalMapGenus0.h new file mode 100644 index 0000000..2e12eae --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ConformalMapGenus0.h @@ -0,0 +1,412 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include + +// Conformally map a 2-dimensional manifold mesh with the topology of a sphere +// to a sphere. The algorithm is an implementation of the one in the paper +// S.Haker, S.Angenent, A.Tannenbaum, R.Kikinis, G.Sapiro, and M.Halle. +// Conformal surface parameterization for texture mapping, +// IEEE Transactions on Visualization and Computer Graphics, +// Volume 6, Number 2, pages 181–189, 2000 +// The paper is available at https://ieeexplore.ieee.org/document/856998 but +// is not freely downloadable. + +namespace WwiseGTE +{ + template + class ConformalMapGenus0 + { + public: + // The input mesh should be a closed, manifold surface that has the + // topology of a sphere (genus 0 surface). + ConformalMapGenus0() + : + mSphereRadius(0.0f) + { + } + + ~ConformalMapGenus0() + { + } + + // The returned 'bool' value is 'true' whenever the conjugate gradient + // algorithm converged. Even if it did not, the results might still + // be acceptable. + bool operator()(int numPositions, Vector3 const* positions, + int numTriangles, int const* indices, int punctureTriangle) + { + bool converged = true; + mPlaneCoordinates.resize(numPositions); + mSphereCoordinates.resize(numPositions); + + // Construct a triangle-edge representation of mesh. + ETManifoldMesh graph; + int const* currentIndex = indices; + int t; + for (t = 0; t < numTriangles; ++t) + { + int v0 = *currentIndex++; + int v1 = *currentIndex++; + int v2 = *currentIndex++; + graph.Insert(v0, v1, v2); + } + auto const& emap = graph.GetEdges(); + + // Construct the nondiagonal entries of the sparse matrix A. + typename LinearSystem::SparseMatrix A; + int v0, v1, v2, i; + Vector3 E0, E1; + Real value; + for (auto const& element : emap) + { + v0 = element.first.V[0]; + v1 = element.first.V[1]; + + value = (Real)0; + for (int j = 0; j < 2; ++j) + { + auto triangle = element.second->T[j].lock(); + for (i = 0; i < 3; ++i) + { + v2 = triangle->V[i]; + if (v2 != v0 && v2 != v1) + { + E0 = positions[v0] - positions[v2]; + E1 = positions[v1] - positions[v2]; + value += Dot(E0, E1) / Length(Cross(E0, E1)); + } + } + } + + value *= -(Real)0.5; + + std::array lookup = { v0, v1 }; + A[lookup] = value; + } + + // Construct the diagonal entries of the sparse matrix A. + std::vector tmp(numPositions, (Real)0); + for (auto const& element : A) + { + tmp[element.first[0]] -= element.second; + tmp[element.first[1]] -= element.second; + } + for (i = 0; i < numPositions; ++i) + { + std::array lookup = { i, i }; + A[lookup] = tmp[i]; + } + LogAssert(static_cast(numPositions) + emap.size() == A.size(), "Mismatched sizes."); + + // Construct the sparse column vector B. + currentIndex = &indices[3 * punctureTriangle]; + v0 = *currentIndex++; + v1 = *currentIndex++; + v2 = *currentIndex++; + Vector3 V0 = positions[v0]; + Vector3 V1 = positions[v1]; + Vector3 V2 = positions[v2]; + Vector3 E10 = V1 - V0; + Vector3 E20 = V2 - V0; + Vector3 E12 = V1 - V2; + Vector3 normal = Cross(E20, E10); + Real len10 = Length(E10); + Real invLen10 = (Real)1 / len10; + Real twoArea = Length(normal); + Real invLenNormal = (Real)1 / twoArea; + Real invProd = invLen10 * invLenNormal; + Real re0 = -invLen10; + Real im0 = invProd * Dot(E12, E10); + Real re1 = invLen10; + Real im1 = invProd * Dot(E20, E10); + Real re2 = (Real)0; + Real im2 = -len10 * invLenNormal; + + // Solve the sparse system for the real parts. + unsigned int const maxIterations = 1024; + Real const tolerance = 1e-06f; + std::fill(tmp.begin(), tmp.end(), (Real)0); + tmp[v0] = re0; + tmp[v1] = re1; + tmp[v2] = re2; + std::vector result(numPositions); + unsigned int iterations = LinearSystem().SolveSymmetricCG( + numPositions, A, tmp.data(), result.data(), maxIterations, tolerance); + if (iterations >= maxIterations) + { + LogWarning("Conjugate gradient solver did not converge."); + converged = false; + } + for (i = 0; i < numPositions; ++i) + { + mPlaneCoordinates[i][0] = result[i]; + } + + // Solve the sparse system for the imaginary parts. + std::fill(tmp.begin(), tmp.end(), (Real)0); + tmp[v0] = -im0; + tmp[v1] = -im1; + tmp[v2] = -im2; + iterations = LinearSystem().SolveSymmetricCG(numPositions, A, + tmp.data(), result.data(), maxIterations, tolerance); + if (iterations >= maxIterations) + { + LogWarning("Conjugate gradient solver did not converge."); + converged = false; + } + for (i = 0; i < numPositions; ++i) + { + mPlaneCoordinates[i][1] = result[i]; + } + + // Scale to [-1,1]^2 for numerical conditioning in later steps. + Real fmin = mPlaneCoordinates[0][0], fmax = fmin; + for (i = 0; i < numPositions; i++) + { + if (mPlaneCoordinates[i][0] < fmin) + { + fmin = mPlaneCoordinates[i][0]; + } + else if (mPlaneCoordinates[i][0] > fmax) + { + fmax = mPlaneCoordinates[i][0]; + } + if (mPlaneCoordinates[i][1] < fmin) + { + fmin = mPlaneCoordinates[i][1]; + } + else if (mPlaneCoordinates[i][1] > fmax) + { + fmax = mPlaneCoordinates[i][1]; + } + } + Real halfRange = (Real)0.5 * (fmax - fmin); + Real invHalfRange = (Real)1 / halfRange; + for (i = 0; i < numPositions; ++i) + { + mPlaneCoordinates[i][0] = (Real)-1 + invHalfRange * (mPlaneCoordinates[i][0] - fmin); + mPlaneCoordinates[i][1] = (Real)-1 + invHalfRange * (mPlaneCoordinates[i][1] - fmin); + } + + // Map the plane coordinates to the sphere using inverse + // stereographic projection. The main issue is selecting a + // translation in (x,y) and a radius of the projection sphere. + // Both factors strongly influence the final result. + + // Use the average as the south pole. The points tend to be + // clustered approximately in the middle of the conformally + // mapped punctured triangle, so the average is a good choice + // to place the pole. + Vector2 origin{ (Real)0, (Real)0 }; + for (i = 0; i < numPositions; ++i) + { + origin += mPlaneCoordinates[i]; + } + origin /= (Real)numPositions; + for (i = 0; i < numPositions; ++i) + { + mPlaneCoordinates[i] -= origin; + } + + mMinPlaneCoordinate = mPlaneCoordinates[0]; + mMaxPlaneCoordinate = mPlaneCoordinates[0]; + for (i = 1; i < numPositions; ++i) + { + if (mPlaneCoordinates[i][0] < mMinPlaneCoordinate[0]) + { + mMinPlaneCoordinate[0] = mPlaneCoordinates[i][0]; + } + else if (mPlaneCoordinates[i][0] > mMaxPlaneCoordinate[0]) + { + mMaxPlaneCoordinate[0] = mPlaneCoordinates[i][0]; + } + + if (mPlaneCoordinates[i][1] < mMinPlaneCoordinate[1]) + { + mMinPlaneCoordinate[1] = mPlaneCoordinates[i][1]; + } + else if (mPlaneCoordinates[i][1] > mMaxPlaneCoordinate[1]) + { + mMaxPlaneCoordinate[1] = mPlaneCoordinates[i][1]; + } + } + + // Select the radius of the sphere so that the projected punctured + // triangle has an area whose fraction of total spherical area is + // the same fraction as the area of the punctured triangle to the + // total area of the original triangle mesh. + Real twoTotalArea = (Real)0; + currentIndex = indices; + for (t = 0; t < numTriangles; ++t) + { + V0 = positions[*currentIndex++]; + V1 = positions[*currentIndex++]; + V2 = positions[*currentIndex++]; + E0 = V1 - V0; + E1 = V2 - V0; + twoTotalArea += Length(Cross(E0, E1)); + } + ComputeSphereRadius(v0, v1, v2, twoArea / twoTotalArea); + Real sqrSphereRadius = mSphereRadius * mSphereRadius; + + // Inverse stereographic projection to obtain sphere coordinates. + // The sphere is centered at the origin and has radius 1. + for (i = 0; i < numPositions; i++) + { + Real rSqr = Dot(mPlaneCoordinates[i], mPlaneCoordinates[i]); + Real mult = (Real)1 / (rSqr + sqrSphereRadius); + Real x = (Real)2 * mult * sqrSphereRadius * mPlaneCoordinates[i][0]; + Real y = (Real)2 * mult * sqrSphereRadius * mPlaneCoordinates[i][1]; + Real z = mult * mSphereRadius * (rSqr - sqrSphereRadius); + mSphereCoordinates[i] = Vector3{ x, y, z } / mSphereRadius; + } + + return converged; + } + + // Conformal mapping of mesh to plane. The array of coordinates has a + // one-to-one correspondence with the input vertex array. + inline std::vector> const& GetPlaneCoordinates() const + { + return mPlaneCoordinates; + } + + inline Vector2 const& GetMinPlaneCoordinate() const + { + return mMinPlaneCoordinate; + } + + inline Vector2 const& GetMaxPlaneCoordinate() const + { + return mMaxPlaneCoordinate; + } + + // Conformal mapping of mesh to sphere (centered at origin). The array + // of coordinates has a one-to-one correspondence with the input vertex + // array. + inline std::vector> const& GetSphereCoordinates() const + { + return mSphereCoordinates; + } + + inline Real GetSphereRadius() const + { + return mSphereRadius; + } + + private: + void ComputeSphereRadius(int v0, int v1, int v2, Real areaFraction) + { + Vector2 V0 = mPlaneCoordinates[v0]; + Vector2 V1 = mPlaneCoordinates[v1]; + Vector2 V2 = mPlaneCoordinates[v2]; + + Real r0Sqr = Dot(V0, V0); + Real r1Sqr = Dot(V1, V1); + Real r2Sqr = Dot(V2, V2); + Real diffR10 = r1Sqr - r0Sqr; + Real diffR20 = r2Sqr - r0Sqr; + Real diffX10 = V1[0] - V0[0]; + Real diffY10 = V1[1] - V0[1]; + Real diffX20 = V2[0] - V0[0]; + Real diffY20 = V2[1] - V0[1]; + Real diffRX10 = V1[0] * r0Sqr - V0[0] * r1Sqr; + Real diffRY10 = V1[1] * r0Sqr - V0[1] * r1Sqr; + Real diffRX20 = V2[0] * r0Sqr - V0[0] * r2Sqr; + Real diffRY20 = V2[1] * r0Sqr - V0[1] * r2Sqr; + + Real c0 = diffR20 * diffRY10 - diffR10 * diffRY20; + Real c1 = diffR20 * diffY10 - diffR10 * diffY20; + Real d0 = diffR10 * diffRX20 - diffR20 * diffRX10; + Real d1 = diffR10 * diffX20 - diffR20 * diffX10; + Real e0 = diffRX10 * diffRY20 - diffRX20 * diffRY10; + Real e1 = diffRX10 * diffY20 - diffRX20 * diffY10; + Real e2 = diffX10 * diffY20 - diffX20 * diffY10; + + Polynomial1 poly0(6); + poly0[0] = (Real)0; + poly0[1] = (Real)0; + poly0[2] = e0 * e0; + poly0[3] = c0 * c0 + d0 * d0 + (Real)2 * e0 * e1; + poly0[4] = (Real)2 * (c0 * c1 + d0 * d1 + e0 * e1) + e1 * e1; + poly0[5] = c1 * c1 + d1 * d1 + (Real)2 * e1 * e2; + poly0[6] = e2 * e2; + + Polynomial1 qpoly0(1), qpoly1(1), qpoly2(1); + qpoly0[0] = r0Sqr; + qpoly0[1] = (Real)1; + qpoly1[0] = r1Sqr; + qpoly1[1] = (Real)1; + qpoly2[0] = r2Sqr; + qpoly2[1] = (Real)1; + + Real tmp = areaFraction * static_cast(GTE_C_PI); + Real amp = tmp * tmp; + + Polynomial1 poly1 = amp * qpoly0; + poly1 = poly1 * qpoly0; + poly1 = poly1 * qpoly0; + poly1 = poly1 * qpoly0; + poly1 = poly1 * qpoly1; + poly1 = poly1 * qpoly1; + poly1 = poly1 * qpoly2; + poly1 = poly1 * qpoly2; + + Polynomial1 poly2 = poly1 - poly0; + LogAssert(poly2.GetDegree() <= 8, "Expecting degree no larger than 8."); + + // Bound a root near zero and apply bisection to find t. + Real tmin = (Real)0, fmin = poly2(tmin); + Real tmax = (Real)1, fmax = poly2(tmax); + LogAssert(fmin > (Real)0 && fmax < (Real)0, "Expecting opposite-signed extremes."); + + // Determine the number of iterations to get 'digits' of accuracy. + int const digits = 6; + Real tmp0 = std::log(tmax - tmin); + Real tmp1 = (Real)digits * static_cast(GTE_C_LN_10); + Real arg = (tmp0 + tmp1) * static_cast(GTE_C_INV_LN_2); + int maxIterations = static_cast(arg + (Real)0.5); + Real tmid = (Real)0, fmid; + for (int i = 0; i < maxIterations; ++i) + { + tmid = (Real)0.5 * (tmin + tmax); + fmid = poly2(tmid); + Real product = fmid * fmin; + if (product < (Real)0) + { + tmax = tmid; + fmax = fmid; + } + else + { + tmin = tmid; + fmin = fmid; + } + } + + mSphereRadius = std::sqrt(tmid); + } + + // Conformal mapping to a plane. The plane's (px,py) points + // correspond to the mesh's (mx,my,mz) points. + std::vector> mPlaneCoordinates; + Vector2 mMinPlaneCoordinate, mMaxPlaneCoordinate; + + // Conformal mapping to a sphere. The sphere's (sx,sy,sz) points + // correspond to the mesh's (mx,my,mz) points. + std::vector> mSphereCoordinates; + Real mSphereRadius; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ConstrainedDelaunay2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ConstrainedDelaunay2.h new file mode 100644 index 0000000..bf5b48e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ConstrainedDelaunay2.h @@ -0,0 +1,686 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// Various parts of the code have LogAssert or LogError tests. For a correct +// algorithm using exact arithmetic, we do not expect to trigger these. +// However, with floating-point arithmetic, it is possible that the +// triangulation becomes malformed. The calls to the member function +// Insert(...) should be made in a try-catch block. If an exception is +// thrown, you are most likely using a floating-point type for ComputeType +// and floating-point rounding errors have caused problems in the edge +// insertions. + +namespace WwiseGTE +{ + template + class ConstrainedDelaunay2 : public Delaunay2 + { + public: + // The class is a functor to support computing the constrained + // Delaunay triangulation of multiple data sets using the same class + // object. + virtual ~ConstrainedDelaunay2() = default; + + ConstrainedDelaunay2() + : + Delaunay2() + { + } + + // This operator computes the Delaunay triangulation only. Read the + // Delaunay2 constructor comments about 'vertices' and 'epsilon'. The + // 'edges' array has indices into the 'vertices' array. No two edges + // should intersect except at endpoints. + bool operator()(int numVertices, Vector2 const* vertices, InputType epsilon) + { + return Delaunay2::operator()(numVertices, vertices, epsilon); + } + + // Insert required edges into the triangulation. For correctness of + // the algorithm, if two edges passed to this function intersect, they + // must do so only at vertices passed to operator(). If you have two + // edges that intersect at a point not in the vertices, compute that + // point of intersection and subdivide the edges at that intersection + // (to form more edges), and add the point to the vertices before + // calling operator(). This function has an output array that + // contains the input edge when the only vertices on the edge are its + // endpoints. If the input edge passes through more vertices, the + // edge is subdivided in this function. The output edge is that + // subdivision with first vertex edge[0] and last vertex edge[1], and + // the other vertices are correctly ordered along the edge. + bool Insert(std::array const& edge, std::vector& outEdge) + { + int v0 = edge[0], v1 = edge[1]; + if (0 <= v0 && v0 < this->mNumVertices + && 0 <= v1 && v1 < this->mNumVertices) + { + int v0Triangle = GetLinkTriangle(v0); + if (v0Triangle >= 0) + { + // Once an edge is inserted, the base-class mGraph no + // longer represents the triangulation. Clear it in case + // the user tries to access it. + this->mGraph.Clear(); + + outEdge.clear(); + return Insert(edge, v0Triangle, outEdge); + } + } + return false; + } + + private: + // The top-level entry point for inserting an edge in the + // triangulation. + bool Insert(std::array const& edge, int v0Triangle, std::vector& outEdge) + { + // Create the neighborhood of triangles that share the vertex v0. + // On entry we already know one such triangle (v0Triangle). + int v0 = edge[0], v1 = edge[1]; + std::list> link; + bool isOpen = true; + bool success = BuildLink(v0, v0Triangle, link, isOpen); + LogAssert(success, CDTFailure()); + + // Determine which triangle contains the edge. Process the edge + // according to whether it is strictly between two triangle edges + // or is coincident with a triangle edge. + auto item = link.begin(); + std::array indices; + success = this->GetIndices(item->first, indices); + LogAssert(success, CDTFailure()); + + int vNext = indices[(item->second + 1) % 3]; + int qr0 = this->mQuery.ToLine(v1, v0, vNext); + while (item != link.end()) + { + if (qr0 == 0) + { + // We have to be careful about parallel edges that point + // in the opposite direction of . + Vector2 const& ctv0 = this->mComputeVertices[v0]; + Vector2 const& ctv1 = this->mComputeVertices[v1]; + Vector2 const& ctvnext = this->mComputeVertices[vNext]; + if (Dot(ctv1 - ctv0, ctvnext - ctv0) > (ComputeType)0) + { + // is coincident to triangle edge0. + return ProcessCoincident(item->first, v0, v1, vNext, outEdge); + } + + // Make sure we enter the next "if" statement to continue + // traversing the link. + qr0 = 1; + } + + if (qr0 > 0) + { + // is not in triangle. Visit the next triangle. + if (++item == link.end()) + { + return false; + } + success = this->GetIndices(item->first, indices); + LogAssert(success, CDTFailure()); + vNext = indices[(item->second + 1) % 3]; + qr0 = this->mQuery.ToLine(v1, v0, vNext); + continue; + } + + int vPrev = indices[(item->second + 2) % 3]; + int qr1 = this->mQuery.ToLine(v1, v0, vPrev); + while (item != link.end()) + { + if (qr1 == 0) + { + // We have to be careful about parallel edges that + // point in the opposite direction of . + Vector2 const& ctv0 = this->mComputeVertices[v0]; + Vector2 const& ctv1 = this->mComputeVertices[v1]; + Vector2 const& ctvprev = + this->mComputeVertices[vPrev]; + if (Dot(ctv1 - ctv0, ctvprev - ctv0) > (ComputeType)0) + { + // is coincident to triangle edge1. + return ProcessCoincident(item->first, v0, v1, vPrev, outEdge); + } + + // Make sure we enter the next "if" statement to + // continue traversing the link. + qr1 = -1; + } + + if (qr1 < 0) + { + // is not in triangle. Visit the next + // triangle. + if (++item == link.end()) + { + return false; + } + this->GetIndices(item->first, indices); + vNext = vPrev; + vPrev = indices[(item->second + 2) % 3]; + qr1 = this->mQuery.ToLine(v1, v0, vPrev); + continue; + } + + // is interior to triangle . + return ProcessInterior(item->first, v0, v1, vNext, vPrev, outEdge); + } + break; + } + + // The edge must be contained in some link triangle. + LogError(CDTFailure()); + } + + // Process the coincident edge. + bool ProcessCoincident(int tri, int v0, int v1, int vOther, std::vector& outEdge) + { + outEdge.push_back(v0); + if (v1 != vOther) + { + // Decompose the edge and process the right-most subedge. + return Insert({ vOther, v1 }, tri, outEdge); + } + else + { + // is already in the triangulation. + outEdge.push_back(v1); + return true; + } + } + + // Process the triangle strip originating at the first endpoint of the + // edge. + bool ProcessInterior(int tri, int v0, int v1, int vNext, int vPrev, std::vector& outEdge) + { + // The triangles of the strip are stored in 'polygon'. The + // retriangulation leads to the same number of triangles, so we + // can reuse the mIndices[] and mAdjacencies[] locations implied + // by the 'polygons' indices. + std::vector polygon; + + // The sBoundary[i] (s in {l,r}) array element is ; see + // the header comments for GetAdjBoundary about what these mean. + // The boundary vertex is 'v0', the adjacent triangle 'adj' is + // outside the strip and shares the edge with a triangle in 'polygon'. This + // information allows us to connect the adjacent triangles outside + // the strip to new triangles inserted by the retriangulation. + // The value sBoundary[0][1,2] values are set to -1 but they are + // not used in the construction. + std::vector> lBoundary, rBoundary; + std::array binfo; + + polygon.push_back(tri); + + lBoundary.push_back({ v0, -1 }); + binfo = GetAdjBoundary(tri, vPrev, vPrev); + LogAssert(binfo[0] != 2, CDTFailure()); + lBoundary.push_back(binfo); + + rBoundary.push_back({ v0, -1 }); + binfo = GetAdjBoundary(tri, vNext, v0); + LogAssert(binfo[0] != 2, CDTFailure()); + rBoundary.push_back(binfo); + + // Visit the triangles in the strip. Guard against an infinite + // loop. + for (int i = 0; i < this->mNumTriangles; ++i) + { + // Find the vertex of the adjacent triangle that is opposite + // the edge shared with the current triangle. + auto iinfo = GetAdjInterior(tri, vNext, vPrev); + int adj = iinfo[0], vOpposite = iinfo[1]; + LogAssert(vOpposite >= 0, CDTFailure()); + + // Visit the adjacent triangle and insert it into the polygon. + tri = adj; + polygon.push_back(tri); + + int qr = this->mQuery.ToLine(vOpposite, v0, v1); + if (qr == 0) + { + // We have encountered a vertex that terminates the + // triangle strip. Retriangulate the polygon. If the + // edge continues through vOpposite, decompose the edge + // and insert the right-most subedge. + binfo = GetAdjBoundary(tri, vOpposite, vOpposite); + LogAssert(binfo[0] != 2, CDTFailure()); + lBoundary.push_back(binfo); + + binfo = GetAdjBoundary(tri, vOpposite, vNext); + LogAssert(binfo[0] != 2, CDTFailure()); + rBoundary.push_back(binfo); + + Retriangulate(polygon, lBoundary, rBoundary); + if (vOpposite != v1) + { + outEdge.push_back(v0); + return Insert({ vOpposite, v1 }, tri, outEdge); + } + else + { + return true; + } + } + + if (qr < 0) + { + binfo = GetAdjBoundary(tri, vOpposite, vOpposite); + LogAssert(binfo[0] != 2, CDTFailure()); + lBoundary.push_back(binfo); + vPrev = vOpposite; + } + else // qr > 0 + { + binfo = GetAdjBoundary(tri, vOpposite, vNext); + LogAssert(binfo[0] != 2, CDTFailure()); + rBoundary.push_back(binfo); + vNext = vOpposite; + } + } + + // The triangle strip should have been located in the loop. + LogError(CDTFailure()); + } + + // Remove the triangles in the triangle strip and retriangulate the + // left and right polygons using the empty circumcircle condition. + bool Retriangulate(std::vector& polygon, + std::vector> const& lBoundary, + std::vector> const& rBoundary) + { + int t0 = RetriangulateLRecurse(lBoundary, 0, + static_cast(lBoundary.size()) - 1, -1, polygon); + + int t1 = RetriangulateRRecurse(rBoundary, 0, + static_cast(rBoundary.size()) - 1, -1, polygon); + + int v0 = lBoundary.front()[0]; + int v1 = lBoundary.back()[0]; + bool success = Connect(t0, t1, v0, v1); + LogAssert(success, CDTFailure()); + return true; + } + + int RetriangulateLRecurse( + std::vector> const& lBoundary, + int i0, int i1, int a0, std::vector& polygon) + { + // Create triangles when recursing down, connect adjacent + // triangles when returning. + + int v0 = lBoundary[i0][0]; + int v1 = lBoundary[i1][0]; + + bool success; + if (i1 - i0 == 1) + { + success = Connect(a0, lBoundary[i1][1], v1, v0); + LogAssert(success, CDTFailure()); + return -1; // No triangle created. + } + else + { + // Select i2 in [i0+1,i1-1] for minimum distance to edge + // . + int i2 = SelectSplit(lBoundary, i0, i1); + int v2 = lBoundary[i2][0]; + + // Reuse a triangle and fill in its new vertices. + int tri = polygon.back(); + polygon.pop_back(); + this->mIndices[3 * tri + 0] = v0; + this->mIndices[3 * tri + 1] = v1; + this->mIndices[3 * tri + 2] = v2; + + // Recurse downward and create triangles. + int ret0 = RetriangulateLRecurse(lBoundary, i0, i2, tri, polygon); + LogAssert(ret0 >= -1, CDTFailure()); + int ret1 = RetriangulateLRecurse(lBoundary, i2, i1, tri, polygon); + LogAssert(ret1 >= -1, CDTFailure()); + + // Return and connect triangles. + success = Connect(a0, tri, v1, v0); + LogAssert(success, CDTFailure()) + return tri; + } + } + + int RetriangulateRRecurse( + std::vector> const& rBoundary, + int i0, int i1, int a0, std::vector& polygon) + { + // Create triangles when recursing down, connect adjacent + // triangles when returning. + + int v0 = rBoundary[i0][0]; + int v1 = rBoundary[i1][0]; + + if (i1 - i0 == 1) + { + bool success = Connect(a0, rBoundary[i1][1], v0, v1); + LogAssert(success, CDTFailure()); + return -1; // No triangle created. + } + else + { + // Select i2 in [i0+1,i1-1] for minimum distance to edge + // . + int i2 = SelectSplit(rBoundary, i0, i1); + int v2 = rBoundary[i2][0]; + + // Reuse a triangle and fill in its new vertices. + int tri = polygon.back(); + polygon.pop_back(); + this->mIndices[3 * tri + 0] = v1; + this->mIndices[3 * tri + 1] = v0; + this->mIndices[3 * tri + 2] = v2; + + // Recurse downward and create triangles. + int ret0 = RetriangulateRRecurse(rBoundary, i0, i2, tri, polygon); + LogAssert(ret0 >= -1, CDTFailure()); + int ret1 = RetriangulateRRecurse(rBoundary, i2, i1, tri, polygon); + LogAssert(ret1 >= -1, CDTFailure()); + + // Return and connect triangles. + bool success = Connect(a0, tri, v0, v1); + LogAssert(success, CDTFailure()); + return tri; + } + } + + int SelectSplit(std::vector> const& boundary, int i0, int i1) const + { + int i2; + if (i1 - i0 == 2) + { + // This is the only candidate. + i2 = i0 + 1; + } + else // i1 - i0 > 2 + { + // Select the index i2 in [i0+1,i1-1] for which the distance + // from the vertex v2 at i2 to the edge is minimized. + // To allow exact arithmetic, use a pseudosquared distance + // that avoids divisions and square roots. + i2 = i0 + 1; + int v0 = boundary[i0][0]; + int v1 = boundary[i1][0]; + int v2 = boundary[i2][0]; + ComputeType minpsd = ComputePSD(v0, v1, v2); + for (int i = i2 + 1; i < i1; ++i) + { + v2 = boundary[i][0]; + ComputeType psd = ComputePSD(v0, v1, v2); + if (psd < minpsd) + { + minpsd = psd; + i2 = i; + } + } + } + return i2; + } + + // Compute a pseudosquared distance from the vertex at v2 to the edge + // . + ComputeType ComputePSD(int v0, int v1, int v2) const + { + ComputeType psd; + + Vector2 const& ctv0 = this->mComputeVertices[v0]; + Vector2 const& ctv1 = this->mComputeVertices[v1]; + Vector2 const& ctv2 = this->mComputeVertices[v2]; + + Vector2 V1mV0 = ctv1 - ctv0; + Vector2 V2mV0 = ctv2 - ctv0; + ComputeType sqrlen10 = Dot(V1mV0, V1mV0); + ComputeType dot = Dot(V1mV0, V2mV0); + ComputeType zero(0); + + if (dot <= zero) + { + ComputeType sqrlen20 = Dot(V2mV0, V2mV0); + psd = sqrlen10 * sqrlen20; + } + else + { + Vector2 V2mV1 = ctv2 - ctv1; + dot = Dot(V1mV0, V2mV1); + if (dot >= zero) + { + ComputeType sqrlen21 = Dot(V2mV1, V2mV1); + psd = sqrlen10 * sqrlen21; + } + else + { + dot = DotPerp(V2mV0, V1mV0); + psd = sqrlen10 * dot * dot; + } + } + + return psd; + } + + // Search the triangulation for a triangle that contains the specified + // vertex. + int GetLinkTriangle(int v) const + { + // Remap in case an edge vertex was specified that is a duplicate. + v = this->mDuplicates[v]; + + int tri = 0; + for (int i = 0; i < this->mNumTriangles; ++i) + { + // Test whether v is a vertex of the triangle. + std::array indices; + bool success = this->GetIndices(tri, indices); + LogAssert(success, CDTFailure()); + for (int j = 0; j < 3; ++j) + { + if (v == indices[j]) + { + return tri; + } + } + + // v must be outside the triangle. + for (int j0 = 2, j1 = 0; j1 < 3; j0 = j1++) + { + if (this->mQuery.ToLine(v, indices[j0], indices[j1]) > 0) + { + // Vertex v sees the edge from outside, so traverse to + // the triangle sharing the edge. + std::array adjacencies; + success = this->GetAdjacencies(tri, adjacencies); + LogAssert(success, CDTFailure()); + int adj = adjacencies[j0]; + LogAssert(adj >= 0, CDTFailure()); + tri = adj; + break; + } + } + } + + // The vertex must be in the triangulation. + LogError(CDTFailure()); + } + + // Determine the index in {0,1,2} for the triangle 'tri' that contains + // the vertex 'v'. + int GetIndexOfVertex(int tri, int v) const + { + std::array indices; + bool success = this->GetIndices(tri, indices); + LogAssert(success, CDTFailure()); + int indexOfV; + for (indexOfV = 0; indexOfV < 3; ++indexOfV) + { + if (v == indices[indexOfV]) + { + return indexOfV; + } + } + + LogError(CDTFailure()); + } + + // Given a triangle 'tri' with CCW-edge , return where + // 'adj' is the index of the triangle adjacent to 'tri' that shares + // the edge and 'v2' is the vertex of the adjacent triangle opposite + // the edge. This function supports traversing a triangle strip that + // contains a constraint edge, so it is called only when an adjacent + // triangle actually exists. + std::array GetAdjInterior(int tri, int v0, int v1) const + { + int vIndex = GetIndexOfVertex(tri, v0); + LogAssert(vIndex >= 0, CDTFailure()); + int adj = this->mAdjacencies[3 * tri + vIndex]; + if (adj >= 0) + { + for (int v2Index = 0; v2Index < 3; ++v2Index) + { + int v2 = this->mIndices[3 * adj + v2Index]; + if (v2 != v0 && v2 != v1) + { + return{ adj, v2 }; + } + } + } + else + { + return{ -1, -1 }; + } + + LogError(CDTFailure()); + } + + // Given a triangle 'tri' of the triangle strip, the boundary edge + // must contain the vertex with index 'needBndVertex'. The input + // 'needAdjVIndex' specifies where to look for the index of the + // triangle outside the strip but adjacent to the boundary edge. The + // return value is and is used to connect 'tri' + // and 'adj' across a triangle strip boundary. + std::array GetAdjBoundary(int tri, int needBndVertex, int needAdjVIndex) const + { + int vIndex = GetIndexOfVertex(tri, needAdjVIndex); + LogAssert(vIndex >= 0, CDTFailure()); + int adj = this->mAdjacencies[3 * tri + vIndex]; + return{ needBndVertex, adj }; + } + + // Set the indices and adjacencies arrays so that 'tri' and 'adj' + // share the common edge; 'tri' has CCW-edge and 'adj' has + // CCW-edge . + bool Connect(int tri, int adj, int v0, int v1) + { + if (tri >= 0) + { + int v0Index = GetIndexOfVertex(tri, v0); + LogAssert(v0Index >= 0, CDTFailure()); + if (adj >= 0) + { + int v1Index = GetIndexOfVertex(adj, v1); + LogAssert(v1Index >= 0, CDTFailure()); + this->mAdjacencies[3 * adj + v1Index] = tri; + } + + this->mAdjacencies[3 * tri + v0Index] = adj; + } + // else: tri = -1, which occurs in the top-level call to + // retriangulate + return true; + } + + // Create an ordered list of triangles forming the link of a vertex. + // The pair of the list is . This + // allows us to cache the index of v relative to each triangle in the + // link. The vertex v might be a boundary vertex, in which case the + // neighborhood is open; otherwise, v is an interior vertex and the + // neighborhood is closed. The 'isOpen' parameter specifies the case. + bool BuildLink(int v, int vTriangle, std::list>& link, bool& isOpen) const + { + // The list starts with a known triangle in the link of v. + int vStartIndex = GetIndexOfVertex(vTriangle, v); + LogAssert(vStartIndex >= 0, CDTFailure()); + link.push_front(std::make_pair(vTriangle, vStartIndex)); + + // Traverse adjacent triangles to the "left" of v. Guard against + // an infinite loop. + int tri = vTriangle, vIndex = vStartIndex; + std::array adjacencies; + for (int i = 0; i < this->mNumTriangles; ++i) + { + bool success = this->GetAdjacencies(tri, adjacencies); + LogAssert(success, CDTFailure()); + int adjPrev = adjacencies[(vIndex + 2) % 3]; + if (adjPrev >= 0) + { + if (adjPrev != vTriangle) + { + tri = adjPrev; + vIndex = GetIndexOfVertex(tri, v); + LogAssert(vIndex >= 0, CDTFailure()); + link.push_back(std::make_pair(tri, vIndex)); + } + else + { + // We have reached the starting triangle, so v is an + // interior vertex. + isOpen = false; + return true; + } + } + else + { + // We have reached a triangle with boundary edge, so v is + // a boundary vertex. We mush find more triangles by + // searching to the "right" of v. Guard against an + // infinite loop. + isOpen = true; + tri = vTriangle; + vIndex = vStartIndex; + for (int j = 0; j < this->mNumTriangles; ++j) + { + this->GetAdjacencies(tri, adjacencies); + int adjNext = adjacencies[vIndex]; + if (adjNext >= 0) + { + tri = adjNext; + vIndex = GetIndexOfVertex(tri, v); + LogAssert(vIndex >= 0, CDTFailure()); + link.push_front(std::make_pair(tri, vIndex)); + } + else + { + // We have reached the other boundary edge that + // shares v. + return true; + } + } + break; + } + } + + LogError(CDTFailure()); + } + + static std::string const& CDTFailure() + { + static std::string message = "Unexpected condition. Caused by floating-point rounding error?"; + return message; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContAlignedBox.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContAlignedBox.h new file mode 100644 index 0000000..909a7a4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContAlignedBox.h @@ -0,0 +1,50 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + // Compute the minimum size aligned bounding box of the points. The + // extreme values are the minima and maxima of the point coordinates. + template + bool GetContainer(int numPoints, Vector const* points, AlignedBox& box) + { + return ComputeExtremes(numPoints, points, box.min, box.max); + } + + // Test for containment. + template + bool InContainer(Vector const& point, AlignedBox const& box) + { + for (int i = 0; i < N; ++i) + { + Real value = point[i]; + if (value < box.min[i] || value > box.max[i]) + { + return false; + } + } + return true; + } + + // Construct an aligned box that contains two other aligned boxes. The + // result is the minimum size box containing the input boxes. + template + bool MergeContainers(AlignedBox const& box0, + AlignedBox const& box1, AlignedBox& merge) + { + for (int i = 0; i < N; ++i) + { + merge.min[i] = std::min(box0.min[i], box1.min[i]); + merge.max[i] = std::max(box0.max[i], box1.max[i]); + } + return true; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContCapsule3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContCapsule3.h new file mode 100644 index 0000000..3c546a1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContCapsule3.h @@ -0,0 +1,269 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + // Compute the axis of the capsule segment using least-squares fitting. + // The radius is the maximum distance from the points to the axis. + // Hemispherical caps are chosen as close together as possible. + template + bool GetContainer(int numPoints, Vector3 const* points, Capsule3& capsule) + { + ApprOrthogonalLine3 fitter; + fitter.Fit(numPoints, points); + Line3 line = fitter.GetParameters(); + + DCPQuery, Line3> plQuery; + Real maxRadiusSqr = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + auto result = plQuery(points[i], line); + if (result.sqrDistance > maxRadiusSqr) + { + maxRadiusSqr = result.sqrDistance; + } + } + + Vector3 basis[3]; + basis[0] = line.direction; + ComputeOrthogonalComplement(1, basis); + + Real minValue = std::numeric_limits::max(); + Real maxValue = -std::numeric_limits::max(); + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - line.origin; + Real uDotDiff = Dot(diff, basis[1]); + Real vDotDiff = Dot(diff, basis[2]); + Real wDotDiff = Dot(diff, basis[0]); + Real discr = maxRadiusSqr - (uDotDiff * uDotDiff + vDotDiff * vDotDiff); + Real radical = std::sqrt(std::max(discr, (Real)0)); + + Real test = wDotDiff + radical; + if (test < minValue) + { + minValue = test; + } + + test = wDotDiff - radical; + if (test > maxValue) + { + maxValue = test; + } + } + + Vector3 center = line.origin + ((Real)0.5 * (minValue + maxValue)) * line.direction; + + Real extent; + if (maxValue > minValue) + { + // Container is a capsule. + extent = (Real)0.5 * (maxValue - minValue); + } + else + { + // Container is a sphere. + extent = (Real)0; + } + + capsule.segment = Segment3(center, line.direction, extent); + capsule.radius = std::sqrt(maxRadiusSqr); + return true; + } + + // Test for containment of a point by a capsule. + template + bool InContainer(Vector3 const& point, Capsule3 const& capsule) + { + DCPQuery, Segment3> psQuery; + auto result = psQuery(point, capsule.segment); + return result.distance <= capsule.radius; + } + + // Test for containment of a sphere by a capsule. + template + bool InContainer(Sphere3 const& sphere, Capsule3 const& capsule) + { + Real rDiff = capsule.radius - sphere.radius; + if (rDiff >= (Real)0) + { + DCPQuery, Segment3> psQuery; + auto result = psQuery(sphere.center, capsule.segment); + return result.distance <= rDiff; + } + return false; + } + + // Test for containment of a capsule by a capsule. + template + bool InContainer(Capsule3 const& testCapsule, Capsule3 const& capsule) + { + Sphere3 spherePosEnd(testCapsule.segment.p[1], testCapsule.radius); + Sphere3 sphereNegEnd(testCapsule.segment.p[0], testCapsule.radius); + return InContainer(spherePosEnd, capsule) + && InContainer(sphereNegEnd, capsule); + } + + // Compute a capsule that contains the input capsules. The returned + // capsule is not necessarily the one of smallest volume that contains + // the inputs. + template + bool MergeContainers(Capsule3 const& capsule0, + Capsule3 const& capsule1, Capsule3& merge) + { + if (InContainer(capsule0, capsule1)) + { + merge = capsule1; + return true; + } + + if (InContainer(capsule1, capsule0)) + { + merge = capsule0; + return true; + } + + Vector3 P0, P1, D0, D1; + Real extent0, extent1; + capsule0.segment.GetCenteredForm(P0, D0, extent0); + capsule1.segment.GetCenteredForm(P1, D1, extent1); + + // Axis of final capsule. + Line3 line; + + // Axis center is average of input axis centers. + line.origin = (Real)0.5 * (P0 + P1); + + // Axis unit direction is average of input axis unit directions. + if (Dot(D0, D1) >= (Real)0) + { + line.direction = D0 + D1; + } + else + { + line.direction = D0 - D1; + } + Normalize(line.direction); + + // Cylinder with axis 'line' must contain the spheres centered at the + // endpoints of the input capsules. + DCPQuery, Line3> plQuery; + Vector3 posEnd0 = capsule0.segment.p[1]; + Real radius = plQuery(posEnd0, line).distance + capsule0.radius; + + Vector3 negEnd0 = capsule0.segment.p[0]; + Real tmp = plQuery(negEnd0, line).distance + capsule0.radius; + + Vector3 posEnd1 = capsule1.segment.p[1]; + tmp = plQuery(posEnd1, line).distance + capsule1.radius; + if (tmp > radius) + { + radius = tmp; + } + + Vector3 negEnd1 = capsule1.segment.p[0]; + tmp = plQuery(negEnd1, line).distance + capsule1.radius; + if (tmp > radius) + { + radius = tmp; + } + + // In the following blocks of code, theoretically k1*k1-k0 >= 0, but + // numerical rounding errors can make it slightly negative. Guard + // against this. + + // Process sphere . + Real rDiff = radius - capsule0.radius; + Real rDiffSqr = rDiff * rDiff; + Vector3 diff = line.origin - posEnd0; + Real k0 = Dot(diff, diff) - rDiffSqr; + Real k1 = Dot(diff, line.direction); + Real discr = k1 * k1 - k0; + Real root = std::sqrt(std::max(discr, (Real)0)); + Real tPos = -k1 - root; + Real tNeg = -k1 + root; + + // Process sphere . + diff = line.origin - negEnd0; + k0 = Dot(diff, diff) - rDiffSqr; + k1 = Dot(diff, line.direction); + discr = k1 * k1 - k0; + root = std::sqrt(std::max(discr, (Real)0)); + tmp = -k1 - root; + if (tmp > tPos) + { + tPos = tmp; + } + tmp = -k1 + root; + if (tmp < tNeg) + { + tNeg = tmp; + } + + // Process sphere . + rDiff = radius - capsule1.radius; + rDiffSqr = rDiff * rDiff; + diff = line.origin - posEnd1; + k0 = Dot(diff, diff) - rDiffSqr; + k1 = Dot(diff, line.direction); + discr = k1 * k1 - k0; + root = std::sqrt(std::max(discr, (Real)0)); + tmp = -k1 - root; + if (tmp > tPos) + { + tPos = tmp; + } + tmp = -k1 + root; + if (tmp < tNeg) + { + tNeg = tmp; + } + + // Process sphere . + diff = line.origin - negEnd1; + k0 = Dot(diff, diff) - rDiffSqr; + k1 = Dot(diff, line.direction); + discr = k1 * k1 - k0; + root = std::sqrt(std::max(discr, (Real)0)); + tmp = -k1 - root; + if (tmp > tPos) + { + tPos = tmp; + } + tmp = -k1 + root; + if (tmp < tNeg) + { + tNeg = tmp; + } + + Vector3 center = line.origin + (Real)0.5 * (tPos + tNeg) * line.direction; + + Real extent; + if (tPos > tNeg) + { + // Container is a capsule. + extent = (Real)0.5 * (tPos - tNeg); + } + else + { + // Container is a sphere. + extent = (Real)0; + } + + merge.segment = Segment3(center, line.direction, extent); + merge.radius = radius; + return true; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContCircle2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContCircle2.h new file mode 100644 index 0000000..b4a2ab1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContCircle2.h @@ -0,0 +1,88 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + // Compute the smallest bounding circle whose center is the average of + // the input points. + template + bool GetContainer(int numPoints, Vector2 const* points, Circle2& circle) + { + circle.center = points[0]; + for (int i = 1; i < numPoints; ++i) + { + circle.center += points[i]; + } + circle.center /= (Real)numPoints; + + circle.radius = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector2 diff = points[i] - circle.center; + Real radiusSqr = Dot(diff, diff); + if (radiusSqr > circle.radius) + { + circle.radius = radiusSqr; + } + } + + circle.radius = std::sqrt(circle.radius); + return true; + } + + template + bool GetContainer(std::vector> const& points, Circle2& circle) + { + return GetContainer(static_cast(points.size()), points.data(), circle); + } + + // Test for containment of a point inside a circle. + template + bool InContainer(Vector2 const& point, Circle2 const& circle) + { + Vector2 diff = point - circle.center; + return Length(diff) <= circle.radius; + } + + // Compute the smallest bounding circle that contains the input circles. + template + bool MergeContainers(Circle2 const& circle0, Circle2 const& circle1, Circle2& merge) + { + Vector2 cenDiff = circle1.center - circle0.center; + Real lenSqr = Dot(cenDiff, cenDiff); + Real rDiff = circle1.radius - circle0.radius; + Real rDiffSqr = rDiff * rDiff; + + if (rDiffSqr >= lenSqr) + { + merge = (rDiff >= (Real)0 ? circle1 : circle0); + } + else + { + Real length = std::sqrt(lenSqr); + if (length > (Real)0) + { + Real coeff = (length + rDiff) / (((Real)2)*length); + merge.center = circle0.center + coeff * cenDiff; + } + else + { + merge.center = circle0.center; + } + + merge.radius = (Real)0.5 * (length + circle0.radius + circle1.radius); + } + + return true; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContCone.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContCone.h new file mode 100644 index 0000000..92fe8bf --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContCone.h @@ -0,0 +1,22 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.29 + +#pragma once + +#include + +namespace WwiseGTE +{ + // Test for containment of a point by a cone. + template + bool InContainer(Vector const& point, Cone const& cone) + { + Vector diff = point - cone.ray.origin; + Real h = Dot(cone.ray.direction, diff); + return cone.HeightInRange(h) && h * h >= cone.cosAngleSqr * Dot(diff, diff); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContCylinder3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContCylinder3.h new file mode 100644 index 0000000..355761a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContCylinder3.h @@ -0,0 +1,76 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + // Compute the cylinder axis segment using least-squares fit. The radius + // is the maximum distance from points to the axis. The height is + // determined by projection of points onto the axis and determining the + // containing interval. + template + bool GetContainer(int numPoints, Vector3 const* points, Cylinder3& cylinder) + { + ApprOrthogonalLine3 fitter; + fitter.Fit(numPoints, points); + Line3 line = fitter.GetParameters(); + + DCPQuery, Line3> plQuery; + Real maxRadiusSqr = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + auto result = plQuery(points[i], line); + if (result.sqrDistance > maxRadiusSqr) + { + maxRadiusSqr = result.sqrDistance; + } + } + + Vector3 diff = points[0] - line.origin; + Real wMin = Dot(line.direction, diff); + Real wMax = wMin; + for (int i = 1; i < numPoints; ++i) + { + diff = points[i] - line.origin; + Real w = Dot(line.direction, diff); + if (w < wMin) + { + wMin = w; + } + else if (w > wMax) + { + wMax = w; + } + } + + cylinder.axis.origin = line.origin + ((Real)0.5 * (wMax + wMin)) * line.direction; + cylinder.axis.direction = line.direction; + cylinder.radius = std::sqrt(maxRadiusSqr); + cylinder.height = wMax - wMin; + return true; + } + + // Test for containment of a point by a cylinder. + template + bool InContainer(Vector3 const& point, Cylinder3 const& cylinder) + { + Vector3 diff = point - cylinder.axis.origin; + Real zProj = Dot(diff, cylinder.axis.direction); + if (std::fabs(zProj) * (Real)2 > cylinder.height) + { + return false; + } + + Vector3 xyProj = diff - zProj * cylinder.axis.direction; + return Length(xyProj) <= cylinder.radius; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContEllipse2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContEllipse2.h new file mode 100644 index 0000000..e9f9d35 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContEllipse2.h @@ -0,0 +1,149 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + // The input points are fit with a Gaussian distribution. The center C of + // the ellipse is chosen to be the mean of the distribution. The axes of + // the ellipse are chosen to be the eigenvectors of the covariance matrix + // M. The shape of the ellipse is determined by the absolute values of + // the eigenvalues. NOTE: The construction is ill-conditioned if the + // points are (nearly) collinear. In this case M has a (nearly) zero + // eigenvalue, so inverting M can be a problem numerically. + template + bool GetContainer(int numPoints, Vector2 const* points, Ellipse2& ellipse) + { + // Fit the points with a Gaussian distribution. The covariance matrix + // is M = sum_j D[j]*U[j]*U[j]^T, where D[j] are the eigenvalues and + // U[j] are corresponding unit-length eigenvectors. + ApprGaussian2 fitter; + if (fitter.Fit(numPoints, points)) + { + OrientedBox2 box = fitter.GetParameters(); + + // If either eigenvalue is nonpositive, adjust the D[] values so + // that we actually build an ellipse. + for (int j = 0; j < 2; ++j) + { + if (box.extent[j] < (Real)0) + { + box.extent[j] = -box.extent[j]; + } + } + + // Grow the ellipse, while retaining its shape determined by the + // covariance matrix, to enclose all the input points. The + // quadratic form that is used for the ellipse construction is + // Q(X) = (X-C)^T*M*(X-C) + // = (X-C)^T*(sum_j D[j]*U[j]*U[j]^T)*(X-C) + // = sum_j D[j]*Dot(U[j],X-C)^2 + // If the maximum value of Q(X[i]) for all input points is V^2, + // then a bounding ellipse is Q(X) = V^2, because Q(X[i]) <= V^2 + // for all i. + + Real maxValue = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector2 diff = points[i] - box.center; + Real dot[2] = + { + Dot(box.axis[0], diff), + Dot(box.axis[1], diff) + }; + + Real value = + box.extent[0] * dot[0] * dot[0] + + box.extent[1] * dot[1] * dot[1]; + + if (value > maxValue) + { + maxValue = value; + } + } + + // Arrange for the quadratic to satisfy Q(X) <= 1. + ellipse.center = box.center; + for (int j = 0; j < 2; ++j) + { + ellipse.axis[j] = box.axis[j]; + ellipse.extent[j] = std::sqrt(maxValue / box.extent[j]); + } + return true; + + } + + return false; + } + + // Test for containment of a point inside an ellipse. + template + bool InContainer(Vector2 const& point, Ellipse2 const& ellipse) + { + Vector2 diff = point - ellipse.center; + Vector2 standardized{ + Dot(diff, ellipse.axis[0]) / ellipse.extent[0], + Dot(diff, ellipse.axis[1]) / ellipse.extent[1] }; + return Length(standardized) <= (Real)1; + } + + // Construct a bounding ellipse for the two input ellipses. The result is + // not necessarily the minimum-area ellipse containing the two ellipses. + template + bool MergeContainers(Ellipse2 const& ellipse0, + Ellipse2 const& ellipse1, Ellipse2& merge) + { + // Compute the average of the input centers. + merge.center = (Real)0.5 * (ellipse0.center + ellipse1.center); + + // The bounding ellipse orientation is the average of the input + // orientations. + if (Dot(ellipse0.axis[0], ellipse1.axis[0]) >= (Real)0) + { + merge.axis[0] = (Real)0.5 * (ellipse0.axis[0] + ellipse1.axis[0]); + } + else + { + merge.axis[0] = (Real)0.5 * (ellipse0.axis[0] - ellipse1.axis[0]); + } + Normalize(merge.axis[0]); + merge.axis[1] = -Perp(merge.axis[0]); + + // Project the input ellipses onto the axes obtained by the average + // of the orientations and that go through the center obtained by the + // average of the centers. + for (int j = 0; j < 2; ++j) + { + // Projection axis. + Line2 line(merge.center, merge.axis[j]); + + // Project ellipsoids onto the axis. + Real min0, max0, min1, max1; + Project(ellipse0, line, min0, max0); + Project(ellipse1, line, min1, max1); + + // Determine the smallest interval containing the projected + // intervals. + Real maxIntr = (max0 >= max1 ? max0 : max1); + Real minIntr = (min0 <= min1 ? min0 : min1); + + // Update the average center to be the center of the bounding box + // defined by the projected intervals. + merge.center += line.direction * ((Real)0.5 * (minIntr + maxIntr)); + + // Compute the extents of the box based on the new center. + merge.extent[j] = (Real)0.5 * (maxIntr - minIntr); + } + + return true; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContEllipse2MinCR.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContEllipse2MinCR.h new file mode 100644 index 0000000..140f4ce --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContEllipse2MinCR.h @@ -0,0 +1,225 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// Compute the minimum-area ellipse, (X-C)^T R D R^T (X-C) = 1, given the +// center C and the orientation matrix R. The columns of R are the axes of +// the ellipse. The algorithm computes the diagonal matrix D. The minimum +// area is pi/sqrt(D[0]*D[1]), where D = diag(D[0],D[1]). The problem is +// equivalent to maximizing the product D[0]*D[1] for a given C and R, and +// subject to the constraints +// (P[i]-C)^T R D R^T (P[i]-C) <= 1 +// for all input points P[i] with 0 <= i < N. Each constraint has the form +// A[0]*D[0] + A[1]*D[1] <= 1 +// where A[0] >= 0 and A[1] >= 0. + +namespace WwiseGTE +{ + template + class ContEllipse2MinCR + { + public: + void operator()(int numPoints, Vector2 const* points, + Vector2 const& C, Matrix2x2 const& R, Real D[2]) const + { + // Compute the constraint coefficients, of the form (A[0],A[1]) + // for each i. + std::vector> A(numPoints); + for (int i = 0; i < numPoints; ++i) + { + Vector2 diff = points[i] - C; // P[i] - C + Vector2 prod = diff * R; // R^T*(P[i] - C) = (u,v) + A[i] = prod * prod; // (u^2, v^2) + } + + // Use a lexicographical sort to eliminate redundant constraints. + // Remove all but the first entry in blocks with x0 = x1 because + // the corresponding constraint lines for the first entry hides + // all the others from the origin. + std::sort(A.begin(), A.end(), + [](Vector2 const& P0, Vector2 const& P1) + { + if (P0[0] > P1[0]) { return true; } + if (P0[0] < P1[0]) { return false; } + return P0[1] > P1[1]; + } + ); + auto end = std::unique(A.begin(), A.end(), + [](Vector2 const& P0, Vector2 const& P1) + { + return P0[0] == P1[0]; + } + ); + A.erase(end, A.end()); + + // Use a lexicographical sort to eliminate redundant constraints. + // Remove all but the first entry in blocks/ with y0 = y1 because + // the corresponding constraint lines for the first entry hides + // all the others from the origin. + std::sort(A.begin(), A.end(), + [](Vector2 const& P0, Vector2 const& P1) + { + if (P0[1] > P1[1]) + { + return true; + } + + if (P0[1] < P1[1]) + { + return false; + } + + return P0[0] > P1[0]; + } + ); + end = std::unique(A.begin(), A.end(), + [](Vector2 const& P0, Vector2 const& P1) + { + return P0[1] == P1[1]; + } + ); + A.erase(end, A.end()); + + MaxProduct(A, D); + } + + private: + static void MaxProduct(std::vector>& A, Real D[2]) + { + // Keep track of which constraint lines have already been used in + // the search. + int numConstraints = static_cast(A.size()); + std::vector used(A.size()); + std::fill(used.begin(), used.end(), false); + + // Find the constraint line whose y-intercept (0,ymin) is closest + // to the origin. This line contributes to the convex hull of the + // constraints and the search for the maximum starts here. Also + // find the constraint line whose x-intercept (xmin,0) is closest + // to the origin. This line contributes to the convex hull of the + // constraints and the search for the maximum terminates before or + // at this line. + int i, iYMin = -1; + int iXMin = -1; + Real axMax = (Real)0, ayMax = (Real)0; // A[i] >= (0,0) by design + for (i = 0; i < numConstraints; ++i) + { + // The minimum x-intercept is 1/A[iXMin][0] for A[iXMin][0] + // the maximum of the A[i][0]. + if (A[i][0] > axMax) + { + axMax = A[i][0]; + iXMin = i; + } + + // The minimum y-intercept is 1/A[iYMin][1] for A[iYMin][1] + // the maximum of the A[i][1]. + if (A[i][1] > ayMax) + { + ayMax = A[i][1]; + iYMin = i; + } + } + LogAssert(iXMin != -1 && iYMin != -1, "Unexpected condition."); + used[iYMin] = true; + + // The convex hull is searched in a clockwise manner starting with + // the constraint line constructed above. The next vertex of the + // hull occurs as the closest point to the first vertex on the + // current constraint line. The following loop finds each + // consecutive vertex. + Real x0 = (Real)0, xMax = ((Real)1) / axMax; + int j; + for (j = 0; j < numConstraints; ++j) + { + // Find the line whose intersection with the current line is + // closest to the last hull vertex. The last vertex is at + // (x0,y0) on the current line. + Real x1 = xMax; + int line = -1; + for (i = 0; i < numConstraints; ++i) + { + if (!used[i]) + { + // This line not yet visited, process it. Given + // current constraint line a0*x+b0*y =1 and candidate + // line a1*x+b1*y = 1, find the point of intersection. + // The determinant of the system is d = a0*b1-a1*b0. + // We care only about lines that have more negative + // slope than the previous one, that is, + // -a1/b1 < -a0/b0, in which case we process only + // lines for which d < 0. + Real det = DotPerp(A[iYMin], A[i]); + if (det < (Real)0) + { + // Compute the x-value for the point of + // intersection, (x1,y1). There may be floating + // point error issues in the comparision + // 'D[0] <= x1'. Consider modifying to + // 'D[0] <= x1 + epsilon'. + D[0] = (A[i][1] - A[iYMin][1]) / det; + if (x0 < D[0] && D[0] <= x1) + { + line = i; + x1 = D[0]; + } + } + } + } + + // Next vertex is at (x1,y1) whose x-value was computed above. + // First check for the maximum of x*y on the current line for + // x in [x0,x1]. On this interval the function is + // f(x) = x*(1-a0*x)/b0. The derivative is + // f'(x) = (1-2*a0*x)/b0 and f'(r) = 0 when r = 1/(2*a0). + // The three candidates for the maximum are f(x0), f(r) and + // f(x1). Comparisons are made between r and the endpoints + // x0 and x1. Because a0 = 0 is possible (constraint line is + // horizontal and f is increasing on line), the division in r + // is not performed and the comparisons are made between + // 1/2 = a0*r and a0*x0 or a0*x1. + + // Compare r < x0. + if ((Real)0.5 < A[iYMin][0] * x0) + { + // The maximum is f(x0) since the quadratic f decreases + // for x > r. The value D[1] is f(x0). + D[0] = x0; + D[1] = ((Real)1 - A[iYMin][0] * D[0]) / A[iYMin][1]; + break; + } + + // Compare r < x1. + if ((Real)0.5 < A[iYMin][0] * x1) + { + // The maximum is f(r). The search ends here because the + // current line is tangent to the level curve of + // f(x) = f(r) and x*y can therefore only decrease as we + // traverse farther around the hull in the clockwise + // direction. The value D[1] is f(r). + D[0] = (Real)0.5 / A[iYMin][0]; + D[1] = (Real)0.5 / A[iYMin][1]; + break; + } + + // The maximum is f(x1). The function x*y is potentially + // larger on the next line, so continue the search. + LogAssert(line != -1, "Unexpected condition."); + x0 = x1; + x1 = xMax; + used[line] = true; + iYMin = line; + } + + LogAssert(j < numConstraints, "Unexpected condition."); + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContEllipsoid3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContEllipsoid3.h new file mode 100644 index 0000000..da42af2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContEllipsoid3.h @@ -0,0 +1,164 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + // The input points are fit with a Gaussian distribution. The center C of + // the ellipsoid is chosen to be the mean of the distribution. The axes + // of the ellipsoid are chosen to be the eigenvectors of the covariance + // matrix M. The shape of the ellipsoid is determined by the absolute + // values of the eigenvalues. NOTE: The construction is ill-conditioned + // if the points are (nearly) collinear or (nearly) planar. In this case + // M has a (nearly) zero eigenvalue, so inverting M is problematic. + template + bool GetContainer(int numPoints, Vector3 const* points, Ellipsoid3& ellipsoid) + { + // Fit the points with a Gaussian distribution. The covariance + // matrix is M = sum_j D[j]*U[j]*U[j]^T, where D[j] are the + // eigenvalues and U[j] are corresponding unit-length eigenvectors. + ApprGaussian3 fitter; + if (fitter.Fit(numPoints, points)) + { + OrientedBox3 box = fitter.GetParameters(); + + // If either eigenvalue is nonpositive, adjust the D[] values so + // that we actually build an ellipsoid. + for (int j = 0; j < 3; ++j) + { + if (box.extent[j] < (Real)0) + { + box.extent[j] = -box.extent[j]; + } + } + + // Grow the ellipsoid, while retaining its shape determined by the + // covariance matrix, to enclose all the input points. The + // quadratic/ form that is used for the ellipsoid construction is + // Q(X) = (X-C)^T*M*(X-C) + // = (X-C)^T*(sum_j D[j]*U[j]*U[j]^T)*(X-C) + // = sum_j D[j]*Dot(U[j],X-C)^2 + // If the maximum value of Q(X[i]) for all input points is V^2, + // then a bounding ellipsoid is Q(X) = V^2 since Q(X[i]) <= V^2 + // for all i. + + Real maxValue = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - box.center; + Real dot[3] = + { + Dot(box.axis[0], diff), + Dot(box.axis[1], diff), + Dot(box.axis[2], diff) + }; + + Real value = + box.extent[0] * dot[0] * dot[0] + + box.extent[1] * dot[1] * dot[1] + + box.extent[2] * dot[2] * dot[2]; + + if (value > maxValue) + { + maxValue = value; + } + } + + // Arrange for the quadratic to satisfy Q(X) <= 1. + ellipsoid.center = box.center; + for (int j = 0; j < 3; ++j) + { + ellipsoid.axis[j] = box.axis[j]; + ellipsoid.extent[j] = std::sqrt(maxValue / box.extent[j]); + } + return true; + } + + return false; + } + + // Test for containment of a point inside an ellipsoid. + template + bool InContainer(Vector3 const& point, Ellipsoid3 const& ellipsoid) + { + Vector3 diff = point - ellipsoid.center; + Vector3 standardized{ + Dot(diff, ellipsoid.axis[0]) / ellipsoid.extent[0], + Dot(diff, ellipsoid.axis[1]) / ellipsoid.extent[1], + Dot(diff, ellipsoid.axis[2]) / ellipsoid.extent[2] }; + return Length(standardized) <= (Real)1; + } + + // Construct a bounding ellipsoid for the two input ellipsoids. The result is + // not necessarily the minimum-volume ellipsoid containing the two ellipsoids. + template + bool MergeContainers(Ellipsoid3 const& ellipsoid0, + Ellipsoid3 const& ellipsoid1, Ellipsoid3& merge) + { + // Compute the average of the input centers + merge.center = (Real)0.5 * (ellipsoid0.center + ellipsoid1.center); + + // The bounding ellipsoid orientation is the average of the input + // orientations. + Matrix3x3 rot0, rot1; + rot0.SetCol(0, ellipsoid0.axis[0]); + rot0.SetCol(1, ellipsoid0.axis[1]); + rot0.SetCol(2, ellipsoid0.axis[2]); + rot1.SetCol(0, ellipsoid1.axis[0]); + rot1.SetCol(1, ellipsoid1.axis[1]); + rot1.SetCol(2, ellipsoid1.axis[2]); + Quaternion q0 = Rotation<3, Real>(rot0); + Quaternion q1 = Rotation<3, Real>(rot1); + if (Dot(q0, q1) < (Real)0) + { + q1 = -q1; + } + + Quaternion q = q0 + q1; + Normalize(q); + Matrix3x3 rot = Rotation<3, Real>(q); + for (int j = 0; j < 3; ++j) + { + merge.axis[j] = rot.GetCol(j); + } + + // Project the input ellipsoids onto the axes obtained by the average + // of the orientations and that go through the center obtained by the + // average of the centers. + for (int i = 0; i < 3; ++i) + { + // Projection axis. + Line3 line(merge.center, merge.axis[i]); + + // Project ellipsoids onto the axis. + Real min0, max0, min1, max1; + Project(ellipsoid0, line, min0, max0); + Project(ellipsoid1, line, min1, max1); + + // Determine the smallest interval containing the projected + // intervals. + Real maxIntr = (max0 >= max1 ? max0 : max1); + Real minIntr = (min0 <= min1 ? min0 : min1); + + // Update the average center to be the center of the bounding box + // defined by the projected intervals. + merge.center += line.direction * ((Real)0.5 * (minIntr + maxIntr)); + + // Compute the extents of the box based on the new center. + merge.extent[i] = (Real)0.5 * (maxIntr - minIntr); + } + + return true; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContEllipsoid3MinCR.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContEllipsoid3MinCR.h new file mode 100644 index 0000000..9962763 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContEllipsoid3MinCR.h @@ -0,0 +1,298 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// Compute the minimum-volume ellipsoid, (X-C)^T R D R^T (X-C) = 1, given the +// center C and orientation matrix R. The columns of R are the axes of the +// ellipsoid. The algorithm computes the diagonal matrix D. The minimum +// volume is (4*pi/3)/sqrt(D[0]*D[1]*D[2]), where D = diag(D[0],D[1],D[2]). +// The problem is equivalent to maximizing the product D[0]*D[1]*D[2] for a +// given C and R, and subject to the constraints +// (P[i]-C)^T R D R^T (P[i]-C) <= 1 +// for all input points P[i] with 0 <= i < N. Each constraint has the form +// A[0]*D[0] + A[1]*D[1] + A[2]*D[2] <= 1 +// where A[0] >= 0, A[1] >= 0, and A[2] >= 0. + +namespace WwiseGTE +{ + template + class ContEllipsoid3MinCR + { + public: + void operator()(int numPoints, Vector3 const* points, + Vector3 const& C, Matrix3x3 const& R, Real D[3]) const + { + // Compute the constraint coefficients, of the form (A[0],A[1]) + // for each i. + std::vector> A(numPoints); + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - C; // P[i] - C + Vector3 prod = diff * R; // R^T*(P[i] - C) = (u,v,w) + A[i] = prod * prod; // (u^2, v^2, w^2) + } + + // TODO: Sort the constraints to eliminate redundant ones. It + // is clear how to do this in ContEllipse2MinCR. How to do this + // in 3D? + + MaxProduct(A, D); + } + + private: + void FindEdgeMax(std::vector>& A, int& plane0, int& plane1, Real D[3]) const + { + // Compute direction to local maximum point on line of + // intersection. + Real xDir = A[plane0][1] * A[plane1][2] - A[plane1][1] * A[plane0][2]; + Real yDir = A[plane0][2] * A[plane1][0] - A[plane1][2] * A[plane0][0]; + Real zDir = A[plane0][0] * A[plane1][1] - A[plane1][0] * A[plane0][1]; + + // Build quadratic Q'(t) = (d/dt)(x(t)y(t)z(t)) = a0+a1*t+a2*t^2. + Real a0 = D[0] * D[1] * zDir + D[0] * D[2] * yDir + D[1] * D[2] * xDir; + Real a1 = (Real)2 * (D[2] * xDir * yDir + D[1] * xDir * zDir + D[0] * yDir * zDir); + Real a2 = (Real)3 * (xDir * yDir * zDir); + + // Find root to Q'(t) = 0 corresponding to maximum. + Real tFinal; + if (a2 != (Real)0) + { + Real invA2 = (Real)1 / a2; + Real discr = a1 * a1 - (Real)4 * a0 * a2; + discr = std::sqrt(std::max(discr, (Real)0)); + tFinal = (Real)-0.5 * (a1 + discr) * invA2; + if (a1 + (Real)2 * a2 * tFinal > (Real)0) + { + tFinal = (Real)0.5 * (-a1 + discr) * invA2; + } + } + else if (a1 != (Real)0) + { + tFinal = -a0 / a1; + } + else if (a0 != (Real)0) + { + Real fmax = std::numeric_limits::max(); + tFinal = (a0 >= (Real)0 ? fmax : -fmax); + } + else + { + return; + } + + if (tFinal < (Real)0) + { + // Make (xDir,yDir,zDir) point in direction of increase of Q. + tFinal = -tFinal; + xDir = -xDir; + yDir = -yDir; + zDir = -zDir; + } + + // Sort remaining planes along line from current point to local + // maximum. + Real tMax = tFinal; + int plane2 = -1; + int numPoints = static_cast(A.size()); + for (int i = 0; i < numPoints; ++i) + { + if (i == plane0 || i == plane1) + { + continue; + } + + Real norDotDir = A[i][0] * xDir + A[i][1] * yDir + A[i][2] * zDir; + if (norDotDir <= (Real)0) + { + continue; + } + + // Theoretically the numerator must be nonnegative since an + // invariant in the algorithm is that (x0,y0,z0) is on the + // convex hull of the constraints. However, some numerical + // error may make this a small negative number. In that case + // set tmax = 0 (no change in position). + Real numer = (Real)1 - A[i][0] * D[0] - A[i][1] * D[1] - A[i][2] * D[2]; + LogAssert(numer >= (Real)0, "Unexpected condition."); + + Real t = numer / norDotDir; + if (0 <= t && t < tMax) + { + plane2 = i; + tMax = t; + } + } + + D[0] += tMax * xDir; + D[1] += tMax * yDir; + D[2] += tMax * zDir; + + if (tMax == tFinal) + { + return; + } + + if (tMax > (Real)0) + { + plane0 = plane2; + FindFacetMax(A, plane0, D); + return; + } + + // tmax == 0, so return with D[0], D[1], and D[2] unchanged. + } + + void FindFacetMax(std::vector>& A, int& plane0, Real D[3]) const + { + Real tFinal, xDir, yDir, zDir; + + if (A[plane0][0] > (Real)0 + && A[plane0][1] > (Real)0 + && A[plane0][2] > (Real)0) + { + // Compute local maximum point on plane. + Real oneThird = (Real)1 / (Real)3; + Real xMax = oneThird / A[plane0][0]; + Real yMax = oneThird / A[plane0][1]; + Real zMax = oneThird / A[plane0][2]; + + // Compute direction to local maximum point on plane. + tFinal = (Real)1; + xDir = xMax - D[0]; + yDir = yMax - D[1]; + zDir = zMax - D[2]; + } + else + { + tFinal = std::numeric_limits::max(); + + if (A[plane0][0] > (Real)0) + { + xDir = (Real)0; + } + else + { + xDir = (Real)1; + } + + if (A[plane0][1] > (Real)0) + { + yDir = (Real)0; + } + else + { + yDir = (Real)1; + } + + if (A[plane0][2] > (Real)0) + { + zDir = (Real)0; + } + else + { + zDir = (Real)1; + } + } + + // Sort remaining planes along line from current point. + Real tMax = tFinal; + int plane1 = -1; + int numPoints = static_cast(A.size()); + for (int i = 0; i < numPoints; ++i) + { + if (i == plane0) + { + continue; + } + + Real norDotDir = A[i][0] * xDir + A[i][1] * yDir + A[i][2] * zDir; + if (norDotDir <= (Real)0) + { + continue; + } + + // Theoretically the numerator must be nonnegative because an + // invariant in the algorithm is that (x0,y0,z0) is on the + // convex hull of the constraints. However, some numerical + // error may make this a small negative number. In that case, + // set tmax = 0 (no change in position). + Real numer = (Real)1 - A[i][0] * D[0] - A[i][1] * D[1] - A[i][2] * D[2]; + LogAssert(numer >= (Real)0, "Unexpected condition."); + + Real t = numer / norDotDir; + if (0 <= t && t < tMax) + { + plane1 = i; + tMax = t; + } + } + + D[0] += tMax * xDir; + D[1] += tMax * yDir; + D[2] += tMax * zDir; + + if (tMax == (Real)1) + { + return; + } + + if (tMax > (Real)0) + { + plane0 = plane1; + FindFacetMax(A, plane0, D); + return; + } + + FindEdgeMax(A, plane0, plane1, D); + } + + void MaxProduct(std::vector>& A, Real D[3]) const + { + // Maximize x*y*z subject to x >= 0, y >= 0, z >= 0, and + // A[i]*x+B[i]*y+C[i]*z <= 1 for 0 <= i < N where A[i] >= 0, + // B[i] >= 0, and C[i] >= 0. + + // Jitter the lines to avoid cases where more than three planes + // intersect at the same point. Should also break parallelism + // and planes parallel to the coordinate planes. + std::mt19937 mte; + std::uniform_real_distribution rnd((Real)0, (Real)1); + Real maxJitter = (Real)1e-12; + int numPoints = static_cast(A.size()); + int i; + for (i = 0; i < numPoints; ++i) + { + A[i][0] += maxJitter * rnd(mte); + A[i][1] += maxJitter * rnd(mte); + A[i][2] += maxJitter * rnd(mte); + } + + // Sort lines along the z-axis (x = 0 and y = 0). + int plane = -1; + Real zmax = (Real)0; + for (i = 0; i < numPoints; ++i) + { + if (A[i][2] > zmax) + { + zmax = A[i][2]; + plane = i; + } + } + LogAssert(plane != -1, "Unexpected condition."); + + // Walk along convex hull searching for maximum. + D[0] = (Real)0; + D[1] = (Real)0; + D[2] = (Real)1 / zmax; + FindFacetMax(A, plane, D); + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContLozenge3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContLozenge3.h new file mode 100644 index 0000000..7f61e55 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContLozenge3.h @@ -0,0 +1,214 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + // Compute the plane of the lozenge rectangle using least-squares fit. + // Parallel planes are chosen close enough together so that all the data + // points lie between them. The radius is half the distance between the + // two planes. The half-cylinder and quarter-cylinder side pieces are + // chosen using a method similar to that used for fitting by capsules. + template + bool GetContainer(int numPoints, Vector3 const* points, Lozenge3& lozenge) + { + ApprGaussian3 fitter; + fitter.Fit(numPoints, points); + OrientedBox3 box = fitter.GetParameters(); + + Vector3 diff = points[0] - box.center; + Real wMin = Dot(box.axis[0], diff); + Real wMax = wMin; + Real w; + for (int i = 1; i < numPoints; ++i) + { + diff = points[i] - box.center; + w = Dot(box.axis[0], diff); + if (w < wMin) + { + wMin = w; + } + else if (w > wMax) + { + wMax = w; + } + } + + Real radius = (Real)0.5 * (wMax - wMin); + Real rSqr = radius * radius; + box.center += ((Real)0.5 * (wMax + wMin)) * box.axis[0]; + + Real aMin = std::numeric_limits::max(); + Real aMax = -aMin; + Real bMin = std::numeric_limits::max(); + Real bMax = -bMin; + Real discr, radical, u, v, test; + for (int i = 0; i < numPoints; ++i) + { + diff = points[i] - box.center; + u = Dot(box.axis[2], diff); + v = Dot(box.axis[1], diff); + w = Dot(box.axis[0], diff); + discr = rSqr - w * w; + radical = std::sqrt(std::max(discr, (Real)0)); + + test = u + radical; + if (test < aMin) + { + aMin = test; + } + + test = u - radical; + if (test > aMax) + { + aMax = test; + } + + test = v + radical; + if (test < bMin) + { + bMin = test; + } + + test = v - radical; + if (test > bMax) + { + bMax = test; + } + } + + // The enclosing region might be a capsule or a sphere. + if (aMin >= aMax) + { + test = (Real)0.5 * (aMin + aMax); + aMin = test; + aMax = test; + } + if (bMin >= bMax) + { + test = (Real)0.5 * (bMin + bMax); + bMin = test; + bMax = test; + } + + // Make correction for points inside mitered corner but outside quarter + // sphere. + for (int i = 0; i < numPoints; ++i) + { + diff = points[i] - box.center; + u = Dot(box.axis[2], diff); + v = Dot(box.axis[1], diff); + + Real* aExtreme = nullptr; + Real* bExtreme = nullptr; + + if (u > aMax) + { + if (v > bMax) + { + aExtreme = &aMax; + bExtreme = &bMax; + } + else if (v < bMin) + { + aExtreme = &aMax; + bExtreme = &bMin; + } + } + else if (u < aMin) + { + if (v > bMax) + { + aExtreme = &aMin; + bExtreme = &bMax; + } + else if (v < bMin) + { + aExtreme = &aMin; + bExtreme = &bMin; + } + } + + if (aExtreme) + { + Real deltaU = u - *aExtreme; + Real deltaV = v - *bExtreme; + Real deltaSumSqr = deltaU * deltaU + deltaV * deltaV; + w = Dot(box.axis[0], diff); + Real wSqr = w * w; + test = deltaSumSqr + wSqr; + if (test > rSqr) + { + discr = (rSqr - wSqr) / deltaSumSqr; + Real t = -std::sqrt(std::max(discr, (Real)0)); + *aExtreme = u + t * deltaU; + *bExtreme = v + t * deltaV; + } + } + } + + lozenge.radius = radius; + lozenge.rectangle.axis[0] = box.axis[2]; + lozenge.rectangle.axis[1] = box.axis[1]; + + if (aMin < aMax) + { + if (bMin < bMax) + { + // Container is a lozenge. + lozenge.rectangle.center = + box.center + aMin * box.axis[2] + bMin * box.axis[1]; + lozenge.rectangle.extent[0] = (Real)0.5 * (aMax - aMin); + lozenge.rectangle.extent[1] = (Real)0.5 * (bMax - bMin); + } + else + { + // Container is a capsule. + lozenge.rectangle.center = box.center + aMin * box.axis[2] + + ((Real)0.5 * (bMin + bMax)) * box.axis[1]; + lozenge.rectangle.extent[0] = (Real)0.5 * (aMax - aMin); + lozenge.rectangle.extent[1] = (Real)0; + } + } + else + { + if (bMin < bMax) + { + // Container is a capsule. + lozenge.rectangle.center = box.center + bMin * box.axis[1] + + ((Real)0.5 * (aMin + aMax)) * box.axis[2]; + lozenge.rectangle.extent[0] = (Real)0; + lozenge.rectangle.extent[1] = (Real)0.5 * (bMax - bMin); + } + else + { + // Container is a sphere. + lozenge.rectangle.center = box.center + + ((Real)0.5 * (aMin + aMax)) * box.axis[2] + + ((Real)0.5 * (bMin + bMax)) * box.axis[1]; + lozenge.rectangle.extent[0] = (Real)0; + lozenge.rectangle.extent[1] = (Real)0; + } + } + + return true; + } + + // Test for containment of a point by a lozenge. + template + bool InContainer(Vector3 const& point, Lozenge3 const& lozenge) + { + DCPQuery, Rectangle3> prQuery; + auto result = prQuery(point, lozenge.rectangle); + return result.distance <= lozenge.radius; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContOrientedBox2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContOrientedBox2.h new file mode 100644 index 0000000..71fdd5f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContOrientedBox2.h @@ -0,0 +1,176 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + // Compute an oriented bounding box of the points. The box center is the + // average of the points. The box axes are the eigenvectors of the + // covariance matrix. + template + bool GetContainer(int numPoints, Vector2 const* points, OrientedBox2& box) + { + // Fit the points with a Gaussian distribution. + ApprGaussian2 fitter; + if (fitter.Fit(numPoints, points)) + { + box = fitter.GetParameters(); + + // Let C be the box center and let U0 and U1 be the box axes. + // Each input point is of the form X = C + y0*U0 + y1*U1. The + // following code computes min(y0), max(y0), min(y1), and max(y1). + // The box center is then adjusted to be + // C' = C + 0.5*(min(y0)+max(y0))*U0 + 0.5*(min(y1)+max(y1))*U1 + + Vector2 diff = points[0] - box.center; + Vector2 pmin{ Dot(diff, box.axis[0]), Dot(diff, box.axis[1]) }; + Vector2 pmax = pmin; + for (int i = 1; i < numPoints; ++i) + { + diff = points[i] - box.center; + for (int j = 0; j < 2; ++j) + { + Real dot = Dot(diff, box.axis[j]); + if (dot < pmin[j]) + { + pmin[j] = dot; + } + else if (dot > pmax[j]) + { + pmax[j] = dot; + } + } + } + + for (int j = 0; j < 2; ++j) + { + box.center += ((Real)0.5 * (pmin[j] + pmax[j])) * box.axis[j]; + box.extent[j] = (Real)0.5 * (pmax[j] - pmin[j]); + } + return true; + } + + return false; + } + + template + bool GetContainer(std::vector> const& points, OrientedBox2& box) + { + return GetContainer(static_cast(points.size()), points.data(), box); + } + + // Test for containment. Let X = C + y0*U0 + y1*U1 where C is the box + // center and U0 and U1 are the orthonormal axes of the box. X is in the + // box when |y_i| <= E_i for all i, where E_i are the extents of the box. + template + bool InContainer(Vector2 const& point, OrientedBox2 const& box) + { + Vector2 diff = point - box.center; + for (int i = 0; i < 2; ++i) + { + Real coeff = Dot(diff, box.axis[i]); + if (std::fabs(coeff) > box.extent[i]) + { + return false; + } + } + return true; + } + + // Construct an oriented box that contains two other oriented boxes. The + // result is not guaranteed to be the minimum area box containing the + // input boxes. + template + bool MergeContainers(OrientedBox2 const& box0, + OrientedBox2 const& box1, OrientedBox2& merge) + { + // The first guess at the box center. This value will be updated + // later after the input box vertices are projected onto axes + // determined by an average of box axes. + merge.center = (Real)0.5 * (box0.center + box1.center); + + // The merged box axes are the averages of the input box axes. The + // axes of the second box are negated, if necessary, so they form + // acute angles with the axes of the first box. + if (Dot(box0.axis[0], box1.axis[0]) >= (Real)0) + { + merge.axis[0] = (Real)0.5 * (box0.axis[0] + box1.axis[0]); + } + else + { + merge.axis[0] = (Real)0.5 * (box0.axis[0] - box1.axis[0]); + } + Normalize(merge.axis[0]); + merge.axis[1] = -Perp(merge.axis[0]); + + // Project the input box vertices onto the merged-box axes. Each + // axis D[i] containing the current center C has a minimum projected + // value min[i] and a maximum projected value max[i]. The + // corresponding endpoints on the axes are C+min[i]*D[i] and + // C+max[i]*D[i]. The point C is not necessarily the midpoint for + // any of the intervals. The actual box center will be adjusted from + // C to a point C' that is the midpoint of each interval, + // C' = C + sum_{i=0}^1 0.5*(min[i]+max[i])*D[i] + // The box extents are + // e[i] = 0.5*(max[i]-min[i]) + + std::array, 4> vertex; + Vector2 pmin{ (Real)0, (Real)0 }; + Vector2 pmax{ (Real)0, (Real)0 }; + + box0.GetVertices(vertex); + for (int i = 0; i < 4; ++i) + { + Vector2 diff = vertex[i] - merge.center; + for (int j = 0; j < 2; ++j) + { + Real dot = Dot(diff, merge.axis[j]); + if (dot > pmax[j]) + { + pmax[j] = dot; + } + else if (dot < pmin[j]) + { + pmin[j] = dot; + } + } + } + + box1.GetVertices(vertex); + for (int i = 0; i < 4; ++i) + { + Vector2 diff = vertex[i] - merge.center; + for (int j = 0; j < 2; ++j) + { + Real dot = Dot(diff, merge.axis[j]); + if (dot > pmax[j]) + { + pmax[j] = dot; + } + else if (dot < pmin[j]) + { + pmin[j] = dot; + } + } + } + + // [min,max] is the axis-aligned box in the coordinate system of the + // merged box axes. Update the current box center to be the center of + // the new box. Compute the extents based on the new center. + Real const half = (Real)0.5; + for (int j = 0; j < 2; ++j) + { + merge.center += half * (pmax[j] + pmin[j]) * merge.axis[j]; + merge.extent[j] = half * (pmax[j] - pmin[j]); + } + + return true; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContOrientedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContOrientedBox3.h new file mode 100644 index 0000000..b7ade34 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContOrientedBox3.h @@ -0,0 +1,198 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + // Compute an oriented bounding box of the points. The box center is the + // average of the points. The box axes are the eigenvectors of the + // covariance matrix. + template + bool GetContainer(int numPoints, Vector3 const* points, OrientedBox3& box) + { + // Fit the points with a Gaussian distribution. + ApprGaussian3 fitter; + if (fitter.Fit(numPoints, points)) + { + box = fitter.GetParameters(); + + // Let C be the box center and let U0, U1, and U2 be the box axes. + // Each input point is of the form X = C + y0*U0 + y1*U1 + y2*U2. + // The following code computes min(y0), max(y0), min(y1), max(y1), + // min(y2), and max(y2). The box center is then adjusted to be + // C' = C + 0.5*(min(y0)+max(y0))*U0 + 0.5*(min(y1)+max(y1))*U1 + // + 0.5*(min(y2)+max(y2))*U2 + + Vector3 diff = points[0] - box.center; + Vector3 pmin{ Dot(diff, box.axis[0]), Dot(diff, box.axis[1]), + Dot(diff, box.axis[2]) }; + Vector3 pmax = pmin; + for (int i = 1; i < numPoints; ++i) + { + diff = points[i] - box.center; + for (int j = 0; j < 3; ++j) + { + Real dot = Dot(diff, box.axis[j]); + if (dot < pmin[j]) + { + pmin[j] = dot; + } + else if (dot > pmax[j]) + { + pmax[j] = dot; + } + } + } + + for (int j = 0; j < 3; ++j) + { + box.center += ((Real)0.5 * (pmin[j] + pmax[j])) * box.axis[j]; + box.extent[j] = (Real)0.5 * (pmax[j] - pmin[j]); + } + return true; + } + + return false; + } + + template + bool GetContainer(std::vector> const& points, OrientedBox3& box) + { + return GetContainer(static_cast(points.size()), points.data(), box); + } + + // Test for containment. Let X = C + y0*U0 + y1*U1 + y2*U2 where C is the + // box center and U0, U1, U2 are the orthonormal axes of the box. X is in + // the box if |y_i| <= E_i for all i where E_i are the extents of the box. + template + bool InContainer(Vector3 const& point, OrientedBox3 const& box) + { + Vector3 diff = point - box.center; + for (int i = 0; i < 3; ++i) + { + Real coeff = Dot(diff, box.axis[i]); + if (std::fabs(coeff) > box.extent[i]) + { + return false; + } + } + return true; + } + + // Construct an oriented box that contains two other oriented boxes. The + // result is not guaranteed to be the minimum volume box containing the + // input boxes. + template + bool MergeContainers(OrientedBox3 const& box0, + OrientedBox3 const& box1, OrientedBox3& merge) + { + // The first guess at the box center. This value will be updated + // later after the input box vertices are projected onto axes + // determined by an average of box axes. + merge.center = (Real)0.5 * (box0.center + box1.center); + + // A box's axes, when viewed as the columns of a matrix, form a + // rotation matrix. The input box axes are converted to quaternions. + // The average quaternion is computed, then normalized to unit length. + // The result is the slerp of the two input quaternions with t-value + // of 1/2. The result is converted back to a rotation matrix and its + // columns are selected as the merged box axes. + // + // TODO: When the GTL Lie Algebra code is posted, use the geodesic + // path between the affine matrices formed by the box centers and + // orientations. Choose t = 1/2 along that geodesic. + Matrix3x3 rot0, rot1; + rot0.SetCol(0, box0.axis[0]); + rot0.SetCol(1, box0.axis[1]); + rot0.SetCol(2, box0.axis[2]); + rot1.SetCol(0, box1.axis[0]); + rot1.SetCol(1, box1.axis[1]); + rot1.SetCol(2, box1.axis[2]); + Quaternion q0 = Rotation<3, Real>(rot0); + Quaternion q1 = Rotation<3, Real>(rot1); + if (Dot(q0, q1) < (Real)0) + { + q1 = -q1; + } + + Quaternion q = q0 + q1; + Normalize(q); + Matrix3x3 rot = Rotation<3, Real>(q); + for (int j = 0; j < 3; ++j) + { + merge.axis[j] = rot.GetCol(j); + } + + // Project the input box vertices onto the merged-box axes. Each axis + // D[i] containing the current center C has a minimum projected value + // min[i] and a maximum projected value max[i]. The corresponding end + // points on the axes are C+min[i]*D[i] and C+max[i]*D[i]. The point + // C is not necessarily the midpoint for any of the intervals. The + // actual box center will be adjusted from C to a point C' that is the + // midpoint of each interval, + // C' = C + sum_{i=0}^2 0.5*(min[i]+max[i])*D[i] + // The box extents are + // e[i] = 0.5*(max[i]-min[i]) + + std::array, 8> vertex; + Vector3 pmin{ (Real)0, (Real)0, (Real)0 }; + Vector3 pmax{ (Real)0, (Real)0, (Real)0 }; + + box0.GetVertices(vertex); + for (int i = 0; i < 8; ++i) + { + Vector3 diff = vertex[i] - merge.center; + for (int j = 0; j < 3; ++j) + { + Real dot = Dot(diff, merge.axis[j]); + if (dot > pmax[j]) + { + pmax[j] = dot; + } + else if (dot < pmin[j]) + { + pmin[j] = dot; + } + } + } + + box1.GetVertices(vertex); + for (int i = 0; i < 8; ++i) + { + Vector3 diff = vertex[i] - merge.center; + for (int j = 0; j < 3; ++j) + { + Real dot = Dot(diff, merge.axis[j]); + if (dot > pmax[j]) + { + pmax[j] = dot; + } + else if (dot < pmin[j]) + { + pmin[j] = dot; + } + } + } + + // [min,max] is the axis-aligned box in the coordinate system of the + // merged box axes. Update the current box center to be the center of + // the new box. Compute the extents based on the new center. + Real const half = (Real)0.5; + for (int j = 0; j < 3; ++j) + { + merge.center += half * (pmax[j] + pmin[j]) * merge.axis[j]; + merge.extent[j] = half * (pmax[j] - pmin[j]); + } + + return true; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContPointInPolygon2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContPointInPolygon2.h new file mode 100644 index 0000000..dcc58fb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContPointInPolygon2.h @@ -0,0 +1,202 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// Given a polygon as an order list of vertices (x[i],y[i]) for 0 <= i < N +// and a test point (xt,yt), return 'true' if (xt,yt) is in the polygon and +// 'false' if it is not. All queries require that the number of vertices +// satisfies N >= 3. + +namespace WwiseGTE +{ + template + class PointInPolygon2 + { + public: + // The class object stores a copy of 'points', so be careful about + // the persistence of 'points' when you have created a + // PointInPolygon2 object. + PointInPolygon2(int numPoints, Vector2 const* points) + : + mNumPoints(numPoints), + mPoints(points) + { + } + + // Simple polygons (ray-intersection counting). + bool Contains(Vector2 const& p) const + { + bool inside = false; + for (int i = 0, j = mNumPoints - 1; i < mNumPoints; j = i++) + { + Vector2 const& U0 = mPoints[i]; + Vector2 const& U1 = mPoints[j]; + Real rhs, lhs; + + if (p[1] < U1[1]) // U1 above ray + { + if (U0[1] <= p[1]) // U0 on or below ray + { + lhs = (p[1] - U0[1]) * (U1[0] - U0[0]); + rhs = (p[0] - U0[0]) * (U1[1] - U0[1]); + if (lhs > rhs) + { + inside = !inside; + } + } + } + else if (p[1] < U0[1]) // U1 on or below ray, U0 above ray + { + lhs = (p[1] - U0[1]) * (U1[0] - U0[0]); + rhs = (p[0] - U0[0]) * (U1[1] - U0[1]); + if (lhs < rhs) + { + inside = !inside; + } + } + } + return inside; + } + + // Algorithms for convex polygons. The input polygons must have + // vertices in counterclockwise order. + + // O(N) algorithm (which-side-of-edge tests) + bool ContainsConvexOrderN(Vector2 const& p) const + { + for (int i1 = 0, i0 = mNumPoints - 1; i1 < mNumPoints; i0 = i1++) + { + Real nx = mPoints[i1][1] - mPoints[i0][1]; + Real ny = mPoints[i0][0] - mPoints[i1][0]; + Real dx = p[0] - mPoints[i0][0]; + Real dy = p[1] - mPoints[i0][1]; + if (nx * dx + ny * dy > (Real)0) + { + return false; + } + } + return true; + } + + // O(log N) algorithm (bisection and recursion, like BSP tree) + bool ContainsConvexOrderLogN(Vector2 const& p) const + { + return SubContainsPoint(p, 0, 0); + } + + // The polygon must have exactly four vertices. This method is like + // the O(log N) and uses three which-side-of-segment test instead of + // four which-side-of-edge tests. If the polygon does not have four + // vertices, the function returns false. + bool ContainsQuadrilateral(Vector2 const& p) const + { + if (mNumPoints != 4) + { + return false; + } + + Real nx = mPoints[2][1] - mPoints[0][1]; + Real ny = mPoints[0][0] - mPoints[2][0]; + Real dx = p[0] - mPoints[0][0]; + Real dy = p[1] - mPoints[0][1]; + + if (nx * dx + ny * dy > (Real)0) + { + // P potentially in + nx = mPoints[1][1] - mPoints[0][1]; + ny = mPoints[0][0] - mPoints[1][0]; + if (nx * dx + ny * dy > (Real)0.0) + { + return false; + } + + nx = mPoints[2][1] - mPoints[1][1]; + ny = mPoints[1][0] - mPoints[2][0]; + dx = p[0] - mPoints[1][0]; + dy = p[1] - mPoints[1][1]; + if (nx * dx + ny * dy > (Real)0) + { + return false; + } + } + else + { + // P potentially in + nx = mPoints[0][1] - mPoints[3][1]; + ny = mPoints[3][0] - mPoints[0][0]; + if (nx * dx + ny * dy > (Real)0) + { + return false; + } + + nx = mPoints[3][1] - mPoints[2][1]; + ny = mPoints[2][0] - mPoints[3][0]; + dx = p[0] - mPoints[3][0]; + dy = p[1] - mPoints[3][1]; + if (nx * dx + ny * dy > (Real)0) + { + return false; + } + } + return true; + } + + private: + // For recursion in ContainsConvexOrderLogN. + bool SubContainsPoint(Vector2 const& p, int i0, int i1) const + { + Real nx, ny, dx, dy; + + int diff = i1 - i0; + if (diff == 1 || (diff < 0 && diff + mNumPoints == 1)) + { + nx = mPoints[i1][1] - mPoints[i0][1]; + ny = mPoints[i0][0] - mPoints[i1][0]; + dx = p[0] - mPoints[i0][0]; + dy = p[1] - mPoints[i0][1]; + return nx * dx + ny * dy <= (Real)0; + } + + // Bisect the index range. + int mid; + if (i0 < i1) + { + mid = (i0 + i1) >> 1; + } + else + { + mid = (i0 + i1 + mNumPoints) >> 1; + if (mid >= mNumPoints) + { + mid -= mNumPoints; + } + } + + // Determine which side of the splitting line contains the point. + nx = mPoints[mid][1] - mPoints[i0][1]; + ny = mPoints[i0][0] - mPoints[mid][0]; + dx = p[0] - mPoints[i0][0]; + dy = p[1] - mPoints[i0][1]; + if (nx * dx + ny * dy > (Real)0) + { + // P potentially in + return SubContainsPoint(p, i0, mid); + } + else + { + // P potentially in + return SubContainsPoint(p, mid, i1); + } + } + + int mNumPoints; + Vector2 const* mPoints; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContPointInPolyhedron3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContPointInPolyhedron3.h new file mode 100644 index 0000000..2c5c686 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContPointInPolyhedron3.h @@ -0,0 +1,575 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// This class contains various implementations for point-in-polyhedron +// queries. The planes stored with the faces are used in all cases to +// reject ray-face intersection tests, a quick culling operation. +// +// The algorithm is to cast a ray from the input point P and test for +// intersection against each face of the polyhedron. If the ray only +// intersects faces at interior points (not vertices, not edge points), +// then the point is inside when the number of intersections is odd and +// the point is outside when the number of intersections is even. If the +// ray intersects an edge or a vertex, then the counting must be handled +// differently. The details are tedious. As an alternative, the approach +// here is to allow you to specify 2*N+1 rays, where N >= 0. You should +// choose these rays randomly. Each ray reports "inside" or "outside". +// Whichever result occurs N+1 or more times is the "winner". The input +// rayQuantity is 2*N+1. The input array Direction must have rayQuantity +// elements. If you are feeling lucky, choose rayQuantity to be 1. + +namespace WwiseGTE +{ + template + class PointInPolyhedron3 + { + public: + // For simple polyhedra with triangle faces. + class TriangleFace + { + public: + // When you view the face from outside, the vertices are + // counterclockwise ordered. The indices array stores the indices + // into the vertex array. + std::array indices; + + // The normal vector is unit length and points to the outside of + // the polyhedron. + Plane3 plane; + }; + + // The Contains query will use ray-triangle intersection queries. + PointInPolyhedron3(int numPoints, Vector3 const* points, + int numFaces, TriangleFace const* faces, int numRays, + Vector3 const* directions) + : + mNumPoints(numPoints), + mPoints(points), + mNumFaces(numFaces), + mTFaces(faces), + mCFaces(nullptr), + mSFaces(nullptr), + mMethod(0), + mNumRays(numRays), + mDirections(directions) + { + } + + // For simple polyhedra with convex polygon faces. + class ConvexFace + { + public: + // When you view the face from outside, the vertices are + // counterclockwise ordered. The indices array stores the indices + // into the vertex array. + std::vector indices; + + // The normal vector is unit length and points to the outside of + // the polyhedron. + Plane3 plane; + }; + + // The Contains() query will use ray-convexpolygon intersection + // queries. A ray-convexpolygon intersection query can be implemented + // in many ways. In this context, uiMethod is one of three value: + // 0 : Use a triangle fan and perform a ray-triangle intersection + // query for each triangle. + // 1 : Find the point of intersection of ray and plane of polygon. + // Test whether that point is inside the convex polygon using an + // O(N) test. + // 2 : Find the point of intersection of ray and plane of polygon. + // Test whether that point is inside the convex polygon using an + // O(log N) test. + PointInPolyhedron3(int numPoints, Vector3 const* points, + int numFaces, ConvexFace const* faces, int numRays, + Vector3 const* directions, unsigned int method) + : + mNumPoints(numPoints), + mPoints(points), + mNumFaces(numFaces), + mTFaces(nullptr), + mCFaces(faces), + mSFaces(nullptr), + mMethod(method), + mNumRays(numRays), + mDirections(directions) + { + } + + // For simple polyhedra with simple polygon faces that are generally + // not all convex. + class SimpleFace + { + public: + // When you view the face from outside, the vertices are + // counterclockwise ordered. The Indices array stores the indices + // into the vertex array. + std::vector indices; + + // The normal vector is unit length and points to the outside of + // the polyhedron. + Plane3 plane; + + // Each simple face may be triangulated. The indices are relative + // to the vertex array. Each triple of indices represents a + // triangle in the triangulation. + std::vector triangles; + }; + + // The Contains query will use ray-simplepolygon intersection queries. + // A ray-simplepolygon intersection query can be implemented in a + // couple of ways. In this context, uiMethod is one of two value: + // 0 : Iterate over the triangles of each face and perform a + // ray-triangle intersection query for each triangle. This + // requires that the SimpleFace::Triangles array be initialized + // for each face. + // 1 : Find the point of intersection of ray and plane of polygon. + // Test whether that point is inside the polygon using an O(N) + // test. The SimpleFace::Triangles array is not used for this + // method, so it does not have to be initialized for each face. + PointInPolyhedron3(int numPoints, Vector3 const* points, + int numFaces, SimpleFace const* faces, int numRays, + Vector3 const* directions, unsigned int method) + : + mNumPoints(numPoints), + mPoints(points), + mNumFaces(numFaces), + mTFaces(nullptr), + mCFaces(nullptr), + mSFaces(faces), + mMethod(method), + mNumRays(numRays), + mDirections(directions) + { + } + + // This function will select the actual algorithm based on which + // constructor you used for this class. + bool Contains(Vector3 const& p) const + { + if (mTFaces) + { + return ContainsT0(p); + } + + if (mCFaces) + { + if (mMethod == 0) + { + return ContainsC0(p); + } + + return ContainsC1C2(p, mMethod); + } + + if (mSFaces) + { + if (mMethod == 0) + { + return ContainsS0(p); + } + + if (mMethod == 1) + { + return ContainsS1(p); + } + } + + return false; + } + + private: + // For all types of faces. The ray origin is the test point. The ray + // direction is one of those passed to the constructors. The plane + // origin is a point on the plane of the face. The plane normal is a + // unit-length normal to the face and that points outside the + // polyhedron. + static bool FastNoIntersect(Ray3 const& ray, Plane3 const& plane) + { + Real planeDistance = Dot(plane.normal, ray.origin) - plane.constant; + Real planeAngle = Dot(plane.normal, ray.direction); + + if (planeDistance < (Real)0) + { + // The ray origin is on the negative side of the plane. + if (planeAngle <= (Real)0) + { + // The ray points away from the plane. + return true; + } + } + + if (planeDistance > (Real)0) + { + // The ray origin is on the positive side of the plane. + if (planeAngle >= (Real)0) + { + // The ray points away from the plane. + return true; + } + } + + return false; + } + + // For triangle faces. + bool ContainsT0(Vector3 const& p) const + { + int insideCount = 0; + + TIQuery, Triangle3> rtQuery; + Triangle3 triangle; + Ray3 ray; + ray.origin = p; + + for (int j = 0; j < mNumRays; ++j) + { + ray.direction = mDirections[j]; + + // Zero intersections to start with. + bool odd = false; + + TriangleFace const* face = mTFaces; + for (int i = 0; i < mNumFaces; ++i, ++face) + { + // Attempt to quickly cull the triangle. + if (FastNoIntersect(ray, face->plane)) + { + continue; + } + + // Get the triangle vertices. + for (int k = 0; k < 3; ++k) + { + triangle.v[k] = mPoints[face->indices[k]]; + } + + // Test for intersection. + if (rtQuery(ray, triangle).intersect) + { + // The ray intersects the triangle. + odd = !odd; + } + } + + if (odd) + { + insideCount++; + } + } + + return insideCount > mNumRays / 2; + } + + // For convex faces. + bool ContainsC0(Vector3 const& p) const + { + int insideCount = 0; + + TIQuery, Triangle3> rtQuery; + Triangle3 triangle; + Ray3 ray; + ray.origin = p; + + for (int j = 0; j < mNumRays; ++j) + { + ray.direction = mDirections[j]; + + // Zero intersections to start with. + bool odd = false; + + ConvexFace const* face = mCFaces; + for (int i = 0; i < mNumFaces; ++i, ++face) + { + // Attempt to quickly cull the triangle. + if (FastNoIntersect(ray, face->plane)) + { + continue; + } + + // Process the triangles in a trifan of the face. + size_t numVerticesM1 = face->indices.size() - 1; + triangle.v[0] = mPoints[face->indices[0]]; + for (size_t k = 1; k < numVerticesM1; ++k) + { + triangle.v[1] = mPoints[face->indices[k]]; + triangle.v[2] = mPoints[face->indices[k + 1]]; + + if (rtQuery(ray, triangle).intersect) + { + // The ray intersects the triangle. + odd = !odd; + } + } + } + + if (odd) + { + insideCount++; + } + } + + return insideCount > mNumRays / 2; + } + + bool ContainsC1C2(Vector3 const& p, unsigned int method) const + { + int insideCount = 0; + + FIQuery, Plane3> rpQuery; + Ray3 ray; + ray.origin = p; + + for (int j = 0; j < mNumRays; ++j) + { + ray.direction = mDirections[j]; + + // Zero intersections to start with. + bool odd = false; + + ConvexFace const* face = mCFaces; + for (int i = 0; i < mNumFaces; ++i, ++face) + { + // Attempt to quickly cull the triangle. + if (FastNoIntersect(ray, face->plane)) + { + continue; + } + + // Compute the ray-plane intersection. + auto result = rpQuery(ray, face->plane); + + // If you trigger this assertion, numerical round-off + // errors have led to a discrepancy between + // FastNoIntersect and the Find() result. + LogAssert(result.intersect, "Unexpected condition."); + + // Get a coordinate system for the plane. Use vertex 0 + // as the origin. + Vector3 const& V0 = mPoints[face->indices[0]]; + Vector3 basis[3]; + basis[0] = face->plane.normal; + ComputeOrthogonalComplement(1, basis); + + // Project the intersection onto the plane. + Vector3 diff = result.point - V0; + Vector2 projIntersect{ Dot(basis[1], diff), Dot(basis[2], diff) }; + + // Project the face vertices onto the plane of the face. + if (face->indices.size() > mProjVertices.size()) + { + mProjVertices.resize(face->indices.size()); + } + + // Project the remaining vertices. Vertex 0 is always the + // origin. + size_t numIndices = face->indices.size(); + mProjVertices[0] = Vector2::Zero(); + for (size_t k = 1; k < numIndices; ++k) + { + diff = mPoints[face->indices[k]] - V0; + mProjVertices[k][0] = Dot(basis[1], diff); + mProjVertices[k][1] = Dot(basis[2], diff); + } + + // Test whether the intersection point is in the convex + // polygon. + PointInPolygon2 PIP(static_cast(mProjVertices.size()), + &mProjVertices[0]); + + if (method == 1) + { + if (PIP.ContainsConvexOrderN(projIntersect)) + { + // The ray intersects the triangle. + odd = !odd; + } + } + else + { + if (PIP.ContainsConvexOrderLogN(projIntersect)) + { + // The ray intersects the triangle. + odd = !odd; + } + } + } + + if (odd) + { + insideCount++; + } + } + + return insideCount > mNumRays / 2; + } + + // For simple faces. + bool ContainsS0(Vector3 const& p) const + { + int insideCount = 0; + + TIQuery, Triangle3> rtQuery; + Triangle3 triangle; + Ray3 ray; + ray.origin = p; + + for (int j = 0; j < mNumRays; ++j) + { + ray.direction = mDirections[j]; + + // Zero intersections to start with. + bool odd = false; + + SimpleFace const* face = mSFaces; + for (int i = 0; i < mNumFaces; ++i, ++face) + { + // Attempt to quickly cull the triangle. + if (FastNoIntersect(ray, face->plane)) + { + continue; + } + + // The triangulation must exist to use it. + size_t numTriangles = face->triangles.size() / 3; + LogAssert(numTriangles > 0, "Triangulation must exist."); + + // Process the triangles in a triangulation of the face. + int const* currIndex = &face->triangles[0]; + for (size_t t = 0; t < numTriangles; ++t) + { + // Get the triangle vertices. + for (int k = 0; k < 3; ++k) + { + triangle.v[k] = mPoints[*currIndex++]; + } + + // Test for intersection. + if (rtQuery(ray, triangle).intersect) + { + // The ray intersects the triangle. + odd = !odd; + } + } + } + + if (odd) + { + insideCount++; + } + } + + return insideCount > mNumRays / 2; + } + + bool ContainsS1(Vector3 const& p) const + { + int insideCount = 0; + + FIQuery, Plane3> rpQuery; + Ray3 ray; + ray.origin = p; + + for (int j = 0; j < mNumRays; ++j) + { + ray.direction = mDirections[j]; + + // Zero intersections to start with. + bool odd = false; + + SimpleFace const* face = mSFaces; + for (int i = 0; i < mNumFaces; ++i, ++face) + { + // Attempt to quickly cull the triangle. + if (FastNoIntersect(ray, face->plane)) + { + continue; + } + + // Compute the ray-plane intersection. + auto result = rpQuery(ray, face->plane); + + // If you trigger this assertion, numerical round-off + // errors have led to a discrepancy between + // FastNoIntersect and the Find() result. + LogAssert(result.intersect, "Unexpected condition."); + + // Get a coordinate system for the plane. Use vertex 0 + // as the origin. + Vector3 const& V0 = mPoints[face->indices[0]]; + Vector3 basis[3]; + basis[0] = face->plane.normal; + ComputeOrthogonalComplement(1, basis); + + // Project the intersection onto the plane. + Vector3 diff = result.point - V0; + Vector2 projIntersect{ Dot(basis[1], diff), Dot(basis[2], diff) }; + + // Project the face vertices onto the plane of the face. + if (face->indices.size() > mProjVertices.size()) + { + mProjVertices.resize(face->indices.size()); + } + + // Project the remaining vertices. Vertex 0 is always the + // origin. + size_t numIndices = face->indices.size(); + mProjVertices[0] = Vector2::Zero(); + for (size_t k = 1; k < numIndices; ++k) + { + diff = mPoints[face->indices[k]] - V0; + mProjVertices[k][0] = Dot(basis[1], diff); + mProjVertices[k][1] = Dot(basis[2], diff); + } + + // Test whether the intersection point is in the convex + // polygon. + PointInPolygon2 PIP(static_cast(mProjVertices.size()), + &mProjVertices[0]); + + if (PIP.Contains(projIntersect)) + { + // The ray intersects the triangle. + odd = !odd; + } + } + + if (odd) + { + insideCount++; + } + } + + return insideCount > mNumRays / 2; + } + + int mNumPoints; + Vector3 const* mPoints; + + int mNumFaces; + TriangleFace const* mTFaces; + ConvexFace const* mCFaces; + SimpleFace const* mSFaces; + + unsigned int mMethod; + int mNumRays; + Vector3 const* mDirections; + + // Temporary storage for those methods that reduce the problem to 2D + // point-in-polygon queries. The array stores the projections of + // face vertices onto the plane of the face. It is resized as needed. + mutable std::vector> mProjVertices; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContScribeCircle2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContScribeCircle2.h new file mode 100644 index 0000000..a87b78b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContScribeCircle2.h @@ -0,0 +1,61 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + // All functions return 'true' if the circle has been constructed, 'false' + // otherwise (input points are linearly dependent). + + // Circle circumscribing triangle. + template + bool Circumscribe(Vector2 const& v0, Vector2 const& v1, + Vector2 const& v2, Circle2& circle) + { + Vector2 e10 = v1 - v0; + Vector2 e20 = v2 - v0; + Matrix2x2 A{ e10[0], e10[1], e20[0], e20[1] }; + Vector2 B{ (Real)0.5 * Dot(e10, e10), (Real)0.5 * Dot(e20, e20) }; + Vector2 solution; + if (LinearSystem::Solve(A, B, solution)) + { + circle.center = v0 + solution; + circle.radius = Length(solution); + return true; + } + return false; + } + + // Circle inscribing triangle. + template + bool Inscribe(Vector2 const& v0, Vector2 const& v1, + Vector2 const& v2, Circle2& circle) + { + Vector2 d10 = v1 - v0; + Vector2 d20 = v2 - v0; + Vector2 d21 = v2 - v1; + Real len10 = Length(d10); + Real len20 = Length(d20); + Real len21 = Length(d21); + Real perimeter = len10 + len20 + len21; + if (perimeter > (Real)0) + { + Real inv = (Real)1 / perimeter; + len10 *= inv; + len20 *= inv; + len21 *= inv; + circle.center = len21 * v0 + len20 * v1 + len10 * v2; + circle.radius = inv * std::fabs(DotPerp(d10, d20)); + return circle.radius > (Real)0; + } + return false; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContScribeCircle3Sphere3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContScribeCircle3Sphere3.h new file mode 100644 index 0000000..04a4f9d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContScribeCircle3Sphere3.h @@ -0,0 +1,169 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + // All functions return 'true' if circle/sphere has been constructed, + // 'false' otherwise (input points are linearly dependent). + + // Circle circumscribing a triangle in 3D. + template + bool Circumscribe(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Circle3& circle) + { + Vector3 E02 = v0 - v2; + Vector3 E12 = v1 - v2; + Real e02e02 = Dot(E02, E02); + Real e02e12 = Dot(E02, E12); + Real e12e12 = Dot(E12, E12); + Real det = e02e02 * e12e12 - e02e12 * e02e12; + if (det != (Real)0) + { + Real halfInvDet = (Real)0.5 / det; + Real u0 = halfInvDet * e12e12 * (e02e02 - e02e12); + Real u1 = halfInvDet * e02e02 * (e12e12 - e02e12); + Vector3 tmp = u0 * E02 + u1 * E12; + circle.center = v2 + tmp; + circle.normal = UnitCross(E02, E12); + circle.radius = Length(tmp); + return true; + } + return false; + } + + // Sphere circumscribing a tetrahedron. + template + bool Circumscribe(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Vector3 const& v3, Sphere3& sphere) + { + Vector3 E10 = v1 - v0; + Vector3 E20 = v2 - v0; + Vector3 E30 = v3 - v0; + + Matrix3x3 A; + A.SetRow(0, E10); + A.SetRow(1, E20); + A.SetRow(2, E30); + + Vector3 B{ + (Real)0.5 * Dot(E10, E10), + (Real)0.5 * Dot(E20, E20), + (Real)0.5 * Dot(E30, E30) }; + + Vector3 solution; + if (LinearSystem::Solve(A, B, solution)) + { + sphere.center = v0 + solution; + sphere.radius = Length(solution); + return true; + } + return false; + } + + // Circle inscribing a triangle in 3D. + template + bool Inscribe(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Circle3& circle) + { + // Edges. + Vector3 E0 = v1 - v0; + Vector3 E1 = v2 - v1; + Vector3 E2 = v0 - v2; + + // Plane normal. + circle.normal = Cross(E1, E0); + + // Edge normals within the plane. + Vector3 N0 = UnitCross(circle.normal, E0); + Vector3 N1 = UnitCross(circle.normal, E1); + Vector3 N2 = UnitCross(circle.normal, E2); + + Real a0 = Dot(N1, E0); + if (a0 == (Real)0) + { + return false; + } + + Real a1 = Dot(N2, E1); + if (a1 == (Real)0) + { + return false; + } + + Real a2 = Dot(N0, E2); + if (a2 == (Real)0) + { + return false; + } + + Real invA0 = (Real)1 / a0; + Real invA1 = (Real)1 / a1; + Real invA2 = (Real)1 / a2; + + circle.radius = (Real)1 / (invA0 + invA1 + invA2); + circle.center = circle.radius * (invA0 * v0 + invA1 * v1 + invA2 * v2); + Normalize(circle.normal); + return true; + } + + // Sphere inscribing tetrahedron. + template + bool Inscribe(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Vector3 const& v3, Sphere3& sphere) + { + // Edges. + Vector3 E10 = v1 - v0; + Vector3 E20 = v2 - v0; + Vector3 E30 = v3 - v0; + Vector3 E21 = v2 - v1; + Vector3 E31 = v3 - v1; + + // Normals. + Vector3 N0 = Cross(E31, E21); + Vector3 N1 = Cross(E20, E30); + Vector3 N2 = Cross(E30, E10); + Vector3 N3 = Cross(E10, E20); + + // Normalize the normals. + if (Normalize(N0) == (Real)0) + { + return false; + } + if (Normalize(N1) == (Real)0) + { + return false; + } + if (Normalize(N2) == (Real)0) + { + return false; + } + if (Normalize(N3) == (Real)0) + { + return false; + } + + Matrix3x3 A; + A.SetRow(0, N1 - N0); + A.SetRow(1, N2 - N0); + A.SetRow(2, N3 - N0); + Vector3 B{ (Real)0, (Real)0, -Dot(N3, E30) }; + Vector3 solution; + if (LinearSystem::Solve(A, B, solution)) + { + sphere.center = v3 + solution; + sphere.radius = std::fabs(Dot(N0, solution)); + return true; + } + return false; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContSphere3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContSphere3.h new file mode 100644 index 0000000..99749cb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ContSphere3.h @@ -0,0 +1,88 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + // Compute the smallest bounding sphere whose center is the average of + // the input points. + template + bool GetContainer(int numPoints, Vector3 const* points, Sphere3& sphere) + { + sphere.center = points[0]; + for (int i = 1; i < numPoints; ++i) + { + sphere.center += points[i]; + } + sphere.center /= (Real)numPoints; + + sphere.radius = (Real)0; + for (int i = 0; i < numPoints; ++i) + { + Vector3 diff = points[i] - sphere.center; + Real radiusSqr = Dot(diff, diff); + if (radiusSqr > sphere.radius) + { + sphere.radius = radiusSqr; + } + } + + sphere.radius = std::sqrt(sphere.radius); + return true; + } + + template + bool GetContainer(std::vector> const& points, Sphere3& sphere) + { + return GetContainer(static_cast(points.size()), points.data(), sphere); + } + + // Test for containment of a point inside a sphere. + template + bool InContainer(Vector3 const& point, Sphere3 const& sphere) + { + Vector3 diff = point - sphere.center; + return Length(diff) <= sphere.radius; + } + + // Compute the smallest bounding sphere that contains the input spheres. + template + bool MergeContainers(Sphere3 const& sphere0, Sphere3 const& sphere1, Sphere3& merge) + { + Vector3 cenDiff = sphere1.center - sphere0.center; + Real lenSqr = Dot(cenDiff, cenDiff); + Real rDiff = sphere1.radius - sphere0.radius; + Real rDiffSqr = rDiff * rDiff; + + if (rDiffSqr >= lenSqr) + { + merge = (rDiff >= (Real)0 ? sphere1 : sphere0); + } + else + { + Real length = std::sqrt(lenSqr); + if (length > (Real)0) + { + Real coeff = (length + rDiff) / (((Real)2)*length); + merge.center = sphere0.center + coeff * cenDiff; + } + else + { + merge.center = sphere0.center; + } + + merge.radius = (Real)0.5 * (length + sphere0.radius + sphere1.radius); + } + + return true; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ConvertCoordinates.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ConvertCoordinates.h new file mode 100644 index 0000000..4b3cdd6 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ConvertCoordinates.h @@ -0,0 +1,354 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// Convert points and transformations between two coordinate systems. +// The mathematics involves a change of basis. See the document +// https://www.geometrictools.com/Documentation/ConvertingBetweenCoordinateSystems.pdf +// for the details. Typical usage for 3D conversion is shown next. +// +// // Linear change of basis. The columns of U are the basis vectors for the +// // source coordinate system. A vector X = { x0, x1, x2 } in the source +// // coordinate system is represented by +// // X = x0*(1,0,0) + x1*(0,1,0) + x2*(0,0,1) +// // The Cartesian coordinates for the point are the combination of these +// // terms, +// // X = (x0, x1, x2) +// // The columns of V are the basis vectors for the target coordinate system. +// // A vector Y = { y0, y1, y2 } in the target coordinate system is +// // represented by +// // Y = y0*(1,0,0,0) + y1*(0,0,1) + y2*(0,1,0) +// // The Cartesian coordinates for the vector are the combination of these +// // terms, +// // Y = (y0, y2, y1) +// // The call Y = convert.UToV(X) computes y0, y1 and y2 so that the Cartesian +// // coordinates for X and for Y are the same. For example, +// // X = { 1.0, 2.0, 3.0 } +// // = 1.0*(1,0,0) + 2.0*(0,1,0) + 3.0*(0,0,1) +// // = (1, 2, 3) +// // Y = { 1.0, 3.0, 2.0 } +// // = 1.0*(1,0,0) + 3.0*(0,0,1) + 2.0*(0,1,0) +// // = (1, 2, 3) +// // X and Y represent the same vector (equal Cartesian coordinates) but have +// // different representations in the source and target coordinates. +// +// ConvertCoordinates<3, double> convert; +// Vector<3, double> X, Y, P0, P1, diff; +// Matrix<3, 3, double> U, V, A, B; +// bool isRHU, isRHV; +// U.SetCol(0, Vector3{1.0, 0.0, 0.0}); +// U.SetCol(1, Vector3{0.0, 1.0, 0.0}); +// U.SetCol(2, Vector3{0.0, 0.0, 1.0}); +// V.SetCol(0, Vector3{1.0, 0.0, 0.0}); +// V.SetCol(1, Vector3{0.0, 0.0, 1.0}); +// V.SetCol(2, Vector3{0.0, 1.0, 0.0}); +// convert(U, true, V, true); +// isRHU = convert.IsRightHandedU(); // true +// isRHV = convert.IsRightHandedV(); // false +// X = { 1.0, 2.0, 3.0 }; +// Y = convert.UToV(X); // { 1.0, 3.0, 2.0 } +// P0 = U*X; +// P1 = V*Y; +// diff = P0 - P1; // { 0, 0, 0 } +// Y = { 0.0, 1.0, 2.0 }; +// X = convert.VToU(Y); // { 0.0, 2.0, 1.0 } +// P0 = U*X; +// P1 = V*Y; +// diff = P0 - P1; // { 0, 0, 0 } +// double cs = 0.6, sn = 0.8; // cs*cs + sn*sn = 1 +// A.SetCol(0, Vector3{ c, s, 0.0}); +// A.SetCol(1, Vector3{ -s, c, 0.0}); +// A.SetCol(2, Vector3{0.0, 0.0, 1.0}); +// B = convert.UToV(A); +// // B.GetCol(0) = { c, 0, s} +// // B.GetCol(1) = { 0, 1, 0} +// // B.GetCol(2) = {-s, 0, c} +// X = A*X; // U is VOR +// Y = B*Y; // V is VOR +// P0 = U*X; +// P1 = V*Y; +// diff = P0 - P1; // { 0, 0, 0 } +// +// // Affine change of basis. The first three columns of U are the basis +// // vectors for the source coordinate system and must have last components +// // set to 0. The last column is the origin for that system and must have +// // last component set to 1. A point X = { x0, x1, x2, 1 } in the source +// // coordinate system is represented by +// // X = x0*(-1,0,0,0) + x1*(0,0,1,0) + x2*(0,-1,0,0) + 1*(1,2,3,1) +// // The Cartesian coordinates for the point are the combination of these +// // terms, +// // X = (-x0 + 1, -x2 + 2, x1 + 3, 1) +// // The first three columns of V are the basis vectors for the target +// // coordinate system and must have last components set to 0. The last +// // column is the origin for that system and must have last component set +// // to 1. A point Y = { y0, y1, y2, 1 } in the target coordinate system is +// // represented by +// // Y = y0*(0,1,0,0) + y1*(-1,0,0,0) + y2*(0,0,1,0) + 1*(4,5,6,1) +// // The Cartesian coordinates for the point are the combination of these +// // terms, +// // Y = (-y1 + 4, y0 + 5, y2 + 6, 1) +// // The call Y = convert.UToV(X) computes y0, y1 and y2 so that the Cartesian +// // coordinates for X and for Y are the same. For example, +// // X = { -1.0, 4.0, -3.0, 1.0 } +// // = -1.0*(-1,0,0,0) + 4.0*(0,0,1,0) - 3.0*(0,-1,0,0) + 1.0*(1,2,3,1) +// // = (2, 5, 7, 1) +// // Y = { 0.0, 2.0, 1.0, 1.0 } +// // = 0.0*(0,1,0,0) + 2.0*(-1,0,0,0) + 1.0*(0,0,1,0) + 1.0*(4,5,6,1) +// // = (2, 5, 7, 1) +// // X and Y represent the same point (equal Cartesian coordinates) but have +// // different representations in the source and target affine coordinates. +// +// ConvertCoordinates<4, double> convert; +// Vector<4, double> X, Y, P0, P1, diff; +// Matrix<4, 4, double> U, V, A, B; +// bool isRHU, isRHV; +// U.SetCol(0, Vector4{-1.0, 0.0, 0.0, 0.0}); +// U.SetCol(1, Vector4{0.0, 0.0, 1.0, 0.0}); +// U.SetCol(2, Vector4{0.0, -1.0, 0.0, 0.0}); +// U.SetCol(3, Vector4{1.0, 2.0, 3.0, 1.0}); +// V.SetCol(0, Vector4{0.0, 1.0, 0.0, 0.0}); +// V.SetCol(1, Vector4{-1.0, 0.0, 0.0, 0.0}); +// V.SetCol(2, Vector4{0.0, 0.0, 1.0, 0.0}); +// V.SetCol(3, Vector4{4.0, 5.0, 6.0, 1.0}); +// convert(U, true, V, false); +// isRHU = convert.IsRightHandedU(); // false +// isRHV = convert.IsRightHandedV(); // true +// X = { -1.0, 4.0, -3.0, 1.0 }; +// Y = convert.UToV(X); // { 0.0, 2.0, 1.0, 1.0 } +// P0 = U*X; +// P1 = V*Y; +// diff = P0 - P1; // { 0, 0, 0, 0 } +// Y = { 1.0, 2.0, 3.0, 1.0 }; +// X = convert.VToU(Y); // { -1.0, 6.0, -4.0, 1.0 } +// P0 = U*X; +// P1 = V*Y; +// diff = P0 - P1; // { 0, 0, 0, 0 } +// double c = 0.6, s = 0.8; // c*c + s*s = 1 +// A.SetCol(0, Vector4{ c, s, 0.0, 0.0}); +// A.SetCol(1, Vector4{ -s, c, 0.0, 0.0}); +// A.SetCol(2, Vector4{0.0, 0.0, 1.0, 0.0}); +// A.SetCol(3, Vector4{0.3, 1.0, -2.0, 1.0}); +// B = convert.UToV(A); +// // B.GetCol(0) = { 1, 0, 0, 0 } +// // B.GetCol(1) = { 0, c, s, 0 } +// // B.GetCol(2) = { 0, -s, c, 0 } +// // B.GetCol(3) = { 2.0, -0.9, -2.6, 1 } +// X = A*X; // U is VOR +// Y = Y*B; // V is VOL (not VOR) +// P0 = U*X; +// P1 = V*Y; +// diff = P0 - P1; // { 0, 0, 0, 0 } + +namespace WwiseGTE +{ + template + class ConvertCoordinates + { + public: + // Construction of the change of basis matrix. The implementation + // supports both linear change of basis and affine change of basis. + ConvertCoordinates() + : + mIsVectorOnRightU(true), + mIsVectorOnRightV(true), + mIsRightHandedU(true), + mIsRightHandedV(true) + { + mC.MakeIdentity(); + mInverseC.MakeIdentity(); + } + + // Compute a change of basis between two coordinate systems. The + // return value is 'true' iff U and V are invertible. The + // matrix-vector multiplication conventions affect the conversion of + // matrix transformations. The Boolean inputs indicate how you want + // the matrices to be interpreted when applied as transformations of + // a vector. + bool operator()( + Matrix const& U, bool vectorOnRightU, + Matrix const& V, bool vectorOnRightV) + { + // Initialize in case of early exit. + mC.MakeIdentity(); + mInverseC.MakeIdentity(); + mIsVectorOnRightU = true; + mIsVectorOnRightV = true; + mIsRightHandedU = true; + mIsRightHandedV = true; + + Matrix inverseU; + Real determinantU; + bool invertibleU = GaussianElimination()(N, &U[0], &inverseU[0], + determinantU, nullptr, nullptr, nullptr, 0, nullptr); + if (!invertibleU) + { + return false; + } + + Matrix inverseV; + Real determinantV; + bool invertibleV = GaussianElimination()(N, &V[0], &inverseV[0], + determinantV, nullptr, nullptr, nullptr, 0, nullptr); + if (!invertibleV) + { + return false; + } + + mC = inverseU * V; + mInverseC = inverseV * U; + mIsVectorOnRightU = vectorOnRightU; + mIsVectorOnRightV = vectorOnRightV; + mIsRightHandedU = (determinantU > (Real)0); + mIsRightHandedV = (determinantV > (Real)0); + return true; + } + + // Member access. + inline Matrix const& GetC() const + { + return mC; + } + + inline Matrix const& GetInverseC() const + { + return mInverseC; + } + + inline bool IsVectorOnRightU() const + { + return mIsVectorOnRightU; + } + + inline bool IsVectorOnRightV() const + { + return mIsVectorOnRightV; + } + + inline bool IsRightHandedU() const + { + return mIsRightHandedU; + } + + inline bool IsRightHandedV() const + { + return mIsRightHandedV; + } + + // Convert points between coordinate systems. The names of the + // systems are U and V to make it clear which inputs of operator() + // they are associated with. The X vector stores coordinates for the + // U-system and the Y vector stores coordinates for the V-system. + + // Y = C^{-1}*X + inline Vector UToV(Vector const& X) const + { + return mInverseC * X; + } + + // X = C*Y + inline Vector VToU(Vector const& Y) const + { + return mC * Y; + } + + // Convert transformations between coordinate systems. The outputs + // are computed according to the tables shown before the function + // declarations. The superscript T denotes the transpose operator. + // vectorOnRightU = true: transformation is X' = A*X + // vectorOnRightU = false: transformation is (X')^T = X^T*A + // vectorOnRightV = true: transformation is Y' = B*Y + // vectorOnRightV = false: transformation is (Y')^T = Y^T*B + + // vectorOnRightU | vectorOnRightV | output + // ----------------+-----------------+--------------------- + // true | true | C^{-1} * A * C + // true | false | (C^{-1} * A * C)^T + // false | true | C^{-1} * A^T * C + // false | false | (C^{-1} * A^T * C)^T + Matrix UToV(Matrix const& A) const + { + Matrix product; + + if (mIsVectorOnRightU) + { + product = mInverseC * A * mC; + if (mIsVectorOnRightV) + { + return product; + } + else + { + return Transpose(product); + } + } + else + { + product = mInverseC * MultiplyATB(A, mC); + if (mIsVectorOnRightV) + { + return product; + } + else + { + return Transpose(product); + } + } + } + + // vectorOnRightU | vectorOnRightV | output + // ----------------+-----------------+--------------------- + // true | true | C * B * C^{-1} + // true | false | C * B^T * C^{-1} + // false | true | (C * B * C^{-1})^T + // false | false | (C * B^T * C^{-1})^T + Matrix VToU(Matrix const& B) const + { + // vectorOnRightU | vectorOnRightV | output + // ----------------+-----------------+--------------------- + // true | true | C * B * C^{-1} + // true | false | C * B^T * C^{-1} + // false | true | (C * B * C^{-1})^T + // false | false | (C * B^T * C^{-1})^T + Matrix product; + + if (mIsVectorOnRightV) + { + product = mC * B * mInverseC; + if (mIsVectorOnRightU) + { + return product; + } + else + { + return Transpose(product); + } + } + else + { + product = mC * MultiplyATB(B, mInverseC); + if (mIsVectorOnRightU) + { + return product; + } + else + { + return Transpose(product); + } + } + } + + private: + // C = U^{-1}*V, C^{-1} = V^{-1}*U + Matrix mC, mInverseC; + bool mIsVectorOnRightU, mIsVectorOnRightV; + bool mIsRightHandedU, mIsRightHandedV; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ConvexHull2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ConvexHull2.h new file mode 100644 index 0000000..8056961 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ConvexHull2.h @@ -0,0 +1,386 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +// Compute the convex hull of 2D points using a divide-and-conquer algorithm. +// This is an O(N log N) algorithm for N input points. The only way to ensure +// a correct result for the input vertices (assumed to be exact) is to choose +// ComputeType for exact rational arithmetic. You may use BSNumber. No +// divisions are performed in this computation, so you do not have to use +// BSRational. +// +// The worst-case choices of N for Real of type BSNumber or BSRational with +// integer storage UIntegerFP32 are listed in the next table. The numerical +// computations are encapsulated in PrimalQuery2::ToLineExtended. We +// recommend using only BSNumber, because no divisions are performed in the +// convex-hull computations. +// +// input type | compute type | N +// -----------+--------------+------ +// float | BSNumber | 18 +// double | BSNumber | 132 +// float | BSRational | 214 +// double | BSRational | 1587 + +#include +#include +#include +#include + +// Uncomment this to assert when an infinite loop is encountered in +// ConvexHull2::GetTangent. +//#define GTE_THROW_ON_CONVEXHULL2_INFINITE_LOOP + +namespace WwiseGTE +{ + template + class ConvexHull2 + { + public: + // The class is a functor to support computing the convex hull of + // multiple data sets using the same class object. + ConvexHull2() + : + mEpsilon((InputType)0), + mDimension(0), + mLine(Vector2::Zero(), Vector2::Zero()), + mNumPoints(0), + mNumUniquePoints(0), + mPoints(nullptr) + { + } + + // The input is the array of points whose convex hull is required. + // The epsilon value is used to determine the intrinsic dimensionality + // of the vertices (d = 0, 1, or 2). When epsilon is positive, the + // determination is fuzzy: points approximately the same point, + // approximately on a line, or planar. The return value is 'true' if + // and only if the hull/ construction is successful. + bool operator()(int numPoints, Vector2 const* points, InputType epsilon) + { + mEpsilon = std::max(epsilon, (InputType)0); + mDimension = 0; + mLine.origin = Vector2::Zero(); + mLine.direction = Vector2::Zero(); + mNumPoints = numPoints; + mNumUniquePoints = 0; + mPoints = points; + mMerged.clear(); + mHull.clear(); + + int i, j; + if (mNumPoints < 3) + { + // ConvexHull2 should be called with at least three points. + return false; + } + + IntrinsicsVector2 info(mNumPoints, mPoints, mEpsilon); + if (info.dimension == 0) + { + // mDimension is 0 + return false; + } + + if (info.dimension == 1) + { + // The set is (nearly) collinear. + mDimension = 1; + mLine = Line2(info.origin, info.direction[0]); + return false; + } + + mDimension = 2; + + // Compute the points for the queries. + mComputePoints.resize(mNumPoints); + mQuery.Set(mNumPoints, &mComputePoints[0]); + for (i = 0; i < mNumPoints; ++i) + { + for (j = 0; j < 2; ++j) + { + mComputePoints[i][j] = points[i][j]; + } + } + + // Sort the points. + mHull.resize(mNumPoints); + for (i = 0; i < mNumPoints; ++i) + { + mHull[i] = i; + } + std::sort(mHull.begin(), mHull.end(), + [points](int i0, int i1) + { + if (points[i0][0] < points[i1][0]) + { + return true; + } + if (points[i0][0] > points[i1][0]) + { + return false; + } + return points[i0][1] < points[i1][1]; + } + ); + + // Remove duplicates. + auto newEnd = std::unique(mHull.begin(), mHull.end(), + [points](int i0, int i1) + { + return points[i0] == points[i1]; + } + ); + mHull.erase(newEnd, mHull.end()); + mNumUniquePoints = static_cast(mHull.size()); + + // Use a divide-and-conquer algorithm. The merge step computes + // the convex hull of two convex polygons. + mMerged.resize(mNumUniquePoints); + int i0 = 0, i1 = mNumUniquePoints - 1; + GetHull(i0, i1); + mHull.resize(i1 - i0 + 1); + return true; + } + + // Dimensional information. If GetDimension() returns 1, the points + // lie on a line P+t*D (fuzzy comparison when epsilon > 0). You can + // sort these if you need a polyline output by projecting onto the + // line each vertex X = P+t*D, where t = Dot(D,X-P). + inline InputType GetEpsilon() const + { + return mEpsilon; + } + + inline int GetDimension() const + { + return mDimension; + } + + inline Line2 const& GetLine() const + { + return mLine; + } + + // Member access. + inline int GetNumPoints() const + { + return mNumPoints; + } + + inline int GetNumUniquePoints() const + { + return mNumUniquePoints; + } + + inline Vector2 const* GetPoints() const + { + return mPoints; + } + + inline PrimalQuery2 const& GetQuery() const + { + return mQuery; + } + + // The convex hull is a convex polygon whose vertices are listed in + // counterclockwise order. + inline std::vector const& GetHull() const + { + return mHull; + } + + private: + // Support for divide-and-conquer. + void GetHull(int& i0, int& i1) + { + int numVertices = i1 - i0 + 1; + if (numVertices > 1) + { + // Compute the middle index of input range. + int mid = (i0 + i1) / 2; + + // Compute the hull of subsets (mid-i0+1 >= i1-mid). + int j0 = i0, j1 = mid, j2 = mid + 1, j3 = i1; + GetHull(j0, j1); + GetHull(j2, j3); + + // Merge the convex hulls into a single convex hull. + Merge(j0, j1, j2, j3, i0, i1); + } + // else: The convex hull is a single point. + } + + void Merge(int j0, int j1, int j2, int j3, int& i0, int& i1) + { + // Subhull0 is to the left of subhull1 because of the initial + // sorting of the points by x-components. We need to find two + // mutually visible points, one on the left subhull and one on + // the right subhull. + int size0 = j1 - j0 + 1; + int size1 = j3 - j2 + 1; + + int i; + Vector2 p; + + // Find the right-most point of the left subhull. + Vector2 pmax0 = mComputePoints[mHull[j0]]; + int imax0 = j0; + for (i = j0 + 1; i <= j1; ++i) + { + p = mComputePoints[mHull[i]]; + if (pmax0 < p) + { + pmax0 = p; + imax0 = i; + } + } + + // Find the left-most point of the right subhull. + Vector2 pmin1 = mComputePoints[mHull[j2]]; + int imin1 = j2; + for (i = j2 + 1; i <= j3; ++i) + { + p = mComputePoints[mHull[i]]; + if (p < pmin1) + { + pmin1 = p; + imin1 = i; + } + } + + // Get the lower tangent to hulls (LL = lower-left, + // LR = lower-right). + int iLL = imax0, iLR = imin1; + GetTangent(j0, j1, j2, j3, iLL, iLR); + + // Get the upper tangent to hulls (UL = upper-left, + // UR = upper-right). + int iUL = imax0, iUR = imin1; + GetTangent(j2, j3, j0, j1, iUR, iUL); + + // Construct the counterclockwise-ordered merged-hull vertices. + int k; + int numMerged = 0; + + i = iUL; + for (k = 0; k < size0; ++k) + { + mMerged[numMerged++] = mHull[i]; + if (i == iLL) + { + break; + } + i = (i < j1 ? i + 1 : j0); + } + LogAssert(k < size0, "Unexpected condition."); + + i = iLR; + for (k = 0; k < size1; ++k) + { + mMerged[numMerged++] = mHull[i]; + if (i == iUR) + { + break; + } + i = (i < j3 ? i + 1 : j2); + } + LogAssert(k < size1, "Unexpected condition."); + + int next = j0; + for (k = 0; k < numMerged; ++k) + { + mHull[next] = mMerged[k]; + ++next; + } + + i0 = j0; + i1 = next - 1; + } + + void GetTangent(int j0, int j1, int j2, int j3, int& i0, int& i1) + { + // In theory the loop terminates in a finite number of steps, + // but the upper bound for the loop variable is used to trap + // problems caused by floating-point roundoff errors that might + // lead to an infinite loop. + + int size0 = j1 - j0 + 1; + int size1 = j3 - j2 + 1; + int const imax = size0 + size1; + int i, iLm1, iRp1; + Vector2 L0, L1, R0, R1; + + for (i = 0; i < imax; i++) + { + // Get the endpoints of the potential tangent. + L1 = mComputePoints[mHull[i0]]; + R0 = mComputePoints[mHull[i1]]; + + // Walk along the left hull to find the point of tangency. + if (size0 > 1) + { + iLm1 = (i0 > j0 ? i0 - 1 : j1); + L0 = mComputePoints[mHull[iLm1]]; + auto order = mQuery.ToLineExtended(R0, L0, L1); + if (order == PrimalQuery2::ORDER_NEGATIVE + || order == PrimalQuery2::ORDER_COLLINEAR_RIGHT) + { + i0 = iLm1; + continue; + } + } + + // Walk along right hull to find the point of tangency. + if (size1 > 1) + { + iRp1 = (i1 < j3 ? i1 + 1 : j2); + R1 = mComputePoints[mHull[iRp1]]; + auto order = mQuery.ToLineExtended(L1, R0, R1); + if (order == PrimalQuery2::ORDER_NEGATIVE + || order == PrimalQuery2::ORDER_COLLINEAR_LEFT) + { + i1 = iRp1; + continue; + } + } + + // The tangent segment has been found. + break; + } + + // Detect an "infinite loop" caused by floating point round-off + // errors. +#if defined(GTE_THROW_ON_CONVEXHULL2_INFINITE_LOOP) + LogAssert(i < imax, "Unexpected condition."); +#endif + } + + // The epsilon value is used for fuzzy determination of intrinsic + // dimensionality. If the dimension is 0 or 1, the constructor + // returns early. The caller is responsible for retrieving the + // dimension and taking an alternate path should the dimension be + // smaller than 2. If the dimension is 0, the caller may as well + // treat all points[] as a single point, say, points[0]. If the + // dimension is 1, the caller can query for the approximating line + // and project points[] onto it for further processing. + InputType mEpsilon; + int mDimension; + Line2 mLine; + + // The array of points used for geometric queries. If you want to + // be certain of a correct result, choose ComputeType to be BSNumber. + std::vector> mComputePoints; + PrimalQuery2 mQuery; + + int mNumPoints; + int mNumUniquePoints; + Vector2 const* mPoints; + std::vector mMerged, mHull; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ConvexHull3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ConvexHull3.h new file mode 100644 index 0000000..9accc12 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ConvexHull3.h @@ -0,0 +1,394 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +// Compute the convex hull of 3D points using incremental insertion. The only +// way to ensure a correct result for the input vertices (assumed to be exact) +// is to choose ComputeType for exact rational arithmetic. You may use +// BSNumber. No divisions are performed in this computation, so you do not +// have to use BSRational. +// +// The worst-case choices of N for Real of type BSNumber or BSRational with +// integer storage UIntegerFP32 are listed in the next table. The +// numerical computations are encapsulated in PrimalQuery3::ToPlane. +// We recommend using only BSNumber, because no divisions are performed in +// the convex-hull computations. +// +// input type | compute type | N +// -----------+--------------+------ +// float | BSNumber | 27 +// double | BSNumber | 197 +// float | BSRational | 2882 +// double | BSRational | 21688 + +#include +#include +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class ConvexHull3 + { + public: + // The class is a functor to support computing the convex hull of + // multiple data sets using the same class object. For + // multithreading in Update, choose 'numThreads' subject to the + // constraints 1 <= numThreads <= std::thread::hardware_concurrency(). + ConvexHull3(unsigned int numThreads = 1) + : + mEpsilon((InputType)0), + mDimension(0), + mLine(Vector3::Zero(), Vector3::Zero()), + mPlane(Vector3::Zero(), (InputType)0), + mNumPoints(0), + mNumUniquePoints(0), + mPoints(nullptr), + mNumThreads(numThreads) + { + } + + // The input is the array of points whose convex hull is required. + // The epsilon value is used to determine the intrinsic dimensionality + // of the vertices (d = 0, 1, 2, or 3). When epsilon is positive, the + // determination is fuzzy--points approximately the same point, + // approximately on a line, approximately planar, or volumetric. + bool operator()(int numPoints, Vector3 const* points, InputType epsilon) + { + mEpsilon = std::max(epsilon, (InputType)0); + mDimension = 0; + mLine.origin = Vector3::Zero(); + mLine.direction = Vector3::Zero(); + mPlane.normal = Vector3::Zero(); + mPlane.constant = (InputType)0; + mNumPoints = numPoints; + mNumUniquePoints = 0; + mPoints = points; + mHullUnordered.clear(); + mHullMesh.Clear(); + + int i, j; + if (mNumPoints < 4) + { + // ConvexHull3 should be called with at least four points. + return false; + } + + IntrinsicsVector3 info(mNumPoints, mPoints, mEpsilon); + if (info.dimension == 0) + { + // The set is (nearly) a point. + return false; + } + + if (info.dimension == 1) + { + // The set is (nearly) collinear. + mDimension = 1; + mLine = Line3(info.origin, info.direction[0]); + return false; + } + + if (info.dimension == 2) + { + // The set is (nearly) coplanar. + mDimension = 2; + mPlane = Plane3(UnitCross(info.direction[0], + info.direction[1]), info.origin); + return false; + } + + mDimension = 3; + + // Compute the vertices for the queries. + mComputePoints.resize(mNumPoints); + mQuery.Set(mNumPoints, &mComputePoints[0]); + for (i = 0; i < mNumPoints; ++i) + { + for (j = 0; j < 3; ++j) + { + mComputePoints[i][j] = points[i][j]; + } + } + + // Insert the faces of the (nondegenerate) tetrahedron + // constructed by the call to GetInformation. + if (!info.extremeCCW) + { + std::swap(info.extreme[2], info.extreme[3]); + } + + mHullUnordered.push_back(TriangleKey(info.extreme[1], + info.extreme[2], info.extreme[3])); + mHullUnordered.push_back(TriangleKey(info.extreme[0], + info.extreme[3], info.extreme[2])); + mHullUnordered.push_back(TriangleKey(info.extreme[0], + info.extreme[1], info.extreme[3])); + mHullUnordered.push_back(TriangleKey(info.extreme[0], + info.extreme[2], info.extreme[1])); + + // Incrementally update the hull. The set of processed points is + // maintained to eliminate duplicates, either in the original + // input points or in the points obtained by snap rounding. + std::set> processed; + for (i = 0; i < 4; ++i) + { + processed.insert(points[info.extreme[i]]); + } + for (i = 0; i < mNumPoints; ++i) + { + if (processed.find(points[i]) == processed.end()) + { + Update(i); + processed.insert(points[i]); + } + } + mNumUniquePoints = static_cast(processed.size()); + return true; + } + + // Dimensional information. If GetDimension() returns 1, the points + // lie on a line P+t*D (fuzzy comparison when epsilon > 0). You can + // sort these if you need a polyline output by projecting onto the + // line each vertex X = P+t*D, where t = Dot(D,X-P). If GetDimension() + // returns 2, the points line on a plane P+s*U+t*V (fuzzy comparison + // when epsilon > 0). You can project each point X = P+s*U+t*V, where + // s = Dot(U,X-P) and t = Dot(V,X-P), then apply ConvexHull2 to the + // (s,t) tuples. + inline InputType GetEpsilon() const + { + return mEpsilon; + } + + inline int GetDimension() const + { + return mDimension; + } + + inline Line3 const& GetLine() const + { + return mLine; + } + + inline Plane3 const& GetPlane() const + { + return mPlane; + } + + // Member access. + inline int GetNumPoints() const + { + return mNumPoints; + } + + inline int GetNumUniquePoints() const + { + return mNumUniquePoints; + } + + inline Vector3 const* GetPoints() const + { + return mPoints; + } + + inline PrimalQuery3 const& GetQuery() const + { + return mQuery; + } + + // The convex hull is a convex polyhedron with triangular faces. + inline std::vector> const& GetHullUnordered() const + { + return mHullUnordered; + } + + ETManifoldMesh const& GetHullMesh() const + { + // Create the mesh only on demand. + if (mHullMesh.GetTriangles().size() == 0) + { + for (auto const& tri : mHullUnordered) + { + mHullMesh.Insert(tri.V[0], tri.V[1], tri.V[2]); + } + } + + return mHullMesh; + } + + private: + // Support for incremental insertion. + void Update(int i) + { + // The terminator that separates visible faces from nonvisible + // faces is constructed by this code. Visible faces for the + // incoming hull are removed, and the boundary of that set of + // triangles is the terminator. New visible faces are added + // using the incoming point and the edges of the terminator. + // + // A simple algorithm for computing terminator edges is the + // following. Back-facing triangles are located and the three + // edges are processed. The first time an edge is visited, + // insert it into the terminator. If it is visited a second time, + // the edge is removed because it is shared by another back-facing + // triangle and, therefore, cannot be a terminator edge. After + // visiting all back-facing triangles, the only remaining edges in + // the map are the terminator edges. + // + // The order of vertices of an edge is important for adding new + // faces with the correct vertex winding. However, the edge + // "toggle" (insert edge, remove edge) should use edges with + // unordered vertices, because the edge shared by one triangle has + // opposite ordering relative to that of the other triangle. The + // map uses unordered edges as the keys but stores the ordered + // edge as the value. This avoids having to look up an edge twice + // in a map with ordered edge keys. + + unsigned int numFaces = static_cast(mHullUnordered.size()); + std::vector queryResult(numFaces); + if (mNumThreads > 1 && numFaces >= mNumThreads) + { + // Partition the data for multiple threads. + unsigned int numFacesPerThread = numFaces / mNumThreads; + std::vector jmin(mNumThreads), jmax(mNumThreads); + for (unsigned int t = 0; t < mNumThreads; ++t) + { + jmin[t] = t * numFacesPerThread; + jmax[t] = jmin[t] + numFacesPerThread - 1; + } + jmax[mNumThreads - 1] = numFaces - 1; + + // Execute the point-plane queries in multiple threads. + std::vector process(mNumThreads); + for (unsigned int t = 0; t < mNumThreads; ++t) + { + process[t] = std::thread([this, i, t, &jmin, &jmax, + &queryResult]() + { + for (unsigned int j = jmin[t]; j <= jmax[t]; ++j) + { + TriangleKey const& tri = mHullUnordered[j]; + queryResult[j] = mQuery.ToPlane(i, tri.V[0], tri.V[1], tri.V[2]); + } + }); + } + + // Wait for all threads to finish. + for (unsigned int t = 0; t < mNumThreads; ++t) + { + process[t].join(); + } + } + else + { + unsigned int j = 0; + for (auto const& tri : mHullUnordered) + { + queryResult[j++] = mQuery.ToPlane(i, tri.V[0], tri.V[1], tri.V[2]); + } + } + + std::map, std::pair> terminator; + std::vector> backFaces; + bool existsFrontFacingTriangle = false; + unsigned int j = 0; + for (auto const& tri : mHullUnordered) + { + if (queryResult[j++] <= 0) + { + // The triangle is back facing. These include triangles + // that are coplanar with the incoming point. + backFaces.push_back(tri); + + // The current hull is a 2-manifold watertight mesh. The + // terminator edges are those shared with a front-facing + // triangle. The first time an edge of a back-facing + // triangle is visited, insert it into the terminator. If + // it is visited a second time, the edge is removed + // because it is shared by another back-facing triangle. + // After all back-facing triangles are visited, the only + // remaining edges are shared by a single back-facing + // triangle, which makes them terminator edges. + for (int j0 = 2, j1 = 0; j1 < 3; j0 = j1++) + { + int v0 = tri.V[j0], v1 = tri.V[j1]; + EdgeKey edge(v0, v1); + auto iter = terminator.find(edge); + if (iter == terminator.end()) + { + // The edge is visited for the first time. + terminator.insert(std::make_pair(edge, std::make_pair(v0, v1))); + } + else + { + // The edge is visited for the second time. + terminator.erase(edge); + } + } + } + else + { + // If there are no strictly front-facing triangles, then + // the incoming point is inside or on the convex hull. If + // we get to this code, then the point is truly outside + // and we can update the hull. + existsFrontFacingTriangle = true; + } + } + + if (!existsFrontFacingTriangle) + { + // The incoming point is inside or on the current hull, so no + // update of the hull is necessary. + return; + } + + // The updated hull contains the triangles not visible to the + // incoming point. + mHullUnordered = backFaces; + + // Insert the triangles formed by the incoming point and the + // terminator edges. + for (auto const& edge : terminator) + { + mHullUnordered.push_back(TriangleKey(i, edge.second.second, edge.second.first)); + } + } + + // The epsilon value is used for fuzzy determination of intrinsic + // dimensionality. If the dimension is 0, 1, or 2, the constructor + // returns early. The caller is responsible for retrieving the + // dimension and taking an alternate path should the dimension be + // smaller than 3. If the dimension is 0, the caller may as well + // treat all points[] as a single point, say, points[0]. If the + // dimension is 1, the caller can query for the approximating line + // and project points[] onto it for further processing. If the + // dimension is 2, the caller can query for the approximating plane + // and project points[] onto it for further processing. + InputType mEpsilon; + int mDimension; + Line3 mLine; + Plane3 mPlane; + + // The array of points used for geometric queries. If you want to be + // certain of a correct result, choose ComputeType to be BSNumber. + std::vector> mComputePoints; + PrimalQuery3 mQuery; + + int mNumPoints; + int mNumUniquePoints; + Vector3 const* mPoints; + std::vector> mHullUnordered; + mutable ETManifoldMesh mHullMesh; + unsigned int mNumThreads; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ConvexPolyhedron3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ConvexPolyhedron3.h new file mode 100644 index 0000000..213bcfc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ConvexPolyhedron3.h @@ -0,0 +1,98 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class ConvexPolyhedron3 + { + public: + // Construction. The convex polyhedra represented by this class has + // triangle faces that are counterclockwise ordered when viewed from + // outside the polyhedron. No attempt is made to verify that the + // polyhedron is convex; the caller is responsible for enforcing this. + // The constructors move (not copy!) the input arrays. The + // constructor succeeds when the number of vertices is at least 4 and + // the number of indices is at least 12. If the constructor fails, + // no move occurs and the member arrays have no elements. + // + // To support geometric algorithms that are formulated using convex + // quadratic programming (such as computing the distance from a point + // to a convex polyhedron), it is necessary to know the planes of the + // faces and an axis-aligned bounding box. If you want either the + // faces or the box, pass 'true' to the appropriate parameters. When + // planes are generated, the normals are not created to be unit length + // in order to support queries using exact rational arithmetic. If a + // normal to a face is N = (n0,n1,n2) and V is a vertex of the face, + // the plane is Dot(N,X-V) = 0 and is stored as + // Dot(n0,n1,n2,-Dot(N,V)). The normals are computed to be outer + // pointing. + ConvexPolyhedron3() = default; + + ConvexPolyhedron3(std::vector>&& inVertices, std::vector&& inIndices, + bool wantPlanes, bool wantAlignedBox) + { + if (inVertices.size() >= 4 && inIndices.size() >= 12) + { + vertices = std::move(inVertices); + indices = std::move(inIndices); + + if (wantPlanes) + { + GeneratePlanes(); + } + + if (wantAlignedBox) + { + GenerateAlignedBox(); + } + } + } + + // If you modifty the vertices or indices and you want the new face + // planes or aligned box computed, call these functions. + void GeneratePlanes() + { + if (vertices.size() > 0 && indices.size() > 0) + { + uint32_t const numTriangles = static_cast(indices.size()) / 3; + planes.resize(numTriangles); + for (uint32_t t = 0, i = 0; t < numTriangles; ++t) + { + Vector3 V0 = vertices[indices[i++]]; + Vector3 V1 = vertices[indices[i++]]; + Vector3 V2 = vertices[indices[i++]]; + Vector3 E1 = V1 - V0; + Vector3 E2 = V2 - V0; + Vector3 N = Cross(E1, E2); + planes[t] = HLift(N, -Dot(N, V0)); + } + } + } + + void GenerateAlignedBox() + { + if (vertices.size() > 0 && indices.size() > 0) + { + ComputeExtremes(static_cast(vertices.size()), vertices.data(), + alignedBox.min, alignedBox.max); + } + } + + std::vector> vertices; + std::vector indices; + std::vector> planes; + AlignedBox3 alignedBox; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CosEstimate.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CosEstimate.h new file mode 100644 index 0000000..8e4ea18 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CosEstimate.h @@ -0,0 +1,139 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// Minimax polynomial approximations to cos(x). The polynomial p(x) of +// degree D has only even-power terms, is required to have constant term 1, +// and p(pi/2) = cos(pi/2) = 0. It minimizes the quantity +// maximum{|cos(x) - p(x)| : x in [-pi/2,pi/2]} over all polynomials of +// degree D subject to the constraints mentioned. + +namespace WwiseGTE +{ + template + class CosEstimate + { + public: + // The input constraint is x in [-pi/2,pi/2]. For example, + // float x; // in [-pi/2,pi/2] + // float result = CosEstimate::Degree<4>(x); + template + inline static Real Degree(Real x) + { + return Evaluate(degree(), x); + } + + // The input x can be any real number. Range reduction is used to + // generate a value y in [-pi/2,pi/2] and a sign s for which + // cos(y) = s*cos(x). For example, + // float x; // x any real number + // float result = CosEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x) + { + Real y, sign; + Reduce(x, y, sign); + Real poly = sign * Degree(y); + return poly; + } + + private: + // Metaprogramming and private implementation to allow specialization + // of a template member function. + template struct degree {}; + + inline static Real Evaluate(degree<2>, Real x) + { + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_COS_DEG2_C1; + poly = (Real)GTE_C_COS_DEG2_C0 + poly * xsqr; + return poly; + } + + inline static Real Evaluate(degree<4>, Real x) + { + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_COS_DEG4_C2; + poly = (Real)GTE_C_COS_DEG4_C1 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG4_C0 + poly * xsqr; + return poly; + } + + inline static Real Evaluate(degree<6>, Real x) + { + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_COS_DEG6_C3; + poly = (Real)GTE_C_COS_DEG6_C2 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG6_C1 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG6_C0 + poly * xsqr; + return poly; + } + + inline static Real Evaluate(degree<8>, Real x) + { + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_COS_DEG8_C4; + poly = (Real)GTE_C_COS_DEG8_C3 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG8_C2 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG8_C1 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG8_C0 + poly * xsqr; + return poly; + } + + inline static Real Evaluate(degree<10>, Real x) + { + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_COS_DEG10_C5; + poly = (Real)GTE_C_COS_DEG10_C4 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG10_C3 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG10_C2 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG10_C1 + poly * xsqr; + poly = (Real)GTE_C_COS_DEG10_C0 + poly * xsqr; + return poly; + } + + // Support for range reduction. + inline static void Reduce(Real x, Real& y, Real& sign) + { + // Map x to y in [-pi,pi], x = 2*pi*quotient + remainder. + Real quotient = (Real)GTE_C_INV_TWO_PI * x; + if (x >= (Real)0) + { + quotient = (Real)((int)(quotient + (Real)0.5)); + } + else + { + quotient = (Real)((int)(quotient - (Real)0.5)); + } + y = x - (Real)GTE_C_TWO_PI * quotient; + + // Map y to [-pi/2,pi/2] with cos(y) = sign*cos(x). + if (y > (Real)GTE_C_HALF_PI) + { + y = (Real)GTE_C_PI - y; + sign = (Real)-1; + } + else if (y < (Real)-GTE_C_HALF_PI) + { + y = (Real)-GTE_C_PI - y; + sign = (Real)-1; + } + else + { + sign = (Real)1; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CubicRootsQR.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CubicRootsQR.h new file mode 100644 index 0000000..7004151 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CubicRootsQR.h @@ -0,0 +1,216 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// An implementation of the QR algorithm described in "Matrix Computations, +// 2nd edition" by G. H. Golub and C. F. Van Loan, The Johns Hopkins +// University Press, Baltimore MD, Fourth Printing 1993. In particular, +// the implementation is based on Chapter 7 (The Unsymmetric Eigenvalue +// Problem), Section 7.5 (The Practical QR Algorithm). The algorithm is +// specialized for the companion matrix associated with a cubic polynomial. + +namespace WwiseGTE +{ + template + class CubicRootsQR + { + public: + typedef std::array, 3> Matrix; + + // Solve p(x) = c0 + c1 * x + c2 * x^2 + x^3 = 0. + uint32_t operator() (uint32_t maxIterations, Real c0, Real c1, Real c2, + uint32_t& numRoots, std::array& roots) const + { + // Create the companion matrix for the polynomial. The matrix is + // in upper Hessenberg form. + Matrix A; + A[0][0] = (Real)0; + A[0][1] = (Real)0; + A[0][2] = -c0; + A[1][0] = (Real)1; + A[1][1] = (Real)0; + A[1][2] = -c1; + A[2][0] = (Real)0; + A[2][1] = (Real)1; + A[2][2] = -c2; + + // Avoid the QR-cycle when c1 = c2 = 0 and avoid the slow + // convergence when c1 and c2 are nearly zero. + std::array V{ + (Real)1, + (Real)0.36602540378443865, + (Real)0.36602540378443865 }; + DoIteration(V, A); + + return operator()(maxIterations, A, numRoots, roots); + } + + // Compute the real eigenvalues of the upper Hessenberg matrix A. The + // matrix is modified by in-place operations, so if you need to remember + // A, you must make your own copy before calling this function. + uint32_t operator() (uint32_t maxIterations, Matrix& A, + uint32_t& numRoots, std::array& roots) const + { + numRoots = 0; + std::fill(roots.begin(), roots.end(), (Real)0); + + for (uint32_t numIterations = 0; numIterations < maxIterations; ++numIterations) + { + // Apply a Francis QR iteration. + Real tr = A[1][1] + A[2][2]; + Real det = A[1][1] * A[2][2] - A[1][2] * A[2][1]; + std::array X{ + A[0][0] * A[0][0] + A[0][1] * A[1][0] - tr * A[0][0] + det, + A[1][0] * (A[0][0] + A[1][1] - tr), + A[1][0] * A[2][1] }; + std::array V = House<3>(X); + DoIteration(V, A); + + // Test for uncoupling of A. + Real tr01 = A[0][0] + A[1][1]; + if (tr01 + A[1][0] == tr01) + { + numRoots = 1; + roots[0] = A[0][0]; + GetQuadraticRoots(1, 2, A, numRoots, roots); + return numIterations; + } + + Real tr12 = A[1][1] + A[2][2]; + if (tr12 + A[2][1] == tr12) + { + numRoots = 1; + roots[0] = A[2][2]; + GetQuadraticRoots(0, 1, A, numRoots, roots); + return numIterations; + } + } + return maxIterations; + } + + private: + void DoIteration(std::array const& V, Matrix& A) const + { + Real multV = (Real)-2 / (V[0] * V[0] + V[1] * V[1] + V[2] * V[2]); + std::array MV{ multV * V[0], multV * V[1], multV * V[2] }; + RowHouse<3>(0, 2, 0, 2, V, MV, A); + ColHouse<3>(0, 2, 0, 2, V, MV, A); + + std::array Y{ A[1][0], A[2][0] }; + std::array W = House<2>(Y); + Real multW = (Real)-2 / (W[0] * W[0] + W[1] * W[1]); + std::array MW{ multW * W[0], multW * W[1] }; + RowHouse<2>(1, 2, 0, 2, W, MW, A); + ColHouse<2>(0, 2, 1, 2, W, MW, A); + } + + template + std::array House(std::array const& X) const + { + std::array V; + Real length = (Real)0; + for (int i = 0; i < N; ++i) + { + length += X[i] * X[i]; + } + length = std::sqrt(length); + if (length != (Real)0) + { + Real sign = (X[0] >= (Real)0 ? (Real)1 : (Real)-1); + Real denom = X[0] + sign * length; + for (int i = 1; i < N; ++i) + { + V[i] = X[i] / denom; + } + } + else + { + V.fill((Real)0); + } + V[0] = (Real)1; + return V; + } + + template + void RowHouse(int rmin, int rmax, int cmin, int cmax, + std::array const& V, std::array const& MV, Matrix& A) const + { + // Only the elements cmin through cmax are used. + std::array W; + + for (int c = cmin; c <= cmax; ++c) + { + W[c] = (Real)0; + for (int r = rmin, k = 0; r <= rmax; ++r, ++k) + { + W[c] += V[k] * A[r][c]; + } + } + + for (int r = rmin, k = 0; r <= rmax; ++r, ++k) + { + for (int c = cmin; c <= cmax; ++c) + { + A[r][c] += MV[k] * W[c]; + } + } + } + + template + void ColHouse(int rmin, int rmax, int cmin, int cmax, + std::array const& V, std::array const& MV, Matrix& A) const + { + // Only elements rmin through rmax are used. + std::array W; + + for (int r = rmin; r <= rmax; ++r) + { + W[r] = (Real)0; + for (int c = cmin, k = 0; c <= cmax; ++c, ++k) + { + W[r] += V[k] * A[r][c]; + } + } + + for (int r = rmin; r <= rmax; ++r) + { + for (int c = cmin, k = 0; c <= cmax; ++c, ++k) + { + A[r][c] += W[r] * MV[k]; + } + } + } + + void GetQuadraticRoots(int i0, int i1, Matrix const& A, + uint32_t& numRoots, std::array& roots) const + { + // Solve x^2 - t * x + d = 0, where t is the trace and d is the + // determinant of the 2x2 matrix defined by indices i0 and i1. + // The discriminant is D = (t/2)^2 - d. When D >= 0, the roots + // are real values t/2 - sqrt(D) and t/2 + sqrt(D). To avoid + // potential numerical issues with subtractive cancellation, the + // roots are computed as + // r0 = t/2 + sign(t/2)*sqrt(D), r1 = trace - r0. + Real trace = A[i0][i0] + A[i1][i1]; + Real halfTrace = trace * (Real)0.5; + Real determinant = A[i0][i0] * A[i1][i1] - A[i0][i1] * A[i1][i0]; + Real discriminant = halfTrace * halfTrace - determinant; + if (discriminant >= (Real)0) + { + Real sign = (trace >= (Real)0 ? (Real)1 : (Real)-1); + Real root = halfTrace + sign * std::sqrt(discriminant); + roots[numRoots++] = root; + roots[numRoots++] = trace - root; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CurvatureFlow2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CurvatureFlow2.h new file mode 100644 index 0000000..1f8553c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CurvatureFlow2.h @@ -0,0 +1,56 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2020.01.11 + +#pragma once + +#include + +namespace WwiseGTE +{ + template + class CurvatureFlow2 : public PdeFilter2 + { + public: + CurvatureFlow2(int xBound, int yBound, Real xSpacing, + Real ySpacing, Real const* data, bool const* mask, + Real borderValue, typename PdeFilter::ScaleType scaleType) + : + PdeFilter2(xBound, yBound, xSpacing, ySpacing, data, mask, + borderValue, scaleType) + { + } + + virtual ~CurvatureFlow2() + { + } + + protected: + virtual void OnUpdateSingle(int x, int y) override + { + this->LookUp9(x, y); + + Real ux = this->mHalfInvDx * (this->mUpz - this->mUmz); + Real uy = this->mHalfInvDy * (this->mUzp - this->mUzm); + Real uxx = this->mInvDxDx * (this->mUpz - (Real)2 * this->mUzz + this->mUmz); + Real uxy = this->mFourthInvDxDy * (this->mUmm + this->mUpp - this->mUmp - this->mUpm); + Real uyy = this->mInvDyDy * (this->mUzp - (Real)2 * this->mUzz + this->mUzm); + + Real sqrUx = ux * ux; + Real sqrUy = uy * uy; + Real denom = sqrUx + sqrUy; + if (denom > (Real)0) + { + Real numer = uxx * sqrUy + uyy * sqrUx - (Real)0.5 * uxy * ux * uy; + this->mBuffer[this->mDst][y][x] = this->mUzz + this->mTimeStep * numer / denom; + } + else + { + this->mBuffer[this->mDst][y][x] = this->mUzz; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CurvatureFlow3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CurvatureFlow3.h new file mode 100644 index 0000000..0fedaac --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CurvatureFlow3.h @@ -0,0 +1,61 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2020.01.11 + +#pragma once + +#include + +namespace WwiseGTE +{ + template + class CurvatureFlow3 : public PdeFilter3 + { + public: + CurvatureFlow3(int xBound, int yBound, int zBound, Real xSpacing, + Real ySpacing, Real zSpacing, Real const* data, bool const* mask, + Real borderValue, typename PdeFilter::ScaleType scaleType) + : + PdeFilter3(xBound, yBound, zBound, xSpacing, ySpacing, + zSpacing, data, mask, borderValue, scaleType) + { + } + + virtual ~CurvatureFlow3() + { + } + + protected: + virtual void OnUpdateSingle(int x, int y, int z) override + { + this->LookUp27(x, y, z); + + Real ux = this->mHalfInvDx * (this->mUpzz - this->mUmzz); + Real uy = this->mHalfInvDy * (this->mUzpz - this->mUzmz); + Real uz = this->mHalfInvDz * (this->mUzzp - this->mUzzm); + Real uxx = this->mInvDxDx * (this->mUpzz - (Real)2 * this->mUzzz + this->mUmzz); + Real uxy = this->mFourthInvDxDy * (this->mUmmz + this->mUppz - this->mUpmz - this->mUmpz); + Real uxz = this->mFourthInvDxDz * (this->mUmzm + this->mUpzp - this->mUpzm - this->mUmzp); + Real uyy = this->mInvDyDy * (this->mUzpz - (Real)2 * this->mUzzz + this->mUzmz); + Real uyz = this->mFourthInvDyDz * (this->mUzmm + this->mUzpp - this->mUzpm - this->mUzmp); + Real uzz = this->mInvDzDz * (this->mUzzp - (Real)2 * this->mUzzz + this->mUzzm); + + Real denom = ux * ux + uy * uy + uz * uz; + if (denom > (Real)0) + { + Real numer0 = uy * (uxx*uy - uxy * ux) + ux * (uyy*ux - uxy * uy); + Real numer1 = uz * (uxx*uz - uxz * ux) + ux * (uzz*ux - uxz * uz); + Real numer2 = uz * (uyy*uz - uyz * uy) + uy * (uzz*uy - uyz * uz); + Real numer = numer0 + numer1 + numer2; + this->mBuffer[this->mDst][z][y][x] = this->mUzzz + this->mTimeStep * numer / denom; + } + else + { + this->mBuffer[this->mDst][z][y][x] = this->mUzzz; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CurveExtractor.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CurveExtractor.h new file mode 100644 index 0000000..81b5e2a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CurveExtractor.h @@ -0,0 +1,282 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + // The image type T must be one of the integer types: int8_t, int16_t, + // int32_t, uint8_t, uint16_t or uint32_t. Internal integer computations + // are performed using int64_t. The type Real is for extraction to + // floating-point vertices. + template + class CurveExtractor + { + public: + // Abstract base class. + virtual ~CurveExtractor() = default; + + // The level curves form a graph of vertices and edges. The vertices + // are computed as pairs of nonnegative rational numbers. Vertex + // represents the rational pair (xNumer/xDenom, yNumer/yDenom) as + // (xNumer, xDenom, yNumer, yDenom), where all components are + // nonnegative. The edges connect pairs of vertices, forming a graph + // that represents the level set. + struct Vertex + { + Vertex() = default; + + Vertex(int64_t inXNumer, int64_t inXDenom, int64_t inYNumer, int64_t inYDenom) + { + // The vertex generation leads to the numerator and + // denominator having the same sign. This constructor changes + // sign to ensure the numerator and denominator are both + // positive. + if (inXDenom > 0) + { + xNumer = inXNumer; + xDenom = inXDenom; + } + else + { + xNumer = -inXNumer; + xDenom = -inXDenom; + } + + if (inYDenom > 0) + { + yNumer = inYNumer; + yDenom = inYDenom; + } + else + { + yNumer = -inYNumer; + yDenom = -inYDenom; + } + } + + // The non-default constructor guarantees that xDenom > 0 and + // yDenom > 0. The following comparison operators assume that + // the denominators are positive. + bool operator==(Vertex const& other) const + { + return + // xn0 / xd0 == xn1 / xd1 + xNumer * other.xDenom == other.xNumer * xDenom + && + // yn0/yd0 == yn1/yd1 + yNumer * other.yDenom == other.yNumer * yDenom; + } + + bool operator<(Vertex const& other) const + { + int64_t xn0txd1 = xNumer * other.xDenom; + int64_t xn1txd0 = other.xNumer * xDenom; + if (xn0txd1 < xn1txd0) + { + // xn0/xd0 < xn1/xd1 + return true; + } + if (xn0txd1 > xn1txd0) + { + // xn0/xd0 > xn1/xd1 + return false; + } + + int64_t yn0tyd1 = yNumer * other.yDenom; + int64_t yn1tyd0 = other.yNumer * yDenom; + // yn0/yd0 < yn1/yd1 + return yn0tyd1 < yn1tyd0; + } + + int64_t xNumer, xDenom, yNumer, yDenom; + }; + + struct Edge + { + Edge() = default; + + Edge(int v0, int v1) + { + if (v0 < v1) + { + v[0] = v0; + v[1] = v1; + } + else + { + v[0] = v1; + v[1] = v0; + } + } + + bool operator==(Edge const& other) const + { + return v[0] == other.v[0] && v[1] == other.v[1]; + } + + bool operator<(Edge const& other) const + { + for (int i = 0; i < 2; ++i) + { + if (v[i] < other.v[i]) + { + return true; + } + if (v[i] > other.v[i]) + { + return false; + } + } + return false; + } + + std::array v; + }; + + // Extract level curves and return rational vertices. + virtual void Extract(T level, std::vector& vertices, + std::vector& edges) = 0; + + void Extract(T level, bool removeDuplicateVertices, + std::vector>& vertices, std::vector& edges) + { + std::vector rationalVertices; + Extract(level, rationalVertices, edges); + if (removeDuplicateVertices) + { + MakeUnique(rationalVertices, edges); + } + Convert(rationalVertices, vertices); + } + + // The extraction has duplicate vertices on edges shared by pixels. + // This function will eliminate the duplicates. + void MakeUnique(std::vector& vertices, std::vector& edges) + { + size_t numVertices = vertices.size(); + size_t numEdges = edges.size(); + if (numVertices == 0 || numEdges == 0) + { + return; + } + + // Compute the map of unique vertices and assign to them new and + // unique indices. + std::map vmap; + int nextVertex = 0; + for (size_t v = 0; v < numVertices; ++v) + { + // Keep only unique vertices. + auto result = vmap.insert(std::make_pair(vertices[v], nextVertex)); + if (result.second) + { + ++nextVertex; + } + } + + // Compute the map of unique edges and assign to them new and + // unique indices. + std::map emap; + int nextEdge = 0; + for (size_t e = 0; e < numEdges; ++e) + { + // Replace old vertex indices by new vertex indices. + Edge& edge = edges[e]; + for (int i = 0; i < 2; ++i) + { + auto iter = vmap.find(vertices[edge.v[i]]); + LogAssert(iter != vmap.end(), "Expecting the vertex to be in the vmap."); + edge.v[i] = iter->second; + } + + // Keep only unique edges. + auto result = emap.insert(std::make_pair(edge, nextEdge)); + if (result.second) + { + ++nextEdge; + } + } + + // Pack the vertices into an array. + vertices.resize(vmap.size()); + for (auto const& element : vmap) + { + vertices[element.second] = element.first; + } + + // Pack the edges into an array. + edges.resize(emap.size()); + for (auto const& element : emap) + { + edges[element.second] = element.first; + } + } + + // Convert from Vertex to std::array rationals. + void Convert(std::vector const& input, std::vector>& output) + { + output.resize(input.size()); + for (size_t i = 0; i < input.size(); ++i) + { + Real rxNumer = static_cast(input[i].xNumer); + Real rxDenom = static_cast(input[i].xDenom); + Real ryNumer = static_cast(input[i].yNumer); + Real ryDenom = static_cast(input[i].yDenom); + output[i][0] = rxNumer / rxDenom; + output[i][1] = ryNumer / ryDenom; + } + } + + protected: + // The input is a 2D image with lexicographically ordered pixels (x,y) + // stored in a linear array. Pixel (x,y) is stored in the array at + // location index = x + xBound * y. The inputs xBound and yBound must + // each be 2 or larger so that there is at least one image square to + // process. The inputPixels must be nonnull and point to contiguous + // storage that contains at least xBound * yBound elements. + CurveExtractor(int xBound, int yBound, T const* inputPixels) + : + mXBound(xBound), + mYBound(yBound), + mInputPixels(inputPixels) + { + static_assert(std::is_integral::value && sizeof(T) <= 4, + "Type T must be int{8,16,32}_t or uint{8,16,32}_t."); + LogAssert(mXBound > 1 && mYBound > 1 && mInputPixels != nullptr, "Invalid input."); + mPixels.resize(static_cast(mXBound * mYBound)); + } + + void AddVertex(std::vector& vertices, + int64_t xNumer, int64_t xDenom, int64_t yNumer, int64_t yDenom) + { + vertices.push_back(Vertex(xNumer, xDenom, yNumer, yDenom)); + } + + void AddEdge(std::vector& vertices, std::vector& edges, + int64_t xNumer0, int64_t xDenom0, int64_t yNumer0, int64_t yDenom0, + int64_t xNumer1, int64_t xDenom1, int64_t yNumer1, int64_t yDenom1) + { + int v0 = static_cast(vertices.size()); + int v1 = v0 + 1; + edges.push_back(Edge(v0, v1)); + vertices.push_back(Vertex(xNumer0, xDenom0, yNumer0, yDenom0)); + vertices.push_back(Vertex(xNumer1, xDenom1, yNumer1, yDenom1)); + } + + int mXBound, mYBound; + T const* mInputPixels; + std::vector mPixels; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CurveExtractorSquares.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CurveExtractorSquares.h new file mode 100644 index 0000000..0b0e63b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CurveExtractorSquares.h @@ -0,0 +1,470 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The level set extraction algorithm implemented here is described +// in Section 3 of the document +// https://www.geometrictools.com/Documentation/ExtractLevelCurves.pdf + +namespace WwiseGTE +{ + // The image type T must be one of the integer types: int8_t, int16_t, + // int32_t, uint8_t, uint16_t or uint32_t. Internal integer computations + // are performed using int64_t. The type Real is for extraction to + // floating-point vertices. + template + class CurveExtractorSquares : public CurveExtractor + { + public: + // Convenience type definitions. + typedef typename CurveExtractor::Vertex Vertex; + typedef typename CurveExtractor::Edge Edge; + + // The input is a 2D image with lexicographically ordered pixels (x,y) + // stored in a linear array. Pixel (x,y) is stored in the array at + // location index = x + xBound * y. The inputs xBound and yBound must + // each be 2 or larger so that there is at least one image square to + // process. The inputPixels must be nonnull and point to contiguous + // storage that contains at least xBound * yBound elements. + CurveExtractorSquares(int xBound, int yBound, T const* inputPixels) + : + CurveExtractor(xBound, yBound, inputPixels) + { + } + + // Extract level curves and return rational vertices. Use the + // base-class Extract if you want real-valued vertices. + virtual void Extract(T level, std::vector& vertices, + std::vector& edges) override + { + // Adjust the image so that the level set is F(x,y) = 0. + int64_t levelI64 = static_cast(level); + for (size_t i = 0; i < this->mPixels.size(); ++i) + { + int64_t inputI64 = static_cast(this->mInputPixels[i]); + this->mPixels[i] = inputI64 - levelI64; + } + + vertices.clear(); + edges.clear(); + for (int y = 0, yp = 1; yp < this->mYBound; ++y, ++yp) + { + for (int x = 0, xp = 1; xp < this->mXBound; ++x, ++xp) + { + // Get the image values at the corners of the square. + int i00 = x + this->mXBound * y; + int i10 = i00 + 1; + int i01 = i00 + this->mXBound; + int i11 = i10 + this->mXBound; + int64_t f00 = this->mPixels[i00]; + int64_t f10 = this->mPixels[i10]; + int64_t f01 = this->mPixels[i01]; + int64_t f11 = this->mPixels[i11]; + + // Construct the vertices and edges of the level curve in + // the square. The x, xp, y and yp values are implicitly + // converted from int to int64_t (which is guaranteed to + // be correct). + ProcessSquare(vertices, edges, x, xp, y, yp, f00, f10, f11, f01); + } + } + } + + protected: + void ProcessSquare(std::vector& vertices, std::vector& edges, + int64_t x, int64_t xp, int64_t y, int64_t yp, + int64_t f00, int64_t f10, int64_t f11, int64_t f01) + { + int64_t xn0, yn0, xn1, yn1, d0, d1, d2, d3, det; + + if (f00 != 0) + { + // convert to case "+***" + if (f00 < 0) + { + f00 = -f00; + f10 = -f10; + f11 = -f11; + f01 = -f01; + } + + if (f10 > 0) + { + if (f11 > 0) + { + if (f01 > 0) + { + // ++++ + return; + } + else if (f01 < 0) + { + // +++- + d0 = f11 - f01; + xn0 = f11 * x - f01 * xp; + d1 = f00 - f01; + yn1 = f00 * yp - f01 * y; + this->AddEdge(vertices, edges, xn0, d0, yp, 1, x, 1, yn1, d1); + } + else + { + // +++0 + this->AddVertex(vertices, x, 1, yp, 1); + } + } + else if (f11 < 0) + { + d0 = f10 - f11; + yn0 = f10 * yp - f11 * y; + + if (f01 > 0) + { + // ++-+ + d1 = f01 - f11; + xn1 = f01 * xp - f11 * x; + this->AddEdge(vertices, edges, xp, 1, yn0, d0, xn1, d1, yp, 1); + } + else if (f01 < 0) + { + // ++-- + d1 = f01 - f00; + yn1 = f01 * y - f00 * yp; + this->AddEdge(vertices, edges, x, 1, yn1, d1, xp, 1, yn0, d0); + } + else + { + // ++-0 + this->AddEdge(vertices, edges, x, 1, yp, 1, xp, 1, yn0, d0); + } + } + else + { + if (f01 > 0) + { + // ++0+ + this->AddVertex(vertices, xp, 1, yp, 1); + } + else if (f01 < 0) + { + // ++0- + d0 = f01 - f00; + yn0 = f01 * y - f00 * yp; + this->AddEdge(vertices, edges, xp, 1, yp, 1, x, 1, yn0, d0); + } + else + { + // ++00 + this->AddEdge(vertices, edges, xp, 1, yp, 1, x, 1, yp, 1); + } + } + } + else if (f10 < 0) + { + d0 = f00 - f10; + xn0 = f00 * xp - f10 * x; + + if (f11 > 0) + { + d1 = f11 - f10; + yn1 = f11 * y - f10 * yp; + + if (f01 > 0) + { + // +-++ + this->AddEdge(vertices, edges, xn0, d0, y, 1, xp, 1, yn1, d1); + } + else if (f01 < 0) + { + // +-+- + d3 = f11 - f01; + xn1 = f11 * x - f01 * xp; + d2 = f01 - f00; + yn0 = f01 * y - f00 * yp; + + if (d0*d3 > 0) + { + det = xn1 * d0 - xn0 * d3; + } + else + { + det = xn0 * d3 - xn1 * d0; + } + + if (det > 0) + { + this->AddEdge(vertices, edges, xn1, d3, yp, 1, xp, 1, yn1, d1); + this->AddEdge(vertices, edges, xn0, d0, y, 1, x, 1, yn0, d2); + } + else if (det < 0) + { + this->AddEdge(vertices, edges, xn1, d3, yp, 1, x, 1, yn0, d2); + this->AddEdge(vertices, edges, xn0, d0, y, 1, xp, 1, yn1, d1); + } + else + { + this->AddEdge(vertices, edges, xn0, d0, yn0, d2, xn0, d0, y, 1); + this->AddEdge(vertices, edges, xn0, d0, yn0, d2, xn0, d0, yp, 1); + this->AddEdge(vertices, edges, xn0, d0, yn0, d2, x, 1, yn0, d2); + this->AddEdge(vertices, edges, xn0, d0, yn0, d2, xp, 1, yn0, d2); + } + } + else + { + // +-+0 + this->AddEdge(vertices, edges, xn0, d0, y, 1, xp, 1, yn1, d1); + this->AddVertex(vertices, x, 1, yp, 1); + } + } + else if (f11 < 0) + { + if (f01 > 0) + { + // +--+ + d1 = f11 - f01; + xn1 = f11 * x - f01 * xp; + this->AddEdge(vertices, edges, xn0, d0, y, 1, xn1, d1, yp, 1); + } + else if (f01 < 0) + { + // +--- + d1 = f01 - f00; + yn1 = f01 * y - f00 * yp; + this->AddEdge(vertices, edges, x, 1, yn1, d1, xn0, d0, y, 1); + } + else + { + // +--0 + this->AddEdge(vertices, edges, x, 1, yp, 1, xn0, d0, y, 1); + } + } + else + { + if (f01 > 0) + { + // +-0+ + this->AddEdge(vertices, edges, xp, 1, yp, 1, xn0, d0, y, 1); + } + else if (f01 < 0) + { + // +-0- + d1 = f01 - f00; + yn1 = f01 * y - f00 * yp; + this->AddEdge(vertices, edges, x, 1, yn1, d1, xn0, d0, y, 1); + this->AddVertex(vertices, xp, 1, yp, 1); + } + else + { + // +-00 + this->AddEdge(vertices, edges, xp, 1, yp, 1, xn0, d0, yp, 1); + this->AddEdge(vertices, edges, xn0, d0, yp, 1, x, 1, yp, 1); + this->AddEdge(vertices, edges, xn0, d0, yp, 1, xn0, d0, y, 1); + } + } + } + else + { + if (f11 > 0) + { + if (f01 > 0) + { + // +0++ + this->AddVertex(vertices, xp, 1, y, 1); + } + else if (f01 < 0) + { + // +0+- + d0 = f11 - f01; + xn0 = f11 * x - f01 * xp; + d1 = f00 - f01; + yn1 = f00 * yp - f01 * y; + this->AddEdge(vertices, edges, xn0, d0, yp, 1, x, 1, yn1, d1); + this->AddVertex(vertices, xp, 1, y, 1); + } + else + { + // +0+0 + this->AddVertex(vertices, xp, 1, y, 1); + this->AddVertex(vertices, x, 1, yp, 1); + } + } + else if (f11 < 0) + { + if (f01 > 0) + { + // +0-+ + d0 = f11 - f01; + xn0 = f11 * x - f01 * xp; + this->AddEdge(vertices, edges, xp, 1, y, 1, xn0, d0, yp, 1); + } + else if (f01 < 0) + { + // +0-- + d0 = f01 - f00; + yn0 = f01 * y - f00 * yp; + this->AddEdge(vertices, edges, xp, 1, y, 1, x, 1, yn0, d0); + } + else + { + // +0-0 + this->AddEdge(vertices, edges, xp, 1, y, 1, x, 1, yp, 1); + } + } + else + { + if (f01 > 0) + { + // +00+ + this->AddEdge(vertices, edges, xp, 1, y, 1, xp, 1, yp, 1); + } + else if (f01 < 0) + { + // +00- + d0 = f00 - f01; + yn0 = f00 * yp - f01 * y; + this->AddEdge(vertices, edges, xp, 1, y, 1, xp, 1, yn0, d0); + this->AddEdge(vertices, edges, xp, 1, yn0, d0, xp, 1, yp, 1); + this->AddEdge(vertices, edges, xp, 1, yn0, d0, x, 1, yn0, d0); + } + else + { + // +000 + this->AddEdge(vertices, edges, x, 1, yp, 1, x, 1, y, 1); + this->AddEdge(vertices, edges, x, 1, y, 1, xp, 1, y, 1); + } + } + } + } + else if (f10 != 0) + { + // convert to case 0+** + if (f10 < 0) + { + f10 = -f10; + f11 = -f11; + f01 = -f01; + } + + if (f11 > 0) + { + if (f01 > 0) + { + // 0+++ + this->AddVertex(vertices, x, 1, y, 1); + } + else if (f01 < 0) + { + // 0++- + d0 = f11 - f01; + xn0 = f11 * x - f01 * xp; + this->AddEdge(vertices, edges, x, 1, y, 1, xn0, d0, yp, 1); + } + else + { + // 0++0 + this->AddEdge(vertices, edges, x, 1, yp, 1, x, 1, y, 1); + } + } + else if (f11 < 0) + { + if (f01 > 0) + { + // 0+-+ + d0 = f10 - f11; + yn0 = f10 * yp - f11 * y; + d1 = f01 - f11; + xn1 = f01 * xp - f11 * x; + this->AddEdge(vertices, edges, xp, 1, yn0, d0, xn1, d1, yp, 1); + this->AddVertex(vertices, x, 1, y, 1); + } + else if (f01 < 0) + { + // 0+-- + d0 = f10 - f11; + yn0 = f10 * yp - f11 * y; + this->AddEdge(vertices, edges, x, 1, y, 1, xp, 1, yn0, d0); + } + else + { + // 0+-0 + d0 = f10 - f11; + yn0 = f10 * yp - f11 * y; + this->AddEdge(vertices, edges, x, 1, y, 1, x, 1, yn0, d0); + this->AddEdge(vertices, edges, x, 1, yn0, d0, x, 1, yp, 1); + this->AddEdge(vertices, edges, x, 1, yn0, d0, xp, 1, yn0, d0); + } + } + else + { + if (f01 > 0) + { + // 0+0+ + this->AddVertex(vertices, x, 1, y, 1); + this->AddVertex(vertices, xp, 1, yp, 1); + } + else if (f01 < 0) + { + // 0+0- + this->AddEdge(vertices, edges, x, 1, y, 1, xp, 1, yp, 1); + } + else + { + // 0+00 + this->AddEdge(vertices, edges, xp, 1, yp, 1, x, 1, yp, 1); + this->AddEdge(vertices, edges, x, 1, yp, 1, x, 1, y, 1); + } + } + } + else if (f11 != 0) + { + // convert to case 00+* + if (f11 < 0) + { + f11 = -f11; + f01 = -f01; + } + + if (f01 > 0) + { + // 00++ + this->AddEdge(vertices, edges, x, 1, y, 1, xp, 1, y, 1); + } + else if (f01 < 0) + { + // 00+- + d0 = f01 - f11; + xn0 = f01 * xp - f11 * x; + this->AddEdge(vertices, edges, x, 1, y, 1, xn0, d0, y, 1); + this->AddEdge(vertices, edges, xn0, d0, y, 1, xp, 1, y, 1); + this->AddEdge(vertices, edges, xn0, d0, y, 1, xn0, d0, yp, 1); + } + else + { + // 00+0 + this->AddEdge(vertices, edges, xp, 1, y, 1, xp, 1, yp, 1); + this->AddEdge(vertices, edges, xp, 1, yp, 1, x, 1, yp, 1); + } + } + else if (f01 != 0) + { + // cases 000+ or 000- + this->AddEdge(vertices, edges, x, 1, y, 1, xp, 1, y, 1); + this->AddEdge(vertices, edges, xp, 1, y, 1, xp, 1, yp, 1); + } + else + { + // case 0000 + this->AddEdge(vertices, edges, x, 1, y, 1, xp, 1, y, 1); + this->AddEdge(vertices, edges, xp, 1, y, 1, xp, 1, yp, 1); + this->AddEdge(vertices, edges, xp, 1, yp, 1, x, 1, yp, 1); + this->AddEdge(vertices, edges, x, 1, yp, 1, x, 1, y, 1); + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CurveExtractorTriangles.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CurveExtractorTriangles.h new file mode 100644 index 0000000..60b36cd --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/CurveExtractorTriangles.h @@ -0,0 +1,227 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The level set extraction algorithm implemented here is described +// in Section 2 of the document +// https://www.geometrictools.com/Documentation/ExtractLevelCurves.pdf + +namespace WwiseGTE +{ + // The image type T must be one of the integer types: int8_t, int16_t, + // int32_t, uint8_t, uint16_t or uint32_t. Internal integer computations + // are performed using int64_t. The type Real is for extraction to + // floating-point vertices. + template + class CurveExtractorTriangles : public CurveExtractor + { + public: + // Convenience type definitions. + typedef typename CurveExtractor::Vertex Vertex; + typedef typename CurveExtractor::Edge Edge; + + // The input is a 2D image with lexicographically ordered pixels (x,y) + // stored in a linear array. Pixel (x,y) is stored in the array at + // location index = x + xBound * y. The inputs xBound and yBound must + // each be 2 or larger so that there is at least one image square to + // process. The inputPixels must be nonnull and point to contiguous + // storage that contains at least xBound * yBound elements. + CurveExtractorTriangles(int xBound, int yBound, T const* inputPixels) + : + CurveExtractor(xBound, yBound, inputPixels) + { + } + + // Extract level curves and return rational vertices. Use the + // base-class Extract if you want real-valued vertices. + virtual void Extract(T level, std::vector& vertices, + std::vector& edges) override + { + // Adjust the image so that the level set is F(x,y) = 0. + int64_t levelI64 = static_cast(level); + for (size_t i = 0; i < this->mPixels.size(); ++i) + { + int64_t inputI64 = static_cast(this->mInputPixels[i]); + this->mPixels[i] = inputI64 - levelI64; + } + + vertices.clear(); + edges.clear(); + for (int y = 0, yp = 1; yp < this->mYBound; ++y, ++yp) + { + int yParity = (y & 1); + + for (int x = 0, xp = 1; xp < this->mXBound; ++x, ++xp) + { + int xParity = (x & 1); + + // Get the image values at the corners of the square. + int i00 = x + this->mXBound * y; + int i10 = i00 + 1; + int i01 = i00 + this->mXBound; + int i11 = i10 + this->mXBound; + int64_t f00 = this->mPixels[i00]; + int64_t f10 = this->mPixels[i10]; + int64_t f01 = this->mPixels[i01]; + int64_t f11 = this->mPixels[i11]; + + // Construct the vertices and edges of the level curve in + // the square. The x, xp, y and yp values are implicitly + // converted from int to int64_t (which is guaranteed to + // be correct). + if (xParity == yParity) + { + ProcessTriangle(vertices, edges, x, y, f00, x, yp, f01, xp, y, f10); + ProcessTriangle(vertices, edges, xp, yp, f11, xp, y, f10, x, yp, f01); + } + else + { + ProcessTriangle(vertices, edges, x, yp, f01, xp, yp, f11, x, y, f00); + ProcessTriangle(vertices, edges, xp, y, f10, x, y, f00, xp, yp, f11); + } + } + } + } + + protected: + void ProcessTriangle(std::vector& vertices, std::vector& edges, + int64_t x0, int64_t y0, int64_t f0, + int64_t x1, int64_t y1, int64_t f1, + int64_t x2, int64_t y2, int64_t f2) + { + int64_t xn0, yn0, xn1, yn1, d0, d1; + + if (f0 != 0) + { + // convert to case "+**" + if (f0 < 0) + { + f0 = -f0; + f1 = -f1; + f2 = -f2; + } + + if (f1 > 0) + { + if (f2 > 0) + { + // +++ + return; + } + else if (f2 < 0) + { + // ++- + d0 = f0 - f2; + xn0 = f0 * x2 - f2 * x0; + yn0 = f0 * y2 - f2 * y0; + d1 = f1 - f2; + xn1 = f1 * x2 - f2 * x1; + yn1 = f1 * y2 - f2 * y1; + this->AddEdge(vertices, edges, xn0, d0, yn0, d0, xn1, d1, yn1, d1); + } + else + { + // ++0 + this->AddVertex(vertices, x2, 1, y2, 1); + } + } + else if (f1 < 0) + { + d0 = f0 - f1; + xn0 = f0 * x1 - f1 * x0; + yn0 = f0 * y1 - f1 * y0; + + if (f2 > 0) + { + // +-+ + d1 = f2 - f1; + xn1 = f2 * x1 - f1 * x2; + yn1 = f2 * y1 - f1 * y2; + this->AddEdge(vertices, edges, xn0, d0, yn0, d0, xn1, d1, yn1, d1); + } + else if (f2 < 0) + { + // +-- + d1 = f2 - f0; + xn1 = f2 * x0 - f0 * x2; + yn1 = f2 * y0 - f0 * y2; + this->AddEdge(vertices, edges, xn0, d0, yn0, d0, xn1, d1, yn1, d1); + } + else + { + // +-0 + this->AddEdge(vertices, edges, x2, 1, y2, 1, xn0, d0, yn0, d0); + } + } + else + { + if (f2 > 0) + { + // +0+ + this->AddVertex(vertices, x1, 1, y1, 1); + } + else if (f2 < 0) + { + // +0- + d0 = f2 - f0; + xn0 = f2 * x0 - f0 * x2; + yn0 = f2 * y0 - f0 * y2; + this->AddEdge(vertices, edges, x1, 1, y1, 1, xn0, d0, yn0, d0); + } + else + { + // +00 + this->AddEdge(vertices, edges, x1, 1, y1, 1, x2, 1, y2, 1); + } + } + } + else if (f1 != 0) + { + // convert to case 0+* + if (f1 < 0) + { + f1 = -f1; + f2 = -f2; + } + + if (f2 > 0) + { + // 0++ + this->AddVertex(vertices, x0, 1, y0, 1); + } + else if (f2 < 0) + { + // 0+- + d0 = f1 - f2; + xn0 = f1 * x2 - f2 * x1; + yn0 = f1 * y2 - f2 * y1; + this->AddEdge(vertices, edges, x0, 1, y0, 1, xn0, d0, yn0, d0); + } + else + { + // 0+0 + this->AddEdge(vertices, edges, x0, 1, y0, 1, x2, 1, y2, 1); + } + } + else if (f2 != 0) + { + // cases 00+ or 00- + this->AddEdge(vertices, edges, x0, 1, y0, 1, x1, 1, y1, 1); + } + else + { + // case 000 + this->AddEdge(vertices, edges, x0, 1, y0, 1, x1, 1, y1, 1); + this->AddEdge(vertices, edges, x1, 1, y1, 1, x2, 1, y2, 1); + this->AddEdge(vertices, edges, x2, 1, y2, 1, x0, 1, y0, 1); + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Cylinder3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Cylinder3.h new file mode 100644 index 0000000..ebb5628 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Cylinder3.h @@ -0,0 +1,100 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The cylinder axis is a line. The origin of the cylinder is chosen to be +// the line origin. The cylinder wall is at a distance R units from the axis. +// An infinite cylinder has infinite height. A finite cylinder has center C +// at the line origin and has a finite height H. The segment for the finite +// cylinder has endpoints C-(H/2)*D and C+(H/2)*D where D is a unit-length +// direction of the line. + +namespace WwiseGTE +{ + template + class Cylinder3 + { + public: + // Construction and destruction. The default constructor sets axis + // to (0,0,1), radius to 1, and height to 1. + Cylinder3() + : + axis(Line3()), + radius((Real)1), + height((Real)1) + { + } + + Cylinder3(Line3 const& inAxis, Real inRadius, Real inHeight) + : + axis(inAxis), + radius(inRadius), + height(inHeight) + { + } + + Line3 axis; + Real radius, height; + + public: + // Comparisons to support sorted containers. + bool operator==(Cylinder3 const& cylinder) const + { + return axis == cylinder.axis + && radius == cylinder.radius + && height == cylinder.height; + } + + bool operator!=(Cylinder3 const& cylinder) const + { + return !operator==(cylinder); + } + + bool operator< (Cylinder3 const& cylinder) const + { + if (axis < cylinder.axis) + { + return true; + } + + if (axis > cylinder.axis) + { + return false; + } + + if (radius < cylinder.radius) + { + return true; + } + + if (radius > cylinder.radius) + { + return false; + } + + return height < cylinder.height; + } + + bool operator<=(Cylinder3 const& cylinder) const + { + return !cylinder.operator<(*this); + } + + bool operator> (Cylinder3 const& cylinder) const + { + return cylinder.operator<(*this); + } + + bool operator>=(Cylinder3 const& cylinder) const + { + return !operator<(cylinder); + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DCPQuery.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DCPQuery.h new file mode 100644 index 0000000..3f6041e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DCPQuery.h @@ -0,0 +1,33 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + // Distance and closest-point queries. + template + class DCPQuery + { + public: + struct Result + { + // A DCPQuery-base class B must define a B::Result struct with + // member 'Real distance'. A DCPQuery-derived class D must also + // derive a D::Result from B:Result but may have no members. The + // idea is to allow Result to store closest-point information in + // addition to the distance. The operator() is non-const so that + // specific implementations can use internal data to support the + // queries. The implementations can also use static functions as + // necessary. + }; + + Result operator()(Type0 const& primitive0, Type1 const& primitive1); + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DarbouxFrame.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DarbouxFrame.h new file mode 100644 index 0000000..d13e1a2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DarbouxFrame.h @@ -0,0 +1,148 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class DarbouxFrame3 + { + public: + // Construction. The curve must persist as long as the DarbouxFrame3 + // object does. + DarbouxFrame3(std::shared_ptr> const& surface) + : + mSurface(surface) + { + } + + // Get a coordinate frame, {T0, T1, N}. At a nondegenerate surface + // points, dX/du and dX/dv are linearly independent tangent vectors. + // The frame is constructed as + // T0 = (dX/du)/|dX/du| + // N = Cross(dX/du,dX/dv)/|Cross(dX/du,dX/dv)| + // T1 = Cross(N, T0) + // so that {T0, T1, N} is a right-handed orthonormal set. + void operator()(Real u, Real v, Vector3& position, Vector3& tangent0, + Vector3& tangent1, Vector3& normal) const + { + std::array, 3> jet; + mSurface->Evaluate(u, v, 1, jet.data()); + position = jet[0]; + tangent0 = jet[1]; + Normalize(tangent0); + tangent1 = jet[2]; + Normalize(tangent1); + normal = UnitCross(tangent0, tangent1); + tangent1 = Cross(normal, tangent0); + } + + // Compute the principal curvatures and principal directions. + void GetPrincipalInformation(Real u, Real v, Real& curvature0, Real& curvature1, + Vector3& direction0, Vector3& direction1) const + { + // Tangents: T0 = (x_u,y_u,z_u), T1 = (x_v,y_v,z_v) + // Normal: N = Cross(T0,T1)/Length(Cross(T0,T1)) + // Metric Tensor: G = +- -+ + // | Dot(T0,T0) Dot(T0,T1) | + // | Dot(T1,T0) Dot(T1,T1) | + // +- -+ + // + // Curvature Tensor: B = +- -+ + // | -Dot(N,T0_u) -Dot(N,T0_v) | + // | -Dot(N,T1_u) -Dot(N,T1_v) | + // +- -+ + // + // Principal curvatures k are the generalized eigenvalues of + // + // Bw = kGw + // + // If k is a curvature and w=(a,b) is the corresponding solution + // to Bw = kGw, then the principal direction as a 3D vector is + // d = a*U+b*V. + // + // Let k1 and k2 be the principal curvatures. The mean curvature + // is (k1+k2)/2 and the Gaussian curvature is k1*k2. + + // Compute derivatives. + std::array, 6> jet; + mSurface->Evaluate(u, v, 2, jet.data()); + Vector3 derU = jet[1]; + Vector3 derV = jet[2]; + Vector3 derUU = jet[3]; + Vector3 derUV = jet[4]; + Vector3 derVV = jet[5]; + + // Compute the metric tensor. + Matrix2x2 metricTensor; + metricTensor(0, 0) = Dot(jet[1], jet[1]); + metricTensor(0, 1) = Dot(jet[1], jet[2]); + metricTensor(1, 0) = metricTensor(0, 1); + metricTensor(1, 1) = Dot(jet[2], jet[2]); + + // Compute the curvature tensor. + Vector3 normal = UnitCross(jet[1], jet[2]); + Matrix2x2 curvatureTensor; + curvatureTensor(0, 0) = -Dot(normal, derUU); + curvatureTensor(0, 1) = -Dot(normal, derUV); + curvatureTensor(1, 0) = curvatureTensor(0, 1); + curvatureTensor(1, 1) = -Dot(normal, derVV); + + // Characteristic polynomial is 0 = det(B-kG) = c2*k^2+c1*k+c0. + Real c0 = Determinant(curvatureTensor); + Real c1 = (Real)2 * curvatureTensor(0, 1) * metricTensor(0, 1) - + curvatureTensor(0, 0) * metricTensor(1, 1) - + curvatureTensor(1, 1) * metricTensor(0, 0); + Real c2 = Determinant(metricTensor); + + // Principal curvatures are roots of characteristic polynomial. + Real temp = std::sqrt(std::max(c1 * c1 - (Real)4 * c0 * c2, (Real)0)); + Real mult = (Real)0.5 / c2; + curvature0 = -mult * (c1 + temp); + curvature1 = -mult * (c1 - temp); + + // Principal directions are solutions to (B-kG)w = 0, + // w1 = (b12-k1*g12,-(b11-k1*g11)) OR (b22-k1*g22,-(b12-k1*g12)). + Real a0 = curvatureTensor(0, 1) - curvature0 * metricTensor(0, 1); + Real a1 = curvature0 * metricTensor(0, 0) - curvatureTensor(0, 0); + Real length = std::sqrt(a0 * a0 + a1 * a1); + if (length > (Real)0) + { + direction0 = a0 * derU + a1 * derV; + } + else + { + a0 = curvatureTensor(1, 1) - curvature0 * metricTensor(1, 1); + a1 = curvature0 * metricTensor(0, 1) - curvatureTensor(0, 1); + length = std::sqrt(a0 * a0 + a1 * a1); + if (length > (Real)0) + { + direction0 = a0 * derU + a1 * derV; + } + else + { + // Umbilic (surface is locally sphere, any direction + // principal). + direction0 = derU; + } + } + Normalize(direction0); + + // Second tangent is cross product of first tangent and normal. + direction1 = Cross(direction0, normal); + } + + private: + std::shared_ptr> mSurface; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Delaunay2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Delaunay2.h new file mode 100644 index 0000000..46d7a41 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Delaunay2.h @@ -0,0 +1,793 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// Delaunay triangulation of points (intrinsic dimensionality 2). +// VQ = number of vertices +// V = array of vertices +// TQ = number of triangles +// I = Array of 3-tuples of indices into V that represent the triangles +// (3*TQ total elements). Access via GetIndices(*). +// A = Array of 3-tuples of indices into I that represent the adjacent +// triangles (3*TQ total elements). Access via GetAdjacencies(*). +// The i-th triangle has vertices +// vertex[0] = V[I[3*i+0]] +// vertex[1] = V[I[3*i+1]] +// vertex[2] = V[I[3*i+2]] +// and edge index pairs +// edge[0] = +// edge[1] = +// edge[2] = +// The triangles adjacent to these edges have indices +// adjacent[0] = A[3*i+0] is the triangle sharing edge[0] +// adjacent[1] = A[3*i+1] is the triangle sharing edge[1] +// adjacent[2] = A[3*i+2] is the triangle sharing edge[2] +// If there is no adjacent triangle, the A[*] value is set to -1. The +// triangle adjacent to edge[j] has vertices +// adjvertex[0] = V[I[3*adjacent[j]+0]] +// adjvertex[1] = V[I[3*adjacent[j]+1]] +// adjvertex[2] = V[I[3*adjacent[j]+2]] +// The only way to ensure a correct result for the input vertices (assumed to +// be exact) is to choose ComputeType for exact rational arithmetic. You may +// use BSNumber. No divisions are performed in this computation, so you do +// not have to use BSRational. +// +// The worst-case choices of N for Real of type BSNumber or BSRational with +// integer storage UIntegerFP32 are listed in the next table. The numerical +// computations are encapsulated in PrimalQuery2::ToLine and +// PrimalQuery2::ToCircumcircle, the latter query the dominant one in +// determining N. We recommend using only BSNumber, because no divisions are +// performed in the convex-hull computations. +// +// input type | compute type | N +// -----------+--------------+------ +// float | BSNumber | 35 +// double | BSNumber | 263 +// float | BSRational | 12302 +// double | BSRational | 92827 + +namespace WwiseGTE +{ + template + class Delaunay2 + { + public: + // The class is a functor to support computing the Delaunay + // triangulation of multiple data sets using the same class object. + virtual ~Delaunay2() = default; + + Delaunay2() + : + mEpsilon((InputType)0), + mDimension(0), + mLine(Vector2::Zero(), Vector2::Zero()), + mNumVertices(0), + mNumUniqueVertices(0), + mNumTriangles(0), + mVertices(nullptr), + mIndex{ { { 0, 1 }, { 1, 2 }, { 2, 0 } } } + { + } + + // The input is the array of vertices whose Delaunay triangulation is + // required. The epsilon value is used to determine the intrinsic + // dimensionality of the vertices (d = 0, 1, or 2). When epsilon is + // positive, the determination is fuzzy--vertices approximately the + // same point, approximately on a line, or planar. The return value + // is 'true' if and only if the hull construction is successful. + bool operator()(int numVertices, Vector2 const* vertices, InputType epsilon) + { + mEpsilon = std::max(epsilon, (InputType)0); + mDimension = 0; + mLine.origin = Vector2::Zero(); + mLine.direction = Vector2::Zero(); + mNumVertices = numVertices; + mNumUniqueVertices = 0; + mNumTriangles = 0; + mVertices = vertices; + mGraph.Clear(); + mIndices.clear(); + mAdjacencies.clear(); + mDuplicates.resize(std::max(numVertices, 3)); + + int i, j; + if (mNumVertices < 3) + { + // Delaunay2 should be called with at least three points. + return false; + } + + IntrinsicsVector2 info(mNumVertices, vertices, mEpsilon); + if (info.dimension == 0) + { + // mDimension is 0; mGraph, mIndices, and mAdjacencies are empty + return false; + } + + if (info.dimension == 1) + { + // The set is (nearly) collinear. + mDimension = 1; + mLine = Line2(info.origin, info.direction[0]); + return false; + } + + mDimension = 2; + + // Compute the vertices for the queries. + mComputeVertices.resize(mNumVertices); + mQuery.Set(mNumVertices, &mComputeVertices[0]); + for (i = 0; i < mNumVertices; ++i) + { + for (j = 0; j < 2; ++j) + { + mComputeVertices[i][j] = vertices[i][j]; + } + } + + // Insert the (nondegenerate) triangle constructed by the call to + // GetInformation. This is necessary for the circumcircle-visibility + // algorithm to work correctly. + if (!info.extremeCCW) + { + std::swap(info.extreme[1], info.extreme[2]); + } + if (!mGraph.Insert(info.extreme[0], info.extreme[1], info.extreme[2])) + { + return false; + } + + // Incrementally update the triangulation. The set of processed + // points is maintained to eliminate duplicates, either in the + // original input points or in the points obtained by snap rounding. + std::set processed; + for (i = 0; i < 3; ++i) + { + j = info.extreme[i]; + processed.insert(ProcessedVertex(vertices[j], j)); + mDuplicates[j] = j; + } + for (i = 0; i < mNumVertices; ++i) + { + ProcessedVertex v(vertices[i], i); + auto iter = processed.find(v); + if (iter == processed.end()) + { + if (!Update(i)) + { + // A failure can occur if ComputeType is not an exact + // arithmetic type. + return false; + } + processed.insert(v); + mDuplicates[i] = i; + } + else + { + mDuplicates[i] = iter->location; + } + } + mNumUniqueVertices = static_cast(processed.size()); + + // Assign integer values to the triangles for use by the caller. + std::map, int> permute; + i = -1; + permute[nullptr] = i++; + for (auto const& element : mGraph.GetTriangles()) + { + permute[element.second] = i++; + } + + // Put Delaunay triangles into an array (vertices and adjacency info). + mNumTriangles = static_cast(mGraph.GetTriangles().size()); + int numindices = 3 * mNumTriangles; + if (numindices > 0) + { + mIndices.resize(numindices); + mAdjacencies.resize(numindices); + i = 0; + for (auto const& element : mGraph.GetTriangles()) + { + std::shared_ptr tri = element.second; + for (j = 0; j < 3; ++j, ++i) + { + mIndices[i] = tri->V[j]; + mAdjacencies[i] = permute[tri->T[j].lock()]; + } + } + } + + return true; + } + + // Dimensional information. If GetDimension() returns 1, the points + // lie on a line P+t*D (fuzzy comparison when epsilon > 0). You can + // sort these if you need a polyline output by projecting onto the + // line each vertex X = P+t*D, where t = Dot(D,X-P). + inline InputType GetEpsilon() const + { + return mEpsilon; + } + + inline int GetDimension() const + { + return mDimension; + } + + inline Line2 const& GetLine() const + { + return mLine; + } + + // Member access. + inline int GetNumVertices() const + { + return mNumVertices; + } + + inline int GetNumUniqueVertices() const + { + return mNumUniqueVertices; + } + + inline int GetNumTriangles() const + { + return mNumTriangles; + } + + inline Vector2 const* GetVertices() const + { + return mVertices; + } + + inline PrimalQuery2 const& GetQuery() const + { + return mQuery; + } + + inline ETManifoldMesh const& GetGraph() const + { + return mGraph; + } + + inline std::vector const& GetIndices() const + { + return mIndices; + } + + inline std::vector const& GetAdjacencies() const + { + return mAdjacencies; + } + + // If 'vertices' has no duplicates, GetDuplicates()[i] = i for all i. + // If vertices[i] is the first occurrence of a vertex and if + // vertices[j] is found later, then GetDuplicates()[j] = i. + inline std::vector const& GetDuplicates() const + { + return mDuplicates; + } + + // Locate those triangle edges that do not share other triangles. The + // returned array has hull.size() = 2*numEdges, each pair representing + // an edge. The edges are not ordered, but the pair of vertices for + // an edge is ordered so that they conform to a counterclockwise + // traversal of the hull. The return value is 'true' if and only if + // the dimension is 2. + bool GetHull(std::vector& hull) const + { + if (mDimension == 2) + { + // Count the number of edges that are not shared by two + // triangles. + int numEdges = 0; + for (auto adj : mAdjacencies) + { + if (adj == -1) + { + ++numEdges; + } + } + + if (numEdges > 0) + { + // Enumerate the edges. + hull.resize(2 * numEdges); + int current = 0, i = 0; + for (auto adj : mAdjacencies) + { + if (adj == -1) + { + int tri = i / 3, j = i % 3; + hull[current++] = mIndices[3 * tri + j]; + hull[current++] = mIndices[3 * tri + ((j + 1) % 3)]; + } + ++i; + } + return true; + } + else + { + LogError("Unexpected. There must be at least one triangle."); + } + } + else + { + LogError("The dimension must be 2."); + } + } + + // Get the vertex indices for triangle i. The function returns 'true' + // when the dimension is 2 and i is a valid triangle index, in which + // case the vertices are valid; otherwise, the function returns + // 'false' and the vertices are invalid. + bool GetIndices(int i, std::array& indices) const + { + if (mDimension == 2) + { + int numTriangles = static_cast(mIndices.size() / 3); + if (0 <= i && i < numTriangles) + { + indices[0] = mIndices[3 * i]; + indices[1] = mIndices[3 * i + 1]; + indices[2] = mIndices[3 * i + 2]; + return true; + } + } + else + { + LogError("The dimension must be 2."); + } + return false; + } + + // Get the indices for triangles adjacent to triangle i. The function + // returns 'true' when the dimension is 2 and if i is a valid triangle + // index, in which case the adjacencies are valid; otherwise, the + // function returns 'false' and the adjacencies are invalid. + bool GetAdjacencies(int i, std::array& adjacencies) const + { + if (mDimension == 2) + { + int numTriangles = static_cast(mIndices.size() / 3); + if (0 <= i && i < numTriangles) + { + adjacencies[0] = mAdjacencies[3 * i]; + adjacencies[1] = mAdjacencies[3 * i + 1]; + adjacencies[2] = mAdjacencies[3 * i + 2]; + return true; + } + } + else + { + LogError("The dimension must be 2."); + } + return false; + } + + // Support for searching the triangulation for a triangle that + // contains a point. If there is a containing triangle, the returned + // value is a triangle index i with 0 <= i < GetNumTriangles(). If + // there is not a containing triangle, -1 is returned. The + // computations are performed using exact rational arithmetic. + // + // The SearchInfo input stores information about the triangle search + // when looking for the triangle (if any) that contains p. The first + // triangle searched is 'initialTriangle'. On return 'path' stores + // those (ordered) triangle indices visited during the search. The + // last visited triangle has index 'finalTriangle and vertex indices + // 'finalV[0,1,2]', stored in counterclockwise order. The last edge + // of the search is . For spatially coherent + // inputs p for numerous calls to this function, you will want to + // specify 'finalTriangle' from the previous call as 'initialTriangle' + // for the next call, which should reduce search times. + struct SearchInfo + { + int initialTriangle; + int numPath; + std::vector path; + int finalTriangle; + std::array finalV; + }; + + int GetContainingTriangle(Vector2 const& p, SearchInfo& info) const + { + if (mDimension == 2) + { + Vector2 test{ p[0], p[1] }; + + int numTriangles = static_cast(mIndices.size() / 3); + info.path.resize(numTriangles); + info.numPath = 0; + int triangle; + if (0 <= info.initialTriangle && info.initialTriangle < numTriangles) + { + triangle = info.initialTriangle; + } + else + { + info.initialTriangle = 0; + triangle = 0; + } + + // Use triangle edges as binary separating lines. + for (int i = 0; i < numTriangles; ++i) + { + int ibase = 3 * triangle; + int const* v = &mIndices[ibase]; + + info.path[info.numPath++] = triangle; + info.finalTriangle = triangle; + info.finalV[0] = v[0]; + info.finalV[1] = v[1]; + info.finalV[2] = v[2]; + + if (mQuery.ToLine(test, v[0], v[1]) > 0) + { + triangle = mAdjacencies[ibase]; + if (triangle == -1) + { + info.finalV[0] = v[0]; + info.finalV[1] = v[1]; + info.finalV[2] = v[2]; + return -1; + } + continue; + } + + if (mQuery.ToLine(test, v[1], v[2]) > 0) + { + triangle = mAdjacencies[ibase + 1]; + if (triangle == -1) + { + info.finalV[0] = v[1]; + info.finalV[1] = v[2]; + info.finalV[2] = v[0]; + return -1; + } + continue; + } + + if (mQuery.ToLine(test, v[2], v[0]) > 0) + { + triangle = mAdjacencies[ibase + 2]; + if (triangle == -1) + { + info.finalV[0] = v[2]; + info.finalV[1] = v[0]; + info.finalV[2] = v[1]; + return -1; + } + continue; + } + + return triangle; + } + } + else + { + LogError("The dimension must be 2."); + } + return -1; + } + + protected: + // Support for incremental Delaunay triangulation. + typedef ETManifoldMesh::Triangle Triangle; + + bool GetContainingTriangle(int i, std::shared_ptr& tri) const + { + int numTriangles = static_cast(mGraph.GetTriangles().size()); + for (int t = 0; t < numTriangles; ++t) + { + int j; + for (j = 0; j < 3; ++j) + { + int v0 = tri->V[mIndex[j][0]]; + int v1 = tri->V[mIndex[j][1]]; + if (mQuery.ToLine(i, v0, v1) > 0) + { + // Point i sees edge from outside the triangle. + auto adjTri = tri->T[j].lock(); + if (adjTri) + { + // Traverse to the triangle sharing the face. + tri = adjTri; + break; + } + else + { + // We reached a hull edge, so the point is outside + // the hull. TODO: Once a hull data structure is + // in place, return tri->T[j] as the candidate for + // starting a search for visible hull edges. + return false; + } + } + + } + + if (j == 3) + { + // The point is inside all four edges, so the point is inside + // a triangle. + return true; + } + } + + LogError("Unexpected termination of loop."); + } + + bool GetAndRemoveInsertionPolygon(int i, std::set>& candidates, + std::set>& boundary) + { + // Locate the triangles that make up the insertion polygon. + ETManifoldMesh polygon; + while (candidates.size() > 0) + { + std::shared_ptr tri = *candidates.begin(); + candidates.erase(candidates.begin()); + + for (int j = 0; j < 3; ++j) + { + auto adj = tri->T[j].lock(); + if (adj && candidates.find(adj) == candidates.end()) + { + int a0 = adj->V[0]; + int a1 = adj->V[1]; + int a2 = adj->V[2]; + if (mQuery.ToCircumcircle(i, a0, a1, a2) <= 0) + { + // Point i is in the circumcircle. + candidates.insert(adj); + } + } + } + + if (!polygon.Insert(tri->V[0], tri->V[1], tri->V[2])) + { + return false; + } + if (!mGraph.Remove(tri->V[0], tri->V[1], tri->V[2])) + { + return false; + } + } + + // Get the boundary edges of the insertion polygon. + for (auto const& element : polygon.GetTriangles()) + { + std::shared_ptr tri = element.second; + for (int j = 0; j < 3; ++j) + { + if (!tri->T[j].lock()) + { + boundary.insert(EdgeKey(tri->V[mIndex[j][0]], tri->V[mIndex[j][1]])); + } + } + } + return true; + } + + bool Update(int i) + { + // The return value of mGraph.Insert(...) is nullptr if there was + // a failure to insert. The Update function will return 'false' + // when the insertion fails. + + auto const& tmap = mGraph.GetTriangles(); + std::shared_ptr tri = tmap.begin()->second; + if (GetContainingTriangle(i, tri)) + { + // The point is inside the convex hull. The insertion polygon + // contains only triangles in the current triangulation; the + // hull does not change. + + // Use a depth-first search for those triangles whose + // circumcircles contain point i. + std::set> candidates; + candidates.insert(tri); + + // Get the boundary of the insertion polygon C that contains + // the triangles whose circumcircles contain point i. Polygon + // C contains the point i. + std::set> boundary; + if (!GetAndRemoveInsertionPolygon(i, candidates, boundary)) + { + return false; + } + + // The insertion polygon consists of the triangles formed by + // point i and the faces of C. + for (auto const& key : boundary) + { + int v0 = key.V[0]; + int v1 = key.V[1]; + if (mQuery.ToLine(i, v0, v1) < 0) + { + if (!mGraph.Insert(i, v0, v1)) + { + return false; + } + } + // else: Point i is on an edge of 'tri', so the + // subdivision has degenerate triangles. Ignore these. + } + } + else + { + // The point is outside the convex hull. The insertion + // polygon is formed by point i and any triangles in the + // current triangulation whose circumcircles contain point i. + + // Locate the convex hull of the triangles. TODO: Maintain a + // hull data structure that is updated incrementally. + std::set> hull; + for (auto const& element : tmap) + { + std::shared_ptr t = element.second; + for (int j = 0; j < 3; ++j) + { + if (!t->T[j].lock()) + { + hull.insert(EdgeKey(t->V[mIndex[j][0]], t->V[mIndex[j][1]])); + } + } + } + + // TODO: Until the hull change, for now just iterate over all + // the hull edges and use the ones visible to point i to + // locate the insertion polygon. + auto const& emap = mGraph.GetEdges(); + std::set> candidates; + std::set> visible; + for (auto const& key : hull) + { + int v0 = key.V[0]; + int v1 = key.V[1]; + if (mQuery.ToLine(i, v0, v1) > 0) + { + auto iter = emap.find(EdgeKey(v0, v1)); + if (iter != emap.end() && iter->second->T[1].lock() == nullptr) + { + auto adj = iter->second->T[0].lock(); + if (adj && candidates.find(adj) == candidates.end()) + { + int a0 = adj->V[0]; + int a1 = adj->V[1]; + int a2 = adj->V[2]; + if (mQuery.ToCircumcircle(i, a0, a1, a2) <= 0) + { + // Point i is in the circumcircle. + candidates.insert(adj); + } + else + { + // Point i is not in the circumcircle but + // the hull edge is visible. + visible.insert(key); + } + } + } + else + { + // TODO: Add a preprocessor symbol to this file + // to allow throwing an exception. Currently, the + // code exits gracefully when floating-point + // rounding causes problems. + // + // LogError("Unexpected condition (ComputeType not exact?)"); + return false; + } + } + } + + // Get the boundary of the insertion subpolygon C that + // contains the triangles whose circumcircles contain point i. + std::set> boundary; + if (!GetAndRemoveInsertionPolygon(i, candidates, boundary)) + { + return false; + } + + // The insertion polygon P consists of the triangles formed by + // point i and the back edges of C *and* the visible edges of + // mGraph-C. + for (auto const& key : boundary) + { + int v0 = key.V[0]; + int v1 = key.V[1]; + if (mQuery.ToLine(i, v0, v1) < 0) + { + // This is a back edge of the boundary. + if (!mGraph.Insert(i, v0, v1)) + { + return false; + } + } + } + for (auto const& key : visible) + { + if (!mGraph.Insert(i, key.V[1], key.V[0])) + { + return false; + } + } + } + + return true; + } + + // The epsilon value is used for fuzzy determination of intrinsic + // dimensionality. If the dimension is 0 or 1, the constructor + // returns early. The caller is responsible for retrieving the + // dimension and taking an alternate path should the dimension be + // smaller than 2. If the dimension is 0, the caller may as well + // treat all vertices[] as a single point, say, vertices[0]. If the + // dimension is 1, the caller can query for the approximating line and + // project vertices[] onto it for further processing. + InputType mEpsilon; + int mDimension; + Line2 mLine; + + // The array of vertices used for geometric queries. If you want to + // be certain of a correct result, choose ComputeType to be BSNumber. + std::vector> mComputeVertices; + PrimalQuery2 mQuery; + + // The graph information. + int mNumVertices; + int mNumUniqueVertices; + int mNumTriangles; + Vector2 const* mVertices; + ETManifoldMesh mGraph; + std::vector mIndices; + std::vector mAdjacencies; + + // If a vertex occurs multiple times in the 'vertices' input to the + // constructor, the first processed occurrence of that vertex has an + // index stored in this array. If there are no duplicates, then + // mDuplicates[i] = i for all i. + + struct ProcessedVertex + { + ProcessedVertex() = default; + + ProcessedVertex(Vector2 const& inVertex, int inLocation) + : + vertex(inVertex), + location(inLocation) + { + } + + bool operator<(ProcessedVertex const& v) const + { + return vertex < v.vertex; + } + + Vector2 vertex; + int location; + }; + + std::vector mDuplicates; + + // Indexing for the vertices of the triangle adjacent to a vertex. + // The edge adjacent to vertex j is and + // is listed so that the triangle interior is to your left as you walk + // around the edges. TODO: Use the "opposite edge" to be consistent + // with that of TetrahedronKey. The "opposite" idea extends easily to + // higher dimensions. + std::array, 3> mIndex; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Delaunay2Mesh.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Delaunay2Mesh.h new file mode 100644 index 0000000..dae2cc9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Delaunay2Mesh.h @@ -0,0 +1,125 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + template + class Delaunay2Mesh + { + public: + // Construction. + Delaunay2Mesh(Delaunay2 const& delaunay) + : + mDelaunay(&delaunay) + { + } + + // Mesh information. + inline int GetNumVertices() const + { + return mDelaunay->GetNumVertices(); + } + + inline int GetNumTriangles() const + { + return mDelaunay->GetNumTriangles(); + } + + inline Vector2 const* GetVertices() const + { + return mDelaunay->GetVertices(); + } + + inline int const* GetIndices() const + { + return &mDelaunay->GetIndices()[0]; + } + + inline int const* GetAdjacencies() const + { + return &mDelaunay->GetAdjacencies()[0]; + } + + // Containment queries. + int GetContainingTriangle(Vector2 const& P) const + { + typename Delaunay2::SearchInfo info; + return mDelaunay->GetContainingTriangle(P, info); + } + + bool GetVertices(int t, std::array, 3>& vertices) const + { + if (mDelaunay->GetDimension() == 2) + { + std::array indices; + if (mDelaunay->GetIndices(t, indices)) + { + PrimalQuery2 const& query = mDelaunay->GetQuery(); + Vector2 const* ctVertices = query.GetVertices(); + for (int i = 0; i < 3; ++i) + { + Vector2 const& V = ctVertices[indices[i]]; + for (int j = 0; j < 2; ++j) + { + vertices[i][j] = (InputType)V[j]; + } + } + return true; + } + } + return false; + } + + bool GetIndices(int t, std::array& indices) const + { + return mDelaunay->GetIndices(t, indices); + } + + bool GetAdjacencies(int t, std::array& adjacencies) const + { + return mDelaunay->GetAdjacencies(t, adjacencies); + } + + bool GetBarycentrics(int t, Vector2 const& P, std::array& bary) const + { + std::array indices; + if (mDelaunay->GetIndices(t, indices)) + { + PrimalQuery2 const& query = mDelaunay->GetQuery(); + Vector2 const* vertices = query.GetVertices(); + Vector2 rtP{ P[0], P[1] }; + std::array, 3> rtV; + for (int i = 0; i < 3; ++i) + { + Vector2 const& V = vertices[indices[i]]; + for (int j = 0; j < 2; ++j) + { + rtV[i][j] = (RationalType)V[j]; + } + }; + + RationalType rtBary[3]; + if (ComputeBarycentrics(rtP, rtV[0], rtV[1], rtV[2], rtBary)) + { + for (int i = 0; i < 3; ++i) + { + bary[i] = (InputType)rtBary[i]; + } + return true; + } + } + return false; + } + + private: + Delaunay2 const* mDelaunay; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Delaunay3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Delaunay3.h new file mode 100644 index 0000000..670fcd1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Delaunay3.h @@ -0,0 +1,849 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// Delaunay tetrahedralization of points (intrinsic dimensionality 3). +// VQ = number of vertices +// V = array of vertices +// TQ = number of tetrahedra +// I = Array of 4-tuples of indices into V that represent the tetrahedra +// (4*TQ total elements). Access via GetIndices(*). +// A = Array of 4-tuples of indices into I that represent the adjacent +// tetrahedra (4*TQ total elements). Access via GetAdjacencies(*). +// The i-th tetrahedron has vertices +// vertex[0] = V[I[4*i+0]] +// vertex[1] = V[I[4*i+1]] +// vertex[2] = V[I[4*i+2]] +// vertex[3] = V[I[4*i+3]] +// and face index triples listed below. The face vertex ordering when +// viewed from outside the tetrahedron is counterclockwise. +// face[0] = +// face[1] = +// face[2] = +// face[3] = +// The tetrahedra adjacent to these faces have indices +// adjacent[0] = A[4*i+0] is the tetrahedron opposite vertex[0], so it +// is the tetrahedron sharing face[0]. +// adjacent[1] = A[4*i+1] is the tetrahedron opposite vertex[1], so it +// is the tetrahedron sharing face[1]. +// adjacent[2] = A[4*i+2] is the tetrahedron opposite vertex[2], so it +// is the tetrahedron sharing face[2]. +// adjacent[3] = A[4*i+3] is the tetrahedron opposite vertex[3], so it +// is the tetrahedron sharing face[3]. +// If there is no adjacent tetrahedron, the A[*] value is set to -1. The +// tetrahedron adjacent to face[j] has vertices +// adjvertex[0] = V[I[4*adjacent[j]+0]] +// adjvertex[1] = V[I[4*adjacent[j]+1]] +// adjvertex[2] = V[I[4*adjacent[j]+2]] +// adjvertex[3] = V[I[4*adjacent[j]+3]] +// The only way to ensure a correct result for the input vertices (assumed to +// be exact) is to choose ComputeType for exact rational arithmetic. You may +// use BSNumber. No divisions are performed in this computation, so you do +// not have to use BSRational. +// +// The worst-case choices of N for Real of type BSNumber or BSRational with +// integer storage UIntegerFP32 are listed in the next table. The numerical +// computations are encapsulated in PrimalQuery3::ToPlane and +// PrimalQuery3::ToCircumsphere, the latter query the dominant one in +// determining N. We recommend using only BSNumber, because no divisions are +// performed in the convex-hull computations. +// +// input type | compute type | N +// -----------+--------------+-------- +// float | BSNumber | 44 +// float | BSRational | 329 +// double | BSNumber | 298037 +// double | BSRational | 2254442 + +namespace WwiseGTE +{ + template + class Delaunay3 + { + public: + // The class is a functor to support computing the Delaunay + // tetrahedralization of multiple data sets using the same class + // object. + Delaunay3() + : + mEpsilon((InputType)0), + mDimension(0), + mLine(Vector3::Zero(), Vector3::Zero()), + mPlane(Vector3::Zero(), (InputType)0), + mNumVertices(0), + mNumUniqueVertices(0), + mNumTetrahedra(0), + mVertices(nullptr) + { + } + + // The input is the array of vertices whose Delaunay + // tetrahedralization is required. The epsilon value is used to + // determine the intrinsic dimensionality of the vertices + // (d = 0, 1, 2, or 3). When epsilon is positive, the determination + // is fuzzy--vertices approximately the same point, approximately on + // a line, approximately planar, or volumetric. + bool operator()(int numVertices, Vector3 const* vertices, InputType epsilon) + { + mEpsilon = std::max(epsilon, (InputType)0); + mDimension = 0; + mLine.origin = Vector3::Zero(); + mLine.direction = Vector3::Zero(); + mPlane.normal = Vector3::Zero(); + mPlane.constant = (InputType)0; + mNumVertices = numVertices; + mNumUniqueVertices = 0; + mNumTetrahedra = 0; + mVertices = vertices; + mGraph = TSManifoldMesh(); + mIndices.clear(); + mAdjacencies.clear(); + + int i, j; + if (mNumVertices < 4) + { + // Delaunay3 should be called with at least four points. + return false; + } + + IntrinsicsVector3 info(mNumVertices, vertices, mEpsilon); + if (info.dimension == 0) + { + // mDimension is 0; mGraph, mIndices, and mAdjacencies are + // empty + return false; + } + + if (info.dimension == 1) + { + // The set is (nearly) collinear. + mDimension = 1; + mLine = Line3(info.origin, info.direction[0]); + return false; + } + + if (info.dimension == 2) + { + // The set is (nearly) coplanar. + mDimension = 2; + mPlane = Plane3(UnitCross(info.direction[0], + info.direction[1]), info.origin); + return false; + } + + mDimension = 3; + + // Compute the vertices for the queries. + mComputeVertices.resize(mNumVertices); + mQuery.Set(mNumVertices, &mComputeVertices[0]); + for (i = 0; i < mNumVertices; ++i) + { + for (j = 0; j < 3; ++j) + { + mComputeVertices[i][j] = vertices[i][j]; + } + } + + // Insert the (nondegenerate) tetrahedron constructed by the call + // to GetInformation. This is necessary for the circumsphere + // visibility algorithm to work correctly. + if (!info.extremeCCW) + { + std::swap(info.extreme[2], info.extreme[3]); + } + if (!mGraph.Insert(info.extreme[0], info.extreme[1], info.extreme[2], info.extreme[3])) + { + return false; + } + + // Incrementally update the tetrahedralization. The set of + // processed points is maintained to eliminate duplicates, either + // in the original input points or in the points obtained by snap + // rounding. + std::set> processed; + for (i = 0; i < 4; ++i) + { + processed.insert(vertices[info.extreme[i]]); + } + for (i = 0; i < mNumVertices; ++i) + { + if (processed.find(vertices[i]) == processed.end()) + { + if (!Update(i)) + { + // A failure can occur if ComputeType is not an exact + // arithmetic type. + return false; + } + processed.insert(vertices[i]); + } + } + mNumUniqueVertices = static_cast(processed.size()); + + // Assign integer values to the tetrahedra for use by the caller. + std::map, int> permute; + i = -1; + permute[nullptr] = i++; + for (auto const& element : mGraph.GetTetrahedra()) + { + permute[element.second] = i++; + } + + // Put Delaunay tetrahedra into an array (vertices and adjacency + // info). + mNumTetrahedra = static_cast(mGraph.GetTetrahedra().size()); + int numIndices = 4 * mNumTetrahedra; + if (mNumTetrahedra > 0) + { + mIndices.resize(numIndices); + mAdjacencies.resize(numIndices); + i = 0; + for (auto const& element : mGraph.GetTetrahedra()) + { + std::shared_ptr tetra = element.second; + for (j = 0; j < 4; ++j, ++i) + { + mIndices[i] = tetra->V[j]; + mAdjacencies[i] = permute[tetra->S[j].lock()]; + } + } + } + + return true; + } + + // Dimensional information. If GetDimension() returns 1, the points + // lie on a line P+t*D (fuzzy comparison when epsilon > 0). You can + // sort these if you need a polyline output by projecting onto the + // line each vertex X = P+t*D, where t = Dot(D,X-P). If + // GetDimension() returns 2, the points line on a plane P+s*U+t*V + // (fuzzy comparison when epsilon > 0). You can project each vertex + // X = P+s*U+t*V, where s = Dot(U,X-P) and t = Dot(V,X-P), then apply + // Delaunay2 to the (s,t) tuples. + inline InputType GetEpsilon() const + { + return mEpsilon; + } + + inline int GetDimension() const + { + return mDimension; + } + + inline Line3 const& GetLine() const + { + return mLine; + } + + inline Plane3 const& GetPlane() const + { + return mPlane; + } + + // Member access. + inline int GetNumVertices() const + { + return mNumVertices; + } + + inline int GetNumUniqueVertices() const + { + return mNumUniqueVertices; + } + + inline int GetNumTetrahedra() const + { + return mNumTetrahedra; + } + + inline Vector3 const* GetVertices() const + { + return mVertices; + } + + inline PrimalQuery3 const& GetQuery() const + { + return mQuery; + } + + inline TSManifoldMesh const& GetGraph() const + { + return mGraph; + } + + inline std::vector const& GetIndices() const + { + return mIndices; + } + + inline std::vector const& GetAdjacencies() const + { + return mAdjacencies; + } + + // Locate those tetrahedra faces that do not share other tetrahedra. + // The returned array has hull.size() = 3*numFaces indices, each + // triple representing a triangle. The triangles are counterclockwise + // ordered when viewed from outside the hull. The return value is + // 'true' iff the dimension is 3. + bool GetHull(std::vector& hull) const + { + if (mDimension == 3) + { + // Count the number of triangles that are not shared by two + // tetrahedra. + int numTriangles = 0; + for (auto adj : mAdjacencies) + { + if (adj == -1) + { + ++numTriangles; + } + } + + if (numTriangles > 0) + { + // Enumerate the triangles. The prototypical case is the + // single tetrahedron V[0] = (0,0,0), V[1] = (1,0,0), + // V[2] = (0,1,0) and V[3] = (0,0,1) with no adjacent + // tetrahedra. The mIndices[] array is <0,1,2,3>. + // i = 0, face = 0: + // skip index 0, , no swap, triangle = <1,2,3> + // i = 1, face = 1: + // skip index 1, <0,x,2,3>, swap, triangle = <0,3,2> + // i = 2, face = 2: + // skip index 2, <0,1,x,3>, no swap, triangle = <0,1,3> + // i = 3, face = 3: + // skip index 3, <0,1,2,x>, swap, triangle = <0,2,1> + // To guarantee counterclockwise order of triangles when + // viewed outside the tetrahedron, the swap of the last + // two indices occurs when face is an odd number; + // (face % 2) != 0. + hull.resize(3 * numTriangles); + int current = 0, i = 0; + for (auto adj : mAdjacencies) + { + if (adj == -1) + { + int tetra = i / 4, face = i % 4; + for (int j = 0; j < 4; ++j) + { + if (j != face) + { + hull[current++] = mIndices[4 * tetra + j]; + } + } + if ((face % 2) != 0) + { + std::swap(hull[current - 1], hull[current - 2]); + } + } + ++i; + } + return true; + } + else + { + LogError("Unexpected. There must be at least one tetrahedron."); + } + } + else + { + LogError("The dimension must be 3."); + } + } + + // Get the vertex indices for tetrahedron i. The function returns + // 'true' when the dimension is 3 and i is a valid tetrahedron index, + // in which case the vertices are valid; otherwise, the function + // returns 'false' and the vertices are invalid. + bool GetIndices(int i, std::array& indices) const + { + if (mDimension == 3) + { + int numTetrahedra = static_cast(mIndices.size() / 4); + if (0 <= i && i < numTetrahedra) + { + indices[0] = mIndices[4 * i]; + indices[1] = mIndices[4 * i + 1]; + indices[2] = mIndices[4 * i + 2]; + indices[3] = mIndices[4 * i + 3]; + return true; + } + } + else + { + LogError("The dimension must be 3."); + } + return false; + } + + // Get the indices for tetrahedra adjacent to tetrahedron i. The + // function returns 'true' when the dimension is 3 and i is a valid + // tetrahedron index, in which case the adjacencies are valid; + // otherwise, the function returns 'false' and the adjacencies are + // invalid. + bool GetAdjacencies(int i, std::array& adjacencies) const + { + if (mDimension == 3) + { + int numTetrahedra = static_cast(mIndices.size() / 4); + if (0 <= i && i < numTetrahedra) + { + adjacencies[0] = mAdjacencies[4 * i]; + adjacencies[1] = mAdjacencies[4 * i + 1]; + adjacencies[2] = mAdjacencies[4 * i + 2]; + adjacencies[3] = mAdjacencies[4 * i + 3]; + return true; + } + } + else + { + LogError("The dimension must be 3."); + } + return false; + } + + // Support for searching the tetrahedralization for a tetrahedron + // that contains a point. If there is a containing tetrahedron, the + // returned value is a tetrahedron index i with + // 0 <= i < GetNumTetrahedra(). If there is not a containing + // tetrahedron, -1 is returned. The computations are performed using + // exact rational arithmetic. + // + // The SearchInfo input stores information about the tetrahedron + // search when looking for the tetrahedron (if any) that contains p. + // The first tetrahedron searched is 'initialTetrahedron'. On return + // 'path' stores those (ordered) tetrahedron indices visited during + // the search. The last visited tetrahedron has index + // 'finalTetrahedron' and vertex indices 'finalV[0,1,2,3]', stored in + // volumetric counterclockwise order. The last face of the search is + // . For spatially coherent inputs p + // for numerous calls to this function, you will want to specify + // 'finalTetrahedron' from the previous call as 'initialTetrahedron' + // for the next call, which should reduce search times. + struct SearchInfo + { + int initialTetrahedron; + int numPath; + std::vector path; + int finalTetrahedron; + std::array finalV; + }; + + int GetContainingTetrahedron(Vector3 const& p, SearchInfo& info) const + { + if (mDimension == 3) + { + Vector3 test{ p[0], p[1], p[2] }; + + int numTetrahedra = static_cast(mIndices.size() / 4); + info.path.resize(numTetrahedra); + info.numPath = 0; + int tetrahedron; + if (0 <= info.initialTetrahedron + && info.initialTetrahedron < numTetrahedra) + { + tetrahedron = info.initialTetrahedron; + } + else + { + info.initialTetrahedron = 0; + tetrahedron = 0; + } + + // Use tetrahedron faces as binary separating planes. + for (int i = 0; i < numTetrahedra; ++i) + { + int ibase = 4 * tetrahedron; + int const* v = &mIndices[ibase]; + + info.path[info.numPath++] = tetrahedron; + info.finalTetrahedron = tetrahedron; + info.finalV[0] = v[0]; + info.finalV[1] = v[1]; + info.finalV[2] = v[2]; + info.finalV[3] = v[3]; + + // counterclockwise when viewed outside + // tetrahedron. + if (mQuery.ToPlane(test, v[1], v[2], v[3]) > 0) + { + tetrahedron = mAdjacencies[ibase]; + if (tetrahedron == -1) + { + info.finalV[0] = v[1]; + info.finalV[1] = v[2]; + info.finalV[2] = v[3]; + info.finalV[3] = v[0]; + return -1; + } + continue; + } + + // counterclockwise when viewed outside + // tetrahedron. + if (mQuery.ToPlane(test, v[0], v[2], v[3]) < 0) + { + tetrahedron = mAdjacencies[ibase + 1]; + if (tetrahedron == -1) + { + info.finalV[0] = v[0]; + info.finalV[1] = v[2]; + info.finalV[2] = v[3]; + info.finalV[3] = v[1]; + return -1; + } + continue; + } + + // counterclockwise when viewed outside + // tetrahedron. + if (mQuery.ToPlane(test, v[0], v[1], v[3]) > 0) + { + tetrahedron = mAdjacencies[ibase + 2]; + if (tetrahedron == -1) + { + info.finalV[0] = v[0]; + info.finalV[1] = v[1]; + info.finalV[2] = v[3]; + info.finalV[3] = v[2]; + return -1; + } + continue; + } + + // counterclockwise when viewed outside + // tetrahedron. + if (mQuery.ToPlane(test, v[0], v[1], v[2]) < 0) + { + tetrahedron = mAdjacencies[ibase + 3]; + if (tetrahedron == -1) + { + info.finalV[0] = v[0]; + info.finalV[1] = v[1]; + info.finalV[2] = v[2]; + info.finalV[3] = v[3]; + return -1; + } + continue; + } + + return tetrahedron; + } + } + else + { + LogError("The dimension must be 3."); + } + return -1; + } + + private: + // Support for incremental Delaunay tetrahedralization. + typedef TSManifoldMesh::Tetrahedron Tetrahedron; + + bool GetContainingTetrahedron(int i, std::shared_ptr& tetra) const + { + int numTetrahedra = static_cast(mGraph.GetTetrahedra().size()); + for (int t = 0; t < numTetrahedra; ++t) + { + int j; + for (j = 0; j < 4; ++j) + { + auto const& opposite = TetrahedronKey::GetOppositeFace(); + int v0 = tetra->V[opposite[j][0]]; + int v1 = tetra->V[opposite[j][1]]; + int v2 = tetra->V[opposite[j][2]]; + if (mQuery.ToPlane(i, v0, v1, v2) > 0) + { + // Point i sees face from outside the + // tetrahedron. + auto adjTetra = tetra->S[j].lock(); + if (adjTetra) + { + // Traverse to the tetrahedron sharing the face. + tetra = adjTetra; + break; + } + else + { + // We reached a hull face, so the point is outside + // the hull. TODO: Once a hull data structure is + // in place, return tetra->S[j] as the candidate + // for starting a search for visible hull faces. + return false; + } + } + + } + + if (j == 4) + { + // The point is inside all four faces, so the point is inside + // a tetrahedron. + return true; + } + } + + LogError("Unexpected termination of loop."); + } + + bool GetAndRemoveInsertionPolyhedron(int i, std::set>& candidates, + std::set>& boundary) + { + // Locate the tetrahedra that make up the insertion polyhedron. + TSManifoldMesh polyhedron; + while (candidates.size() > 0) + { + std::shared_ptr tetra = *candidates.begin(); + candidates.erase(candidates.begin()); + + for (int j = 0; j < 4; ++j) + { + auto adj = tetra->S[j].lock(); + if (adj && candidates.find(adj) == candidates.end()) + { + int a0 = adj->V[0]; + int a1 = adj->V[1]; + int a2 = adj->V[2]; + int a3 = adj->V[3]; + if (mQuery.ToCircumsphere(i, a0, a1, a2, a3) <= 0) + { + // Point i is in the circumsphere. + candidates.insert(adj); + } + } + } + + int v0 = tetra->V[0]; + int v1 = tetra->V[1]; + int v2 = tetra->V[2]; + int v3 = tetra->V[3]; + if (!polyhedron.Insert(v0, v1, v2, v3)) + { + return false; + } + if (!mGraph.Remove(v0, v1, v2, v3)) + { + return false; + } + } + + // Get the boundary triangles of the insertion polyhedron. + for (auto const& element : polyhedron.GetTetrahedra()) + { + std::shared_ptr tetra = element.second; + for (int j = 0; j < 4; ++j) + { + if (!tetra->S[j].lock()) + { + auto const& opposite = TetrahedronKey::GetOppositeFace(); + int v0 = tetra->V[opposite[j][0]]; + int v1 = tetra->V[opposite[j][1]]; + int v2 = tetra->V[opposite[j][2]]; + boundary.insert(TriangleKey(v0, v1, v2)); + } + } + } + return true; + } + + bool Update(int i) + { + auto const& smap = mGraph.GetTetrahedra(); + std::shared_ptr tetra = smap.begin()->second; + if (GetContainingTetrahedron(i, tetra)) + { + // The point is inside the convex hull. The insertion + // polyhedron contains only tetrahedra in the current + // tetrahedralization; the hull does not change. + + // Use a depth-first search for those tetrahedra whose + // circumspheres contain point i. + std::set> candidates; + candidates.insert(tetra); + + // Get the boundary of the insertion polyhedron C that + // contains the tetrahedra whose circumspheres contain point + // i. Polyhedron C contains the point i. + std::set> boundary; + if (!GetAndRemoveInsertionPolyhedron(i, candidates, boundary)) + { + return false; + } + + // The insertion polyhedron consists of the tetrahedra formed + // by point i and the faces of C. + for (auto const& key : boundary) + { + int v0 = key.V[0]; + int v1 = key.V[1]; + int v2 = key.V[2]; + if (mQuery.ToPlane(i, v0, v1, v2) < 0) + { + if (!mGraph.Insert(i, v0, v1, v2)) + { + return false; + } + } + // else: Point i is on an edge or face of 'tetra', so the + // subdivision has degenerate tetrahedra. Ignore these. + } + } + else + { + // The point is outside the convex hull. The insertion + // polyhedron is formed by point i and any tetrahedra in the + // current tetrahedralization whose circumspheres contain + // point i. + + // Locate the convex hull of the tetrahedra. TODO: Maintain + // a hull data structure that is updated incrementally. + std::set> hull; + for (auto const& element : smap) + { + std::shared_ptr t = element.second; + for (int j = 0; j < 4; ++j) + { + if (!t->S[j].lock()) + { + auto const& opposite = TetrahedronKey::GetOppositeFace(); + int v0 = t->V[opposite[j][0]]; + int v1 = t->V[opposite[j][1]]; + int v2 = t->V[opposite[j][2]]; + hull.insert(TriangleKey(v0, v1, v2)); + } + } + } + + // TODO: Until the hull change, for now just iterate over all + // the hull faces and use the ones visible to point i to + // locate the insertion polyhedron. + auto const& tmap = mGraph.GetTriangles(); + std::set> candidates; + std::set> visible; + for (auto const& key : hull) + { + int v0 = key.V[0]; + int v1 = key.V[1]; + int v2 = key.V[2]; + if (mQuery.ToPlane(i, v0, v1, v2) > 0) + { + auto iter = tmap.find(TriangleKey(v0, v1, v2)); + if (iter != tmap.end() && iter->second->T[1].lock() == nullptr) + { + auto adj = iter->second->T[0].lock(); + if (adj && candidates.find(adj) == candidates.end()) + { + int a0 = adj->V[0]; + int a1 = adj->V[1]; + int a2 = adj->V[2]; + int a3 = adj->V[3]; + if (mQuery.ToCircumsphere(i, a0, a1, a2, a3) <= 0) + { + // Point i is in the circumsphere. + candidates.insert(adj); + } + else + { + // Point i is not in the circumsphere but + // the hull face is visible. + visible.insert(key); + } + } + } + else + { + // TODO: Add a preprocessor symbol to this file + // to allow throwing an exception. Currently, the + // code exits gracefully when floating-point + // rounding causes problems. + // + // LogError("Unexpected condition (ComputeType not exact?)"); + return false; + } + } + } + + // Get the boundary of the insertion subpolyhedron C that + // contains the tetrahedra whose circumspheres contain + // point i. + std::set> boundary; + if (!GetAndRemoveInsertionPolyhedron(i, candidates, boundary)) + { + return false; + } + + // The insertion polyhedron P consists of the tetrahedra + // formed by point i and the back faces of C *and* the visible + // faces of mGraph-C. + for (auto const& key : boundary) + { + int v0 = key.V[0]; + int v1 = key.V[1]; + int v2 = key.V[2]; + if (mQuery.ToPlane(i, v0, v1, v2) < 0) + { + // This is a back face of the boundary. + if (!mGraph.Insert(i, v0, v1, v2)) + { + return false; + } + } + } + for (auto const& key : visible) + { + if (!mGraph.Insert(i, key.V[0], key.V[2], key.V[1])) + { + return false; + } + } + } + + return true; + } + + // The epsilon value is used for fuzzy determination of intrinsic + // dimensionality. If the dimension is 0, 1, or 2, the constructor + // returns early. The caller is responsible for retrieving the + // dimension and taking an alternate path should the dimension be + // smaller than 3. If the dimension is 0, the caller may as well + // treat all vertices[] as a single point, say, vertices[0]. If the + // dimension is 1, the caller can query for the approximating line + // and project vertices[] onto it for further processing. If the + // dimension is 2, the caller can query for the approximating plane + // and project vertices[] onto it for further processing. + InputType mEpsilon; + int mDimension; + Line3 mLine; + Plane3 mPlane; + + // The array of vertices used for geometric queries. If you want to + // be certain of a correct result, choose ComputeType to be BSNumber. + std::vector> mComputeVertices; + PrimalQuery3 mQuery; + + // The graph information. + int mNumVertices; + int mNumUniqueVertices; + int mNumTetrahedra; + Vector3 const* mVertices; + TSManifoldMesh mGraph; + std::vector mIndices; + std::vector mAdjacencies; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Delaunay3Mesh.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Delaunay3Mesh.h new file mode 100644 index 0000000..d762767 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Delaunay3Mesh.h @@ -0,0 +1,126 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + template + class Delaunay3Mesh + { + public: + // Construction. + Delaunay3Mesh(Delaunay3 const& delaunay) + : + mDelaunay(&delaunay) + { + } + + // Mesh information. + inline int GetNumVertices() const + { + return mDelaunay->GetNumVertices(); + } + + inline int GetNumTetrahedra() const + { + return mDelaunay->GetNumTetrahedra(); + } + + inline Vector3 const* GetVertices() const + { + return mDelaunay->GetVertices(); + } + + inline int const* GetIndices() const + { + return &mDelaunay->GetIndices()[0]; + } + + inline int const* GetAdjacencies() const + { + return &mDelaunay->GetAdjacencies()[0]; + } + + // Containment queries. + + int GetContainingTetrahedron(Vector3 const& P) const + { + typename Delaunay3::SearchInfo info; + return mDelaunay->GetContainingTetrahedron(P, info); + } + + bool GetVertices(int t, std::array, 4>& vertices) const + { + if (mDelaunay->GetDimension() == 3) + { + std::array indices; + if (mDelaunay->GetIndices(t, indices)) + { + PrimalQuery3 const& query = mDelaunay->GetQuery(); + Vector3 const* ctVertices = query.GetVertices(); + for (int i = 0; i < 4; ++i) + { + Vector3 const& V = ctVertices[indices[i]]; + for (int j = 0; j < 3; ++j) + { + vertices[i][j] = (InputType)V[j]; + } + } + return true; + } + } + return false; + } + + bool GetIndices(int t, std::array& indices) const + { + return mDelaunay->GetIndices(t, indices); + } + + bool GetAdjacencies(int t, std::array& adjacencies) const + { + return mDelaunay->GetAdjacencies(t, adjacencies); + } + + bool GetBarycentrics(int t, Vector3 const& P, std::array& bary) const + { + std::array indices; + if (mDelaunay->GetIndices(t, indices)) + { + PrimalQuery3 const& query = mDelaunay->GetQuery(); + Vector3 const* vertices = query.GetVertices(); + Vector3 rtP{ P[0], P[1], P[2] }; + std::array, 4> rtV; + for (int i = 0; i < 4; ++i) + { + Vector3 const& V = vertices[indices[i]]; + for (int j = 0; j < 3; ++j) + { + rtV[i][j] = (RationalType)V[j]; + } + }; + + RationalType rtBary[4]; + if (ComputeBarycentrics(rtP, rtV[0], rtV[1], rtV[2], rtV[3], rtBary)) + { + for (int i = 0; i < 4; ++i) + { + bary[i] = (InputType)rtBary[i]; + } + return true; + } + } + return false; + } + + private: + Delaunay3 const* mDelaunay; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DisjointIntervals.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DisjointIntervals.h new file mode 100644 index 0000000..60cc480 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DisjointIntervals.h @@ -0,0 +1,401 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + // Compute Boolean operations of disjoint sets of half-open intervals of + // the form [xmin,xmax) with xmin < xmax. + template + class DisjointIntervals + { + public: + // Construction and destruction. The non-default constructor requires + // that xmin < xmax. + DisjointIntervals() + { + } + + DisjointIntervals(Scalar const& xmin, Scalar const& xmax) + { + if (xmin < xmax) + { + mEndpoints = { xmin, xmax }; + mEndpoints[0] = xmin; + mEndpoints[1] = xmax; + } + } + + ~DisjointIntervals() + { + } + + // Copy operations. + DisjointIntervals(DisjointIntervals const& other) + : + mEndpoints(other.mEndpoints) + { + } + + DisjointIntervals& operator=(DisjointIntervals const& other) + { + mEndpoints = other.mEndpoints; + return *this; + } + + // Move operations. + DisjointIntervals(DisjointIntervals&& other) + : + mEndpoints(std::move(other.mEndpoints)) + { + } + + DisjointIntervals& operator=(DisjointIntervals&& other) + { + mEndpoints = std::move(other.mEndpoints); + return *this; + } + + // The number of intervals in the set. + inline int GetNumIntervals() const + { + return static_cast(mEndpoints.size() / 2); + } + + // The i-th interval is [xmin,xmax). The values xmin and xmax are + // valid only when 0 <= i < GetNumIntervals(). + bool GetInterval(int i, Scalar& xmin, Scalar& xmax) const + { + int index = 2 * i; + if (0 <= index && index < static_cast(mEndpoints.size())) + { + xmin = mEndpoints[index]; + xmax = mEndpoints[++index]; + return true; + } + xmin = (Scalar)0; + xmax = (Scalar)0; + return false; + } + + // Make this set empty. + inline void Clear() + { + mEndpoints.clear(); + } + + // Insert [xmin,xmax) into the set. This is a Boolean 'union' + // operation. The operation is successful only when xmin < xmax. + bool Insert(Scalar const& xmin, Scalar const& xmax) + { + if (xmin < xmax) + { + DisjointIntervals input(xmin, xmax); + DisjointIntervals output = *this | input; + mEndpoints = std::move(output.mEndpoints); + return true; + } + return false; + } + + // Remove [xmin,xmax) from the set. This is a Boolean 'difference' + // operation. The operation is successful only when xmin < xmax. + bool Remove(Scalar const& xmin, Scalar const& xmax) + { + if (xmin < xmax) + { + DisjointIntervals input(xmin, xmax); + DisjointIntervals output = std::move(*this - input); + mEndpoints = std::move(output.mEndpoints); + return true; + } + return false; + } + + // Get the union of the interval sets, input0 union input1. + friend DisjointIntervals operator|(DisjointIntervals const& input0, DisjointIntervals const& input1) + { + DisjointIntervals output; + + size_t const numEndpoints0 = input0.mEndpoints.size(); + size_t const numEndpoints1 = input1.mEndpoints.size(); + size_t i0 = 0, i1 = 0; + int parity0 = 0, parity1 = 0; + while (i0 < numEndpoints0 && i1 < numEndpoints1) + { + Scalar const& value0 = input0.mEndpoints[i0]; + Scalar const& value1 = input1.mEndpoints[i1]; + + if (value0 < value1) + { + if (parity0 == 0) + { + parity0 = 1; + if (parity1 == 0) + { + output.mEndpoints.push_back(value0); + } + } + else + { + if (parity1 == 0) + { + output.mEndpoints.push_back(value0); + } + parity0 = 0; + } + ++i0; + } + else if (value1 < value0) + { + if (parity1 == 0) + { + parity1 = 1; + if (parity0 == 0) + { + output.mEndpoints.push_back(value1); + } + } + else + { + if (parity0 == 0) + { + output.mEndpoints.push_back(value1); + } + parity1 = 0; + } + ++i1; + } + else // value0 == value1 + { + if (parity0 == parity1) + { + output.mEndpoints.push_back(value0); + } + parity0 ^= 1; + parity1 ^= 1; + ++i0; + ++i1; + } + } + + while (i0 < numEndpoints0) + { + output.mEndpoints.push_back(input0.mEndpoints[i0]); + ++i0; + } + + while (i1 < numEndpoints1) + { + output.mEndpoints.push_back(input1.mEndpoints[i1]); + ++i1; + } + + return output; + } + + // Get the intersection of the interval sets, input0 intersect is1. + friend DisjointIntervals operator&(DisjointIntervals const& input0, DisjointIntervals const& input1) + { + DisjointIntervals output; + + size_t const numEndpoints0 = input0.mEndpoints.size(); + size_t const numEndpoints1 = input1.mEndpoints.size(); + size_t i0 = 0, i1 = 0; + int parity0 = 0, parity1 = 0; + while (i0 < numEndpoints0 && i1 < numEndpoints1) + { + Scalar const& value0 = input0.mEndpoints[i0]; + Scalar const& value1 = input1.mEndpoints[i1]; + + if (value0 < value1) + { + if (parity0 == 0) + { + parity0 = 1; + if (parity1 == 1) + { + output.mEndpoints.push_back(value0); + } + } + else + { + if (parity1 == 1) + { + output.mEndpoints.push_back(value0); + } + parity0 = 0; + } + ++i0; + } + else if (value1 < value0) + { + if (parity1 == 0) + { + parity1 = 1; + if (parity0 == 1) + { + output.mEndpoints.push_back(value1); + } + } + else + { + if (parity0 == 1) + { + output.mEndpoints.push_back(value1); + } + parity1 = 0; + } + ++i1; + } + else // value0 == value1 + { + if (parity0 == parity1) + { + output.mEndpoints.push_back(value0); + } + parity0 ^= 1; + parity1 ^= 1; + ++i0; + ++i1; + } + } + + return output; + } + + // Get the differences of the interval sets, input0 minus input1. + friend DisjointIntervals operator-(DisjointIntervals const& input0, DisjointIntervals const& input1) + { + DisjointIntervals output; + + size_t const numEndpoints0 = input0.mEndpoints.size(); + size_t const numEndpoints1 = input1.mEndpoints.size(); + size_t i0 = 0, i1 = 0; + int parity0 = 0, parity1 = 1; + while (i0 < numEndpoints0 && i1 < numEndpoints1) + { + Scalar const& value0 = input0.mEndpoints[i0]; + Scalar const& value1 = input1.mEndpoints[i1]; + + if (value0 < value1) + { + if (parity0 == 0) + { + parity0 = 1; + if (parity1 == 1) + { + output.mEndpoints.push_back(value0); + } + } + else + { + if (parity1 == 1) + { + output.mEndpoints.push_back(value0); + } + parity0 = 0; + } + ++i0; + } + else if (value1 < value0) + { + if (parity1 == 0) + { + parity1 = 1; + if (parity0 == 1) + { + output.mEndpoints.push_back(value1); + } + } + else + { + if (parity0 == 1) + { + output.mEndpoints.push_back(value1); + } + parity1 = 0; + } + ++i1; + } + else // value0 == value1 + { + if (parity0 == parity1) + { + output.mEndpoints.push_back(value0); + } + parity0 ^= 1; + parity1 ^= 1; + ++i0; + ++i1; + } + } + + while (i0 < numEndpoints0) + { + output.mEndpoints.push_back(input0.mEndpoints[i0]); + ++i0; + } + + return output; + } + + // Get the exclusive or of the interval sets, input0 xor input1 = + // (input0 minus input1) or (input1 minus input0). + friend DisjointIntervals operator^(DisjointIntervals const& input0, DisjointIntervals const& input1) + { + DisjointIntervals output; + + size_t const numEndpoints0 = input0.mEndpoints.size(); + size_t const numEndpoints1 = input1.mEndpoints.size(); + size_t i0 = 0, i1 = 0; + while (i0 < numEndpoints0 && i1 < numEndpoints1) + { + Scalar const& value0 = input0.mEndpoints[i0]; + Scalar const& value1 = input1.mEndpoints[i1]; + + if (value0 < value1) + { + output.mEndpoints.push_back(value0); + ++i0; + } + else if (value1 < value0) + { + output.mEndpoints.push_back(value1); + ++i1; + } + else // value0 == value1 + { + ++i0; + ++i1; + } + } + + while (i0 < numEndpoints0) + { + output.mEndpoints.push_back(input0.mEndpoints[i0]); + ++i0; + } + + while (i1 < numEndpoints1) + { + output.mEndpoints.push_back(input1.mEndpoints[i1]); + ++i1; + } + + return output; + } + + private: + // The array of endpoints has an even number of elements. The i-th + // interval is [mEndPoints[2*i],mEndPoints[2*i+1]). + std::vector mEndpoints; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DisjointRectangles.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DisjointRectangles.h new file mode 100644 index 0000000..3b98df8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DisjointRectangles.h @@ -0,0 +1,449 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + // Compute Boolean operations of disjoint sets of half-open rectangles of + // the form [xmin,xmax)x[ymin,ymax) with xmin < xmax and ymin < ymax. + template + class DisjointRectangles + { + public: + // Convenient type definition. + typedef DisjointIntervals ISet; + + // Construction and destruction. The non-default constructor requires + // that xmin < xmax and ymin < ymax. + DisjointRectangles() + : + mNumRectangles(0) + { + } + + DisjointRectangles(Scalar const& xmin, Scalar const& xmax, Scalar const& ymin, Scalar const& ymax) + { + if (xmin < xmax && ymin < ymax) + { + mNumRectangles = 1; + mStrips.push_back(Strip(ymin, ymax, ISet(xmin, xmax))); + } + else + { + mNumRectangles = 0; + } + } + + ~DisjointRectangles() + { + } + + // Copy operations. + DisjointRectangles(DisjointRectangles const& other) + { + *this = other; + } + + DisjointRectangles& operator=(DisjointRectangles const& other) + { + mNumRectangles = other.mNumRectangles; + mStrips = other.mStrips; + return *this; + } + + // Move operations. + DisjointRectangles(DisjointRectangles&& other) + { + *this = std::move(other); + } + + DisjointRectangles& operator=(DisjointRectangles&& other) + { + mNumRectangles = other.mNumRectangles; + mStrips = std::move(other.mStrips); + return *this; + } + + // The rectangle set consists of y-strips of interval sets. + class Strip + { + public: + // Construction and destruction. + Strip() + : + ymin((Scalar)0), + ymax((Scalar)0) + { + } + + Strip(Scalar const& inYMin, Scalar const& inYMax, ISet const& inIntervalSet) + : + ymin(inYMin), + ymax(inYMax), + intervalSet(inIntervalSet) + { + } + + ~Strip() + { + } + + // Copy operations. + Strip(Strip const& other) + { + *this = other; + } + + Strip& operator=(Strip const& other) + { + ymin = other.ymin; + ymax = other.ymax; + intervalSet = other.intervalSet; + return *this; + } + + // Move operations. + Strip(Strip&& other) + { + *this = std::move(other); + } + + Strip& operator=(Strip&& other) + { + ymin = other.ymin; + ymax = other.ymax; + intervalSet = std::move(other.intervalSet); + other.ymin = (Scalar)0; + other.ymax = (Scalar)0; + return *this; + } + + // Member access. + Scalar ymin, ymax; + ISet intervalSet; + }; + + // The number of rectangles in the set. + inline int GetNumRectangles() const + { + return mNumRectangles; + } + + // The i-th rectangle is [xmin,xmax)x[ymin,ymax). The values xmin, + // xmax, ymin and ymax are valid when 0 <= i < GetNumRectangles(). + bool GetRectangle(int i, Scalar& xmin, Scalar& xmax, Scalar& ymin, Scalar& ymax) const + { + int totalQuantity = 0; + for (auto const& strip : mStrips) + { + ISet const& intervalSet = strip.intervalSet; + int xQuantity = intervalSet.GetNumIntervals(); + int nextTotalQuantity = totalQuantity + xQuantity; + if (i < nextTotalQuantity) + { + i -= totalQuantity; + intervalSet.GetInterval(i, xmin, xmax); + ymin = strip.ymin; + ymax = strip.ymax; + return true; + } + totalQuantity = nextTotalQuantity; + } + return false; + } + + // Make this set empty. + inline void Clear() + { + mNumRectangles = 0; + mStrips.clear(); + } + + // The number of y-strips in the set. + inline int GetNumStrips() const + { + return static_cast(mStrips.size()); + } + + // The i-th strip. The returned values are valid when + // 0 <= i < GetStripQuantity(). + bool GetStrip(int i, Scalar& ymin, Scalar& ymax, ISet& xIntervalSet) const + { + if (0 <= i && i < GetNumStrips()) + { + Strip const& strip = mStrips[i]; + ymin = strip.ymin; + ymax = strip.ymax; + xIntervalSet = strip.intervalSet; + return true; + } + return false; + } + + // Insert [xmin,xmax)x[ymin,ymax) into the set. This is a Boolean + // union operation. The operation is successful only when xmin < xmax + // and ymin < ymax. + bool Insert(Scalar const& xmin, Scalar const& xmax, Scalar const& ymin, Scalar const& ymax) + { + if (xmin < xmax && ymin < ymax) + { + DisjointRectangles input(xmin, xmax, ymin, ymax); + DisjointRectangles output = *this | input; + *this = std::move(output); + return true; + } + return false; + } + + // Remove [xmin,xmax)x[ymin,ymax) from the set. This is a Boolean + // difference operation. The operation is successful only when + // xmin < xmax and ymin < ymax. + bool Remove(Scalar const& xmin, Scalar const& xmax, Scalar const& ymin, Scalar const& ymax) + { + if (xmin < xmax && ymin < ymax) + { + DisjointRectangles input(xmin, xmax, ymin, ymax); + DisjointRectangles output = *this - input; + *this = std::move(output); + return true; + } + return false; + } + + // Get the union of the rectangle sets sets, input0 union input1. + friend DisjointRectangles operator|(DisjointRectangles const& input0, DisjointRectangles const& input1) + { + return Execute( + [](ISet const& i0, ISet const& i1) { return i0 | i1; }, + true, true, input0, input1); + } + + // Get the intersection of the rectangle sets, input0 intersect is1. + friend DisjointRectangles operator&(DisjointRectangles const& input0, DisjointRectangles const& input1) + { + return Execute( + [](ISet const& i0, ISet const& i1) { return i0 & i1; }, + false, false, input0, input1); + } + + // Get the differences of the rectangle sets, input0 minus input1. + friend DisjointRectangles operator-(DisjointRectangles const& input0, DisjointRectangles const& input1) + { + return Execute( + [](ISet const& i0, ISet const& i1) { return i0 - i1; }, + false, true, input0, input1); + } + + // Get the exclusive or of the rectangle sets, input0 xor input1 = + // (input0 minus input1) or (input1 minus input0). + friend DisjointRectangles operator^(DisjointRectangles const& input0, DisjointRectangles const& input1) + { + return Execute( + [](ISet const& i0, ISet const& i1) { return i0 ^ i1; }, + true, true, input0, input1); + } + + private: + static DisjointRectangles Execute( + std::function const& operation, + bool unionExclusiveOr, bool unionExclusiveOrDifference, + DisjointRectangles const& input0, DisjointRectangles const& input1) + { + DisjointRectangles output; + + size_t const numStrips0 = input0.GetNumStrips(); + size_t const numStrips1 = input1.GetNumStrips(); + size_t i0 = 0, i1 = 0; + bool getOriginal0 = true, getOriginal1 = true; + Scalar ymin0 = (Scalar)0; + Scalar ymax0 = (Scalar)0; + Scalar ymin1 = (Scalar)0; + Scalar ymax1 = (Scalar)0; + + while (i0 < numStrips0 && i1 < numStrips1) + { + ISet const& intr0 = input0.mStrips[i0].intervalSet; + if (getOriginal0) + { + ymin0 = input0.mStrips[i0].ymin; + ymax0 = input0.mStrips[i0].ymax; + } + + ISet const& intr1 = input1.mStrips[i1].intervalSet; + if (getOriginal1) + { + ymin1 = input1.mStrips[i1].ymin; + ymax1 = input1.mStrips[i1].ymax; + } + + // Case 1. + if (ymax1 <= ymin0) + { + // operator(empty,strip1) + if (unionExclusiveOr) + { + output.mStrips.push_back(Strip(ymin1, ymax1, intr1)); + } + + ++i1; + getOriginal0 = false; + getOriginal1 = true; + continue; // using next ymin1/ymax1 + } + + // Case 11. + if (ymin1 >= ymax0) + { + // operator(strip0,empty) + if (unionExclusiveOrDifference) + { + output.mStrips.push_back(Strip(ymin0, ymax0, intr0)); + } + + ++i0; + getOriginal0 = true; + getOriginal1 = false; + continue; // using next ymin0/ymax0 + } + + // Reduce cases 2, 3, 4 to cases 5, 6, 7. + if (ymin1 < ymin0) + { + // operator(empty,[ymin1,ymin0)) + if (unionExclusiveOr) + { + output.mStrips.push_back(Strip(ymin1, ymin0, intr1)); + } + + ymin1 = ymin0; + getOriginal1 = false; + } + + // Reduce cases 8, 9, 10 to cases 5, 6, 7. + if (ymin1 > ymin0) + { + // operator([ymin0,ymin1),empty) + if (unionExclusiveOrDifference) + { + output.mStrips.push_back(Strip(ymin0, ymin1, intr0)); + } + + ymin0 = ymin1; + getOriginal0 = false; + } + + // Case 5. + if (ymax1 < ymax0) + { + // operator(strip0,[ymin1,ymax1)) + auto result = operation(intr0, intr1); + output.mStrips.push_back(Strip(ymin1, ymax1, result)); + + ymin0 = ymax1; + ++i1; + getOriginal0 = false; + getOriginal1 = true; + continue; // using next ymin1/ymax1 + } + + // Case 6. + if (ymax1 == ymax0) + { + // operator(strip0,[ymin1,ymax1)) + auto result = operation(intr0, intr1); + output.mStrips.push_back(Strip(ymin1, ymax1, result)); + + ++i0; + ++i1; + getOriginal0 = true; + getOriginal1 = true; + continue; // using next ymin0/ymax0 and ymin1/ymax1 + } + + // Case 7. + if (ymax1 > ymax0) + { + // operator(strip0,[ymin1,ymax0)) + auto result = operation(intr0, intr1); + output.mStrips.push_back(Strip(ymin1, ymax0, result)); + + ymin1 = ymax0; + ++i0; + getOriginal0 = true; + getOriginal1 = false; + // continue; using current ymin1/ymax1 + } + } + + if (unionExclusiveOrDifference) + { + while (i0 < numStrips0) + { + if (getOriginal0) + { + ymin0 = input0.mStrips[i0].ymin; + ymax0 = input0.mStrips[i0].ymax; + } + else + { + getOriginal0 = true; + } + + // operator(strip0,empty) + output.mStrips.push_back(Strip(ymin0, ymax0, + input0.mStrips[i0].intervalSet)); + + ++i0; + } + } + + if (unionExclusiveOr) + { + while (i1 < numStrips1) + { + if (getOriginal1) + { + ymin1 = input1.mStrips[i1].ymin; + ymax1 = input1.mStrips[i1].ymax; + } + else + { + getOriginal1 = true; + } + + // operator(empty,strip1) + output.mStrips.push_back(Strip(ymin1, ymax1, + input1.mStrips[i1].intervalSet)); + + ++i1; + } + } + + output.ComputeRectangleQuantity(); + return output; + } + + void ComputeRectangleQuantity() + { + mNumRectangles = 0; + for (auto strip : mStrips) + { + mNumRectangles += strip.intervalSet.GetNumIntervals(); + } + } + + // The number of rectangles in the set. + int mNumRectangles; + + // The y-strips of the set, each containing an x-interval set. + std::vector mStrips; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistAlignedBox3OrientedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistAlignedBox3OrientedBox3.h new file mode 100644 index 0000000..13d56c7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistAlignedBox3OrientedBox3.h @@ -0,0 +1,150 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// Compute the distance between an aligned box and an oriented box in 3D. The +// algorithm is based on using an LCP solver for the convex quadratic +// programming problem. For details, see +// https://www.geometrictools.com/Documentation/ConvexQuadraticProgramming.pdf + + +namespace WwiseGTE +{ + template + class DCPQuery, OrientedBox3> + { + public: + struct Result + { + bool queryIsSuccessful; + + // These members are valid only when queryIsSuccessful is true; + // otherwise, they are all set to zero. + Real distance, sqrDistance; + std::array box0Parameter, box1Parameter; + Vector3 closestPoint[2]; + + // The number of iterations used by LCPSolver regardless of + // whether the query is successful. + int numLCPIterations; + }; + + // Default maximum iterations is 144 (n = 12, maxIterations = n*n). + // If the solver fails to converge, try increasing the maximum number + // of iterations. + void SetMaxLCPIterations(int maxLCPIterations) + { + mLCP.SetMaxIterations(maxLCPIterations); + } + + Result operator()(AlignedBox3 const& box0, OrientedBox3 const& box1) + { + Result result; + + // Translate the boxes so that the aligned box becomes a canonical + // box. Modify the oriented box coefficients to be nonnegative. + Vector3 K = box0.max - box0.min; + Vector3 delta = box1.center - box0.min; + for (int i = 0; i < 3; ++i) + { + delta -= box1.extent[i] * box1.axis[i]; + } + + Vector3 rotDelta; + for (int i = 0; i < 3; ++i) + { + rotDelta[i] = Dot(box1.axis[i], delta); + } + + Vector3 twoExtent = box1.extent * (Real)2; + + // The LCP has 6 variables and 6 (nontrivial) inequality + // constraints. + std::array q = + { + -delta[0], -delta[1], -delta[2], rotDelta[0], rotDelta[1], rotDelta[2], + K[0], K[1], K[2], twoExtent[0], twoExtent[1], twoExtent[2] + }; + + std::array, 12> M; + { + Real const z = (Real)0; + Real const p = (Real)1; + Real const m = (Real)-1; + Vector3 const& U0 = box1.axis[0]; + Vector3 const& U1 = box1.axis[1]; + Vector3 const& U2 = box1.axis[2]; + M[0] = { p, z, z, -U0[0], -U1[0], -U1[2], p, z, z, z, z, z }; + M[1] = { z, p, z, -U0[1], -U1[1], -U1[1], z, p, z, z, z, z }; + M[2] = { z, z, p, -U0[2], -U1[2], -U1[2], z, z, p, z, z, z }; + M[3] = { -U0[0], -U0[1], -U0[2], p, z, z, z, z, z, p, z, z }; + M[4] = { -U1[0], -U1[1], -U1[2], z, p, z, z, z, z, z, p, z }; + M[5] = { -U2[0], -U2[1], -U2[2], z, z, p, z, z, z, z, z, p }; + M[6] = { m, z, z, z, z, z, z, z, z, z, z, z }; + M[7] = { z, m, z, z, z, z, z, z, z, z, z, z }; + M[8] = { z, z, m, z, z, z, z, z, z, z, z, z }; + M[9] = { z, z, z, m, z, z, z, z, z, z, z, z }; + M[10] = { z, z, z, z, m, z, z, z, z, z, z, z }; + M[11] = { z, z, z, z, z, m, z, z, z, z, z, z }; + } + + std::array w, z; + if (mLCP.Solve(q, M, w, z)) + { + result.queryIsSuccessful = true; + + for (int i = 0; i < 3; ++i) + { + result.box0Parameter[i] = z[i] + box0.min[i]; + result.closestPoint[0][i] = result.box0Parameter[i]; + } + + result.closestPoint[1] = box1.center; + for (int i = 0, j = 3; i < 3; ++i, ++j) + { + result.box1Parameter[i] = z[j] - box1.extent[i]; + result.closestPoint[1] += result.box1Parameter[i] * box1.axis[i]; + } + + Vector3 diff = result.closestPoint[1] - result.closestPoint[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + } + else + { + // If you reach this case, the maximum number of iterations + // was not specified to be large enough or there is a problem + // due to floating-point rounding errors. If you believe the + // latter is true, file a bug report. + result.queryIsSuccessful = false; + + for (int i = 0; i < 3; ++i) + { + result.box0Parameter[i] = (Real)0; + result.box1Parameter[i] = (Real)0; + result.closestPoint[0][i] = (Real)0; + result.closestPoint[1][i] = (Real)0; + } + result.distance = (Real)0; + result.sqrDistance = (Real)0; + } + + result.numLCPIterations = mLCP.GetNumIterations(); + return result; + } + + private: + LCPSolver mLCP; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistAlignedBoxAlignedBox.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistAlignedBoxAlignedBox.h new file mode 100644 index 0000000..c3d3273 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistAlignedBoxAlignedBox.h @@ -0,0 +1,73 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, AlignedBox> + { + public: + struct Result + { + Real distance, sqrDistance; + + // To compute a single closest point on each box, use + // Vector closest0 = + // (closestPoints[0].min + closestPoints[0].max)/2; + // Vector closest1 = + // (closestPoints[1].min + closestPoints[1].max)/2; + AlignedBox closestPoints[2]; + }; + + Result operator()(AlignedBox const& box0, AlignedBox const& box1) + { + Result result; + result.sqrDistance = (Real)0; + for (int i = 0; i < N; ++i) + { + if (box0.min[i] >= box1.max[i]) + { + Real delta = box0.min[i] - box1.min[i]; + result.sqrDistance += delta * delta; + result.closestPoints[0].min[i] = box0.min[i]; + result.closestPoints[0].max[i] = box0.min[i]; + result.closestPoints[1].min[i] = box1.max[i]; + result.closestPoints[1].max[i] = box1.max[i]; + } + else if (box1.min[i] >= box0.max[i]) + { + Real delta = box1.min[i] - box0.max[i]; + result.sqrDistance += delta * delta; + result.closestPoints[0].min[i] = box0.max[i]; + result.closestPoints[0].max[i] = box0.max[i]; + result.closestPoints[1].min[i] = box1.min[i]; + result.closestPoints[1].max[i] = box1.min[i]; + } + else + { + std::array intr0 = { box0.min[i], box0.max[i] }; + std::array intr1 = { box1.min[i], box1.max[i] }; + FIQuery, std::array> query; + auto iiResult = query(intr0, intr1); + for (int j = 0; j < 2; ++j) + { + result.closestPoints[j].min[i] = iiResult.overlap[0]; + result.closestPoints[j].max[i] = iiResult.overlap[1]; + } + } + } + result.distance = std::sqrt(result.sqrDistance); + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistCircle3Circle3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistCircle3Circle3.h new file mode 100644 index 0000000..8f73cfe --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistCircle3Circle3.h @@ -0,0 +1,387 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The 3D circle-circle distance algorithm is described in +// https://www.geometrictools.com/Documentation/DistanceToCircle3.pdf +// The notation used in the code matches that of the document. + +namespace WwiseGTE +{ + template + class DCPQuery, Circle3> + { + public: + struct Result + { + Real distance, sqrDistance; + int numClosestPairs; + Vector3 circle0Closest[2], circle1Closest[2]; + bool equidistant; + }; + + Result operator()(Circle3 const& circle0, Circle3 const& circle1) + { + Result result; + Vector3 const vzero = Vector3::Zero(); + Real const zero = (Real)0; + + Vector3 N0 = circle0.normal, N1 = circle1.normal; + Real r0 = circle0.radius, r1 = circle1.radius; + Vector3 D = circle1.center - circle0.center; + Vector3 N0xN1 = Cross(N0, N1); + + if (N0xN1 != vzero) + { + // Get parameters for constructing the degree-8 polynomial phi. + Real const one = (Real)1, two = (Real)2; + Real r0sqr = r0 * r0, r1sqr = r1 * r1; + + // Compute U1 and V1 for the plane of circle1. + Vector3 basis[3]; + basis[0] = circle1.normal; + ComputeOrthogonalComplement(1, basis); + Vector3 U1 = basis[1], V1 = basis[2]; + + // Construct the polynomial phi(cos(theta)). + Vector3 N0xD = Cross(N0, D); + Vector3 N0xU1 = Cross(N0, U1), N0xV1 = Cross(N0, V1); + Real a0 = r1 * Dot(D, U1), a1 = r1 * Dot(D, V1); + Real a2 = Dot(N0xD, N0xD), a3 = r1 * Dot(N0xD, N0xU1); + Real a4 = r1 * Dot(N0xD, N0xV1), a5 = r1sqr * Dot(N0xU1, N0xU1); + Real a6 = r1sqr * Dot(N0xU1, N0xV1), a7 = r1sqr * Dot(N0xV1, N0xV1); + Polynomial1 p0{ a2 + a7, two * a3, a5 - a7 }; + Polynomial1 p1{ two * a4, two * a6 }; + Polynomial1 p2{ zero, a1 }; + Polynomial1 p3{ -a0 }; + Polynomial1 p4{ -a6, a4, two * a6 }; + Polynomial1 p5{ -a3, a7 - a5 }; + Polynomial1 tmp0{ one, zero, -one }; + Polynomial1 tmp1 = p2 * p2 + tmp0 * p3 * p3; + Polynomial1 tmp2 = two * p2 * p3; + Polynomial1 tmp3 = p4 * p4 + tmp0 * p5 * p5; + Polynomial1 tmp4 = two * p4 * p5; + Polynomial1 p6 = p0 * tmp1 + tmp0 * p1 * tmp2 - r0sqr * tmp3; + Polynomial1 p7 = p0 * tmp2 + p1 * tmp1 - r0sqr * tmp4; + + // The use of 'double' is intentional in case Real is a BSNumber or + // BSRational type. We want the bisections to terminate in a + // reasonable amount of time. + unsigned int const maxIterations = GTE_C_MAX_BISECTIONS_GENERIC; + Real roots[8], sn, temp; + int i, degree, numRoots; + + // The RootsPolynomial::Find(...) function currently does not + // combine duplicate roots. We need only the unique ones here. + std::set uniqueRoots; + + std::array, 16> pairs; + int numPairs = 0; + if (p7.GetDegree() > 0 || p7[0] != zero) + { + // H(cs,sn) = p6(cs) + sn * p7(cs) + Polynomial1 phi = p6 * p6 - tmp0 * p7 * p7; + degree = static_cast(phi.GetDegree()); + LogAssert(degree > 0, "Unexpected degree for phi."); + numRoots = RootsPolynomial::Find(degree, &phi[0], maxIterations, roots); + for (i = 0; i < numRoots; ++i) + { + uniqueRoots.insert(roots[i]); + } + + for (auto cs : uniqueRoots) + { + if (std::fabs(cs) <= one) + { + temp = p7(cs); + if (temp != zero) + { + sn = -p6(cs) / temp; + pairs[numPairs++] = std::make_pair(cs, sn); + } + else + { + temp = std::max(one - cs * cs, zero); + sn = std::sqrt(temp); + pairs[numPairs++] = std::make_pair(cs, sn); + if (sn != zero) + { + pairs[numPairs++] = std::make_pair(cs, -sn); + } + } + } + } + } + else + { + // H(cs,sn) = p6(cs) + degree = static_cast(p6.GetDegree()); + LogAssert(degree > 0, "Unexpected degree for p6."); + numRoots = RootsPolynomial::Find(degree, &p6[0], maxIterations, roots); + for (i = 0; i < numRoots; ++i) + { + uniqueRoots.insert(roots[i]); + } + + for (auto cs : uniqueRoots) + { + if (std::fabs(cs) <= one) + { + temp = std::max(one - cs * cs, zero); + sn = std::sqrt(temp); + pairs[numPairs++] = std::make_pair(cs, sn); + if (sn != zero) + { + pairs[numPairs++] = std::make_pair(cs, -sn); + } + } + } + } + + std::array candidates; + for (i = 0; i < numPairs; ++i) + { + ClosestInfo& info = candidates[i]; + Vector3 delta = + D + r1 * (pairs[i].first * U1 + pairs[i].second * V1); + info.circle1Closest = circle0.center + delta; + Real N0dDelta = Dot(N0, delta); + Real lenN0xDelta = Length(Cross(N0, delta)); + if (lenN0xDelta > 0) + { + Real diff = lenN0xDelta - r0; + info.sqrDistance = N0dDelta * N0dDelta + diff * diff; + delta -= N0dDelta * circle0.normal; + Normalize(delta); + info.circle0Closest = circle0.center + r0 * delta; + info.equidistant = false; + } + else + { + Vector3 r0U0 = r0 * GetOrthogonal(N0, true); + Vector3 diff = delta - r0U0; + info.sqrDistance = Dot(diff, diff); + info.circle0Closest = circle0.center + r0U0; + info.equidistant = true; + } + } + + std::sort(candidates.begin(), candidates.begin() + numPairs); + + result.numClosestPairs = 1; + result.sqrDistance = candidates[0].sqrDistance; + result.circle0Closest[0] = candidates[0].circle0Closest; + result.circle1Closest[0] = candidates[0].circle1Closest; + result.equidistant = candidates[0].equidistant; + if (numRoots > 1 + && candidates[1].sqrDistance == candidates[0].sqrDistance) + { + result.numClosestPairs = 2; + result.circle0Closest[1] = candidates[1].circle0Closest; + result.circle1Closest[1] = candidates[1].circle1Closest; + } + } + else + { + // The planes of the circles are parallel. Whether the planes + // are the same or different, the problem reduces to + // determining how two circles in the same plane are + // separated, tangent with one circle outside the other, + // overlapping, or one circle contained inside the other + // circle. + DoQueryParallelPlanes(circle0, circle1, D, result); + } + + result.distance = std::sqrt(result.sqrDistance); + return result; + } + + private: + class SCPolynomial + { + public: + SCPolynomial() + { + } + + SCPolynomial(Real oneTerm, Real cosTerm, Real sinTerm) + { + mPoly[0] = Polynomial1{ oneTerm, cosTerm }; + mPoly[1] = Polynomial1{ sinTerm }; + } + + inline Polynomial1 const& operator[] (unsigned int i) const + { + return mPoly[i]; + } + + inline Polynomial1& operator[] (unsigned int i) + { + return mPoly[i]; + } + + SCPolynomial operator+(SCPolynomial const& object) const + { + SCPolynomial result; + result.mPoly[0] = mPoly[0] + object.mPoly[0]; + result.mPoly[1] = mPoly[1] + object.mPoly[1]; + return result; + } + + SCPolynomial operator-(SCPolynomial const& object) const + { + SCPolynomial result; + result.mPoly[0] = mPoly[0] - object.mPoly[0]; + result.mPoly[1] = mPoly[1] - object.mPoly[1]; + return result; + } + + SCPolynomial operator*(SCPolynomial const& object) const + { + // 1 - c^2 + Polynomial1 omcsqr{ (Real)1, (Real)0, (Real)-1 }; + SCPolynomial result; + result.mPoly[0] = mPoly[0] * object.mPoly[0] + omcsqr * mPoly[1] * object.mPoly[1]; + result.mPoly[1] = mPoly[0] * object.mPoly[1] + mPoly[1] * object.mPoly[0]; + return result; + } + + SCPolynomial operator*(Real scalar) const + { + SCPolynomial result; + result.mPoly[0] = scalar * mPoly[0]; + result.mPoly[1] = scalar * mPoly[1]; + return result; + } + + private: + // poly0(c) + s * poly1(c) + Polynomial1 mPoly[2]; + }; + + struct ClosestInfo + { + Real sqrDistance; + Vector3 circle0Closest, circle1Closest; + bool equidistant; + + inline bool operator< (ClosestInfo const& info) const + { + return sqrDistance < info.sqrDistance; + } + }; + + // The two circles are in parallel planes where D = C1 - C0, the + // difference of circle centers. + void DoQueryParallelPlanes(Circle3 const& circle0, + Circle3 const& circle1, Vector3 const& D, Result& result) + { + Real N0dD = Dot(circle0.normal, D); + Vector3 normProj = N0dD * circle0.normal; + Vector3 compProj = D - normProj; + Vector3 U = compProj; + Real d = Normalize(U); + + // The configuration is determined by the relative location of the + // intervals of projection of the circles on to the D-line. + // Circle0 projects to [-r0,r0] and circle1 projects to + // [d-r1,d+r1]. + Real r0 = circle0.radius, r1 = circle1.radius; + Real dmr1 = d - r1; + Real distance; + if (dmr1 >= r0) // d >= r0 + r1 + { + // The circles are separated (d > r0 + r1) or tangent with one + // outside the other (d = r0 + r1). + distance = dmr1 - r0; + result.numClosestPairs = 1; + result.circle0Closest[0] = circle0.center + r0 * U; + result.circle1Closest[0] = circle1.center - r1 * U; + result.equidistant = false; + } + else // d < r0 + r1 + { + // The cases implicitly use the knowledge that d >= 0. + Real dpr1 = d + r1; + if (dpr1 <= r0) + { + // Circle1 is inside circle0. + distance = r0 - dpr1; + result.numClosestPairs = 1; + if (d > (Real)0) + { + result.circle0Closest[0] = circle0.center + r0 * U; + result.circle1Closest[0] = circle1.center + r1 * U; + result.equidistant = false; + } + else + { + // The circles are concentric, so U = (0,0,0). + // Construct a vector perpendicular to N0 to use for + // closest points. + U = GetOrthogonal(circle0.normal, true); + result.circle0Closest[0] = circle0.center + r0 * U; + result.circle1Closest[0] = circle1.center + r1 * U; + result.equidistant = true; + } + } + else if (dmr1 <= -r0) + { + // Circle0 is inside circle1. + distance = -r0 - dmr1; + result.numClosestPairs = 1; + if (d > (Real)0) + { + result.circle0Closest[0] = circle0.center - r0 * U; + result.circle1Closest[0] = circle1.center - r1 * U; + result.equidistant = false; + } + else + { + // The circles are concentric, so U = (0,0,0). + // Construct a vector perpendicular to N0 to use for + // closest points. + U = GetOrthogonal(circle0.normal, true); + result.circle0Closest[0] = circle0.center + r0 * U; + result.circle1Closest[0] = circle1.center + r1 * U; + result.equidistant = true; + } + } + else + { + // The circles are overlapping. The two points of + // intersection are C0 + s*(C1-C0) +/- h*Cross(N,U), where + // s = (1 + (r0^2 - r1^2)/d^2)/2 and + // h = sqrt(r0^2 - s^2 * d^2). + Real r0sqr = r0 * r0, r1sqr = r1 * r1, dsqr = d * d; + Real s = ((Real)1 + (r0sqr - r1sqr) / dsqr) / (Real)2; + Real arg = std::max(r0sqr - dsqr * s * s, (Real)0); + Real h = std::sqrt(arg); + Vector3 midpoint = circle0.center + s * compProj; + Vector3 hNxU = h * Cross(circle0.normal, U); + distance = (Real)0; + result.numClosestPairs = 2; + result.circle0Closest[0] = midpoint + hNxU; + result.circle0Closest[1] = midpoint - hNxU; + result.circle1Closest[0] = result.circle0Closest[0] + normProj; + result.circle1Closest[1] = result.circle0Closest[1] + normProj; + result.equidistant = false; + } + } + + result.sqrDistance = distance * distance + N0dD * N0dD; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLine3AlignedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLine3AlignedBox3.h new file mode 100644 index 0000000..9b262ed --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLine3AlignedBox3.h @@ -0,0 +1,514 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, AlignedBox3> + { + public: + struct Result + { + Real distance, sqrDistance; + Real lineParameter; + Vector3 closestPoint[2]; + }; + + Result operator()(Line3 const& line, AlignedBox3 const& box) + { + // Translate the line and box so that the box has center at the + // origin. + Vector3 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + Vector3 point = line.origin - boxCenter; + Vector3 direction = line.direction; + + Result result; + DoQuery(point, direction, boxExtent, result); + + // Compute the closest point on the line. + result.closestPoint[0] = line.origin + result.lineParameter * line.direction; + + // Compute the closest point on the box. + result.closestPoint[1] = boxCenter + point; + return result; + } + + protected: + // Compute the distance and closest point between a line and an + // axis-aligned box whose center is the origin. On input, 'point' is + // the line origin and 'direction' is the line direction. On output, + // 'point' is the point on the box closest to the line. The + // 'direction' is non-const to allow transforming the problem into + // the first octant. + void DoQuery(Vector3& point, Vector3& direction, + Vector3 const& boxExtent, Result& result) + { + result.sqrDistance = (Real)0; + result.lineParameter = (Real)0; + + // Apply reflections so that direction vector has nonnegative + // components. + bool reflect[3]; + for (int i = 0; i < 3; ++i) + { + if (direction[i] < (Real)0) + { + point[i] = -point[i]; + direction[i] = -direction[i]; + reflect[i] = true; + } + else + { + reflect[i] = false; + } + } + + if (direction[0] > (Real)0) + { + if (direction[1] > (Real)0) + { + if (direction[2] > (Real)0) // (+,+,+) + { + CaseNoZeros(point, direction, boxExtent, result); + } + else // (+,+,0) + { + Case0(0, 1, 2, point, direction, boxExtent, result); + } + } + else + { + if (direction[2] > (Real)0) // (+,0,+) + { + Case0(0, 2, 1, point, direction, boxExtent, result); + } + else // (+,0,0) + { + Case00(0, 1, 2, point, direction, boxExtent, result); + } + } + } + else + { + if (direction[1] > (Real)0) + { + if (direction[2] > (Real)0) // (0,+,+) + { + Case0(1, 2, 0, point, direction, boxExtent, result); + } + else // (0,+,0) + { + Case00(1, 0, 2, point, direction, boxExtent, result); + } + } + else + { + if (direction[2] > (Real)0) // (0,0,+) + { + Case00(2, 0, 1, point, direction, boxExtent, result); + } + else // (0,0,0) + { + Case000(point, boxExtent, result); + } + } + } + + // Undo the reflections applied previously. + for (int i = 0; i < 3; ++i) + { + if (reflect[i]) + { + point[i] = -point[i]; + } + } + + result.distance = std::sqrt(result.sqrDistance); + } + + private: + void Face(int i0, int i1, int i2, Vector3& pnt, + Vector3 const& dir, Vector3 const& PmE, + Vector3 const& boxExtent, Result& result) + { + Vector3 PpE; + Real lenSqr, inv, tmp, param, t, delta; + + PpE[i1] = pnt[i1] + boxExtent[i1]; + PpE[i2] = pnt[i2] + boxExtent[i2]; + if (dir[i0] * PpE[i1] >= dir[i1] * PmE[i0]) + { + if (dir[i0] * PpE[i2] >= dir[i2] * PmE[i0]) + { + // v[i1] >= -e[i1], v[i2] >= -e[i2] (distance = 0) + pnt[i0] = boxExtent[i0]; + inv = ((Real)1) / dir[i0]; + pnt[i1] -= dir[i1] * PmE[i0] * inv; + pnt[i2] -= dir[i2] * PmE[i0] * inv; + result.lineParameter = -PmE[i0] * inv; + } + else + { + // v[i1] >= -e[i1], v[i2] < -e[i2] + lenSqr = dir[i0] * dir[i0] + dir[i2] * dir[i2]; + tmp = lenSqr * PpE[i1] - dir[i1] * (dir[i0] * PmE[i0] + + dir[i2] * PpE[i2]); + if (tmp <= ((Real)2) * lenSqr * boxExtent[i1]) + { + t = tmp / lenSqr; + lenSqr += dir[i1] * dir[i1]; + tmp = PpE[i1] - t; + delta = dir[i0] * PmE[i0] + dir[i1] * tmp + dir[i2] * PpE[i2]; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + tmp * tmp + + PpE[i2] * PpE[i2] + delta * param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = t - boxExtent[i1]; + pnt[i2] = -boxExtent[i2]; + } + else + { + lenSqr += dir[i1] * dir[i1]; + delta = dir[i0] * PmE[i0] + dir[i1] * PmE[i1] + dir[i2] * PpE[i2]; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + PmE[i1] * PmE[i1] + + PpE[i2] * PpE[i2] + delta * param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = boxExtent[i1]; + pnt[i2] = -boxExtent[i2]; + } + } + } + else + { + if (dir[i0] * PpE[i2] >= dir[i2] * PmE[i0]) + { + // v[i1] < -e[i1], v[i2] >= -e[i2] + lenSqr = dir[i0] * dir[i0] + dir[i1] * dir[i1]; + tmp = lenSqr * PpE[i2] - dir[i2] * (dir[i0] * PmE[i0] + + dir[i1] * PpE[i1]); + if (tmp <= ((Real)2) * lenSqr * boxExtent[i2]) + { + t = tmp / lenSqr; + lenSqr += dir[i2] * dir[i2]; + tmp = PpE[i2] - t; + delta = dir[i0] * PmE[i0] + dir[i1] * PpE[i1] + dir[i2] * tmp; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + PpE[i1] * PpE[i1] + + tmp * tmp + delta * param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = -boxExtent[i1]; + pnt[i2] = t - boxExtent[i2]; + } + else + { + lenSqr += dir[i2] * dir[i2]; + delta = dir[i0] * PmE[i0] + dir[i1] * PpE[i1] + dir[i2] * PmE[i2]; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + PpE[i1] * PpE[i1] + + PmE[i2] * PmE[i2] + delta * param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = -boxExtent[i1]; + pnt[i2] = boxExtent[i2]; + } + } + else + { + // v[i1] < -e[i1], v[i2] < -e[i2] + lenSqr = dir[i0] * dir[i0] + dir[i2] * dir[i2]; + tmp = lenSqr * PpE[i1] - dir[i1] * (dir[i0] * PmE[i0] + + dir[i2] * PpE[i2]); + if (tmp >= (Real)0) + { + // v[i1]-edge is closest + if (tmp <= ((Real)2) * lenSqr * boxExtent[i1]) + { + t = tmp / lenSqr; + lenSqr += dir[i1] * dir[i1]; + tmp = PpE[i1] - t; + delta = dir[i0] * PmE[i0] + dir[i1] * tmp + dir[i2] * PpE[i2]; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + tmp * tmp + + PpE[i2] * PpE[i2] + delta * param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = t - boxExtent[i1]; + pnt[i2] = -boxExtent[i2]; + } + else + { + lenSqr += dir[i1] * dir[i1]; + delta = dir[i0] * PmE[i0] + dir[i1] * PmE[i1] + + dir[i2] * PpE[i2]; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + PmE[i1] * PmE[i1] + + PpE[i2] * PpE[i2] + delta * param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = boxExtent[i1]; + pnt[i2] = -boxExtent[i2]; + } + return; + } + + lenSqr = dir[i0] * dir[i0] + dir[i1] * dir[i1]; + tmp = lenSqr * PpE[i2] - dir[i2] * (dir[i0] * PmE[i0] + + dir[i1] * PpE[i1]); + if (tmp >= (Real)0) + { + // v[i2]-edge is closest + if (tmp <= ((Real)2) * lenSqr * boxExtent[i2]) + { + t = tmp / lenSqr; + lenSqr += dir[i2] * dir[i2]; + tmp = PpE[i2] - t; + delta = dir[i0] * PmE[i0] + dir[i1] * PpE[i1] + dir[i2] * tmp; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + PpE[i1] * PpE[i1] + + tmp * tmp + delta * param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = -boxExtent[i1]; + pnt[i2] = t - boxExtent[i2]; + } + else + { + lenSqr += dir[i2] * dir[i2]; + delta = dir[i0] * PmE[i0] + dir[i1] * PpE[i1] + + dir[i2] * PmE[i2]; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + PpE[i1] * PpE[i1] + + PmE[i2] * PmE[i2] + delta * param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = -boxExtent[i1]; + pnt[i2] = boxExtent[i2]; + } + return; + } + + // (v[i1],v[i2])-corner is closest + lenSqr += dir[i2] * dir[i2]; + delta = dir[i0] * PmE[i0] + dir[i1] * PpE[i1] + dir[i2] * PpE[i2]; + param = -delta / lenSqr; + result.sqrDistance += PmE[i0] * PmE[i0] + PpE[i1] * PpE[i1] + + PpE[i2] * PpE[i2] + delta * param; + + result.lineParameter = param; + pnt[i0] = boxExtent[i0]; + pnt[i1] = -boxExtent[i1]; + pnt[i2] = -boxExtent[i2]; + } + } + } + + void CaseNoZeros(Vector3& pnt, Vector3 const& dir, + Vector3 const& boxExtent, Result& result) + { + Vector3 PmE = pnt - boxExtent; + Real prodDxPy = dir[0] * PmE[1]; + Real prodDyPx = dir[1] * PmE[0]; + Real prodDzPx, prodDxPz, prodDzPy, prodDyPz; + + if (prodDyPx >= prodDxPy) + { + prodDzPx = dir[2] * PmE[0]; + prodDxPz = dir[0] * PmE[2]; + if (prodDzPx >= prodDxPz) + { + // line intersects x = e0 + Face(0, 1, 2, pnt, dir, PmE, boxExtent, result); + } + else + { + // line intersects z = e2 + Face(2, 0, 1, pnt, dir, PmE, boxExtent, result); + } + } + else + { + prodDzPy = dir[2] * PmE[1]; + prodDyPz = dir[1] * PmE[2]; + if (prodDzPy >= prodDyPz) + { + // line intersects y = e1 + Face(1, 2, 0, pnt, dir, PmE, boxExtent, result); + } + else + { + // line intersects z = e2 + Face(2, 0, 1, pnt, dir, PmE, boxExtent, result); + } + } + } + + void Case0(int i0, int i1, int i2, Vector3& pnt, + Vector3 const& dir, Vector3 const& boxExtent, Result& result) + { + Real PmE0 = pnt[i0] - boxExtent[i0]; + Real PmE1 = pnt[i1] - boxExtent[i1]; + Real prod0 = dir[i1] * PmE0; + Real prod1 = dir[i0] * PmE1; + Real delta, invLSqr, inv; + + if (prod0 >= prod1) + { + // line intersects P[i0] = e[i0] + pnt[i0] = boxExtent[i0]; + + Real PpE1 = pnt[i1] + boxExtent[i1]; + delta = prod0 - dir[i0] * PpE1; + if (delta >= (Real)0) + { + invLSqr = ((Real)1) / (dir[i0] * dir[i0] + dir[i1] * dir[i1]); + result.sqrDistance += delta * delta * invLSqr; + pnt[i1] = -boxExtent[i1]; + result.lineParameter = -(dir[i0] * PmE0 + dir[i1] * PpE1) * invLSqr; + } + else + { + inv = ((Real)1) / dir[i0]; + pnt[i1] -= prod0 * inv; + result.lineParameter = -PmE0 * inv; + } + } + else + { + // line intersects P[i1] = e[i1] + pnt[i1] = boxExtent[i1]; + + Real PpE0 = pnt[i0] + boxExtent[i0]; + delta = prod1 - dir[i1] * PpE0; + if (delta >= (Real)0) + { + invLSqr = ((Real)1) / (dir[i0] * dir[i0] + dir[i1] * dir[i1]); + result.sqrDistance += delta * delta * invLSqr; + pnt[i0] = -boxExtent[i0]; + result.lineParameter = -(dir[i0] * PpE0 + dir[i1] * PmE1) * invLSqr; + } + else + { + inv = ((Real)1) / dir[i1]; + pnt[i0] -= prod1 * inv; + result.lineParameter = -PmE1 * inv; + } + } + + if (pnt[i2] < -boxExtent[i2]) + { + delta = pnt[i2] + boxExtent[i2]; + result.sqrDistance += delta * delta; + pnt[i2] = -boxExtent[i2]; + } + else if (pnt[i2] > boxExtent[i2]) + { + delta = pnt[i2] - boxExtent[i2]; + result.sqrDistance += delta * delta; + pnt[i2] = boxExtent[i2]; + } + } + + void Case00(int i0, int i1, int i2, Vector3& pnt, + Vector3 const& dir, Vector3 const& boxExtent, Result& result) + { + Real delta; + + result.lineParameter = (boxExtent[i0] - pnt[i0]) / dir[i0]; + + pnt[i0] = boxExtent[i0]; + + if (pnt[i1] < -boxExtent[i1]) + { + delta = pnt[i1] + boxExtent[i1]; + result.sqrDistance += delta * delta; + pnt[i1] = -boxExtent[i1]; + } + else if (pnt[i1] > boxExtent[i1]) + { + delta = pnt[i1] - boxExtent[i1]; + result.sqrDistance += delta * delta; + pnt[i1] = boxExtent[i1]; + } + + if (pnt[i2] < -boxExtent[i2]) + { + delta = pnt[i2] + boxExtent[i2]; + result.sqrDistance += delta * delta; + pnt[i2] = -boxExtent[i2]; + } + else if (pnt[i2] > boxExtent[i2]) + { + delta = pnt[i2] - boxExtent[i2]; + result.sqrDistance += delta * delta; + pnt[i2] = boxExtent[i2]; + } + } + + void Case000(Vector3& pnt, Vector3 const& boxExtent, Result& result) + { + Real delta; + + if (pnt[0] < -boxExtent[0]) + { + delta = pnt[0] + boxExtent[0]; + result.sqrDistance += delta * delta; + pnt[0] = -boxExtent[0]; + } + else if (pnt[0] > boxExtent[0]) + { + delta = pnt[0] - boxExtent[0]; + result.sqrDistance += delta * delta; + pnt[0] = boxExtent[0]; + } + + if (pnt[1] < -boxExtent[1]) + { + delta = pnt[1] + boxExtent[1]; + result.sqrDistance += delta * delta; + pnt[1] = -boxExtent[1]; + } + else if (pnt[1] > boxExtent[1]) + { + delta = pnt[1] - boxExtent[1]; + result.sqrDistance += delta * delta; + pnt[1] = boxExtent[1]; + } + + if (pnt[2] < -boxExtent[2]) + { + delta = pnt[2] + boxExtent[2]; + result.sqrDistance += delta * delta; + pnt[2] = -boxExtent[2]; + } + else if (pnt[2] > boxExtent[2]) + { + delta = pnt[2] - boxExtent[2]; + result.sqrDistance += delta * delta; + pnt[2] = boxExtent[2]; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLine3Circle3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLine3Circle3.h new file mode 100644 index 0000000..08e54d6 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLine3Circle3.h @@ -0,0 +1,431 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// The 3D line-circle distance algorithm is described in +// https://www.geometrictools.com/Documentation/DistanceToCircle3.pdf +// The notation used in the code matches that of the document. + +namespace WwiseGTE +{ + template + class DCPQuery, Circle3> + { + public: + // The possible number of closest line-circle pairs is 1, 2 or all + // circle points. If 1 or 2, numClosestPairs is set to this number + // and 'equidistant' is false; the number of valid elements in + // lineClosest[] and circleClosest[] is numClosestPairs. If all + // circle points are closest, the line must be C+t*N where C is the + // circle center, N is the normal to the plane of the circle, and + // lineClosest[0] is set to C. In this case, 'equidistant' is true + // and circleClosest[0] is set to C+r*U, where r is the circle + // and U is a vector perpendicular to N. + struct Result + { + Real distance, sqrDistance; + int numClosestPairs; + Vector3 lineClosest[2], circleClosest[2]; + bool equidistant; + }; + + // The polynomial-based algorithm. Type Real can be floating-point or + // rational. + Result operator()(Line3 const& line, Circle3 const& circle) + { + Result result; + Vector3 const vzero = Vector3::Zero(); + Real const zero = (Real)0; + + Vector3 D = line.origin - circle.center; + Vector3 NxM = Cross(circle.normal, line.direction); + Vector3 NxD = Cross(circle.normal, D); + Real t; + + if (NxM != vzero) + { + if (NxD != vzero) + { + Real NdM = Dot(circle.normal, line.direction); + if (NdM != zero) + { + // H(t) = (a*t^2 + 2*b*t + c)*(t + d)^2 + // - r^2*(a*t + b)^2 + // = h0 + h1*t + h2*t^2 + h3*t^3 + h4*t^4 + Real a = Dot(NxM, NxM), b = Dot(NxM, NxD); + Real c = Dot(NxD, NxD), d = Dot(line.direction, D); + Real rsqr = circle.radius * circle.radius; + Real asqr = a * a, bsqr = b * b, dsqr = d * d; + Real h0 = c * dsqr - bsqr * rsqr; + Real h1 = 2 * (c * d + b * dsqr - a * b * rsqr); + Real h2 = c + 4 * b * d + a * dsqr - asqr * rsqr; + Real h3 = 2 * (b + a * d); + Real h4 = a; + + std::map rmMap; + RootsPolynomial::template SolveQuartic( + h0, h1, h2, h3, h4, rmMap); + std::array candidates; + int numRoots = 0; + for (auto const& rm : rmMap) + { + t = rm.first; + ClosestInfo info; + Vector3 NxDelta = NxD + t * NxM; + if (NxDelta != vzero) + { + GetPair(line, circle, D, t, info.lineClosest, + info.circleClosest); + info.equidistant = false; + } + else + { + Vector3 U = GetOrthogonal(circle.normal, true); + info.lineClosest = circle.center; + info.circleClosest = + circle.center + circle.radius * U; + info.equidistant = true; + } + Vector3 diff = info.lineClosest - info.circleClosest; + info.sqrDistance = Dot(diff, diff); + candidates[numRoots++] = info; + } + + std::sort(candidates.begin(), candidates.begin() + numRoots); + + result.numClosestPairs = 1; + result.lineClosest[0] = candidates[0].lineClosest; + result.circleClosest[0] = candidates[0].circleClosest; + if (numRoots > 1 + && candidates[1].sqrDistance == candidates[0].sqrDistance) + { + result.numClosestPairs = 2; + result.lineClosest[1] = candidates[1].lineClosest; + result.circleClosest[1] = candidates[1].circleClosest; + } + } + else + { + // The line is parallel to the plane of the circle. + // The polynomial has the form + // H(t) = (t+v)^2*[(t+v)^2-(r^2-u^2)]. + Real u = Dot(NxM, D), v = Dot(line.direction, D); + Real discr = circle.radius * circle.radius - u * u; + if (discr > zero) + { + result.numClosestPairs = 2; + Real rootDiscr = std::sqrt(discr); + t = -v + rootDiscr; + GetPair(line, circle, D, t, result.lineClosest[0], + result.circleClosest[0]); + t = -v - rootDiscr; + GetPair(line, circle, D, t, result.lineClosest[1], + result.circleClosest[1]); + } + else + { + result.numClosestPairs = 1; + t = -v; + GetPair(line, circle, D, t, result.lineClosest[0], + result.circleClosest[0]); + } + } + } + else + { + // The line is C+t*M, where M is not parallel to N. The + // polynomial is + // H(t) = |Cross(N,M)|^2*t^2*(t^2 - r^2*|Cross(N,M)|^2) + // where root t = 0 does not correspond to the global + // minimum. The other roots produce the global minimum. + result.numClosestPairs = 2; + t = circle.radius * Length(NxM); + GetPair(line, circle, D, t, result.lineClosest[0], + result.circleClosest[0]); + t = -t; + GetPair(line, circle, D, t, result.lineClosest[1], + result.circleClosest[1]); + } + result.equidistant = false; + } + else + { + if (NxD != vzero) + { + // The line is A+t*N (perpendicular to plane) but with + // A != C. The polyhomial is + // H(t) = |Cross(N,D)|^2*(t + Dot(M,D))^2. + result.numClosestPairs = 1; + t = -Dot(line.direction, D); + GetPair(line, circle, D, t, result.lineClosest[0], + result.circleClosest[0]); + result.equidistant = false; + } + else + { + // The line is C+t*N, so C is the closest point for the + // line and all circle points are equidistant from it. + Vector3 U = GetOrthogonal(circle.normal, true); + result.numClosestPairs = 1; + result.lineClosest[0] = circle.center; + result.circleClosest[0] = circle.center + circle.radius * U; + result.equidistant = true; + } + } + + Vector3 diff = result.lineClosest[0] - result.circleClosest[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; + } + + // The nonpolynomial-based algorithm that uses bisection. Because the + // bisection is iterative, you should choose Real to be a + // floating-point type. However, the algorithm will still work for a + // rational type, but it is costly because of the increase in + // arbitrary-size integers used during the bisection. + Result Robust(Line3 const& line, Circle3 const& circle) + { + // The line is P(t) = B+t*M. The circle is |X-C| = r with + // Dot(N,X-C)=0. + Result result; + Vector3 vzero = Vector3::Zero(); + Real const zero = (Real)0; + + Vector3 D = line.origin - circle.center; + Vector3 MxN = Cross(line.direction, circle.normal); + Vector3 DxN = Cross(D, circle.normal); + + Real m0sqr = Dot(MxN, MxN); + if (m0sqr > zero) + { + // Compute the critical points s for F'(s) = 0. + Real s, t; + int numRoots = 0; + std::array roots; + + // The line direction M and the plane normal N are not + // parallel. Move the line origin B = (b0,b1,b2) to + // B' = B + lambda*line.direction = (0,b1',b2'). + Real m0 = std::sqrt(m0sqr); + Real rm0 = circle.radius * m0; + Real lambda = -Dot(MxN, DxN) / m0sqr; + Vector3 oldD = D; + D += lambda * line.direction; + DxN += lambda * MxN; + Real m2b2 = Dot(line.direction, D); + Real b1sqr = Dot(DxN, DxN); + if (b1sqr > zero) + { + // B' = (0,b1',b2') where b1' != 0. See Sections 1.1.2 + // and 1.2.2 of the PDF documentation. + Real b1 = std::sqrt(b1sqr); + Real rm0sqr = circle.radius * m0sqr; + if (rm0sqr > b1) + { + Real const twoThirds = (Real)2 / (Real)3; + Real sHat = std::sqrt(std::pow(rm0sqr * b1sqr, twoThirds) - b1sqr) / m0; + Real gHat = rm0sqr * sHat / std::sqrt(m0sqr * sHat * sHat + b1sqr); + Real cutoff = gHat - sHat; + if (m2b2 <= -cutoff) + { + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2, -m2b2 + rm0); + roots[numRoots++] = s; + if (m2b2 == -cutoff) + { + roots[numRoots++] = -sHat; + } + } + else if (m2b2 >= cutoff) + { + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2 - rm0, -m2b2); + roots[numRoots++] = s; + if (m2b2 == cutoff) + { + roots[numRoots++] = sHat; + } + } + else + { + if (m2b2 <= zero) + { + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2, -m2b2 + rm0); + roots[numRoots++] = s; + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2 - rm0, -sHat); + roots[numRoots++] = s; + } + else + { + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2 - rm0, -m2b2); + roots[numRoots++] = s; + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, sHat, -m2b2 + rm0); + roots[numRoots++] = s; + } + } + } + else + { + if (m2b2 < zero) + { + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2, -m2b2 + rm0); + } + else if (m2b2 > zero) + { + s = Bisect(m2b2, rm0sqr, m0sqr, b1sqr, -m2b2 - rm0, -m2b2); + } + else + { + s = zero; + } + roots[numRoots++] = s; + } + } + else + { + // The new line origin is B' = (0,0,b2'). + if (m2b2 < zero) + { + s = -m2b2 + rm0; + roots[numRoots++] = s; + } + else if (m2b2 > zero) + { + s = -m2b2 - rm0; + roots[numRoots++] = s; + } + else + { + s = -m2b2 + rm0; + roots[numRoots++] = s; + s = -m2b2 - rm0; + roots[numRoots++] = s; + } + } + + std::array candidates; + for (int i = 0; i < numRoots; ++i) + { + t = roots[i] + lambda; + ClosestInfo info; + Vector3 NxDelta = + Cross(circle.normal, oldD + t * line.direction); + if (NxDelta != vzero) + { + GetPair(line, circle, oldD, t, info.lineClosest, + info.circleClosest); + info.equidistant = false; + } + else + { + Vector3 U = GetOrthogonal(circle.normal, true); + info.lineClosest = circle.center; + info.circleClosest = circle.center + circle.radius * U; + info.equidistant = true; + } + Vector3 diff = info.lineClosest - info.circleClosest; + info.sqrDistance = Dot(diff, diff); + candidates[i] = info; + } + + std::sort(candidates.begin(), candidates.begin() + numRoots); + + result.numClosestPairs = 1; + result.lineClosest[0] = candidates[0].lineClosest; + result.circleClosest[0] = candidates[0].circleClosest; + if (numRoots > 1 + && candidates[1].sqrDistance == candidates[0].sqrDistance) + { + result.numClosestPairs = 2; + result.lineClosest[1] = candidates[1].lineClosest; + result.circleClosest[1] = candidates[1].circleClosest; + } + + result.equidistant = false; + } + else + { + // The line direction and the plane normal are parallel. + if (DxN != vzero) + { + // The line is A+t*N but with A != C. + result.numClosestPairs = 1; + GetPair(line, circle, D, -Dot(line.direction, D), + result.lineClosest[0], result.circleClosest[0]); + result.equidistant = false; + } + else + { + // The line is C+t*N, so C is the closest point for the + // line and all circle points are equidistant from it. + Vector3 U = GetOrthogonal(circle.normal, true); + result.numClosestPairs = 1; + result.lineClosest[0] = circle.center; + result.circleClosest[0] = circle.center + circle.radius * U; + result.equidistant = true; + } + } + + Vector3 diff = result.lineClosest[0] - result.circleClosest[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; + } + + private: + // Support for operator(...). + struct ClosestInfo + { + Real sqrDistance; + Vector3 lineClosest, circleClosest; + bool equidistant; + + bool operator< (ClosestInfo const& info) const + { + return sqrDistance < info.sqrDistance; + } + }; + + void GetPair(Line3 const& line, Circle3 const& circle, + Vector3 const& D, Real t, Vector3& lineClosest, + Vector3& circleClosest) + { + Vector3 delta = D + t * line.direction; + lineClosest = circle.center + delta; + delta -= Dot(circle.normal, delta) * circle.normal; + Normalize(delta); + circleClosest = circle.center + circle.radius * delta; + } + + // Support for Robust(...). Bisect the function + // F(s) = s + m2b2 - r*m0sqr*s/sqrt(m0sqr*s*s + b1sqr) + // on the specified interval [smin,smax]. + Real Bisect(Real m2b2, Real rm0sqr, Real m0sqr, Real b1sqr, Real smin, Real smax) + { + std::function G = [&, m2b2, rm0sqr, m0sqr, b1sqr](Real s) + { + return s + m2b2 - rm0sqr * s / std::sqrt(m0sqr * s * s + b1sqr); + }; + + // The function is known to be increasing, so we can specify -1 and +1 + // as the function values at the bounding interval endpoints. The use + // of 'double' is intentional in case Real is a BSNumber or BSRational + // type. We want the bisections to terminate in a reasonable amount of + // time. + unsigned int const maxIterations = GTE_C_MAX_BISECTIONS_GENERIC; + Real root; + RootsBisection::Find(G, smin, smax, (Real)-1, (Real)+1, maxIterations, root); + return root; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLine3OrientedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLine3OrientedBox3.h new file mode 100644 index 0000000..7b4959a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLine3OrientedBox3.h @@ -0,0 +1,57 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, OrientedBox3> + : + public DCPQuery, AlignedBox3> + { + public: + struct Result + : + public DCPQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Line3 const& line, OrientedBox3 const& box) + { + // Transform the line to the coordinate system of the oriented + // box. In this system, the box is axis-aligned with center at + // the origin. + Vector3 diff = line.origin - box.center; + Vector3 point, direction; + for (int i = 0; i < 3; ++i) + { + point[i] = Dot(diff, box.axis[i]); + direction[i] = Dot(line.direction, box.axis[i]); + } + + Result result; + this->DoQuery(point, direction, box.extent, result); + + // Compute the closest point on the line. + result.closestPoint[0] = line.origin + result.lineParameter * line.direction; + + // Compute the closest point on the box. + result.closestPoint[1] = box.center; + for (int i = 0; i < 3; ++i) + { + result.closestPoint[1] += point[i] * box.axis[i]; + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLine3Rectangle3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLine3Rectangle3.h new file mode 100644 index 0000000..24b6753 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLine3Rectangle3.h @@ -0,0 +1,127 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, Rectangle3> + { + public: + struct Result + { + Real distance, sqrDistance; + Real lineParameter, rectangleParameter[2]; + Vector3 closestPoint[2]; + }; + + Result operator()(Line3 const& line, Rectangle3 const& rectangle) + { + Result result; + + // Test if line intersects rectangle. If so, the squared distance + // is zero. + Vector3 N = Cross(rectangle.axis[0], rectangle.axis[1]); + Real NdD = Dot(N, line.direction); + if (std::fabs(NdD) > (Real)0) + { + // The line and rectangle are not parallel, so the line + // intersects the plane of the rectangle. + Vector3 diff = line.origin - rectangle.center; + Vector3 basis[3]; // {D, U, V} + basis[0] = line.direction; + ComputeOrthogonalComplement(1, basis); + Real UdD0 = Dot(basis[1], rectangle.axis[0]); + Real UdD1 = Dot(basis[1], rectangle.axis[1]); + Real UdPmC = Dot(basis[1], diff); + Real VdD0 = Dot(basis[2], rectangle.axis[0]); + Real VdD1 = Dot(basis[2], rectangle.axis[1]); + Real VdPmC = Dot(basis[2], diff); + Real invDet = ((Real)1) / (UdD0 * VdD1 - UdD1 * VdD0); + + // Rectangle coordinates for the point of intersection. + Real s0 = (VdD1 * UdPmC - UdD1 * VdPmC) * invDet; + Real s1 = (UdD0 * VdPmC - VdD0 * UdPmC) * invDet; + + if (std::fabs(s0) <= rectangle.extent[0] && std::fabs(s1) <= rectangle.extent[1]) + { + // Line parameter for the point of intersection. + Real DdD0 = Dot(line.direction, rectangle.axis[0]); + Real DdD1 = Dot(line.direction, rectangle.axis[1]); + Real DdDiff = Dot(line.direction, diff); + result.lineParameter = s0 * DdD0 + s1 * DdD1 - DdDiff; + + // Rectangle coordinates for the point of intersection. + result.rectangleParameter[0] = s0; + result.rectangleParameter[1] = s1; + + // The intersection point is inside or on the rectangle. + result.closestPoint[0] = + line.origin + result.lineParameter * line.direction; + result.closestPoint[1] = + rectangle.center + s0 * rectangle.axis[0] + s1 * rectangle.axis[1]; + + result.distance = (Real)0; + result.sqrDistance = (Real)0; + return result; + } + } + + // Either (1) the line is not parallel to the rectangle and the + // point of intersection of the line and the plane of the + // rectangle is outside the rectangle or (2) the line and + // rectangle are parallel. Regardless, the closest point on + // the rectangle is on an edge of the rectangle. Compare the + // line to all four edges of the rectangle. + result.distance = std::numeric_limits::max(); + result.sqrDistance = std::numeric_limits::max(); + Vector3 scaledDir[2] = + { + rectangle.extent[0] * rectangle.axis[0], + rectangle.extent[1] * rectangle.axis[1] + }; + for (int i1 = 0, omi1 = 1; i1 <= 1; ++i1, --omi1) + { + for (int i0 = -1; i0 <= 1; i0 += 2) + { + Vector3 segCenter = rectangle.center + scaledDir[i1] * (Real)i0; + Vector3 segDirection = rectangle.axis[omi1]; + Real segExtent = rectangle.extent[omi1]; + Segment3 segment(segCenter, segDirection, segExtent); + + DCPQuery, Segment3> query; + auto lsResult = query(line, segment); + if (lsResult.sqrDistance < result.sqrDistance) + { + result.sqrDistance = lsResult.sqrDistance; + result.distance = lsResult.distance; + result.lineParameter = lsResult.parameter[0]; + // ratio is in [-1,1] + Real ratio = lsResult.parameter[1] / segExtent; + result.rectangleParameter[0] = + rectangle.extent[0] * (omi1 * i0 + i1 * ratio); + result.rectangleParameter[1] = + rectangle.extent[1] * (i1 * i0 + omi1 * ratio); + result.closestPoint[0] = lsResult.closestPoint[0]; + result.closestPoint[1] = lsResult.closestPoint[1]; + } + } + } + + return result; + } + }; + + // Template alias for convenience. + template + using DCPLine3Rectangle3 = DCPQuery, Rectangle3>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLine3Triangle3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLine3Triangle3.h new file mode 100644 index 0000000..2ea8a90 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLine3Triangle3.h @@ -0,0 +1,116 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, Triangle3> + { + public: + struct Result + { + Real distance, sqrDistance; + Real lineParameter, triangleParameter[3]; + Vector3 closestPoint[2]; + }; + + Result operator()(Line3 const& line, Triangle3 const& triangle) + { + Result result; + + // Test if line intersects triangle. If so, the squared distance + // is zero. + Vector3 edge0 = triangle.v[1] - triangle.v[0]; + Vector3 edge1 = triangle.v[2] - triangle.v[0]; + Vector3 normal = UnitCross(edge0, edge1); + Real NdD = Dot(normal, line.direction); + if (std::fabs(NdD) > (Real)0) + { + // The line and triangle are not parallel, so the line + // intersects/ the plane of the triangle. + Vector3 diff = line.origin - triangle.v[0]; + Vector3 basis[3]; // {D, U, V} + basis[0] = line.direction; + ComputeOrthogonalComplement(1, basis); + Real UdE0 = Dot(basis[1], edge0); + Real UdE1 = Dot(basis[1], edge1); + Real UdDiff = Dot(basis[1], diff); + Real VdE0 = Dot(basis[2], edge0); + Real VdE1 = Dot(basis[2], edge1); + Real VdDiff = Dot(basis[2], diff); + Real invDet = ((Real)1) / (UdE0 * VdE1 - UdE1 * VdE0); + + // Barycentric coordinates for the point of intersection. + Real b1 = (VdE1 * UdDiff - UdE1 * VdDiff) * invDet; + Real b2 = (UdE0 * VdDiff - VdE0 * UdDiff) * invDet; + Real b0 = (Real)1 - b1 - b2; + + if (b0 >= (Real)0 && b1 >= (Real)0 && b2 >= (Real)0) + { + // Line parameter for the point of intersection. + Real DdE0 = Dot(line.direction, edge0); + Real DdE1 = Dot(line.direction, edge1); + Real DdDiff = Dot(line.direction, diff); + result.lineParameter = b1 * DdE0 + b2 * DdE1 - DdDiff; + + // Barycentric coordinates for the point of intersection. + result.triangleParameter[0] = b0; + result.triangleParameter[1] = b1; + result.triangleParameter[2] = b2; + + // The intersection point is inside or on the triangle. + result.closestPoint[0] = line.origin + result.lineParameter * line.direction; + result.closestPoint[1] = triangle.v[0] + b1 * edge0 + b2 * edge1; + + result.distance = (Real)0; + result.sqrDistance = (Real)0; + return result; + } + } + + // Either (1) the line is not parallel to the triangle and the + // point of intersection of the line and the plane of the triangle + // is outside the triangle or (2) the line and triangle are + // parallel. Regardless, the closest point on the triangle is on + // an edge of the triangle. Compare the line to all three edges + // of the triangle. + result.distance = std::numeric_limits::max(); + result.sqrDistance = std::numeric_limits::max(); + for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + Vector3 segCenter = (Real)0.5 * (triangle.v[i0] + triangle.v[i1]); + Vector3 segDirection = triangle.v[i1] - triangle.v[i0]; + Real segExtent = (Real)0.5 * Normalize(segDirection); + Segment3 segment(segCenter, segDirection, segExtent); + + DCPQuery, Segment3> query; + auto lsResult = query(line, segment); + if (lsResult.sqrDistance < result.sqrDistance) + { + result.sqrDistance = lsResult.sqrDistance; + result.distance = lsResult.distance; + result.lineParameter = lsResult.parameter[0]; + result.triangleParameter[i0] = (Real)0.5 * ((Real)1 - + lsResult.parameter[0] / segExtent); + result.triangleParameter[i1] = (Real)1 - + result.triangleParameter[i0]; + result.triangleParameter[3 - i0 - i1] = (Real)0; + result.closestPoint[0] = lsResult.closestPoint[0]; + result.closestPoint[1] = lsResult.closestPoint[1]; + } + } + + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLineLine.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLineLine.h new file mode 100644 index 0000000..5b60510 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLineLine.h @@ -0,0 +1,70 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, Line> + { + public: + struct Result + { + Real distance, sqrDistance; + Real parameter[2]; + Vector closestPoint[2]; + }; + + Result operator()(Line const& line0, Line const& line1) + { + Result result; + + Vector diff = line0.origin - line1.origin; + Real a01 = -Dot(line0.direction, line1.direction); + Real b0 = Dot(diff, line0.direction); + Real s0, s1; + + if (std::fabs(a01) < (Real)1) + { + // Lines are not parallel. + Real det = (Real)1 - a01 * a01; + Real b1 = -Dot(diff, line1.direction); + s0 = (a01 * b1 - b0) / det; + s1 = (a01 * b0 - b1) / det; + } + else + { + // Lines are parallel, select any pair of closest points. + s0 = -b0; + s1 = (Real)0; + } + + result.parameter[0] = s0; + result.parameter[1] = s1; + result.closestPoint[0] = line0.origin + s0 * line0.direction; + result.closestPoint[1] = line1.origin + s1 * line1.direction; + diff = result.closestPoint[0] - result.closestPoint[1]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; + } + }; + + // Template aliases for convenience. + template + using DCPLineLine = DCPQuery, Line>; + + template + using DCPLine2Line2 = DCPLineLine<2, Real>; + + template + using DCPLine3Line3 = DCPLineLine<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLineRay.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLineRay.h new file mode 100644 index 0000000..a4f5f9f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLineRay.h @@ -0,0 +1,84 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, Ray> + { + public: + struct Result + { + Real distance, sqrDistance; + Real parameter[2]; + Vector closestPoint[2]; + }; + + Result operator()(Line const& line, Ray const& ray) + { + Result result; + + Vector diff = line.origin - ray.origin; + Real a01 = -Dot(line.direction, ray.direction); + Real b0 = Dot(diff, line.direction); + Real s0, s1; + + if (std::fabs(a01) < (Real)1) + { + Real b1 = -Dot(diff, ray.direction); + s1 = a01 * b0 - b1; + + if (s1 >= (Real)0) + { + // Two interior points are closest, one on line and one + // on ray. + Real det = (Real)1 - a01 * a01; + s0 = (a01 * b1 - b0) / det; + s1 /= det; + } + else + { + // Origin of ray and interior point of line are closest. + s0 = -b0; + s1 = (Real)0; + } + } + else + { + // Lines are parallel, closest pair with one point at ray + // origin. + s0 = -b0; + s1 = (Real)0; + } + + result.parameter[0] = s0; + result.parameter[1] = s1; + result.closestPoint[0] = line.origin + s0 * line.direction; + result.closestPoint[1] = ray.origin + s1 * ray.direction; + diff = result.closestPoint[0] - result.closestPoint[1]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; + } + }; + + // Template aliases for convenience. + template + using DCPLineRay = DCPQuery, Ray>; + + template + using DCPLine2Ray2 = DCPLineRay<2, Real>; + + template + using DCPLine3Ray3 = DCPLineRay<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLineSegment.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLineSegment.h new file mode 100644 index 0000000..ac78f04 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistLineSegment.h @@ -0,0 +1,103 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, Segment> + { + public: + struct Result + { + Real distance, sqrDistance; + Real parameter[2]; + Vector closestPoint[2]; + }; + + // The centered form of the 'segment' is used. Thus, parameter[1] of + // the result is in [-e,e], where e = |segment.p[1] - segment.p[0]|/2. + Result operator()(Line const& line, Segment const& segment) + { + Result result; + + Vector segCenter, segDirection; + Real segExtent; + segment.GetCenteredForm(segCenter, segDirection, segExtent); + + Vector diff = line.origin - segCenter; + Real a01 = -Dot(line.direction, segDirection); + Real b0 = Dot(diff, line.direction); + Real s0, s1; + + if (std::fabs(a01) < (Real)1) + { + // The line and segment are not parallel. + Real det = (Real)1 - a01 * a01; + Real extDet = segExtent * det; + Real b1 = -Dot(diff, segDirection); + s1 = a01 * b0 - b1; + + if (s1 >= -extDet) + { + if (s1 <= extDet) + { + // Two interior points are closest, one on the line + // and one on the segment. + s0 = (a01 * b1 - b0) / det; + s1 /= det; + } + else + { + // The endpoint e1 of the segment and an interior + // point of the line are closest. + s1 = segExtent; + s0 = -(a01 * s1 + b0); + } + } + else + { + // The endpoint e0 of the segment and an interior point + // of the line are closest. + s1 = -segExtent; + s0 = -(a01 * s1 + b0); + } + } + else + { + // The line and segment are parallel. Choose the closest pair + // so that one point is at segment origin. + s1 = (Real)0; + s0 = -b0; + } + + result.parameter[0] = s0; + result.parameter[1] = s1; + result.closestPoint[0] = line.origin + s0 * line.direction; + result.closestPoint[1] = segCenter + s1 * segDirection; + diff = result.closestPoint[0] - result.closestPoint[1]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; + } + }; + + // Template aliases for convenience. + template + using DCPLineSegment = DCPQuery, Segment>; + + template + using DCPLine2Segment2 = DCPLineSegment<2, Real>; + + template + using DCPLine3Segment3 = DCPLineSegment<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistOrientedBox3OrientedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistOrientedBox3OrientedBox3.h new file mode 100644 index 0000000..d774c6d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistOrientedBox3OrientedBox3.h @@ -0,0 +1,157 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +// Compute the distance between oriented boxes in 3D. The algorithm is based +// on using an LCP solver for the convex quadratic programming problem. For +// details, see +// https://www.geometrictools.com/Documentation/ConvexQuadraticProgramming.pdf + +namespace WwiseGTE +{ + template + class DCPQuery, OrientedBox3> + { + public: + struct Result + { + bool queryIsSuccessful; + + // These members are valid only when queryIsSuccessful is true; + // otherwise, they are all set to zero. + Real distance, sqrDistance; + std::array box0Parameter, box1Parameter; + Vector3 closestPoint[2]; + + // The number of iterations used by LCPSolver regardless of + // whether the query is successful. + int numLCPIterations; + }; + + // Default maximum iterations is 144 (n = 12, maxIterations = n*n). + // If the solver fails to converge, try increasing the maximum number + // of iterations. + void SetMaxLCPIterations(int maxLCPIterations) + { + mLCP.SetMaxIterations(maxLCPIterations); + } + + Result operator()(OrientedBox3 const& box0, OrientedBox3 const& box1) + { + Result result; + + // Translate the center of box0 to the origin. Modify the + // oriented box coefficients to be nonnegative. + Vector3 delta = box1.center - box0.center; + for (int i = 0; i < 3; ++i) + { + delta += box0.extent[i] * box0.axis[i]; + delta -= box1.extent[i] * box1.axis[i]; + } + + Vector3 R0Delta, R1Delta; + for (int i = 0; i < 3; ++i) + { + R0Delta[i] = Dot(box0.axis[i], delta); + R1Delta[i] = Dot(box1.axis[i], delta); + } + + std::array, 3> R0TR1; + for (int r = 0; r < 3; ++r) + { + for (int c = 0; c < 3; ++c) + { + R0TR1[r][c] = Dot(box0.axis[r], box1.axis[c]); + } + } + + Vector3 twoExtent0 = box0.extent * (Real)2; + Vector3 twoExtent1 = box1.extent * (Real)2; + + // The LCP has 6 variables and 6 (nontrivial) inequality + // constraints. + std::array q = + { + -R0Delta[0], -R0Delta[1], -R0Delta[2], R1Delta[0], R1Delta[1], R1Delta[2], + twoExtent0[0], twoExtent0[1], twoExtent0[2], twoExtent1[0], twoExtent1[1], twoExtent1[2] + }; + + std::array, 12> M; + { + Real const z = (Real)0; + Real const p = (Real)1; + Real const m = (Real)-1; + M[0] = { p, z, z, -R0TR1[0][0], -R0TR1[0][1], -R0TR1[0][2], p, z, z, z, z, z }; + M[1] = { z, p, z, -R0TR1[1][0], -R0TR1[1][1], -R0TR1[1][2], z, p, z, z, z, z }; + M[2] = { z, z, p, -R0TR1[2][0], -R0TR1[2][1], -R0TR1[2][2], z, z, p, z, z, z }; + M[3] = { -R0TR1[0][0], -R0TR1[1][0], -R0TR1[2][0], p, z, z, z, z, z, p, z, z }; + M[4] = { -R0TR1[0][1], -R0TR1[1][1], -R0TR1[2][1], z, p, z, z, z, z, z, p, z }; + M[5] = { -R0TR1[0][2], -R0TR1[1][2], -R0TR1[2][2], z, z, p, z, z, z, z, z, p }; + M[6] = { m, z, z, z, z, z, z, z, z, z, z, z }; + M[7] = { z, m, z, z, z, z, z, z, z, z, z, z }; + M[8] = { z, z, m, z, z, z, z, z, z, z, z, z }; + M[9] = { z, z, z, m, z, z, z, z, z, z, z, z }; + M[10] = { z, z, z, z, m, z, z, z, z, z, z, z }; + M[11] = { z, z, z, z, z, m, z, z, z, z, z, z }; + } + + std::array w, z; + if (mLCP.Solve(q, M, w, z)) + { + result.queryIsSuccessful = true; + + result.closestPoint[0] = box0.center; + for (int i = 0; i < 3; ++i) + { + result.box0Parameter[i] = z[i] - box0.extent[i]; + result.closestPoint[0] += result.box0Parameter[i] * box0.axis[i]; + } + + result.closestPoint[1] = box1.center; + for (int i = 0, j = 3; i < 3; ++i, ++j) + { + result.box1Parameter[i] = z[j] - box1.extent[i]; + result.closestPoint[1] += result.box1Parameter[i] * box1.axis[i]; + } + + Vector3 diff = result.closestPoint[1] - result.closestPoint[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + } + else + { + // If you reach this case, the maximum number of iterations + // was not specified to be large enough or there is a problem + // due to floating-point rounding errors. If you believe the + // latter is true, file a bug report. + result.queryIsSuccessful = false; + + for (int i = 0; i < 3; ++i) + { + result.box0Parameter[i] = (Real)0; + result.box1Parameter[i] = (Real)0; + result.closestPoint[0][i] = (Real)0; + result.closestPoint[1][i] = (Real)0; + } + result.distance = (Real)0; + result.sqrDistance = (Real)0; + } + + result.numLCPIterations = mLCP.GetNumIterations(); + return result; + } + + private: + LCPSolver mLCP; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPoint3Circle3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPoint3Circle3.h new file mode 100644 index 0000000..54c286d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPoint3Circle3.h @@ -0,0 +1,66 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The 3D point-circle distance algorithm is described in +// https://www.geometrictools.com/Documentation/DistanceToCircle3.pdf +// The notation used in the code matches that of the document. + +namespace WwiseGTE +{ + template + class DCPQuery, Circle3> + { + public: + // Either a single point on the circle is closest to 'point', in + // which case 'equidistant' is false, or the entire circle is + // closest to 'point', in which case 'equidistant' is true. In the + // latter case, the query returns the circle point C+r*U, where C is + // the circle center, r is the circle radius, and U is a vector + // perpendicular to the normal N for the plane of the circle. + struct Result + { + Real distance, sqrDistance; + Vector3 circleClosest; + bool equidistant; + }; + + Result operator()(Vector3 const& point, Circle3 const& circle) + { + Result result; + + // Projection of P-C onto plane is Q-C = P-C - Dot(N,P-C)*N. + Vector3 PmC = point - circle.center; + Vector3 QmC = PmC - Dot(circle.normal, PmC) * circle.normal; + Real lengthQmC = Length(QmC); + if (lengthQmC > (Real)0) + { + result.circleClosest = circle.center + (circle.radius / lengthQmC) * QmC; + result.equidistant = false; + } + else + { + // All circle points are equidistant from P. Return one of + // them. + Vector3 basis[3]; + basis[0] = circle.normal; + ComputeOrthogonalComplement(1, basis); + result.circleClosest = circle.center + circle.radius * basis[1]; + result.equidistant = true; + } + + Vector3 diff = point - result.circleClosest; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPoint3ConvexPolyhedron3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPoint3ConvexPolyhedron3.h new file mode 100644 index 0000000..242055a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPoint3ConvexPolyhedron3.h @@ -0,0 +1,182 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +// Compute the distance between a point and a convex polyhedron in 3D. The +// algorithm is based on using an LCP solver for the convex quadratic +// programming problem. For details, see +// https://www.geometrictools.com/Documentation/ConvexQuadraticProgramming.pdf + + +namespace WwiseGTE +{ + template + class DCPQuery, ConvexPolyhedron3> + { + public: + // Construction. If you have no knowledge of the number of faces + // for the convex polyhedra you plan on applying the query to, pass + // 'numTriangles' of zero. This is a request to the operator() + // function to create the LCP solver for each query, and this + // requires memory allocation and deallocation per query. If you + // plan on applying the query multiple/ times to a single polyhedron, + // even if the vertices of the polyhedron are modified for each query, + // then pass 'numTriangles' to be the number of triangle faces for + // that polyhedron. This lets the operator() function know to create + // the LCP solver once at construction time, thus avoiding the memory + // management costs during the query. + DCPQuery(int numTriangles = 0) + { + if (numTriangles > 0) + { + int const n = numTriangles + 3; + mLCP = std::make_unique>(n); + mMaxLCPIterations = mLCP->GetMaxIterations(); + } + else + { + mMaxLCPIterations = 0; + } + } + + struct Result + { + bool queryIsSuccessful; + + // These members are valid only when queryIsSuccessful is true; + // otherwise, they are all set to zero. + Real distance, sqrDistance; + Vector3 closestPoint[2]; + + // The number of iterations used by LCPSolver regardless of + // whether the query is successful. + int numLCPIterations; + }; + + // Default maximum iterations is 144 (n = 12, maxIterations = n*n). + // If the solver fails to converge, try increasing the maximum number + // of iterations. + void SetMaxLCPIterations(int maxLCPIterations) + { + mMaxLCPIterations = maxLCPIterations; + if (mLCP) + { + mLCP->SetMaxIterations(mMaxLCPIterations); + } + } + + Result operator()(Vector3 const& point, ConvexPolyhedron3 const& polyhedron) + { + Result result; + + int const numTriangles = static_cast(polyhedron.planes.size()); + if (numTriangles == 0) + { + // The polyhedron planes and aligned box need to be created. + result.queryIsSuccessful = false; + for (int i = 0; i < 3; ++i) + { + result.closestPoint[0][i] = (Real)0; + result.closestPoint[1][i] = (Real)0; + } + result.distance = (Real)0; + result.sqrDistance = (Real)0; + result.numLCPIterations = 0; + return result; + } + + int const n = numTriangles + 3; + + // Translate the point and convex polyhedron so that the + // polyhedron is in the first octant. The translation is not + // explicit; rather, the q and M for the LCP are initialized using + // the translation information. + Vector4 hmin = HLift(polyhedron.alignedBox.min, (Real)1); + + std::vector q(n); + for (int r = 0; r < 3; ++r) + { + q[r] = polyhedron.alignedBox.min[r] - point[r]; + } + for (int r = 3, t = 0; r < n; ++r, ++t) + { + q[r] = -Dot(polyhedron.planes[t], hmin); + } + + std::vector M(n * n); + M[0] = (Real)1; M[1] = (Real)0; M[2] = (Real)0; + M[n] = (Real)0; M[n + 1] = (Real)1; M[n + 2] = (Real)0; + M[2 * n] = (Real)0; M[2 * n + 1] = (Real)0; M[2 * n + 2] = (Real)1; + for (int t = 0, c = 3; t < numTriangles; ++t, ++c) + { + Vector3 normal = HProject(polyhedron.planes[t]); + for (int r = 0; r < 3; ++r) + { + M[c + n * r] = normal[r]; + M[r + n * c] = -normal[r]; + } + } + for (int r = 3; r < n; ++r) + { + for (int c = 3; c < n; ++c) + { + M[c + n * r] = (Real)0; + } + } + + bool needsLCP = (mLCP == nullptr); + if (needsLCP) + { + mLCP = std::make_unique>(n); + if (mMaxLCPIterations > 0) + { + mLCP->SetMaxIterations(mMaxLCPIterations); + } + } + + std::vector w(n), z(n); + if (mLCP->Solve(q, M, w, z)) + { + result.queryIsSuccessful = true; + result.closestPoint[0] = point; + for (int i = 0; i < 3; ++i) + { + result.closestPoint[1][i] = z[i] + polyhedron.alignedBox.min[i]; + } + + Vector3 diff = result.closestPoint[1] - result.closestPoint[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + } + else + { + // If you reach this case, the maximum number of iterations + // was not specified to be large enough or there is a problem + // due to floating-point rounding errors. If you believe the + // latter is true, file a bug report. + result.queryIsSuccessful = false; + } + + result.numLCPIterations = mLCP->GetNumIterations(); + if (needsLCP) + { + mLCP = nullptr; + } + return result; + } + + private: + int mMaxLCPIterations; + std::unique_ptr> mLCP; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPoint3Cylinder3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPoint3Cylinder3.h new file mode 100644 index 0000000..623007d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPoint3Cylinder3.h @@ -0,0 +1,109 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The queries consider the cylinder to be a solid. + +namespace WwiseGTE +{ + template + class DCPQuery, Cylinder3> + { + public: + struct Result + { + Real distance; + Vector3 cylinderClosest; + }; + + Result operator()(Vector3 const& point, Cylinder3 const& cylinder) + { + Result result; + + // Convert the point to the cylinder coordinate system. In this + // system, the point believes (0,0,0) is the cylinder axis origin + // and (0,0,1) is the cylinder axis direction. + Vector3 basis[3]; + basis[0] = cylinder.axis.direction; + ComputeOrthogonalComplement(1, basis); + + Vector3 delta = point - cylinder.axis.origin; + Vector3 P + { + Dot(basis[1], delta), + Dot(basis[2], delta), + Dot(basis[0], delta) + }; + + if (cylinder.height == std::numeric_limits::max()) + { + DoQueryInfiniteCylinder(P, cylinder.radius, result); + } + else + { + DoQueryFiniteCylinder(P, cylinder.radius, cylinder.height, result); + } + + // Convert the closest point from the cylinder coordinate system + // to the original coordinate system. + result.cylinderClosest = cylinder.axis.origin + + result.cylinderClosest[0] * basis[1] + + result.cylinderClosest[1] * basis[2] + + result.cylinderClosest[2] * basis[0]; + + return result; + } + + private: + void DoQueryInfiniteCylinder(Vector3 const& P, Real radius, + Result& result) + { + Real sqrRadius = radius * radius; + Real sqrDistance = P[0] * P[0] + P[1] * P[1]; + if (sqrDistance >= sqrRadius) + { + // The point is outside the cylinder or on the cylinder wall. + Real distance = std::sqrt(sqrDistance); + result.distance = distance - radius; + Real temp = radius / distance; + result.cylinderClosest[0] = P[0] * temp; + result.cylinderClosest[1] = P[1] * temp; + result.cylinderClosest[2] = P[2]; + } + else + { + // The point is inside the cylinder. + result.distance = (Real)0; + result.cylinderClosest = P; + } + } + + void DoQueryFiniteCylinder(Vector3 const& P, Real radius, + Real height, Result& result) + { + DoQueryInfiniteCylinder(P, radius, result); + + // Clamp the infinite cylinder's closest point to the finite + // cylinder. + if (result.cylinderClosest[2] > height) + { + result.cylinderClosest[2] = height; + result.distance = Length(result.cylinderClosest - P); + } + else if (result.cylinderClosest[2] < -height) + { + result.cylinderClosest[2] = -height; + result.distance = Length(result.cylinderClosest - P); + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPoint3Frustum3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPoint3Frustum3.h new file mode 100644 index 0000000..45e9d8b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPoint3Frustum3.h @@ -0,0 +1,422 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, Frustum3> + { + public: + struct Result + { + Real distance, sqrDistance; + Vector3 frustumClosestPoint; + }; + + Result operator()(Vector3 const& point, Frustum3 const& frustum) + { + Result result; + + // Compute coordinates of point with respect to frustum coordinate + // system. + Vector3 diff = point - frustum.origin; + Vector3 test = { + Dot(diff, frustum.rVector), + Dot(diff, frustum.uVector), + Dot(diff, frustum.dVector) }; + + // Perform calculations in octant with nonnegative R and U + // coordinates. + bool rSignChange; + if (test[0] < (Real)0) + { + rSignChange = true; + test[0] = -test[0]; + } + else + { + rSignChange = false; + } + + bool uSignChange; + if (test[1] < (Real)0) + { + uSignChange = true; + test[1] = -test[1]; + } + else + { + uSignChange = false; + } + + // Frustum derived parameters. + Real rmin = frustum.rBound; + Real rmax = frustum.GetDRatio() * rmin; + Real umin = frustum.uBound; + Real umax = frustum.GetDRatio() * umin; + Real dmin = frustum.dMin; + Real dmax = frustum.dMax; + Real rminSqr = rmin * rmin; + Real uminSqr = umin * umin; + Real dminSqr = dmin * dmin; + Real minRDDot = rminSqr + dminSqr; + Real minUDDot = uminSqr + dminSqr; + Real minRUDDot = rminSqr + minUDDot; + Real maxRDDot = frustum.GetDRatio() * minRDDot; + Real maxUDDot = frustum.GetDRatio() * minUDDot; + Real maxRUDDot = frustum.GetDRatio() * minRUDDot; + + // Algorithm computes closest point in all cases by determining + // in which Voronoi region of the vertices, edges, and faces of + // the frustum that the test point lives. + Vector3 closest; + Real rDot, uDot, rdDot, udDot, rudDot, rEdgeDot, uEdgeDot, t; + if (test[2] >= dmax) + { + if (test[0] <= rmax) + { + if (test[1] <= umax) + { + // F-face + closest[0] = test[0]; + closest[1] = test[1]; + closest[2] = dmax; + } + else + { + // UF-edge + closest[0] = test[0]; + closest[1] = umax; + closest[2] = dmax; + } + } + else + { + if (test[1] <= umax) + { + // LF-edge + closest[0] = rmax; + closest[1] = test[1]; + closest[2] = dmax; + } + else + { + // LUF-vertex + closest[0] = rmax; + closest[1] = umax; + closest[2] = dmax; + } + } + } + else if (test[2] <= dmin) + { + if (test[0] <= rmin) + { + if (test[1] <= umin) + { + // N-face + closest[0] = test[0]; + closest[1] = test[1]; + closest[2] = dmin; + } + else + { + udDot = umin * test[1] + dmin * test[2]; + if (udDot >= maxUDDot) + { + // UF-edge + closest[0] = test[0]; + closest[1] = umax; + closest[2] = dmax; + } + else if (udDot >= minUDDot) + { + // U-face + uDot = dmin * test[1] - umin * test[2]; + t = uDot / minUDDot; + closest[0] = test[0]; + closest[1] = test[1] - t * dmin; + closest[2] = test[2] + t * umin; + } + else + { + // UN-edge + closest[0] = test[0]; + closest[1] = umin; + closest[2] = dmin; + } + } + } + else + { + if (test[1] <= umin) + { + rdDot = rmin * test[0] + dmin * test[2]; + if (rdDot >= maxRDDot) + { + // LF-edge + closest[0] = rmax; + closest[1] = test[1]; + closest[2] = dmax; + } + else if (rdDot >= minRDDot) + { + // L-face + rDot = dmin * test[0] - rmin * test[2]; + t = rDot / minRDDot; + closest[0] = test[0] - t * dmin; + closest[1] = test[1]; + closest[2] = test[2] + t * rmin; + } + else + { + // LN-edge + closest[0] = rmin; + closest[1] = test[1]; + closest[2] = dmin; + } + } + else + { + rudDot = rmin * test[0] + umin * test[1] + dmin * test[2]; + rEdgeDot = umin * rudDot - minRUDDot * test[1]; + if (rEdgeDot >= (Real)0) + { + rdDot = rmin * test[0] + dmin * test[2]; + if (rdDot >= maxRDDot) + { + // LF-edge + closest[0] = rmax; + closest[1] = test[1]; + closest[2] = dmax; + } + else if (rdDot >= minRDDot) + { + // L-face + rDot = dmin * test[0] - rmin * test[2]; + t = rDot / minRDDot; + closest[0] = test[0] - t * dmin; + closest[1] = test[1]; + closest[2] = test[2] + t * rmin; + } + else + { + // LN-edge + closest[0] = rmin; + closest[1] = test[1]; + closest[2] = dmin; + } + } + else + { + uEdgeDot = rmin * rudDot - minRUDDot * test[0]; + if (uEdgeDot >= (Real)0) + { + udDot = umin * test[1] + dmin * test[2]; + if (udDot >= maxUDDot) + { + // UF-edge + closest[0] = test[0]; + closest[1] = umax; + closest[2] = dmax; + } + else if (udDot >= minUDDot) + { + // U-face + uDot = dmin * test[1] - umin * test[2]; + t = uDot / minUDDot; + closest[0] = test[0]; + closest[1] = test[1] - t * dmin; + closest[2] = test[2] + t * umin; + } + else + { + // UN-edge + closest[0] = test[0]; + closest[1] = umin; + closest[2] = dmin; + } + } + else + { + if (rudDot >= maxRUDDot) + { + // LUF-vertex + closest[0] = rmax; + closest[1] = umax; + closest[2] = dmax; + } + else if (rudDot >= minRUDDot) + { + // LU-edge + t = rudDot / minRUDDot; + closest[0] = t * rmin; + closest[1] = t * umin; + closest[2] = t * dmin; + } + else + { + // LUN-vertex + closest[0] = rmin; + closest[1] = umin; + closest[2] = dmin; + } + } + } + } + } + } + else + { + rDot = dmin * test[0] - rmin * test[2]; + uDot = dmin * test[1] - umin * test[2]; + if (rDot <= (Real)0) + { + if (uDot <= (Real)0) + { + // point inside frustum + closest = test; + } + else + { + udDot = umin * test[1] + dmin * test[2]; + if (udDot >= maxUDDot) + { + // UF-edge + closest[0] = test[0]; + closest[1] = umax; + closest[2] = dmax; + } + else + { + // U-face + t = uDot / minUDDot; + closest[0] = test[0]; + closest[1] = test[1] - t * dmin; + closest[2] = test[2] + t * umin; + } + } + } + else + { + if (uDot <= (Real)0) + { + rdDot = rmin * test[0] + dmin * test[2]; + if (rdDot >= maxRDDot) + { + // LF-edge + closest[0] = rmax; + closest[1] = test[1]; + closest[2] = dmax; + } + else + { + // L-face + t = rDot / minRDDot; + closest[0] = test[0] - t * dmin; + closest[1] = test[1]; + closest[2] = test[2] + t * rmin; + } + } + else + { + rudDot = rmin * test[0] + umin * test[1] + dmin * test[2]; + rEdgeDot = umin * rudDot - minRUDDot * test[1]; + if (rEdgeDot >= (Real)0) + { + rdDot = rmin * test[0] + dmin * test[2]; + if (rdDot >= maxRDDot) + { + // LF-edge + closest[0] = rmax; + closest[1] = test[1]; + closest[2] = dmax; + } + else // assert( rdDot >= minRDDot ) + { + // L-face + t = rDot / minRDDot; + closest[0] = test[0] - t * dmin; + closest[1] = test[1]; + closest[2] = test[2] + t * rmin; + } + } + else + { + uEdgeDot = rmin * rudDot - minRUDDot * test[0]; + if (uEdgeDot >= (Real)0) + { + udDot = umin * test[1] + dmin * test[2]; + if (udDot >= maxUDDot) + { + // UF-edge + closest[0] = test[0]; + closest[1] = umax; + closest[2] = dmax; + } + else // assert( udDot >= minUDDot ) + { + // U-face + t = uDot / minUDDot; + closest[0] = test[0]; + closest[1] = test[1] - t * dmin; + closest[2] = test[2] + t * umin; + } + } + else + { + if (rudDot >= maxRUDDot) + { + // LUF-vertex + closest[0] = rmax; + closest[1] = umax; + closest[2] = dmax; + } + else // assert( rudDot >= minRUDDot ) + { + // LU-edge + t = rudDot / minRUDDot; + closest[0] = t * rmin; + closest[1] = t * umin; + closest[2] = t * dmin; + } + } + } + } + } + } + + diff = test - closest; + + // Convert back to original quadrant. + if (rSignChange) + { + closest[0] = -closest[0]; + } + + if (uSignChange) + { + closest[1] = -closest[1]; + } + + // Convert back to original coordinates. + result.frustumClosestPoint = frustum.origin + + closest[0] * frustum.rVector + + closest[1] * frustum.uVector + + closest[2] * frustum.dVector; + + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPoint3Plane3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPoint3Plane3.h new file mode 100644 index 0000000..e411bea --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPoint3Plane3.h @@ -0,0 +1,35 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, Plane3> + { + public: + struct Result + { + Real distance, signedDistance; + Vector3 planeClosestPoint; + }; + + Result operator()(Vector3 const& point, Plane3 const& plane) + { + Result result; + result.signedDistance = Dot(plane.normal, point) - plane.constant; + result.distance = std::fabs(result.signedDistance); + result.planeClosestPoint = point - result.signedDistance * plane.normal; + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPoint3Rectangle3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPoint3Rectangle3.h new file mode 100644 index 0000000..33f9743 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPoint3Rectangle3.h @@ -0,0 +1,74 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, Rectangle3> + { + public: + struct Result + { + Real distance, sqrDistance; + Real rectangleParameter[2]; + Vector3 rectangleClosestPoint; + }; + + Result operator()(Vector3 const& point, Rectangle3 const& rectangle) + { + Result result; + + Vector3 diff = rectangle.center - point; + Real b0 = Dot(diff, rectangle.axis[0]); + Real b1 = Dot(diff, rectangle.axis[1]); + Real s0 = -b0, s1 = -b1; + result.sqrDistance = Dot(diff, diff); + + if (s0 < -rectangle.extent[0]) + { + s0 = -rectangle.extent[0]; + } + else if (s0 > rectangle.extent[0]) + { + s0 = rectangle.extent[0]; + } + result.sqrDistance += s0 * (s0 + (Real)2 * b0); + + if (s1 < -rectangle.extent[1]) + { + s1 = -rectangle.extent[1]; + } + else if (s1 > rectangle.extent[1]) + { + s1 = rectangle.extent[1]; + } + result.sqrDistance += s1 * (s1 + (Real)2 * b1); + + // Account for numerical round-off error. + if (result.sqrDistance < (Real)0) + { + result.sqrDistance = (Real)0; + } + + result.distance = std::sqrt(result.sqrDistance); + result.rectangleParameter[0] = s0; + result.rectangleParameter[1] = s1; + result.rectangleClosestPoint = rectangle.center; + for (int i = 0; i < 2; ++i) + { + result.rectangleClosestPoint += result.rectangleParameter[i] * rectangle.axis[i]; + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPoint3Tetrahedron3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPoint3Tetrahedron3.h new file mode 100644 index 0000000..a375974 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPoint3Tetrahedron3.h @@ -0,0 +1,73 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, Tetrahedron3> + { + public: + struct Result + { + Real distance, sqrDistance; + Vector3 tetrahedronClosestPoint; + }; + + Result operator()(Vector3 const& point, Tetrahedron3 const& tetrahedron) + { + Result result; + + // Construct the planes for the faces of the tetrahedron. The + // normals are outer pointing, but specified not to be unit + // length. We only need to know sidedness of the query point, + // so we will save cycles by not computing unit-length normals. + Plane3 planes[4]; + tetrahedron.GetPlanes(planes); + + // Determine which faces are visible to the query point. Only + // these need to be processed by point-to-triangle distance + // queries. + result.sqrDistance = std::numeric_limits::max(); + result.tetrahedronClosestPoint = Vector3::Zero(); + for (int i = 0; i < 4; ++i) + { + if (Dot(planes[i].normal, point) >= planes[i].constant) + { + int indices[3] = { 0, 0, 0 }; + tetrahedron.GetFaceIndices(i, indices); + Triangle3 triangle( + tetrahedron.v[indices[0]], + tetrahedron.v[indices[1]], + tetrahedron.v[indices[2]]); + + DCPQuery, Triangle3> query; + auto ptResult = query(point, triangle); + if (ptResult.sqrDistance < result.sqrDistance) + { + result.sqrDistance = ptResult.sqrDistance; + result.tetrahedronClosestPoint = ptResult.closest; + } + } + } + + if (result.sqrDistance == std::numeric_limits::max()) + { + // The query point is inside the solid tetrahedron. Report a + // zero distance. The closest points are identical. + result.sqrDistance = (Real)0; + result.tetrahedronClosestPoint = point; + } + result.distance = std::sqrt(result.sqrDistance); + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointAlignedBox.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointAlignedBox.h new file mode 100644 index 0000000..71da1ff --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointAlignedBox.h @@ -0,0 +1,77 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, AlignedBox> + { + public: + struct Result + { + Real distance, sqrDistance; + Vector boxClosest; + }; + + Result operator()(Vector const& point, AlignedBox const& box) + { + // Translate the point and box so that the box has center at the + // origin. + Vector boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + Vector closest = point - boxCenter; + + Result result; + DoQuery(closest, boxExtent, result); + + // Compute the closest point on the box. + result.boxClosest = boxCenter + closest; + return result; + } + + protected: + // On input, 'point' is the difference of the query point and the box + // center. On output, 'point' is the point on the box closest to the + // query point. + void DoQuery(Vector& point, Vector const& boxExtent, + Result& result) + { + result.sqrDistance = (Real)0; + for (int i = 0; i < N; ++i) + { + if (point[i] < -boxExtent[i]) + { + Real delta = point[i] + boxExtent[i]; + result.sqrDistance += delta * delta; + point[i] = -boxExtent[i]; + } + else if (point[i] > boxExtent[i]) + { + Real delta = point[i] - boxExtent[i]; + result.sqrDistance += delta * delta; + point[i] = boxExtent[i]; + } + } + result.distance = std::sqrt(result.sqrDistance); + } + }; + + // Template aliases for convenience. + template + using DCPPointAlignedBox = DCPQuery, AlignedBox>; + + template + using DCPPoint2AlignedBox2 = DCPPointAlignedBox<2, Real>; + + template + using DCPPoint3AlignedBox3 = DCPPointAlignedBox<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointHyperellipsoid.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointHyperellipsoid.h new file mode 100644 index 0000000..0792f51 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointHyperellipsoid.h @@ -0,0 +1,352 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// Compute the distance from a point to a hyperellipsoid. In 2D, this is a +// point-ellipse distance query. In 3D, this is a point-ellipsoid distance +// query. The following document describes the algorithm. +// https://www.geometrictools.com/Documentation/DistancePointEllipseEllipsoid.pdf +// The hyperellipsoid can have arbitrary center and orientation; that is, it +// does not have to be axis-aligned with center at the origin. +// +// For the 2D query, +// Vector2 point; // initialized to something +// Ellipse2 ellipse; // initialized to something +// DCPPoint2Ellipse2 query; +// auto result = query(point, ellipse); +// Real distance = result.distance; +// Vector2 closestEllipsePoint = result.closest; +// +// For the 3D query, +// Vector3 point; // initialized to something +// Ellipsoid3 ellipsoid; // initialized to something +// DCPPoint3Ellipsoid3 query; +// auto result = query(point, ellipsoid); +// Real distance = result.distance; +// Vector3 closestEllipsoidPoint = result.closest; + +namespace WwiseGTE +{ + template + class DCPQuery, Hyperellipsoid> + { + public: + struct Result + { + Real distance, sqrDistance; + Vector closest; + }; + + // The query for any hyperellipsoid. + Result operator()(Vector const& point, + Hyperellipsoid const& hyperellipsoid) + { + Result result; + + // Compute the coordinates of Y in the hyperellipsoid coordinate + // system. + Vector diff = point - hyperellipsoid.center; + Vector y; + for (int i = 0; i < N; ++i) + { + y[i] = Dot(diff, hyperellipsoid.axis[i]); + } + + // Compute the closest hyperellipsoid point in the axis-aligned + // coordinate system. + Vector x; + result.sqrDistance = SqrDistance(hyperellipsoid.extent, y, x); + result.distance = std::sqrt(result.sqrDistance); + + // Convert back to the original coordinate system. + result.closest = hyperellipsoid.center; + for (int i = 0; i < N; ++i) + { + result.closest += x[i] * hyperellipsoid.axis[i]; + } + + return result; + } + + // The 'hyperellipsoid' is assumed to be axis-aligned and centered at the + // origin , so only the extent[] values are used. + Result operator()(Vector const& point, Vector const& extent) + { + Result result; + result.sqrDistance = SqrDistance(extent, point, result.closest); + result.distance = std::sqrt(result.sqrDistance); + return result; + } + + private: + // The hyperellipsoid is sum_{d=0}^{N-1} (x[d]/e[d])^2 = 1 with no + // constraints on the orderind of the e[d]. The query point is + // (y[0],...,y[N-1]) with no constraints on the signs of the components. + // The function returns the squared distance from the query point to the + // hyperellipsoid. It also computes the hyperellipsoid point + // (x[0],...,x[N-1]) that is closest to (y[0],...,y[N-1]). + Real SqrDistance(Vector const& e, + Vector const& y, Vector& x) + { + // Determine negations for y to the first octant. + std::array negate; + int i, j; + for (i = 0; i < N; ++i) + { + negate[i] = (y[i] < (Real)0); + } + + // Determine the axis order for decreasing extents. + std::array, N> permute; + for (i = 0; i < N; ++i) + { + permute[i].first = -e[i]; + permute[i].second = i; + } + std::sort(permute.begin(), permute.end()); + + std::array invPermute; + for (i = 0; i < N; ++i) + { + invPermute[permute[i].second] = i; + } + + Vector locE, locY; + for (i = 0; i < N; ++i) + { + j = permute[i].second; + locE[i] = e[j]; + locY[i] = std::fabs(y[j]); + } + + Vector locX; + Real sqrDistance = SqrDistanceSpecial(locE, locY, locX); + + // Restore the axis order and reflections. + for (i = 0; i < N; ++i) + { + j = invPermute[i]; + if (negate[i]) + { + locX[j] = -locX[j]; + } + x[i] = locX[j]; + } + + return sqrDistance; + } + + // The hyperellipsoid is sum_{d=0}^{N-1} (x[d]/e[d])^2 = 1 with the e[d] + // positive and nonincreasing: e[d] >= e[d + 1] for all d. The query + // point is (y[0],...,y[N-1]) with y[d] >= 0 for all d. The function + // returns the squared distance from the query point to the + // hyperellipsoid. It also computes the hyperellipsoid point + // (x[0],...,x[N-1]) that is closest to (y[0],...,y[N-1]), where + // x[d] >= 0 for all d. + Real SqrDistanceSpecial(Vector const& e, + Vector const& y, Vector& x) + { + Real sqrDistance = (Real)0; + + Vector ePos, yPos, xPos; + int numPos = 0; + int i; + for (i = 0; i < N; ++i) + { + if (y[i] > (Real)0) + { + ePos[numPos] = e[i]; + yPos[numPos] = y[i]; + ++numPos; + } + else + { + x[i] = (Real)0; + } + } + + if (y[N - 1] > (Real)0) + { + sqrDistance = Bisector(numPos, ePos, yPos, xPos); + } + else // y[N-1] = 0 + { + Vector numer, denom; + Real eNm1Sqr = e[N - 1] * e[N - 1]; + for (i = 0; i < numPos; ++i) + { + numer[i] = ePos[i] * yPos[i]; + denom[i] = ePos[i] * ePos[i] - eNm1Sqr; + } + + bool inSubHyperbox = true; + for (i = 0; i < numPos; ++i) + { + if (numer[i] >= denom[i]) + { + inSubHyperbox = false; + break; + } + } + + bool inSubHyperellipsoid = false; + if (inSubHyperbox) + { + // yPos[] is inside the axis-aligned bounding box of the + // subhyperellipsoid. This intermediate test is designed + // to guard against the division by zero when + // ePos[i] == e[N-1] for some i. + Vector xde; + Real discr = (Real)1; + for (i = 0; i < numPos; ++i) + { + xde[i] = numer[i] / denom[i]; + discr -= xde[i] * xde[i]; + } + if (discr > (Real)0) + { + // yPos[] is inside the subhyperellipsoid. The + // closest hyperellipsoid point has x[N-1] > 0. + sqrDistance = (Real)0; + for (i = 0; i < numPos; ++i) + { + xPos[i] = ePos[i] * xde[i]; + Real diff = xPos[i] - yPos[i]; + sqrDistance += diff * diff; + } + x[N - 1] = e[N - 1] * std::sqrt(discr); + sqrDistance += x[N - 1] * x[N - 1]; + inSubHyperellipsoid = true; + } + } + + if (!inSubHyperellipsoid) + { + // yPos[] is outside the subhyperellipsoid. The closest + // hyperellipsoid point has x[N-1] == 0 and is on the + // domain-boundary hyperellipsoid. + x[N - 1] = (Real)0; + sqrDistance = Bisector(numPos, ePos, yPos, xPos); + } + } + + // Fill in those x[] values that were not zeroed out initially. + for (i = 0, numPos = 0; i < N; ++i) + { + if (y[i] > (Real)0) + { + x[i] = xPos[numPos]; + ++numPos; + } + } + + return sqrDistance; + } + + // The bisection algorithm to find the unique root of F(t). + Real Bisector(int numComponents, Vector const& e, + Vector const& y, Vector& x) + { + Vector z; + Real sumZSqr = (Real)0; + int i; + for (i = 0; i < numComponents; ++i) + { + z[i] = y[i] / e[i]; + sumZSqr += z[i] * z[i]; + } + + if (sumZSqr == (Real)1) + { + // The point is on the hyperellipsoid. + for (i = 0; i < numComponents; ++i) + { + x[i] = y[i]; + } + return (Real)0; + } + + Real emin = e[numComponents - 1]; + Vector pSqr, numerator; + for (i = 0; i < numComponents; ++i) + { + Real p = e[i] / emin; + pSqr[i] = p * p; + numerator[i] = pSqr[i] * z[i]; + } + + Real s = (Real)0, smin = z[numComponents - 1] - (Real)1, smax; + if (sumZSqr < (Real)1) + { + // The point is strictly inside the hyperellipsoid. + smax = (Real)0; + } + else + { + // The point is strictly outside the hyperellipsoid. + smax = Length(numerator, true) - (Real)1; + } + + // The use of 'double' is intentional in case Real is a BSNumber + // or BSRational type. We want the bisections to terminate in a + // reasonable/ amount of time. + unsigned int const jmax = GTE_C_MAX_BISECTIONS_GENERIC; + for (unsigned int j = 0; j < jmax; ++j) + { + s = (smin + smax) * (Real)0.5; + if (s == smin || s == smax) + { + break; + } + + Real g = (Real)-1; + for (i = 0; i < numComponents; ++i) + { + Real ratio = numerator[i] / (s + pSqr[i]); + g += ratio * ratio; + } + + if (g > (Real)0) + { + smin = s; + } + else if (g < (Real)0) + { + smax = s; + } + else + { + break; + } + } + + Real sqrDistance = (Real)0; + for (i = 0; i < numComponents; ++i) + { + x[i] = pSqr[i] * y[i] / (s + pSqr[i]); + Real diff = x[i] - y[i]; + sqrDistance += diff * diff; + } + return sqrDistance; + } + }; + + // Template aliases for convenience. + template + using DCPPointHyperellipsoid = DCPQuery, Hyperellipsoid>; + + template + using DCPPoint2Ellipse2 = DCPPointHyperellipsoid<2, Real>; + + template + using DCPPoint3Ellipsoid3 = DCPPointHyperellipsoid<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointLine.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointLine.h new file mode 100644 index 0000000..e44fea0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointLine.h @@ -0,0 +1,51 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, Line> + { + public: + struct Result + { + Real distance, sqrDistance; + Real lineParameter; // t in (-infinity,+infinity) + Vector lineClosest; // origin + t * direction + }; + + Result operator()(Vector const& point, Line const& line) + { + Result result; + + Vector diff = point - line.origin; + result.lineParameter = Dot(line.direction, diff); + result.lineClosest = line.origin + result.lineParameter * line.direction; + + diff = point - result.lineClosest; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + + return result; + } + }; + + // Template aliases for convenience. + template + using DCPPointLine = DCPQuery, Line>; + + template + using DCPPoint2Line2 = DCPPointLine<2, Real>; + + template + using DCPPoint3Line3 = DCPPointLine<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointOrientedBox.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointOrientedBox.h new file mode 100644 index 0000000..06155d2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointOrientedBox.h @@ -0,0 +1,61 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, OrientedBox> + : + public DCPQuery, AlignedBox> + { + public: + struct Result + : + public DCPQuery, AlignedBox>::Result + { + // No additional information to compute. + }; + + Result operator()(Vector const& point, OrientedBox const& box) + { + // Translate the point to the coordinate system of the box. In + // this system, the box is axis-aligned with center at the origin. + Vector diff = point - box.center; + Vector closest; + for (int i = 0; i < N; ++i) + { + closest[i] = Dot(diff, box.axis[i]); + } + + Result result; + this->DoQuery(closest, box.extent, result); + + // Compute the closest point on the box. + result.boxClosest = box.center; + for (int i = 0; i < N; ++i) + { + result.boxClosest += closest[i] * box.axis[i]; + } + return result; + } + }; + + // Template aliases for convenience. + template + using DCPPointOrientedBox = DCPQuery, AlignedBox>; + + template + using DCPPoint2OrientedBox2 = DCPPointOrientedBox<2, Real>; + + template + using DCPPoint3OrientedBox3 = DCPPointOrientedBox<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointRay.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointRay.h new file mode 100644 index 0000000..82e66dc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointRay.h @@ -0,0 +1,58 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, Ray> + { + public: + struct Result + { + Real distance, sqrDistance; + Real rayParameter; // t in [0,+infinity) + Vector rayClosest; // origin + t * direction + }; + + Result operator()(Vector const& point, Ray const& ray) + { + Result result; + + Vector diff = point - ray.origin; + result.rayParameter = Dot(ray.direction, diff); + if (result.rayParameter > (Real)0) + { + result.rayClosest = ray.origin + result.rayParameter * ray.direction; + } + else + { + result.rayClosest = ray.origin; + } + + diff = point - result.rayClosest; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + + return result; + } + }; + + // Template aliases for convenience. + template + using DCPPointRay = DCPQuery, Ray>; + + template + using DCPPoint2Ray2 = DCPPointRay<2, Real>; + + template + using DCPPoint3Ray3 = DCPPointRay<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointSegment.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointSegment.h new file mode 100644 index 0000000..d3df60d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointSegment.h @@ -0,0 +1,83 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, Segment> + { + public: + struct Result + { + Real distance, sqrDistance; + Real segmentParameter; // t in [0,1] + Vector segmentClosest; // (1-t)*p[0] + t*p[1] + }; + + Result operator()(Vector const& point, Segment const& segment) + { + Result result; + + // The direction vector is not unit length. The normalization is + // deferred until it is needed. + Vector direction = segment.p[1] - segment.p[0]; + Vector diff = point - segment.p[1]; + Real t = Dot(direction, diff); + if (t >= (Real)0) + { + result.segmentParameter = (Real)1; + result.segmentClosest = segment.p[1]; + } + else + { + diff = point - segment.p[0]; + t = Dot(direction, diff); + if (t <= (Real)0) + { + result.segmentParameter = (Real)0; + result.segmentClosest = segment.p[0]; + } + else + { + Real sqrLength = Dot(direction, direction); + if (sqrLength > (Real)0) + { + t /= sqrLength; + result.segmentParameter = t; + result.segmentClosest = segment.p[0] + t * direction; + } + else + { + result.segmentParameter = (Real)0; + result.segmentClosest = segment.p[0]; + } + } + } + + diff = point - result.segmentClosest; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + + return result; + } + }; + + // Template aliases for convenience. + template + using DCPPointSegment = DCPQuery, Segment>; + + template + using DCPPoint2Segment2 = DCPPointSegment<2, Real>; + + template + using DCPPoint3Segment3 = DCPPointSegment<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointTriangle.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointTriangle.h new file mode 100644 index 0000000..71ae36e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointTriangle.h @@ -0,0 +1,487 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, Triangle> + { + public: + struct Result + { + Real distance, sqrDistance; + Real parameter[3]; // barycentric coordinates for triangle.v[3] + Vector closest; + }; + + Result operator()(Vector const& point, Triangle const& triangle) + { + Vector diff = point - triangle.v[0]; + Vector edge0 = triangle.v[1] - triangle.v[0]; + Vector edge1 = triangle.v[2] - triangle.v[0]; + Real a00 = Dot(edge0, edge0); + Real a01 = Dot(edge0, edge1); + Real a11 = Dot(edge1, edge1); + Real b0 = -Dot(diff, edge0); + Real b1 = -Dot(diff, edge1); + + Real f00 = b0; + Real f10 = b0 + a00; + Real f01 = b0 + a01; + + Vector<2, Real> p0, p1, p; + Real dt1, h0, h1; + + // Compute the endpoints p0 and p1 of the segment. The segment is + // parameterized by L(z) = (1-z)*p0 + z*p1 for z in [0,1] and the + // directional derivative of half the quadratic on the segment is + // H(z) = Dot(p1-p0,gradient[Q](L(z))/2), where gradient[Q]/2 = + // (F,G). By design, F(L(z)) = 0 for cases (2), (4), (5), and + // (6). Cases (1) and (3) can correspond to no-intersection or + // intersection of F = 0 with the triangle. + if (f00 >= (Real)0) + { + if (f01 >= (Real)0) + { + // (1) p0 = (0,0), p1 = (0,1), H(z) = G(L(z)) + GetMinEdge02(a11, b1, p); + } + else + { + // (2) p0 = (0,t10), p1 = (t01,1-t01), + // H(z) = (t11 - t10)*G(L(z)) + p0[0] = (Real)0; + p0[1] = f00 / (f00 - f01); + p1[0] = f01 / (f01 - f10); + p1[1] = (Real)1 - p1[0]; + dt1 = p1[1] - p0[1]; + h0 = dt1 * (a11 * p0[1] + b1); + if (h0 >= (Real)0) + { + GetMinEdge02(a11, b1, p); + } + else + { + h1 = dt1 * (a01 * p1[0] + a11 * p1[1] + b1); + if (h1 <= (Real)0) + { + GetMinEdge12(a01, a11, b1, f10, f01, p); + } + else + { + GetMinInterior(p0, h0, p1, h1, p); + } + } + } + } + else if (f01 <= (Real)0) + { + if (f10 <= (Real)0) + { + // (3) p0 = (1,0), p1 = (0,1), H(z) = G(L(z)) - F(L(z)) + GetMinEdge12(a01, a11, b1, f10, f01, p); + } + else + { + // (4) p0 = (t00,0), p1 = (t01,1-t01), H(z) = t11*G(L(z)) + p0[0] = f00 / (f00 - f10); + p0[1] = (Real)0; + p1[0] = f01 / (f01 - f10); + p1[1] = (Real)1 - p1[0]; + h0 = p1[1] * (a01 * p0[0] + b1); + if (h0 >= (Real)0) + { + p = p0; // GetMinEdge01 + } + else + { + h1 = p1[1] * (a01 * p1[0] + a11 * p1[1] + b1); + if (h1 <= (Real)0) + { + GetMinEdge12(a01, a11, b1, f10, f01, p); + } + else + { + GetMinInterior(p0, h0, p1, h1, p); + } + } + } + } + else if (f10 <= (Real)0) + { + // (5) p0 = (0,t10), p1 = (t01,1-t01), + // H(z) = (t11 - t10)*G(L(z)) + p0[0] = (Real)0; + p0[1] = f00 / (f00 - f01); + p1[0] = f01 / (f01 - f10); + p1[1] = (Real)1 - p1[0]; + dt1 = p1[1] - p0[1]; + h0 = dt1 * (a11 * p0[1] + b1); + if (h0 >= (Real)0) + { + GetMinEdge02(a11, b1, p); + } + else + { + h1 = dt1 * (a01 * p1[0] + a11 * p1[1] + b1); + if (h1 <= (Real)0) + { + GetMinEdge12(a01, a11, b1, f10, f01, p); + } + else + { + GetMinInterior(p0, h0, p1, h1, p); + } + } + } + else + { + // (6) p0 = (t00,0), p1 = (0,t11), H(z) = t11*G(L(z)) + p0[0] = f00 / (f00 - f10); + p0[1] = (Real)0; + p1[0] = (Real)0; + p1[1] = f00 / (f00 - f01); + h0 = p1[1] * (a01 * p0[0] + b1); + if (h0 >= (Real)0) + { + p = p0; // GetMinEdge01 + } + else + { + h1 = p1[1] * (a11 * p1[1] + b1); + if (h1 <= (Real)0) + { + GetMinEdge02(a11, b1, p); + } + else + { + GetMinInterior(p0, h0, p1, h1, p); + } + } + } + + Result result; + result.parameter[0] = (Real)1 - p[0] - p[1]; + result.parameter[1] = p[0]; + result.parameter[2] = p[1]; + result.closest = triangle.v[0] + p[0] * edge0 + p[1] * edge1; + diff = point - result.closest; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; + } + + // TODO: This is the previous implementation based on quadratic + // minimization with constraints. It was replaced by the current + // operator() that uses the conjugate gradient algorithm. I will + // keep both in the upcoming GTL code, so the old code is restored + // here for now. + Result DistanceByQM(Vector const& point, Triangle const& triangle) + { + // The member result.sqrDistance is set each block of the nested + // if-then-else statements. The remaining members are all set at + // the end of the function. + Result result; + + Vector diff = triangle.v[0] - point; + Vector edge0 = triangle.v[1] - triangle.v[0]; + Vector edge1 = triangle.v[2] - triangle.v[0]; + Real a00 = Dot(edge0, edge0); + Real a01 = Dot(edge0, edge1); + Real a11 = Dot(edge1, edge1); + Real b0 = Dot(diff, edge0); + Real b1 = Dot(diff, edge1); + Real c = Dot(diff, diff); + Real det = std::max(a00 * a11 - a01 * a01, (Real)0); + Real s = a01 * b1 - a11 * b0; + Real t = a01 * b0 - a00 * b1; + + if (s + t <= det) + { + if (s < (Real)0) + { + if (t < (Real)0) // region 4 + { + if (b0 < (Real)0) + { + t = (Real)0; + if (-b0 >= a00) + { + s = (Real)1; + result.sqrDistance = a00 + (Real)2 * b0 + c; + } + else + { + s = -b0 / a00; + result.sqrDistance = b0 * s + c; + } + } + else + { + s = (Real)0; + if (b1 >= (Real)0) + { + t = (Real)0; + result.sqrDistance = c; + } + else if (-b1 >= a11) + { + t = (Real)1; + result.sqrDistance = a11 + (Real)2 * b1 + c; + } + else + { + t = -b1 / a11; + result.sqrDistance = b1 * t + c; + } + } + } + else // region 3 + { + s = (Real)0; + if (b1 >= (Real)0) + { + t = (Real)0; + result.sqrDistance = c; + } + else if (-b1 >= a11) + { + t = (Real)1; + result.sqrDistance = a11 + (Real)2 * b1 + c; + } + else + { + t = -b1 / a11; + result.sqrDistance = b1 * t + c; + } + } + } + else if (t < (Real)0) // region 5 + { + t = (Real)0; + if (b0 >= (Real)0) + { + s = (Real)0; + result.sqrDistance = c; + } + else if (-b0 >= a00) + { + s = (Real)1; + result.sqrDistance = a00 + (Real)2 * b0 + c; + } + else + { + s = -b0 / a00; + result.sqrDistance = b0 * s + c; + } + } + else // region 0 + { + // minimum at interior point + Real invDet = ((Real)1) / det; + s *= invDet; + t *= invDet; + result.sqrDistance = s * (a00 * s + a01 * t + (Real)2 * b0) + + t * (a01 * s + a11 * t + (Real)2 * b1) + c; + } + } + else + { + Real tmp0, tmp1, numer, denom; + + if (s < (Real)0) // region 2 + { + tmp0 = a01 + b0; + tmp1 = a11 + b1; + if (tmp1 > tmp0) + { + numer = tmp1 - tmp0; + denom = a00 - (Real)2 * a01 + a11; + if (numer >= denom) + { + s = (Real)1; + t = (Real)0; + result.sqrDistance = a00 + (Real)2 * b0 + c; + } + else + { + s = numer / denom; + t = (Real)1 - s; + result.sqrDistance = s * (a00 * s + a01 * t + (Real)2 * b0) + + t * (a01 * s + a11 * t + (Real)2 * b1) + c; + } + } + else + { + s = (Real)0; + if (tmp1 <= (Real)0) + { + t = (Real)1; + result.sqrDistance = a11 + (Real)2 * b1 + c; + } + else if (b1 >= (Real)0) + { + t = (Real)0; + result.sqrDistance = c; + } + else + { + t = -b1 / a11; + result.sqrDistance = b1 * t + c; + } + } + } + else if (t < (Real)0) // region 6 + { + tmp0 = a01 + b1; + tmp1 = a00 + b0; + if (tmp1 > tmp0) + { + numer = tmp1 - tmp0; + denom = a00 - (Real)2 * a01 + a11; + if (numer >= denom) + { + t = (Real)1; + s = (Real)0; + result.sqrDistance = a11 + (Real)2 * b1 + c; + } + else + { + t = numer / denom; + s = (Real)1 - t; + result.sqrDistance = s * (a00 * s + a01 * t + (Real)2 * b0) + + t * (a01 * s + a11 * t + (Real)2 * b1) + c; + } + } + else + { + t = (Real)0; + if (tmp1 <= (Real)0) + { + s = (Real)1; + result.sqrDistance = a00 + (Real)2 * b0 + c; + } + else if (b0 >= (Real)0) + { + s = (Real)0; + result.sqrDistance = c; + } + else + { + s = -b0 / a00; + result.sqrDistance = b0 * s + c; + } + } + } + else // region 1 + { + numer = a11 + b1 - a01 - b0; + if (numer <= (Real)0) + { + s = (Real)0; + t = (Real)1; + result.sqrDistance = a11 + (Real)2 * b1 + c; + } + else + { + denom = a00 - ((Real)2) * a01 + a11; + if (numer >= denom) + { + s = (Real)1; + t = (Real)0; + result.sqrDistance = a00 + (Real)2 * b0 + c; + } + else + { + s = numer / denom; + t = (Real)1 - s; + result.sqrDistance = s * (a00 * s + a01 * t + (Real)2 * b0) + + t * (a01 * s + a11 * t + (Real)2 * b1) + c; + } + } + } + } + + // Account for numerical round-off error. + if (result.sqrDistance < (Real)0) + { + result.sqrDistance = (Real)0; + } + + result.distance = sqrt(result.sqrDistance); + result.closest = triangle.v[0] + s * edge0 + t * edge1; + result.parameter[1] = s; + result.parameter[2] = t; + result.parameter[0] = (Real)1 - s - t; + return result; + } + + private: + void GetMinEdge02(Real const& a11, Real const& b1, Vector<2, Real>& p) + { + p[0] = (Real)0; + if (b1 >= (Real)0) + { + p[1] = (Real)0; + } + else if (a11 + b1 <= (Real)0) + { + p[1] = (Real)1; + } + else + { + p[1] = -b1 / a11; + } + } + + inline void GetMinEdge12(Real const& a01, Real const& a11, Real const& b1, + Real const& f10, Real const& f01, Vector<2, Real>& p) + { + Real h0 = a01 + b1 - f10; + if (h0 >= (Real)0) + { + p[1] = (Real)0; + } + else + { + Real h1 = a11 + b1 - f01; + if (h1 <= (Real)0) + { + p[1] = (Real)1; + } + else + { + p[1] = h0 / (h0 - h1); + } + } + p[0] = (Real)1 - p[1]; + } + + inline void GetMinInterior(Vector<2, Real> const& p0, Real const& h0, + Vector<2, Real> const& p1, Real const& h1, Vector<2, Real>& p) + { + Real z = h0 / (h0 - h1); + p = ((Real)1 - z) * p0 + z * p1; + } + }; + + // Template aliases for convenience. + template + using DCPPointTriangle = DCPQuery, Triangle>; + + template + using DCPPoint2Triangle2 = DCPPointTriangle<2, Real>; + + template + using DCPPoint3Triangle3 = DCPPointTriangle<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointTriangleExact.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointTriangleExact.h new file mode 100644 index 0000000..6a5f2ad --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistPointTriangleExact.h @@ -0,0 +1,229 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class DistancePointTriangleExact + { + public: + struct Result + { + Rational sqrDistance; + // barycentric coordinates for triangle.v[3] + Rational parameter[3]; + Vector closest; + }; + + Result operator()(Vector const& point, Triangle const& triangle) + { + Vector diff = point - triangle.v[0]; + Vector edge0 = triangle.v[1] - triangle.v[0]; + Vector edge1 = triangle.v[2] - triangle.v[0]; + Rational a00 = Dot(edge0, edge0); + Rational a01 = Dot(edge0, edge1); + Rational a11 = Dot(edge1, edge1); + Rational b0 = -Dot(diff, edge0); + Rational b1 = -Dot(diff, edge1); + Rational const zero = (Rational)0; + Rational const one = (Rational)1; + Rational det = a00 * a11 - a01 * a01; + Rational t0 = a01 * b1 - a11 * b0; + Rational t1 = a01 * b0 - a00 * b1; + + if (t0 + t1 <= det) + { + if (t0 < zero) + { + if (t1 < zero) // region 4 + { + if (b0 < zero) + { + t1 = zero; + if (-b0 >= a00) // V1 + { + t0 = one; + } + else // E01 + { + t0 = -b0 / a00; + } + } + else + { + t0 = zero; + if (b1 >= zero) // V0 + { + t1 = zero; + } + else if (-b1 >= a11) // V2 + { + t1 = one; + } + else // E20 + { + t1 = -b1 / a11; + } + } + } + else // region 3 + { + t0 = zero; + if (b1 >= zero) // V0 + { + t1 = zero; + } + else if (-b1 >= a11) // V2 + { + t1 = one; + } + else // E20 + { + t1 = -b1 / a11; + } + } + } + else if (t1 < zero) // region 5 + { + t1 = zero; + if (b0 >= zero) // V0 + { + t0 = zero; + } + else if (-b0 >= a00) // V1 + { + t0 = one; + } + else // E01 + { + t0 = -b0 / a00; + } + } + else // region 0, interior + { + Rational invDet = one / det; + t0 *= invDet; + t1 *= invDet; + } + } + else + { + Rational tmp0, tmp1, numer, denom; + + if (t0 < zero) // region 2 + { + tmp0 = a01 + b0; + tmp1 = a11 + b1; + if (tmp1 > tmp0) + { + numer = tmp1 - tmp0; + denom = a00 - (Rational)2 * a01 + a11; + if (numer >= denom) // V1 + { + t0 = one; + t1 = zero; + } + else // E12 + { + t0 = numer / denom; + t1 = one - t0; + } + } + else + { + t0 = zero; + if (tmp1 <= zero) // V2 + { + t1 = one; + } + else if (b1 >= zero) // V0 + { + t1 = zero; + } + else // E20 + { + t1 = -b1 / a11; + } + } + } + else if (t1 < zero) // region 6 + { + tmp0 = a01 + b1; + tmp1 = a00 + b0; + if (tmp1 > tmp0) + { + numer = tmp1 - tmp0; + denom = a00 - (Rational)2 * a01 + a11; + if (numer >= denom) // V2 + { + t1 = one; + t0 = zero; + } + else // E12 + { + t1 = numer / denom; + t0 = one - t1; + } + } + else + { + t1 = zero; + if (tmp1 <= zero) // V1 + { + t0 = one; + } + else if (b0 >= zero) // V0 + { + t0 = zero; + } + else // E01 + { + t0 = -b0 / a00; + } + } + } + else // region 1 + { + numer = a11 + b1 - a01 - b0; + if (numer <= zero) // V2 + { + t0 = zero; + t1 = one; + } + else + { + denom = a00 - (Rational)2 * a01 + a11; + if (numer >= denom) // V1 + { + t0 = one; + t1 = zero; + } + else // 12 + { + t0 = numer / denom; + t1 = one - t0; + } + } + } + } + + Result result; + result.parameter[0] = one - t0 - t1; + result.parameter[1] = t0; + result.parameter[2] = t1; + result.closest = triangle.v[0] + t0 * edge0 + t1 * edge1; + diff = point - result.closest; + result.sqrDistance = Dot(diff, diff); + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRay3AlignedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRay3AlignedBox3.h new file mode 100644 index 0000000..9b0590f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRay3AlignedBox3.h @@ -0,0 +1,55 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, AlignedBox3> + { + public: + struct Result + { + Real distance, sqrDistance; + Real rayParameter; + Vector3 closestPoint[2]; + }; + + Result operator()(Ray3 const& ray, AlignedBox3 const& box) + { + Result result; + + Line3 line(ray.origin, ray.direction); + DCPQuery, AlignedBox3> lbQuery; + auto lbResult = lbQuery(line, box); + + if (lbResult.lineParameter >= (Real)0) + { + result.sqrDistance = lbResult.sqrDistance; + result.distance = lbResult.distance; + result.rayParameter = lbResult.lineParameter; + result.closestPoint[0] = lbResult.closestPoint[0]; + result.closestPoint[1] = lbResult.closestPoint[1]; + } + else + { + DCPQuery, AlignedBox3> pbQuery; + auto pbResult = pbQuery(ray.origin, box); + result.sqrDistance = pbResult.sqrDistance; + result.distance = pbResult.distance; + result.rayParameter = (Real)0; + result.closestPoint[0] = ray.origin; + result.closestPoint[1] = pbResult.boxClosest; + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRay3OrientedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRay3OrientedBox3.h new file mode 100644 index 0000000..326ceb0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRay3OrientedBox3.h @@ -0,0 +1,56 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, OrientedBox3> + { + public: + struct Result + { + Real distance, sqrDistance; + Real rayParameter; + Vector3 closestPoint[2]; + }; + + Result operator()(Ray3 const& ray, OrientedBox3 const& box) + { + Result result; + + Line3 line(ray.origin, ray.direction); + DCPQuery, OrientedBox3> lbQuery; + auto lbResult = lbQuery(line, box); + + if (lbResult.lineParameter >= (Real)0) + { + result.sqrDistance = lbResult.sqrDistance; + result.distance = lbResult.distance; + result.rayParameter = lbResult.lineParameter; + result.closestPoint[0] = lbResult.closestPoint[0]; + result.closestPoint[1] = lbResult.closestPoint[1]; + } + else + { + DCPQuery, OrientedBox3> pbQuery; + auto pbResult = pbQuery(ray.origin, box); + result.sqrDistance = pbResult.sqrDistance; + result.distance = pbResult.distance; + result.rayParameter = (Real)0; + result.closestPoint[0] = ray.origin; + result.closestPoint[1] = pbResult.boxClosest; + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRay3Rectangle3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRay3Rectangle3.h new file mode 100644 index 0000000..114791a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRay3Rectangle3.h @@ -0,0 +1,60 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, Rectangle3> + { + public: + struct Result + { + Real distance, sqrDistance; + Real rayParameter, rectangleParameter[2]; + Vector3 closestPoint[2]; + }; + + Result operator()(Ray3 const& ray, Rectangle3 const& rectangle) + { + Result result; + + Line3 line(ray.origin, ray.direction); + DCPQuery, Rectangle3> lrQuery; + auto lrResult = lrQuery(line, rectangle); + + if (lrResult.lineParameter >= (Real)0) + { + result.distance = lrResult.distance; + result.sqrDistance = lrResult.sqrDistance; + result.rayParameter = lrResult.lineParameter; + result.rectangleParameter[0] = lrResult.rectangleParameter[0]; + result.rectangleParameter[1] = lrResult.rectangleParameter[1]; + result.closestPoint[0] = lrResult.closestPoint[0]; + result.closestPoint[1] = lrResult.closestPoint[1]; + } + else + { + DCPQuery, Rectangle3> prQuery; + auto prResult = prQuery(ray.origin, rectangle); + result.distance = prResult.distance; + result.sqrDistance = prResult.sqrDistance; + result.rayParameter = (Real)0; + result.rectangleParameter[0] = prResult.rectangleParameter[0]; + result.rectangleParameter[1] = prResult.rectangleParameter[1]; + result.closestPoint[0] = ray.origin; + result.closestPoint[1] = prResult.rectangleClosestPoint; + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRay3Triangle3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRay3Triangle3.h new file mode 100644 index 0000000..e6f13fa --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRay3Triangle3.h @@ -0,0 +1,62 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, Triangle3> + { + public: + struct Result + { + Real distance, sqrDistance; + Real rayParameter, triangleParameter[3]; + Vector3 closestPoint[2]; + }; + + Result operator()(Ray3 const& ray, Triangle3 const& triangle) + { + Result result; + + Line3 line(ray.origin, ray.direction); + DCPQuery, Triangle3> ltQuery; + auto ltResult = ltQuery(line, triangle); + + if (ltResult.lineParameter >= (Real)0) + { + result.distance = ltResult.distance; + result.sqrDistance = ltResult.sqrDistance; + result.rayParameter = ltResult.lineParameter; + result.triangleParameter[0] = ltResult.triangleParameter[0]; + result.triangleParameter[1] = ltResult.triangleParameter[1]; + result.triangleParameter[2] = ltResult.triangleParameter[2]; + result.closestPoint[0] = ltResult.closestPoint[0]; + result.closestPoint[1] = ltResult.closestPoint[1]; + } + else + { + DCPQuery, Triangle3> ptQuery; + auto ptResult = ptQuery(ray.origin, triangle); + result.distance = ptResult.distance; + result.sqrDistance = ptResult.sqrDistance; + result.rayParameter = (Real)0; + result.triangleParameter[0] = ptResult.parameter[0]; + result.triangleParameter[1] = ptResult.parameter[1]; + result.triangleParameter[2] = ptResult.parameter[2]; + result.closestPoint[0] = ray.origin; + result.closestPoint[1] = ptResult.closest; + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRayRay.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRayRay.h new file mode 100644 index 0000000..e804191 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRayRay.h @@ -0,0 +1,153 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, Ray> + { + public: + struct Result + { + Real distance, sqrDistance; + Real parameter[2]; + Vector closestPoint[2]; + }; + + Result operator()(Ray const& ray0, Ray const& ray1) + { + Result result; + + Vector diff = ray0.origin - ray1.origin; + Real a01 = -Dot(ray0.direction, ray1.direction); + Real b0 = Dot(diff, ray0.direction), b1; + Real s0, s1; + + if (std::fabs(a01) < (Real)1) + { + // Rays are not parallel. + b1 = -Dot(diff, ray1.direction); + s0 = a01 * b1 - b0; + s1 = a01 * b0 - b1; + + if (s0 >= (Real)0) + { + if (s1 >= (Real)0) // region 0 (interior) + { + // Minimum at two interior points of rays. + Real det = (Real)1 - a01 * a01; + s0 /= det; + s1 /= det; + } + else // region 3 (side) + { + s1 = (Real)0; + if (b0 >= (Real)0) + { + s0 = (Real)0; + } + else + { + s0 = -b0; + } + } + } + else + { + if (s1 >= (Real)0) // region 1 (side) + { + s0 = (Real)0; + if (b1 >= (Real)0) + { + s1 = (Real)0; + } + else + { + s1 = -b1; + } + } + else // region 2 (corner) + { + if (b0 < (Real)0) + { + s0 = -b0; + s1 = (Real)0; + } + else + { + s0 = (Real)0; + if (b1 >= (Real)0) + { + s1 = (Real)0; + } + else + { + s1 = -b1; + } + } + } + } + } + else + { + // Rays are parallel. + if (a01 > (Real)0) + { + // Opposite direction vectors. + s1 = (Real)0; + if (b0 >= (Real)0) + { + s0 = (Real)0; + } + else + { + s0 = -b0; + } + } + else + { + // Same direction vectors. + if (b0 >= (Real)0) + { + b1 = -Dot(diff, ray1.direction); + s0 = (Real)0; + s1 = -b1; + } + else + { + s0 = -b0; + s1 = (Real)0; + } + } + } + + result.parameter[0] = s0; + result.parameter[1] = s1; + result.closestPoint[0] = ray0.origin + s0 * ray0.direction; + result.closestPoint[1] = ray1.origin + s1 * ray1.direction; + diff = result.closestPoint[0] - result.closestPoint[1]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; + } + }; + + // Template aliases for convenience. + template + using DCPRayRay = DCPQuery, Ray>; + + template + using DCPRay2Ray2 = DCPRayRay<2, Real>; + + template + using DCPRay3Ray3 = DCPRayRay<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRaySegment.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRaySegment.h new file mode 100644 index 0000000..f5061a1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRaySegment.h @@ -0,0 +1,169 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, Segment> + { + public: + struct Result + { + Real distance, sqrDistance; + Real parameter[2]; + Vector closestPoint[2]; + }; + + // The centered form of the 'segment' is used. Thus, parameter[1] of + // the result is in [-e,e], where e = |segment.p[1] - segment.p[0]|/2. + Result operator()(Ray const& ray, Segment const& segment) + { + Result result; + + Vector segCenter, segDirection; + Real segExtent; + segment.GetCenteredForm(segCenter, segDirection, segExtent); + + Vector diff = ray.origin - segCenter; + Real a01 = -Dot(ray.direction, segDirection); + Real b0 = Dot(diff, ray.direction); + Real s0, s1; + + if (std::fabs(a01) < (Real)1) + { + // The ray and segment are not parallel. + Real det = (Real)1 - a01 * a01; + Real extDet = segExtent * det; + Real b1 = -Dot(diff, segDirection); + s0 = a01 * b1 - b0; + s1 = a01 * b0 - b1; + + if (s0 >= (Real)0) + { + if (s1 >= -extDet) + { + if (s1 <= extDet) // region 0 + { + // Minimum at interior points of ray and segment. + s0 /= det; + s1 /= det; + } + else // region 1 + { + s1 = segExtent; + s0 = std::max(-(a01 * s1 + b0), (Real)0); + } + } + else // region 5 + { + s1 = -segExtent; + s0 = std::max(-(a01 * s1 + b0), (Real)0); + } + } + else + { + if (s1 <= -extDet) // region 4 + { + s0 = -(-a01 * segExtent + b0); + if (s0 > (Real)0) + { + s1 = -segExtent; + } + else + { + s0 = (Real)0; + s1 = -b1; + if (s1 < -segExtent) + { + s1 = -segExtent; + } + else if (s1 > segExtent) + { + s1 = segExtent; + } + } + } + else if (s1 <= extDet) // region 3 + { + s0 = (Real)0; + s1 = -b1; + if (s1 < -segExtent) + { + s1 = -segExtent; + } + else if (s1 > segExtent) + { + s1 = segExtent; + } + } + else // region 2 + { + s0 = -(a01 * segExtent + b0); + if (s0 > (Real)0) + { + s1 = segExtent; + } + else + { + s0 = (Real)0; + s1 = -b1; + if (s1 < -segExtent) + { + s1 = -segExtent; + } + else if (s1 > segExtent) + { + s1 = segExtent; + } + } + } + } + } + else + { + // Ray and segment are parallel. + if (a01 > (Real)0) + { + // Opposite direction vectors. + s1 = -segExtent; + } + else + { + // Same direction vectors. + s1 = segExtent; + } + + s0 = std::max(-(a01 * s1 + b0), (Real)0); + } + + result.parameter[0] = s0; + result.parameter[1] = s1; + result.closestPoint[0] = ray.origin + s0 * ray.direction; + result.closestPoint[1] = segCenter + s1 * segDirection; + diff = result.closestPoint[0] - result.closestPoint[1]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; + } + }; + + // Template aliases for convenience. + template + using DCPRaySegment = DCPQuery, Segment>; + + template + using DCPRay2Segment2 = DCPRaySegment<2, Real>; + + template + using DCPRay3Segment3 = DCPRaySegment<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRectangle3AlignedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRectangle3AlignedBox3.h new file mode 100644 index 0000000..31876cf --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRectangle3AlignedBox3.h @@ -0,0 +1,141 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// Compute the distance between a rectangle and an aligned box in 3D. The +// algorithm is based on using an LCP solver for the convex quadratic +// programming problem. For details, see +// https://www.geometrictools.com/Documentation/ConvexQuadraticProgramming.pdf + +namespace WwiseGTE +{ + template + class DCPQuery, AlignedBox3> + { + public: + struct Result + { + bool queryIsSuccessful; + + // These members are valid only when queryIsSuccessful is true; + // otherwise, they are all set to zero. + Real distance, sqrDistance; + std::array rectangleParameter; + std::array boxParameter; + Vector3 closestPoint[2]; + + // The number of iterations used by LCPSolver regardless of + // whether the query is successful. + int numLCPIterations; + }; + + // The default maximum iterations is 81 (n = 9, maxIterations = n*n). + // If the solver fails to converge, try increasing the maximum number + // of iterations. + void SetMaxLCPIterations(int maxLCPIterations) + { + mLCP.SetMaxIterations(maxLCPIterations); + } + + Result operator()(Rectangle3 const& rectangle, AlignedBox3 const& box) + { + Result result; + + // Translate the rectangle and aligned box so that the aligned + // box becomes a canonical box. + Vector3 K = box.max - box.min; + Vector3 V = rectangle.center - box.min; + + // Convert the oriented rectangle to a regular one (origin at a + // corner). + Vector3 scaledE0 = rectangle.axis[0] * rectangle.extent[0]; + Vector3 scaledE1 = rectangle.axis[1] * rectangle.extent[1]; + Vector3 E0 = scaledE0 * (Real)2; + Vector3 E1 = scaledE1 * (Real)2; + V -= scaledE0 + scaledE1; + + // Compute quantities to initialize q and M in the LCP. + Real dotVE0 = Dot(V, E0); + Real dotVE1 = Dot(V, E1); + Real dotE0E0 = Dot(E0, E0); + Real dotE1E1 = Dot(E1, E1); + + // The LCP has 5 variables and 5 (nontrivial) inequality + // constraints. + std::array q = + { + -V[0], -V[1], -V[2], dotVE0, dotVE1, K[0], K[1], K[2], (Real)1, (Real)1 + }; + + std::array, 10> M; + M[0] = { (Real)1, (Real)0, (Real)0, -E0[0], -E1[0], (Real)1, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[1] = { (Real)0, (Real)1, (Real)0, -E0[1], -E1[1], (Real)0, (Real)1, (Real)0, (Real)0, (Real)0 }; + M[2] = { (Real)0, (Real)0, (Real)1, -E0[2], -E1[2], (Real)0, (Real)0, (Real)1, (Real)0 , (Real)0 }; + M[3] = { -E0[0], -E0[1], -E0[2], dotE0E0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)1, (Real)0 }; + M[4] = { -E1[0], -E1[1], -E1[2], (Real)0, dotE1E1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)1 }; + M[5] = { (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[6] = { (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[7] = { (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[8] = { (Real)0, (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[9] = { (Real)0, (Real)0, (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + + std::array w, z; + if (mLCP.Solve(q, M, w, z)) + { + result.queryIsSuccessful = true; + Real t0 = (z[3] * (Real)2 - (Real)1) * rectangle.extent[0]; + Real t1 = (z[4] * (Real)2 - (Real)1) * rectangle.extent[1]; + result.rectangleParameter[0] = t0; + result.rectangleParameter[1] = t1; + result.closestPoint[0] = rectangle.center + t0 * rectangle.axis[0] + t1 * rectangle.axis[1]; + for (int i = 0; i < 3; ++i) + { + result.boxParameter[i] = z[i] + box.min[i]; + result.closestPoint[1][i] = result.boxParameter[i]; + } + + Vector3 diff = result.closestPoint[1] - result.closestPoint[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + } + else + { + // If you reach this case, the maximum number of iterations + // was not specified to be large enough or there is a problem + // due to floating-point rounding errors. If you believe the + // latter is true, file a bug report. + result.queryIsSuccessful = false; + + for (int i = 0; i < 2; ++i) + { + result.rectangleParameter[i] = (Real)0; + } + for (int i = 0; i < 3; ++i) + { + result.boxParameter[i] = (Real)0; + result.closestPoint[0][i] = (Real)0; + result.closestPoint[1][i] = (Real)0; + } + result.distance = (Real)0; + result.sqrDistance = (Real)0; + } + + result.numLCPIterations = mLCP.GetNumIterations(); + return result; + } + + private: + LCPSolver mLCP; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRectangle3OrientedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRectangle3OrientedBox3.h new file mode 100644 index 0000000..3fad436 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRectangle3OrientedBox3.h @@ -0,0 +1,150 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// Compute the distance between a rectangle and an oriented box in 3D. The +// algorithm is based on using an LCP solver for the convex quadratic +// programming problem. For details, see +// https://www.geometrictools.com/Documentation/ConvexQuadraticProgramming.pdf + +namespace WwiseGTE +{ + template + class DCPQuery, OrientedBox3> + { + public: + struct Result + { + bool queryIsSuccessful; + + // These members are valid only when queryIsSuccessful is true; + // otherwise, they are all set to zero. + Real distance, sqrDistance; + std::array rectangleParameter; + std::array boxParameter; + Vector3 closestPoint[2]; + + // The number of iterations used by LCPSolver regardless of + // whether the query is successful. + int numLCPIterations; + }; + + // The default maximum iterations is 81 (n = 9, maxIterations = n*n). + // If the solver fails to converge, try increasing the maximum number + // of iterations. + void SetMaxLCPIterations(int maxLCPIterations) + { + mLCP.SetMaxIterations(maxLCPIterations); + } + + Result operator()(Rectangle3 const& rectangle, OrientedBox3 const& box) + { + Result result; + + // Rigidly transform the rectangle and oriented box so that the + // oriented box becomes a canonical box. + Vector3 K = box.extent * (Real)2; + Vector3 tempV = rectangle.center - box.center; + Vector3 V, E0, E1; + for (int i = 0; i < 3; ++i) + { + V[i] = Dot(box.axis[i], tempV) + box.extent[i]; + E0[i] = Dot(box.axis[i], rectangle.axis[0]); + E1[i] = Dot(box.axis[i], rectangle.axis[1]); + } + + // Convert the oriented rectangle to a regular one (origin at a + // corner). + Vector3 scaledE0 = E0 * rectangle.extent[0]; + Vector3 scaledE1 = E1 * rectangle.extent[1]; + V -= scaledE0 + scaledE1; + E0 = scaledE0 * (Real)2; + E1 = scaledE1 * (Real)2; + + // Compute quantities to initialize q and M in the LCP. + Real dotVE0 = Dot(V, E0); + Real dotVE1 = Dot(V, E1); + Real dotE0E0 = Dot(E0, E0); + Real dotE1E1 = Dot(E1, E1); + + // The LCP has 5 variables and 5 (nontrivial) inequality + // constraints. + std::array q = + { + -V[0], -V[1], -V[2], dotVE0, dotVE1, K[0], K[1], K[2], (Real)1, (Real)1 + }; + + std::array, 10> M; + M[0] = { (Real)1, (Real)0, (Real)0, -E0[0], -E1[0], (Real)1, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[1] = { (Real)0, (Real)1, (Real)0, -E0[1], -E1[1], (Real)0, (Real)1, (Real)0, (Real)0, (Real)0 }; + M[2] = { (Real)0, (Real)0, (Real)1, -E0[2], -E1[2], (Real)0, (Real)0, (Real)1, (Real)0 , (Real)0 }; + M[3] = { -E0[0], -E0[1], -E0[2], dotE0E0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)1, (Real)0 }; + M[4] = { -E1[0], -E1[1], -E1[2], (Real)0, dotE1E1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)1 }; + M[5] = { (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[6] = { (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[7] = { (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[8] = { (Real)0, (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[9] = { (Real)0, (Real)0, (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + + std::array w, z; + if (mLCP.Solve(q, M, w, z)) + { + result.queryIsSuccessful = true; + + Real t0 = (z[3] * (Real)2 - (Real)1) * rectangle.extent[0]; + Real t1 = (z[4] * (Real)2 - (Real)1) * rectangle.extent[1]; + result.rectangleParameter[0] = t0; + result.rectangleParameter[1] = t1; + result.closestPoint[0] = rectangle.center + t0 * rectangle.axis[0] + t1 * rectangle.axis[1]; + result.closestPoint[1] = box.center; + for (int i = 0; i < 3; ++i) + { + result.boxParameter[i] = z[i] - box.extent[i]; + result.closestPoint[1] += result.boxParameter[i] * box.axis[i]; + } + + Vector3 diff = result.closestPoint[1] - result.closestPoint[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + } + else + { + // If you reach this case, the maximum number of iterations + // was not specified to be large enough or there is a problem + // due to floating-point rounding errors. If you believe the + // latter is true, file a bug report. + result.queryIsSuccessful = false; + + for (int i = 0; i < 2; ++i) + { + result.rectangleParameter[i] = (Real)0; + } + for (int i = 0; i < 3; ++i) + { + result.boxParameter[i] = (Real)0; + result.closestPoint[0][i] = (Real)0; + result.closestPoint[1][i] = (Real)0; + } + result.distance = (Real)0; + result.sqrDistance = (Real)0; + } + + result.numLCPIterations = mLCP.GetNumIterations(); + return result; + } + + private: + LCPSolver mLCP; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRectangle3Rectangle3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRectangle3Rectangle3.h new file mode 100644 index 0000000..b92bc0e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistRectangle3Rectangle3.h @@ -0,0 +1,93 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + template + class DCPQuery, Rectangle3> + { + public: + struct Result + { + Real distance, sqrDistance; + Real rectangle0Parameter[2], rectangle1Parameter[2]; + Vector3 closestPoint[2]; + }; + + Result operator()(Rectangle3 const& rectangle0, Rectangle3 const& rectangle1) + { + Result result; + + DCPQuery, Rectangle3> srQuery; + typename DCPQuery, Rectangle3>::Result srResult; + result.sqrDistance = std::numeric_limits::max(); + + // Compare edges of rectangle0 to the interior of rectangle1. + for (int i1 = 0; i1 < 2; ++i1) + { + for (int i0 = -1; i0 <= 1; i0 += 2) + { + Real s = i0 * rectangle0.extent[1 - i1]; + Vector3 segCenter = rectangle0.center + + s * rectangle0.axis[1 - i1]; + Segment3 edge(segCenter, rectangle0.axis[i1], + rectangle0.extent[i1]); + + srResult = srQuery(edge, rectangle1); + if (srResult.sqrDistance < result.sqrDistance) + { + result.distance = srResult.distance; + result.sqrDistance = srResult.sqrDistance; + result.rectangle0Parameter[i1] = s; + result.rectangle0Parameter[1 - i1] = + srResult.segmentParameter; + result.rectangle1Parameter[0] = + srResult.rectangleParameter[0]; + result.rectangle1Parameter[1] = + srResult.rectangleParameter[1]; + result.closestPoint[0] = srResult.closestPoint[0]; + result.closestPoint[1] = srResult.closestPoint[1]; + } + } + } + + // Compare edges of rectangle1 to the interior of rectangle0. + for (int i1 = 0; i1 < 2; ++i1) + { + for (int i0 = -1; i0 <= 1; i0 += 2) + { + Real s = i0 * rectangle1.extent[1 - i1]; + Vector3 segCenter = rectangle1.center + + s * rectangle1.axis[1 - i1]; + Segment3 edge(segCenter, rectangle0.axis[i1], + rectangle0.extent[i1]); + + srResult = srQuery(edge, rectangle0); + if (srResult.sqrDistance < result.sqrDistance) + { + result.distance = srResult.distance; + result.sqrDistance = srResult.sqrDistance; + result.rectangle0Parameter[0] = + srResult.rectangleParameter[0]; + result.rectangle0Parameter[1] = + srResult.rectangleParameter[1]; + result.rectangle1Parameter[i1] = s; + result.rectangle1Parameter[1 - i1] = + srResult.segmentParameter; + result.closestPoint[0] = srResult.closestPoint[1]; + result.closestPoint[1] = srResult.closestPoint[0]; + } + } + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistSegment3AlignedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistSegment3AlignedBox3.h new file mode 100644 index 0000000..9e34630 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistSegment3AlignedBox3.h @@ -0,0 +1,74 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, AlignedBox3> + { + public: + struct Result + { + Real distance, sqrDistance; + Real segmentParameter; + Vector3 closestPoint[2]; + }; + + Result operator()(Segment3 const& segment, AlignedBox3 const& box) + { + Result result; + + Vector3 segCenter, segDirection; + Real segExtent; + segment.GetCenteredForm(segCenter, segDirection, segExtent); + + Line3 line(segCenter, segDirection); + DCPQuery, AlignedBox3> lbQuery; + auto lbResult = lbQuery(line, box); + + if (lbResult.lineParameter >= -segExtent) + { + if (lbResult.lineParameter <= segExtent) + { + result.sqrDistance = lbResult.sqrDistance; + result.distance = lbResult.distance; + result.segmentParameter = lbResult.lineParameter; + result.closestPoint[0] = lbResult.closestPoint[0]; + result.closestPoint[1] = lbResult.closestPoint[1]; + } + else + { + DCPQuery, AlignedBox3> pbQuery; + Vector3 point = segCenter + segExtent * segDirection; + auto pbResult = pbQuery(point, box); + result.sqrDistance = pbResult.sqrDistance; + result.distance = pbResult.distance; + result.segmentParameter = segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = pbResult.boxClosest; + } + } + else + { + DCPQuery, AlignedBox3> pbQuery; + Vector3 point = segCenter - segExtent * segDirection; + auto pbResult = pbQuery(point, box); + result.sqrDistance = pbResult.sqrDistance; + result.distance = pbResult.distance; + result.segmentParameter = -segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = pbResult.boxClosest; + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistSegment3OrientedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistSegment3OrientedBox3.h new file mode 100644 index 0000000..01def4a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistSegment3OrientedBox3.h @@ -0,0 +1,76 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, OrientedBox3> + { + public: + struct Result + { + Real distance, sqrDistance; + Real segmentParameter; + Vector3 closestPoint[2]; + }; + + Result operator()(Segment3 const& segment, OrientedBox3 const& box) + { + Result result; + + Vector3 segCenter, segDirection; + Real segExtent; + segment.GetCenteredForm(segCenter, segDirection, segExtent); + + Line3 line(segCenter, segDirection); + DCPQuery, OrientedBox3> lbQuery; + auto lbResult = lbQuery(line, box); + + if (lbResult.lineParameter >= -segExtent) + { + if (lbResult.lineParameter <= segExtent) + { + result.sqrDistance = lbResult.sqrDistance; + result.distance = lbResult.distance; + result.segmentParameter = lbResult.lineParameter; + result.closestPoint[0] = lbResult.closestPoint[0]; + result.closestPoint[1] = lbResult.closestPoint[1]; + } + else + { + DCPQuery, OrientedBox3> pbQuery; + Vector3 point = segCenter + segExtent * segDirection; + auto pbResult = pbQuery(point, box); + result.sqrDistance = pbResult.sqrDistance; + result.distance = pbResult.distance; + result.segmentParameter = segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = pbResult.boxClosest; + } + } + else + { + DCPQuery, OrientedBox3> pbQuery; + Vector3 point = segCenter - segExtent * segDirection; + auto pbResult = pbQuery(point, box); + result.sqrDistance = pbResult.sqrDistance; + result.distance = pbResult.distance; + result.segmentParameter = segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = pbResult.boxClosest; + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistSegment3Rectangle3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistSegment3Rectangle3.h new file mode 100644 index 0000000..0724a69 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistSegment3Rectangle3.h @@ -0,0 +1,77 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, Rectangle3> + { + public: + struct Result + { + Real distance, sqrDistance; + Real segmentParameter, rectangleParameter[2]; + Vector3 closestPoint[2]; + }; + + Result operator()(Segment3 const& segment, Rectangle3 const& rectangle) + { + Result result; + + Vector3 segCenter, segDirection; + Real segExtent; + segment.GetCenteredForm(segCenter, segDirection, segExtent); + + Line3 line(segCenter, segDirection); + DCPQuery, Rectangle3> lrQuery; + auto lrResult = lrQuery(line, rectangle); + + if (lrResult.lineParameter >= -segExtent) + { + if (lrResult.lineParameter <= segExtent) + { + result.distance = lrResult.distance; + result.sqrDistance = lrResult.sqrDistance; + result.segmentParameter = lrResult.lineParameter; + result.rectangleParameter[0] = lrResult.rectangleParameter[0]; + result.rectangleParameter[1] = lrResult.rectangleParameter[1]; + result.closestPoint[0] = lrResult.closestPoint[0]; + result.closestPoint[1] = lrResult.closestPoint[1]; + } + else + { + DCPQuery, Rectangle3> prQuery; + Vector3 point = segCenter + segExtent * segDirection; + auto prResult = prQuery(point, rectangle); + result.sqrDistance = prResult.sqrDistance; + result.distance = prResult.distance; + result.segmentParameter = segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = prResult.rectangleClosestPoint; + } + } + else + { + DCPQuery, Rectangle3> prQuery; + Vector3 point = segCenter - segExtent * segDirection; + auto prResult = prQuery(point, rectangle); + result.sqrDistance = prResult.sqrDistance; + result.distance = prResult.distance; + result.segmentParameter = segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = prResult.rectangleClosestPoint; + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistSegment3Triangle3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistSegment3Triangle3.h new file mode 100644 index 0000000..31a2ed0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistSegment3Triangle3.h @@ -0,0 +1,78 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, Triangle3> + { + public: + struct Result + { + Real distance, sqrDistance; + Real segmentParameter, triangleParameter[3]; + Vector3 closestPoint[2]; + }; + + Result operator()(Segment3 const& segment, Triangle3 const& triangle) + { + Result result; + + Vector3 segCenter, segDirection; + Real segExtent; + segment.GetCenteredForm(segCenter, segDirection, segExtent); + + Line3 line(segCenter, segDirection); + DCPQuery, Triangle3> ltQuery; + auto ltResult = ltQuery(line, triangle); + + if (ltResult.lineParameter >= -segExtent) + { + if (ltResult.lineParameter <= segExtent) + { + result.distance = ltResult.distance; + result.sqrDistance = ltResult.sqrDistance; + result.segmentParameter = ltResult.lineParameter; + result.triangleParameter[0] = ltResult.triangleParameter[0]; + result.triangleParameter[1] = ltResult.triangleParameter[1]; + result.triangleParameter[2] = ltResult.triangleParameter[2]; + result.closestPoint[0] = ltResult.closestPoint[0]; + result.closestPoint[1] = ltResult.closestPoint[1]; + } + else + { + DCPQuery, Triangle3> ptQuery; + Vector3 point = segCenter + segExtent * segDirection; + auto ptResult = ptQuery(point, triangle); + result.sqrDistance = ptResult.sqrDistance; + result.distance = ptResult.distance; + result.segmentParameter = segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = ptResult.closest; + } + } + else + { + DCPQuery, Triangle3> ptQuery; + Vector3 point = segCenter - segExtent * segDirection; + auto ptResult = ptQuery(point, triangle); + result.sqrDistance = ptResult.sqrDistance; + result.distance = ptResult.distance; + result.segmentParameter = segExtent; + result.closestPoint[0] = point; + result.closestPoint[1] = ptResult.closest; + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistSegmentSegment.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistSegmentSegment.h new file mode 100644 index 0000000..8e496d5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistSegmentSegment.h @@ -0,0 +1,408 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// Compute the closest points on the line segments P(s) = (1-s)*P0 + s*P1 and +// Q(t) = (1-t)*Q0 + t*Q1 for 0 <= s <= 1 and 0 <= t <= 1. The algorithm is +// robust even for nearly parallel segments. Effectively, it uses a conjugate +// gradient search for the minimum of the squared distance function, which +// avoids the numerical problems introduced by divisions in the case the +// minimum is located at an interior point of the domain. See the document +// https://www.geometrictools.com/Documentation/DistanceLine3Line3.pdf +// for details. + +namespace WwiseGTE +{ + template + class DCPQuery, Segment> + { + public: + struct Result + { + Real distance, sqrDistance; + Real parameter[2]; + Vector closest[2]; + }; + + Result operator()(Segment const& segment0, + Segment const& segment1) + { + return operator()(segment0.p[0], segment0.p[1], segment1.p[0], segment1.p[1]); + } + + Result operator()(Vector const& P0, Vector const& P1, + Vector const& Q0, Vector const& Q1) + { + Result result; + + // The code allows degenerate line segments; that is, P0 and P1 + // can be the same point or Q0 and Q1 can be the same point. The + // quadratic function for squared distance between the segment is + // R(s,t) = a*s^2 - 2*b*s*t + c*t^2 + 2*d*s - 2*e*t + f + // for (s,t) in [0,1]^2 where + // a = Dot(P1-P0,P1-P0), b = Dot(P1-P0,Q1-Q0), c = Dot(Q1-Q0,Q1-Q0), + // d = Dot(P1-P0,P0-Q0), e = Dot(Q1-Q0,P0-Q0), f = Dot(P0-Q0,P0-Q0) + Vector P1mP0 = P1 - P0; + Vector Q1mQ0 = Q1 - Q0; + Vector P0mQ0 = P0 - Q0; + mA = Dot(P1mP0, P1mP0); + mB = Dot(P1mP0, Q1mQ0); + mC = Dot(Q1mQ0, Q1mQ0); + mD = Dot(P1mP0, P0mQ0); + mE = Dot(Q1mQ0, P0mQ0); + + mF00 = mD; + mF10 = mF00 + mA; + mF01 = mF00 - mB; + mF11 = mF10 - mB; + + mG00 = -mE; + mG10 = mG00 - mB; + mG01 = mG00 + mC; + mG11 = mG10 + mC; + + if (mA > (Real)0 && mC > (Real)0) + { + // Compute the solutions to dR/ds(s0,0) = 0 and + // dR/ds(s1,1) = 0. The location of sI on the s-axis is + // stored in classifyI (I = 0 or 1). If sI <= 0, classifyI + // is -1. If sI >= 1, classifyI is 1. If 0 < sI < 1, + // classifyI is 0. This information helps determine where to + // search for the minimum point (s,t). The fij values are + // dR/ds(i,j) for i and j in {0,1}. + + Real sValue[2]; + sValue[0] = GetClampedRoot(mA, mF00, mF10); + sValue[1] = GetClampedRoot(mA, mF01, mF11); + + int classify[2]; + for (int i = 0; i < 2; ++i) + { + if (sValue[i] <= (Real)0) + { + classify[i] = -1; + } + else if (sValue[i] >= (Real)1) + { + classify[i] = +1; + } + else + { + classify[i] = 0; + } + } + + if (classify[0] == -1 && classify[1] == -1) + { + // The minimum must occur on s = 0 for 0 <= t <= 1. + result.parameter[0] = (Real)0; + result.parameter[1] = GetClampedRoot(mC, mG00, mG01); + } + else if (classify[0] == +1 && classify[1] == +1) + { + // The minimum must occur on s = 1 for 0 <= t <= 1. + result.parameter[0] = (Real)1; + result.parameter[1] = GetClampedRoot(mC, mG10, mG11); + } + else + { + // The line dR/ds = 0 intersects the domain [0,1]^2 in a + // nondegenerate segment. Compute the endpoints of that + // segment, end[0] and end[1]. The edge[i] flag tells you + // on which domain edge end[i] lives: 0 (s=0), 1 (s=1), + // 2 (t=0), 3 (t=1). + int edge[2]; + Real end[2][2]; + ComputeIntersection(sValue, classify, edge, end); + + // The directional derivative of R along the segment of + // intersection is + // H(z) = (end[1][1]-end[1][0]) * + // dR/dt((1-z)*end[0] + z*end[1]) + // for z in [0,1]. The formula uses the fact that + // dR/ds = 0 on the segment. Compute the minimum of + // H on [0,1]. + ComputeMinimumParameters(edge, end, result.parameter); + } + } + else + { + if (mA > (Real)0) + { + // The Q-segment is degenerate (Q0 and Q1 are the same + // point) and the quadratic is R(s,0) = a*s^2 + 2*d*s + f + // and has (half) first derivative F(t) = a*s + d. The + // closest P-point is interior to the P-segment when + // F(0) < 0 and F(1) > 0. + result.parameter[0] = GetClampedRoot(mA, mF00, mF10); + result.parameter[1] = (Real)0; + } + else if (mC > (Real)0) + { + // The P-segment is degenerate (P0 and P1 are the same + // point) and the quadratic is R(0,t) = c*t^2 - 2*e*t + f + // and has (half) first derivative G(t) = c*t - e. The + // closest Q-point is interior to the Q-segment when + // G(0) < 0 and G(1) > 0. + result.parameter[0] = (Real)0; + result.parameter[1] = GetClampedRoot(mC, mG00, mG01); + } + else + { + // P-segment and Q-segment are degenerate. + result.parameter[0] = (Real)0; + result.parameter[1] = (Real)0; + } + } + + + result.closest[0] = + ((Real)1 - result.parameter[0]) * P0 + result.parameter[0] * P1; + result.closest[1] = + ((Real)1 - result.parameter[1]) * Q0 + result.parameter[1] * Q1; + Vector diff = result.closest[0] - result.closest[1]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + return result; + } + + private: + // Compute the root of h(z) = h0 + slope*z and clamp it to the interval + // [0,1]. It is required that for h1 = h(1), either (h0 < 0 and h1 > 0) + // or (h0 > 0 and h1 < 0). + Real GetClampedRoot(Real slope, Real h0, Real h1) + { + // Theoretically, r is in (0,1). However, when the slope is + // nearly zero, then so are h0 and h1. Significant numerical + // rounding problems can occur when using floating-point + // arithmetic. If the rounding causes r to be outside the + // interval, clamp it. It is possible that r is in (0,1) and has + // rounding errors, but because h0 and h1 are both nearly zero, + // the quadratic is nearly constant on (0,1). Any choice of p + // should not cause undesirable accuracy problems for the final + // distance computation. + // + // NOTE: You can use bisection to recompute the root or even use + // bisection to compute the root and skip the division. This is + // generally slower, which might be a problem for high-performance + // applications. + + Real r; + if (h0 < (Real)0) + { + if (h1 > (Real)0) + { + r = -h0 / slope; + if (r > (Real)1) + { + r = (Real)0.5; + } + // The slope is positive and -h0 is positive, so there is + // no need to test for a negative value and clamp it. + } + else + { + r = (Real)1; + } + } + else + { + r = (Real)0; + } + return r; + } + + // Compute the intersection of the line dR/ds = 0 with the domain + // [0,1]^2. The direction of the line dR/ds is conjugate to (1,0), + // so the algorithm for minimization is effectively the conjugate + // gradient algorithm for a quadratic function. + void ComputeIntersection(Real const sValue[2], int const classify[2], + int edge[2], Real end[2][2]) + { + // The divisions are theoretically numbers in [0,1]. Numerical + // rounding errors might cause the result to be outside the + // interval. When this happens, it must be that both numerator + // and denominator are nearly zero. The denominator is nearly + // zero when the segments are nearly perpendicular. The + // numerator is nearly zero when the P-segment is nearly + // degenerate (mF00 = a is small). The choice of 0.5 should not + // cause significant accuracy problems. + // + // NOTE: You can use bisection to recompute the root or even use + // bisection to compute the root and skip the division. This is + // generally slower, which might be a problem for high-performance + // applications. + + if (classify[0] < 0) + { + edge[0] = 0; + end[0][0] = (Real)0; + end[0][1] = mF00 / mB; + if (end[0][1] < (Real)0 || end[0][1] > (Real)1) + { + end[0][1] = (Real)0.5; + } + + if (classify[1] == 0) + { + edge[1] = 3; + end[1][0] = sValue[1]; + end[1][1] = (Real)1; + } + else // classify[1] > 0 + { + edge[1] = 1; + end[1][0] = (Real)1; + end[1][1] = mF10 / mB; + if (end[1][1] < (Real)0 || end[1][1] > (Real)1) + { + end[1][1] = (Real)0.5; + } + } + } + else if (classify[0] == 0) + { + edge[0] = 2; + end[0][0] = sValue[0]; + end[0][1] = (Real)0; + + if (classify[1] < 0) + { + edge[1] = 0; + end[1][0] = (Real)0; + end[1][1] = mF00 / mB; + if (end[1][1] < (Real)0 || end[1][1] > (Real)1) + { + end[1][1] = (Real)0.5; + } + } + else if (classify[1] == 0) + { + edge[1] = 3; + end[1][0] = sValue[1]; + end[1][1] = (Real)1; + } + else + { + edge[1] = 1; + end[1][0] = (Real)1; + end[1][1] = mF10 / mB; + if (end[1][1] < (Real)0 || end[1][1] > (Real)1) + { + end[1][1] = (Real)0.5; + } + } + } + else // classify[0] > 0 + { + edge[0] = 1; + end[0][0] = (Real)1; + end[0][1] = mF10 / mB; + if (end[0][1] < (Real)0 || end[0][1] > (Real)1) + { + end[0][1] = (Real)0.5; + } + + if (classify[1] == 0) + { + edge[1] = 3; + end[1][0] = sValue[1]; + end[1][1] = (Real)1; + } + else + { + edge[1] = 0; + end[1][0] = (Real)0; + end[1][1] = mF00 / mB; + if (end[1][1] < (Real)0 || end[1][1] > (Real)1) + { + end[1][1] = (Real)0.5; + } + } + } + } + + // Compute the location of the minimum of R on the segment of + // intersection for the line dR/ds = 0 and the domain [0,1]^2. + void ComputeMinimumParameters(int const edge[2], Real const end[2][2], + Real parameter[2]) + { + Real delta = end[1][1] - end[0][1]; + Real h0 = delta * (-mB * end[0][0] + mC * end[0][1] - mE); + if (h0 >= (Real)0) + { + if (edge[0] == 0) + { + parameter[0] = (Real)0; + parameter[1] = GetClampedRoot(mC, mG00, mG01); + } + else if (edge[0] == 1) + { + parameter[0] = (Real)1; + parameter[1] = GetClampedRoot(mC, mG10, mG11); + } + else + { + parameter[0] = end[0][0]; + parameter[1] = end[0][1]; + } + } + else + { + Real h1 = delta * (-mB * end[1][0] + mC * end[1][1] - mE); + if (h1 <= (Real)0) + { + if (edge[1] == 0) + { + parameter[0] = (Real)0; + parameter[1] = GetClampedRoot(mC, mG00, mG01); + } + else if (edge[1] == 1) + { + parameter[0] = (Real)1; + parameter[1] = GetClampedRoot(mC, mG10, mG11); + } + else + { + parameter[0] = end[1][0]; + parameter[1] = end[1][1]; + } + } + else // h0 < 0 and h1 > 0 + { + Real z = std::min(std::max(h0 / (h0 - h1), (Real)0), (Real)1); + Real omz = (Real)1 - z; + parameter[0] = omz * end[0][0] + z * end[1][0]; + parameter[1] = omz * end[0][1] + z * end[1][1]; + } + } + } + + // The coefficients of R(s,t), not including the constant term. + Real mA, mB, mC, mD, mE; + + // dR/ds(i,j) at the four corners of the domain + Real mF00, mF10, mF01, mF11; + + // dR/dt(i,j) at the four corners of the domain + Real mG00, mG10, mG01, mG11; + }; + + // Template aliases for convenience. + template + using DCPSegmentSegment = DCPQuery, Segment>; + + template + using DCPSegment2Segment2 = DCPSegmentSegment<2, Real>; + + template + using DCPSegment3Segment3 = DCPSegmentSegment<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistSegmentSegmentExact.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistSegmentSegmentExact.h new file mode 100644 index 0000000..238ceb1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistSegmentSegmentExact.h @@ -0,0 +1,276 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// Compute the closest points on the line segments P(s) = (1-s)*P0 + s*P1 and +// Q(t) = (1-t)*Q0 + t*Q1 for 0 <= s <= 1 and 0 <= t <= 1. The algorithm +// relies on exact rational arithmetic. See the document +// https://www.geometrictools.com/Documentation/DistanceLine3Line3.pdf +// for details. + +namespace WwiseGTE +{ + template + class DistanceSegmentSegmentExact + { + public: + struct Result + { + Rational sqrDistance; + Rational parameter[2]; + Vector closest[2]; + }; + + Result operator()(Segment const& segment0, + Segment const& segment1) + { + return operator()(segment0.p[0], segment0.p[1], segment1.p[0], segment1.p[1]); + } + + Result operator()( + Vector const& P0, Vector const& P1, + Vector const& Q0, Vector const& Q1) + { + Vector P1mP0 = P1 - P0; + Vector Q1mQ0 = Q1 - Q0; + Vector P0mQ0 = P0 - Q0; + Rational a = Dot(P1mP0, P1mP0); + Rational b = Dot(P1mP0, Q1mQ0); + Rational c = Dot(Q1mQ0, Q1mQ0); + Rational d = Dot(P1mP0, P0mQ0); + Rational e = Dot(Q1mQ0, P0mQ0); + Rational const zero = (Rational)0; + Rational const one = (Rational)1; + Rational det = a * c - b * b; + Rational s, t, nd, bmd, bte, ctd, bpe, ate, btd; + + if (det > zero) + { + bte = b * e; + ctd = c * d; + if (bte <= ctd) // s <= 0 + { + s = zero; + if (e <= zero) // t <= 0 + { + // region 6 + t = zero; + nd = -d; + if (nd >= a) + { + s = one; + } + else if (nd > zero) + { + s = nd / a; + } + // else: s is already zero + } + else if (e < c) // 0 < t < 1 + { + // region 5 + t = e / c; + } + else // t >= 1 + { + // region 4 + t = one; + bmd = b - d; + if (bmd >= a) + { + s = one; + } + else if (bmd > zero) + { + s = bmd / a; + } + // else: s is already zero + } + } + else // s > 0 + { + s = bte - ctd; + if (s >= det) // s >= 1 + { + // s = 1 + s = one; + bpe = b + e; + if (bpe <= zero) // t <= 0 + { + // region 8 + t = zero; + nd = -d; + if (nd <= zero) + { + s = zero; + } + else if (nd < a) + { + s = nd / a; + } + // else: s is already one + } + else if (bpe < c) // 0 < t < 1 + { + // region 1 + t = bpe / c; + } + else // t >= 1 + { + // region 2 + t = one; + bmd = b - d; + if (bmd <= zero) + { + s = zero; + } + else if (bmd < a) + { + s = bmd / a; + } + // else: s is already one + } + } + else // 0 < s < 1 + { + ate = a * e; + btd = b * d; + if (ate <= btd) // t <= 0 + { + // region 7 + t = zero; + nd = -d; + if (nd <= zero) + { + s = zero; + } + else if (nd >= a) + { + s = one; + } + else + { + s = nd / a; + } + } + else // t > 0 + { + t = ate - btd; + if (t >= det) // t >= 1 + { + // region 3 + t = one; + bmd = b - d; + if (bmd <= zero) + { + s = zero; + } + else if (bmd >= a) + { + s = one; + } + else + { + s = bmd / a; + } + } + else // 0 < t < 1 + { + // region 0 + s /= det; + t /= det; + } + } + } + } + } + else + { + // The segments are parallel. The quadratic factors to + // R(s,t) = a*(s-(b/a)*t)^2 + 2*d*(s - (b/a)*t) + f + // where a*c = b^2, e = b*d/a, f = |P0-Q0|^2, and b is not + // zero. R is constant along lines of the form s-(b/a)*t = k + // and its occurs on the line a*s - b*t + d = 0. This line + // must intersect both the s-axis and the t-axis because 'a' + // and 'b' are not zero. Because of parallelism, the line is + // also represented by -b*s + c*t - e = 0. + // + // The code determines an edge of the domain [0,1]^2 that + // intersects the minimum line, or if none of the edges + // intersect, it determines the closest corner to the minimum + // line. The conditionals are designed to test first for + // intersection with the t-axis (s = 0) using + // -b*s + c*t - e = 0 and then with the s-axis (t = 0) using + // a*s - b*t + d = 0. + + // When s = 0, solve c*t - e = 0 (t = e/c). + if (e <= zero) // t <= 0 + { + // Now solve a*s - b*t + d = 0 for t = 0 (s = -d/a). + t = zero; + nd = -d; + if (nd <= zero) // s <= 0 + { + // region 6 + s = zero; + } + else if (nd >= a) // s >= 1 + { + // region 8 + s = one; + } + else // 0 < s < 1 + { + // region 7 + s = nd / a; + } + } + else if (e >= c) // t >= 1 + { + // Now solve a*s - b*t + d = 0 for t = 1 (s = (b-d)/a). + t = one; + bmd = b - d; + if (bmd <= zero) // s <= 0 + { + // region 4 + s = zero; + } + else if (bmd >= a) // s >= 1 + { + // region 2 + s = one; + } + else // 0 < s < 1 + { + // region 3 + s = bmd / a; + } + } + else // 0 < t < 1 + { + // The point (0,e/c) is on the line and domain, so we have + // one point at which R is a minimum. + s = zero; + t = e / c; + } + } + + Result result; + result.parameter[0] = s; + result.parameter[1] = t; + result.closest[0] = P0 + s * P1mP0; + result.closest[1] = Q0 + t * Q1mQ0; + Vector diff = result.closest[1] - result.closest[0]; + result.sqrDistance = Dot(diff, diff); + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistTriangle3AlignedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistTriangle3AlignedBox3.h new file mode 100644 index 0000000..63f2027 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistTriangle3AlignedBox3.h @@ -0,0 +1,131 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// Compute the distance between a triangle and an aligned box in 3D. The +// algorithm is based on using an LCP solver for the convex quadratic +// programming problem. For details, see +// https://www.geometrictools.com/Documentation/ConvexQuadraticProgramming.pdf + +namespace WwiseGTE +{ + template + class DCPQuery, AlignedBox3> + { + public: + struct Result + { + bool queryIsSuccessful; + + // These members are valid only when queryIsSuccessful is true; + // otherwise, they are all set to zero. + Real distance, sqrDistance; + std::array triangleParameter, boxParameter; + Vector3 closestPoint[2]; + + // The number of iterations used by LCPSolver regardless of + // whether the query is successful. + int numLCPIterations; + }; + + // The default maximum iterations is 81 (n = 9, maxIterations = n*n). + // If the solver fails to converge, try increasing the maximum number + // of iterations. + void SetMaxLCPIterations(int maxLCPIterations) + { + mLCP.SetMaxIterations(maxLCPIterations); + } + + Result operator()(Triangle3 const& triangle, AlignedBox3 const& box) + { + Result result; + + // Translate the triangle and aligned box so that the aligned box + // becomes a canonical box. + Vector3 K = box.max - box.min; + Vector3 V = triangle.v[0] - box.min; + Vector3 E0 = triangle.v[1] - triangle.v[0]; + Vector3 E1 = triangle.v[2] - triangle.v[0]; + + // Compute quantities to initialize q and M in the LCP. + Real dotVE0 = Dot(V, E0); + Real dotVE1 = Dot(V, E1); + Real dotE0E0 = Dot(E0, E0); + Real dotE0E1 = Dot(E0, E1); + Real dotE1E1 = Dot(E1, E1); + + // The LCP has 5 variables and 4 (nontrivial) inequality + // constraints. + std::array q = + { + -V[0], -V[1], -V[2], dotVE0, dotVE1, K[0], K[1], K[2], (Real)1 + }; + + std::array, 9> M; + M[0] = { (Real)1, (Real)0, (Real)0, -E0[0], -E1[0], (Real)1, (Real)0, (Real)0, (Real)0 }; + M[1] = { (Real)0, (Real)1, (Real)0, -E0[1], -E1[1], (Real)0, (Real)1, (Real)0, (Real)0 }; + M[2] = { (Real)0, (Real)0, (Real)1, -E0[2], -E1[2], (Real)0, (Real)0, (Real)1, (Real)0 }; + M[3] = { -E0[0], -E0[1], -E0[2], dotE0E0, dotE0E1, (Real)0, (Real)0, (Real)0, (Real)1 }; + M[4] = { -E1[0], -E1[1], -E1[2], dotE0E1, dotE1E1, (Real)0, (Real)0, (Real)0, (Real)1 }; + M[5] = { (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[6] = { (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[7] = { (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[8] = { (Real)0, (Real)0, (Real)0, (Real)-1, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0 }; + + std::array w, z; + if (mLCP.Solve(q, M, w, z)) + { + result.queryIsSuccessful = true; + + result.triangleParameter[0] = (Real)1 - z[3] - z[4]; + result.triangleParameter[1] = z[3]; + result.triangleParameter[2] = z[4]; + result.closestPoint[0] = triangle.v[0] + z[3] * E0 + z[4] * E1; + for (int i = 0; i < 3; ++i) + { + result.boxParameter[i] = z[i] + box.min[i]; + result.closestPoint[1][i] = result.boxParameter[i]; + } + + Vector3 diff = result.closestPoint[1] - result.closestPoint[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + } + else + { + // If you reach this case, the maximum number of iterations + // was not specified to be large enough or there is a problem + // due to floating-point rounding errors. If you believe the + // latter is true, file a bug report. + result.queryIsSuccessful = false; + + for (int i = 0; i < 3; ++i) + { + result.triangleParameter[i] = (Real)0; + result.boxParameter[i] = (Real)0; + result.closestPoint[0][i] = (Real)0; + result.closestPoint[1][i] = (Real)0; + } + result.distance = (Real)0; + result.sqrDistance = (Real)0; + } + + result.numLCPIterations = mLCP.GetNumIterations(); + return result; + } + + private: + LCPSolver mLCP; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistTriangle3OrientedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistTriangle3OrientedBox3.h new file mode 100644 index 0000000..687facd --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistTriangle3OrientedBox3.h @@ -0,0 +1,139 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// Compute the distance between a triangle and an oriented box in 3D. The +// algorithm is based on using an LCP solver for the convex quadratic +// programming problem. For details, see +// https://www.geometrictools.com/Documentation/ConvexQuadraticProgramming.pdf + +namespace WwiseGTE +{ + template + class DCPQuery, OrientedBox3> + { + public: + struct Result + { + bool queryIsSuccessful; + + // These members are valid only when queryIsSuccessful is true; + // otherwise, they are all set to zero. + Real distance, sqrDistance; + std::array triangleParameter, boxParameter; + Vector3 closestPoint[2]; + + // The number of iterations used by LCPSolver regardless of + // whether the query is successful. + int numLCPIterations; + }; + + // The default maximum iterations is 81 (n = 9, maxIterations = n*n). + // If the solver fails to converge, try increasing the maximum number + // of iterations. + void SetMaxLCPIterations(int maxLCPIterations) + { + mLCP.SetMaxIterations(maxLCPIterations); + } + + Result operator()(Triangle3 const& triangle, OrientedBox3 const& box) + { + Result result; + + // Rigidly transform the triangle and oriented box so that the + // oriented box becomes a canonical box. + Vector3 K = box.extent * (Real)2; + Vector3 tempV = triangle.v[0] - box.center; + Vector3 tempE0 = triangle.v[1] - triangle.v[0]; + Vector3 tempE1 = triangle.v[2] - triangle.v[0]; + Vector3 V, E0, E1; + for (int i = 0; i < 3; ++i) + { + V[i] = Dot(box.axis[i], tempV) + box.extent[i]; + E0[i] = Dot(box.axis[i], tempE0); + E1[i] = Dot(box.axis[i], tempE1); + } + + // Compute quantities to initialize q and M in the LCP. + Real dotVE0 = Dot(V, E0); + Real dotVE1 = Dot(V, E1); + Real dotE0E0 = Dot(E0, E0); + Real dotE0E1 = Dot(E0, E1); + Real dotE1E1 = Dot(E1, E1); + + // The LCP has 5 variables and 4 (nontrivial) inequality + // constraints. + std::array q = + { + -V[0], -V[1], -V[2], dotVE0, dotVE1, K[0], K[1], K[2], (Real)1 + }; + + std::array, 9> M; + M[0] = { (Real)1, (Real)0, (Real)0, -E0[0], -E1[0], (Real)1, (Real)0, (Real)0, (Real)0 }; + M[1] = { (Real)0, (Real)1, (Real)0, -E0[1], -E1[1], (Real)0, (Real)1, (Real)0, (Real)0 }; + M[2] = { (Real)0, (Real)0, (Real)1, -E0[2], -E1[2], (Real)0, (Real)0, (Real)1, (Real)0 }; + M[3] = { -E0[0], -E0[1], -E0[2], dotE0E0, dotE0E1, (Real)0, (Real)0, (Real)0, (Real)1 }; + M[4] = { -E1[0], -E1[1], -E1[2], dotE0E1, dotE1E1, (Real)0, (Real)0, (Real)0, (Real)1 }; + M[5] = { (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[6] = { (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[7] = { (Real)0, (Real)0, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0, (Real)0 }; + M[8] = { (Real)0, (Real)0, (Real)0, (Real)-1, (Real)-1, (Real)0, (Real)0, (Real)0, (Real)0 }; + + std::array w, z; + if (mLCP.Solve(q, M, w, z)) + { + result.queryIsSuccessful = true; + + result.triangleParameter[0] = (Real)1 - z[3] - z[4]; + result.triangleParameter[1] = z[3]; + result.triangleParameter[2] = z[4]; + result.closestPoint[0] = triangle.v[0] + z[3] * tempE0 + z[4] * tempE1; + result.closestPoint[1] = box.center; + for (int i = 0; i < 3; ++i) + { + result.boxParameter[i] = z[i] - box.extent[i]; + result.closestPoint[1] += result.boxParameter[i] * box.axis[i]; + } + + Vector3 diff = result.closestPoint[1] - result.closestPoint[0]; + result.sqrDistance = Dot(diff, diff); + result.distance = std::sqrt(result.sqrDistance); + } + else + { + // If you reach this case, the maximum number of iterations + // was not specified to be large enough or there is a problem + // due to floating-point rounding errors. If you believe the + // latter is true, file a bug report. + result.queryIsSuccessful = false; + + for (int i = 0; i < 3; ++i) + { + result.triangleParameter[i] = (Real)0; + result.boxParameter[i] = (Real)0; + result.closestPoint[0][i] = (Real)0; + result.closestPoint[1][i] = (Real)0; + } + result.distance = (Real)0; + result.sqrDistance = (Real)0; + } + + result.numLCPIterations = mLCP.GetNumIterations(); + return result; + } + + private: + LCPSolver mLCP; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistTriangle3Rectangle3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistTriangle3Rectangle3.h new file mode 100644 index 0000000..2a3b53d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistTriangle3Rectangle3.h @@ -0,0 +1,86 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class DCPQuery, Rectangle3> + { + public: + struct Result + { + Real distance, sqrDistance; + Real triangleParameter[3], rectangleParameter[2]; + Vector3 closestPoint[2]; + }; + + Result operator()(Triangle3 const& triangle, Rectangle3 const& rectangle) + { + Result result; + + result.sqrDistance = std::numeric_limits::max(); + + // Compare edges of triangle to the interior of rectangle. + for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + Vector3 segCenter = (Real)0.5 * (triangle.v[i0] + triangle.v[i1]); + Vector3 segDirection = triangle.v[i1] - triangle.v[i0]; + Real segExtent = (Real)0.5 * Normalize(segDirection); + Segment3 edge(segCenter, segDirection, segExtent); + + DCPQuery, Rectangle3> srQuery; + auto srResult = srQuery(edge, rectangle); + if (srResult.sqrDistance < result.sqrDistance) + { + result.distance = srResult.distance; + result.sqrDistance = srResult.sqrDistance; + // ratio is in [-1,1] + Real ratio = srResult.segmentParameter / segExtent; + result.triangleParameter[i0] = (Real)0.5 * ((Real)1 - ratio); + result.triangleParameter[i1] = (Real)1 - result.triangleParameter[i0]; + result.triangleParameter[3 - i0 - i1] = (Real)0; + result.rectangleParameter[0] = srResult.rectangleParameter[0]; + result.rectangleParameter[1] = srResult.rectangleParameter[1]; + result.closestPoint[0] = srResult.closestPoint[0]; + result.closestPoint[1] = srResult.closestPoint[1]; + } + } + + // Compare edges of rectangle to the interior of triangle. + for (int i1 = 0; i1 < 2; ++i1) + { + for (int i0 = -1; i0 <= 1; i0 += 2) + { + Real s = i0 * rectangle.extent[1 - i1]; + Vector3 segCenter = rectangle.center + s * rectangle.axis[1 - i1]; + Segment3 edge(segCenter, rectangle.axis[i1], rectangle.extent[i1]); + + DCPQuery, Triangle3> stQuery; + auto stResult = stQuery(edge, triangle); + if (stResult.sqrDistance < result.sqrDistance) + { + result.distance = stResult.distance; + result.sqrDistance = stResult.sqrDistance; + result.triangleParameter[0] = stResult.triangleParameter[0]; + result.triangleParameter[1] = stResult.triangleParameter[1]; + result.triangleParameter[2] = stResult.triangleParameter[2]; + result.rectangleParameter[i1] = s; + result.rectangleParameter[1 - i1] = stResult.segmentParameter; + result.closestPoint[0] = stResult.closestPoint[1]; + result.closestPoint[1] = stResult.closestPoint[0]; + } + } + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistTriangle3Triangle3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistTriangle3Triangle3.h new file mode 100644 index 0000000..260bd4b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/DistTriangle3Triangle3.h @@ -0,0 +1,87 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + template + class DCPQuery, Triangle3> + { + public: + struct Result + { + Real distance, sqrDistance; + Real triangle0Parameter[3], triangle1Parameter[3]; + Vector3 closestPoint[2]; + }; + + Result operator()(Triangle3 const& triangle0, Triangle3 const& triangle1) + { + Result result; + + DCPQuery, Triangle3> stQuery; + typename DCPQuery, Triangle3>::Result + stResult; + result.sqrDistance = std::numeric_limits::max(); + + // Compare edges of triangle0 to the interior of triangle1. + for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + Vector3 segCenter = (Real)0.5 * (triangle0.v[i0] + triangle0.v[i1]); + Vector3 segDirection = triangle0.v[i1] - triangle0.v[i0]; + Real segExtent = (Real)0.5 * Normalize(segDirection); + Segment3 edge(segCenter, segDirection, segExtent); + + stResult = stQuery(edge, triangle1); + if (stResult.sqrDistance < result.sqrDistance) + { + result.distance = stResult.distance; + result.sqrDistance = stResult.sqrDistance; + // ratio is in [-1,1] + Real ratio = stResult.segmentParameter / segExtent; + result.triangle0Parameter[i0] = (Real)0.5 * ((Real)1 - ratio); + result.triangle0Parameter[i1] = (Real)1 - result.triangle0Parameter[i0]; + result.triangle0Parameter[3 - i0 - i1] = (Real)0; + result.triangle1Parameter[0] = stResult.triangleParameter[0]; + result.triangle1Parameter[1] = stResult.triangleParameter[1]; + result.triangle1Parameter[2] = stResult.triangleParameter[2]; + result.closestPoint[0] = stResult.closestPoint[0]; + result.closestPoint[1] = stResult.closestPoint[1]; + } + } + + // Compare edges of triangle1 to the interior of triangle0. + for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + Vector3 segCenter = (Real)0.5 * (triangle1.v[i0] + triangle1.v[i1]); + Vector3 segDirection = triangle1.v[i1] - triangle1.v[i0]; + Real segExtent = (Real)0.5 * Normalize(segDirection); + Segment3 edge(segCenter, segDirection, segExtent); + + stResult = stQuery(edge, triangle0); + if (stResult.sqrDistance < result.sqrDistance) + { + result.distance = stResult.distance; + result.sqrDistance = stResult.sqrDistance; + Real ratio = stResult.segmentParameter / segExtent; // in [-1,1] + result.triangle0Parameter[0] = stResult.triangleParameter[0]; + result.triangle0Parameter[1] = stResult.triangleParameter[1]; + result.triangle0Parameter[2] = stResult.triangleParameter[2]; + result.triangle1Parameter[i0] = (Real)0.5 * ((Real)1 - ratio); + result.triangle1Parameter[i1] = (Real)1 - result.triangle0Parameter[i0]; + result.triangle1Parameter[3 - i0 - i1] = (Real)0; + result.closestPoint[0] = stResult.closestPoint[0]; + result.closestPoint[1] = stResult.closestPoint[1]; + } + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ETManifoldMesh.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ETManifoldMesh.h new file mode 100644 index 0000000..c003b12 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ETManifoldMesh.h @@ -0,0 +1,465 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + class ETManifoldMesh + { + public: + // Edge data types. + class Edge; + typedef std::shared_ptr(*ECreator)(int, int); + typedef std::map, std::shared_ptr> EMap; + + // Triangle data types. + class Triangle; + typedef std::shared_ptr(*TCreator)(int, int, int); + typedef std::map, std::shared_ptr> TMap; + + // Edge object. + class Edge + { + public: + virtual ~Edge() = default; + + Edge(int v0, int v1) + : + V{ v0, v1 } + { + } + + // Vertices of the edge. + std::array V; + + // Triangles sharing the edge. + std::array, 2> T; + }; + + // Triangle object. + class Triangle + { + public: + virtual ~Triangle() = default; + + Triangle(int v0, int v1, int v2) + : + V{ v0, v1, v2 } + { + } + + // Vertices, listed in counterclockwise order (V[0],V[1],V[2]). + int V[3]; + + // Adjacent edges. E[i] points to edge (V[i],V[(i+1)%3]). + std::array, 3> E; + + // Adjacent triangles. T[i] points to the adjacent triangle + // sharing edge E[i]. + std::array, 3> T; + }; + + + // Construction and destruction. + virtual ~ETManifoldMesh() = default; + + ETManifoldMesh(ECreator eCreator = nullptr, TCreator tCreator = nullptr) + : + mECreator(eCreator ? eCreator : CreateEdge), + mTCreator(tCreator ? tCreator : CreateTriangle), + mThrowOnNonmanifoldInsertion(true) + { + } + + // Support for a deep copy of the mesh. The mEMap and mTMap objects + // have dynamically allocated memory for edges and triangles. A + // shallow copy of the pointers to this memory is problematic. + // Allowing sharing, say, via std::shared_ptr, is an option but not + // really the intent of copying the mesh graph. + ETManifoldMesh(ETManifoldMesh const& mesh) + { + *this = mesh; + } + + ETManifoldMesh& operator=(ETManifoldMesh const& mesh) + { + Clear(); + + mECreator = mesh.mECreator; + mTCreator = mesh.mTCreator; + mThrowOnNonmanifoldInsertion = mesh.mThrowOnNonmanifoldInsertion; + for (auto const& element : mesh.mTMap) + { + Insert(element.first.V[0], element.first.V[1], element.first.V[2]); + } + + return *this; + } + + // Member access. + inline EMap const& GetEdges() const + { + return mEMap; + } + + inline TMap const& GetTriangles() const + { + return mTMap; + } + + // If the insertion of a triangle fails because the mesh would become + // nonmanifold, the default behavior is to throw an exception. You + // can disable this behavior and continue gracefully without an + // exception. The return value is the previous value of the internal + // state mAssertOnNonmanifoldInsertion. + bool ThrowOnNonmanifoldInsertion(bool doException) + { + std::swap(doException, mThrowOnNonmanifoldInsertion); + return doException; // return the previous state + } + + // If is not in the mesh, a Triangle object is created and + // returned; otherwise, is in the mesh and nullptr is + // returned. If the insertion leads to a nonmanifold mesh, the call + // fails with a nullptr returned. + virtual std::shared_ptr Insert(int v0, int v1, int v2) + { + TriangleKey tkey(v0, v1, v2); + if (mTMap.find(tkey) != mTMap.end()) + { + // The triangle already exists. Return a null pointer as a + // signal to the caller that the insertion failed. + return nullptr; + } + + // Create the new triangle. It will be added to mTMap at the end + // of the function so that if an assertion is triggered and the + // function returns early, the (bad) triangle will not be part of + // the mesh. + std::shared_ptr tri = mTCreator(v0, v1, v2); + + // Add the edges to the mesh if they do not already exist. + for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + EdgeKey ekey(tri->V[i0], tri->V[i1]); + std::shared_ptr edge; + auto eiter = mEMap.find(ekey); + if (eiter == mEMap.end()) + { + // This is the first time the edge is encountered. + edge = mECreator(tri->V[i0], tri->V[i1]); + mEMap[ekey] = edge; + + // Update the edge and triangle. + edge->T[0] = tri; + tri->E[i0] = edge; + } + else + { + // This is the second time the edge is encountered. + edge = eiter->second; + LogAssert(edge != nullptr, "Unexpected condition."); + + // Update the edge. + if (edge->T[1].lock()) + { + if (mThrowOnNonmanifoldInsertion) + { + LogError("Attempt to create nonmanifold mesh."); + } + else + { + return nullptr; + } + } + edge->T[1] = tri; + + // Update the adjacent triangles. + auto adjacent = edge->T[0].lock(); + LogAssert(adjacent != nullptr, "Unexpected condition."); + for (int j = 0; j < 3; ++j) + { + if (adjacent->E[j].lock() == edge) + { + adjacent->T[j] = tri; + break; + } + } + + // Update the triangle. + tri->E[i0] = edge; + tri->T[i0] = adjacent; + } + } + + mTMap[tkey] = tri; + return tri; + } + + // If is in the mesh, it is removed and 'true' is + // returned; otherwise, is not in the mesh and 'false' is + // returned. + virtual bool Remove(int v0, int v1, int v2) + { + TriangleKey tkey(v0, v1, v2); + auto titer = mTMap.find(tkey); + if (titer == mTMap.end()) + { + // The triangle does not exist. + return false; + } + + // Get the triangle. + std::shared_ptr tri = titer->second; + + // Remove the edges and update adjacent triangles if necessary. + for (int i = 0; i < 3; ++i) + { + // Inform the edges the triangle is being deleted. + auto edge = tri->E[i].lock(); + LogAssert(edge != nullptr, "Unexpected condition."); + + if (edge->T[0].lock() == tri) + { + // One-triangle edges always have pointer at index zero. + edge->T[0] = edge->T[1]; + edge->T[1].reset(); + } + else if (edge->T[1].lock() == tri) + { + edge->T[1].reset(); + } + else + { + LogError("Unexpected condition."); + } + + // Remove the edge if you have the last reference to it. + if (!edge->T[0].lock() && !edge->T[1].lock()) + { + EdgeKey ekey(edge->V[0], edge->V[1]); + mEMap.erase(ekey); + } + + // Inform adjacent triangles the triangle is being deleted. + auto adjacent = tri->T[i].lock(); + if (adjacent) + { + for (int j = 0; j < 3; ++j) + { + if (adjacent->T[j].lock() == tri) + { + adjacent->T[j].reset(); + break; + } + } + } + } + + mTMap.erase(tkey); + return true; + } + + // Destroy the edges and triangles to obtain an empty mesh. + virtual void Clear() + { + mEMap.clear(); + mTMap.clear(); + } + + // A manifold mesh is closed if each edge is shared twice. A closed + // mesh is not necessarily oriented. For example, you could have a + // mesh with spherical topology. The upper hemisphere has outer + // facing normals and the lower hemisphere has inner-facing normals. + // The discontinuity in orientation occurs on the circle shared by the + // hemispheres. + bool IsClosed() const + { + for (auto const& element : mEMap) + { + auto edge = element.second; + if (!edge->T[0].lock() || !edge->T[1].lock()) + { + return false; + } + } + return true; + } + + // Test whether all triangles in the mesh are oriented consistently + // and that no two triangles are coincident. The latter means that + // you cannot have both triangles and in the + // mesh to be considered oriented. + bool IsOriented() const + { + for (auto const& element : mEMap) + { + auto edge = element.second; + if (edge->T[0].lock() && edge->T[1].lock()) + { + // In each triangle, find the ordered edge that + // corresponds to the unordered edge element.first. Also + // find the vertex opposite that edge. + bool edgePositive[2] = { false, false }; + int vOpposite[2] = { -1, -1 }; + for (int j = 0; j < 2; ++j) + { + auto tri = edge->T[j].lock(); + for (int i = 0; i < 3; ++i) + { + if (tri->V[i] == element.first.V[0]) + { + int vNext = tri->V[(i + 1) % 3]; + if (vNext == element.first.V[1]) + { + edgePositive[j] = true; + vOpposite[j] = tri->V[(i + 2) % 3]; + } + else + { + edgePositive[j] = false; + vOpposite[j] = vNext; + } + break; + } + } + } + + // To be oriented consistently, the edges must have + // reversed ordering and the oppositive vertices cannot + // match. + if (edgePositive[0] == edgePositive[1] || vOpposite[0] == vOpposite[1]) + { + return false; + } + } + } + return true; + } + + // Compute the connected components of the edge-triangle graph that + // the mesh represents. The first function returns pointers into + // 'this' object's containers, so you must consume the components + // before clearing or destroying 'this'. The second function returns + // triangle keys, which requires three times as much storage as the + // pointers but allows you to clear or destroy 'this' before consuming + // the components. + void GetComponents(std::vector>>& components) const + { + // visited: 0 (unvisited), 1 (discovered), 2 (finished) + std::map, int> visited; + for (auto const& element : mTMap) + { + visited.insert(std::make_pair(element.second, 0)); + } + + for (auto& element : mTMap) + { + auto tri = element.second; + if (visited[tri] == 0) + { + std::vector> component; + DepthFirstSearch(tri, visited, component); + components.push_back(component); + } + } + } + + void GetComponents(std::vector>>& components) const + { + // visited: 0 (unvisited), 1 (discovered), 2 (finished) + std::map, int> visited; + for (auto const& element : mTMap) + { + visited.insert(std::make_pair(element.second, 0)); + } + + for (auto& element : mTMap) + { + std::shared_ptr tri = element.second; + if (visited[tri] == 0) + { + std::vector> component; + DepthFirstSearch(tri, visited, component); + + std::vector> keyComponent; + keyComponent.reserve(component.size()); + for (auto const& t : component) + { + keyComponent.push_back(TriangleKey(t->V[0], t->V[1], t->V[2])); + } + components.push_back(keyComponent); + } + } + } + + protected: + // The edge data and default edge creation. + static std::shared_ptr CreateEdge(int v0, int v1) + { + return std::make_shared(v0, v1); + } + + ECreator mECreator; + EMap mEMap; + + // The triangle data and default triangle creation. + static std::shared_ptr CreateTriangle(int v0, int v1, int v2) + { + return std::make_shared(v0, v1, v2); + } + + TCreator mTCreator; + TMap mTMap; + bool mThrowOnNonmanifoldInsertion; // default: true + + // Support for computing connected components. This is a + // straightforward depth-first search of the graph but uses a + // preallocated stack rather than a recursive function that could + // possibly overflow the call stack. + void DepthFirstSearch(std::shared_ptr const& tInitial, + std::map, int>& visited, + std::vector>& component) const + { + // Allocate the maximum-size stack that can occur in the + // depth-first search. The stack is empty when the index top + // is -1. + std::vector> tStack(mTMap.size()); + int top = -1; + tStack[++top] = tInitial; + while (top >= 0) + { + std::shared_ptr tri = tStack[top]; + visited[tri] = 1; + int i; + for (i = 0; i < 3; ++i) + { + std::shared_ptr adj = tri->T[i].lock(); + if (adj && visited[adj] == 0) + { + tStack[++top] = adj; + break; + } + } + if (i == 3) + { + visited[tri] = 2; + component.push_back(tri); + --top; + } + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ETNonmanifoldMesh.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ETNonmanifoldMesh.h new file mode 100644 index 0000000..b52a930 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ETNonmanifoldMesh.h @@ -0,0 +1,381 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + class ETNonmanifoldMesh + { + public: + // Edge data types. + class Edge; + typedef std::shared_ptr(*ECreator)(int, int); + typedef std::map, std::shared_ptr> EMap; + + // Triangle data types. + class Triangle; + typedef std::shared_ptr(*TCreator)(int, int, int); + typedef std::map, std::shared_ptr> TMap; + + // Edge object. + class Edge + { + public: + virtual ~Edge() = default; + + Edge(int v0, int v1) + : + V{ v0, v1 } + { + } + + bool operator<(Edge const& other) const + { + return EdgeKey(V[0], V[1]) < EdgeKey(other.V[0], other.V[1]); + } + + // Vertices of the edge. + std::array V; + + // Triangles sharing the edge. + std::set, WeakPtrLT> T; + }; + + // Triangle object. + class Triangle + { + public: + virtual ~Triangle() = default; + + Triangle(int v0, int v1, int v2) + : + V{ v0, v1, v2 } + { + } + + bool operator<(Triangle const& other) const + { + return TriangleKey(V[0], V[1], V[2]) < TriangleKey(other.V[0], other.V[1], other.V[2]); + } + + // Vertices listed in counterclockwise order (V[0],V[1],V[2]). + std::array V; + + // Adjacent edges. E[i] points to edge (V[i],V[(i+1)%3]). + std::array, 3> E; + }; + + + // Construction and destruction. + virtual ~ETNonmanifoldMesh() = default; + + ETNonmanifoldMesh(ECreator eCreator = nullptr, TCreator tCreator = nullptr) + : + mECreator(eCreator ? eCreator : CreateEdge), + mTCreator(tCreator ? tCreator : CreateTriangle) + { + } + + // Support for a deep copy of the mesh. The mEMap and mTMap objects + // have dynamically allocated memory for edges and triangles. A + // shallow copy of the pointers to this memory is problematic. + // Allowing sharing, say, via std::shared_ptr, is an option but not + // really the intent of copying the mesh graph. + ETNonmanifoldMesh(ETNonmanifoldMesh const& mesh) + { + *this = mesh; + } + + ETNonmanifoldMesh& operator=(ETNonmanifoldMesh const& mesh) + { + Clear(); + + mECreator = mesh.mECreator; + mTCreator = mesh.mTCreator; + for (auto const& element : mesh.mTMap) + { + Insert(element.first.V[0], element.first.V[1], element.first.V[2]); + } + + return *this; + } + + // Member access. + inline EMap const& GetEdges() const + { + return mEMap; + } + + inline TMap const& GetTriangles() const + { + return mTMap; + } + + // If is not in the mesh, a Triangle object is created and + // returned; otherwise, is in the mesh and nullptr is + // returned. + virtual std::shared_ptr Insert(int v0, int v1, int v2) + { + TriangleKey tkey(v0, v1, v2); + if (mTMap.find(tkey) != mTMap.end()) + { + // The triangle already exists. Return a null pointer as a + // signal to the caller that the insertion failed. + return nullptr; + } + + // Create the new triangle. It will be added to mTMap at the end + // of the function so that if an assertion is triggered and the + // function returns early, the (bad) triangle will not be part of + // the mesh. + std::shared_ptr tri = mTCreator(v0, v1, v2); + + // Add the edges to the mesh if they do not already exist. + for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + EdgeKey ekey(tri->V[i0], tri->V[i1]); + std::shared_ptr edge; + auto eiter = mEMap.find(ekey); + if (eiter == mEMap.end()) + { + // This is the first time the edge is encountered. + edge = mECreator(tri->V[i0], tri->V[i1]); + mEMap[ekey] = edge; + } + else + { + // The edge was previously encountered and created. + edge = eiter->second; + LogAssert(edge != nullptr, "Unexpected condition."); + } + + // Associate the edge with the triangle. + tri->E[i0] = edge; + + // Update the adjacent set of triangles for the edge. + edge->T.insert(tri); + } + + mTMap[tkey] = tri; + return tri; + } + + // If is in the mesh, it is removed and 'true' is returned; + // otherwise, is not in the mesh and 'false' is returned. + virtual bool Remove(int v0, int v1, int v2) + { + TriangleKey tkey(v0, v1, v2); + auto titer = mTMap.find(tkey); + if (titer == mTMap.end()) + { + // The triangle does not exist. + return false; + } + + // Get the triangle. + std::shared_ptr tri = titer->second; + + // Remove the edges and update adjacent triangles if necessary. + for (int i = 0; i < 3; ++i) + { + // Inform the edges the triangle is being deleted. + auto edge = tri->E[i].lock(); + LogAssert(edge != nullptr, "Unexpected condition."); + + // Remove the triangle from the edge's set of adjacent + // triangles. + size_t numRemoved = edge->T.erase(tri); + LogAssert(numRemoved > 0, "Unexpected condition."); + + // Remove the edge if you have the last reference to it. + if (edge->T.size() == 0) + { + EdgeKey ekey(edge->V[0], edge->V[1]); + mEMap.erase(ekey); + } + } + + // Remove the triangle from the graph. + mTMap.erase(tkey); + return true; + } + + // Destroy the edges and triangles to obtain an empty mesh. + virtual void Clear() + { + mEMap.clear(); + mTMap.clear(); + } + + // A manifold mesh has the property that an edge is shared by at most + // two triangles sharing. + bool IsManifold() const + { + for (auto const& element : mEMap) + { + if (element.second->T.size() > 2) + { + return false; + } + } + return true; + } + + // A manifold mesh is closed if each edge is shared twice. A closed + // mesh is not necessarily oriented. For example, you could have a + // mesh with spherical topology. The upper hemisphere has outer-facing + // normals and the lower hemisphere has inner-facing normals. The + // discontinuity in orientation occurs on the circle shared by the + // hemispheres. + bool IsClosed() const + { + for (auto const& element : mEMap) + { + if (element.second->T.size() != 2) + { + return false; + } + } + return true; + } + + // Compute the connected components of the edge-triangle graph that + // the mesh represents. The first function returns pointers into + // 'this' object's containers, so you must consume the components + // before clearing or destroying 'this'. The second function returns + // triangle keys, which requires three times as much storage as the + // pointers but allows you to clear or destroy 'this' before consuming + // the components. + void GetComponents(std::vector>>& components) const + { + // visited: 0 (unvisited), 1 (discovered), 2 (finished) + std::map, int> visited; + for (auto const& element : mTMap) + { + visited.insert(std::make_pair(element.second, 0)); + } + + for (auto& element : mTMap) + { + auto tri = element.second; + if (visited[tri] == 0) + { + std::vector> component; + DepthFirstSearch(tri, visited, component); + components.push_back(component); + } + } + } + + void GetComponents(std::vector>>& components) const + { + // visited: 0 (unvisited), 1 (discovered), 2 (finished) + std::map, int> visited; + for (auto const& element : mTMap) + { + visited.insert(std::make_pair(element.second, 0)); + } + + for (auto& element : mTMap) + { + std::shared_ptr tri = element.second; + if (visited[tri] == 0) + { + std::vector> component; + DepthFirstSearch(tri, visited, component); + + std::vector> keyComponent; + keyComponent.reserve(component.size()); + for (auto const& t : component) + { + keyComponent.push_back(TriangleKey(t->V[0], t->V[1], t->V[2])); + } + components.push_back(keyComponent); + } + } + } + + protected: + // The edge data and default edge creation. + static std::shared_ptr CreateEdge(int v0, int v1) + { + return std::make_shared(v0, v1); + } + + ECreator mECreator; + EMap mEMap; + + // The triangle data and default triangle creation. + static std::shared_ptr CreateTriangle(int v0, int v1, int v2) + { + return std::make_shared(v0, v1, v2); + } + + TCreator mTCreator; + TMap mTMap; + + // Support for computing connected components. This is a + // straightforward depth-first search of the graph but uses a + // preallocated stack rather than a recursive function that could + // possibly overflow the call stack. + void DepthFirstSearch(std::shared_ptr const& tInitial, + std::map, int>& visited, + std::vector>& component) const + { + // Allocate the maximum-size stack that can occur in the + // depth-first search. The stack is empty when the index top + // is -1. + std::vector> tStack(mTMap.size()); + int top = -1; + tStack[++top] = tInitial; + while (top >= 0) + { + std::shared_ptr tri = tStack[top]; + visited[tri] = 1; + int i; + for (i = 0; i < 3; ++i) + { + auto edge = tri->E[i].lock(); + LogAssert(edge != nullptr, "Unexpected condition."); + + bool foundUnvisited = false; + for (auto const& adjw : edge->T) + { + auto adj = adjw.lock(); + LogAssert(adj != nullptr, "Unexpected condition."); + + if (visited[adj] == 0) + { + tStack[++top] = adj; + foundUnvisited = true; + break; + } + } + + if (foundUnvisited) + { + break; + } + } + if (i == 3) + { + visited[tri] = 2; + component.push_back(tri); + --top; + } + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/EdgeKey.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/EdgeKey.h new file mode 100644 index 0000000..a14afe9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/EdgeKey.h @@ -0,0 +1,60 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// An ordered edge has (V[0], V[1]) = (v0, v1). An unordered edge has +// (V[0], V[1]) = (min(V[0],V[1]), max(V[0],V[1])). + +namespace WwiseGTE +{ + template + class EdgeKey : public FeatureKey<2, Ordered> + { + public: + // Initialize to invalid indices. + EdgeKey() + { + this->V = { -1, -1 }; + } + + // This constructor is specialized based on Ordered. + explicit EdgeKey(int v0, int v1) + { + Initialize(v0, v1); + } + + private: + template + typename std::enable_if::type + Initialize(int v0, int v1) + { + this->V[0] = v0; + this->V[1] = v1; + } + + template + typename std::enable_if::type + Initialize(int v0, int v1) + { + if (v0 < v1) + { + // v0 is minimum + this->V[0] = v0; + this->V[1] = v1; + } + else + { + // v1 is minimum + this->V[0] = v1; + this->V[1] = v0; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Ellipse3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Ellipse3.h new file mode 100644 index 0000000..e9fd5c0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Ellipse3.h @@ -0,0 +1,133 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2020.01.10 + +#pragma once + +#include +#include + +// The plane containing ellipse is Dot(N,X-C) = 0 where X is any point in the +// plane, C is the ellipse center, and N is a unit-length normal to the plane. +// Vectors A0, A1, and N form an orthonormal right-handed set. The ellipse in +// the plane is parameterized by X = C + e0*cos(t)*A0 + e1*sin(t)*A1, where A0 +// is the major axis, A1 is the minor axis, and e0 and e1 are the extents +// along those axes. The angle t is in [-pi,pi) and e0 >= e1 > 0. + +namespace WwiseGTE +{ + template + class Ellipse3 + { + public: + // Construction and destruction. The default constructor sets center + // to (0,0,0), A0 to (1,0,0), A1 to (0,1,0), normal to (0,0,1), e0 + // to 1, and e1 to 1. + Ellipse3() + : + center(Vector3::Zero()), + normal(Vector3::Unit(2)), + extent{ (Real)1, (Real)1 } + { + axis[0] = Vector3::Unit(0); + axis[1] = Vector3::Unit(1); + } + + Ellipse3(Vector3 const& inCenter, Vector3 const& inNormal, + Vector3 const inAxis[2], Vector2 const& inExtent) + : + center(inCenter), + normal(inNormal), + extent(inExtent) + { + for (int i = 0; i < 2; ++i) + { + axis[i] = inAxis[i]; + } + } + + // Public member access. + Vector3 center, normal; + Vector3 axis[2]; + Vector2 extent; + + public: + // Comparisons to support sorted containers. + bool operator==(Ellipse3 const& ellipse) const + { + return center == ellipse.center + && normal == ellipse.normal + && axis[0] == ellipse.axis[0] + && axis[1] == ellipse.axis[1] + && extent == ellipse.extent; + } + + bool operator!=(Ellipse3 const& ellipse) const + { + return !operator==(ellipse); + } + + bool operator< (Ellipse3 const& ellipse) const + { + if (center < ellipse.center) + { + return true; + } + + if (center > ellipse.center) + { + return false; + } + + if (normal < ellipse.normal) + { + return true; + } + + if (normal > ellipse.normal) + { + return false; + } + + if (axis[0] < ellipse.axis[0]) + { + return true; + } + + if (axis[0] > ellipse.axis[0]) + { + return false; + } + + if (axis[1] < ellipse.axis[1]) + { + return true; + } + + if (axis[1] > ellipse.axis[1]) + { + return false; + } + + return extent < ellipse.extent; + } + + bool operator<=(Ellipse3 const& ellipse) const + { + return !ellipse.operator<(*this); + } + + bool operator> (Ellipse3 const& ellipse) const + { + return ellipse.operator<(*this); + } + + bool operator>=(Ellipse3 const& ellipse) const + { + return !operator<(ellipse); + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/EllipsoidGeodesic.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/EllipsoidGeodesic.h new file mode 100644 index 0000000..826676b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/EllipsoidGeodesic.h @@ -0,0 +1,137 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + template + class EllipsoidGeodesic : public RiemannianGeodesic + { + public: + // The ellipsoid is (x/a)^2 + (y/b)^2 + (z/c)^2 = 1, where xExtent is + // 'a', yExtent is 'b', and zExtent is 'c'. The surface is represented + // parametrically by angles u and v, say + // P(u,v) = (x(u,v),y(u,v),z(u,v)), + // P(u,v) =(a*cos(u)*sin(v), b*sin(u)*sin(v), c*cos(v)) + // with 0 <= u < 2*pi and 0 <= v <= pi. The first-order derivatives + // are + // dP/du = (-a*sin(u)*sin(v), b*cos(u)*sin(v), 0) + // dP/dv = (a*cos(u)*cos(v), b*sin(u)*cos(v), -c*sin(v)) + // The metric tensor elements are + // g_{00} = Dot(dP/du,dP/du) + // g_{01} = Dot(dP/du,dP/dv) + // g_{10} = g_{01} + // g_{11} = Dot(dP/dv,dP/dv) + + EllipsoidGeodesic(Real xExtent, Real yExtent, Real zExtent) + : + RiemannianGeodesic(2), + mXExtent(xExtent), + mYExtent(yExtent), + mZExtent(zExtent) + { + } + + virtual ~EllipsoidGeodesic() + { + } + + Vector3 ComputePosition(GVector const& point) + { + Real cos0 = std::cos(point[0]); + Real sin0 = std::sin(point[0]); + Real cos1 = std::cos(point[1]); + Real sin1 = std::sin(point[1]); + + return Vector3 + { + mXExtent * cos0 * sin1, + mYExtent * sin0 * sin1, + mZExtent * cos1 + }; + } + + // To compute the geodesic path connecting two parameter points + // (u0,v0) and (u1,v1): + // + // float a, b, c; // the extents of the ellipsoid + // EllipsoidGeodesic EG(a,b,c); + // GVector param0(2), param1(2); + // param0[0] = u0; + // param0[1] = v0; + // param1[0] = u1; + // param1[1] = v1; + // + // int quantity; + // std:vector> path; + // EG.ComputeGeodesic(param0, param1, quantity, path); + + private: + virtual void ComputeMetric(GVector const& point) override + { + mCos0 = std::cos(point[0]); + mSin0 = std::sin(point[0]); + mCos1 = std::cos(point[1]); + mSin1 = std::sin(point[1]); + + mDer0 = { -mXExtent * mSin0 * mSin1, mYExtent * mCos0 * mSin1, (Real)0 }; + mDer1 = { mXExtent * mCos0 * mCos1, mYExtent * mSin0 * mCos1, -mZExtent * mSin1 }; + + this->mMetric(0, 0) = Dot(mDer0, mDer0); + this->mMetric(0, 1) = Dot(mDer0, mDer1); + this->mMetric(1, 0) = this->mMetric(0, 1); + this->mMetric(1, 1) = Dot(mDer1, mDer1); + } + + virtual void ComputeChristoffel1(GVector const&) override + { + Vector3 der00 + { + -mXExtent * mCos0 * mSin1, + -mYExtent * mSin0 * mSin1, + (Real)0 + }; + + Vector3 der01 + { + -mXExtent * mSin0 * mCos1, + mYExtent * mCos0 * mCos1, + (Real)0 + }; + + Vector3 der11 + { + -mXExtent * mCos0 * mSin1, + -mYExtent * mSin0 * mSin1, + -mZExtent * mCos1 + }; + + this->mChristoffel1[0](0, 0) = Dot(der00, mDer0); + this->mChristoffel1[0](0, 1) = Dot(der01, mDer0); + this->mChristoffel1[0](1, 0) = this->mChristoffel1[0](0, 1); + this->mChristoffel1[0](1, 1) = Dot(der11, mDer0); + + this->mChristoffel1[1](0, 0) = Dot(der00, mDer1); + this->mChristoffel1[1](0, 1) = Dot(der01, mDer1); + this->mChristoffel1[1](1, 0) = this->mChristoffel1[1](0, 1); + this->mChristoffel1[1](1, 1) = Dot(der11, mDer1); + } + + // The ellipsoid axis half-lengths. + Real mXExtent, mYExtent, mZExtent; + + // We are guaranteed that RiemannianGeodesic calls ComputeMetric + // before ComputeChristoffel1. Thus, we can compute the surface + // first- and second-order derivatives in ComputeMetric and cache + // the results for use in ComputeChristoffel1. + Real mCos0, mSin0, mCos1, mSin1; + Vector3 mDer0, mDer1; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/EulerAngles.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/EulerAngles.h new file mode 100644 index 0000000..95a40a3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/EulerAngles.h @@ -0,0 +1,69 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The Euler angle data structure for representing rotations. See the +// document +// https://www.geometrictools.com/Documentation/EulerAngles.pdf + +namespace WwiseGTE +{ + // Factorization into Euler angles is not necessarily unique. Let the + // integer indices for the axes be (N0,N1,N2), which must be in the set + // {(0,1,2),(0,2,1),(1,0,2),(1,2,0),(2,0,1),(2,1,0), + // (0,1,0),(0,2,0),(1,0,1),(1,2,1),(2,0,2),(2,1,2)} + // Let the corresponding angles be (angleN0,angleN1,angleN2). If the + // result is ER_NOT_UNIQUE_SUM, then the multiple solutions occur because + // angleN2+angleN0 is constant. If the result is ER_NOT_UNIQUE_DIF, then + // the multiple solutions occur because angleN2-angleN0 is constant. In + // either type of nonuniqueness, the function returns angleN0=0. + enum EulerResult + { + // The solution is invalid (incorrect axis indices). + ER_INVALID, + + // The solution is unique. + ER_UNIQUE, + + // The solution is not unique. A sum of angles is constant. + ER_NOT_UNIQUE_SUM, + + // The solution is not unique. A difference of angles is constant. + ER_NOT_UNIQUE_DIF + }; + + template + class EulerAngles + { + public: + EulerAngles() + : + axis{0, 0, 0}, + angle{ (Real)0, (Real)0, (Real)0 }, + result(ER_INVALID) + { + } + + EulerAngles(int i0, int i1, int i2, Real a0, Real a1, Real a2) + : + axis{ i0, i1, i2 }, + angle{ a0, a1, a2 }, + result(ER_UNIQUE) + { + } + + std::array axis; + std::array angle; + + // This member is set during conversions from rotation matrices, + // quaternions, or axis-angles. + EulerResult result; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Exp2Estimate.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Exp2Estimate.h new file mode 100644 index 0000000..19ee35b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Exp2Estimate.h @@ -0,0 +1,128 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// Minimax polynomial approximations to 2^x. The polynomial p(x) of +// degree D minimizes the quantity maximum{|2^x - p(x)| : x in [0,1]} +// over all polynomials of degree D. + +namespace WwiseGTE +{ + template + class Exp2Estimate + { + public: + // The input constraint is x in [0,1]. For example, + // float x; // in [0,1] + // float result = Exp2Estimate::Degree<3>(x); + template + inline static Real Degree(Real x) + { + return Evaluate(degree(), x); + } + + // The input x can be any real number. Range reduction is used to + // generate a value y in [0,1], call Degree(y), and combine the output + // with the proper exponent to obtain the approximation. For example, + // float x; // x >= 0 + // float result = Exp2Estimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x) + { + Real p = std::floor(x); + Real y = x - p; + Real poly = Degree(y); + Real result = std::ldexp(poly, (int)p); + return result; + } + + private: + // Metaprogramming and private implementation to allow specialization + // of a template member function. + template struct degree {}; + + inline static Real Evaluate(degree<1>, Real t) + { + Real poly; + poly = (Real)GTE_C_EXP2_DEG1_C1; + poly = (Real)GTE_C_EXP2_DEG1_C0 + poly * t; + return poly; + } + + inline static Real Evaluate(degree<2>, Real t) + { + Real poly; + poly = (Real)GTE_C_EXP2_DEG2_C2; + poly = (Real)GTE_C_EXP2_DEG2_C1 + poly * t; + poly = (Real)GTE_C_EXP2_DEG2_C0 + poly * t; + return poly; + } + + inline static Real Evaluate(degree<3>, Real t) + { + Real poly; + poly = (Real)GTE_C_EXP2_DEG3_C3; + poly = (Real)GTE_C_EXP2_DEG3_C2 + poly * t; + poly = (Real)GTE_C_EXP2_DEG3_C1 + poly * t; + poly = (Real)GTE_C_EXP2_DEG3_C0 + poly * t; + return poly; + } + + inline static Real Evaluate(degree<4>, Real t) + { + Real poly; + poly = (Real)GTE_C_EXP2_DEG4_C4; + poly = (Real)GTE_C_EXP2_DEG4_C3 + poly * t; + poly = (Real)GTE_C_EXP2_DEG4_C2 + poly * t; + poly = (Real)GTE_C_EXP2_DEG4_C1 + poly * t; + poly = (Real)GTE_C_EXP2_DEG4_C0 + poly * t; + return poly; + } + + inline static Real Evaluate(degree<5>, Real t) + { + Real poly; + poly = (Real)GTE_C_EXP2_DEG5_C5; + poly = (Real)GTE_C_EXP2_DEG5_C4 + poly * t; + poly = (Real)GTE_C_EXP2_DEG5_C3 + poly * t; + poly = (Real)GTE_C_EXP2_DEG5_C2 + poly * t; + poly = (Real)GTE_C_EXP2_DEG5_C1 + poly * t; + poly = (Real)GTE_C_EXP2_DEG5_C0 + poly * t; + return poly; + } + + inline static Real Evaluate(degree<6>, Real t) + { + Real poly; + poly = (Real)GTE_C_EXP2_DEG6_C6; + poly = (Real)GTE_C_EXP2_DEG6_C5 + poly * t; + poly = (Real)GTE_C_EXP2_DEG6_C4 + poly * t; + poly = (Real)GTE_C_EXP2_DEG6_C3 + poly * t; + poly = (Real)GTE_C_EXP2_DEG6_C2 + poly * t; + poly = (Real)GTE_C_EXP2_DEG6_C1 + poly * t; + poly = (Real)GTE_C_EXP2_DEG6_C0 + poly * t; + return poly; + } + + inline static Real Evaluate(degree<7>, Real t) + { + Real poly; + poly = (Real)GTE_C_EXP2_DEG7_C7; + poly = (Real)GTE_C_EXP2_DEG7_C6 + poly * t; + poly = (Real)GTE_C_EXP2_DEG7_C5 + poly * t; + poly = (Real)GTE_C_EXP2_DEG7_C4 + poly * t; + poly = (Real)GTE_C_EXP2_DEG7_C3 + poly * t; + poly = (Real)GTE_C_EXP2_DEG7_C2 + poly * t; + poly = (Real)GTE_C_EXP2_DEG7_C1 + poly * t; + poly = (Real)GTE_C_EXP2_DEG7_C0 + poly * t; + return poly; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ExpEstimate.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ExpEstimate.h new file mode 100644 index 0000000..c4a2e3d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ExpEstimate.h @@ -0,0 +1,44 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.12.23 + +#pragma once + +#include + +// Minimax polynomial approximations to 2^x. The polynomial p(x) of +// degree D minimizes the quantity maximum{|2^x - p(x)| : x in [0,1]} +// over all polynomials of degree D. The natural exponential is +// computed using exp(x) = 2^{x/log(2)}, where log(2) is the natural +// logarithm of 2. + +namespace WwiseGTE +{ + template + class ExpEstimate + { + public: + // The input constraint is x in [0,1]. For example, + // float x; // in [0,1] + // float result = ExpEstimate::Degree<3>(x); + template + inline static Real Degree(Real x) + { + return Exp2Estimate::Degree(x * (Real)GTE_C_INV_LN_2); + } + + // The input x can be any real number. Range reduction is used to + // generate a value y in [0,1], call Degree(y), and combine the output + // with the proper exponent to obtain the approximation. For example, + // float x; // x >= 0 + // float result = ExpEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x) + { + return Exp2Estimate::DegreeRR(x * (Real)GTE_C_INV_LN_2); + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ExtremalQuery3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ExtremalQuery3.h new file mode 100644 index 0000000..6ba6abd --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ExtremalQuery3.h @@ -0,0 +1,66 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + template + class ExtremalQuery3 + { + public: + // Abstract base class. + virtual ~ExtremalQuery3() = default; + + // Disallow copying and assignment. + ExtremalQuery3(ExtremalQuery3 const&) = delete; + ExtremalQuery3& operator=(ExtremalQuery3 const&) = delete; + + // Member access. + inline Polyhedron3 const& GetPolytope() const + { + return mPolytope; + } + + inline std::vector> const& GetFaceNormals() const + { + return mFaceNormals; + } + + // Compute the extreme vertices in the specified direction and return + // the indices of the vertices in the polyhedron vertex array. + virtual void GetExtremeVertices(Vector3 const& direction, + int& positiveDirection, int& negativeDirection) = 0; + + protected: + // The caller must ensure that the input polyhedron is convex. + ExtremalQuery3(Polyhedron3 const& polytope) + : + mPolytope(polytope) + { + // Create the face normals. + auto vertexPool = mPolytope.GetVertices(); + auto const& indices = mPolytope.GetIndices(); + int const numTriangles = static_cast(indices.size()) / 3; + mFaceNormals.resize(numTriangles); + for (int t = 0; t < numTriangles; ++t) + { + Vector3 v0 = vertexPool[indices[3 * t + 0]]; + Vector3 v1 = vertexPool[indices[3 * t + 1]]; + Vector3 v2 = vertexPool[indices[3 * t + 2]]; + Vector3 edge1 = v1 - v0; + Vector3 edge2 = v2 - v0; + mFaceNormals[t] = UnitCross(edge1, edge2); + } + } + + Polyhedron3 const& mPolytope; + std::vector> mFaceNormals; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ExtremalQuery3BSP.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ExtremalQuery3BSP.h new file mode 100644 index 0000000..3e87e63 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ExtremalQuery3BSP.h @@ -0,0 +1,419 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class ExtremalQuery3BSP : public ExtremalQuery3 + { + public: + // Construction. + ExtremalQuery3BSP(Polyhedron3 const& polytope) + : + ExtremalQuery3(polytope) + { + // Create the adjacency information for the polytope. + VETManifoldMesh mesh; + auto const& indices = this->mPolytope.GetIndices(); + int const numTriangles = static_cast(indices.size() / 3); + for (int t = 0; t < numTriangles; ++t) + { + int V[3]; + for (int j = 0; j < 3; ++j) + { + V[j] = indices[3 * t + j]; + } + auto triangle = mesh.Insert(V[0], V[1], V[2]); + mTriToNormal.insert(std::make_pair(triangle, t)); + } + + // Create the set of unique arcs which are used to create the BSP + // tree. + std::multiset arcs; + CreateSphericalArcs(mesh, arcs); + + // Create the BSP tree to be used in the extremal query. + CreateBSPTree(arcs); + } + + // Disallow copying and assignment. + ExtremalQuery3BSP(ExtremalQuery3BSP const&) = delete; + ExtremalQuery3BSP& operator=(ExtremalQuery3BSP const&) = delete; + + // Compute the extreme vertices in the specified direction and return + // the indices of the vertices in the polyhedron vertex array. + virtual void GetExtremeVertices(Vector3 const& direction, + int& positiveDirection, int& negativeDirection) override + { + // Do a nonrecursive depth-first search of the BSP tree to + // determine spherical polygon contains the incoming direction D. + // Index 0 is the root of the BSP tree. + int current = 0; + while (current >= 0) + { + SphericalArc& node = mNodes[current]; + int sign = WwiseGTE::isign(Dot(direction, node.normal)); + if (sign >= 0) + { + current = node.posChild; + if (current == -1) + { + // At a leaf node. + positiveDirection = node.posVertex; + } + } + else + { + current = node.negChild; + if (current == -1) + { + // At a leaf node. + positiveDirection = node.negVertex; + } + } + } + + // Do a nonrecursive depth-first search of the BSP tree to + // determine spherical polygon contains the reverse incoming + // direction -D. + current = 0; // the root of the BSP tree + while (current >= 0) + { + SphericalArc& node = mNodes[current]; + int sign = WwiseGTE::isign(Dot(direction, node.normal)); + if (sign <= 0) + { + current = node.posChild; + if (current == -1) + { + // At a leaf node. + negativeDirection = node.posVertex; + } + } + else + { + current = node.negChild; + if (current == -1) + { + // At a leaf node. + negativeDirection = node.negVertex; + } + } + } + } + + // Tree statistics. + inline int GetNumNodes() const + { + return static_cast(mNodes.size()); + } + + inline int GetTreeDepth() const + { + return mTreeDepth; + } + + private: + class SphericalArc + { + public: + // Construction. + SphericalArc() + : + nIndex{ -1, -1 }, + separation(0), + posVertex(-1), + negVertex(-1), + posChild(-1), + negChild(-1) + { + } + + // The arcs are stored in a multiset ordered by increasing + // separation. The multiset will be traversed in reverse order. + // This heuristic is designed to create BSP trees whose top-most + // nodes can eliminate as many arcs as possible during an extremal + // query. + bool operator<(SphericalArc const& arc) const + { + return separation < arc.separation; + } + + // Indices N[] into the face normal array for the endpoints of the + // arc. + std::array nIndex; + + // The number of arcs in the path from normal N[0] to normal N[1]. + // For spherical polygon edges, the number is 1. The number is 2 + // or larger for bisector arcs of the spherical polygon. + int separation; + + // The normal is Cross(FaceNormal[N[0]],FaceNormal[N[1]]). + Vector3 normal; + + // Indices into the vertex array for the extremal points for the + // two regions sharing the arc. As the arc is traversed from + // normal N[0] to normal N[1], PosVertex is the index for the + // extreme vertex to the left of the arc and NegVertex is the + // index for the extreme vertex to the right of the arc. + int posVertex, negVertex; + + // Support for BSP trees stored as contiguous nodes in an array. + int posChild, negChild; + }; + + typedef VETManifoldMesh::Triangle Triangle; + + void SortAdjacentTriangles(int vIndex, + std::set> const& tAdj, + std::vector>& tAdjSorted) + { + // Copy the set of adjacent triangles into a vector container. + int const numTriangles = static_cast(tAdj.size()); + tAdjSorted.resize(tAdj.size()); + + // Traverse the triangles adjacent to vertex V using edge-triangle + // adjacency information to produce a sorted array of adjacent + // triangles. + auto tri = *tAdj.begin(); + for (int i = 0; i < numTriangles; ++i) + { + for (int prev = 2, curr = 0; curr < 3; prev = curr++) + { + if (tri->V[curr] == vIndex) + { + tAdjSorted[i] = tri; + tri = tri->T[prev].lock(); + break; + } + } + } + } + + void CreateSphericalArcs(VETManifoldMesh& mesh, std::multiset& arcs) + { + int const prev[3] = { 2, 0, 1 }; + int const next[3] = { 1, 2, 0 }; + + for (auto const& element : mesh.GetEdges()) + { + auto edge = element.second; + + SphericalArc arc; + arc.nIndex[0] = mTriToNormal[edge->T[0].lock()]; + arc.nIndex[1] = mTriToNormal[edge->T[1].lock()]; + arc.separation = 1; + arc.normal = Cross(this->mFaceNormals[arc.nIndex[0]], this->mFaceNormals[arc.nIndex[1]]); + + auto adj = edge->T[0].lock(); + int j; + for (j = 0; j < 3; ++j) + { + if (adj->V[j] != edge->V[0] && adj->V[j] != edge->V[1]) + { + arc.posVertex = adj->V[prev[j]]; + arc.negVertex = adj->V[next[j]]; + break; + } + } + LogAssert(j < 3, "Unexpected condition."); + + arcs.insert(arc); + } + + CreateSphericalBisectors(mesh, arcs); + } + + void CreateSphericalBisectors(VETManifoldMesh& mesh, std::multiset& arcs) + { + std::queue> queue; + for (auto const& element : mesh.GetVertices()) + { + // Sort the normals into a counterclockwise spherical polygon + // when viewed from outside the sphere. + auto vertex = element.second; + int const vIndex = vertex->V; + std::vector> tAdjSorted; + SortAdjacentTriangles(vIndex, vertex->TAdjacent, tAdjSorted); + int const numTriangles = static_cast(vertex->TAdjacent.size()); + queue.push(std::make_pair(0, numTriangles)); + while (!queue.empty()) + { + std::pair item = queue.front(); + queue.pop(); + int i0 = item.first, i1 = item.second; + int separation = i1 - i0; + if (separation > 1 && separation != numTriangles - 1) + { + if (i1 < numTriangles) + { + SphericalArc arc; + arc.nIndex[0] = mTriToNormal[tAdjSorted[i0]]; + arc.nIndex[1] = mTriToNormal[tAdjSorted[i1]]; + arc.separation = separation; + + arc.normal = Cross(this->mFaceNormals[arc.nIndex[0]], + this->mFaceNormals[arc.nIndex[1]]); + + arc.posVertex = vIndex; + arc.negVertex = vIndex; + arcs.insert(arc); + } + int imid = (i0 + i1 + 1) / 2; + if (imid != i1) + { + queue.push(std::make_pair(i0, imid)); + queue.push(std::make_pair(imid, i1)); + } + } + } + } + } + + void CreateBSPTree(std::multiset& arcs) + { + // The tree has at least a root. + mTreeDepth = 1; + + for (auto const& arc : WwiseGTE::reverse(arcs)) + { + InsertArc(arc); + } + + // The leaf nodes are not counted in the traversal of InsertArc. + // The depth must be incremented to account for leaves. + ++mTreeDepth; + } + + void InsertArc(SphericalArc const& arc) + { + // The incoming arc is stored at the end of the nodes array. + if (mNodes.size() > 0) + { + // Do a nonrecursive depth-first search of the current BSP + // tree to place the incoming arc. Index 0 is the root of the + // BSP tree. + std::stack candidates; + candidates.push(0); + while (!candidates.empty()) + { + int current = candidates.top(); + candidates.pop(); + SphericalArc* node = &mNodes[current]; + + int sign0; + if (arc.nIndex[0] == node->nIndex[0] || arc.nIndex[0] == node->nIndex[1]) + { + sign0 = 0; + } + else + { + Real dot = Dot(this->mFaceNormals[arc.nIndex[0]], node->normal); + sign0 = WwiseGTE::isign(dot); + } + + int sign1; + if (arc.nIndex[1] == node->nIndex[0] || arc.nIndex[1] == node->nIndex[1]) + { + sign1 = 0; + } + else + { + Real dot = Dot(this->mFaceNormals[arc.nIndex[1]], node->normal); + sign1 = WwiseGTE::isign(dot); + } + + int doTest = 0; + if (sign0 * sign1 < 0) + { + // The new arc straddles the current arc, so propagate + // it to both child nodes. + doTest = 3; + } + else if (sign0 > 0 || sign1 > 0) + { + // The new arc is on the positive side of the current + // arc. + doTest = 1; + } + else if (sign0 < 0 || sign1 < 0) + { + // The new arc is on the negative side of the current + // arc. + doTest = 2; + } + // else: sign0 = sign1 = 0, in which case no propagation + // is needed because the current BSP node will handle the + // correct partitioning of the arcs during extremal + // queries. + + int depth; + + if (doTest & 1) + { + if (node->posChild != -1) + { + candidates.push(node->posChild); + depth = static_cast(candidates.size()); + if (depth > mTreeDepth) + { + mTreeDepth = depth; + } + } + else + { + node->posChild = static_cast(mNodes.size()); + mNodes.push_back(arc); + + // The push_back can cause a reallocation, so the + // current pointer must be refreshed. + node = &mNodes[current]; + } + } + + if (doTest & 2) + { + if (node->negChild != -1) + { + candidates.push(node->negChild); + depth = static_cast(candidates.size()); + if (depth > mTreeDepth) + { + mTreeDepth = depth; + } + } + else + { + node->negChild = static_cast(mNodes.size()); + mNodes.push_back(arc); + } + } + } + } + else + { + // root node + mNodes.push_back(arc); + } + } + + // Lookup table for indexing into mFaceNormals. + std::map, int> mTriToNormal; + + // Fixed-size storage for the BSP nodes. + std::vector mNodes; + int mTreeDepth; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ExtremalQuery3PRJ.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ExtremalQuery3PRJ.h new file mode 100644 index 0000000..cd20d5d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ExtremalQuery3PRJ.h @@ -0,0 +1,60 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + template + class ExtremalQuery3PRJ : public ExtremalQuery3 + { + public: + // Construction. + ExtremalQuery3PRJ(Polyhedron3 const& polytope) + : + ExtremalQuery3(polytope) + { + mCentroid = this->mPolytope.ComputeVertexAverage(); + } + + // Disallow copying and assignment. + ExtremalQuery3PRJ(ExtremalQuery3PRJ const&) = delete; + ExtremalQuery3PRJ& operator=(ExtremalQuery3PRJ const&) = delete; + + // Compute the extreme vertices in the specified direction and return + // the indices of the vertices in the polyhedron vertex array. + virtual void GetExtremeVertices(Vector3 const& direction, + int& positiveDirection, int& negativeDirection) override + { + Real minValue = std::numeric_limits::max(), maxValue = -minValue; + negativeDirection = -1; + positiveDirection = -1; + + auto vertexPool = this->mPolytope.GetVertexPool(); + for (auto i : this->mPolytope.GetUniqueIndices()) + { + Vector3 diff = vertexPool.get()->at(i) - mCentroid; + Real dot = Dot(direction, diff); + if (dot < minValue) + { + negativeDirection = i; + minValue = dot; + } + if (dot > maxValue) + { + positiveDirection = i; + maxValue = dot; + } + } + } + + private: + Vector3 mCentroid; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FIQuery.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FIQuery.h new file mode 100644 index 0000000..083bc40 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FIQuery.h @@ -0,0 +1,33 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + // Find-intersection queries. + + template + class FIQuery + { + public: + struct Result + { + // A FIQuery-base class B must define a B::Result struct with + // member 'bool intersect'. A FIQuery-derived class D must also + // derive a D::Result from B:Result but may have no members. The + // member 'intersect' is 'true' iff the primitives intersect. The + // operator() is const for conceptual constness, but derived + // classes can use internal data to support the queries and tag + // that data with the mutable modifier. + }; + + Result operator()(Type0 const& primitive0, Type1 const& primitive1) const; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FPInterval.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FPInterval.h new file mode 100644 index 0000000..02e9a8f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FPInterval.h @@ -0,0 +1,462 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.1.2019.10.09 + +#pragma once + +#include +#include +#include + +// The FPInterval [e0,e1] must satisfy e0 <= e1. Expose this define to trap +// invalid construction where e0 > e1. +#define GTE_THROW_ON_INVALID_INTERVAL + +namespace WwiseGTE +{ + // The FPType must be 'float' or 'double'. + template + class FPInterval + { + public: + // Construction. This is the only way to create an interval. All such + // intervals are immutable once created. The constructor + // FPInterval(FPType) is used to create the degenerate interval [e,e]. + FPInterval() + : + mEndpoints{ static_cast(0), static_cast(0) } + { + static_assert(std::is_floating_point::value, "Invalid type."); + } + + FPInterval(FPInterval const& other) + : + mEndpoints(other.mEndpoints) + { + static_assert(std::is_floating_point::value, "Invalid type."); + } + + explicit FPInterval(FPType e) + : + mEndpoints{ e, e } + { + static_assert(std::is_floating_point::value, "Invalid type."); + } + + FPInterval(FPType e0, FPType e1) + : + mEndpoints{ e0, e1 } + { + static_assert(std::is_floating_point::value, "Invalid type."); +#if defined(GTE_THROW_ON_INVALID_INTERVAL) + LogAssert(mEndpoints[0] <= mEndpoints[1], "Invalid FPInterval."); +#endif + } + + FPInterval(std::array const& endpoint) + : + mEndpoints(endpoint) + { + static_assert(std::is_floating_point::value, "Invalid type."); +#if defined(GTE_THROW_ON_INVALID_INTERVAL) + LogAssert(mEndpoints[0] <= mEndpoints[1], "Invalid FPInterval."); +#endif + } + + FPInterval& operator=(FPInterval const& other) + { + static_assert(std::is_floating_point::value, "Invalid type."); + mEndpoints = other.mEndpoints; + return *this; + } + + // Member access. It is only possible to read the endpoints. You + // cannot modify the endpoints outside the arithmetic operations. + inline FPType operator[](size_t i) const + { + return mEndpoints[i]; + } + + inline std::array GetEndpoints() const + { + return mEndpoints; + } + + // Arithmetic operations to compute intervals at the leaf nodes of + // an expression tree. Such nodes correspond to the raw floating-point + // variables of the expression. The non-class operators defined after + // the class definition are used to compute intervals at the interior + // nodes of the expression tree. + inline static FPInterval Add(FPType u, FPType v) + { + FPInterval w; + auto saveMode = std::fegetround(); + std::fesetround(FE_DOWNWARD); + w.mEndpoints[0] = u + v; + std::fesetround(FE_UPWARD); + w.mEndpoints[1] = u + v; + std::fesetround(saveMode); + return w; + } + + inline static FPInterval Sub(FPType u, FPType v) + { + FPInterval w; + auto saveMode = std::fegetround(); + std::fesetround(FE_DOWNWARD); + w.mEndpoints[0] = u - v; + std::fesetround(FE_UPWARD); + w.mEndpoints[1] = u - v; + std::fesetround(saveMode); + return w; + } + + inline static FPInterval Mul(FPType u, FPType v) + { + FPInterval w; + auto saveMode = std::fegetround(); + std::fesetround(FE_DOWNWARD); + w.mEndpoints[0] = u * v; + std::fesetround(FE_UPWARD); + w.mEndpoints[1] = u * v; + std::fesetround(saveMode); + return w; + } + + inline static FPInterval Div(FPType u, FPType v) + { + FPType const zero = static_cast(0); + if (v != zero) + { + FPInterval w; + auto saveMode = std::fegetround(); + std::fesetround(FE_DOWNWARD); + w.mEndpoints[0] = u / v; + std::fesetround(FE_UPWARD); + w.mEndpoints[1] = u / v; + std::fesetround(saveMode); + return w; + } + else + { + // Division by zero does not lead to a determinate FPInterval. + // Just return the entire set of real numbers. + return Reals(); + } + } + + private: + std::array mEndpoints; + + public: + // FOR INTERNAL USE ONLY. These are used by the non-class operators + // defined after the class definition. + inline static FPInterval Add(FPType u0, FPType u1, FPType v0, FPType v1) + { + FPInterval w; + auto saveMode = std::fegetround(); + std::fesetround(FE_DOWNWARD); + w.mEndpoints[0] = u0 + v0; + std::fesetround(FE_UPWARD); + w.mEndpoints[1] = u1 + v1; + std::fesetround(saveMode); + return w; + } + + inline static FPInterval Sub(FPType u0, FPType u1, FPType v0, FPType v1) + { + FPInterval w; + auto saveMode = std::fegetround(); + std::fesetround(FE_DOWNWARD); + w.mEndpoints[0] = u0 - v1; + std::fesetround(FE_UPWARD); + w.mEndpoints[1] = u1 - v0; + std::fesetround(saveMode); + return w; + } + + inline static FPInterval Mul(FPType u0, FPType u1, FPType v0, FPType v1) + { + FPInterval w; + auto saveMode = std::fegetround(); + std::fesetround(FE_DOWNWARD); + w.mEndpoints[0] = u0 * v0; + std::fesetround(FE_UPWARD); + w.mEndpoints[1] = u1 * v1; + std::fesetround(saveMode); + return w; + } + + inline static FPInterval Mul2(FPType u0, FPType u1, FPType v0, FPType v1) + { + auto saveMode = std::fegetround(); + std::fesetround(FE_DOWNWARD); + FPType u0mv1 = u0 * v1; + FPType u1mv0 = u1 * v0; + std::fesetround(FE_UPWARD); + FPType u0mv0 = u0 * v0; + FPType u1mv1 = u1 * v1; + std::fesetround(saveMode); + return FPInterval(std::min(u0mv1, u1mv0), std::max(u0mv0, u1mv1)); + } + + inline static FPInterval Div(FPType u0, FPType u1, FPType v0, FPType v1) + { + FPInterval w; + auto saveMode = std::fegetround(); + std::fesetround(FE_DOWNWARD); + w.mEndpoints[0] = u0 / v1; + std::fesetround(FE_UPWARD); + w.mEndpoints[1] = u1 / v0; + std::fesetround(saveMode); + return w; + } + + inline static FPInterval Reciprocal(FPType v0, FPType v1) + { + FPType const one = static_cast(1); + FPInterval w; + auto saveMode = std::fegetround(); + std::fesetround(FE_DOWNWARD); + w.mEndpoints[0] = one / v1; + std::fesetround(FE_UPWARD); + w.mEndpoints[1] = one / v0; + std::fesetround(saveMode); + return w; + } + + inline static FPInterval ReciprocalDown(FPType v) + { + auto saveMode = std::fegetround(); + std::fesetround(FE_DOWNWARD); + FPType recpv = static_cast(1) / v; + std::fesetround(saveMode); + FPType const inf = std::numeric_limits::infinity(); + return FPInterval(recpv, +inf); + } + + inline static FPInterval ReciprocalUp(FPType v) + { + auto saveMode = std::fegetround(); + std::fesetround(FE_UPWARD); + FPType recpv = static_cast(1) / v; + std::fesetround(saveMode); + FPType const inf = std::numeric_limits::infinity(); + return FPInterval(-inf, recpv); + } + + inline static FPInterval Reals() + { + FPType const inf = std::numeric_limits::infinity(); + return FPInterval(-inf, +inf); + } + }; + + // Unary operations. Negation of [e0,e1] produces [-e1,-e0]. This + // operation needs to be supported in the sense of negating a + // "number" in an arithmetic expression. + template + FPInterval operator+(FPInterval const& u) + { + return u; + } + + template + FPInterval operator-(FPInterval const& u) + { + return FPInterval(-u[1], -u[0]); + } + + // Addition operations. + template + FPInterval operator+(FPType u, FPInterval const& v) + { + return FPInterval::Add(u, u, v[0], v[1]); + } + + template + FPInterval operator+(FPInterval const& u, FPType v) + { + return FPInterval::Add(u[0], u[1], v, v); + } + + template + FPInterval operator+(FPInterval const& u, FPInterval const& v) + { + return FPInterval::Add(u[0], u[1], v[0], v[1]); + } + + // Subtraction operations. + template + FPInterval operator-(FPType u, FPInterval const& v) + { + return FPInterval::Sub(u, u, v[0], v[1]); + } + + template + FPInterval operator-(FPInterval const& u, FPType v) + { + return FPInterval::Sub(u[0], u[1], v, v); + } + + template + FPInterval operator-(FPInterval const& u, FPInterval const& v) + { + return FPInterval::Sub(u[0], u[1], v[0], v[1]); + } + + // Multiplication operations. + template + FPInterval operator*(FPType u, FPInterval const& v) + { + FPType const zero = static_cast(0); + if (u >= zero) + { + return FPInterval::Mul(u, u, v[0], v[1]); + } + else + { + return FPInterval::Mul(u, u, v[1], v[0]); + } + } + + template + FPInterval operator*(FPInterval const& u, FPType v) + { + FPType const zero = static_cast(0); + if (v >= zero) + { + return FPInterval::Mul(u[0], u[1], v, v); + } + else + { + return FPInterval::Mul(u[1], u[0], v, v); + } + } + + template + FPInterval operator*(FPInterval const& u, FPInterval const& v) + { + FPType const zero = static_cast(0); + if (u[0] >= zero) + { + if (v[0] >= zero) + { + return FPInterval::Mul(u[0], u[1], v[0], v[1]); + } + else if (v[1] <= zero) + { + return FPInterval::Mul(u[1], u[0], v[0], v[1]); + } + else // v[0] < 0 < v[1] + { + return FPInterval::Mul(u[1], u[1], v[0], v[1]); + } + } + else if (u[1] <= zero) + { + if (v[0] >= zero) + { + return FPInterval::Mul(u[0], u[1], v[1], v[0]); + } + else if (v[1] <= zero) + { + return FPInterval::Mul(u[1], u[0], v[1], v[0]); + } + else // v[0] < 0 < v[1] + { + return FPInterval::Mul(u[0], u[0], v[1], v[0]); + } + } + else // u[0] < 0 < u[1] + { + if (v[0] >= zero) + { + return FPInterval::Mul(u[0], u[1], v[1], v[1]); + } + else if (v[1] <= zero) + { + return FPInterval::Mul(u[1], u[0], v[0], v[0]); + } + else // v[0] < 0 < v[1] + { + return FPInterval::Mul2(u[0], u[1], v[0], v[1]); + } + } + } + + // Division operations. If the divisor FPInterval is [v0,v1] with + // v0 < 0 < v1, then the returned FPInterval is (-infinity,+infinity) + // instead of Union((-infinity,1/v0),(1/v1,+infinity)). An application + // should try to avoid this case by branching based on [v0,0] and [0,v1]. + template + FPInterval operator/(FPType u, FPInterval const& v) + { + FPType const zero = static_cast(0); + if (v[0] > zero || v[1] < zero) + { + return u * FPInterval::Reciprocal(v[0], v[1]); + } + else + { + if (v[0] == zero) + { + return u * FPInterval::ReciprocalDown(v[1]); + } + else if (v[1] == zero) + { + return u * FPInterval::ReciprocalUp(v[0]); + } + else // v[0] < 0 < v[1] + { + return FPInterval::Reals(); + } + } + } + + template + FPInterval operator/(FPInterval const& u, FPType v) + { + FPType const zero = static_cast(0); + if (v > zero) + { + return FPInterval::Div(u[0], u[1], v, v); + } + else if (v < zero) + { + return FPInterval::Div(u[1], u[0], v, v); + } + else // v = 0 + { + return FPInterval::Reals(); + } + } + + template + FPInterval operator/(FPInterval const& u, FPInterval const& v) + { + FPType const zero = static_cast(0); + if (v[0] > zero || v[1] < zero) + { + return u * FPInterval::Reciprocal(v[0], v[1]); + } + else + { + if (v[0] == zero) + { + return u * FPInterval::ReciprocalDown(v[1]); + } + else if (v[1] == zero) + { + return u * FPInterval::ReciprocalUp(v[0]); + } + else // v[0] < 0 < v[1] + { + return FPInterval::Reals(); + } + } + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FastGaussianBlur1.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FastGaussianBlur1.h new file mode 100644 index 0000000..41d3435 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FastGaussianBlur1.h @@ -0,0 +1,100 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The algorithms here are based on solving the linear heat equation using +// finite differences in scale, not in time. The following document has +// a brief summary of the concept, +// https://www.geometrictools.com/Documentation/FastGaussianBlur.pdf +// The idea is to represent the blurred image as f(x,s) in terms of position +// x and scale s. Gaussian blurring is accomplished by using the input image +// I(x,s0) as the initial image (of scale s0 > 0) for the partial differential +// equation +// s*df/ds = s^2*Laplacian(f) +// where the Laplacian operator is +// Laplacian = (d/dx)^2, dimension 1 +// Laplacian = (d/dx)^2+(d/dy)^2, dimension 2 +// Laplacian = (d/dx)^2+(d/dy)^2+(d/dz)^2, dimension 3 +// +// The term s*df/ds is approximated by +// s*df(x,s)/ds = (f(x,b*s)-f(x,s))/ln(b) +// for b > 1, but close to 1, where ln(b) is the natural logarithm of b. If +// you take the limit of the right-hand side as b approaches 1, you get the +// left-hand side. +// +// The term s^2*((d/dx)^2)f is approximated by +// s^2*((d/dx)^2)f = (f(x+h*s,s)-2*f(x,s)+f(x-h*s,s))/h^2 +// for h > 0, but close to zero. +// +// Equating the approximations for the left-hand side and the right-hand side +// of the partial differential equation leads to the numerical method used in +// this code. +// +// For iterative application of these functions, the caller is responsible +// for constructing a geometric sequence of scales, +// s0, s1 = s0*b, s2 = s1*b = s0*b^2, ... +// where the base b satisfies 1 < b < exp(0.5*d) where d is the dimension of +// the image. The upper bound on b guarantees stability of the finite +// difference method used to approximate the partial differential equation. +// The method assumes a pixel size of h = 1. + +namespace WwiseGTE +{ + // The image type must be one of short, int, float or double. The + // computations are performed using double. The input and output images + // must both have xBound elements. + + template + class FastGaussianBlur1 + { + public: + void Execute(int xBound, T const* input, T* output, + double scale, double logBase) + { + int xBoundM1 = xBound - 1; + for (int x = 0; x < xBound; ++x) + { + double rxps = static_cast(x) + scale; + double rxms = static_cast(x) - scale; + int xp1 = static_cast(std::floor(rxps)); + int xm1 = static_cast(std::ceil(rxms)); + + double center = static_cast(input[x]); + double xsum = -2.0 * center; + + if (xp1 >= xBoundM1) // use boundary value + { + xsum += static_cast(input[xBoundM1]); + } + else // linearly interpolate + { + double imgXp1 = static_cast(input[xp1]); + double imgXp2 = static_cast(input[xp1 + 1]); + double delta = rxps - static_cast(xp1); + xsum += imgXp1 + delta * (imgXp2 - imgXp1); + } + + if (xm1 <= 0) // use boundary value + { + xsum += static_cast(input[0]); + } + else // linearly interpolate + { + double imgXm1 = static_cast(input[xm1]); + double imgXm2 = static_cast(input[xm1 - 1]); + double delta = rxms - static_cast(xm1); + xsum += imgXm1 + delta * (imgXm1 - imgXm2); + } + + output[x] = static_cast(center + logBase * xsum); + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FastGaussianBlur2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FastGaussianBlur2.h new file mode 100644 index 0000000..6263aed --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FastGaussianBlur2.h @@ -0,0 +1,160 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The algorithms here are based on solving the linear heat equation using +// finite differences in scale, not in time. The following document has +// a brief summary of the concept, +// https://www.geometrictools.com/Documentation/FastGaussianBlur.pdf +// The idea is to represent the blurred image as f(x,s) in terms of position +// x and scale s. Gaussian blurring is accomplished by using the input image +// I(x,s0) as the initial image (of scale s0 > 0) for the partial differential +// equation +// s*df/ds = s^2*Laplacian(f) +// where the Laplacian operator is +// Laplacian = (d/dx)^2, dimension 1 +// Laplacian = (d/dx)^2+(d/dy)^2, dimension 2 +// Laplacian = (d/dx)^2+(d/dy)^2+(d/dz)^2, dimension 3 +// +// The term s*df/ds is approximated by +// s*df(x,s)/ds = (f(x,b*s)-f(x,s))/ln(b) +// for b > 1, but close to 1, where ln(b) is the natural logarithm of b. If +// you take the limit of the right-hand side as b approaches 1, you get the +// left-hand side. +// +// The term s^2*((d/dx)^2)f is approximated by +// s^2*((d/dx)^2)f = (f(x+h*s,s)-2*f(x,s)+f(x-h*s,s))/h^2 +// for h > 0, but close to zero. +// +// Equating the approximations for the left-hand side and the right-hand side +// of the partial differential equation leads to the numerical method used in +// this code. +// +// For iterative application of these functions, the caller is responsible +// for constructing a geometric sequence of scales, +// s0, s1 = s0*b, s2 = s1*b = s0*b^2, ... +// where the base b satisfies 1 < b < exp(0.5*d) where d is the dimension of +// the image. The upper bound on b guarantees stability of the finite +// difference method used to approximate the partial differential equation. +// The method assumes a pixel size of h = 1. + +namespace WwiseGTE +{ + // The image type must be one of short, int, float or double. The + // computations are performed using double. The input and output images + // must both have xBound*yBound elements and be stored in lexicographical + // order. The indexing is i = x + xBound * y. + + template + class FastGaussianBlur2 + { + public: + void Execute(int xBound, int yBound, T const* input, T* output, + double scale, double logBase) + { + mXBound = xBound; + mYBound = yBound; + mInput = input; + mOutput = output; + + int xBoundM1 = xBound - 1, yBoundM1 = yBound - 1; + for (int y = 0; y < yBound; ++y) + { + double ryps = static_cast(y) + scale; + double ryms = static_cast(y) - scale; + int yp1 = static_cast(std::floor(ryps)); + int ym1 = static_cast(std::ceil(ryms)); + + for (int x = 0; x < xBound; ++x) + { + double rxps = x + scale; + double rxms = x - scale; + int xp1 = static_cast(std::floor(rxps)); + int xm1 = static_cast(std::ceil(rxms)); + + double center = Input(x, y); + double xsum = -2.0 * center, ysum = xsum; + + // x portion of second central difference + if (xp1 >= xBoundM1) // use boundary value + { + xsum += Input(xBoundM1, y); + } + else // linearly interpolate + { + double imgXp1 = Input(xp1, y); + double imgXp2 = Input(xp1 + 1, y); + double delta = rxps - static_cast(xp1); + xsum += imgXp1 + delta * (imgXp2 - imgXp1); + } + + if (xm1 <= 0) // use boundary value + { + xsum += Input(0, y); + } + else // linearly interpolate + { + double imgXm1 = Input(xm1, y); + double imgXm2 = Input(xm1 - 1, y); + double delta = rxms - static_cast(xm1); + xsum += imgXm1 + delta * (imgXm1 - imgXm2); + } + + // y portion of second central difference + if (yp1 >= yBoundM1) // use boundary value + { + ysum += Input(x, yBoundM1); + } + else // linearly interpolate + { + double imgYp1 = Input(x, yp1); + double imgYp2 = Input(x, yp1 + 1); + double delta = ryps - static_cast(yp1); + ysum += imgYp1 + delta * (imgYp2 - imgYp1); + } + + if (ym1 <= 0) // use boundary value + { + ysum += Input(x, 0); + } + else // linearly interpolate + { + double imgYm1 = Input(x, ym1); + double imgYm2 = Input(x, ym1 - 1); + double delta = ryms - static_cast(ym1); + ysum += imgYm1 + delta * (imgYm1 - imgYm2); + } + + Output(x, y) = static_cast(center + logBase * (xsum + ysum)); + } + } + + mXBound = 0; + mYBound = 0; + mInput = nullptr; + mOutput = nullptr; + } + + private: + inline double Input(int x, int y) const + { + return static_cast(mInput[x + mXBound * y]); + } + + inline T& Output(int x, int y) + { + return mOutput[x + mXBound * y]; + } + + int mXBound, mYBound; + T const* mInput; + T* mOutput; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FastGaussianBlur3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FastGaussianBlur3.h new file mode 100644 index 0000000..d330e2d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FastGaussianBlur3.h @@ -0,0 +1,184 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The algorithms here are based on solving the linear heat equation using +// finite differences in scale, not in time. The following document has +// a brief summary of the concept, +// https://www.geometrictools.com/Documentation/FastGaussianBlur.pdf +// The idea is to represent the blurred image as f(x,s) in terms of position +// x and scale s. Gaussian blurring is accomplished by using the input image +// I(x,s0) as the initial image (of scale s0 > 0) for the partial differential +// equation +// s*df/ds = s^2*Laplacian(f) +// where the Laplacian operator is +// Laplacian = (d/dx)^2, dimension 1 +// Laplacian = (d/dx)^2+(d/dy)^2, dimension 2 +// Laplacian = (d/dx)^2+(d/dy)^2+(d/dz)^2, dimension 3 +// +// The term s*df/ds is approximated by +// s*df(x,s)/ds = (f(x,b*s)-f(x,s))/ln(b) +// for b > 1, but close to 1, where ln(b) is the natural logarithm of b. If +// you take the limit of the right-hand side as b approaches 1, you get the +// left-hand side. +// +// The term s^2*((d/dx)^2)f is approximated by +// s^2*((d/dx)^2)f = (f(x+h*s,s)-2*f(x,s)+f(x-h*s,s))/h^2 +// for h > 0, but close to zero. +// +// Equating the approximations for the left-hand side and the right-hand side +// of the partial differential equation leads to the numerical method used in +// this code. +// +// For iterative application of these functions, the caller is responsible +// for constructing a geometric sequence of scales, +// s0, s1 = s0*b, s2 = s1*b = s0*b^2, ... +// where the base b satisfies 1 < b < exp(0.5*d) where d is the dimension of +// the image. The upper bound on b guarantees stability of the finite +// difference method used to approximate the partial differential equation. +// The method assumes a pixel size of h = 1. + +namespace WwiseGTE +{ + template + class FastGaussianBlur3 + { + public: + void Execute(int xBound, int yBound, int zBound, T const* input, T* output, + double scale, double logBase) + { + mXBound = xBound; + mYBound = yBound; + mZBound = zBound; + mInput = input; + mOutput = output; + + int xBoundM1 = xBound - 1, yBoundM1 = yBound - 1, zBoundM1 = zBound - 1; + for (int z = 0; z < zBound; ++z) + { + double rzps = static_cast(z) + scale; + double rzms = static_cast(z) - scale; + int zp1 = static_cast(std::floor(rzps)); + int zm1 = static_cast(std::ceil(rzms)); + + for (int y = 0; y < yBound; ++y) + { + double ryps = static_cast(y) + scale; + double ryms = static_cast(y) - scale; + int yp1 = static_cast(std::floor(ryps)); + int ym1 = static_cast(std::ceil(ryms)); + + for (int x = 0; x < xBound; ++x) + { + double rxps = static_cast(x) + scale; + double rxms = static_cast(x) - scale; + int xp1 = static_cast(std::floor(rxps)); + int xm1 = static_cast(std::ceil(rxms)); + + double center = Input(x, y, z); + double xsum = -2.0 * center, ysum = xsum, zsum = xsum; + + // x portion of second central difference + if (xp1 >= xBoundM1) // use boundary value + { + xsum += Input(xBoundM1, y, z); + } + else // linearly interpolate + { + double imgXp1 = Input(xp1, y, z); + double imgXp2 = Input(xp1 + 1, y, z); + double delta = rxps - static_cast(xp1); + xsum += imgXp1 + delta * (imgXp2 - imgXp1); + } + + if (xm1 <= 0) // use boundary value + { + xsum += Input(0, y, z); + } + else // linearly interpolate + { + double imgXm1 = Input(xm1, y, z); + double imgXm2 = Input(xm1 - 1, y, z); + double delta = rxms - static_cast(xm1); + xsum += imgXm1 + delta * (imgXm1 - imgXm2); + } + + // y portion of second central difference + if (yp1 >= yBoundM1) // use boundary value + { + ysum += Input(x, yBoundM1, z); + } + else // linearly interpolate + { + double imgYp1 = Input(x, yp1, z); + double imgYp2 = Input(x, yp1 + 1, z); + double delta = ryps - static_cast(yp1); + ysum += imgYp1 + delta * (imgYp2 - imgYp1); + } + + if (ym1 <= 0) // use boundary value + { + ysum += Input(x, 0, z); + } + else // linearly interpolate + { + double imgYm1 = Input(x, ym1, z); + double imgYm2 = Input(x, ym1 - 1, z); + double delta = ryms - static_cast(ym1); + ysum += imgYm1 + delta * (imgYm1 - imgYm2); + } + + // z portion of second central difference + if (zp1 >= zBoundM1) // use boundary value + { + zsum += Input(x, y, zBoundM1); + } + else // linearly interpolate + { + double imgZp1 = Input(x, y, zp1); + double imgZp2 = Input(x, y, zp1 + 1); + double delta = rzps - static_cast(zp1); + zsum += imgZp1 + delta * (imgZp2 - imgZp1); + } + + if (zm1 <= 0) // use boundary value + { + zsum += Input(x, y, 0); + } + else // linearly interpolate + { + double imgZm1 = Input(x, y, zm1); + double imgZm2 = Input(x, y, zm1 - 1); + double delta = rzms - static_cast(zm1); + zsum += imgZm1 + delta * (imgZm1 - imgZm2); + } + + Output(x, y, z) = static_cast(center + logBase * (xsum + ysum + zsum)); + } + } + } + } + + private: + inline double Input(int x, int y, int z) const + { + return static_cast(mInput[x + mXBound * (y + mYBound * z)]); + } + + inline T& Output(int x, int y, int z) + { + return mOutput[x + mXBound * (y + mYBound * z)]; + } + + int mXBound, mYBound, mZBound; + T const* mInput; + T* mOutput; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FastMarch.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FastMarch.h new file mode 100644 index 0000000..c685fc7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FastMarch.h @@ -0,0 +1,193 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The topic of fast marching methods are discussed in the book +// Level Set Methods and Fast Marching Methods: +// Evolving Interfaces in Computational Geometry, Fluid Mechanics, +// Computer Vision, and Materials Science +// J.A. Sethian, +// Cambridge University Press, 1999 + +namespace WwiseGTE +{ + template + class FastMarch + { + // Abstract base class. + public: + virtual ~FastMarch() + { + } + + protected: + // The seed points have a crossing time of 0. As the iterations + // occur, some of the non-seed points are visited by the moving + // front. Define maxReal to be std::numeric_limits::max(). + // The valid crossing times are 0 <= t < maxReal. A value of + // maxReal indicates the pixel has not yet been reached by the + // moving front. If the speed value at a pixel is 0, the pixel + // is marked with a time of -maxReal. Such pixels can never be + // visited; the minus sign distinguishes these from pixels not yet + // reached during iteration. + // + // Trial pixels are identified by having min-heap records + // associated with them. Known or far pixels have no associated + // record. + // + // The speeds must be nonnegative and are inverted because the + // reciprocals are all that are needed in the numerical method. + + FastMarch(size_t quantity, std::vector const& seeds, std::vector const& speeds) + : + mQuantity(quantity), + mTimes(quantity, std::numeric_limits::max()), + mInvSpeeds(quantity), + mHeap(static_cast(quantity)), + mTrials(quantity, nullptr) + { + for (auto seed : seeds) + { + mTimes[seed] = (Real)0; + } + + for (size_t i = 0; i < mQuantity; ++i) + { + if (speeds[i] > (Real)0) + { + mInvSpeeds[i] = (Real)1 / speeds[i]; + } + else + { + mInvSpeeds[i] = std::numeric_limits::max(); + mTimes[i] = -std::numeric_limits::max(); + } + } + } + + FastMarch(size_t quantity, std::vector const& seeds, Real speed) + : + mQuantity(quantity), + mTimes(quantity, std::numeric_limits::max()), + mInvSpeeds(quantity, (Real)1 / speed), + mHeap(static_cast(quantity)), + mTrials(quantity, nullptr) + { + for (auto seed : seeds) + { + mTimes[seed] = (Real)0; + } + } + + public: + // Member access. + inline size_t GetQuantity() const + { + return mQuantity; + } + + inline void SetTime(size_t i, Real time) + { + mTimes[i] = time; + } + + inline Real GetTime(size_t i) const + { + return mTimes[i]; + } + + void GetTimeExtremes(Real& minValue, Real& maxValue) const + { + minValue = std::numeric_limits::max(); + maxValue = -std::numeric_limits::max(); + size_t i; + for (i = 0; i < mQuantity; ++i) + { + if (IsValid(i)) + { + minValue = mTimes[i]; + maxValue = minValue; + break; + } + } + + // Assert: At least one time must be valid, in which case + // i < mQuantity at this point. If all times are invalid, + // minValue = +maxReal and maxValue = -maxReal on exit. + + for (/**/; i < mQuantity; ++i) + { + if (IsValid(i)) + { + if (mTimes[i] < minValue) + { + minValue = mTimes[i]; + } + else if (mTimes[i] > maxValue) + { + maxValue = mTimes[i]; + } + } + } + } + + // Image element classification. + inline bool IsValid(size_t i) const + { + return (Real)0 <= mTimes[i] && mTimes[i] < std::numeric_limits::max(); + } + + inline bool IsTrial(size_t i) const + { + return mTrials[i] != nullptr; + } + + inline bool IsFar(size_t i) const + { + return mTimes[i] == std::numeric_limits::max(); + } + + inline bool IsZeroSpeed(size_t i) const + { + return mTimes[i] == -std::numeric_limits::max(); + } + + inline bool IsInterior(size_t i) const + { + return IsValid(i) && !IsTrial(i); + } + + void GetInterior(std::vector& interior) const + { + interior.clear(); + for (size_t i = 0; i < mQuantity; ++i) + { + if (IsValid(i) && !IsTrial(i)) + { + interior.push_back(i); + } + } + } + + virtual void GetBoundary(std::vector& boundary) const = 0; + virtual bool IsBoundary(size_t i) const = 0; + + // Run one step of the fast marching algorithm. + virtual void Iterate() = 0; + + protected: + size_t mQuantity; + std::vector mTimes; + std::vector mInvSpeeds; + MinHeap mHeap; + std::vector::Record*> mTrials; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FastMarch2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FastMarch2.h new file mode 100644 index 0000000..06d9c80 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FastMarch2.h @@ -0,0 +1,351 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The topic of fast marching methods are discussed in the book +// Level Set Methods and Fast Marching Methods: +// Evolving Interfaces in Computational Geometry, Fluid Mechanics, +// Computer Vision, and Materials Science +// J.A. Sethian, +// Cambridge University Press, 1999 + +namespace WwiseGTE +{ + template + class FastMarch2 : public FastMarch + { + public: + // Construction and destruction. + FastMarch2(size_t xBound, size_t yBound, Real xSpacing, Real ySpacing, + std::vector const& seeds, std::vector const& speeds) + : + FastMarch(xBound * yBound, seeds, speeds) + { + Initialize(xBound, yBound, xSpacing, ySpacing); + } + + FastMarch2(size_t xBound, size_t yBound, Real xSpacing, Real ySpacing, + std::vector const& seeds, Real speed) + : + FastMarch(xBound * yBound, seeds, speed) + { + Initialize(xBound, yBound, xSpacing, ySpacing); + } + + virtual ~FastMarch2() + { + } + + // Member access. + inline size_t GetXBound() const + { + return mXBound; + } + + inline size_t GetYBound() const + { + return mYBound; + } + + inline Real GetXSpacing() const + { + return mXSpacing; + } + + inline Real GetYSpacing() const + { + return mYSpacing; + } + + inline size_t Index(size_t x, size_t y) const + { + return x + mXBound * y; + } + + // Pixel classification. + virtual void GetBoundary(std::vector& boundary) const override + { + for (size_t i = 0; i < this->mQuantity; ++i) + { + if (this->IsValid(i) && !this->IsTrial(i)) + { + if (this->IsTrial(i - 1) + || this->IsTrial(i + 1) + || this->IsTrial(i - mXBound) + || this->IsTrial(i + mXBound)) + { + boundary.push_back(i); + } + } + } + } + + virtual bool IsBoundary(size_t i) const override + { + if (this->IsValid(i) && !this->IsTrial(i)) + { + if (this->IsTrial(i - 1) + || this->IsTrial(i + 1) + || this->IsTrial(i - mXBound) + || this->IsTrial(i + mXBound)) + { + return true; + } + } + return false; + } + + // Run one step of the fast marching algorithm. + virtual void Iterate() override + { + // Remove the minimum trial value from the heap. + size_t i; + Real value; + this->mHeap.Remove(i, value); + + // Promote the trial value to a known value. The value was + // negative but is now nonnegative (the heap stores only + // nonnegative numbers). + this->mTrials[i] = nullptr; + + // All trial pixels must be updated. All far neighbors must become trial + // pixels. + size_t iM1 = i - 1; + if (this->IsTrial(iM1)) + { + ComputeTime(iM1); + this->mHeap.Update(this->mTrials[iM1], this->mTimes[iM1]); + } + else if (this->IsFar(iM1)) + { + ComputeTime(iM1); + this->mTrials[iM1] = this->mHeap.Insert(iM1, this->mTimes[iM1]); + } + + size_t iP1 = i + 1; + if (this->IsTrial(iP1)) + { + ComputeTime(iP1); + this->mHeap.Update(this->mTrials[iP1], this->mTimes[iP1]); + } + else if (this->IsFar(iP1)) + { + ComputeTime(iP1); + this->mTrials[iP1] = this->mHeap.Insert(iP1, this->mTimes[iP1]); + } + + size_t iMXB = i - mXBound; + if (this->IsTrial(iMXB)) + { + ComputeTime(iMXB); + this->mHeap.Update(this->mTrials[iMXB], this->mTimes[iMXB]); + } + else if (this->IsFar(iMXB)) + { + ComputeTime(iMXB); + this->mTrials[iMXB] = this->mHeap.Insert(iMXB, this->mTimes[iMXB]); + } + + size_t iPXB = i + mXBound; + if (this->IsTrial(iPXB)) + { + ComputeTime(iPXB); + this->mHeap.Update(this->mTrials[iPXB], this->mTimes[iPXB]); + } + else if (this->IsFar(iPXB)) + { + ComputeTime(iPXB); + this->mTrials[iPXB] = this->mHeap.Insert(iPXB, this->mTimes[iPXB]); + } + } + + protected: + // Called by the constructors. + void Initialize(size_t xBound, size_t yBound, Real xSpacing, Real ySpacing) + { + mXBound = xBound; + mYBound = yBound; + mXBoundM1 = mXBound - 1; + mYBoundM1 = mYBound - 1; + mXSpacing = xSpacing; + mYSpacing = ySpacing; + mInvXSpacing = (Real)1 / xSpacing; + mInvYSpacing = (Real)1 / ySpacing; + + // Boundary pixels are marked as zero speed to allow us to avoid + // having to process the boundary pixels separately during the + // iteration. + size_t x, y, i; + + // vertex (0,0) + i = Index(0, 0); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + + // vertex (xmax,0) + i = Index(mXBoundM1, 0); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + + // vertex (0,ymax) + i = Index(0, mYBoundM1); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + + // vertex (xmax,ymax) + i = Index(mXBoundM1, mYBoundM1); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + + // edges (x,0) and (x,ymax) + for (x = 0; x < mXBound; ++x) + { + i = Index(x, 0); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + i = Index(x, mYBoundM1); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + } + + // edges (0,y) and (xmax,y) + for (y = 0; y < mYBound; ++y) + { + i = Index(0, y); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + i = Index(mXBoundM1, y); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + } + + // Compute the first batch of trial pixels. These are pixels a + // grid distance of one away from the seed pixels. + for (y = 1; y < mYBoundM1; ++y) + { + for (x = 1; x < mXBoundM1; ++x) + { + i = Index(x, y); + if (this->IsFar(i)) + { + if ((this->IsValid(i - 1) && !this->IsTrial(i - 1)) + || (this->IsValid(i + 1) && !this->IsTrial(i + 1)) + || (this->IsValid(i - mXBound) && !this->IsTrial(i - mXBound)) + || (this->IsValid(i + mXBound) && !this->IsTrial(i + mXBound))) + { + ComputeTime(i); + this->mTrials[i] = this->mHeap.Insert(i, this->mTimes[i]); + } + } + } + } + } + + // Called by Iterate(). + void ComputeTime(size_t i) + { + bool hasXTerm; + Real xConst; + if (this->IsValid(i - 1)) + { + hasXTerm = true; + xConst = this->mTimes[i - 1]; + if (this->IsValid(i + 1)) + { + if (this->mTimes[i + 1] < xConst) + { + xConst = this->mTimes[i + 1]; + } + } + } + else if (this->IsValid(i + 1)) + { + hasXTerm = true; + xConst = this->mTimes[i + 1]; + } + else + { + hasXTerm = false; + xConst = (Real)0; + } + + bool hasYTerm; + Real yConst; + if (this->IsValid(i - mXBound)) + { + hasYTerm = true; + yConst = this->mTimes[i - mXBound]; + if (this->IsValid(i + mXBound)) + { + if (this->mTimes[i + mXBound] < yConst) + { + yConst = this->mTimes[i + mXBound]; + } + } + } + else if (this->IsValid(i + mXBound)) + { + hasYTerm = true; + yConst = this->mTimes[i + mXBound]; + } + else + { + hasYTerm = false; + yConst = (Real)0; + } + + if (hasXTerm) + { + if (hasYTerm) + { + Real sum = xConst + yConst; + Real diff = xConst - yConst; + Real discr = (Real)2 * this->mInvSpeeds[i] * this->mInvSpeeds[i] - diff * diff; + if (discr >= (Real)0) + { + // The quadratic equation has a real-valued solution. + // Choose the largest positive root for the crossing + // time. + this->mTimes[i] = (Real)0.5 * (sum + std::sqrt(discr)); + } + else + { + // The quadratic equation does not have a real-valued + // solution. This can happen when the speed is so + // large that the time gradient has very small length, + // which means that the time has not changed + // significantly from the neighbors to the current + // pixel. Just choose the maximum time of the + // neighbors. (Is there a better choice?) + this->mTimes[i] = (diff >= (Real)0 ? xConst : yConst); + } + } + else + { + // The equation is linear. + this->mTimes[i] = this->mInvSpeeds[i] + xConst; + } + } + else if (hasYTerm) + { + // The equation is linear. + this->mTimes[i] = this->mInvSpeeds[i] + yConst; + } + else + { + // Assert: The pixel must have at least one known neighbor. + } + } + + size_t mXBound, mYBound, mXBoundM1, mYBoundM1; + Real mXSpacing, mYSpacing, mInvXSpacing, mInvYSpacing; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FastMarch3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FastMarch3.h new file mode 100644 index 0000000..3fba3c2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FastMarch3.h @@ -0,0 +1,609 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The topic of fast marching methods are discussed in the book +// Level Set Methods and Fast Marching Methods: +// Evolving Interfaces in Computational Geometry, Fluid Mechanics, +// Computer Vision, and Materials Science +// J.A. Sethian, +// Cambridge University Press, 1999 + +namespace WwiseGTE +{ + template + class FastMarch3 : public FastMarch + { + public: + // Construction and destruction. + FastMarch3(size_t xBound, size_t yBound, size_t zBound, + Real xSpacing, Real ySpacing, Real zSpacing, + std::vector const& seeds, std::vector const& speeds) + : + FastMarch(xBound * yBound * zBound, seeds, speeds) + { + Initialize(xBound, yBound, zBound, xSpacing, ySpacing, zSpacing); + } + + FastMarch3(size_t xBound, size_t yBound, size_t zBound, + Real xSpacing, Real ySpacing, Real zSpacing, + std::vector const& seeds, Real speed) + : + FastMarch(xBound * yBound * zBound, seeds, speed) + { + Initialize(xBound, yBound, zBound, xSpacing, ySpacing, zSpacing); + } + + virtual ~FastMarch3() + { + } + + // Member access. + inline size_t GetXBound() const + { + return mXBound; + } + + inline size_t GetYBound() const + { + return mYBound; + } + + inline size_t GetZBound() const + { + return mZBound; + } + + inline Real GetXSpacing() const + { + return mXSpacing; + } + + inline Real GetYSpacing() const + { + return mYSpacing; + } + + inline Real GetZSpacing() const + { + return mZSpacing; + } + + inline size_t Index(size_t x, size_t y, size_t z) const + { + return x + mXBound * (y + mYBound * z); + } + + // Voxel classification. + virtual void GetBoundary(std::vector& boundary) const override + { + for (size_t i = 0; i < this->mQuantity; ++i) + { + if (this->IsValid(i) && !this->IsTrial(i)) + { + if (this->IsTrial(i - 1) + || this->IsTrial(i + 1) + || this->IsTrial(i - mXBound) + || this->IsTrial(i + mXBound) + || this->IsTrial(i - mXYBound) + || this->IsTrial(i + mXYBound)) + { + boundary.push_back(i); + } + } + } + } + + virtual bool IsBoundary(size_t i) const override + { + if (this->IsValid(i) && !this->IsTrial(i)) + { + if (this->IsTrial(i - 1) + || this->IsTrial(i + 1) + || this->IsTrial(i - mXBound) + || this->IsTrial(i + mXBound) + || this->IsTrial(i - mXYBound) + || this->IsTrial(i + mXYBound)) + { + return true; + } + } + return false; + } + + // Run one step of the fast marching algorithm. + virtual void Iterate() override + { + // Remove the minimum trial value from the heap. + size_t i; + Real value; + this->mHeap.Remove(i, value); + + // Promote the trial value to a known value. The value was + // negative but is now nonnegative (the heap stores only + // nonnegative numbers). + this->mTrials[i] = nullptr; + + // All trial pixels must be updated. All far neighbors must + // become trial pixels. + size_t iM1 = i - 1; + if (this->IsTrial(iM1)) + { + ComputeTime(iM1); + this->mHeap.Update(this->mTrials[iM1], this->mTimes[iM1]); + } + else if (this->IsFar(iM1)) + { + ComputeTime(iM1); + this->mTrials[iM1] = this->mHeap.Insert(iM1, this->mTimes[iM1]); + } + + size_t iP1 = i + 1; + if (this->IsTrial(iP1)) + { + ComputeTime(iP1); + this->mHeap.Update(this->mTrials[iP1], this->mTimes[iP1]); + } + else if (this->IsFar(iP1)) + { + ComputeTime(iP1); + this->mTrials[iP1] = this->mHeap.Insert(iP1, this->mTimes[iP1]); + } + + size_t iMXB = i - mXBound; + if (this->IsTrial(iMXB)) + { + ComputeTime(iMXB); + this->mHeap.Update(this->mTrials[iMXB], this->mTimes[iMXB]); + } + else if (this->IsFar(iMXB)) + { + ComputeTime(iMXB); + this->mTrials[iMXB] = this->mHeap.Insert(iMXB, this->mTimes[iMXB]); + } + + size_t iPXB = i + mXBound; + if (this->IsTrial(iPXB)) + { + ComputeTime(iPXB); + this->mHeap.Update(this->mTrials[iPXB], this->mTimes[iPXB]); + } + else if (this->IsFar(iPXB)) + { + ComputeTime(iPXB); + this->mTrials[iPXB] = this->mHeap.Insert(iPXB, this->mTimes[iPXB]); + } + + size_t iMXYB = i - mXYBound; + if (this->IsTrial(iMXYB)) + { + ComputeTime(iMXYB); + this->mHeap.Update(this->mTrials[iMXYB], this->mTimes[iMXYB]); + } + else if (this->IsFar(iMXYB)) + { + ComputeTime(iMXYB); + this->mTrials[iMXYB] = this->mHeap.Insert(iMXYB, this->mTimes[iMXYB]); + } + + size_t iPXYB = i + mXYBound; + if (this->IsTrial(iPXYB)) + { + ComputeTime(iPXYB); + this->mHeap.Update(this->mTrials[iPXYB], this->mTimes[iPXYB]); + } + else if (this->IsFar(iPXYB)) + { + ComputeTime(iPXYB); + this->mTrials[iPXYB] = this->mHeap.Insert(iPXYB, this->mTimes[iPXYB]); + } + } + + protected: + // Called by the constructors. + void Initialize(size_t xBound, size_t yBound, size_t zBound, + Real xSpacing, Real ySpacing, Real zSpacing) + { + mXBound = xBound; + mYBound = yBound; + mZBound = zBound; + mXYBound = xBound * yBound; + mXBoundM1 = mXBound - 1; + mYBoundM1 = mYBound - 1; + mZBoundM1 = mZBound - 1; + mXSpacing = xSpacing; + mYSpacing = ySpacing; + mZSpacing = zSpacing; + mInvXSpacing = (Real)1 / xSpacing; + mInvYSpacing = (Real)1 / ySpacing; + mInvZSpacing = (Real)1 / zSpacing; + + // Boundary pixels are marked as zero speed to allow us to avoid + // having to process the boundary pixels separately during the + // iteration. + size_t x, y, z, i; + + // vertex (0,0,0) + i = Index(0, 0, 0); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + + // vertex (xmax,0,0) + i = Index(mXBoundM1, 0, 0); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + + // vertex (0,ymax,0) + i = Index(0, mYBoundM1, 0); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + + // vertex (xmax,ymax,0) + i = Index(mXBoundM1, mYBoundM1, 0); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + + // vertex (0,0,zmax) + i = Index(0, 0, mZBoundM1); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + + // vertex (xmax,0,zmax) + i = Index(mXBoundM1, 0, mZBoundM1); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + + // vertex (0,ymax,zmax) + i = Index(0, mYBoundM1, mZBoundM1); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + + // vertex (xmax,ymax,zmax) + i = Index(mXBoundM1, mYBoundM1, mZBoundM1); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + + // edges (x,0,0) and (x,ymax,0) + for (x = 0; x < mXBound; ++x) + { + i = Index(x, 0, 0); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + i = Index(x, mYBoundM1, 0); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + } + + // edges (0,y,0) and (xmax,y,0) + for (y = 0; y < mYBound; ++y) + { + i = Index(0, y, 0); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + i = Index(mXBoundM1, y, 0); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + } + + // edges (x,0,zmax) and (x,ymax,zmax) + for (x = 0; x < mXBound; ++x) + { + i = Index(x, 0, mZBoundM1); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + i = Index(x, mYBoundM1, mZBoundM1); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + } + + // edges (0,y,zmax) and (xmax,y,zmax) + for (y = 0; y < mYBound; ++y) + { + i = Index(0, y, mZBoundM1); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + i = Index(mXBoundM1, y, mZBoundM1); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + } + + // edges (0,0,z) and (xmax,0,z) + for (z = 0; z < mZBound; ++z) + { + i = Index(0, 0, z); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + i = Index(mXBoundM1, 0, z); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + } + + // edges (0,ymax,z) and (xmax,ymax,z) + for (z = 0; z < mZBound; ++z) + { + i = Index(0, mYBoundM1, z); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + i = Index(mXBoundM1, mYBoundM1, z); + this->mInvSpeeds[i] = std::numeric_limits::max(); + this->mTimes[i] = -std::numeric_limits::max(); + } + + // Compute the first batch of trial pixels. These are pixels a grid + // distance of one away from the seed pixels. + for (z = 1; z < mZBoundM1; ++z) + { + for (y = 1; y < mYBoundM1; ++y) + { + for (x = 1; x < mXBoundM1; ++x) + { + i = Index(x, y, z); + if (this->IsFar(i)) + { + if ((this->IsValid(i - 1) && !this->IsTrial(i - 1)) + || (this->IsValid(i + 1) && !this->IsTrial(i + 1)) + || (this->IsValid(i - mXBound) && !this->IsTrial(i - mXBound)) + || (this->IsValid(i + mXBound) && !this->IsTrial(i + mXBound)) + || (this->IsValid(i - mXYBound) && !this->IsTrial(i - mXYBound)) + || (this->IsValid(i + mXYBound) && !this->IsTrial(i + mXYBound))) + { + ComputeTime(i); + this->mTrials[i] = this->mHeap.Insert(i, this->mTimes[i]); + } + } + } + } + } + } + + // Called by Iterate(). + void ComputeTime(size_t i) + { + bool hasXTerm; + Real xConst; + if (this->IsValid(i - 1)) + { + hasXTerm = true; + xConst = this->mTimes[i - 1]; + if (this->IsValid(i + 1)) + { + if (this->mTimes[i + 1] < xConst) + { + xConst = this->mTimes[i + 1]; + } + } + } + else if (this->IsValid(i + 1)) + { + hasXTerm = true; + xConst = this->mTimes[i + 1]; + } + else + { + hasXTerm = false; + xConst = (Real)0; + } + + bool hasYTerm; + Real yConst; + if (this->IsValid(i - mXBound)) + { + hasYTerm = true; + yConst = this->mTimes[i - mXBound]; + if (this->IsValid(i + mXBound)) + { + if (this->mTimes[i + mXBound] < yConst) + { + yConst = this->mTimes[i + mXBound]; + } + } + } + else if (this->IsValid(i + mXBound)) + { + hasYTerm = true; + yConst = this->mTimes[i + mXBound]; + } + else + { + hasYTerm = false; + yConst = (Real)0; + } + + bool hasZTerm; + Real zConst; + if (this->IsValid(i - mXYBound)) + { + hasZTerm = true; + zConst = this->mTimes[i - mXYBound]; + if (this->IsValid(i + mXYBound)) + { + if (this->mTimes[i + mXYBound] < zConst) + { + zConst = this->mTimes[i + mXYBound]; + } + } + } + else if (this->IsValid(i + mXYBound)) + { + hasZTerm = true; + zConst = this->mTimes[i + mXYBound]; + } + else + { + hasZTerm = false; + zConst = (Real)0; + } + + Real sum, diff, discr; + + if (hasXTerm) + { + if (hasYTerm) + { + if (hasZTerm) + { + // xyz + sum = xConst + yConst + zConst; + discr = (Real)3 * this->mInvSpeeds[i] * this->mInvSpeeds[i]; + diff = xConst - yConst; + discr -= diff * diff; + diff = xConst - zConst; + discr -= diff * diff; + diff = yConst - zConst; + discr -= diff * diff; + if (discr >= (Real)0) + { + // The quadratic equation has a real-valued + // solution. Choose the largest positive root for + // the crossing time. + this->mTimes[i] = (sum + std::sqrt(discr)) / (Real)3; + } + else + { + // The quadratic equation does not have a + // real-valued solution. This can happen when the + // speed is so large that the time gradient has + // very small length, which means that the time + // has not changed significantly from the + // neighbors to the current pixel. Just choose + // the maximum time of the neighbors. (Is there a + // better choice?) + this->mTimes[i] = xConst; + if (yConst > this->mTimes[i]) + { + this->mTimes[i] = yConst; + } + if (zConst > this->mTimes[i]) + { + this->mTimes[i] = zConst; + } + } + } + else + { + // xy + sum = xConst + yConst; + diff = xConst - yConst; + discr = (Real)2 * this->mInvSpeeds[i] * this->mInvSpeeds[i] - diff * diff; + if (discr >= (Real)0) + { + // The quadratic equation has a real-valued + // solution. Choose the largest positive root for + // the crossing time. + this->mTimes[i] = (Real)0.5 * (sum + std::sqrt(discr)); + } + else + { + // The quadratic equation does not have a + // real-valued solution. This can happen when the + // speed is so large that the time gradient has + // very small length, which means that the time + // has not changed significantly from the + // neighbors to the current pixel. Just choose + // the maximum time of the neighbors. (Is there a + // better choice?) + this->mTimes[i] = (diff >= (Real)0 ? xConst : yConst); + } + } + } + else + { + if (hasZTerm) + { + // xz + sum = xConst + zConst; + diff = xConst - zConst; + discr = (Real)2 * this->mInvSpeeds[i] * this->mInvSpeeds[i] - diff * diff; + if (discr >= (Real)0) + { + // The quadratic equation has a real-valued + // solution. Choose the largest positive root for + // the crossing time. + this->mTimes[i] = (Real)0.5 * (sum + std::sqrt(discr)); + } + else + { + // The quadratic equation does not have a + // real-valued solution. This can happen when the + // speed is so large that the time gradient has + // very small length, which means that the time + // has not changed significantly from the + // neighbors to the current pixel. Just choose + // the maximum time of the neighbors. (Is there a + // better choice?) + this->mTimes[i] = (diff >= (Real)0 ? xConst : zConst); + } + } + else + { + // x + this->mTimes[i] = this->mInvSpeeds[i] + xConst; + } + } + } + else + { + if (hasYTerm) + { + if (hasZTerm) + { + // yz + sum = yConst + zConst; + diff = yConst - zConst; + discr = (Real)2 * this->mInvSpeeds[i] * this->mInvSpeeds[i] - diff * diff; + if (discr >= (Real)0) + { + // The quadratic equation has a real-valued + // solution. Choose the largest positive root for + // the crossing time. + this->mTimes[i] = (Real)0.5 * (sum + std::sqrt(discr)); + } + else + { + // The quadratic equation does not have a + // real-valued solution. This can happen when the + // speed is so large that the time gradient has + // very small length, which means that the time + // has not changed significantly from the + // neighbors to the current pixel. Just choose + // the maximum time of the neighbors. (Is there a + // better choice?) + this->mTimes[i] = (diff >= (Real)0 ? yConst : zConst); + } + } + else + { + // y + this->mTimes[i] = this->mInvSpeeds[i] + yConst; + } + } + else + { + if (hasZTerm) + { + // z + this->mTimes[i] = this->mInvSpeeds[i] + zConst; + } + else + { + // Assert: The pixel must have at least one valid + // neighbor. + } + } + } + } + + size_t mXBound, mYBound, mZBound, mXYBound; + size_t mXBoundM1, mYBoundM1, mZBoundM1; + Real mXSpacing, mYSpacing, mZSpacing; + Real mInvXSpacing, mInvYSpacing, mInvZSpacing; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FeatureKey.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FeatureKey.h new file mode 100644 index 0000000..d648f90 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FeatureKey.h @@ -0,0 +1,65 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class FeatureKey + { + protected: + // Abstract base class with V[] uninitialized. The derived classes + // must set the V[] values accordingly. + // + // An ordered feature key has V[0] = min(V[]) with + // (V[0],V[1],...,V[N-1]) a permutation of N inputs with an even + // number of transpositions. + // + // An unordered feature key has V[0] < V[1] < ... < V[N-1]. + // + // Note that the word 'order' is about the geometry of the feature, not + // the comparison order for any sorting. + FeatureKey() = default; + + public: + bool operator==(FeatureKey const& key) const + { + return V == key.V; + } + + bool operator!=(FeatureKey const& key) const + { + return V != key.V; + } + + bool operator<(FeatureKey const& key) const + { + return V < key.V; + } + + bool operator<=(FeatureKey const& key) const + { + return V <= key.V; + } + + bool operator>(FeatureKey const& key) const + { + return V > key.V; + } + + bool operator>=(FeatureKey const& key) const + { + return V >= key.V; + } + + std::array V; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FrenetFrame.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FrenetFrame.h new file mode 100644 index 0000000..d2cd4e4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/FrenetFrame.h @@ -0,0 +1,132 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class FrenetFrame2 + { + public: + // Construction. The curve must persist as long as the FrenetFrame2 + // object does. + FrenetFrame2(std::shared_ptr> const& curve) + : + mCurve(curve) + { + } + + // The normal is perpendicular to the tangent, rotated clockwise by + // pi/2 radians. + void operator()(Real t, Vector2& position, Vector2& tangent, + Vector2& normal) const + { + std::array, 2> jet; + mCurve->Evaluate(t, 1, jet.data()); + position = jet[0]; + tangent = jet[1]; + Normalize(tangent); + normal = Perp(tangent); + } + + Real GetCurvature(Real t) const + { + std::array, 3> jet; + mCurve->Evaluate(t, 2, jet.data()); + Real speedSqr = Dot(jet[1], jet[1]); + if (speedSqr > (Real)0) + { + Real numer = DotPerp(jet[1], jet[2]); + Real denom = std::pow(speedSqr, (Real)1.5); + return numer / denom; + } + else + { + // Curvature is indeterminate, just return 0. + return (Real)0; + } + } + + private: + std::shared_ptr> mCurve; + }; + + + template + class FrenetFrame3 + { + public: + // Construction. The curve must persist as long as the FrenetFrame3 + // object does. + FrenetFrame3(std::shared_ptr> const& curve) + : + mCurve(curve) + { + } + + // The binormal is Cross(tangent, normal). + void operator()(Real t, Vector3& position, Vector3& tangent, + Vector3& normal, Vector3& binormal) const + { + std::array, 3> jet; + mCurve->Evaluate(t, 2, jet.data()); + position = jet[0]; + Real VDotV = Dot(jet[1], jet[1]); + Real VDotA = Dot(jet[1], jet[2]); + normal = VDotV * jet[2] - VDotA * jet[1]; + Normalize(normal); + tangent = jet[1]; + Normalize(tangent); + binormal = Cross(tangent, normal); + } + + Real GetCurvature(Real t) const + { + std::array, 3> jet; + mCurve->Evaluate(t, 2, jet.data()); + Real speedSqr = Dot(jet[1], jet[1]); + if (speedSqr > (Real)0) + { + Real numer = Length(Cross(jet[1], jet[2])); + Real denom = std::pow(speedSqr, (Real)1.5); + return numer / denom; + } + else + { + // Curvature is indeterminate, just return 0. + return (Real)0; + } + } + + Real GetTorsion(Real t) const + { + std::array, 4> jet; + mCurve->Evaluate(t, 3, jet.data()); + Vector3 cross = Cross(jet[1], jet[2]); + Real denom = Dot(cross, cross); + if (denom > (Real)0) + { + Real numer = Dot(cross, jet[3]); + return numer / denom; + } + else + { + // Torsion is indeterminate, just return 0. + return (Real)0; + } + } + + private: + std::shared_ptr> mCurve; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Frustum3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Frustum3.h new file mode 100644 index 0000000..1e259de --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Frustum3.h @@ -0,0 +1,220 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// Orthogonal frustum. Let E be the origin, D be the direction vector, U be +// the up vector, and R be the right vector. Let u > 0 and r > 0 be the +// extents in the U and R directions, respectively. Let n and f be the +// extents in the D direction with 0 < n < f. The four corners of the frustum +// in the near plane are E + n*D + s0*u*U + s1*r*R where |s0| = |s1| = 1 (four +// choices). The four corners of the frustum in the far plane are +// E + f*D + (f/n)*(s0*u*U + s1*r*R) where |s0| = |s1| = 1 (four choices). + +namespace WwiseGTE +{ + template + class Frustum3 + { + public: + // Construction and destruction. The default constructor sets the + // following values: origin (E) to (0,0,0), dVector (D) to (0,0,1), + // uVector (U) to (0,1,0), rVector (R) to (1,0,0), dMin (n) to 1, + // dMax (f) to 2, uBound (u) to 1, and rBound (r) to 1. + Frustum3() + : + origin(Vector3::Zero()), + dVector(Vector3::Unit(2)), + uVector(Vector3::Unit(1)), + rVector(Vector3::Unit(0)), + dMin((Real)1), + dMax((Real)2), + uBound((Real)1), + rBound((Real)1) + { + Update(); + } + + Frustum3(Vector3 const& inOrigin, Vector3 const& inDVector, + Vector3 const& inUVector, Vector3 const& inRVector, + Real inDMin, Real inDMax, Real inUBound, Real inRBound) + : + origin(inOrigin), + dVector(inDVector), + uVector(inUVector), + rVector(inRVector), + dMin(inDMin), + dMax(inDMax), + uBound(inUBound), + rBound(inRBound) + { + Update(); + } + + // The Update() function must be called whenever changes are made to + // dMin, dMax, uBound or rBound. The values mDRatio, mMTwoUF and + // mMTwoRF are dependent on the changes, so call the Get*() accessors + // only after the Update() call. + void Update() + { + mDRatio = dMax / dMin; + mMTwoUF = (Real)-2 * uBound * dMax; + mMTwoRF = (Real)-2 * rBound * dMax; + } + + inline Real GetDRatio() const + { + return mDRatio; + } + + inline Real GetMTwoUF() const + { + return mMTwoUF; + } + + inline Real GetMTwoRF() const + { + return mMTwoRF; + } + + void ComputeVertices(std::array, 8>& vertex) const + { + Vector3 dScaled = dMin * dVector; + Vector3 uScaled = uBound * uVector; + Vector3 rScaled = rBound * rVector; + + vertex[0] = dScaled - uScaled - rScaled; + vertex[1] = dScaled - uScaled + rScaled; + vertex[2] = dScaled + uScaled + rScaled; + vertex[3] = dScaled + uScaled - rScaled; + + for (int i = 0, ip = 4; i < 4; ++i, ++ip) + { + vertex[ip] = origin + mDRatio * vertex[i]; + vertex[i] += origin; + } + } + + Vector3 origin, dVector, uVector, rVector; + Real dMin, dMax, uBound, rBound; + + public: + // Comparisons to support sorted containers. + bool operator==(Frustum3 const& frustum) const + { + return origin == frustum.origin + && dVector == frustum.dVector + && uVector == frustum.uVector + && rVector == frustum.rVector + && dMin == frustum.dMin + && dMax == frustum.dMax + && uBound == frustum.uBound + && rBound == frustum.rBound; + } + + bool operator!=(Frustum3 const& frustum) const + { + return !operator==(frustum); + } + + bool operator< (Frustum3 const& frustum) const + { + if (origin < frustum.origin) + { + return true; + } + + if (origin > frustum.origin) + { + return false; + } + + if (dVector < frustum.dVector) + { + return true; + } + + if (dVector > frustum.dVector) + { + return false; + } + + if (uVector < frustum.uVector) + { + return true; + } + + if (uVector > frustum.uVector) + { + return false; + } + + if (rVector < frustum.rVector) + { + return true; + } + + if (rVector > frustum.rVector) + { + return false; + } + + if (dMin < frustum.dMin) + { + return true; + } + + if (dMin > frustum.dMin) + { + return false; + } + + if (dMax < frustum.dMax) + { + return true; + } + + if (dMax > frustum.dMax) + { + return false; + } + + if (uBound < frustum.uBound) + { + return true; + } + + if (uBound > frustum.uBound) + { + return false; + } + + return rBound < frustum.rBound; + } + + bool operator<=(Frustum3 const& frustum) const + { + return !frustum.operator<(*this); + } + + bool operator> (Frustum3 const& frustum) const + { + return frustum.operator<(*this); + } + + bool operator>=(Frustum3 const& frustum) const + { + return !operator<(frustum); + } + + protected: + // Quantities derived from the constructor inputs. + Real mDRatio, mMTwoUF, mMTwoRF; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GMatrix.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GMatrix.h new file mode 100644 index 0000000..f8921ca --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GMatrix.h @@ -0,0 +1,692 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class GMatrix + { + public: + // The table is length zero and mNumRows and mNumCols are set to zero. + GMatrix() + : + mNumRows(0), + mNumCols(0) + { + } + + // The table is length numRows*numCols and the elements are + // initialized to zero. + GMatrix(int numRows, int numCols) + { + SetSize(numRows, numCols); + std::fill(mElements.begin(), mElements.end(), (Real)0); + } + + // For 0 <= r < numRows and 0 <= c < numCols, element (r,c) is 1 and + // all others are 0. If either of r or c is invalid, the zero matrix + // is created. This is a convenience for creating the standard + // Euclidean basis matrices; see also MakeUnit(int,int) and + // Unit(int,int). + GMatrix(int numRows, int numCols, int r, int c) + { + SetSize(numRows, numCols); + MakeUnit(r, c); + } + + // The copy constructor, destructor, and assignment operator are + // generated by the compiler. + + // Member access for which the storage representation is transparent. + // The matrix entry in row r and column c is A(r,c). The first + // operator() returns a const reference rather than a Real value. + // This supports writing via standard file operations that require a + // const pointer to data. + void SetSize(int numRows, int numCols) + { + if (numRows > 0 && numCols > 0) + { + mNumRows = numRows; + mNumCols = numCols; + mElements.resize(mNumRows * mNumCols); + } + else + { + mNumRows = 0; + mNumCols = 0; + mElements.clear(); + } + } + + inline void GetSize(int& numRows, int& numCols) const + { + numRows = mNumRows; + numCols = mNumCols; + } + + inline int GetNumRows() const + { + return mNumRows; + } + + inline int GetNumCols() const + { + return mNumCols; + } + + inline int GetNumElements() const + { + return static_cast(mElements.size()); + } + + inline Real const& operator()(int r, int c) const + { + if (0 <= r && r < GetNumRows() && 0 <= c && c < GetNumCols()) + { +#if defined(GTE_USE_ROW_MAJOR) + return mElements[c + mNumCols * r]; +#else + return mElements[r + mNumRows * c]; +#endif + } + LogError("Invalid index."); + } + + inline Real& operator()(int r, int c) + { + if (0 <= r && r < GetNumRows() && 0 <= c && c < GetNumCols()) + { +#if defined(GTE_USE_ROW_MAJOR) + return mElements[c + mNumCols * r]; +#else + return mElements[r + mNumRows * c]; +#endif + } + LogError("Invalid index."); + } + + // Member access by rows or by columns. The input vectors must have + // the correct number of elements for the matrix size. + void SetRow(int r, GVector const& vec) + { + if (0 <= r && r < mNumRows) + { + if (vec.GetSize() == GetNumCols()) + { + for (int c = 0; c < mNumCols; ++c) + { + operator()(r, c) = vec[c]; + } + } + LogError("Mismatched sizes."); + } + LogError("Invalid index."); + } + + void SetCol(int c, GVector const& vec) + { + if (0 <= c && c < mNumCols) + { + if (vec.GetSize() == GetNumRows()) + { + for (int r = 0; r < mNumRows; ++r) + { + operator()(r, c) = vec[r]; + } + return; + } + LogError("Mismatched sizes."); + } + LogError("Invalid index."); + } + + GVector GetRow(int r) const + { + if (0 <= r && r < mNumRows) + { + GVector vec(mNumCols); + for (int c = 0; c < mNumCols; ++c) + { + vec[c] = operator()(r, c); + } + return vec; + } + LogError("Invalid index."); + } + + GVector GetCol(int c) const + { + if (0 <= c && c < mNumCols) + { + GVector vec(mNumRows); + for (int r = 0; r < mNumRows; ++r) + { + vec[r] = operator()(r, c); + } + return vec; + } + LogError("Invalid index."); + } + + // Member access by 1-dimensional index. NOTE: These accessors are + // useful for the manipulation of matrix entries when it does not + // matter whether storage is row-major or column-major. Do not use + // constructs such as M[c+NumCols*r] or M[r+NumRows*c] that expose the + // storage convention. + inline Real const& operator[](int i) const + { + return mElements[i]; + } + + inline Real& operator[](int i) + { + return mElements[i]; + } + + // Comparisons for sorted containers and geometric ordering. + inline bool operator==(GMatrix const& mat) const + { + return mNumRows == mat.mNumRows && mNumCols == mat.mNumCols + && mElements == mat.mElements; + } + + inline bool operator!=(GMatrix const& mat) const + { + return mNumRows == mat.mNumRows && mNumCols == mat.mNumCols + && mElements != mat.mElements; + } + + inline bool operator< (GMatrix const& mat) const + { + return mNumRows == mat.mNumRows && mNumCols == mat.mNumCols + && mElements < mat.mElements; + } + + inline bool operator<=(GMatrix const& mat) const + { + return mNumRows == mat.mNumRows && mNumCols == mat.mNumCols + && mElements <= mat.mElements; + } + + inline bool operator> (GMatrix const& mat) const + { + return mNumRows == mat.mNumRows && mNumCols == mat.mNumCols + && mElements > mat.mElements; + } + + inline bool operator>=(GMatrix const& mat) const + { + return mNumRows == mat.mNumRows && mNumCols == mat.mNumCols + && mElements >= mat.mElements; + } + + // Special matrices. + + // All components are 0. + void MakeZero() + { + std::fill(mElements.begin(), mElements.end(), (Real)0); + } + + // Component (r,c) is 1, all others zero. + void MakeUnit(int r, int c) + { + if (0 <= r && r < mNumRows && 0 <= c && c < mNumCols) + { + MakeZero(); + operator()(r, c) = (Real)1; + return; + } + LogError("Invalid index."); + } + + // Diagonal entries 1, others 0, even when nonsquare. + void MakeIdentity() + { + MakeZero(); + int const numDiagonal = (mNumRows <= mNumCols ? mNumRows : mNumCols); + for (int i = 0; i < numDiagonal; ++i) + { + operator()(i, i) = (Real)1; + } + } + + static GMatrix Zero(int numRows, int numCols) + { + GMatrix M(numRows, numCols); + M.MakeZero(); + return M; + } + + static GMatrix Unit(int numRows, int numCols, int r, int c) + { + GMatrix M(numRows, numCols); + M.MakeUnit(r, c); + return M; + } + + static GMatrix Identity(int numRows, int numCols) + { + GMatrix M(numRows, numCols); + M.MakeIdentity(); + return M; + } + + protected: + // The matrix is stored as a 1-dimensional array. The convention of + // row-major or column-major is your choice. + int mNumRows, mNumCols; + std::vector mElements; + }; + + // Unary operations. + template + GMatrix operator+(GMatrix const& M) + { + return M; + } + + template + GMatrix operator-(GMatrix const& M) + { + GMatrix result(M.GetNumRows(), M.GetNumCols()); + for (int i = 0; i < M.GetNumElements(); ++i) + { + result[i] = -M[i]; + } + return result; + } + + // Linear-algebraic operations. + template + GMatrix operator+(GMatrix const& M0, GMatrix const& M1) + { + GMatrix result = M0; + return result += M1; + } + + template + GMatrix operator-(GMatrix const& M0, GMatrix const& M1) + { + GMatrix result = M0; + return result -= M1; + } + + template + GMatrix operator*(GMatrix const& M, Real scalar) + { + GMatrix result = M; + return result *= scalar; + } + + template + GMatrix operator*(Real scalar, GMatrix const& M) + { + GMatrix result = M; + return result *= scalar; + } + + template + GMatrix operator/(GMatrix const& M, Real scalar) + { + GMatrix result = M; + return result /= scalar; + } + + template + GMatrix& operator+=(GMatrix& M0, GMatrix const& M1) + { + if (M0.GetNumRows() == M1.GetNumRows() && M0.GetNumCols() == M1.GetNumCols()) + { + for (int i = 0; i < M0.GetNumElements(); ++i) + { + M0[i] += M1[i]; + } + return M0; + } + LogError("Mismatched sizes"); + } + + template + GMatrix& operator-=(GMatrix& M0, GMatrix const& M1) + { + if (M0.GetNumRows() == M1.GetNumRows() && M0.GetNumCols() == M1.GetNumCols()) + { + for (int i = 0; i < M0.GetNumElements(); ++i) + { + M0[i] -= M1[i]; + } + return M0; + } + LogError("Mismatched sizes"); + } + + template + GMatrix& operator*=(GMatrix& M, Real scalar) + { + for (int i = 0; i < M.GetNumElements(); ++i) + { + M[i] *= scalar; + } + return M; + } + + template + GMatrix& operator/=(GMatrix& M, Real scalar) + { + if (scalar != (Real)0) + { + Real invScalar = ((Real)1) / scalar; + for (int i = 0; i < M.GetNumElements(); ++i) + { + M[i] *= invScalar; + } + return M; + } + LogError("Division by zero."); + } + + // Geometric operations. + template + Real L1Norm(GMatrix const& M) + { + Real sum(0); + for (int i = 0; i < M.GetNumElements(); ++i) + { + sum += std::fabs(M[i]); + } + return sum; + } + + template + Real L2Norm(GMatrix const& M) + { + Real sum(0); + for (int i = 0; i < M.GetNumElements(); ++i) + { + sum += M[i] * M[i]; + } + return std::sqrt(sum); + } + + template + Real LInfinityNorm(GMatrix const& M) + { + Real maxAbsElement(0); + for (int i = 0; i < M.GetNumElements(); ++i) + { + Real absElement = std::fabs(M[i]); + if (absElement > maxAbsElement) + { + maxAbsElement = absElement; + } + } + return maxAbsElement; + } + + template + GMatrix Inverse(GMatrix const& M, bool* reportInvertibility = nullptr) + { + if (M.GetNumRows() == M.GetNumCols()) + { + GMatrix invM(M.GetNumRows(), M.GetNumCols()); + Real determinant; + bool invertible = GaussianElimination()(M.GetNumRows(), &M[0], + &invM[0], determinant, nullptr, nullptr, nullptr, 0, nullptr); + if (reportInvertibility) + { + *reportInvertibility = invertible; + } + return invM; + } + LogError("Matrix must be square."); + } + + template + Real Determinant(GMatrix const& M) + { + if (M.GetNumRows() == M.GetNumCols()) + { + Real determinant; + GaussianElimination()(M.GetNumRows(), &M[0], nullptr, + determinant, nullptr, nullptr, nullptr, 0, nullptr); + return determinant; + } + LogError("Matrix must be square."); + } + + // M^T + template + GMatrix Transpose(GMatrix const& M) + { + GMatrix result(M.GetNumCols(), M.GetNumRows()); + for (int r = 0; r < M.GetNumRows(); ++r) + { + for (int c = 0; c < M.GetNumCols(); ++c) + { + result(c, r) = M(r, c); + } + } + return result; + } + + // M*V + template + GVector operator*(GMatrix const& M, GVector const& V) + { + if (V.GetSize() == M.GetNumCols()) + { + GVector result(M.GetNumRows()); + for (int r = 0; r < M.GetNumRows(); ++r) + { + result[r] = (Real)0; + for (int c = 0; c < M.GetNumCols(); ++c) + { + result[r] += M(r, c) * V[c]; + } + } + return result; + } + LogError("Mismatched sizes."); + } + + // V^T*M + template + GVector operator*(GVector const& V, GMatrix const& M) + { + if (V.GetSize() == M.GetNumRows()) + { + GVector result(M.GetNumCols()); + for (int c = 0; c < M.GetNumCols(); ++c) + { + result[c] = (Real)0; + for (int r = 0; r < M.GetNumRows(); ++r) + { + result[c] += V[r] * M(r, c); + } + } + return result; + } + LogError("Mismatched sizes."); + } + + // A*B + template + GMatrix operator*(GMatrix const& A, GMatrix const& B) + { + return MultiplyAB(A, B); + } + + template + GMatrix MultiplyAB(GMatrix const& A, GMatrix const& B) + { + if (A.GetNumCols() == B.GetNumRows()) + { + GMatrix result(A.GetNumRows(), B.GetNumCols()); + int const numCommon = A.GetNumCols(); + for (int r = 0; r < result.GetNumRows(); ++r) + { + for (int c = 0; c < result.GetNumCols(); ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < numCommon; ++i) + { + result(r, c) += A(r, i) * B(i, c); + } + } + } + return result; + } + LogError("Mismatched sizes."); + } + + // A*B^T + template + GMatrix MultiplyABT(GMatrix const& A, GMatrix const& B) + { + if (A.GetNumCols() == B.GetNumCols()) + { + GMatrix result(A.GetNumRows(), B.GetNumRows()); + int const numCommon = A.GetNumCols(); + for (int r = 0; r < result.GetNumRows(); ++r) + { + for (int c = 0; c < result.GetNumCols(); ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < numCommon; ++i) + { + result(r, c) += A(r, i) * B(c, i); + } + } + } + return result; + } + LogError("Mismatched sizes."); + } + + // A^T*B + template + GMatrix MultiplyATB(GMatrix const& A, GMatrix const& B) + { + if (A.GetNumRows() == B.GetNumRows()) + { + GMatrix result(A.GetNumCols(), B.GetNumCols()); + int const numCommon = A.GetNumRows(); + for (int r = 0; r < result.GetNumRows(); ++r) + { + for (int c = 0; c < result.GetNumCols(); ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < numCommon; ++i) + { + result(r, c) += A(i, r) * B(i, c); + } + } + } + return result; + } + LogError("Mismatched sizes."); + } + + // A^T*B^T + template + GMatrix MultiplyATBT(GMatrix const& A, GMatrix const& B) + { + if (A.GetNumRows() == B.GetNumCols()) + { + GMatrix result(A.GetNumCols(), B.GetNumRows()); + int const numCommon = A.GetNumRows(); + for (int r = 0; r < result.GetNumRows(); ++r) + { + for (int c = 0; c < result.GetNumCols(); ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < numCommon; ++i) + { + result(r, c) += A(i, r) * B(c, i); + } + } + } + return result; + } + LogError("Mismatched sizes."); + } + + // M*D, D is square diagonal (stored as vector) + template + GMatrix MultiplyMD(GMatrix const& M, GVector const& D) + { + if (D.GetSize() == M.GetNumCols()) + { + GMatrix result(M.GetNumRows(), M.GetNumCols()); + for (int r = 0; r < result.GetNumRows(); ++r) + { + for (int c = 0; c < result.GetNumCols(); ++c) + { + result(r, c) = M(r, c) * D[c]; + } + } + return result; + } + LogError("Mismatched sizes."); + } + + // D*M, D is square diagonal (stored as vector) + template + GMatrix MultiplyDM(GVector const& D, GMatrix const& M) + { + if (D.GetSize() == M.GetNumRows()) + { + GMatrix result(M.GetNumRows(), M.GetNumCols()); + for (int r = 0; r < result.GetNumRows(); ++r) + { + for (int c = 0; c < result.GetNumCols(); ++c) + { + result(r, c) = D[r] * M(r, c); + } + } + return result; + } + LogError("Mismatched sizes."); + } + + // U*V^T, U is N-by-1, V is M-by-1, result is N-by-M. + template + GMatrix OuterProduct(GVector const& U, GVector const& V) + { + GMatrix result(U.GetSize(), V.GetSize()); + for (int r = 0; r < result.GetNumRows(); ++r) + { + for (int c = 0; c < result.GetNumCols(); ++c) + { + result(r, c) = U[r] * V[c]; + } + } + return result; + } + + // Initialization to a diagonal matrix whose diagonal entries are the + // components of D, even when nonsquare. + template + void MakeDiagonal(GVector const& D, GMatrix& M) + { + int const numRows = M.GetNumRows(); + int const numCols = M.GetNumCols(); + int const numDiagonal = (numRows <= numCols ? numRows : numCols); + M.MakeZero(); + for (int i = 0; i < numDiagonal; ++i) + { + M(i, i) = D[i]; + } + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GVector.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GVector.h new file mode 100644 index 0000000..c61ea3b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GVector.h @@ -0,0 +1,510 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class GVector + { + public: + // The tuple is length zero (uninitialized). + GVector() = default; + + // The tuple is length 'size' and the elements are uninitialized. + GVector(int size) + { + SetSize(size); + } + + // For 0 <= d <= size, element d is 1 and all others are zero. If d + // is invalid, the zero vector is created. This is a convenience for + // creating the standard Euclidean basis vectors; see also + // MakeUnit(int,int) and Unit(int,int). + GVector(int size, int d) + { + SetSize(size); + MakeUnit(d); + } + + // The copy constructor, destructor, and assignment operator are + // generated by the compiler. + + // Member access. SetSize(int) does not initialize the tuple. The + // first operator[] returns a const reference rather than a Real + // value. This supports writing via standard file operations that + // require a const pointer to data. + void SetSize(int size) + { + LogAssert(size >= 0, "Invalid size."); + mTuple.resize(size); + } + + inline int GetSize() const + { + return static_cast(mTuple.size()); + } + + inline Real const& operator[](int i) const + { + return mTuple[i]; + } + + inline Real& operator[](int i) + { + return mTuple[i]; + } + + // Comparison (for use by STL containers). + inline bool operator==(GVector const& vec) const + { + return mTuple == vec.mTuple; + } + + inline bool operator!=(GVector const& vec) const + { + return mTuple != vec.mTuple; + } + + inline bool operator< (GVector const& vec) const + { + return mTuple < vec.mTuple; + } + + inline bool operator<=(GVector const& vec) const + { + return mTuple <= vec.mTuple; + } + + inline bool operator> (GVector const& vec) const + { + return mTuple > vec.mTuple; + } + + inline bool operator>=(GVector const& vec) const + { + return mTuple >= vec.mTuple; + } + + // Special vectors. + + // All components are 0. + void MakeZero() + { + std::fill(mTuple.begin(), mTuple.end(), (Real)0); + } + + // Component d is 1, all others are zero. + void MakeUnit(int d) + { + std::fill(mTuple.begin(), mTuple.end(), (Real)0); + if (0 <= d && d < (int)mTuple.size()) + { + mTuple[d] = (Real)1; + } + } + + static GVector Zero(int size) + { + GVector v(size); + v.MakeZero(); + return v; + } + + static GVector Unit(int size, int d) + { + GVector v(size); + v.MakeUnit(d); + return v; + } + + protected: + // This data structure takes advantage of the built-in operator[], + // range checking and visualizers in MSVS. + std::vector mTuple; + }; + + // Unary operations. + template + GVector operator+(GVector const& v) + { + return v; + } + + template + GVector operator-(GVector const& v) + { + GVector result(v.GetSize()); + for (int i = 0; i < v.GetSize(); ++i) + { + result[i] = -v[i]; + } + return result; + } + + // Linear-algebraic operations. + template + GVector operator+(GVector const& v0, GVector const& v1) + { + GVector result = v0; + return result += v1; + } + + template + GVector operator-(GVector const& v0, GVector const& v1) + { + GVector result = v0; + return result -= v1; + } + + template + GVector operator*(GVector const& v, Real scalar) + { + GVector result = v; + return result *= scalar; + } + + template + GVector operator*(Real scalar, GVector const& v) + { + GVector result = v; + return result *= scalar; + } + + template + GVector operator/(GVector const& v, Real scalar) + { + GVector result = v; + return result /= scalar; + } + + template + GVector& operator+=(GVector& v0, GVector const& v1) + { + if (v0.GetSize() == v1.GetSize()) + { + for (int i = 0; i < v0.GetSize(); ++i) + { + v0[i] += v1[i]; + } + return v0; + } + LogError("Mismatched sizes."); + } + + template + GVector& operator-=(GVector& v0, GVector const& v1) + { + if (v0.GetSize() == v1.GetSize()) + { + for (int i = 0; i < v0.GetSize(); ++i) + { + v0[i] -= v1[i]; + } + return v0; + } + LogError("Mismatched sizes."); + } + + template + GVector& operator*=(GVector& v, Real scalar) + { + for (int i = 0; i < v.GetSize(); ++i) + { + v[i] *= scalar; + } + return v; + } + + template + GVector& operator/=(GVector& v, Real scalar) + { + if (scalar != (Real)0) + { + Real invScalar = (Real)1 / scalar; + for (int i = 0; i < v.GetSize(); ++i) + { + v[i] *= invScalar; + } + return v; + } + LogError("Division by zero."); + } + + // Geometric operations. The functions with 'robust' set to 'false' use + // the standard algorithm for normalizing a vector by computing the length + // as a square root of the squared length and dividing by it. The results + // can be infinite (or NaN) if the length is zero. When 'robust' is set + // to 'true', the algorithm is designed to avoid floating-point overflow + // and sets the normalized vector to zero when the length is zero. + template + Real Dot(GVector const& v0, GVector const& v1) + { + if (v0.GetSize() == v1.GetSize()) + { + Real dot(0); + for (int i = 0; i < v0.GetSize(); ++i) + { + dot += v0[i] * v1[i]; + } + return dot; + } + LogError("Mismatched sizes."); + } + + template + Real Length(GVector const& v, bool robust = false) + { + if (robust) + { + Real maxAbsComp = std::fabs(v[0]); + for (int i = 1; i < v.GetSize(); ++i) + { + Real absComp = std::fabs(v[i]); + if (absComp > maxAbsComp) + { + maxAbsComp = absComp; + } + } + + Real length; + if (maxAbsComp > (Real)0) + { + GVector scaled = v / maxAbsComp; + length = maxAbsComp * std::sqrt(Dot(scaled, scaled)); + } + else + { + length = (Real)0; + } + return length; + } + else + { + return std::sqrt(Dot(v, v)); + } + } + + template + Real Normalize(GVector& v, bool robust = false) + { + if (robust) + { + Real maxAbsComp = std::fabs(v[0]); + for (int i = 1; i < v.GetSize(); ++i) + { + Real absComp = std::fabs(v[i]); + if (absComp > maxAbsComp) + { + maxAbsComp = absComp; + } + } + + Real length; + if (maxAbsComp > (Real)0) + { + v /= maxAbsComp; + length = std::sqrt(Dot(v, v)); + v /= length; + length *= maxAbsComp; + } + else + { + length = (Real)0; + for (int i = 0; i < v.GetSize(); ++i) + { + v[i] = (Real)0; + } + } + return length; + } + else + { + Real length = std::sqrt(Dot(v, v)); + if (length > (Real)0) + { + v /= length; + } + else + { + for (int i = 0; i < v.GetSize(); ++i) + { + v[i] = (Real)0; + } + } + return length; + } + } + + // Gram-Schmidt orthonormalization to generate orthonormal vectors from + // the linearly independent inputs. The function returns the smallest + // length of the unnormalized vectors computed during the process. If + // this value is nearly zero, it is possible that the inputs are linearly + // dependent (within numerical round-off errors). On input, + // 1 <= numElements <= N and v[0] through v[numElements-1] must be + // initialized. On output, the vectors v[0] through v[numElements-1] + // form an orthonormal set. + template + Real Orthonormalize(int numInputs, GVector* v, bool robust = false) + { + if (v && 1 <= numInputs && numInputs <= v[0].GetSize()) + { + for (int i = 1; i < numInputs; ++i) + { + if (v[0].GetSize() != v[i].GetSize()) + { + LogError("Mismatched sizes."); + } + } + + Real minLength = Normalize(v[0], robust); + for (int i = 1; i < numInputs; ++i) + { + for (int j = 0; j < i; ++j) + { + Real dot = Dot(v[i], v[j]); + v[i] -= v[j] * dot; + } + Real length = Normalize(v[i], robust); + if (length < minLength) + { + minLength = length; + } + } + return minLength; + } + LogError("Invalid input."); + } + + // Compute the axis-aligned bounding box of the vectors. The return value is + // 'true' iff the inputs are valid, in which case vmin and vmax have valid + // values. + template + bool ComputeExtremes(int numVectors, GVector const* v, + GVector& vmin, GVector& vmax) + { + if (v && numVectors > 0) + { + for (int i = 1; i < numVectors; ++i) + { + if (v[0].GetSize() != v[i].GetSize()) + { + LogError("Mismatched sizes."); + } + } + + int const size = v[0].GetSize(); + vmin = v[0]; + vmax = vmin; + for (int j = 1; j < numVectors; ++j) + { + GVector const& vec = v[j]; + for (int i = 0; i < size; ++i) + { + if (vec[i] < vmin[i]) + { + vmin[i] = vec[i]; + } + else if (vec[i] > vmax[i]) + { + vmax[i] = vec[i]; + } + } + } + return true; + } + LogError("Invalid input."); + } + + // Lift n-tuple v to homogeneous (n+1)-tuple (v,last). + template + GVector HLift(GVector const& v, Real last) + { + int const size = v.GetSize(); + GVector result(size + 1); + for (int i = 0; i < size; ++i) + { + result[i] = v[i]; + } + result[size] = last; + return result; + } + + // Project homogeneous n-tuple v = (u,v[n-1]) to (n-1)-tuple u. + template + GVector HProject(GVector const& v) + { + int const size = v.GetSize(); + if (size > 1) + { + GVector result(size - 1); + for (int i = 0; i < size - 1; ++i) + { + result[i] = v[i]; + } + return result; + } + else + { + return GVector(); + } + } + + + // Lift n-tuple v = (w0,w1) to (n+1)-tuple u = (w0,u[inject],w1). By + // inference, w0 is a (inject)-tuple [nonexistent when inject=0] and w1 + // is a (n-inject)-tuple [nonexistent when inject=n]. + template + GVector Lift(GVector const& v, int inject, Real value) + { + int const size = v.GetSize(); + GVector result(size + 1); + int i; + for (i = 0; i < inject; ++i) + { + result[i] = v[i]; + } + result[i] = value; + int j = i; + for (++j; i < size; ++i, ++j) + { + result[j] = v[i]; + } + return result; + } + + // Project n-tuple v = (w0,v[reject],w1) to (n-1)-tuple u = (w0,w1). By + // inference, w0 is a (reject)-tuple [nonexistent when reject=0] and w1 + // is a (n-1-reject)-tuple [nonexistent when reject=n-1]. + template + GVector Project(GVector const& v, int reject) + { + int const size = v.GetSize(); + if (size > 1) + { + GVector result(size - 1); + for (int i = 0, j = 0; i < size - 1; ++i, ++j) + { + if (j == reject) + { + ++j; + } + result[i] = v[j]; + } + return result; + } + else + { + return GVector(); + } + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GaussNewtonMinimizer.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GaussNewtonMinimizer.h new file mode 100644 index 0000000..9aae13e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GaussNewtonMinimizer.h @@ -0,0 +1,220 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// Let F(p) = (F_{0}(p), F_{1}(p), ..., F_{n-1}(p)) be a vector-valued +// function of the parameters p = (p_{0}, p_{1}, ..., p_{m-1}). The +// nonlinear least-squares problem is to minimize the real-valued error +// function E(p) = |F(p)|^2, which is the squared length of F(p). +// +// Let J = dF/dp = [dF_{r}/dp_{c}] denote the Jacobian matrix, which is the +// matrix of first-order partial derivatives of F. The matrix has n rows and +// m columns, and the indexing (r,c) refers to row r and column c. A +// first-order approximation is F(p + d) = F(p) + J(p)d, where d is an m-by-1 +// vector with small length. Consequently, an approximation to E is E(p + d) +// = |F(p + d)|^2 = |F(p) + J(p)d|^2. The goal is to choose d to minimize +// |F(p) + J(p)d|^2 and, hopefully, with E(p + d) < E(p). Choosing an initial +// p_{0}, the hope is that the algorithm generates a sequence p_{i} for which +// E(p_{i+1}) < E(p_{i}) and, in the limit, E(p_{j}) approaches the global +// minimum of E. The algorithm is referred to as Gauss-Newton iteration. If +// E does not decrease for a step of the algorithm, one can modify the +// algorithm to the Levenberg-Marquardt iteration. See +// GteLevenbergMarquardtMinimizer.h for a description and an implementation. +// +// For a single Gauss-Newton iteration, we need to choose d to minimize +// |F(p) + J(p)d|^2 where p is fixed. This is a linear least squares problem +// which can be formulated using the normal equations +// (J^T(p)*J(p))*d = -J^T(p)*F(p). The matrix J^T*J is positive semidefinite. +// If it is invertible, then d = -(J^T(p)*J(p))^{-1}*F(p). If it is not +// invertible, some other algorithm must be used to choose d; one option is +// to use gradient descent for the step. A Cholesky decomposition can be +// used to solve the linear system. +// +// Although an implementation can allow the caller to pass an array of +// functions F_{i}(p) and an array of derivatives dF_{r}/dp_{c}, some +// applications might involve a very large n that precludes storing all +// the computed Jacobian matrix entries because of excessive memory +// requirements. In such an application, it is better to compute instead +// the entries of the m-by-m matrix J^T*J and the m-by-1 vector J^T*F. +// Typically, m is small, so the memory requirements are not excessive. Also, +// there might be additional structure to F for which the caller can take +// advantage; for example, 3-tuples of components of F(p) might correspond to +// vectors that can be manipulated using an already existing mathematics +// library. The implementation here supports both approaches. + +namespace WwiseGTE +{ + template + class GaussNewtonMinimizer + { + public: + // Convenient types for the domain vectors, the range vectors, the + // function F and the Jacobian J. + typedef GVector DVector; // numPDimensions + typedef GVector RVector; // numFDimensions + typedef GMatrix JMatrix; // numFDimensions-by-numPDimensions + typedef GMatrix JTJMatrix; // numPDimensions-by-numPDimensions + typedef GVector JTFVector; // numPDimensions + typedef std::function FFunction; + typedef std::function JFunction; + typedef std::function JPlusFunction; + + // Create the minimizer that computes F(p) and J(p) directly. + GaussNewtonMinimizer(int numPDimensions, int numFDimensions, + FFunction const& inFFunction, JFunction const& inJFunction) + : + mNumPDimensions(numPDimensions), + mNumFDimensions(numFDimensions), + mFFunction(inFFunction), + mJFunction(inJFunction), + mF(mNumFDimensions), + mJ(mNumFDimensions, mNumPDimensions), + mJTJ(mNumPDimensions, mNumPDimensions), + mNegJTF(mNumPDimensions), + mDecomposer(mNumPDimensions), + mUseJFunction(true) + { + LogAssert(mNumPDimensions > 0 && mNumFDimensions > 0, "Invalid dimensions."); + } + + // Create the minimizer that computes J^T(p)*J(p) and -J(p)*F(p). + GaussNewtonMinimizer(int numPDimensions, int numFDimensions, + FFunction const& inFFunction, JPlusFunction const& inJPlusFunction) + : + mNumPDimensions(numPDimensions), + mNumFDimensions(numFDimensions), + mFFunction(inFFunction), + mJPlusFunction(inJPlusFunction), + mF(mNumFDimensions), + mJ(mNumFDimensions, mNumPDimensions), + mJTJ(mNumPDimensions, mNumPDimensions), + mNegJTF(mNumPDimensions), + mDecomposer(mNumPDimensions), + mUseJFunction(false) + { + LogAssert(mNumPDimensions > 0 && mNumFDimensions > 0, "Invalid dimensions."); + } + + // Disallow copy, assignment and move semantics. + GaussNewtonMinimizer(GaussNewtonMinimizer const&) = delete; + GaussNewtonMinimizer& operator=(GaussNewtonMinimizer const&) = delete; + GaussNewtonMinimizer(GaussNewtonMinimizer&&) = delete; + GaussNewtonMinimizer& operator=(GaussNewtonMinimizer&&) = delete; + + inline int GetNumPDimensions() const + { + return mNumPDimensions; + } + + inline int GetNumFDimensions() const + { + return mNumFDimensions; + } + + struct Result + { + DVector minLocation; + Real minError; + Real minErrorDifference; + Real minUpdateLength; + size_t numIterations; + bool converged; + }; + + Result operator()(DVector const& p0, size_t maxIterations, + Real updateLengthTolerance, Real errorDifferenceTolerance) + { + Result result; + result.minLocation = p0; + result.minError = std::numeric_limits::max(); + result.minErrorDifference = std::numeric_limits::max(); + result.minUpdateLength = (Real)0; + result.numIterations = 0; + result.converged = false; + + // As a simple precaution, ensure the tolerances are nonnegative. + updateLengthTolerance = std::max(updateLengthTolerance, (Real)0); + errorDifferenceTolerance = std::max(errorDifferenceTolerance, (Real)0); + + // Compute the initial error. + mFFunction(p0, mF); + result.minError = Dot(mF, mF); + + // Do the Gauss-Newton iterations. + auto pCurrent = p0; + for (result.numIterations = 1; result.numIterations <= maxIterations; ++result.numIterations) + { + ComputeLinearSystemInputs(pCurrent); + if (!mDecomposer.Factor(mJTJ)) + { + // TODO: The matrix mJTJ is positive semi-definite, so the + // failure can occur when mJTJ has a zero eigenvalue in + // which case mJTJ is not invertible. Generate an iterate + // anyway, perhaps using gradient descent? + return result; + } + mDecomposer.SolveLower(mJTJ, mNegJTF); + mDecomposer.SolveUpper(mJTJ, mNegJTF); + + auto pNext = pCurrent + mNegJTF; + mFFunction(pNext, mF); + Real error = Dot(mF, mF); + if (error < result.minError) + { + result.minErrorDifference = result.minError - error; + result.minUpdateLength = Length(mNegJTF); + result.minLocation = pNext; + result.minError = error; + if (result.minErrorDifference <= errorDifferenceTolerance + || result.minUpdateLength <= updateLengthTolerance) + { + result.converged = true; + return result; + } + } + + pCurrent = pNext; + } + + return result; + } + + private: + void ComputeLinearSystemInputs(DVector const& pCurrent) + { + if (mUseJFunction) + { + mJFunction(pCurrent, mJ); + mJTJ = MultiplyATB(mJ, mJ); + mNegJTF = -(mF * mJ); + } + else + { + mJPlusFunction(pCurrent, mJTJ, mNegJTF); + } + } + + int mNumPDimensions, mNumFDimensions; + FFunction mFFunction; + JFunction mJFunction; + JPlusFunction mJPlusFunction; + + // Storage for J^T(p)*J(p) and -J^T(p)*F(p) during the iterations. + RVector mF; + JMatrix mJ; + JTJMatrix mJTJ; + JTFVector mNegJTF; + + CholeskyDecomposition mDecomposer; + + bool mUseJFunction; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GaussianBlur2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GaussianBlur2.h new file mode 100644 index 0000000..b112cbc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GaussianBlur2.h @@ -0,0 +1,51 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2020.01.11 + +#pragma once + +#include + +namespace WwiseGTE +{ + template + class GaussianBlur2 : public PdeFilter2 + { + public: + GaussianBlur2(int xBound, int yBound, Real xSpacing, Real ySpacing, + Real const* data, bool const* mask, Real borderValue, + typename PdeFilter::ScaleType scaleType) + : + PdeFilter2(xBound, yBound, xSpacing, ySpacing, data, mask, + borderValue, scaleType) + { + mMaximumTimeStep = (Real)0.5 / (this->mInvDxDx + this->mInvDyDy); + } + + virtual ~GaussianBlur2() + { + + } + + inline Real GetMaximumTimeStep() const + { + return mMaximumTimeStep; + } + + protected: + virtual void OnUpdateSingle(int x, int y) override + { + this->LookUp5(x, y); + + Real uxx = this->mInvDxDx * (this->mUpz - (Real)2 * this->mUzz + this->mUmz); + Real uyy = this->mInvDyDy * (this->mUzp - (Real)2 * this->mUzz + this->mUzm); + + this->mBuffer[this->mDst][y][x] = this->mUzz + this->mTimeStep * (uxx + uyy); + } + + Real mMaximumTimeStep; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GaussianBlur3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GaussianBlur3.h new file mode 100644 index 0000000..83399bb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GaussianBlur3.h @@ -0,0 +1,51 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2020.01.11 + +#pragma once + +#include + +namespace WwiseGTE +{ + template + class GaussianBlur3 : public PdeFilter3 + { + public: + GaussianBlur3(int xBound, int yBound, int zBound, Real xSpacing, + Real ySpacing, Real zSpacing, Real const* data, bool const* mask, + Real borderValue, typename PdeFilter::ScaleType scaleType) + : + PdeFilter3(xBound, yBound, zBound, xSpacing, ySpacing, zSpacing, + data, mask, borderValue, scaleType) + { + mMaximumTimeStep = (Real)0.5 / (this->mInvDxDx + this->mInvDyDy + this->mInvDzDz); + } + + virtual ~GaussianBlur3() + { + } + + inline Real GetMaximumTimeStep() const + { + return mMaximumTimeStep; + } + + protected: + virtual void OnUpdateSingle(int x, int y, int z) override + { + this->LookUp7(x, y, z); + + Real uxx = this->mInvDxDx * (this->mUpzz - (Real)2 * this->mUzzz + this->mUmzz); + Real uyy = this->mInvDyDy * (this->mUzpz - (Real)2 * this->mUzzz + this->mUzmz); + Real uzz = this->mInvDzDz * (this->mUzzp - (Real)2 * this->mUzzz + this->mUzzm); + + this->mBuffer[this->mDst][z][y][x] = this->mUzzz + this->mTimeStep * (uxx + uyy + uzz); + } + + Real mMaximumTimeStep; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GaussianElimination.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GaussianElimination.h new file mode 100644 index 0000000..99577cf --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GaussianElimination.h @@ -0,0 +1,275 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.11.23 + +#pragma once + +#include +#include +#include +#include + +// The input matrix M must be NxN. The storage convention for element lookup +// is determined by GTE_USE_ROW_MAJOR or GTE_USE_COL_MAJOR, whichever is +// active. If you want the inverse of M, pass a nonnull pointer inverseM; +// this matrix must also be NxN and use the same storage convention as M. If +// you do not want the inverse of M, pass a nullptr for inverseM. If you want +// to solve M*X = B for X, where X and B are Nx1, pass nonnull pointers for B +// and X. If you want to solve M*Y = C for Y, where X and C are NxK, pass +// nonnull pointers for C and Y and pass K to numCols. In all cases, pass +// N to numRows. + +namespace WwiseGTE +{ + template + class GaussianElimination + { + public: + bool operator()(int numRows, + Real const* M, Real* inverseM, Real& determinant, + Real const* B, Real* X, + Real const* C, int numCols, Real* Y) const + { + if (numRows <= 0 || !M + || ((B != nullptr) != (X != nullptr)) + || ((C != nullptr) != (Y != nullptr)) + || (C != nullptr && numCols < 1)) + { + LogError("Invalid input."); + } + + int numElements = numRows * numRows; + bool wantInverse = (inverseM != nullptr); + std::vector localInverseM; + if (!wantInverse) + { + localInverseM.resize(numElements); + inverseM = localInverseM.data(); + } + Set(numElements, M, inverseM); + + if (B) + { + Set(numRows, B, X); + } + + if (C) + { + Set(numRows * numCols, C, Y); + } + +#if defined(GTE_USE_ROW_MAJOR) + LexicoArray2 matInvM(numRows, numRows, inverseM); + LexicoArray2 matY(numRows, numCols, Y); +#else + LexicoArray2 matInvM(numRows, numRows, inverseM); + LexicoArray2 matY(numRows, numCols, Y); +#endif + + std::vector colIndex(numRows), rowIndex(numRows), pivoted(numRows); + std::fill(pivoted.begin(), pivoted.end(), 0); + + Real const zero = (Real)0; + Real const one = (Real)1; + bool odd = false; + determinant = one; + + // Elimination by full pivoting. + int i1, i2, row = 0, col = 0; + for (int i0 = 0; i0 < numRows; ++i0) + { + // Search matrix (excluding pivoted rows) for maximum absolute entry. + Real maxValue = zero; + for (i1 = 0; i1 < numRows; ++i1) + { + if (!pivoted[i1]) + { + for (i2 = 0; i2 < numRows; ++i2) + { + if (!pivoted[i2]) + { + Real value = matInvM(i1, i2); + Real absValue = (value >= zero ? value : -value); + if (absValue > maxValue) + { + maxValue = absValue; + row = i1; + col = i2; + } + } + } + } + } + + if (maxValue == zero) + { + // The matrix is not invertible. + if (wantInverse) + { + Set(numElements, nullptr, inverseM); + } + determinant = zero; + + if (B) + { + Set(numRows, nullptr, X); + } + + if (C) + { + Set(numRows * numCols, nullptr, Y); + } + return false; + } + + pivoted[col] = true; + + // Swap rows so that the pivot entry is in row 'col'. + if (row != col) + { + odd = !odd; + for (int i = 0; i < numRows; ++i) + { + std::swap(matInvM(row, i), matInvM(col, i)); + } + + if (B) + { + std::swap(X[row], X[col]); + } + + if (C) + { + for (int i = 0; i < numCols; ++i) + { + std::swap(matY(row, i), matY(col, i)); + } + } + } + + // Keep track of the permutations of the rows. + rowIndex[i0] = row; + colIndex[i0] = col; + + // Scale the row so that the pivot entry is 1. + Real diagonal = matInvM(col, col); + determinant *= diagonal; + Real inv = one / diagonal; + matInvM(col, col) = one; + for (i2 = 0; i2 < numRows; ++i2) + { + matInvM(col, i2) *= inv; + } + + if (B) + { + X[col] *= inv; + } + + if (C) + { + for (i2 = 0; i2 < numCols; ++i2) + { + matY(col, i2) *= inv; + } + } + + // Zero out the pivot column locations in the other rows. + for (i1 = 0; i1 < numRows; ++i1) + { + if (i1 != col) + { + Real save = matInvM(i1, col); + matInvM(i1, col) = zero; + for (i2 = 0; i2 < numRows; ++i2) + { + matInvM(i1, i2) -= matInvM(col, i2) * save; + } + + if (B) + { + X[i1] -= X[col] * save; + } + + if (C) + { + for (i2 = 0; i2 < numCols; ++i2) + { + matY(i1, i2) -= matY(col, i2) * save; + } + } + } + } + } + + if (wantInverse) + { + // Reorder rows to undo any permutations in Gaussian elimination. + for (i1 = numRows - 1; i1 >= 0; --i1) + { + if (rowIndex[i1] != colIndex[i1]) + { + for (i2 = 0; i2 < numRows; ++i2) + { + std::swap(matInvM(i2, rowIndex[i1]), + matInvM(i2, colIndex[i1])); + } + } + } + } + + if (odd) + { + determinant = -determinant; + } + + return true; + } + + private: + // Support for copying source to target or to set target to zero. If + // source is nullptr, then target is set to zero; otherwise source is + // copied to target. This function hides the type traits used to + // determine whether Real is native floating-point or otherwise (such + // as BSNumber or BSRational). + void Set(int numElements, Real const* source, Real* target) const + { + if (std::is_floating_point() == std::true_type()) + { + // Fast set/copy for native floating-point. + size_t numBytes = numElements * sizeof(Real); + if (source) + { + std::memcpy(target, source, numBytes); + } + else + { + std::memset(target, 0, numBytes); + } + } + else + { + // The inputs are not std containers, so ensure assignment works + // correctly. + if (source) + { + for (int i = 0; i < numElements; ++i) + { + target[i] = source[i]; + } + } + else + { + Real const zero = (Real)0; + for (int i = 0; i < numElements; ++i) + { + target[i] = zero; + } + } + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GenerateMeshUV.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GenerateMeshUV.h new file mode 100644 index 0000000..342e5ae --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GenerateMeshUV.h @@ -0,0 +1,661 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// This class is an implementation of the barycentric mapping algorithm +// described in Section 5.3 of the book +// Polygon Mesh Processing +// Mario Botsch, Leif Kobbelt, Mark Pauly, Pierre Alliez, Bruno Levy +// AK Peters, Ltd., Natick MA, 2010 +// It uses the mean value weights described in Section 5.3.1 to allow the mesh +// geometry to influence the texture coordinate generation, and it uses +// Gauss-Seidel iteration to solve the sparse linear system. The authors' +// advice is that the Gauss-Seidel approach works well for at most about 5000 +// vertices, presumably the convergence rate degrading as the number of +// vertices increases. +// +// The algorithm implemented here has an additional preprocessing step that +// computes a topological distance transform of the vertices. The boundary +// texture coordinates are propagated inward by updating the vertices in +// topological distance order, leading to fast convergence for large numbers +// of vertices. + +namespace WwiseGTE +{ + template + class GenerateMeshUV + { + public: + // Construction and destruction. Set the number of threads to 0 when + // you want the code to run in the main thread of the applications. + // Set the number of threads to a positive number when you want the + // code to run multithreaded on the CPU. Derived classes that use the + // GPU ignore the number of threads, setting the constructor input to + // std::numeric_limits::max(). Provide a callback when you + // want to monitor each iteration of the uv-solver. The input to the + // progress callback is the current iteration; it starts at 1 and + // increases to the numIterations input to the operator() member + // function. + GenerateMeshUV(uint32_t numThreads, + std::function const* progress = nullptr) + : + mNumThreads(numThreads), + mProgress(progress), + mNumVertices(0), + mVertices(nullptr), + mTCoords(nullptr), + mNumBoundaryEdges(0), + mBoundaryStart(0) + { + } + + virtual ~GenerateMeshUV() = default; + + // The incoming mesh must be edge-triangle manifold and have rectangle + // topology (simply connected, closed polyline boundary). The arrays + // 'vertices' and 'tcoords' must both have 'numVertices' elements. + // Set 'useSquareTopology' to true for the generated coordinates to + // live in the uv-square [0,1]^2. Set it to false for the generated + // coordinates to live in a convex polygon that inscribes the uv-disk + // of center (1/2,1/2) and radius 1/2. + void operator()(uint32_t numIterations, bool useSquareTopology, + int numVertices, Vector3 const* vertices, int numIndices, + int const* indices, Vector2* tcoords) + { + // Ensure that numIterations is even, which avoids having a memory + // copy from the temporary ping-pong buffer to 'tcoords'. + if (numIterations & 1) + { + ++numIterations; + } + + mNumVertices = numVertices; + mVertices = vertices; + mTCoords = tcoords; + + // The linear system solver has a first pass to initialize the + // texture coordinates to ensure the Gauss-Seidel iteration + // converges rapidly. This requires the texture coordinates all + // start as (-1,-1). + for (int i = 0; i < numVertices; ++i) + { + mTCoords[i][0] = (Real)-1; + mTCoords[i][1] = (Real)-1; + } + + // Create the manifold mesh data structure. + mGraph.Clear(); + int const numTriangles = numIndices / 3; + for (int t = 0; t < numTriangles; ++t) + { + int v0 = *indices++; + int v1 = *indices++; + int v2 = *indices++; + mGraph.Insert(v0, v1, v2); + } + + TopologicalVertexDistanceTransform(); + + if (useSquareTopology) + { + AssignBoundaryTextureCoordinatesSquare(); + } + else + { + AssignBoundaryTextureCoordinatesDisk(); + } + + ComputeMeanValueWeights(); + SolveSystem(numIterations); + } + + protected: + // A CPU-based implementation is provided by this class. The derived + // classes using the GPU override this function. + virtual void SolveSystemInternal(uint32_t numIterations) + { + if (mNumThreads > 1) + { + SolveSystemCPUMultiple(numIterations); + } + else + { + SolveSystemCPUSingle(numIterations); + } + } + + // Constructor inputs. + uint32_t mNumThreads; + std::function const* mProgress; + + // Convenience members that store the input parameters to operator(). + int mNumVertices; + Vector3 const* mVertices; + Vector2* mTCoords; + + // The edge-triangle manifold graph, where each edge is shared by at + // most two triangles. + ETManifoldMesh mGraph; + + // The mVertexInfo array stores -1 for the interior vertices. For a + // boundary edge that is counterclockwise, + // mVertexInfo[v0] = v1, which gives us an orded boundary polyline. + enum { INTERIOR_VERTEX = -1 }; + std::vector mVertexInfo; + int mNumBoundaryEdges, mBoundaryStart; + typedef ETManifoldMesh::Edge Edge; + std::set> mInteriorEdges; + + // The vertex graph required to set up a sparse linear system of + // equations to determine the texture coordinates. + struct Vertex + { + // The topological distance from the boundary of the mesh. + int distance; + + // The value range0 is the index into mVertexGraphData for the + // first adjacent vertex. The value range1 is the number of + // adjacent vertices. + int range0, range1; + + // Unused. The padding is necessary for the GLSL program in the + // derived class for GL45. + int padding; + }; + + std::vector mVertexGraph; + std::vector> mVertexGraphData; + + // The vertices are listed in the order determined by a topological + // distance transform. Boundary vertices have 'distance' 0. Any + // vertices that are not boundary vertices but are edge-adjacent to + // boundary vertices have 'distance' 1. Neighbors of those have + // distance '2', and so on. The mOrderedVertices array stores + // distance-0 vertices first, distance-1 vertices second, and so on. + std::vector mOrderedVertices; + + private: + void TopologicalVertexDistanceTransform() + { + // Initialize the graph information. + mVertexInfo.resize(mNumVertices); + std::fill(mVertexInfo.begin(), mVertexInfo.end(), INTERIOR_VERTEX); + mVertexGraph.resize(mNumVertices); + mVertexGraphData.resize(2 * mGraph.GetEdges().size()); + std::pair initialData = std::make_pair(-1, (Real)-1); + std::fill(mVertexGraphData.begin(), mVertexGraphData.end(), initialData); + mOrderedVertices.resize(mNumVertices); + mInteriorEdges.clear(); + mNumBoundaryEdges = 0; + mBoundaryStart = std::numeric_limits::max(); + + // Count the number of adjacent vertices for each vertex. For + // data sets with a large number of vertices, this is a + // preprocessing step to avoid a dynamic data structure that has + // a large number of std:map objects that take a very long time + // to destroy when a debugger is attached to the executable. + // Instead, we allocate a single array that stores all the + // adjacency information. It is also necessary to bundle the + // data this way for a GPU version of the algorithm. + std::vector numAdjacencies(mNumVertices); + std::fill(numAdjacencies.begin(), numAdjacencies.end(), 0); + + for (auto const& element : mGraph.GetEdges()) + { + ++numAdjacencies[element.first.V[0]]; + ++numAdjacencies[element.first.V[1]]; + + if (element.second->T[1].lock()) + { + // This is an interior edge. + mInteriorEdges.insert(element.second); + } + else + { + // This is a boundary edge. Determine the ordering of the + // vertex indices to make the edge counterclockwise. + ++mNumBoundaryEdges; + int v0 = element.second->V[0], v1 = element.second->V[1]; + auto tri = element.second->T[0].lock(); + int i; + for (i = 0; i < 3; ++i) + { + int v2 = tri->V[i]; + if (v2 != v0 && v2 != v1) + { + // The vertex is opposite the boundary edge. + v0 = tri->V[(i + 1) % 3]; + v1 = tri->V[(i + 2) % 3]; + mVertexInfo[v0] = v1; + mBoundaryStart = std::min(mBoundaryStart, v0); + break; + } + } + } + } + + // Set the range data for each vertex. + for (int vIndex = 0, aIndex = 0; vIndex < mNumVertices; ++vIndex) + { + int numAdjacent = numAdjacencies[vIndex]; + mVertexGraph[vIndex].range0 = aIndex; + mVertexGraph[vIndex].range1 = numAdjacent; + aIndex += numAdjacent; + mVertexGraph[vIndex].padding = 0; + } + + // Compute a topological distance transform of the vertices. + std::set currFront; + for (auto const& element : mGraph.GetEdges()) + { + int v0 = element.second->V[0], v1 = element.second->V[1]; + for (int i = 0; i < 2; ++i) + { + if (mVertexInfo[v0] == INTERIOR_VERTEX) + { + mVertexGraph[v0].distance = -1; + } + else + { + mVertexGraph[v0].distance = 0; + currFront.insert(v0); + } + + // Insert v1 into the first available slot of the + // adjacency array. + int range0 = mVertexGraph[v0].range0; + int range1 = mVertexGraph[v0].range1; + for (int j = 0; j < range1; ++j) + { + std::pair& data = mVertexGraphData[range0 + j]; + if (data.second == (Real)-1) + { + data.first = v1; + data.second = (Real)0; + break; + } + } + + std::swap(v0, v1); + } + } + + // Use a breadth-first search to propagate the distance + // information. + int nextDistance = 1; + size_t numFrontVertices = currFront.size(); + std::copy(currFront.begin(), currFront.end(), mOrderedVertices.begin()); + while (currFront.size() > 0) + { + std::set nextFront; + for (auto v : currFront) + { + int range0 = mVertexGraph[v].range0; + int range1 = mVertexGraph[v].range1; + auto* current = &mVertexGraphData[range0]; + for (int j = 0; j < range1; ++j, ++current) + { + int a = current->first; + if (mVertexGraph[a].distance == -1) + { + mVertexGraph[a].distance = nextDistance; + nextFront.insert(a); + } + } + } + std::copy(nextFront.begin(), nextFront.end(), mOrderedVertices.begin() + numFrontVertices); + numFrontVertices += nextFront.size(); + currFront = std::move(nextFront); + ++nextDistance; + } + } + + void AssignBoundaryTextureCoordinatesSquare() + { + // Map the boundary of the mesh to the unit square [0,1]^2. The + // selection of square vertices is such that the relative + // distances between boundary vertices and the relative distances + // between polygon vertices is preserved, except that the four + // corners of the square are required to have boundary points + // mapped to them. The first boundary point has an implied + // distance of zero. The value distance[i] is the length of the + // boundary polyline from vertex 0 to vertex i+1. + std::vector distance(mNumBoundaryEdges); + Real total = (Real)0; + int v0 = mBoundaryStart, v1, i; + for (i = 0; i < mNumBoundaryEdges; ++i) + { + v1 = mVertexInfo[v0]; + total += Length(mVertices[v1] - mVertices[v0]); + distance[i] = total; + v0 = v1; + } + + Real invTotal = (Real)1 / total; + for (auto& d : distance) + { + d *= invTotal; + } + + auto begin = distance.begin(), end = distance.end(); + int endYMin = (int)(std::lower_bound(begin, end, (Real)0.25) - begin); + int endXMax = (int)(std::lower_bound(begin, end, (Real)0.50) - begin); + int endYMax = (int)(std::lower_bound(begin, end, (Real)0.75) - begin); + int endXMin = (int)distance.size() - 1; + + // The first polygon vertex is (0,0). The remaining vertices are + // chosen counterclockwise around the square. + v0 = mBoundaryStart; + mTCoords[v0][0] = (Real)0; + mTCoords[v0][1] = (Real)0; + for (i = 0; i < endYMin; ++i) + { + v1 = mVertexInfo[v0]; + mTCoords[v1][0] = distance[i] * (Real)4; + mTCoords[v1][1] = (Real)0; + v0 = v1; + } + + v1 = mVertexInfo[v0]; + mTCoords[v1][0] = (Real)1; + mTCoords[v1][1] = (Real)0; + v0 = v1; + for (++i; i < endXMax; ++i) + { + v1 = mVertexInfo[v0]; + mTCoords[v1][0] = (Real)1; + mTCoords[v1][1] = distance[i] * (Real)4 - (Real)1; + v0 = v1; + } + + v1 = mVertexInfo[v0]; + mTCoords[v1][0] = (Real)1; + mTCoords[v1][1] = (Real)1; + v0 = v1; + for (++i; i < endYMax; ++i) + { + v1 = mVertexInfo[v0]; + mTCoords[v1][0] = (Real)3 - distance[i] * (Real)4; + mTCoords[v1][1] = (Real)1; + v0 = v1; + } + + v1 = mVertexInfo[v0]; + mTCoords[v1][0] = (Real)0; + mTCoords[v1][1] = (Real)1; + v0 = v1; + for (++i; i < endXMin; ++i) + { + v1 = mVertexInfo[v0]; + mTCoords[v1][0] = (Real)0; + mTCoords[v1][1] = (Real)4 - distance[i] * (Real)4; + v0 = v1; + } + } + + void AssignBoundaryTextureCoordinatesDisk() + { + // Map the boundary of the mesh to a convex polygon. The selection + // of convex polygon vertices is such that the relative distances + // between boundary vertices and the relative distances between + // polygon vertices is preserved. The first boundary point has an + // implied distance of zero. The value distance[i] is the length + // of the boundary polyline from vertex 0 to vertex i+1. + std::vector distance(mNumBoundaryEdges); + Real total = (Real)0; + int v0 = mBoundaryStart; + for (int i = 0; i < mNumBoundaryEdges; ++i) + { + int v1 = mVertexInfo[v0]; + total += Length(mVertices[v1] - mVertices[v0]); + distance[i] = total; + v0 = v1; + } + + // The convex polygon lives in [0,1]^2 and inscribes a circle with + // center (1/2,1/2) and radius 1/2. The polygon center is not + // necessarily the circle center! This is the case when a + // boundary edge has length larger than half the total length of + // the boundary polyline; we do not expect such data for our + // meshes. The first polygon vertex is (1/2,0). The remaining + // vertices are chosen counterclockwise around the polygon. + Real multiplier = (Real)GTE_C_TWO_PI / total; + v0 = mBoundaryStart; + mTCoords[v0][0] = (Real)1; + mTCoords[v0][1] = (Real)0.5; + for (int i = 1; i < mNumBoundaryEdges; ++i) + { + int v1 = mVertexInfo[v0]; + Real angle = multiplier * distance[i - 1]; + mTCoords[v1][0] = (std::cos(angle) + (Real)1) * (Real)0.5; + mTCoords[v1][1] = (std::sin(angle) + (Real)1) * (Real)0.5; + v0 = v1; + } + } + + void ComputeMeanValueWeights() + { + for (auto const& edge : mInteriorEdges) + { + int v0 = edge->V[0], v1 = edge->V[1]; + for (int i = 0; i < 2; ++i) + { + // Compute the direction from X0 to X1 and compute the + // length of the edge (X0,X1). + Vector3 X0 = mVertices[v0]; + Vector3 X1 = mVertices[v1]; + Vector3 X1mX0 = X1 - X0; + Real x1mx0length = Normalize(X1mX0); + Real weight; + if (x1mx0length > (Real)0) + { + // Compute the weight for X0 associated with X1. + weight = (Real)0; + for (int j = 0; j < 2; ++j) + { + // Find the vertex of triangle T[j] opposite edge + // . + auto tri = edge->T[j].lock(); + int k; + for (k = 0; k < 3; ++k) + { + int v2 = tri->V[k]; + if (v2 != v0 && v2 != v1) + { + Vector3 X2 = mVertices[v2]; + Vector3 X2mX0 = X2 - X0; + Real x2mx0Length = Normalize(X2mX0); + if (x2mx0Length > (Real)0) + { + Real dot = Dot(X2mX0, X1mX0); + Real cs = std::min(std::max(dot, (Real)-1), (Real)1); + Real angle = std::acos(cs); + weight += std::tan(angle * (Real)0.5); + } + else + { + weight += (Real)1; + } + break; + } + } + } + weight /= x1mx0length; + } + else + { + weight = (Real)1; + } + + int range0 = mVertexGraph[v0].range0; + int range1 = mVertexGraph[v0].range1; + for (int j = 0; j < range1; ++j) + { + std::pair& data = mVertexGraphData[range0 + j]; + if (data.first == v1) + { + data.second = weight; + } + } + + std::swap(v0, v1); + } + } + } + + void SolveSystem(uint32_t numIterations) + { + // On the first pass, average only neighbors whose texture + // coordinates have been computed. This is a good initial guess + // for the linear system and leads to relatively fast convergence + // of the Gauss-Seidel iterates. + Real zero = (Real)0; + for (int i = mNumBoundaryEdges; i < mNumVertices; ++i) + { + int v0 = mOrderedVertices[i]; + int range0 = mVertexGraph[v0].range0; + int range1 = mVertexGraph[v0].range1; + auto const* current = &mVertexGraphData[range0]; + Vector2 tcoord{ zero, zero }; + Real weight, weightSum = zero; + for (int j = 0; j < range1; ++j, ++current) + { + int v1 = current->first; + if (mTCoords[v1][0] != -1.0f) + { + weight = current->second; + weightSum += weight; + tcoord += weight * mTCoords[v1]; + } + } + tcoord /= weightSum; + mTCoords[v0] = tcoord; + } + + SolveSystemInternal(numIterations); + } + + void SolveSystemCPUSingle(uint32_t numIterations) + { + // Use ping-pong buffers for the texture coordinates. + std::vector> tcoords(mNumVertices); + size_t numBytes = mNumVertices * sizeof(Vector2); + std::memcpy(&tcoords[0], mTCoords, numBytes); + Vector2* inTCoords = mTCoords; + Vector2* outTCoords = &tcoords[0]; + + // The value numIterations is even, so we always swap an even + // number of times. This ensures that on exit from the loop, + // outTCoords is tcoords. + for (uint32_t i = 1; i <= numIterations; ++i) + { + if (mProgress) + { + (*mProgress)(i); + } + + for (int j = mNumBoundaryEdges; j < mNumVertices; ++j) + { + int v0 = mOrderedVertices[j]; + int range0 = mVertexGraph[v0].range0; + int range1 = mVertexGraph[v0].range1; + auto const* current = &mVertexGraphData[range0]; + Vector2 tcoord{ (Real)0, (Real)0 }; + Real weight, weightSum = (Real)0; + for (int k = 0; k < range1; ++k, ++current) + { + int v1 = current->first; + weight = current->second; + weightSum += weight; + tcoord += weight * inTCoords[v1]; + } + tcoord /= weightSum; + outTCoords[v0] = tcoord; + } + + std::swap(inTCoords, outTCoords); + } + } + + void SolveSystemCPUMultiple(uint32_t numIterations) + { + // Use ping-pong buffers for the texture coordinates. + std::vector> tcoords(mNumVertices); + size_t numBytes = mNumVertices * sizeof(Vector2); + std::memcpy(&tcoords[0], mTCoords, numBytes); + Vector2* inTCoords = mTCoords; + Vector2* outTCoords = &tcoords[0]; + + // Partition the data for multiple threads. + int numV = mNumVertices - mNumBoundaryEdges; + int numVPerThread = numV / mNumThreads; + std::vector vmin(mNumThreads), vmax(mNumThreads); + for (uint32_t t = 0; t < mNumThreads; ++t) + { + vmin[t] = mNumBoundaryEdges + t * numVPerThread; + vmax[t] = vmin[t] + numVPerThread - 1; + } + vmax[mNumThreads - 1] = mNumVertices - 1; + + // The value numIterations is even, so we always swap an even + // number of times. This ensures that on exit from the loop, + // outTCoords is tcoords. + for (uint32_t i = 1; i <= numIterations; ++i) + { + if (mProgress) + { + (*mProgress)(i); + } + + // Execute Gauss-Seidel iterations in multiple threads. + std::vector process(mNumThreads); + for (uint32_t t = 0; t < mNumThreads; ++t) + { + process[t] = std::thread([this, t, &vmin, &vmax, inTCoords, + outTCoords]() + { + for (int j = vmin[t]; j <= vmax[t]; ++j) + { + int v0 = mOrderedVertices[j]; + int range0 = mVertexGraph[v0].range0; + int range1 = mVertexGraph[v0].range1; + auto const* current = &mVertexGraphData[range0]; + Vector2 tcoord{ (Real)0, (Real)0 }; + Real weight, weightSum = (Real)0; + for (int k = 0; k < range1; ++k, ++current) + { + int v1 = current->first; + weight = current->second; + weightSum += weight; + tcoord += weight * inTCoords[v1]; + } + tcoord /= weightSum; + outTCoords[v0] = tcoord; + } + }); + } + + // Wait for all threads to finish. + for (uint32_t t = 0; t < mNumThreads; ++t) + { + process[t].join(); + } + + std::swap(inTCoords, outTCoords); + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GradientAnisotropic2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GradientAnisotropic2.h new file mode 100644 index 0000000..1484d53 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GradientAnisotropic2.h @@ -0,0 +1,112 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2020.01.11 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class GradientAnisotropic2 : public PdeFilter2 + { + public: + GradientAnisotropic2(int xBound, int yBound, Real xSpacing, Real ySpacing, + Real const* data, bool const* mask, Real borderValue, + typename PdeFilter::ScaleType scaleType, Real K) + : + PdeFilter2(xBound, yBound, xSpacing, ySpacing, data, mask, + borderValue, scaleType), + mK(K) + { + ComputeParameter(); + } + + virtual ~GradientAnisotropic2() + { + } + + protected: + void ComputeParameter() + { + Real gradMagSqr = (Real)0; + for (int y = 1; y <= this->mYBound; ++y) + { + for (int x = 1; x <= this->mXBound; ++x) + { + Real ux = this->GetUx(x, y); + Real uy = this->GetUy(x, y); + gradMagSqr += ux * ux + uy * uy; + } + } + gradMagSqr /= (Real)this->mQuantity; + + mParameter = (Real)1 / (mK * mK * gradMagSqr); + mMHalfParameter = (Real)-0.5 * mParameter; + } + + virtual void OnPreUpdate() override + { + ComputeParameter(); + } + + virtual void OnUpdateSingle(int x, int y) override + { + this->LookUp9(x, y); + + // one-sided U-derivative estimates + Real uxFwd = this->mInvDx * (this->mUpz - this->mUzz); + Real uxBwd = this->mInvDx * (this->mUzz - this->mUmz); + Real uyFwd = this->mInvDy * (this->mUzp - this->mUzz); + Real uyBwd = this->mInvDy * (this->mUzz - this->mUzm); + + // centered U-derivative estimates + Real uxCenM = this->mHalfInvDx * (this->mUpm - this->mUmm); + Real uxCenZ = this->mHalfInvDx * (this->mUpz - this->mUmz); + Real uxCenP = this->mHalfInvDx * (this->mUpp - this->mUmp); + Real uyCenM = this->mHalfInvDy * (this->mUmp - this->mUmm); + Real uyCenZ = this->mHalfInvDy * (this->mUzp - this->mUzm); + Real uyCenP = this->mHalfInvDy * (this->mUpp - this->mUpm); + + Real uxCenZSqr = uxCenZ * uxCenZ; + Real uyCenZSqr = uyCenZ * uyCenZ; + Real gradMagSqr; + + // estimate for C(x+1,y) + Real uyEstP = (Real)0.5 * (uyCenZ + uyCenP); + gradMagSqr = uxCenZSqr + uyEstP * uyEstP; + Real cxp = std::exp(mMHalfParameter * gradMagSqr); + + // estimate for C(x-1,y) + Real uyEstM = (Real)0.5 * (uyCenZ + uyCenM); + gradMagSqr = uxCenZSqr + uyEstM * uyEstM; + Real cxm = std::exp(mMHalfParameter * gradMagSqr); + + // estimate for C(x,y+1) + Real uxEstP = (Real)0.5 * (uxCenZ + uxCenP); + gradMagSqr = uyCenZSqr + uxEstP * uxEstP; + Real cyp = std::exp(mMHalfParameter * gradMagSqr); + + // estimate for C(x,y-1) + Real uxEstM = (Real)0.5 * (uxCenZ + uxCenM); + gradMagSqr = uyCenZSqr + uxEstM * uxEstM; + Real cym = std::exp(mMHalfParameter * gradMagSqr); + + this->mBuffer[this->mDst][y][x] = this->mUzz + this->mTimeStep * ( + cxp * uxFwd - cxm * uxBwd + + cyp * uyFwd - cym * uyBwd); + } + + // These are updated on each iteration, since they depend on the + // current average of the squared length of the gradients at the + // pixels. + Real mK; // k + Real mParameter; // 1/(k^2*average(gradMagSqr)) + Real mMHalfParameter; // -0.5*mParameter + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GradientAnisotropic3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GradientAnisotropic3.h new file mode 100644 index 0000000..f4bdd6d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/GradientAnisotropic3.h @@ -0,0 +1,148 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2020.01.11 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class GradientAnisotropic3 : public PdeFilter3 + { + public: + GradientAnisotropic3(int xBound, int yBound, int zBound, Real xSpacing, + Real ySpacing, Real zSpacing, Real const* data, bool const* mask, + Real borderValue, typename PdeFilter::ScaleType scaleType, Real K) + : + PdeFilter3(xBound, yBound, zBound, xSpacing, ySpacing, zSpacing, + data, mask, borderValue, scaleType), + mK(K) + { + ComputeParameter(); + } + + virtual ~GradientAnisotropic3() + { + } + + protected: + void ComputeParameter() + { + Real gradMagSqr = (Real)0; + for (int z = 1; z <= this->mZBound; ++z) + { + for (int y = 1; y <= this->mYBound; ++y) + { + for (int x = 1; x <= this->mXBound; ++x) + { + Real ux = this->GetUx(x, y, z); + Real uy = this->GetUy(x, y, z); + Real uz = this->GetUz(x, y, z); + gradMagSqr += ux * ux + uy * uy + uz * uz; + } + } + } + gradMagSqr /= (Real)this->mQuantity; + + mParameter = (Real)1 / (mK * mK * gradMagSqr); + mMHalfParameter = (Real)-0.5 * mParameter; + } + + virtual void OnPreUpdate() override + { + ComputeParameter(); + } + + virtual void OnUpdateSingle(int x, int y, int z) override + { + this->LookUp27(x, y, z); + + // one-sided U-derivative estimates + Real uxFwd = this->mInvDx * (this->mUpzz - this->mUzzz); + Real uxBwd = this->mInvDx * (this->mUzzz - this->mUmzz); + Real uyFwd = this->mInvDy * (this->mUzpz - this->mUzzz); + Real uyBwd = this->mInvDy * (this->mUzzz - this->mUzmz); + Real uzFwd = this->mInvDz * (this->mUzzp - this->mUzzz); + Real uzBwd = this->mInvDz * (this->mUzzz - this->mUzzm); + + // centered U-derivative estimates + Real duvzz = this->mHalfInvDx * (this->mUpzz - this->mUmzz); + Real duvpz = this->mHalfInvDx * (this->mUppz - this->mUmpz); + Real duvmz = this->mHalfInvDx * (this->mUpmz - this->mUmmz); + Real duvzp = this->mHalfInvDx * (this->mUpzp - this->mUmzp); + Real duvzm = this->mHalfInvDx * (this->mUpzm - this->mUmzm); + + Real duzvz = this->mHalfInvDy * (this->mUzpz - this->mUzmz); + Real dupvz = this->mHalfInvDy * (this->mUppz - this->mUpmz); + Real dumvz = this->mHalfInvDy * (this->mUmpz - this->mUmmz); + Real duzvp = this->mHalfInvDy * (this->mUzpp - this->mUzmp); + Real duzvm = this->mHalfInvDy * (this->mUzpm - this->mUzmm); + + Real duzzv = this->mHalfInvDz * (this->mUzzp - this->mUzzm); + Real dupzv = this->mHalfInvDz * (this->mUpzp - this->mUpzm); + Real dumzv = this->mHalfInvDz * (this->mUmzp - this->mUmzm); + Real duzpv = this->mHalfInvDz * (this->mUzpp - this->mUzpm); + Real duzmv = this->mHalfInvDz * (this->mUzmp - this->mUzmm); + + Real uxCenSqr = duvzz * duvzz; + Real uyCenSqr = duzvz * duzvz; + Real uzCenSqr = duzzv * duzzv; + + Real uxEst, uyEst, uzEst, gradMagSqr; + + // estimate for C(x+1,y,z) + uyEst = (Real)0.5 *(duzvz + dupvz); + uzEst = (Real)0.5 *(duzzv + dupzv); + gradMagSqr = uxCenSqr + uyEst * uyEst + uzEst * uzEst; + Real cxp = std::exp(mMHalfParameter * gradMagSqr); + + // estimate for C(x-1,y,z) + uyEst = (Real)0.5 *(duzvz + dumvz); + uzEst = (Real)0.5 *(duzzv + dumzv); + gradMagSqr = uxCenSqr + uyEst * uyEst + uzEst * uzEst; + Real cxm = std::exp(mMHalfParameter * gradMagSqr); + + // estimate for C(x,y+1,z) + uxEst = (Real)0.5 *(duvzz + duvpz); + uzEst = (Real)0.5 *(duzzv + duzpv); + gradMagSqr = uxEst * uxEst + uyCenSqr + uzEst * uzEst; + Real cyp = std::exp(mMHalfParameter * gradMagSqr); + + // estimate for C(x,y-1,z) + uxEst = (Real)0.5 *(duvzz + duvmz); + uzEst = (Real)0.5 *(duzzv + duzmv); + gradMagSqr = uxEst * uxEst + uyCenSqr + uzEst * uzEst; + Real cym = std::exp(mMHalfParameter * gradMagSqr); + + // estimate for C(x,y,z+1) + uxEst = (Real)0.5 *(duvzz + duvzp); + uyEst = (Real)0.5 *(duzvz + duzvp); + gradMagSqr = uxEst * uxEst + uyEst * uyEst + uzCenSqr; + Real czp = std::exp(mMHalfParameter * gradMagSqr); + + // estimate for C(x,y,z-1) + uxEst = (Real)0.5 *(duvzz + duvzm); + uyEst = (Real)0.5 *(duzvz + duzvm); + gradMagSqr = uxEst * uxEst + uyEst * uyEst + uzCenSqr; + Real czm = std::exp(mMHalfParameter * gradMagSqr); + + this->mBuffer[this->mDst][z][y][x] = this->mUzzz + this->mTimeStep * ( + cxp * uxFwd - cxm * uxBwd + + cyp * uyFwd - cym * uyBwd + + czp * uzFwd - czm * uzBwd); + } + + // These are updated on each iteration, since they depend on the + // current average of the squared length of the gradients at the + // voxels. + Real mK; // k + Real mParameter; // 1/(k^2*average(gradMagSqr)) + Real mMHalfParameter; // -0.5*mParameter + }; +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Halfspace.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Halfspace.h new file mode 100644 index 0000000..20f0127 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Halfspace.h @@ -0,0 +1,90 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The halfspace is represented as Dot(N,X) >= c where N is a unit-length +// normal vector, c is the plane constant, and X is any point in space. +// The user must ensure that the normal vector is unit length. + +namespace WwiseGTE +{ + template + class Halfspace + { + public: + // Construction and destruction. The default constructor sets the + // normal to (0,...,0,1) and the constant to zero (halfspace + // x[N-1] >= 0). + Halfspace() + : + constant((Real)0) + { + normal.MakeUnit(N - 1); + } + + // Specify N and c directly. + Halfspace(Vector const& inNormal, Real inConstant) + : + normal(inNormal), + constant(inConstant) + { + } + + // Public member access. + Vector normal; + Real constant; + + public: + // Comparisons to support sorted containers. + bool operator==(Halfspace const& halfspace) const + { + return normal == halfspace.normal && constant == halfspace.constant; + } + + bool operator!=(Halfspace const& halfspace) const + { + return !operator==(halfspace); + } + + bool operator< (Halfspace const& halfspace) const + { + if (normal < halfspace.normal) + { + return true; + } + + if (normal > halfspace.normal) + { + return false; + } + + return constant < halfspace.constant; + } + + bool operator<=(Halfspace const& halfspace) const + { + return !halfspace.operator<(*this); + } + + bool operator> (Halfspace const& halfspace) const + { + return halfspace.operator<(*this); + } + + bool operator>=(Halfspace const& halfspace) const + { + return !operator<(halfspace); + } + }; + + // Template alias for convenience. + template + using Halfspace3 = Halfspace<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Histogram.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Histogram.h new file mode 100644 index 0000000..738ff0c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Histogram.h @@ -0,0 +1,338 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + class Histogram + { + public: + // In the constructor with input 'int const* samples', set noRescaling + // to 'true' when you want the sample values mapped directly to the + // buckets. Typically, you know that the sample values are in the set + // of numbers {0,1,...,numBuckets-1}, but in the event of out-of-range + // values, the histogram stores a count for those numbers smaller than + // 0 and those numbers larger or equal to numBuckets. + Histogram(int numBuckets, int numSamples, int const* samples, bool noRescaling) + : + mBuckets(numBuckets), + mExcessLess(0), + mExcessGreater(0) + { + LogAssert(numBuckets > 0 && numSamples > 0 && samples != nullptr, "Invalid input."); + + std::fill(mBuckets.begin(), mBuckets.end(), 0); + + if (noRescaling) + { + // Map to the buckets, also counting out-of-range pixels. + for (int i = 0; i < numSamples; ++i) + { + int value = samples[i]; + if (0 <= value) + { + if (value < numBuckets) + { + ++mBuckets[value]; + } + else + { + ++mExcessGreater; + } + } + else + { + ++mExcessLess; + } + } + } + else + { + // Compute the extremes. + int minValue = samples[0], maxValue = minValue; + for (int i = 1; i < numSamples; ++i) + { + int value = samples[i]; + if (value < minValue) + { + minValue = value; + } + else if (value > maxValue) + { + maxValue = value; + } + } + + // Map to the buckets. + if (minValue < maxValue) + { + // The image is not constant. + double numer = static_cast(numBuckets - 1); + double denom = static_cast(maxValue - minValue); + double mult = numer / denom; + for (int i = 0; i < numSamples; ++i) + { + int index = static_cast(mult * static_cast(samples[i] - minValue)); + ++mBuckets[index]; + } + } + else + { + // The image is constant. + mBuckets[0] = numSamples; + } + } + } + + Histogram(int numBuckets, int numSamples, float const* samples) + : + mBuckets(numBuckets), + mExcessLess(0), + mExcessGreater(0) + { + LogAssert(numBuckets > 0 && numSamples > 0 && samples != nullptr, "Invalid input."); + + std::fill(mBuckets.begin(), mBuckets.end(), 0); + + // Compute the extremes. + float minValue = samples[0], maxValue = minValue; + for (int i = 1; i < numSamples; ++i) + { + float value = samples[i]; + if (value < minValue) + { + minValue = value; + } + else if (value > maxValue) + { + maxValue = value; + } + } + + // Map to the buckets. + if (minValue < maxValue) + { + // The image is not constant. + double numer = static_cast(numBuckets - 1); + double denom = static_cast(maxValue - minValue); + double mult = numer / denom; + for (int i = 0; i < numSamples; ++i) + { + int index = static_cast(mult * static_cast(samples[i] - minValue)); + ++mBuckets[index]; + } + } + else + { + // The image is constant. + mBuckets[0] = numSamples; + } + } + + Histogram(int numBuckets, int numSamples, double const* samples) + : + mBuckets(numBuckets), + mExcessLess(0), + mExcessGreater(0) + { + LogAssert(numBuckets > 0 && numSamples > 0 && samples != nullptr, "Invalid input."); + + std::fill(mBuckets.begin(), mBuckets.end(), 0); + + // Compute the extremes. + double minValue = samples[0], maxValue = minValue; + for (int i = 1; i < numSamples; ++i) + { + double value = samples[i]; + if (value < minValue) + { + minValue = value; + } + else if (value > maxValue) + { + maxValue = value; + } + } + + // Map to the buckets. + if (minValue < maxValue) + { + // The image is not constant. + double numer = static_cast(numBuckets - 1); + double denom = maxValue - minValue; + double mult = numer / denom; + for (int i = 0; i < numSamples; ++i) + { + int index = static_cast(mult * (samples[i] - minValue)); + ++mBuckets[index]; + } + } + else + { + // The image is constant. + mBuckets[0] = numSamples; + } + } + + // Construction when you plan on updating the histogram incrementally. + // The incremental update is implemented only for integer samples and + // no rescaling. + Histogram(int numBuckets) + : + mBuckets(numBuckets), + mExcessLess(0), + mExcessGreater(0) + { + LogAssert(numBuckets > 0, "Invalid input."); + + std::fill(mBuckets.begin(), mBuckets.end(), 0); + } + + // This function is called when you have used the Histogram(int) + // constructor. No bounds checking is used; you must ensure that the + // input value is in {0,...,numBuckets-1}. + inline void Insert(int value) + { + ++mBuckets[value]; + } + + // This function is called when you have used the Histogram(int) + // constructor. Bounds checking is used. + void InsertCheck(int value) + { + if (0 <= value) + { + if (value < static_cast(mBuckets.size())) + { + ++mBuckets[value]; + } + else + { + ++mExcessGreater; + } + } + else + { + ++mExcessLess; + } + } + + // Member access. + inline std::vector const& GetBuckets() const + { + return mBuckets; + } + + inline int GetExcessLess() const + { + return mExcessLess; + } + + inline int GetExcessGreater() const + { + return mExcessGreater; + } + + // In the following, define cdf(V) = sum_{i=0}^{V} bucket[i], where + // 0 <= V < B and B is the number of buckets. Define N = cdf(B-1), + // which must be the number of pixels in the image. + + // Get the lower tail of the histogram. The returned index L has the + // properties: cdf(L-1)/N < tailAmount and cdf(L)/N >= tailAmount. + int GetLowerTail(double tailAmount) + { + int const numBuckets = static_cast(mBuckets.size()); + int hSum = 0; + for (int i = 0; i < numBuckets; ++i) + { + hSum += mBuckets[i]; + } + + int hTailSum = static_cast(tailAmount * hSum); + int hLowerSum = 0; + int lower; + for (lower = 0; lower < numBuckets; ++lower) + { + hLowerSum += mBuckets[lower]; + if (hLowerSum >= hTailSum) + { + break; + } + } + return lower; + } + + // Get the upper tail of the histogram. The returned index U has the + // properties: cdf(U)/N >= 1-tailAmount and cdf(U+1) < 1-tailAmount. + int GetUpperTail(double tailAmount) + { + int const numBuckets = static_cast(mBuckets.size()); + int hSum = 0; + for (int i = 0; i < numBuckets; ++i) + { + hSum += mBuckets[i]; + } + + int hTailSum = static_cast(tailAmount * hSum); + int hUpperSum = 0; + int upper; + for (upper = numBuckets - 1; upper >= 0; --upper) + { + hUpperSum += mBuckets[upper]; + if (hUpperSum >= hTailSum) + { + break; + } + } + return upper; + } + + // Get the lower and upper tails of the histogram. The returned + // indices are L and U and have the properties: + // cdf(L-1)/N < tailAmount/2, cdf(L)/N >= tailAmount/2, + // cdf(U)/N >= 1-tailAmount/2, and cdf(U+1) < 1-tailAmount/2. + void GetTails(double tailAmount, int& lower, int& upper) + { + int const numBuckets = static_cast(mBuckets.size()); + int hSum = 0; + for (int i = 0; i < numBuckets; ++i) + { + hSum += mBuckets[i]; + } + + int hTailSum = static_cast(0.5 * tailAmount * hSum); + int hLowerSum = 0; + for (lower = 0; lower < numBuckets; ++lower) + { + hLowerSum += mBuckets[lower]; + if (hLowerSum >= hTailSum) + { + break; + } + } + + int hUpperSum = 0; + for (upper = numBuckets - 1; upper >= 0; --upper) + { + hUpperSum += mBuckets[upper]; + if (hUpperSum >= hTailSum) + { + break; + } + } + } + + private: + std::vector mBuckets; + int mExcessLess, mExcessGreater; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Hyperellipsoid.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Hyperellipsoid.h new file mode 100644 index 0000000..9c142df --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Hyperellipsoid.h @@ -0,0 +1,321 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// A hyperellipsoid has center K; axis directions U[0] through U[N-1], all +// unit-length vectors; and extents e[0] through e[N-1], all positive numbers. +// A point X = K + sum_{d=0}^{N-1} y[d]*U[d] is on the hyperellipsoid whenever +// sum_{d=0}^{N-1} (y[d]/e[d])^2 = 1. An algebraic representation for the +// hyperellipsoid is (X-K)^T * M * (X-K) = 1, where M is the NxN symmetric +// matrix M = sum_{d=0}^{N-1} U[d]*U[d]^T/e[d]^2, where the superscript T +// denotes transpose. Observe that U[i]*U[i]^T is a matrix, not a scalar dot +// product. The hyperellipsoid is also represented by a quadratic equation +// 0 = C + B^T*X + X^T*A*X, where C is a scalar, B is an Nx1 vector, and A is +// an NxN symmetric matrix with positive eigenvalues. The coefficients can be +// stored from lowest degree to highest degree, +// C = k[0] +// B = k[1], ..., k[N] +// A = k[N+1], ..., k[(N+1)(N+2)/2 - 1] +// where the A-coefficients are the upper-triangular elements of A listed in +// row-major order. For N = 2, X = (x[0],x[1]) and +// 0 = k[0] + +// k[1]*x[0] + k[2]*x[1] + +// k[3]*x[0]*x[0] + k[4]*x[0]*x[1] +// + k[5]*x[1]*x[1] +// For N = 3, X = (x[0],x[1],x[2]) and +// 0 = k[0] + +// k[1]*x[0] + k[2]*x[1] + k[3]*x[2] + +// k[4]*x[0]*x[0] + k[5]*x[0]*x[1] + k[6]*x[0]*x[2] + +// + k[7]*x[1]*x[1] + k[8]*x[1]*x[2] + +// + k[9]*x[2]*x[2] +// This equation can be factored to the form (X-K)^T * M * (X-K) = 1, where +// K = -A^{-1}*B/2, M = A/(B^T*A^{-1}*B/4-C). + +namespace WwiseGTE +{ + template + class Hyperellipsoid + { + public: + // Construction and destruction. The default constructor sets the + // center to Vector::Zero(), the axes to + // Vector::Unit(d), and all extents to 1. + Hyperellipsoid() + { + center.MakeZero(); + for (int d = 0; d < N; ++d) + { + axis[d].MakeUnit(d); + extent[d] = (Real)1; + } + } + + Hyperellipsoid(Vector const& inCenter, + std::array, N> const inAxis, + Vector const& inExtent) + : + center(inCenter), + axis(inAxis), + extent(inExtent) + { + } + + // Compute M = sum_{d=0}^{N-1} U[d]*U[d]^T/e[d]^2. + void GetM(Matrix& M) const + { + M.MakeZero(); + for (int d = 0; d < N; ++d) + { + Vector ratio = axis[d] / extent[d]; + M += OuterProduct(ratio, ratio); + } + } + + // Compute M^{-1} = sum_{d=0}^{N-1} U[d]*U[d]^T*e[d]^2. + void GetMInverse(Matrix& MInverse) const + { + MInverse.MakeZero(); + for (int d = 0; d < N; ++d) + { + Vector product = axis[d] * extent[d]; + MInverse += OuterProduct(product, product); + } + } + + // Construct the coefficients in the quadratic equation that represents + // the hyperellipsoid. + void ToCoefficients(std::array & coeff) const + { + int const numCoefficients = (N + 1) * (N + 2) / 2; + Matrix A; + Vector B; + Real C; + ToCoefficients(A, B, C); + Convert(A, B, C, coeff); + + // Arrange for one of the coefficients of the quadratic terms + // to be 1. + int quadIndex = numCoefficients - 1; + int maxIndex = quadIndex; + Real maxValue = std::fabs(coeff[quadIndex]); + for (int d = 2; d < N; ++d) + { + quadIndex -= d; + Real absValue = std::fabs(coeff[quadIndex]); + if (absValue > maxValue) + { + maxIndex = quadIndex; + maxValue = absValue; + } + } + + Real invMaxValue = (Real)1 / maxValue; + for (int i = 0; i < numCoefficients; ++i) + { + if (i != maxIndex) + { + coeff[i] *= invMaxValue; + } + else + { + coeff[i] = (Real)1; + } + } + } + + void ToCoefficients(Matrix& A, Vector& B, Real& C) const + { + GetM(A); + Vector product = A * center; + B = (Real)-2 * product; + C = Dot(center, product) - (Real)1; + } + + // Construct C, U[i], and e[i] from the equation. The return value is + // 'true' if and only if the input coefficients represent a + // hyperellipsoid. If the function returns 'false', the hyperellipsoid + // data members are undefined. + bool FromCoefficients(std::array const& coeff) + { + Matrix A; + Vector B; + Real C; + Convert(coeff, A, B, C); + return FromCoefficients(A, B, C); + } + + bool FromCoefficients(Matrix const& A, Vector const& B, Real C) + { + // Compute the center K = -A^{-1}*B/2. + bool invertible; + Matrix invA = Inverse(A, &invertible); + if (!invertible) + { + return false; + } + + center = ((Real)-0.5) * (invA * B); + + // Compute B^T*A^{-1}*B/4 - C = K^T*A*K - C = -K^T*B/2 - C. + Real rightSide = (Real)-0.5 * Dot(center, B) - C; + if (rightSide == (Real)0) + { + return false; + } + + // Compute M = A/(K^T*A*K - C). + Real invRightSide = (Real)1 / rightSide; + Matrix M = invRightSide * A; + + // Factor into M = R*D*R^T. M is symmetric, so it does not matter whether + // the matrix is stored in row-major or column-major order; they are + // equivalent. The output R, however, is in row-major order. + SymmetricEigensolver es(N, 32); + Matrix rotation; + std::array diagonal; + es.Solve(&M[0], +1); // diagonal[i] are nondecreasing + es.GetEigenvalues(&diagonal[0]); + es.GetEigenvectors(&rotation[0]); + if (es.GetEigenvectorMatrixType() == 0) + { + auto negLast = -rotation.GetCol(N - 1); + rotation.SetCol(N - 1, negLast); + } + + for (int d = 0; d < N; ++d) + { + if (diagonal[d] <= (Real)0) + { + return false; + } + + extent[d] = (Real)1 / std::sqrt(diagonal[d]); + axis[d] = rotation.GetCol(d); + } + + return true; + } + + // Public member access. + Vector center; + std::array, N> axis; + Vector extent; + + private: + static void Convert(std::array const& coeff, + Matrix& A, Vector& B, Real& C) + { + int i = 0; + C = coeff[i++]; + + for (int j = 0; j < N; ++j) + { + B[j] = coeff[i++]; + } + + for (int r = 0; r < N; ++r) + { + for (int c = 0; c < r; ++c) + { + A(r, c) = A(c, r); + } + + A(r, r) = coeff[i++]; + + for (int c = r + 1; c < N; ++c) + { + A(r, c) = coeff[i++] * (Real)0.5; + } + } + } + + static void Convert(Matrix const& A, Vector const& B, + Real C, std::array & coeff) + { + int i = 0; + coeff[i++] = C; + + for (int j = 0; j < N; ++j) + { + coeff[i++] = B[j]; + } + + for (int r = 0; r < N; ++r) + { + coeff[i++] = A(r, r); + for (int c = r + 1; c < N; ++c) + { + coeff[i++] = A(r, c) * (Real)2; + } + } + } + + public: + // Comparisons to support sorted containers. + bool operator==(Hyperellipsoid const& hyperellipsoid) const + { + return center == hyperellipsoid.center && axis == hyperellipsoid.axis + && extent == hyperellipsoid.extent; + } + + bool operator!=(Hyperellipsoid const& hyperellipsoid) const + { + return !operator==(hyperellipsoid); + } + + bool operator< (Hyperellipsoid const& hyperellipsoid) const + { + if (center < hyperellipsoid.center) + { + return true; + } + + if (center > hyperellipsoid.center) + { + return false; + } + + if (axis < hyperellipsoid.axis) + { + return true; + } + + if (axis > hyperellipsoid.axis) + { + return false; + } + + return extent < hyperellipsoid.extent; + } + + bool operator<=(Hyperellipsoid const& hyperellipsoid) const + { + return !hyperellipsoid.operator<(*this); + } + + bool operator> (Hyperellipsoid const& hyperellipsoid) const + { + return hyperellipsoid.operator<(*this); + } + + bool operator>=(Hyperellipsoid const& hyperellipsoid) const + { + return !operator<(hyperellipsoid); + } + }; + + // Template aliases for convenience. + template + using Ellipse2 = Hyperellipsoid<2, Real>; + + template + using Ellipsoid3 = Hyperellipsoid<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Hyperplane.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Hyperplane.h new file mode 100644 index 0000000..e7efc7d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Hyperplane.h @@ -0,0 +1,118 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The plane is represented as Dot(U,X) = c where U is a unit-length normal +// vector, c is the plane constant, and X is any point on the plane. The user +// must ensure that the normal vector is unit length. + +namespace WwiseGTE +{ + template + class Hyperplane + { + public: + // Construction and destruction. The default constructor sets the + // normal to (0,...,0,1) and the constant to zero (plane z = 0). + Hyperplane() + : + constant((Real)0) + { + normal.MakeUnit(N - 1); + } + + // Specify U and c directly. + Hyperplane(Vector const& inNormal, Real inConstant) + : + normal(inNormal), + constant(inConstant) + { + } + + // U is specified, c = Dot(U,p) where p is a point on the hyperplane. + Hyperplane(Vector const& inNormal, Vector const& p) + : + normal(inNormal), + constant(Dot(inNormal, p)) + { + } + + // U is a unit-length vector in the orthogonal complement of the set + // {p[1]-p[0],...,p[n-1]-p[0]} and c = Dot(U,p[0]), where the p[i] are + // pointson the hyperplane. + Hyperplane(std::array, N> const& p) + { + Matrix edge; + for (int i = 0; i < N - 1; ++i) + { + edge.SetCol(i, p[i + 1] - p[0]); + } + + // Compute the 1-dimensional orthogonal complement of the edges of + // the simplex formed by the points p[]. + SingularValueDecomposition svd(N, N - 1, 32); + svd.Solve(&edge[0], -1); + svd.GetUColumn(N - 1, &normal[0]); + + constant = Dot(normal, p[0]); + } + + // Public member access. + Vector normal; + Real constant; + + public: + // Comparisons to support sorted containers. + bool operator==(Hyperplane const& hyperplane) const + { + return normal == hyperplane.normal && constant == hyperplane.constant; + } + + bool operator!=(Hyperplane const& hyperplane) const + { + return !operator==(hyperplane); + } + + bool operator< (Hyperplane const& hyperplane) const + { + if (normal < hyperplane.normal) + { + return true; + } + + if (normal > hyperplane.normal) + { + return false; + } + + return constant < hyperplane.constant; + } + + bool operator<=(Hyperplane const& hyperplane) const + { + return !hyperplane.operator<(*this); + } + + bool operator> (Hyperplane const& hyperplane) const + { + return hyperplane.operator<(*this); + } + + bool operator>=(Hyperplane const& hyperplane) const + { + return !operator<(hyperplane); + } + }; + + // Template alias for convenience. + template + using Plane3 = Hyperplane<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Hypersphere.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Hypersphere.h new file mode 100644 index 0000000..54606b5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Hypersphere.h @@ -0,0 +1,91 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The hypersphere is represented as |X-C| = R where C is the center and R is +// the radius. The hypersphere is a circle for dimension 2 or a sphere for +// dimension 3. + +namespace WwiseGTE +{ + template + class Hypersphere + { + public: + // Construction and destruction. The default constructor sets the center + // to (0,...,0) and the radius to 1. + Hypersphere() + : + radius((Real)1) + { + center.MakeZero(); + } + + Hypersphere(Vector const& inCenter, Real inRadius) + : + center(inCenter), + radius(inRadius) + { + } + + // Public member access. + Vector center; + Real radius; + + public: + // Comparisons to support sorted containers. + bool operator==(Hypersphere const& hypersphere) const + { + return center == hypersphere.center && radius == hypersphere.radius; + } + + bool operator!=(Hypersphere const& hypersphere) const + { + return !operator==(hypersphere); + } + + bool operator< (Hypersphere const& hypersphere) const + { + if (center < hypersphere.center) + { + return true; + } + + if (center > hypersphere.center) + { + return false; + } + + return radius < hypersphere.radius; + } + + bool operator<=(Hypersphere const& hypersphere) const + { + return !hypersphere.operator<(*this); + } + + bool operator> (Hypersphere const& hypersphere) const + { + return hypersphere.operator<(*this); + } + + bool operator>=(Hypersphere const& hypersphere) const + { + return !operator<(hypersphere); + } + }; + + // Template aliases for convenience. + template + using Circle2 = Hypersphere<2, Real>; + + template + using Sphere3 = Hypersphere<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IEEEBinary.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IEEEBinary.h new file mode 100644 index 0000000..e1d629b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IEEEBinary.h @@ -0,0 +1,374 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.09.14 + +#pragma once + +#include + +namespace WwiseGTE +{ + template + class IEEEBinary + { + public: + // For generic access of the template types. + using FloatType = Float; + using UIntType = UInt; + + // Construction from an encoding. Copy constructor, destructor, and + // assignment operator are implicitly generated. For the 3-parameter + // constructor, see the comments for SetEncoding(...). + ~IEEEBinary() = default; + IEEEBinary() = default; + + IEEEBinary(IEEEBinary const& object) + : + encoding(object.encoding) + { + } + + IEEEBinary(UInt inEncoding) + : + encoding(inEncoding) + { + } + + IEEEBinary(UInt inSign, UInt inBiased, UInt inTrailing) + { + SetEncoding(inSign, inBiased, inTrailing); + } + + IEEEBinary(Float inNumber) + : + number(inNumber) + { + } + + // Implicit conversion to floating-point type. + inline operator UInt () const + { + return encoding; + } + + inline operator Float () const + { + return number; + } + + // Assignment. + IEEEBinary& operator= (IEEEBinary const& object) + { + encoding = object.encoding; + return *this; + } + + // Special constants. + static int const NUM_ENCODING_BITS = NumBits; + static int const NUM_EXPONENT_BITS = NumBits - Precision; + static int const NUM_SIGNIFICAND_BITS = Precision; + static int const NUM_TRAILING_BITS = Precision - 1; + static int const EXPONENT_BIAS = (1 << (NUM_EXPONENT_BITS - 1)) - 1; + static int const MAX_BIASED_EXPONENT = (1 << NUM_EXPONENT_BITS) - 1; + static int const MIN_SUB_EXPONENT = 1 - EXPONENT_BIAS; + static int const MIN_EXPONENT = MIN_SUB_EXPONENT - NUM_TRAILING_BITS; + static int const SIGN_SHIFT = NumBits - 1; + + static UInt const SIGN_MASK = (UInt(1) << (NumBits - 1)); + static UInt const NOT_SIGN_MASK = UInt(~SIGN_MASK); + static UInt const TRAILING_MASK = (UInt(1) << NUM_TRAILING_BITS) - 1; + static UInt const EXPONENT_MASK = NOT_SIGN_MASK & ~TRAILING_MASK; + static UInt const NAN_QUIET_MASK = (UInt(1) << (NUM_TRAILING_BITS - 1)); + static UInt const NAN_PAYLOAD_MASK = (TRAILING_MASK >> 1); + static UInt const MAX_TRAILING = TRAILING_MASK; + static UInt const SUP_TRAILING = (UInt(1) << NUM_TRAILING_BITS); + static UInt const POS_ZERO = UInt(0); + static UInt const NEG_ZERO = SIGN_MASK; + static UInt const MIN_SUBNORMAL = UInt(1); + static UInt const MAX_SUBNORMAL = TRAILING_MASK; + static UInt const MIN_NORMAL = SUP_TRAILING; + static UInt const MAX_NORMAL = NOT_SIGN_MASK & ~SUP_TRAILING; + static UInt const POS_INFINITY = EXPONENT_MASK; + static UInt const NEG_INFINITY = SIGN_MASK | EXPONENT_MASK; + + // The types of numbers. + enum Classification + { + CLASS_NEG_INFINITY, + CLASS_NEG_SUBNORMAL, + CLASS_NEG_NORMAL, + CLASS_NEG_ZERO, + CLASS_POS_ZERO, + CLASS_POS_SUBNORMAL, + CLASS_POS_NORMAL, + CLASS_POS_INFINITY, + CLASS_QUIET_NAN, + CLASS_SIGNALING_NAN + }; + + Classification GetClassification() const + { + UInt sign, biased, trailing; + GetEncoding(sign, biased, trailing); + + if (biased == 0) + { + if (trailing == 0) + { + return (sign != 0 ? CLASS_NEG_ZERO : CLASS_POS_ZERO); + } + else + { + return (sign != 0 ? CLASS_NEG_SUBNORMAL : CLASS_POS_SUBNORMAL); + } + } + else if (biased < MAX_BIASED_EXPONENT) + { + return (sign != 0 ? CLASS_NEG_NORMAL : CLASS_POS_NORMAL); + } + else if (trailing == 0) + { + return (sign != 0 ? CLASS_NEG_INFINITY : CLASS_POS_INFINITY); + } + else if (trailing & NAN_QUIET_MASK) + { + return CLASS_QUIET_NAN; + } + else + { + return CLASS_SIGNALING_NAN; + } + } + + bool IsZero() const + { + return encoding == POS_ZERO || encoding == NEG_ZERO; + } + + bool IsSignMinus() const + { + return (encoding & SIGN_MASK) != 0; + } + + bool IsSubnormal() const + { + return GetBiased() == 0 && GetTrailing() > 0; + } + + bool IsNormal() const + { + UInt biased = GetBiased(); + return 0 < biased&& biased < MAX_BIASED_EXPONENT; + } + + bool IsFinite() const + { + return GetBiased() < MAX_BIASED_EXPONENT; + } + + bool IsInfinite() const + { + return GetBiased() == MAX_BIASED_EXPONENT && GetTrailing() == 0; + } + + bool IsNaN() const + { + return GetBiased() == MAX_BIASED_EXPONENT && GetTrailing() != 0; + } + + bool IsSignalingNaN() const + { + UInt trailing = GetTrailing(); + return GetBiased() == MAX_BIASED_EXPONENT + && (trailing & NAN_QUIET_MASK) == 0 + && (trailing & NAN_PAYLOAD_MASK) != 0; + } + + // Get neighboring numbers. + UInt GetNextUp() const + { + UInt sign, biased, trailing; + GetEncoding(sign, biased, trailing); + + if (biased == 0) + { + if (trailing == 0) + { + // The next-up for both -0 and +0 is MIN_SUBNORMAL. + return MIN_SUBNORMAL; + } + else + { + if (sign != 0) + { + // When trailing is 1, 'this' is -MIN_SUBNORMAL and next-up + // is -0. + --trailing; + return SIGN_MASK | trailing; + } + else + { + // When trailing is MAX_TRAILING, 'this' is MAX_SUBNORMAL + // and next-up is MIN_NORMAL. + ++trailing; + return trailing; + } + } + } + else if (biased < MAX_BIASED_EXPONENT) + { + UInt nonnegative = (encoding & NOT_SIGN_MASK); + if (sign != 0) + { + --nonnegative; + return SIGN_MASK | nonnegative; + } + else + { + ++nonnegative; + return nonnegative; + } + } + else if (trailing == 0) + { + if (sign != 0) + { + // The next-up of -INFINITY is -MAX_NORMAL. + return SIGN_MASK | MAX_NORMAL; + } + else + { + // The next-up of +INFINITY is +INFINITY. + return POS_INFINITY; + } + } + else if (trailing & NAN_QUIET_MASK) + { + // TODO. The IEEE standard is not clear what to do here. Figure + // out what it means. + return 0; + } + else + { + // TODO. The IEEE standard is not clear what to do here. Figure + // out what it means. + return 0; + } + } + + UInt GetNextDown() const + { + UInt sign, biased, trailing; + GetEncoding(sign, biased, trailing); + + if (biased == 0) + { + if (trailing == 0) + { + // The next-down for both -0 and +0 is -MIN_SUBNORMAL. + return SIGN_MASK | MIN_SUBNORMAL; + } + else + { + if (sign == 0) + { + // When trailing is 1, 'this' is MIN_SUBNORMAL and next-down + // is +0. + --trailing; + return trailing; + } + else + { + // When trailing is MAX_TRAILING, 'this' is -MAX_SUBNORMAL + // and next-down is -MIN_NORMAL. + ++trailing; + return SIGN_MASK | trailing; + } + } + } + else if (biased < MAX_BIASED_EXPONENT) + { + UInt nonnegative = (encoding & NOT_SIGN_MASK); + if (sign == 0) + { + --nonnegative; + return nonnegative; + } + else + { + ++nonnegative; + return SIGN_MASK | nonnegative; + } + } + else if (trailing == 0) + { + if (sign == 0) + { + // The next-down of +INFINITY is +MAX_NORMAL. + return MAX_NORMAL; + } + else + { + // The next-down of -INFINITY is -INFINITY. + return NEG_INFINITY; + } + } + else if (trailing & NAN_QUIET_MASK) + { + // TODO. The IEEE standard is not clear what to do here. Figure + // out what it means. + return 0; + } + else + { + // TODO. The IEEE standard is not clear what to do here. Figure + // out what it means. + return 0; + } + } + + // Encode and decode the binary representation. The sign is 0 (number + // is nonnegative) or 1 (number is negative). The biased exponent is + // in the range [0,MAX_BIASED_EXPONENT]. The trailing significand is + // in the range [0,MAX_TRAILING]. + UInt GetSign() const + { + return (encoding & SIGN_MASK) >> SIGN_SHIFT; + } + + UInt GetBiased() const + { + return (encoding & EXPONENT_MASK) >> NUM_TRAILING_BITS; + } + + UInt GetTrailing() const + { + return encoding & TRAILING_MASK; + } + + void SetEncoding(UInt sign, UInt biased, UInt trailing) + { + encoding = (sign << SIGN_SHIFT) | (biased << NUM_TRAILING_BITS) | trailing; + } + + void GetEncoding(UInt& sign, UInt& biased, UInt& trailing) const + { + sign = GetSign(); + biased = GetBiased(); + trailing = GetTrailing(); + } + + // Access for direct manipulation of the object. + union + { + UInt encoding; + Float number; + }; + }; + + using IEEEBinary32 = IEEEBinary; + using IEEEBinary64 = IEEEBinary; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IEEEBinary16.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IEEEBinary16.h new file mode 100644 index 0000000..7e0087d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IEEEBinary16.h @@ -0,0 +1,592 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2020.01.08 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + class IEEEBinary16 : public IEEEBinary + { + public: + // Construction and destruction. The base class destructor is hidden, + // but this is safe because there are no side effects of the + // destruction. + ~IEEEBinary16() = default; + + IEEEBinary16() + : + IEEEBinary() + { + // uninitialized + } + + IEEEBinary16(IEEEBinary16 const& object) + : + IEEEBinary(object) + { + } + + IEEEBinary16(float inNumber) + : + IEEEBinary() + { + union { float n; uint32_t e; } temp = { inNumber }; + encoding = Convert32To16(temp.e); + } + + IEEEBinary16(double inNumber) + : + IEEEBinary() + { + union { float n; uint32_t e; } temp; + temp.n = (float)inNumber; + encoding = Convert32To16(temp.e); + } + + IEEEBinary16(uint16_t inEncoding) + : + IEEEBinary(inEncoding) + { + } + + // Implicit conversions. + operator float() const + { + union { uint32_t e; float n; } temp = { Convert16To32(encoding) }; + return temp.n; + } + + operator double() const + { + union { uint32_t e; float n; } temp = { Convert16To32(encoding) }; + return (double)temp.n; + } + + // Assignment. + IEEEBinary16& operator=(IEEEBinary16 const& object) + { + IEEEBinary::operator=(object); + return *this; + } + + // Comparison. + bool operator==(IEEEBinary16 const& object) const + { + return (float)*this == (float)object; + } + + bool operator!=(IEEEBinary16 const& object) const + { + return (float)*this != (float)object; + } + + bool operator< (IEEEBinary16 const& object) const + { + return (float)*this < (float)object; + } + + bool operator<=(IEEEBinary16 const& object) const + { + return (float)*this <= (float)object; + } + + bool operator> (IEEEBinary16 const& object) const + { + return (float)* this > (float)object; + } + + bool operator>=(IEEEBinary16 const& object) const + { + return (float)* this >= (float)object; + } + + private: + // Support for conversions between encodings. + enum + { + F32_NUM_ENCODING_BITS = 32, + F32_NUM_TRAILING_BITS = 23, + F32_EXPONENT_BIAS = 127, + F32_MAX_BIASED_EXPONENT = 255, + F32_SIGN_MASK = 0x80000000, + F32_NOT_SIGN_MASK = 0x7FFFFFFF, + F32_BIASED_EXPONENT_MASK = 0x7F800000, + F32_TRAILING_MASK = 0x007FFFFF, + F16_AVR_MIN_SUBNORMAL_ZERO = 0x33000000, + F16_MIN_SUBNORMAL = 0x33800000, + F16_MIN_NORMAL = 0x38800000, + F16_MAX_NORMAL = 0x477FE000, + F16_AVR_MAX_NORMAL_INFINITY = 0x477FF000, + DIFF_NUM_ENCODING_BITS = 16, + DIFF_NUM_TRAILING_BITS = 13, + DIFF_PAYLOAD_SHIFT = 13, + INT_PART_MASK = 0x007FE000, + FRC_PART_MASK = 0x00001FFF, + FRC_HALF = 0x00001000 + }; + + static uint16_t Convert32To16(uint32_t inEncoding) + { + // Extract the channels for the binary32 number. + uint32_t sign32 = (inEncoding & F32_SIGN_MASK); + uint32_t biased32 = + ((inEncoding & F32_BIASED_EXPONENT_MASK) >> F32_NUM_TRAILING_BITS); + uint32_t trailing32 = (inEncoding & F32_TRAILING_MASK); + uint32_t nonneg32 = (inEncoding & F32_NOT_SIGN_MASK); + + // Generate the channels for the IEEEBinary16 number. + uint16_t sign16 = static_cast(sign32 >> DIFF_NUM_ENCODING_BITS); + uint16_t biased16, trailing16; + uint32_t frcpart; + + if (biased32 == 0) + { + // nonneg32 is 32-zero or 32-subnormal, nearest is 16-zero. + return sign16; + } + + if (biased32 < F32_MAX_BIASED_EXPONENT) + { + // nonneg32 is 32-normal. + if (nonneg32 <= F16_AVR_MIN_SUBNORMAL_ZERO) + { + // nonneg32 <= 2^{-25}, nearest is 16-zero. + return sign16; + } + + if (nonneg32 <= F16_MIN_SUBNORMAL) + { + // 2^{-25} < nonneg32 <= 2^{-24}, nearest is + // 16-min-subnormal. + return sign16 | IEEEBinary16::MIN_SUBNORMAL; + } + + if (nonneg32 < F16_MIN_NORMAL) + { + // 2^{-24} < nonneg32 < 2^{-14}, round to nearest + // 16-subnormal with ties to even. Note that biased16 is + // zero. + trailing16 = static_cast(((trailing32 & INT_PART_MASK) >> DIFF_NUM_TRAILING_BITS)); + frcpart = (trailing32 & FRC_PART_MASK); + if (frcpart > FRC_HALF || (frcpart == FRC_HALF && (trailing16 & 1))) + { + // If there is a carry into the exponent, the nearest + // is actually 16-min-normal 1.0*2^{-14}, so the + // high-order bit of trailing16 makes biased16 equal + // to 1 and the result is correct. + ++trailing16; + } + return sign16 | trailing16; + } + + if (nonneg32 <= F16_MAX_NORMAL) + { + // 2^{-14} <= nonneg32 <= 1.1111111111*2^{15}, round to + // nearest 16-normal with ties to even. + biased16 = static_cast((biased32 - F32_EXPONENT_BIAS + + IEEEBinary16::EXPONENT_BIAS) + << IEEEBinary16::NUM_TRAILING_BITS); + trailing16 = static_cast(((trailing32 & INT_PART_MASK) >> DIFF_NUM_TRAILING_BITS)); + frcpart = (trailing32 & FRC_PART_MASK); + if (frcpart > FRC_HALF || (frcpart == FRC_HALF && (trailing16 & 1))) + { + // If there is a carry into the exponent, the addition + // of trailing16 to biased16 (rather than or-ing) + // produces the correct result. + ++trailing16; + } + return sign16 | (biased16 + trailing16); + } + + if (nonneg32 < F16_AVR_MAX_NORMAL_INFINITY) + { + // 1.1111111111*2^{15} < nonneg32 < (MAX_NORMAL+INFINITY)/2, + // so the number is closest to 16-max-normal. + return sign16 | IEEEBinary16::MAX_NORMAL; + } + + // nonneg32 >= (MAX_NORMAL+INFINITY)/2, so convert to + // 16-infinite. + return sign16 | IEEEBinary16::POS_INFINITY; + } + + if (trailing32 == 0) + { + // The number is 32-infinite. Convert to 16-infinite. + return sign16 | IEEEBinary16::POS_INFINITY; + } + + // The number is 32-NaN. Convert to 16-NaN with 16-payload the + // high-order 9 bits of the 32-payload. The code also grabs the + // 32-quietNaN mask bit. + uint16_t maskPayload = static_cast((trailing32 & 0x007FE000u) >> 13); + return sign16 | IEEEBinary16::EXPONENT_MASK | maskPayload; + } + + static uint32_t Convert16To32(uint16_t inEncoding) + { + // Extract the channels for the IEEEBinary16 number. + uint16_t sign16 = (inEncoding & IEEEBinary16::SIGN_MASK); + uint16_t biased16 = ((inEncoding & IEEEBinary16::EXPONENT_MASK) >> IEEEBinary16::NUM_TRAILING_BITS); + uint16_t trailing16 = (inEncoding & IEEEBinary16::TRAILING_MASK); + + // Generate the channels for the binary32 number. + uint32_t sign32 = static_cast(sign16 << DIFF_NUM_ENCODING_BITS); + uint32_t biased32, trailing32; + + if (biased16 == 0) + { + if (trailing16 == 0) + { + // The number is 16-zero. Convert to 32-zero. + return sign32; + } + else + { + // The number is 16-subnormal. Convert to 32-normal. + trailing32 = static_cast(trailing16); + int32_t leading = BitHacks::GetLeadingBit(trailing32); + int32_t shift = 23 - leading; + biased32 = static_cast(F32_EXPONENT_BIAS - 1 - shift); + trailing32 = (trailing32 << shift) & F32_TRAILING_MASK; + return sign32 | (biased32 << F32_NUM_TRAILING_BITS) | trailing32; + } + } + + if (biased16 < IEEEBinary16::MAX_BIASED_EXPONENT) + { + // The number is 16-normal. Convert to 32-normal. + biased32 = static_cast(biased16 - IEEEBinary16::EXPONENT_BIAS + F32_EXPONENT_BIAS); + trailing32 = (static_cast(trailing16) << DIFF_NUM_TRAILING_BITS); + return sign32 | (biased32 << F32_NUM_TRAILING_BITS) | trailing32; + } + + if (trailing16 == 0) + { + // The number is 16-infinite. Convert to 32-infinite. + return sign32 | F32_BIASED_EXPONENT_MASK; + } + + // The number is 16-NaN. Convert to 32-NaN with 16-payload + // embedded in the high-order 9 bits of the 32-payload. The + // code also copies the 16-quietNaN mask bit. + uint32_t maskPayload = ((trailing16 & IEEEBinary16::TRAILING_MASK) << DIFF_PAYLOAD_SHIFT); + return sign32 | F32_BIASED_EXPONENT_MASK | maskPayload; + } + }; + + // Arithmetic operations (high-precision). + inline IEEEBinary16 operator-(IEEEBinary16 x) + { + uint16_t result = static_cast(x) ^ IEEEBinary16::SIGN_MASK; + return result; + } + + inline float operator+(IEEEBinary16 x, IEEEBinary16 y) + { + return static_cast(x) + static_cast(y); + } + + inline float operator-(IEEEBinary16 x, IEEEBinary16 y) + { + return static_cast(x) - static_cast(y); + } + + inline float operator*(IEEEBinary16 x, IEEEBinary16 y) + { + return static_cast(x)* static_cast(y); + } + + inline float operator/(IEEEBinary16 x, IEEEBinary16 y) + { + return static_cast(x) / static_cast(y); + } + + inline float operator+(IEEEBinary16 x, float y) + { + return static_cast(x) + y; + } + + inline float operator-(IEEEBinary16 x, float y) + { + return static_cast(x) - y; + } + + inline float operator*(IEEEBinary16 x, float y) + { + return static_cast(x)* y; + } + + inline float operator/(IEEEBinary16 x, float y) + { + return static_cast(x) / y; + } + + inline float operator+(float x, IEEEBinary16 y) + { + return x + static_cast(y); + } + + inline float operator-(float x, IEEEBinary16 y) + { + return x - static_cast(y); + } + + inline float operator*(float x, IEEEBinary16 y) + { + return x * static_cast(y); + } + + inline float operator/(float x, IEEEBinary16 y) + { + return x / static_cast(y); + } + + // Arithmetic updates. + inline IEEEBinary16& operator+=(IEEEBinary16& x, IEEEBinary16 y) + { + x = static_cast(x) + static_cast(y); + return x; + } + + inline IEEEBinary16& operator-=(IEEEBinary16& x, IEEEBinary16 y) + { + x = static_cast(x) - static_cast(y); + return x; + } + + inline IEEEBinary16& operator*=(IEEEBinary16& x, IEEEBinary16 y) + { + x = static_cast(x) * static_cast(y); + return x; + } + + inline IEEEBinary16& operator/=(IEEEBinary16& x, IEEEBinary16 y) + { + x = static_cast(x) / static_cast(y); + return x; + } + + inline IEEEBinary16& operator+=(IEEEBinary16& x, float y) + { + x = static_cast(x) + y; + return x; + } + + inline IEEEBinary16& operator-=(IEEEBinary16& x, float y) + { + x = static_cast(x) - y; + return x; + } + + inline IEEEBinary16& operator*=(IEEEBinary16& x, float y) + { + x = static_cast(x) * y; + return x; + } + + inline IEEEBinary16& operator/=(IEEEBinary16& x, float y) + { + x = static_cast(x) / y; + return x; + } +} + +namespace std +{ + inline WwiseGTE::IEEEBinary16 acos(WwiseGTE::IEEEBinary16 x) + { + return (WwiseGTE::IEEEBinary16)std::acos((float)x); + } + + inline WwiseGTE::IEEEBinary16 acosh(WwiseGTE::IEEEBinary16 x) + { + return (WwiseGTE::IEEEBinary16)std::acosh((float)x); + } + + inline WwiseGTE::IEEEBinary16 asin(WwiseGTE::IEEEBinary16 x) + { + return (WwiseGTE::IEEEBinary16)std::asin((float)x); + } + + inline WwiseGTE::IEEEBinary16 asinh(WwiseGTE::IEEEBinary16 x) + { + return (WwiseGTE::IEEEBinary16)std::asin((float)x); + } + + inline WwiseGTE::IEEEBinary16 atan(WwiseGTE::IEEEBinary16 x) + { + return (WwiseGTE::IEEEBinary16)std::atan((float)x); + } + + inline WwiseGTE::IEEEBinary16 atanh(WwiseGTE::IEEEBinary16 x) + { + return (WwiseGTE::IEEEBinary16)std::atanh((float)x); + } + + inline WwiseGTE::IEEEBinary16 atan2(WwiseGTE::IEEEBinary16 y, WwiseGTE::IEEEBinary16 x) + { + return (WwiseGTE::IEEEBinary16)std::atan2((float)y, (float)x); + } + + inline WwiseGTE::IEEEBinary16 ceil(WwiseGTE::IEEEBinary16 x) + { + return (WwiseGTE::IEEEBinary16)std::ceil((float)x); + } + + inline WwiseGTE::IEEEBinary16 cos(WwiseGTE::IEEEBinary16 x) + { + return (WwiseGTE::IEEEBinary16)std::cos((float)x); + } + + inline WwiseGTE::IEEEBinary16 cosh(WwiseGTE::IEEEBinary16 x) + { + return (WwiseGTE::IEEEBinary16)std::cosh((float)x); + } + + inline WwiseGTE::IEEEBinary16 exp(WwiseGTE::IEEEBinary16 x) + { + return (WwiseGTE::IEEEBinary16)std::exp((float)x); + } + + inline WwiseGTE::IEEEBinary16 exp2(WwiseGTE::IEEEBinary16 x) + { + return (WwiseGTE::IEEEBinary16)std::exp2((float)x); + } + + inline WwiseGTE::IEEEBinary16 fabs(WwiseGTE::IEEEBinary16 x) + { + return (WwiseGTE::IEEEBinary16)std::fabs((float)x); + } + + inline WwiseGTE::IEEEBinary16 floor(WwiseGTE::IEEEBinary16 x) + { + return (WwiseGTE::IEEEBinary16)std::floor((float)x); + } + + inline WwiseGTE::IEEEBinary16 fmod(WwiseGTE::IEEEBinary16 x, WwiseGTE::IEEEBinary16 y) + { + return (WwiseGTE::IEEEBinary16)std::fmod((float)x, (float)y); + } + + inline WwiseGTE::IEEEBinary16 frexp(WwiseGTE::IEEEBinary16 x, int* exponent) + { + return (WwiseGTE::IEEEBinary16)std::frexp((float)x, exponent); + } + + inline WwiseGTE::IEEEBinary16 ldexp(WwiseGTE::IEEEBinary16 x, int exponent) + { + return (WwiseGTE::IEEEBinary16)std::ldexp((float)x, exponent); + } + + inline WwiseGTE::IEEEBinary16 log(WwiseGTE::IEEEBinary16 x) + { + return (WwiseGTE::IEEEBinary16)std::log((float)x); + } + + inline WwiseGTE::IEEEBinary16 log2(WwiseGTE::IEEEBinary16 x) + { + return (WwiseGTE::IEEEBinary16)std::log2((float)x); + } + + inline WwiseGTE::IEEEBinary16 log10(WwiseGTE::IEEEBinary16 x) + { + return (WwiseGTE::IEEEBinary16)std::log10((float)x); + } + + inline WwiseGTE::IEEEBinary16 pow(WwiseGTE::IEEEBinary16 x, WwiseGTE::IEEEBinary16 y) + { + return (WwiseGTE::IEEEBinary16)std::pow((float)x, (float)y); + } + + inline WwiseGTE::IEEEBinary16 sin(WwiseGTE::IEEEBinary16 x) + { + return (WwiseGTE::IEEEBinary16)std::sin((float)x); + } + + inline WwiseGTE::IEEEBinary16 sinh(WwiseGTE::IEEEBinary16 x) + { + return (WwiseGTE::IEEEBinary16)std::sinh((float)x); + } + + inline WwiseGTE::IEEEBinary16 sqrt(WwiseGTE::IEEEBinary16 x) + { + return (WwiseGTE::IEEEBinary16)std::sqrt((float)x); + } + + inline WwiseGTE::IEEEBinary16 tan(WwiseGTE::IEEEBinary16 x) + { + return (WwiseGTE::IEEEBinary16)std::tan((float)x); + } + + inline WwiseGTE::IEEEBinary16 tanh(WwiseGTE::IEEEBinary16 x) + { + return (WwiseGTE::IEEEBinary16)std::tanh((float)x); + } +} + +namespace WwiseGTE +{ + inline IEEEBinary16 atandivpi(IEEEBinary16 x) + { + return (IEEEBinary16)atandivpi((float)x); + } + + inline IEEEBinary16 atan2divpi(IEEEBinary16 y, IEEEBinary16 x) + { + return (IEEEBinary16)atan2divpi((float)y, (float)x); + } + + inline IEEEBinary16 clamp(IEEEBinary16 x, IEEEBinary16 xmin, IEEEBinary16 xmax) + { + return (IEEEBinary16)clamp((float)x, (float)xmin, (float)xmax); + } + + inline IEEEBinary16 cospi(IEEEBinary16 x) + { + return (IEEEBinary16)cospi((float)x); + } + + inline IEEEBinary16 exp10(IEEEBinary16 x) + { + return (IEEEBinary16)exp10((float)x); + } + + inline IEEEBinary16 invsqrt(IEEEBinary16 x) + { + return (IEEEBinary16)invsqrt((float)x); + } + + inline int isign(IEEEBinary16 x) + { + return isign((float)x); + } + + inline IEEEBinary16 saturate(IEEEBinary16 x) + { + return (IEEEBinary16)saturate((float)x); + } + + inline IEEEBinary16 sign(IEEEBinary16 x) + { + return (IEEEBinary16)sign((float)x); + } + + inline IEEEBinary16 sinpi(IEEEBinary16 x) + { + return (IEEEBinary16)sinpi((float)x); + } + + inline IEEEBinary16 sqr(IEEEBinary16 x) + { + return (IEEEBinary16)sqr((float)x); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Image.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Image.h new file mode 100644 index 0000000..63314ef --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Image.h @@ -0,0 +1,192 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + template + class Image + { + public: + // Construction and destruction. + virtual ~Image() + { + } + + Image() + { + } + + Image(std::vector const& dimensions) + { + Reconstruct(dimensions); + } + + // Support for copy semantics. + Image(Image const& image) + { + *this = image; + } + + Image& operator=(Image const& image) + { + mDimensions = image.mDimensions; + mOffsets = image.mOffsets; + mPixels = image.mPixels; + return *this; + } + + // Support for move semantics. + Image(Image&& image) + { + *this = std::move(image); + } + + Image& operator=(Image&& image) + { + mDimensions = std::move(image.mDimensions); + mOffsets = std::move(image.mOffsets); + mPixels = std::move(image.mPixels); + return *this; + } + + // Support for changing the image dimensions. All pixel data is lost + // by this operation. + void Reconstruct(std::vector const& dimensions) + { + mDimensions.clear(); + mOffsets.clear(); + mPixels.clear(); + + if (dimensions.size() > 0) + { + for (auto dim : dimensions) + { + if (dim <= 0) + { + return; + } + } + + mDimensions = dimensions; + mOffsets.resize(dimensions.size()); + + size_t numPixels = 1; + for (size_t d = 0; d < dimensions.size(); ++d) + { + numPixels *= static_cast(mDimensions[d]); + } + + mOffsets[0] = 1; + for (size_t d = 1; d < dimensions.size(); ++d) + { + mOffsets[d] = static_cast(mDimensions[d - 1]) * mOffsets[d - 1]; + } + + mPixels.resize(numPixels); + } + } + + // Access to image data. + inline std::vector const& GetDimensions() const + { + return mDimensions; + } + + inline int GetNumDimensions() const + { + return static_cast(mDimensions.size()); + } + + inline int GetDimension(int d) const + { + return mDimensions[d]; + } + + inline std::vector const& GetOffsets() const + { + return mOffsets; + } + + inline size_t GetOffset(int d) const + { + return mOffsets[d]; + } + + inline std::vector const& GetPixels() const + { + return mPixels; + } + + inline std::vector& GetPixels() + { + return mPixels; + } + + inline size_t GetNumPixels() const + { + return mPixels.size(); + } + + // Conversions between n-dim and 1-dim structures. The 'coord' arrays + // must have GetNumDimensions() elements. + size_t GetIndex(int const* coord) const + { + // assert: coord is array of mNumDimensions elements + int const numDimensions = static_cast(mDimensions.size()); + size_t index = coord[0]; + for (int d = 1; d < numDimensions; ++d) + { + index += mOffsets[d] * coord[d]; + } + return index; + } + + void GetCoordinates(size_t index, int* coord) const + { + // assert: coord is array of numDimensions elements + int const numDimensions = static_cast(mDimensions.size()); + for (int d = 0; d < numDimensions; ++d) + { + coord[d] = index % mDimensions[d]; + index /= mDimensions[d]; + } + } + + // Access the data as a 1-dimensional array. The operator[] functions + // test for valid i when iterator checking is enabled and assert on + // invalid i. The Get() functions test for valid i and clamp when + // invalid; these functions cannot fail. + inline PixelType& operator[] (size_t i) + { + return mPixels[i]; + } + + inline PixelType const& operator[] (size_t i) const + { + return mPixels[i]; + } + + PixelType& Get(size_t i) + { + return (i < mPixels.size() ? mPixels[i] : mPixels.front()); + } + + PixelType const& Get(size_t i) const + { + return (i < mPixels.size() ? mPixels[i] : mPixels.front()); + } + + protected: + std::vector mDimensions; + std::vector mOffsets; + std::vector mPixels; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Image2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Image2.h new file mode 100644 index 0000000..eb438ed --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Image2.h @@ -0,0 +1,512 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +//#define GTE_THROW_ON_IMAGE2_ERRORS + +namespace WwiseGTE +{ + template + class Image2 : public Image + { + public: + // Construction and destruction. The last constructor must have + // positive dimensions; otherwise, the image is empty. + virtual ~Image2() + { + } + + Image2() + { + } + + Image2(int dimension0, int dimension1) + : + Image(std::vector{ dimension0, dimension1 }) + { + } + + // Support for copy semantics. + Image2(Image2 const& image) + : + Image(image) + { + } + + Image2& operator=(Image2 const& image) + { + Image::operator=(image); + return *this; + } + + // Support for move semantics. + Image2(Image2&& image) + { + *this = std::move(image); + } + + Image2& operator= (Image2&& image) + { + Image::operator=(image); + return *this; + } + + // Support for changing the image dimensions. All pixel data is lost + // by this operation. + void Reconstruct(int dimension0, int dimension1) + { + Image::Reconstruct(std::vector{ dimension0, dimension1 }); + } + + // Conversion between 1-dimensional indices and 2-dimensional + // coordinates. + inline size_t GetIndex(int x, int y) const + { +#if defined(GTE_THROW_ON_IMAGE2_ERRORS) + if (0 <= x && x < this->mDimensions[0] + && 0 <= y && y < this->mDimensions[1]) + { + return static_cast(x) + + static_cast(this->mDimensions[0]) * static_cast(y); + } + else + { + LogError( + "Invalid coordinates (" + std::to_string(x) + "," + + std::to_string(y) + ")."); + } +#else + return static_cast(x) + + static_cast(this->mDimensions[0]) * static_cast(y); +#endif + } + + inline size_t GetIndex(std::array const& coord) const + { +#if defined(GTE_THROW_ON_IMAGE2_ERRORS) + if (0 <= coord[0] && coord[0] < this->mDimensions[0] + && 0 <= coord[1] && coord[1] < this->mDimensions[1]) + { + return static_cast(coord[0]) + + static_cast(this->mDimensions[0]) * static_cast(coord[1]); + } + else + { + LogError( + "Invalid coordinates (" + std::to_string(coord[0]) + "," + + std::to_string(coord[1]) + ")."); + } +#else + return static_cast(coord[0]) + + static_cast(this->mDimensions[0]) * static_cast(coord[1]); +#endif + } + + inline void GetCoordinates(size_t index, int& x, int& y) const + { +#if defined(GTE_THROW_ON_IMAGE2_ERRORS) + if (index < this->mPixels.size()) + { + x = static_cast(index % this->mDimensions[0]); + y = static_cast(index / this->mDimensions[0]); + } + else + { + LogError( + "Invalid index " + std::to_string(index) + "."); + } +#else + x = static_cast(index % this->mDimensions[0]); + y = static_cast(index / this->mDimensions[0]); +#endif + } + + inline std::array GetCoordinates(size_t index) const + { + std::array coord; +#if defined(GTE_THROW_ON_IMAGE2_ERRORS) + if (index < this->mPixels.size()) + { + coord[0] = static_cast(index % this->mDimensions[0]); + coord[1] = static_cast(index / this->mDimensions[0]); + return coord; + } + else + { + LogError( + "Invalid index " + std::to_string(index) + "."); + } +#else + coord[0] = static_cast(index % this->mDimensions[0]); + coord[1] = static_cast(index / this->mDimensions[0]); + return coord; +#endif + } + + // Access the data as a 2-dimensional array. The operator() functions + // test for valid (x,y) when iterator checking is enabled and throw + // on invalid (x,y). The Get() functions test for valid (x,y) and + // clamp when invalid; these functions cannot fail. + inline PixelType& operator() (int x, int y) + { +#if defined(GTE_THROW_ON_IMAGE2_ERRORS) + if (0 <= x && x < this->mDimensions[0] + && 0 <= y && y < this->mDimensions[1]) + { + return this->mPixels[x + this->mDimensions[0] * y]; + } + else + { + LogError( + "Invalid coordinates (" + std::to_string(x) + "," + + std::to_string(y) + ")."); + } +#else + return this->mPixels[x + this->mDimensions[0] * y]; +#endif + } + + inline PixelType const& operator() (int x, int y) const + { +#if defined(GTE_THROW_ON_IMAGE2_ERRORS) + if (0 <= x && x < this->mDimensions[0] + && 0 <= y && y < this->mDimensions[1]) + { + return this->mPixels[x + this->mDimensions[0] * y]; + } + else + { + LogError( + "Invalid coordinates (" + std::to_string(x) + "," + + std::to_string(y) + ")."); + } +#else + return this->mPixels[x + this->mDimensions[0] * y]; +#endif + } + + inline PixelType& operator() (std::array const& coord) + { +#if defined(GTE_THROW_ON_IMAGE2_ERRORS) + if (0 <= coord[0] && coord[0] < this->mDimensions[0] + && 0 <= coord[1] && coord[1] < this->mDimensions[1]) + { + return this->mPixels[coord[0] + this->mDimensions[0] * coord[1]]; + } + else + { + LogError( + "Invalid coordinates (" + std::to_string(coord[0]) + "," + + std::to_string(coord[1]) + ")."); + } +#else + return this->mPixels[coord[0] + this->mDimensions[0] * coord[1]]; +#endif + } + + inline PixelType const& operator() (std::array const& coord) const + { +#if defined(GTE_THROW_ON_IMAGE2_ERRORS) + if (0 <= coord[0] && coord[0] < this->mDimensions[0] + && 0 <= coord[1] && coord[1] < this->mDimensions[1]) + { + return this->mPixels[coord[0] + this->mDimensions[0] * coord[1]]; + } + else + { + LogError( + "Invalid coordinates (" + std::to_string(coord[0]) + "," + + std::to_string(coord[1]) + ")."); + } +#else + return this->mPixels[coord[0] + this->mDimensions[0] * coord[1]]; +#endif + } + + inline PixelType& Get(int x, int y) + { + // Clamp to valid (x,y). + if (x < 0) + { + x = 0; + } + else if (x >= this->mDimensions[0]) + { + x = this->mDimensions[0] - 1; + } + + if (y < 0) + { + y = 0; + } + else if (y >= this->mDimensions[1]) + { + y = this->mDimensions[1] - 1; + } + + return this->mPixels[x + this->mDimensions[0] * y]; + } + + inline PixelType const& Get(int x, int y) const + { + // Clamp to valid (x,y). + if (x < 0) + { + x = 0; + } + else if (x >= this->mDimensions[0]) + { + x = this->mDimensions[0] - 1; + } + + if (y < 0) + { + y = 0; + } + else if (y >= this->mDimensions[1]) + { + y = this->mDimensions[1] - 1; + } + + return this->mPixels[x + this->mDimensions[0] * y]; + } + + inline PixelType& Get(std::array coord) + { + // Clamp to valid (x,y). + for (int i = 0; i < 2; ++i) + { + if (coord[i] < 0) + { + coord[i] = 0; + } + else if (coord[i] >= this->mDimensions[i]) + { + coord[i] = this->mDimensions[i] - 1; + } + } + + return this->mPixels[coord[0] + this->mDimensions[0] * coord[1]]; + } + + inline PixelType const& Get(std::array coord) const + { + // Clamp to valid (x,y). + for (int i = 0; i < 2; ++i) + { + if (coord[i] < 0) + { + coord[i] = 0; + } + else if (coord[i] >= this->mDimensions[i]) + { + coord[i] = this->mDimensions[i] - 1; + } + } + + return this->mPixels[coord[0] + this->mDimensions[0] * coord[1]]; + } + + // In the following discussion, u and v are in {-1,1}. Given a pixel + // (x,y), the 4-connected neighbors have relative offsets (u,0) and + // (0,v). The 8-connected neighbors include the 4-connected neighbors + // and have additional relative offsets (u,v). The corner neighbors + // have relative offsets (0,0), (1,0), (0,1), and (1,1) in that order. + // The full neighborhood is the set of 3x3 pixels centered at (x,y). + + // The neighborhoods can be accessed as 1-dimensional indices using + // these functions. The first four functions provide 1-dimensional + // indices relative to any pixel location; these depend only on the + // image dimensions. The last four functions provide 1-dimensional + // indices for the actual pixels in the neighborhood; no clamping is + // used when (x,y) is on the boundary. + void GetNeighborhood(std::array& nbr) const + { + int dim0 = this->mDimensions[0]; + nbr[0] = -1; // (x-1,y) + nbr[1] = +1; // (x+1,y) + nbr[2] = -dim0; // (x,y-1) + nbr[3] = +dim0; // (x,y+1) + } + + void GetNeighborhood(std::array& nbr) const + { + int dim0 = this->mDimensions[0]; + nbr[0] = -1; // (x-1,y) + nbr[1] = +1; // (x+1,y) + nbr[2] = -dim0; // (x,y-1) + nbr[3] = +dim0; // (x,y+1) + nbr[4] = -1 - dim0; // (x-1,y-1) + nbr[5] = +1 - dim0; // (x+1,y-1) + nbr[6] = -1 + dim0; // (x-1,y+1) + nbr[7] = +1 + dim0; // (x+1,y+1) + } + + void GetCorners(std::array& nbr) const + { + int dim0 = this->mDimensions[0]; + nbr[0] = 0; // (x,y) + nbr[1] = 1; // (x+1,y) + nbr[2] = dim0; // (x,y+1) + nbr[3] = dim0 + 1; // (x+1,y+1) + } + + void GetFull(std::array& nbr) const + { + int dim0 = this->mDimensions[0]; + nbr[0] = -1 - dim0; // (x-1,y-1) + nbr[1] = -dim0; // (x,y-1) + nbr[2] = +1 - dim0; // (x+1,y-1) + nbr[3] = -1; // (x-1,y) + nbr[4] = 0; // (x,y) + nbr[5] = +1; // (x+1,y) + nbr[6] = -1 + dim0; // (x-1,y+1) + nbr[7] = +dim0; // (x,y+1) + nbr[8] = +1 + dim0; // (x+1,y+1) + } + + void GetNeighborhood(int x, int y, std::array& nbr) const + { + size_t index = GetIndex(x, y); + std::array inbr; + GetNeighborhood(inbr); + for (int i = 0; i < 4; ++i) + { + nbr[i] = index + inbr[i]; + } + } + + void GetNeighborhood(int x, int y, std::array& nbr) const + { + size_t index = GetIndex(x, y); + std::array inbr; + GetNeighborhood(inbr); + for (int i = 0; i < 8; ++i) + { + nbr[i] = index + inbr[i]; + } + } + + void GetCorners(int x, int y, std::array& nbr) const + { + size_t index = GetIndex(x, y); + std::array inbr; + GetCorners(inbr); + for (int i = 0; i < 4; ++i) + { + nbr[i] = index + inbr[i]; + } + } + + void GetFull(int x, int y, std::array& nbr) const + { + size_t index = GetIndex(x, y); + std::array inbr; + GetFull(inbr); + for (int i = 0; i < 9; ++i) + { + nbr[i] = index + inbr[i]; + } + } + + // The neighborhoods can be accessed as 2-tuples using these + // functions. The first four functions provide 2-tuples relative to + // any pixel location; these depend only on the image dimensions. The + // last four functions provide 2-tuples for the actual pixels in the + // neighborhood; no clamping is used when (x,y) is on the boundary. + void GetNeighborhood(std::array, 4>& nbr) const + { + nbr[0] = { { -1, 0 } }; + nbr[1] = { { +1, 0 } }; + nbr[2] = { { 0, -1 } }; + nbr[3] = { { 0, +1 } }; + } + + void GetNeighborhood(std::array, 8>& nbr) const + { + nbr[0] = { { -1, -1 } }; + nbr[1] = { { 0, -1 } }; + nbr[2] = { { +1, -1 } }; + nbr[3] = { { -1, 0 } }; + nbr[4] = { { +1, 0 } }; + nbr[5] = { { -1, +1 } }; + nbr[6] = { { 0, +1 } }; + nbr[7] = { { +1, +1 } }; + } + + void GetCorners(std::array, 4>& nbr) const + { + nbr[0] = { { 0, 0 } }; + nbr[1] = { { 1, 0 } }; + nbr[2] = { { 0, 1 } }; + nbr[3] = { { 1, 1 } }; + } + + void GetFull(std::array, 9>& nbr) const + { + nbr[0] = { { -1, -1 } }; + nbr[1] = { { 0, -1 } }; + nbr[2] = { { +1, -1 } }; + nbr[3] = { { -1, 0 } }; + nbr[4] = { { 0, 0 } }; + nbr[5] = { { +1, 0 } }; + nbr[6] = { { -1, +1 } }; + nbr[7] = { { 0, +1 } }; + nbr[8] = { { +1, +1 } }; + } + + void GetNeighborhood(int x, int y, std::array, 4>& nbr) const + { + std::array, 4> inbr; + GetNeighborhood(inbr); + for (int i = 0; i < 4; ++i) + { + nbr[i][0] = static_cast(x) + inbr[i][0]; + nbr[i][1] = static_cast(y) + inbr[i][1]; + } + } + + void GetNeighborhood(int x, int y, std::array, 8>& nbr) const + { + std::array, 8> inbr; + GetNeighborhood(inbr); + for (int i = 0; i < 8; ++i) + { + nbr[i][0] = static_cast(x) + inbr[i][0]; + nbr[i][1] = static_cast(y) + inbr[i][1]; + } + } + + void GetCorners(int x, int y, std::array, 4>& nbr) const + { + std::array, 4> inbr; + GetCorners(inbr); + for (int i = 0; i < 4; ++i) + { + nbr[i][0] = static_cast(x) + inbr[i][0]; + nbr[i][1] = static_cast(y) + inbr[i][1]; + } + } + + void GetFull(int x, int y, std::array, 9>& nbr) const + { + std::array, 9> inbr; + GetFull(inbr); + for (int i = 0; i < 9; ++i) + { + nbr[i][0] = static_cast(x) + inbr[i][0]; + nbr[i][1] = static_cast(y) + inbr[i][1]; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Image3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Image3.h new file mode 100644 index 0000000..b519cf4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Image3.h @@ -0,0 +1,749 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +//#define GTE_THROW_ON_IMAGE3_ERRORS + +namespace WwiseGTE +{ + template + class Image3 : public Image + { + public: + // Construction and destruction. The last constructor must have + // positive dimensions; otherwise, the image is empty. + virtual ~Image3() + { + } + + Image3() + { + } + + Image3(int dimension0, int dimension1, int dimension2) + : + Image(std::vector{ dimension0, dimension1, dimension2 }) + { + } + + // Support for copy semantics. + Image3(Image3 const& image) + : + Image(image) + { + } + + Image3& operator= (Image3 const& image) + { + Image::operator=(image); + return *this; + } + + // Support for move semantics. + Image3(Image3&& image) + { + *this = std::move(image); + } + + Image3& operator= (Image3&& image) + { + Image::operator=(image); + return *this; + } + + // Support for changing the image dimensions. All pixel data is lost + // by this operation. + void Reconstruct(int dimension0, int dimension1, int dimension2) + { + Image::Reconstruct(std::vector{ dimension0, dimension1, dimension2 }); + } + + // Conversion between 1-dimensional indices and 3-dimensional + // coordinates. + inline size_t GetIndex(int x, int y, int z) const + { +#if defined(GTE_THROW_ON_IMAGE3_ERRORS) + if (0 <= x && x < this->mDimensions[0] + && 0 <= y && y < this->mDimensions[1] + && 0 <= z && z < this->mDimensions[2]) + { + return static_cast(x) + + static_cast(this->mDimensions[0]) * (static_cast(y) + + static_cast(this->mDimensions[1]) * static_cast(z)); + } + else + { + LogError( + "Invalid coordinates (" + std::to_string(x) + "," + + std::to_string(y) + "," + std::to_string(z) + ")."); + } +#else + return static_cast(x) + + static_cast(this->mDimensions[0]) * (static_cast(y) + + static_cast(this->mDimensions[1]) * static_cast(z)); +#endif + } + + inline size_t GetIndex(std::array const& coord) const + { +#if defined(GTE_THROW_ON_IMAGE3_ERRORS) + if (0 <= coord[0] && coord[0] < this->mDimensions[0] + && 0 <= coord[1] && coord[1] < this->mDimensions[1] + && 0 <= coord[2] && coord[2] < this->mDimensions[2]) + { + return static_cast(coord[0]) + + static_cast(this->mDimensions[0]) * (static_cast(coord[1]) + + static_cast(this->mDimensions[1]) * static_cast(coord[2])); + } + else + { + LogError( + "Invalid coordinates (" + std::to_string(coord[0]) + "," + + std::to_string(coord[1]) + "," + std::to_string(coord[2]) + ")."); + } +#else + return static_cast(coord[0]) + + static_cast(this->mDimensions[0]) * (static_cast(coord[1]) + + static_cast(this->mDimensions[1]) * static_cast(coord[2])); +#endif + } + + inline void GetCoordinates(size_t index, int& x, int& y, int& z) const + { +#if defined(GTE_THROW_ON_IMAGE3_ERRORS) + if (index < this->mPixels.size()) + { + x = static_cast(index % this->mDimensions[0]); + index /= this->mDimensions[0]; + y = static_cast(index % this->mDimensions[1]); + z = static_cast(index / this->mDimensions[1]); + } + else + { + LogError( + "Invalid index " + std::to_string(index) + "."); + } +#else + x = static_cast(index % this->mDimensions[0]); + index /= this->mDimensions[0]; + y = static_cast(index % this->mDimensions[1]); + z = static_cast(index / this->mDimensions[1]); +#endif + } + + inline std::array GetCoordinates(size_t index) const + { + std::array coord; +#if defined(GTE_THROW_ON_IMAGE3_ERRORS) + if (index < this->mPixels.size()) + { + coord[0] = static_cast(index % this->mDimensions[0]); + index /= this->mDimensions[0]; + coord[1] = static_cast(index % this->mDimensions[1]); + coord[2] = static_cast(index / this->mDimensions[1]); + return coord; + } + else + { + LogError( + "Invalid index " + std::to_string(index) + "."); + } +#else + coord[0] = static_cast(index % this->mDimensions[0]); + index /= this->mDimensions[0]; + coord[1] = static_cast(index % this->mDimensions[1]); + coord[2] = static_cast(index / this->mDimensions[1]); + return coord; +#endif + } + + // Access the data as a 3-dimensional array. The operator() functions + // test for valid (x,y,z) when iterator checking is enabled and throw + // on invalid (x,y,z). The Get() functions test for valid (x,y,z) and + // clamp when invalid; these functions cannot fail. + inline PixelType& operator() (int x, int y, int z) + { +#if defined(GTE_THROW_ON_IMAGE3_ERRORS) + if (0 <= x && x < this->mDimensions[0] + && 0 <= y && y < this->mDimensions[1] + && 0 <= z && z < this->mDimensions[2]) + { + size_t i = static_cast(x) + + static_cast(this->mDimensions[0]) * (static_cast(y) + + static_cast(this->mDimensions[1]) * static_cast(z)); + return this->mPixels[i]; + } + else + { + LogError( + "Invalid coordinates (" + std::to_string(x) + "," + + std::to_string(y) + "," + std::to_string(z) + ")."); + } +#else + size_t i = static_cast(x) + + static_cast(this->mDimensions[0]) * (static_cast(y) + + static_cast(this->mDimensions[1]) * static_cast(z)); + return this->mPixels[i]; +#endif + } + + inline PixelType const& operator() (int x, int y, int z) const + { +#if defined(GTE_THROW_ON_IMAGE3_ERRORS) + if (0 <= x && x < this->mDimensions[0] + && 0 <= y && y < this->mDimensions[1] + && 0 <= z && z < this->mDimensions[2]) + { + size_t i = static_cast(x) + + static_cast(this->mDimensions[0]) * (static_cast(y) + + static_cast(this->mDimensions[1]) * static_cast(z)); + return this->mPixels[i]; + } + else + { + LogError( + "Invalid coordinates (" + std::to_string(x) + "," + + std::to_string(y) + "," + std::to_string(z) + ")."); + } +#else + size_t i = static_cast(x) + + static_cast(this->mDimensions[0]) * (static_cast(y) + + static_cast(this->mDimensions[1]) * static_cast(z)); + return this->mPixels[i]; +#endif + } + + inline PixelType& operator() (std::array const& coord) + { +#if defined(GTE_THROW_ON_IMAGE3_ERRORS) + if (0 <= coord[0] && coord[0] < this->mDimensions[0] + && 0 <= coord[1] && coord[1] < this->mDimensions[1] + && 0 <= coord[2] && coord[2] < this->mDimensions[2]) + { + size_t i = static_cast(coord[0]) + + static_cast(this->mDimensions[0]) * (static_cast(coord[1]) + + static_cast(this->mDimensions[1]) * static_cast(coord[2])); + return this->mPixels[i]; + } + else + { + LogError( + "Invalid coordinates (" + std::to_string(coord[0]) + "," + + std::to_string(coord[1]) + "," + std::to_string(coord[2]) + ")."); + } +#else + size_t i = static_cast(coord[0]) + + static_cast(this->mDimensions[0]) * (static_cast(coord[1]) + + static_cast(this->mDimensions[1] * coord[2])); + return this->mPixels[i]; +#endif + } + + inline PixelType const& operator() (std::array const& coord) const + { +#if defined(GTE_THROW_ON_IMAGE3_ERRORS) + if (0 <= coord[0] && coord[0] < this->mDimensions[0] + && 0 <= coord[1] && coord[1] < this->mDimensions[1] + && 0 <= coord[2] && coord[2] < this->mDimensions[2]) + { + size_t i = static_cast(coord[0]) + + static_cast(this->mDimensions[0]) * (static_cast(coord[1]) + + static_cast(this->mDimensions[1]) * static_cast(coord[2])); + return this->mPixels[i]; + } + else + { + LogError( + "Invalid coordinates (" + std::to_string(coord[0]) + "," + + std::to_string(coord[1]) + "," + std::to_string(coord[2]) + ")."); + } +#else + size_t i = static_cast(coord[0]) + + static_cast(this->mDimensions[0]) * (static_cast(coord[1]) + + static_cast(this->mDimensions[1] * coord[2])); + return this->mPixels[i]; +#endif + } + + inline PixelType& Get(int x, int y, int z) + { + // Clamp to valid (x,y,z). + if (x < 0) + { + x = 0; + } + else if (x >= this->mDimensions[0]) + { + x = this->mDimensions[0] - 1; + } + + if (y < 0) + { + y = 0; + } + else if (y >= this->mDimensions[1]) + { + y = this->mDimensions[1] - 1; + } + + if (z < 0) + { + z = 0; + } + else if (z >= this->mDimensions[2]) + { + z = this->mDimensions[2] - 1; + } + + size_t i = static_cast(x) + + static_cast(this->mDimensions[0]) * (static_cast(y) + + static_cast(this->mDimensions[1]) * static_cast(z)); + return this->mPixels[i]; + } + + inline PixelType const& Get(int x, int y, int z) const + { + // Clamp to valid (x,y,z). + if (x < 0) + { + x = 0; + } + else if (x >= this->mDimensions[0]) + { + x = this->mDimensions[0] - 1; + } + + if (y < 0) + { + y = 0; + } + else if (y >= this->mDimensions[1]) + { + y = this->mDimensions[1] - 1; + } + + if (z < 0) + { + z = 0; + } + else if (z >= this->mDimensions[2]) + { + z = this->mDimensions[2] - 1; + } + + size_t i = static_cast(x) + + static_cast(this->mDimensions[0]) * (static_cast(y) + + static_cast(this->mDimensions[1]) * static_cast(z)); + return this->mPixels[i]; + } + + inline PixelType& Get(std::array coord) + { + // Clamp to valid (x,y,z). + for (int d = 0; d < 3; ++d) + { + if (coord[d] < 0) + { + coord[d] = 0; + } + else if (coord[d] >= this->mDimensions[d]) + { + coord[d] = this->mDimensions[d] - 1; + } + } + + size_t i = static_cast(coord[0]) + + static_cast(this->mDimensions[0]) * (static_cast(coord[1]) + + static_cast(this->mDimensions[1] * coord[2])); + return this->mPixels[i]; + } + + inline PixelType const& Get(std::array coord) const + { + // Clamp to valid (x,y,z). + for (int d = 0; d < 3; ++d) + { + if (coord[d] < 0) + { + coord[d] = 0; + } + else if (coord[d] >= this->mDimensions[d]) + { + coord[d] = this->mDimensions[d] - 1; + } + } + + size_t i = static_cast(coord[0]) + + static_cast(this->mDimensions[0]) * (static_cast(coord[1]) + + static_cast(this->mDimensions[1] * coord[2])); + return this->mPixels[i]; + } + + // In the following discussion, u, v and w are in {-1,1}. Given a + // voxel (x,y,z), the 6-connected neighbors have relative offsets + // (u,0,0), (0,v,0), and (0,0,w). The 18-connected neighbors include + // the 6-connected neighbors and have additional relative offsets + // (u,v,0), (u,0,w), and (0,v,w). The 26-connected neighbors include + // the 18-connected neighbors and have additional relative offsets + // (u,v,w). The corner neighbors have offsets (0,0,0), (1,0,0), + // (0,1,0), (1,1,0), (0,0,1), (1,0,1), (0,1,1), and (1,1,1) in that + // order. The full neighborhood is the set of 3x3x3 pixels centered + // at (x,y). + + // The neighborhoods can be accessed as 1-dimensional indices using + // these functions. The first five functions provide 1-dimensional + // indices relative to any voxel location; these depend only on the + // image dimensions. The last five functions provide 1-dimensional + // indices for the actual voxels in the neighborhood; no clamping is + // used when (x,y,z) is on the boundary. + void GetNeighborhood(std::array& nbr) const + { + int dim0 = this->mDimensions[0]; + int dim01 = this->mDimensions[0] * this->mDimensions[1]; + nbr[0] = -1; // (x-1,y,z) + nbr[1] = +1; // (x+1,y,z) + nbr[2] = -dim0; // (x,y-1,z) + nbr[3] = +dim0; // (x,y+1,z) + nbr[4] = -dim01; // (x,y,z-1) + nbr[5] = +dim01; // (x,y,z+1) + } + + void GetNeighborhood(std::array& nbr) const + { + int dim0 = this->mDimensions[0]; + int dim01 = this->mDimensions[0] * this->mDimensions[1]; + nbr[0] = -1; // (x-1,y,z) + nbr[1] = +1; // (x+1,y,z) + nbr[2] = -dim0; // (x,y-1,z) + nbr[3] = +dim0; // (x,y+1,z) + nbr[4] = -dim01; // (x,y,z-1) + nbr[5] = +dim01; // (x,y,z+1) + nbr[6] = -1 - dim0; // (x-1,y-1,z) + nbr[7] = +1 - dim0; // (x+1,y-1,z) + nbr[8] = -1 + dim0; // (x-1,y+1,z) + nbr[9] = +1 + dim0; // (x+1,y+1,z) + nbr[10] = -1 + dim01; // (x-1,y,z+1) + nbr[11] = +1 + dim01; // (x+1,y,z+1) + nbr[12] = -dim0 + dim01; // (x,y-1,z+1) + nbr[13] = +dim0 + dim01; // (x,y+1,z+1) + nbr[14] = -1 - dim01; // (x-1,y,z-1) + nbr[15] = +1 - dim01; // (x+1,y,z-1) + nbr[16] = -dim0 - dim01; // (x,y-1,z-1) + nbr[17] = +dim0 - dim01; // (x,y+1,z-1) + } + + void GetNeighborhood(std::array& nbr) const + { + int dim0 = this->mDimensions[0]; + int dim01 = this->mDimensions[0] * this->mDimensions[1]; + nbr[0] = -1; // (x-1,y,z) + nbr[1] = +1; // (x+1,y,z) + nbr[2] = -dim0; // (x,y-1,z) + nbr[3] = +dim0; // (x,y+1,z) + nbr[4] = -dim01; // (x,y,z-1) + nbr[5] = +dim01; // (x,y,z+1) + nbr[6] = -1 - dim0; // (x-1,y-1,z) + nbr[7] = +1 - dim0; // (x+1,y-1,z) + nbr[8] = -1 + dim0; // (x-1,y+1,z) + nbr[9] = +1 + dim0; // (x+1,y+1,z) + nbr[10] = -1 + dim01; // (x-1,y,z+1) + nbr[11] = +1 + dim01; // (x+1,y,z+1) + nbr[12] = -dim0 + dim01; // (x,y-1,z+1) + nbr[13] = +dim0 + dim01; // (x,y+1,z+1) + nbr[14] = -1 - dim01; // (x-1,y,z-1) + nbr[15] = +1 - dim01; // (x+1,y,z-1) + nbr[16] = -dim0 - dim01; // (x,y-1,z-1) + nbr[17] = +dim0 - dim01; // (x,y+1,z-1) + nbr[18] = -1 - dim0 - dim01; // (x-1,y-1,z-1) + nbr[19] = +1 - dim0 - dim01; // (x+1,y-1,z-1) + nbr[20] = -1 + dim0 - dim01; // (x-1,y+1,z-1) + nbr[21] = +1 + dim0 - dim01; // (x+1,y+1,z-1) + nbr[22] = -1 - dim0 + dim01; // (x-1,y-1,z+1) + nbr[23] = +1 - dim0 + dim01; // (x+1,y-1,z+1) + nbr[24] = -1 + dim0 + dim01; // (x-1,y+1,z+1) + nbr[25] = +1 + dim0 + dim01; // (x+1,y+1,z+1) + } + + void GetCorners(std::array& nbr) const + { + int dim0 = this->mDimensions[0]; + int dim01 = this->mDimensions[0] * this->mDimensions[1]; + nbr[0] = 0; // (x,y,z) + nbr[1] = 1; // (x+1,y,z) + nbr[2] = dim0; // (x,y+1,z) + nbr[3] = dim0 + 1; // (x+1,y+1,z) + nbr[4] = dim01; // (x,y,z+1) + nbr[5] = dim01 + 1; // (x+1,y,z+1) + nbr[6] = dim01 + dim0; // (x,y+1,z+1) + nbr[7] = dim01 + dim0 + 1; // (x+1,y+1,z+1) + } + + void GetFull(std::array& nbr) const + { + int dim0 = this->mDimensions[0]; + int dim01 = this->mDimensions[0] * this->mDimensions[1]; + nbr[0] = -1 - dim0 - dim01; // (x-1,y-1,z-1) + nbr[1] = -dim0 - dim01; // (x, y-1,z-1) + nbr[2] = +1 - dim0 - dim01; // (x+1,y-1,z-1) + nbr[3] = -1 - dim01; // (x-1,y, z-1) + nbr[4] = -dim01; // (x, y, z-1) + nbr[5] = +1 - dim01; // (x+1,y, z-1) + nbr[6] = -1 + dim0 - dim01; // (x-1,y+1,z-1) + nbr[7] = +dim0 - dim01; // (x, y+1,z-1) + nbr[8] = +1 + dim0 - dim01; // (x+1,y+1,z-1) + nbr[9] = -1 - dim0; // (x-1,y-1,z) + nbr[10] = -dim0; // (x, y-1,z) + nbr[11] = +1 - dim0; // (x+1,y-1,z) + nbr[12] = -1; // (x-1,y, z) + nbr[13] = 0; // (x, y, z) + nbr[14] = +1; // (x+1,y, z) + nbr[15] = -1 + dim0; // (x-1,y+1,z) + nbr[16] = +dim0; // (x, y+1,z) + nbr[17] = +1 + dim0; // (x+1,y+1,z) + nbr[18] = -1 - dim0 + dim01; // (x-1,y-1,z+1) + nbr[19] = -dim0 + dim01; // (x, y-1,z+1) + nbr[20] = +1 - dim0 + dim01; // (x+1,y-1,z+1) + nbr[21] = -1 + dim01; // (x-1,y, z+1) + nbr[22] = +dim01; // (x, y, z+1) + nbr[23] = +1 + dim01; // (x+1,y, z+1) + nbr[24] = -1 + dim0 + dim01; // (x-1,y+1,z+1) + nbr[25] = +dim0 + dim01; // (x, y+1,z+1) + nbr[26] = +1 + dim0 + dim01; // (x+1,y+1,z+1) + } + + void GetNeighborhood(int x, int y, int z, std::array& nbr) const + { + size_t index = GetIndex(x, y, z); + std::array inbr; + GetNeighborhood(inbr); + for (int i = 0; i < 6; ++i) + { + nbr[i] = index + inbr[i]; + } + } + + void GetNeighborhood(int x, int y, int z, std::array& nbr) const + { + size_t index = GetIndex(x, y, z); + std::array inbr; + GetNeighborhood(inbr); + for (int i = 0; i < 18; ++i) + { + nbr[i] = index + inbr[i]; + } + } + + void GetNeighborhood(int x, int y, int z, std::array& nbr) const + { + size_t index = GetIndex(x, y, z); + std::array inbr; + GetNeighborhood(inbr); + for (int i = 0; i < 26; ++i) + { + nbr[i] = index + inbr[i]; + } + } + + void GetCorners(int x, int y, int z, std::array& nbr) const + { + size_t index = GetIndex(x, y, z); + std::array inbr; + GetCorners(inbr); + for (int i = 0; i < 8; ++i) + { + nbr[i] = index + inbr[i]; + } + } + + void GetFull(int x, int y, int z, std::array& nbr) const + { + size_t index = GetIndex(x, y, z); + std::array inbr; + GetFull(inbr); + for (int i = 0; i < 27; ++i) + { + nbr[i] = index + inbr[i]; + } + } + + // The neighborhoods can be accessed as 3-tuples using these + // functions. The first five functions provide 3-tuples relative to + // any voxel location; these depend only on the image dimensions. The + // last five functions provide 3-tuples for the actual voxels in the + // neighborhood; no clamping is used when (x,y,z) is on the boundary. + void GetNeighborhood(std::array, 6>& nbr) const + { + nbr[0] = { { -1, 0, 0 } }; + nbr[1] = { { +1, 0, 0 } }; + nbr[2] = { { 0, -1, 0 } }; + nbr[3] = { { 0, +1, 0 } }; + nbr[4] = { { 0, 0, -1 } }; + nbr[5] = { { 0, 0, +1 } }; + } + + void GetNeighborhood(std::array, 18>& nbr) const + { + nbr[0] = { { -1, 0, 0 } }; + nbr[1] = { { +1, 0, 0 } }; + nbr[2] = { { 0, -1, 0 } }; + nbr[3] = { { 0, +1, 0 } }; + nbr[4] = { { 0, 0, -1 } }; + nbr[5] = { { 0, 0, +1 } }; + nbr[6] = { { -1, -1, 0 } }; + nbr[7] = { { +1, -1, 0 } }; + nbr[8] = { { -1, +1, 0 } }; + nbr[9] = { { +1, +1, 0 } }; + nbr[10] = { { -1, 0, +1 } }; + nbr[11] = { { +1, 0, +1 } }; + nbr[12] = { { 0, -1, +1 } }; + nbr[13] = { { 0, +1, +1 } }; + nbr[14] = { { -1, 0, -1 } }; + nbr[15] = { { +1, 0, -1 } }; + nbr[16] = { { 0, -1, -1 } }; + nbr[17] = { { 0, +1, -1 } }; + } + + void GetNeighborhood(std::array, 26>& nbr) const + { + nbr[0] = { { -1, 0, 0 } }; + nbr[1] = { { +1, 0, 0 } }; + nbr[2] = { { 0, -1, 0 } }; + nbr[3] = { { 0, +1, 0 } }; + nbr[4] = { { 0, 0, -1 } }; + nbr[5] = { { 0, 0, +1 } }; + nbr[6] = { { -1, -1, 0 } }; + nbr[7] = { { +1, -1, 0 } }; + nbr[8] = { { -1, +1, 0 } }; + nbr[9] = { { +1, +1, 0 } }; + nbr[10] = { { -1, 0, +1 } }; + nbr[11] = { { +1, 0, +1 } }; + nbr[12] = { { 0, -1, +1 } }; + nbr[13] = { { 0, +1, +1 } }; + nbr[14] = { { -1, 0, -1 } }; + nbr[15] = { { +1, 0, -1 } }; + nbr[16] = { { 0, -1, -1 } }; + nbr[17] = { { 0, +1, -1 } }; + nbr[18] = { { -1, -1, -1 } }; + nbr[19] = { { +1, -1, -1 } }; + nbr[20] = { { -1, +1, -1 } }; + nbr[21] = { { +1, +1, -1 } }; + nbr[22] = { { -1, -1, +1 } }; + nbr[23] = { { +1, -1, +1 } }; + nbr[24] = { { -1, +1, +1 } }; + nbr[25] = { { +1, +1, +1 } }; + } + + void GetCorners(std::array, 8>& nbr) const + { + nbr[0] = { { 0, 0, 0 } }; + nbr[1] = { { 1, 0, 0 } }; + nbr[2] = { { 0, 1, 0 } }; + nbr[3] = { { 1, 1, 0 } }; + nbr[4] = { { 0, 0, 1 } }; + nbr[5] = { { 1, 0, 1 } }; + nbr[6] = { { 0, 1, 1 } }; + nbr[7] = { { 1, 1, 1 } }; + } + + void GetFull(std::array, 27>& nbr) const + { + nbr[0] = { { -1, -1, -1 } }; + nbr[1] = { { 0, -1, -1 } }; + nbr[2] = { { +1, -1, -1 } }; + nbr[3] = { { -1, 0, -1 } }; + nbr[4] = { { 0, 0, -1 } }; + nbr[5] = { { +1, 0, -1 } }; + nbr[6] = { { -1, +1, -1 } }; + nbr[7] = { { 0, +1, -1 } }; + nbr[8] = { { +1, +1, -1 } }; + nbr[9] = { { -1, -1, 0 } }; + nbr[10] = { { 0, -1, 0 } }; + nbr[11] = { { +1, -1, 0 } }; + nbr[12] = { { -1, 0, 0 } }; + nbr[13] = { { 0, 0, 0 } }; + nbr[14] = { { +1, 0, 0 } }; + nbr[15] = { { -1, +1, 0 } }; + nbr[16] = { { 0, +1, 0 } }; + nbr[17] = { { +1, +1, 0 } }; + nbr[18] = { { -1, -1, +1 } }; + nbr[19] = { { 0, -1, +1 } }; + nbr[20] = { { +1, -1, +1 } }; + nbr[21] = { { -1, 0, +1 } }; + nbr[22] = { { 0, 0, +1 } }; + nbr[23] = { { +1, 0, +1 } }; + nbr[24] = { { -1, +1, +1 } }; + nbr[25] = { { 0, +1, +1 } }; + nbr[26] = { { +1, +1, +1 } }; + } + + void GetNeighborhood(int x, int y, int z, std::array, 6>& nbr) const + { + std::array, 6> inbr; + GetNeighborhood(inbr); + for (int i = 0; i < 6; ++i) + { + nbr[i][0] = static_cast(x) + inbr[i][0]; + nbr[i][1] = static_cast(y) + inbr[i][1]; + nbr[i][2] = static_cast(z) + inbr[i][2]; + } + } + + void GetNeighborhood(int x, int y, int z, std::array, 18>& nbr) const + { + std::array, 18> inbr; + GetNeighborhood(inbr); + for (int i = 0; i < 18; ++i) + { + nbr[i][0] = static_cast(x) + inbr[i][0]; + nbr[i][1] = static_cast(y) + inbr[i][1]; + nbr[i][2] = static_cast(z) + inbr[i][2]; + } + } + + void GetNeighborhood(int x, int y, int z, std::array, 26>& nbr) const + { + std::array, 26> inbr; + GetNeighborhood(inbr); + for (int i = 0; i < 26; ++i) + { + nbr[i][0] = static_cast(x) + inbr[i][0]; + nbr[i][1] = static_cast(y) + inbr[i][1]; + nbr[i][2] = static_cast(z) + inbr[i][2]; + } + } + + void GetCorners(int x, int y, int z, std::array, 8>& nbr) const + { + std::array, 8> inbr; + GetCorners(inbr); + for (int i = 0; i < 8; ++i) + { + nbr[i][0] = static_cast(x) + inbr[i][0]; + nbr[i][1] = static_cast(y) + inbr[i][1]; + nbr[i][2] = static_cast(z) + inbr[i][2]; + } + } + + void GetFull(int x, int y, int z, std::array, 27>& nbr) const + { + std::array, 27> inbr; + GetFull(inbr); + for (int i = 0; i < 27; ++i) + { + nbr[i][0] = static_cast(x) + inbr[i][0]; + nbr[i][1] = static_cast(y) + inbr[i][1]; + nbr[i][2] = static_cast(z) + inbr[i][2]; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ImageUtility2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ImageUtility2.h new file mode 100644 index 0000000..fad538b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ImageUtility2.h @@ -0,0 +1,1486 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +// Image utilities for Image2 objects. TODO: Extend this to a template +// class to allow the pixel type to be int*_t and uint*_t for * in +// {8,16,32,64}. +// +// All but the Draw* functions are operations on binary images. Let the image +// have d0 columns and d1 rows. The input image must have zeros on its +// boundaries x = 0, x = d0-1, y = 0 and y = d1-1. The 0-valued pixels are +// considered to be background. The 1-valued pixels are considered to be +// foreground. In some of the operations, to save memory and time the input +// image is modified by the algorithms. If you need to preserve the input +// image, make a copy of it before calling these functions. Dilation and +// erosion functions do not have the requirement that the boundary pixels of +// the binary image inputs be zero. + +namespace WwiseGTE +{ + class ImageUtility2 + { + public: + // Compute the 4-connected components of a binary image. The input + // image is modified to avoid the cost of making a copy. On output, + // the image values are the labels for the components. The array + // components[k], k >= 1, contains the indices for the k-th component. + static void GetComponents4(Image2& image, + std::vector>& components) + { + std::array neighbors; + image.GetNeighborhood(neighbors); + GetComponents(4, &neighbors[0], image, components); + } + + // Compute the 8-connected components of a binary image. The input + // image is modified to avoid the cost of making a copy. On output, + // the image values are the labels for the components. The array + // components[k], k >= 1, contains the indices for the k-th component. + static void GetComponents8(Image2& image, + std::vector>& components) + { + std::array neighbors; + image.GetNeighborhood(neighbors); + GetComponents(8, &neighbors[0], image, components); + } + + // Compute a dilation with a structuring element consisting of the + // 4-connected neighbors of each pixel. The input image is binary + // with 0 for background and 1 for foreground. The output image must + // be an object different from the input image. + static void Dilate4(Image2 const& input, Image2& output) + { + std::array, 4> neighbors; + input.GetNeighborhood(neighbors); + Dilate(input, 4, &neighbors[0], output); + } + + // Compute a dilation with a structuring element consisting of the + // 8-connected neighbors of each pixel. The input image is binary + // with 0 for background and 1 for foreground. The output image must + // be an object different from the input image. + static void Dilate8(Image2 const& input, Image2& output) + { + std::array, 8> neighbors; + input.GetNeighborhood(neighbors); + Dilate(input, 8, &neighbors[0], output); + } + + // Compute a dilation with a structing element consisting of neighbors + // specified by offsets relative to the pixel. The input image is + // binary with 0 for background and 1 for foreground. The output + // image must be an object different from the input image. + static void Dilate(Image2 const& input, int numNeighbors, + std::array const* neighbors, Image2& output) + { + LogAssert(&output != &input, "Input and output must be different."); + + output = input; + + // If the pixel at (x,y) is 1, then the pixels at (x+dx,y+dy) are + // set to 1 where (dx,dy) is in the 'neighbors' array. Boundary + // testing is used to avoid accessing out-of-range pixels. + int const dim0 = input.GetDimension(0); + int const dim1 = input.GetDimension(1); + for (int y = 0; y < dim1; ++y) + { + for (int x = 0; x < dim0; ++x) + { + if (input(x, y) == 1) + { + for (int j = 0; j < numNeighbors; ++j) + { + int xNbr = x + neighbors[j][0]; + int yNbr = y + neighbors[j][1]; + if (0 <= xNbr && xNbr < dim0 && 0 <= yNbr && yNbr < dim1) + { + output(xNbr, yNbr) = 1; + } + } + } + } + } + } + + // Compute an erosion with a structuring element consisting of the + // 4-connected neighbors of each pixel. The input image is binary + // with 0 for background and 1 for foreground. The output image must + // be an object different from the input image. If zeroExterior is + // true, the image exterior is assumed to be 0, so 1-valued boundary + // pixels are set to 0; otherwise, boundary pixels are set to 0 only + // when they have neighboring image pixels that are 0. + static void Erode4(Image2 const& input, bool zeroExterior, Image2& output) + { + std::array, 4> neighbors; + input.GetNeighborhood(neighbors); + Erode(input, zeroExterior, 4, &neighbors[0], output); + } + + // Compute an erosion with a structuring element consisting of the + // 8-connected neighbors of each pixel. The input image is binary + // with 0 for background and 1 for foreground. The output image must + // be an object different from the input image. If zeroExterior is + // true, the image exterior is assumed to be 0, so 1-valued boundary + // pixels are set to 0; otherwise, boundary pixels are set to 0 only + // when they have neighboring image pixels that are 0. + static void Erode8(Image2 const& input, bool zeroExterior, Image2& output) + { + std::array, 8> neighbors; + input.GetNeighborhood(neighbors); + Erode(input, zeroExterior, 8, &neighbors[0], output); + } + + // Compute an erosion with a structuring element consisting of + // neighbors specified by offsets relative to the pixel. The input + // image is binary with 0 for background and 1 for foreground. The + // output image must be an object different from the input image. If + // zeroExterior is true, the image exterior is assumed to be 0, so + // 1-valued boundary pixels are set to 0; otherwise, boundary pixels + // are set to 0 only when they have neighboring image pixels that + // are 0. + static void Erode(Image2 const& input, bool zeroExterior, + int numNeighbors, std::array const* neighbors, Image2& output) + { + LogAssert(&output != &input, "Input and output must be different."); + + output = input; + + // If the pixel at (x,y) is 1, it is changed to 0 when at least + // one neighbor (x+dx,y+dy) is 0, where (dx,dy) is in the + // 'neighbors' array. + int const dim0 = input.GetDimension(0); + int const dim1 = input.GetDimension(1); + for (int y = 0; y < dim1; ++y) + { + for (int x = 0; x < dim0; ++x) + { + if (input(x, y) == 1) + { + for (int j = 0; j < numNeighbors; ++j) + { + int xNbr = x + neighbors[j][0]; + int yNbr = y + neighbors[j][1]; + if (0 <= xNbr && xNbr < dim0 && 0 <= yNbr && yNbr < dim1) + { + if (input(xNbr, yNbr) == 0) + { + output(x, y) = 0; + break; + } + } + else if (zeroExterior) + { + output(x, y) = 0; + break; + } + } + } + } + } + } + + // Compute an opening with a structuring element consisting of the + // 4-connected neighbors of each pixel. The input image is binary + // with 0 for background and 1 for foreground. The output image must + // be an object different from the input image. If zeroExterior is + // true, the image exterior is assumed to consist of 0-valued pixels; + // otherwise, the image exterior is assumed to consist of 1-valued + // pixels. + static void Open4(Image2 const& input, bool zeroExterior, Image2& output) + { + Image2 temp(input.GetDimension(0), input.GetDimension(1)); + Erode4(input, zeroExterior, temp); + Dilate4(temp, output); + } + + // Compute an opening with a structuring element consisting of the + // 8-connected neighbors of each pixel. The input image is binary + // with 0 for background and 1 for foreground. The output image must + // be an object different from the input image. If zeroExterior is + // true, the image exterior is assumed to consist of 0-valued pixels; + // otherwise, the image exterior is assumed to consist of 1-valued + // pixels. + static void Open8(Image2 const& input, bool zeroExterior, Image2& output) + { + Image2 temp(input.GetDimension(0), input.GetDimension(1)); + Erode8(input, zeroExterior, temp); + Dilate8(temp, output); + } + + // Compute an opening with a structuring element consisting of + // neighbors specified by offsets relative to the pixel. The input + // image is binary with 0 for background and 1 for foreground. The + // output image must be an object different from the input image. If + // zeroExterior is true, the image exterior is assumed to consist of + // 0-valued pixels; otherwise, the image exterior is assumed to + // consist of 1-valued pixels. + static void Open(Image2 const& input, bool zeroExterior, + int numNeighbors, std::array const* neighbors, Image2& output) + { + Image2 temp(input.GetDimension(0), input.GetDimension(1)); + Erode(input, zeroExterior, numNeighbors, neighbors, temp); + Dilate(temp, numNeighbors, neighbors, output); + } + + // Compute a closing with a structuring element consisting of the + // 4-connected neighbors of each pixel. The input image is binary + // with 0 for background and 1 for foreground. The output image must + // be an object different from the input image. If zeroExterior is + // true, the image exterior is assumed to consist of 0-valued pixels; + // otherwise, the image exterior is assumed to consist of 1-valued + // pixels. + static void Close4(Image2 const& input, bool zeroExterior, Image2& output) + { + Image2 temp(input.GetDimension(0), input.GetDimension(1)); + Dilate4(input, temp); + Erode4(temp, zeroExterior, output); + } + + // Compute a closing with a structuring element consisting of the + // 8-connected neighbors of each pixel. The input image is binary + // with 0 for background and 1 for foreground. The output image must + // be an object different from the input image. If zeroExterior is + // true, the image exterior is assumed to consist of 0-valued pixels; + // otherwise, the image exterior is assumed to consist of 1-valued + // pixels. + static void Close8(Image2 const& input, bool zeroExterior, Image2& output) + { + Image2 temp(input.GetDimension(0), input.GetDimension(1)); + Dilate8(input, temp); + Erode8(temp, zeroExterior, output); + } + + // Compute a closing with a structuring element consisting of + // neighbors specified by offsets relative to the pixel. The input + // image is binary with 0 for background and 1 for foreground. The + // output image must be an object different from the input image. If + // zeroExterior is true, the image exterior is assumed to consist of + // 0-valued pixels; otherwise, the image exterior is assumed to + // consist of 1-valued pixels. + static void Close(Image2 const& input, bool zeroExterior, + int numNeighbors, std::array const* neighbors, Image2& output) + { + Image2 temp(input.GetDimension(0), input.GetDimension(1)); + Dilate(input, numNeighbors, neighbors, temp); + Erode(temp, zeroExterior, numNeighbors, neighbors, output); + } + + // Locate a pixel and walk around the edge of a component. The input + // (x,y) is where the search starts for a nonzero pixel. If (x,y) is + // outside the component, the walk is around the outside the + // component. If the component has a hole and (x,y) is inside that + // hole, the walk is around the boundary surrounding the hole. The + // function returns 'true' on a success walk. The return value is + // 'false' when no boundary was found from the starting (x,y). + static bool ExtractBoundary(int x, int y, Image2& image, std::vector& boundary) + { + // Find a first boundary pixel. + size_t const numPixels = image.GetNumPixels(); + size_t i; + for (i = image.GetIndex(x, y); i < numPixels; ++i) + { + if (image[i]) + { + break; + } + } + if (i == numPixels) + { + // No boundary pixel found. + return false; + } + + std::array const dx = { -1, 0, +1, +1, +1, 0, -1, -1 }; + std::array const dy = { -1, -1, -1, 0, +1, +1, +1, 0 }; + + // Create a new point list that contains the first boundary point. + boundary.push_back(i); + + // The direction from background 0 to boundary pixel 1 is + // (dx[7],dy[7]). + std::array coord = image.GetCoordinates(i); + int x0 = coord[0], y0 = coord[1]; + int cx = x0, cy = y0; + int nx = x0 - 1, ny = y0, dir = 7; + + // Traverse the boundary in clockwise order. Mark visited pixels + // as 2. + image(cx, cy) = 2; + bool notDone = true; + while (notDone) + { + int j, nbr; + for (j = 0, nbr = dir; j < 8; ++j, nbr = (nbr + 1) % 8) + { + nx = cx + dx[nbr]; + ny = cy + dy[nbr]; + if (image(nx, ny)) // next boundary pixel found + { + break; + } + } + + if (j == 8) + { + // (cx,cy) is isolated + notDone = false; + continue; + } + + if (nx == x0 && ny == y0) + { + // boundary traversal completed + notDone = false; + continue; + } + + // (nx,ny) is next boundary point, add point to list. Note + // that the index for the pixel is computed for the original + // image, not for the larger temporary image. + boundary.push_back(image.GetIndex(nx, ny)); + + // Mark visited pixels as 2. + image(nx, ny) = 2; + + // Start search for next point. + cx = nx; + cy = ny; + dir = (j + 5 + dir) % 8; + } + + return true; + } + + // Use a depth-first search for filling a 4-connected region. This is + // nonrecursive, simulated by using a heap-allocated "stack". The + // input (x,y) is the seed point that starts the fill. + template + static void FloodFill4(Image2& image, int x, int y, + PixelType foreColor, PixelType backColor) + { + // Test for a valid seed. + int const dim0 = image.GetDimension(0); + int const dim1 = image.GetDimension(1); + if (x < 0 || x >= dim0 || y < 0 || y >= dim1) + { + // The seed point is outside the image domain, so there is + // nothing to fill. + return; + } + + // Allocate the maximum amount of space needed for the stack. + // An empty stack has top == -1. + size_t const numPixels = image.GetNumPixels(); + std::vector xStack(numPixels), yStack(numPixels); + + // Push seed point onto stack if it has the background color. All + // points pushed onto stack have background color backColor. + int top = 0; + xStack[top] = x; + yStack[top] = y; + + while (top >= 0) // stack is not empty + { + // Read top of stack. Do not pop since we need to return to + // this top value later to restart the fill in a different + // direction. + x = xStack[top]; + y = yStack[top]; + + // Fill the pixel. + image(x, y) = foreColor; + + int xp1 = x + 1; + if (xp1 < dim0 && image(xp1, y) == backColor) + { + // Push pixel with background color. + ++top; + xStack[top] = xp1; + yStack[top] = y; + continue; + } + + int xm1 = x - 1; + if (0 <= xm1 && image(xm1, y) == backColor) + { + // Push pixel with background color. + ++top; + xStack[top] = xm1; + yStack[top] = y; + continue; + } + + int yp1 = y + 1; + if (yp1 < dim1 && image(x, yp1) == backColor) + { + // Push pixel with background color. + ++top; + xStack[top] = x; + yStack[top] = yp1; + continue; + } + + int ym1 = y - 1; + if (0 <= ym1 && image(x, ym1) == backColor) + { + // Push pixel with background color. + ++top; + xStack[top] = x; + yStack[top] = ym1; + continue; + } + + // Done in all directions, pop and return to search other + // directions of predecessor. + --top; + } + } + + // Compute the L1-distance transform of the binary image. The function + // returns the maximum distance and a point at which the maximum + // distance is attained. + static void GetL1Distance(Image2& image, int& maxDistance, int& xMax, int& yMax) + { + int const dim0 = image.GetDimension(0); + int const dim1 = image.GetDimension(1); + int const dim0m1 = dim0 - 1; + int const dim1m1 = dim1 - 1; + + // Use a grass-fire approach, computing distance from boundary to + // interior one pass at a time. + bool changeMade = true; + int distance; + for (distance = 1, xMax = 0, yMax = 0; changeMade; ++distance) + { + changeMade = false; + int distanceP1 = distance + 1; + for (int y = 1; y < dim1m1; ++y) + { + for (int x = 1; x < dim0m1; ++x) + { + if (image(x, y) == distance) + { + if (image(x - 1, y) >= distance + && image(x + 1, y) >= distance + && image(x, y - 1) >= distance + && image(x, y + 1) >= distance) + { + image(x, y) = distanceP1; + xMax = x; + yMax = y; + changeMade = true; + } + } + } + } + } + + maxDistance = --distance; + } + + // Compute the L2-distance transform of the binary image. The maximum + // distance should not be larger than 100, so you have to ensure this + // is the case for the input image. The function returns the maximum + // distance and a point at which the maximum distance is attained. + // Comments about the algorithm are in the source file. + static void GetL2Distance(Image2 const& image, float& maxDistance, + int& xMax, int& yMax, Image2& transform) + { + // This program calculates the Euclidean distance transform of a + // binary input image. The adaptive algorithm is guaranteed to + // give exact distances for all distances < 100. The algorithm + // was provided John Gauch at University of Kansas. The following + // is a quote: + /// + // The basic idea is similar to a EDT described recently in PAMI + // by Laymarie from McGill. By keeping the dx and dy offset to + // the nearest edge (feature) point in the image, we can search to + // see which dx dy is closest to a given point by examining a set + // of neighbors. The Laymarie method (and Borgfors) look at a + // fixed 3x3 or 5x5 neighborhood and call it a day. What we did + // was calculate (painfully) what neighborhoods you need to look + // at to guarentee that the exact distance is obtained. Thus, + // you will see in the code, that we L2Check the current distance + // and depending on what we have so far, we extend the search + // region. Since our algorithm for L2Checking the exactness of + // each neighborhood is on the order N^4, we have only gone to + // N=100. In theory, you could make this large enough to get all + // distances exact. We have implemented the algorithm to get all + // distances < 100 to be exact. + int const dim0 = image.GetDimension(0); + int const dim1 = image.GetDimension(1); + int const dim0m1 = dim0 - 1; + int const dim1m1 = dim1 - 1; + int x, y, distance; + + // Create and initialize intermediate images. + Image2 xNear(dim0, dim1); + Image2 yNear(dim0, dim1); + Image2 dist(dim0, dim1); + for (y = 0; y < dim1; ++y) + { + for (x = 0; x < dim0; ++x) + { + if (image(x, y) != 0) + { + xNear(x, y) = 0; + yNear(x, y) = 0; + dist(x, y) = std::numeric_limits::max(); + } + else + { + xNear(x, y) = x; + yNear(x, y) = y; + dist(x, y) = 0; + } + } + } + + int const K1 = 1; + int const K2 = 169; // 13^2 + int const K3 = 961; // 31^2 + int const K4 = 2401; // 49^2 + int const K5 = 5184; // 72^2 + + // Pass in the ++ direction. + for (y = 0; y < dim1; ++y) + { + for (x = 0; x < dim0; ++x) + { + distance = dist(x, y); + if (distance > K1) + { + L2Check(x, y, -1, 0, xNear, yNear, dist); + L2Check(x, y, -1, -1, xNear, yNear, dist); + L2Check(x, y, 0, -1, xNear, yNear, dist); + } + if (distance > K2) + { + L2Check(x, y, -2, -1, xNear, yNear, dist); + L2Check(x, y, -1, -2, xNear, yNear, dist); + } + if (distance > K3) + { + L2Check(x, y, -3, -1, xNear, yNear, dist); + L2Check(x, y, -3, -2, xNear, yNear, dist); + L2Check(x, y, -2, -3, xNear, yNear, dist); + L2Check(x, y, -1, -3, xNear, yNear, dist); + } + if (distance > K4) + { + L2Check(x, y, -4, -1, xNear, yNear, dist); + L2Check(x, y, -4, -3, xNear, yNear, dist); + L2Check(x, y, -3, -4, xNear, yNear, dist); + L2Check(x, y, -1, -4, xNear, yNear, dist); + } + if (distance > K5) + { + L2Check(x, y, -5, -1, xNear, yNear, dist); + L2Check(x, y, -5, -2, xNear, yNear, dist); + L2Check(x, y, -5, -3, xNear, yNear, dist); + L2Check(x, y, -5, -4, xNear, yNear, dist); + L2Check(x, y, -4, -5, xNear, yNear, dist); + L2Check(x, y, -2, -5, xNear, yNear, dist); + L2Check(x, y, -3, -5, xNear, yNear, dist); + L2Check(x, y, -1, -5, xNear, yNear, dist); + } + } + } + + // Pass in -- direction. + for (y = dim1m1; y >= 0; --y) + { + for (x = dim0m1; x >= 0; --x) + { + distance = dist(x, y); + if (distance > K1) + { + L2Check(x, y, 1, 0, xNear, yNear, dist); + L2Check(x, y, 1, 1, xNear, yNear, dist); + L2Check(x, y, 0, 1, xNear, yNear, dist); + } + if (distance > K2) + { + L2Check(x, y, 2, 1, xNear, yNear, dist); + L2Check(x, y, 1, 2, xNear, yNear, dist); + } + if (distance > K3) + { + L2Check(x, y, 3, 1, xNear, yNear, dist); + L2Check(x, y, 3, 2, xNear, yNear, dist); + L2Check(x, y, 2, 3, xNear, yNear, dist); + L2Check(x, y, 1, 3, xNear, yNear, dist); + } + if (distance > K4) + { + L2Check(x, y, 4, 1, xNear, yNear, dist); + L2Check(x, y, 4, 3, xNear, yNear, dist); + L2Check(x, y, 3, 4, xNear, yNear, dist); + L2Check(x, y, 1, 4, xNear, yNear, dist); + } + if (distance > K5) + { + L2Check(x, y, 5, 1, xNear, yNear, dist); + L2Check(x, y, 5, 2, xNear, yNear, dist); + L2Check(x, y, 5, 3, xNear, yNear, dist); + L2Check(x, y, 5, 4, xNear, yNear, dist); + L2Check(x, y, 4, 5, xNear, yNear, dist); + L2Check(x, y, 2, 5, xNear, yNear, dist); + L2Check(x, y, 3, 5, xNear, yNear, dist); + L2Check(x, y, 1, 5, xNear, yNear, dist); + } + } + } + + // Pass in the +- direction. + for (y = dim1m1; y >= 0; --y) + { + for (x = 0; x < dim0; ++x) + { + distance = dist(x, y); + if (distance > K1) + { + L2Check(x, y, -1, 0, xNear, yNear, dist); + L2Check(x, y, -1, 1, xNear, yNear, dist); + L2Check(x, y, 0, 1, xNear, yNear, dist); + } + if (distance > K2) + { + L2Check(x, y, -2, 1, xNear, yNear, dist); + L2Check(x, y, -1, 2, xNear, yNear, dist); + } + if (distance > K3) + { + L2Check(x, y, -3, 1, xNear, yNear, dist); + L2Check(x, y, -3, 2, xNear, yNear, dist); + L2Check(x, y, -2, 3, xNear, yNear, dist); + L2Check(x, y, -1, 3, xNear, yNear, dist); + } + if (distance > K4) + { + L2Check(x, y, -4, 1, xNear, yNear, dist); + L2Check(x, y, -4, 3, xNear, yNear, dist); + L2Check(x, y, -3, 4, xNear, yNear, dist); + L2Check(x, y, -1, 4, xNear, yNear, dist); + } + if (distance > K5) + { + L2Check(x, y, -5, 1, xNear, yNear, dist); + L2Check(x, y, -5, 2, xNear, yNear, dist); + L2Check(x, y, -5, 3, xNear, yNear, dist); + L2Check(x, y, -5, 4, xNear, yNear, dist); + L2Check(x, y, -4, 5, xNear, yNear, dist); + L2Check(x, y, -2, 5, xNear, yNear, dist); + L2Check(x, y, -3, 5, xNear, yNear, dist); + L2Check(x, y, -1, 5, xNear, yNear, dist); + } + } + } + + // Pass in the -+ direction. + for (y = 0; y < dim1; ++y) + { + for (x = dim0m1; x >= 0; --x) + { + distance = dist(x, y); + if (distance > K1) + { + L2Check(x, y, 1, 0, xNear, yNear, dist); + L2Check(x, y, 1, -1, xNear, yNear, dist); + L2Check(x, y, 0, -1, xNear, yNear, dist); + } + if (distance > K2) + { + L2Check(x, y, 2, -1, xNear, yNear, dist); + L2Check(x, y, 1, -2, xNear, yNear, dist); + } + if (distance > K3) + { + L2Check(x, y, 3, -1, xNear, yNear, dist); + L2Check(x, y, 3, -2, xNear, yNear, dist); + L2Check(x, y, 2, -3, xNear, yNear, dist); + L2Check(x, y, 1, -3, xNear, yNear, dist); + } + if (distance > K4) + { + L2Check(x, y, 4, -1, xNear, yNear, dist); + L2Check(x, y, 4, -3, xNear, yNear, dist); + L2Check(x, y, 3, -4, xNear, yNear, dist); + L2Check(x, y, 1, -4, xNear, yNear, dist); + } + if (distance > K5) + { + L2Check(x, y, 5, -1, xNear, yNear, dist); + L2Check(x, y, 5, -2, xNear, yNear, dist); + L2Check(x, y, 5, -3, xNear, yNear, dist); + L2Check(x, y, 5, -4, xNear, yNear, dist); + L2Check(x, y, 4, -5, xNear, yNear, dist); + L2Check(x, y, 2, -5, xNear, yNear, dist); + L2Check(x, y, 3, -5, xNear, yNear, dist); + L2Check(x, y, 1, -5, xNear, yNear, dist); + } + } + } + + xMax = 0; + yMax = 0; + maxDistance = 0.0f; + for (y = 0; y < dim1; ++y) + { + for (x = 0; x < dim0; ++x) + { + float fdistance = std::sqrt((float)dist(x, y)); + if (fdistance > maxDistance) + { + maxDistance = fdistance; + xMax = x; + yMax = y; + } + transform(x, y) = fdistance; + } + } + } + + // Compute a skeleton of a binary image. Boundary pixels are trimmed + // from the object one layer at a time based on their adjacency to + // interior pixels. At each step the connectivity and cycles of the + // object are preserved. The skeleton overwrites the contents of the + // input image. + static void GetSkeleton(Image2& image) + { + int const dim0 = image.GetDimension(0); + int const dim1 = image.GetDimension(1); + + // Trim pixels, mark interior as 4. + bool notDone = true; + while (notDone) + { + if (MarkInterior(image, 4, Interior4)) + { + // No interior pixels, trimmed set is at most 2-pixels + // thick. + notDone = false; + continue; + } + + if (ClearInteriorAdjacent(image, 4)) + { + // All remaining interior pixels are either articulation + // points or part of blobs whose boundary pixels are all + // articulation points. An example of the latter case is + // shown below. The background pixels are marked with '.' + // rather than '0' for readability. The interior pixels + // are marked with '4' and the boundary pixels are marked + // with '1'. + // + // ......... + // .....1... + // ..1.1.1.. + // .1.141... + // ..14441.. + // ..1441.1. + // .1.11.1.. + // ..1..1... + // ......... + // + // This is a pathological problem where there are many + // small holes (0-pixel with north, south, west, and east + // neighbors all 1-pixels) that your application can try + // to avoid by an initial pass over the image to fill in + // such holes. Of course, you do have problems with + // checkerboard patterns... + notDone = false; + continue; + } + } + + // Trim pixels, mark interior as 3. + notDone = true; + while (notDone) + { + if (MarkInterior(image, 3, Interior3)) + { + // No interior pixels, trimmed set is at most 2-pixels + // thick. + notDone = false; + continue; + } + + if (ClearInteriorAdjacent(image, 3)) + { + // All remaining 3-values can be safely removed since they + // are not articulation points and the removal will not + // cause new holes. + for (int y = 0; y < dim1; ++y) + { + for (int x = 0; x < dim0; ++x) + { + if (image(x, y) == 3 && !IsArticulation(image, x, y)) + { + image(x, y) = 0; + } + } + } + notDone = false; + continue; + } + } + + // Trim pixels, mark interior as 2. + notDone = true; + while (notDone) + { + if (MarkInterior(image, 2, Interior2)) + { + // No interior pixels, trimmed set is at most 1-pixel + // thick. Call it a skeleton. + notDone = false; + continue; + } + + if (ClearInteriorAdjacent(image, 2)) + { + // Removes 2-values that are not articulation points. + for (int y = 0; y < dim1; ++y) + { + for (int x = 0; x < dim0; ++x) + { + if (image(x, y) == 2 && !IsArticulation(image, x, y)) + { + image(x, y) = 0; + } + } + } + notDone = false; + continue; + } + } + + // Make the skeleton a binary image. + size_t const numPixels = image.GetNumPixels(); + for (size_t i = 0; i < numPixels; ++i) + { + if (image[i] != 0) + { + image[i] = 1; + } + } + } + + // In the remaining public member functions, the callback represents + // the action you want applied to each pixel as it is visited. + + // Visit pixels in a (2*thick+1)x(2*thick+1) square centered at (x,y). + static void DrawThickPixel(int x, int y, int thick, + std::function const& callback) + { + for (int dy = -thick; dy <= thick; ++dy) + { + for (int dx = -thick; dx <= thick; ++dx) + { + callback(x + dx, y + dy); + } + } + } + + // Visit pixels using Bresenham's line drawing algorithm. + static void DrawLine(int x0, int y0, int x1, int y1, + std::function const& callback) + { + // Starting point of line. + int x = x0, y = y0; + + // Direction of line. + int dx = x1 - x0, dy = y1 - y0; + + // Increment or decrement depending on direction of line. + int sx = (dx > 0 ? 1 : (dx < 0 ? -1 : 0)); + int sy = (dy > 0 ? 1 : (dy < 0 ? -1 : 0)); + + // Decision parameters for pixel selection. + if (dx < 0) + { + dx = -dx; + } + if (dy < 0) + { + dy = -dy; + } + int ax = 2 * dx, ay = 2 * dy; + int decX, decY; + + // Determine largest direction component, single-step related + // variable. + int maxValue = dx, var = 0; + if (dy > maxValue) + { + var = 1; + } + + // Traverse Bresenham line. + switch (var) + { + case 0: // Single-step in x-direction. + decY = ay - dx; + for (/**/; /**/; x += sx, decY += ay) + { + callback(x, y); + + // Take Bresenham step. + if (x == x1) + { + break; + } + if (decY >= 0) + { + decY -= ax; + y += sy; + } + } + break; + case 1: // Single-step in y-direction. + decX = ax - dy; + for (/**/; /**/; y += sy, decX += ax) + { + callback(x, y); + + // Take Bresenham step. + if (y == y1) + { + break; + } + if (decX >= 0) + { + decX -= ay; + x += sx; + } + } + break; + } + } + + // Visit pixels using Bresenham's circle drawing algorithm. Set + // 'solid' to false for drawing only the circle. Set 'solid' to true + // to draw all pixels on and inside the circle. + static void DrawCircle(int xCenter, int yCenter, int radius, bool solid, + std::function const& callback) + { + int x, y, dec; + + if (solid) + { + int xValue, yMin, yMax, i; + for (x = 0, y = radius, dec = 3 - 2 * radius; x <= y; ++x) + { + xValue = xCenter + x; + yMin = yCenter - y; + yMax = yCenter + y; + for (i = yMin; i <= yMax; ++i) + { + callback(xValue, i); + } + + xValue = xCenter - x; + for (i = yMin; i <= yMax; ++i) + { + callback(xValue, i); + } + + xValue = xCenter + y; + yMin = yCenter - x; + yMax = yCenter + x; + for (i = yMin; i <= yMax; ++i) + { + callback(xValue, i); + } + + xValue = xCenter - y; + for (i = yMin; i <= yMax; ++i) + { + callback(xValue, i); + } + + if (dec >= 0) + { + dec += -4 * (y--) + 4; + } + dec += 4 * x + 6; + } + } + else + { + for (x = 0, y = radius, dec = 3 - 2 * radius; x <= y; ++x) + { + callback(xCenter + x, yCenter + y); + callback(xCenter + x, yCenter - y); + callback(xCenter - x, yCenter + y); + callback(xCenter - x, yCenter - y); + callback(xCenter + y, yCenter + x); + callback(xCenter + y, yCenter - x); + callback(xCenter - y, yCenter + x); + callback(xCenter - y, yCenter - x); + + if (dec >= 0) + { + dec += -4 * (y--) + 4; + } + dec += 4 * x + 6; + } + } + } + + // Visit pixels in a rectangle of the specified dimensions. Set + // 'solid' to false for drawing only the rectangle. Set 'solid' to + // true to draw all pixels on and inside the rectangle. + static void DrawRectangle(int xMin, int yMin, int xMax, int yMax, + bool solid, std::function const& callback) + { + int x, y; + + if (solid) + { + for (y = yMin; y <= yMax; ++y) + { + for (x = xMin; x <= xMax; ++x) + { + callback(x, y); + } + } + } + else + { + for (x = xMin; x <= xMax; ++x) + { + callback(x, yMin); + callback(x, yMax); + } + for (y = yMin + 1; y <= yMax - 1; ++y) + { + callback(xMin, y); + callback(xMax, y); + } + } + } + + // Visit the pixels using Bresenham's algorithm for the axis-aligned + // ellipse ((x-xc)/a)^2 + ((y-yc)/b)^2 = 1, where xCenter is xc, + // yCenter is yc, xExtent is a, and yExtent is b. + static void DrawEllipse(int xCenter, int yCenter, int xExtent, int yExtent, + std::function const& callback) + { + int xExtSqr = xExtent * xExtent, yExtSqr = yExtent * yExtent; + int x, y, dec; + + x = 0; + y = yExtent; + dec = 2 * yExtSqr + xExtSqr * (1 - 2 * yExtent); + for (/**/; yExtSqr * x <= xExtSqr * y; ++x) + { + callback(xCenter + x, yCenter + y); + callback(xCenter - x, yCenter + y); + callback(xCenter + x, yCenter - y); + callback(xCenter - x, yCenter - y); + + if (dec >= 0) + { + dec += 4 * xExtSqr * (1 - y); + --y; + } + dec += yExtSqr * (4 * x + 6); + } + if (y == 0 && x < xExtent) + { + // The discretization caused us to reach the y-axis before the + // x-values reached the ellipse vertices. Draw a solid line + // along the x-axis to those vertices. + for (/**/; x <= xExtent; ++x) + { + callback(xCenter + x, yCenter); + callback(xCenter - x, yCenter); + } + return; + } + + x = xExtent; + y = 0; + dec = 2 * xExtSqr + yExtSqr * (1 - 2 * xExtent); + for (/**/; xExtSqr * y <= yExtSqr * x; ++y) + { + callback(xCenter + x, yCenter + y); + callback(xCenter - x, yCenter + y); + callback(xCenter + x, yCenter - y); + callback(xCenter - x, yCenter - y); + + if (dec >= 0) + { + dec += 4 * yExtSqr * (1 - x); + --x; + } + dec += xExtSqr * (4 * y + 6); + } + if (x == 0 && y < yExtent) + { + // The discretization caused us to reach the x-axis before the + // y-values reached the ellipse vertices. Draw a solid line + // along the y-axis to those vertices. + for (/**/; y <= yExtent; ++y) + { + callback(xCenter, yCenter + y); + callback(xCenter, yCenter - y); + } + } + } + + // Use a depth-first search for filling a 4-connected region. This is + // nonrecursive, simulated by using a heap-allocated "stack". The + // input (x,y) is the seed point that starts the fill. The x-value is + // in {0..xSize-1} and the y-value is in {0..ySize-1}. + template + static void DrawFloodFill4(int x, int y, int xSize, int ySize, + PixelType foreColor, PixelType backColor, + std::function const& setCallback, + std::function const& getCallback) + { + // Test for a valid seed. + if (x < 0 || x >= xSize || y < 0 || y >= ySize) + { + // The seed point is outside the image domain, so nothing to + // fill. + return; + } + + // Allocate the maximum amount of space needed for the stack. An + // empty stack has top == -1. + int const numPixels = xSize * ySize; + std::vector xStack(numPixels), yStack(numPixels); + + // Push seed point onto stack if it has the background color. All + // points pushed onto stack have background color backColor. + int top = 0; + xStack[top] = x; + yStack[top] = y; + + while (top >= 0) // stack is not empty + { + // Read top of stack. Do not pop since we need to return to + // this top value later to restart the fill in a different + // direction. + x = xStack[top]; + y = yStack[top]; + + // Fill the pixel. + setCallback(x, y, foreColor); + + int xp1 = x + 1; + if (xp1 < xSize && getCallback(xp1, y) == backColor) + { + // Push pixel with background color. + ++top; + xStack[top] = xp1; + yStack[top] = y; + continue; + } + + int xm1 = x - 1; + if (0 <= xm1 && getCallback(xm1, y) == backColor) + { + // Push pixel with background color. + ++top; + xStack[top] = xm1; + yStack[top] = y; + continue; + } + + int yp1 = y + 1; + if (yp1 < ySize && getCallback(x, yp1) == backColor) + { + // Push pixel with background color. + ++top; + xStack[top] = x; + yStack[top] = yp1; + continue; + } + + int ym1 = y - 1; + if (0 <= ym1 && getCallback(x, ym1) == backColor) + { + // Push pixel with background color. + ++top; + xStack[top] = x; + yStack[top] = ym1; + continue; + } + + // Done in all directions, pop and return to search other + // directions of the predecessor. + --top; + } + } + + private: + // Connected component labeling using depth-first search. + static void GetComponents(int numNeighbors, int const* delta, + Image2& image, std::vector>& components) + { + size_t const numPixels = image.GetNumPixels(); + std::vector numElements(numPixels); + std::vector vstack(numPixels); + size_t i, numComponents = 0; + int label = 2; + for (i = 0; i < numPixels; ++i) + { + if (image[i] == 1) + { + int top = -1; + vstack[++top] = i; + + int& count = numElements[numComponents + 1]; + count = 0; + while (top >= 0) + { + size_t v = vstack[top]; + image[v] = -1; + int j; + for (j = 0; j < numNeighbors; ++j) + { + size_t adj = v + delta[j]; + if (image[adj] == 1) + { + vstack[++top] = adj; + break; + } + } + if (j == numNeighbors) + { + image[v] = label; + ++count; + --top; + } + } + + ++numComponents; + ++label; + } + } + + if (numComponents > 0) + { + components.resize(numComponents + 1); + for (i = 1; i <= numComponents; ++i) + { + components[i].resize(numElements[i]); + numElements[i] = 0; + } + + for (i = 0; i < numPixels; ++i) + { + int value = image[i]; + if (value != 0) + { + // Labels started at 2 to support the depth-first + // search, so they need to be decremented for the + // correct labels. + image[i] = --value; + components[value][numElements[value]] = i; + ++numElements[value]; + } + } + } + } + + // Support for GetL2Distance. + static void L2Check(int x, int y, int dx, int dy, Image2& xNear, + Image2& yNear, Image2& dist) + { + int const dim0 = dist.GetDimension(0); + int const dim1 = dist.GetDimension(1); + int xp = x + dx, yp = y + dy; + if (0 <= xp && xp < dim0 && 0 <= yp && yp < dim1) + { + if (dist(xp, yp) < dist(x, y)) + { + int dx0 = xNear(xp, yp) - x; + int dy0 = yNear(xp, yp) - y; + int newDist = dx0 * dx0 + dy0 * dy0; + if (newDist < dist(x, y)) + { + xNear(x, y) = xNear(xp, yp); + yNear(x, y) = yNear(xp, yp); + dist(x, y) = newDist; + } + } + } + } + + // Support for GetSkeleton. + static bool Interior2(Image2& image, int x, int y) + { + bool b1 = (image(x, y - 1) != 0); + bool b3 = (image(x + 1, y) != 0); + bool b5 = (image(x, y + 1) != 0); + bool b7 = (image(x - 1, y) != 0); + return (b1 && b3) || (b3 && b5) || (b5 && b7) || (b7 && b1); + } + + static bool Interior3(Image2& image, int x, int y) + { + int numNeighbors = 0; + if (image(x - 1, y) != 0) + { + ++numNeighbors; + } + if (image(x + 1, y) != 0) + { + ++numNeighbors; + } + if (image(x, y - 1) != 0) + { + ++numNeighbors; + } + if (image(x, y + 1) != 0) + { + ++numNeighbors; + } + return numNeighbors == 3; + } + + static bool Interior4(Image2& image, int x, int y) + { + return image(x - 1, y) != 0 + && image(x + 1, y) != 0 + && image(x, y - 1) != 0 + && image(x, y + 1) != 0; + } + + static bool MarkInterior(Image2& image, int value, + bool (*function)(Image2&, int, int)) + { + int const dim0 = image.GetDimension(0); + int const dim1 = image.GetDimension(1); + bool noInterior = true; + for (int y = 0; y < dim1; ++y) + { + for (int x = 0; x < dim0; ++x) + { + if (image(x, y) > 0) + { + if (function(image, x, y)) + { + image(x, y) = value; + noInterior = false; + } + else + { + image(x, y) = 1; + } + } + } + } + return noInterior; + } + + static bool IsArticulation(Image2& image, int x, int y) + { + static std::array const articulation = + { + 0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0, + 0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0, + 0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0, + 0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0, + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0, + 0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0, + 0,0,0,0,1,1,0,0,1,1,0,0,1,1,0,0, + 1,1,1,1,1,1,1,1,1,1,0,0,1,1,0,0, + 0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,0,0,1,1,0,0,1,1,0,0, + 1,1,1,1,1,1,1,1,1,1,0,0,1,1,0,0, + 0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0 + }; + + // Converts 8 neighbors of pixel (x,y) to an 8-bit value, + // bit = 1 iff pixel is set. + int byteMask = 0; + if (image(x - 1, y - 1) != 0) + { + byteMask |= 0x01; + } + if (image(x, y - 1) != 0) + { + byteMask |= 0x02; + } + if (image(x + 1, y - 1) != 0) + { + byteMask |= 0x04; + } + if (image(x + 1, y) != 0) + { + byteMask |= 0x08; + } + if (image(x + 1, y + 1) != 0) + { + byteMask |= 0x10; + } + if (image(x, y + 1) != 0) + { + byteMask |= 0x20; + } + if (image(x - 1, y + 1) != 0) + { + byteMask |= 0x40; + } + if (image(x - 1, y) != 0) + { + byteMask |= 0x80; + } + + return articulation[byteMask] == 1; + } + + static bool ClearInteriorAdjacent(Image2& image, int value) + { + int const dim0 = image.GetDimension(0); + int const dim1 = image.GetDimension(1); + bool noRemoval = true; + for (int y = 0; y < dim1; ++y) + { + for (int x = 0; x < dim0; ++x) + { + if (image(x, y) == 1) + { + bool interiorAdjacent = + image(x - 1, y - 1) == value || + image(x, y - 1) == value || + image(x + 1, y - 1) == value || + image(x + 1, y) == value || + image(x + 1, y + 1) == value || + image(x, y + 1) == value || + image(x - 1, y + 1) == value || + image(x - 1, y) == value; + + if (interiorAdjacent && !IsArticulation(image, x, y)) + { + image(x, y) = 0; + noRemoval = false; + } + } + } + } + return noRemoval; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ImageUtility3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ImageUtility3.h new file mode 100644 index 0000000..7e36356 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ImageUtility3.h @@ -0,0 +1,562 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// Image utilities for Image3 objects. TODO: Extend this to a template +// class to allow the pixel type to be int*_t and uint*_t for * in +// {8,16,32,64}. +// +// All but the Draw* functions are operations on binary images. Let the image +// have d0 columns, d1 rows and d2 slices. The input image must have zeros on +// its boundaries x = 0, x = d0-1, y = 0, y = d1-1, z = 0 and z = d2-1. The +// 0-valued voxels are considered to be background. The 1-valued voxels are +// considered to be foreground. In some of the operations, to save memory and +// time the input image is modified by the algorithms. If you need to +// preserve the input image, make a copy of it before calling these +// functions. + +namespace WwiseGTE +{ + class ImageUtility3 + { + public: + // Compute the 6-connected components of a binary image. The input + // image is modified to avoid the cost of making a copy. On output, + // the image values are the labels for the components. The array + // components[k], k >= 1, contains the indices for the k-th component. + static void GetComponents6(Image3& image, + std::vector>& components) + { + std::array neighbors; + image.GetNeighborhood(neighbors); + GetComponents(6, &neighbors[0], image, components); + } + + // Compute the 18-connected components of a binary image. The input + // image is modified to avoid the cost of making a copy. On output, + // the image values are the labels for the components. The array + // components[k], k >= 1, contains the indices for the k-th component. + static void GetComponents18(Image3& image, + std::vector>& components) + { + std::array neighbors; + image.GetNeighborhood(neighbors); + GetComponents(18, &neighbors[0], image, components); + } + + // Compute the 26-connected components of a binary image. The input + // image is modified to avoid the cost of making a copy. On output, + // the image values are the labels for the components. The array + // components[k], k >= 1, contains the indices for the k-th component. + static void GetComponents26(Image3& image, + std::vector>& components) + { + std::array neighbors; + image.GetNeighborhood(neighbors); + GetComponents(26, &neighbors[0], image, components); + } + + // Dilate the image using a structuring element that contains the + // 6-connected neighbors. + static void Dilate6(Image3 const& inImage, Image3& outImage) + { + std::array, 6> neighbors; + inImage.GetNeighborhood(neighbors); + Dilate(6, &neighbors[0], inImage, outImage); + } + + // Dilate the image using a structuring element that contains the + // 18-connected neighbors. + static void Dilate18(Image3 const& inImage, Image3& outImage) + { + std::array, 18> neighbors; + inImage.GetNeighborhood(neighbors); + Dilate(18, &neighbors[0], inImage, outImage); + } + + // Dilate the image using a structuring element that contains the + // 26-connected neighbors. + static void Dilate26(Image3 const& inImage, Image3& outImage) + { + std::array, 26> neighbors; + inImage.GetNeighborhood(neighbors); + Dilate(26, &neighbors[0], inImage, outImage); + } + + // Compute coordinate-directional convex set. For a given coordinate + // direction (x, y, or z), identify the first and last 1-valued voxels + // on a segment of voxels in that direction. All voxels from first to + // last are set to 1. This is done for all segments in each of the + // coordinate directions. + static void ComputeCDConvex(Image3& image) + { + int const dim0 = image.GetDimension(0); + int const dim1 = image.GetDimension(1); + int const dim2 = image.GetDimension(2); + + Image3 temp = image; + int i0, i1, i2; + for (i1 = 0; i1 < dim1; ++i1) + { + for (i0 = 0; i0 < dim0; ++i0) + { + int i2min; + for (i2min = 0; i2min < dim2; ++i2min) + { + if ((temp(i0, i1, i2min) & 1) == 0) + { + temp(i0, i1, i2min) |= 2; + } + else + { + break; + } + } + if (i2min < dim2) + { + int i2max; + for (i2max = dim2 - 1; i2max >= i2min; --i2max) + { + if ((temp(i0, i1, i2max) & 1) == 0) + { + temp(i0, i1, i2max) |= 2; + } + else + { + break; + } + } + } + } + } + + for (i2 = 0; i2 < dim2; ++i2) + { + for (i0 = 0; i0 < dim0; ++i0) + { + int i1min; + for (i1min = 0; i1min < dim1; ++i1min) + { + if ((temp(i0, i1min, i2) & 1) == 0) + { + temp(i0, i1min, i2) |= 2; + } + else + { + break; + } + } + if (i1min < dim1) + { + int i1max; + for (i1max = dim1 - 1; i1max >= i1min; --i1max) + { + if ((temp(i0, i1max, i2) & 1) == 0) + { + temp(i0, i1max, i2) |= 2; + } + else + { + break; + } + } + } + } + } + + for (i2 = 0; i2 < dim2; ++i2) + { + for (i1 = 0; i1 < dim1; ++i1) + { + int i0min; + for (i0min = 0; i0min < dim0; ++i0min) + { + if ((temp(i0min, i1, i2) & 1) == 0) + { + temp(i0min, i1, i2) |= 2; + } + else + { + break; + } + } + if (i0min < dim0) + { + int i0max; + for (i0max = dim0 - 1; i0max >= i0min; --i0max) + { + if ((temp(i0max, i1, i2) & 1) == 0) + { + temp(i0max, i1, i2) |= 2; + } + else + { + break; + } + } + } + } + } + + for (size_t i = 0; i < image.GetNumPixels(); ++i) + { + image[i] = (temp[i] & 2 ? 0 : 1); + } + } + + // Use a depth-first search for filling a 6-connected region. This is + // nonrecursive, simulated by using a heap-allocated "stack". The input + // (x,y,z) is the seed point that starts the fill. + template + static void FloodFill6(Image3 & image, int x, int y, int z, + PixelType foreColor, PixelType backColor) + { + // Test for a valid seed. + int const dim0 = image.GetDimension(0); + int const dim1 = image.GetDimension(1); + int const dim2 = image.GetDimension(2); + if (x < 0 || x >= dim0 || y < 0 || y >= dim1 || z < 0 || z >= dim2) + { + // The seed point is outside the image domain, so there is + // nothing to fill. + return; + } + + // Allocate the maximum amount of space needed for the stack. An + // empty stack has top == -1. + size_t const numVoxels = image.GetNumPixels(); + std::vector xStack(numVoxels), yStack(numVoxels), zStack(numVoxels); + + // Push seed point onto stack if it has the background color. All + // points pushed onto stack have background color backColor. + int top = 0; + xStack[top] = x; + yStack[top] = y; + zStack[top] = z; + + while (top >= 0) // stack is not empty + { + // Read top of stack. Do not pop since we need to return to + // this top value later to restart the fill in a different + // direction. + x = xStack[top]; + y = yStack[top]; + z = zStack[top]; + + // Fill the pixel. + image(x, y, z) = foreColor; + + int xp1 = x + 1; + if (xp1 < dim0 && image(xp1, y, z) == backColor) + { + // Push pixel with background color. + ++top; + xStack[top] = xp1; + yStack[top] = y; + zStack[top] = z; + continue; + } + + int xm1 = x - 1; + if (0 <= xm1 && image(xm1, y, z) == backColor) + { + // Push pixel with background color. + ++top; + xStack[top] = xm1; + yStack[top] = y; + zStack[top] = z; + continue; + } + + int yp1 = y + 1; + if (yp1 < dim1 && image(x, yp1, z) == backColor) + { + // Push pixel with background color. + ++top; + xStack[top] = x; + yStack[top] = yp1; + zStack[top] = z; + continue; + } + + int ym1 = y - 1; + if (0 <= ym1 && image(x, ym1, z) == backColor) + { + // Push pixel with background color. + ++top; + xStack[top] = x; + yStack[top] = ym1; + zStack[top] = z; + continue; + } + + int zp1 = z + 1; + if (zp1 < dim2 && image(x, y, zp1) == backColor) + { + // Push pixel with background color. + ++top; + xStack[top] = x; + yStack[top] = y; + zStack[top] = zp1; + continue; + } + + int zm1 = z - 1; + if (0 <= zm1 && image(x, y, zm1) == backColor) + { + // Push pixel with background color. + ++top; + xStack[top] = x; + yStack[top] = y; + zStack[top] = zm1; + continue; + } + + // Done in all directions, pop and return to search other + // directions for the predecessor. + --top; + } + } + + // Visit pixels using Bresenham's line drawing algorithm. The callback + // represents the action you want applied to each voxel as it is visited. + static void DrawLine(int x0, int y0, int z0, int x1, int y1, int z1, + std::function const& callback) + { + // Starting point of line. + int x = x0, y = y0, z = z0; + + // Direction of line. + int dx = x1 - x0, dy = y1 - y0, dz = z1 - z0; + + // Increment or decrement depending on direction of line. + int sx = (dx > 0 ? 1 : (dx < 0 ? -1 : 0)); + int sy = (dy > 0 ? 1 : (dy < 0 ? -1 : 0)); + int sz = (dz > 0 ? 1 : (dz < 0 ? -1 : 0)); + + // Decision parameters for voxel selection. + if (dx < 0) + { + dx = -dx; + } + if (dy < 0) + { + dy = -dy; + } + if (dz < 0) + { + dz = -dz; + } + int ax = 2 * dx, ay = 2 * dy, az = 2 * dz; + int decX, decY, decZ; + + // Determine largest direction component, single-step related + // variable. + int maxValue = dx, var = 0; + if (dy > maxValue) + { + maxValue = dy; + var = 1; + } + if (dz > maxValue) + { + var = 2; + } + + // Traverse Bresenham line. + switch (var) + { + case 0: // Single-step in x-direction. + decY = ay - dx; + decZ = az - dx; + for (/**/; /**/; x += sx, decY += ay, decZ += az) + { + // Process voxel. + callback(x, y, z); + + // Take Bresenham step. + if (x == x1) + { + break; + } + if (decY >= 0) + { + decY -= ax; + y += sy; + } + if (decZ >= 0) + { + decZ -= ax; + z += sz; + } + } + break; + case 1: // Single-step in y-direction. + decX = ax - dy; + decZ = az - dy; + for (/**/; /**/; y += sy, decX += ax, decZ += az) + { + // Process voxel. + callback(x, y, z); + + // Take Bresenham step. + if (y == y1) + { + break; + } + if (decX >= 0) + { + decX -= ay; + x += sx; + } + if (decZ >= 0) + { + decZ -= ay; + z += sz; + } + } + break; + case 2: // Single-step in z-direction. + decX = ax - dz; + decY = ay - dz; + for (/**/; /**/; z += sz, decX += ax, decY += ay) + { + // Process voxel. + callback(x, y, z); + + // Take Bresenham step. + if (z == z1) + { + break; + } + if (decX >= 0) + { + decX -= az; + x += sx; + } + if (decY >= 0) + { + decY -= az; + y += sy; + } + } + break; + } + } + + private: + // Dilation using the specified structuring element. + static void Dilate(int numNeighbors, std::array const* delta, + Image3 const& inImage, Image3 & outImage) + { + int const bound0M1 = inImage.GetDimension(0) - 1; + int const bound1M1 = inImage.GetDimension(1) - 1; + int const bound2M1 = inImage.GetDimension(2) - 1; + for (int i2 = 1; i2 < bound2M1; ++i2) + { + for (int i1 = 1; i1 < bound1M1; ++i1) + { + for (int i0 = 1; i0 < bound0M1; ++i0) + { + if (inImage(i0, i1, i2) == 0) + { + for (int n = 0; n < numNeighbors; ++n) + { + int d0 = delta[n][0]; + int d1 = delta[n][1]; + int d2 = delta[n][2]; + if (inImage(i0 + d0, i1 + d1, i2 + d2) == 1) + { + outImage(i0, i1, i2) = 1; + break; + } + } + } + else + { + outImage(i0, i1, i2) = 1; + } + } + } + } + } + + // Connected component labeling using depth-first search. + static void GetComponents(int numNeighbors, int const* delta, + Image3 & image, std::vector> & components) + { + size_t const numVoxels = image.GetNumPixels(); + std::vector numElements(numVoxels); + std::vector vstack(numVoxels); + size_t i, numComponents = 0; + int label = 2; + for (i = 0; i < numVoxels; ++i) + { + if (image[i] == 1) + { + int top = -1; + vstack[++top] = i; + + int& count = numElements[numComponents + 1]; + count = 0; + while (top >= 0) + { + size_t v = vstack[top]; + image[v] = -1; + int j; + for (j = 0; j < numNeighbors; ++j) + { + size_t adj = v + delta[j]; + if (image[adj] == 1) + { + vstack[++top] = adj; + break; + } + } + if (j == numNeighbors) + { + image[v] = label; + ++count; + --top; + } + } + + ++numComponents; + ++label; + } + } + + if (numComponents > 0) + { + components.resize(numComponents + 1); + for (i = 1; i <= numComponents; ++i) + { + components[i].resize(numElements[i]); + numElements[i] = 0; + } + + for (i = 0; i < numVoxels; ++i) + { + int value = image[i]; + if (value != 0) + { + // Labels started at 2 to support the depth-first + // search, so they need to be decremented for the + // correct labels. + image[i] = --value; + components[value][numElements[value]] = i; + ++numElements[value]; + } + } + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IndexAttribute.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IndexAttribute.h new file mode 100644 index 0000000..adb0276 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IndexAttribute.h @@ -0,0 +1,85 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The IndexAttribute class represents an array of triples of indices into a +// vertex array for an indexed triangle mesh. For now, the source must be +// either uint16_t or uint32_t. + +namespace WwiseGTE +{ + struct IndexAttribute + { + // Construction. + inline IndexAttribute(void* inSource = nullptr, size_t inSize = 0) + : + source(inSource), + size(inSize) + { + } + + // Triangle access. + inline void SetTriangle(uint32_t t, uint32_t v0, uint32_t v1, uint32_t v2) + { + if (size == sizeof(uint32_t)) + { + uint32_t* index = reinterpret_cast(source) + 3 * t; + index[0] = v0; + index[1] = v1; + index[2] = v2; + return; + } + + if (size == sizeof(uint16_t)) + { + uint16_t* index = reinterpret_cast(source) + 3 * t; + index[0] = static_cast(v0); + index[1] = static_cast(v1); + index[2] = static_cast(v2); + return; + } + + // Unsupported type. + } + + inline void GetTriangle(uint32_t t, uint32_t& v0, uint32_t& v1, uint32_t& v2) const + { + if (size == sizeof(uint32_t)) + { + uint32_t* index = reinterpret_cast(source) + 3 * t; + v0 = index[0]; + v1 = index[1]; + v2 = index[2]; + return; + } + + if (size == sizeof(uint16_t)) + { + uint16_t* index = reinterpret_cast(source) + 3 * t; + v0 = static_cast(index[0]); + v1 = static_cast(index[1]); + v2 = static_cast(index[2]); + return; + } + + // Unsupported type. + v0 = 0; + v1 = 0; + v2 = 0; + } + + // The source pointer must be 4-byte aligned, which is guaranteed on + // 32-bit and 64-bit architectures. The size is the number of bytes + // per index. + void* source; + size_t size; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Integration.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Integration.h new file mode 100644 index 0000000..727e0a9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Integration.h @@ -0,0 +1,217 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class Integration + { + public: + // A simple algorithm, but slow to converge as the number of samples + // is increased. The 'numSamples' needs to be two or larger. + static Real TrapezoidRule(int numSamples, Real a, Real b, + std::function const& integrand) + { + Real h = (b - a) / (Real)(numSamples - 1); + Real result = (Real)0.5 * (integrand(a) + integrand(b)); + for (int i = 1; i <= numSamples - 2; ++i) + { + result += integrand(a + i * h); + } + result *= h; + return result; + } + + // The trapezoid rule is used to generate initial estimates, but then + // Richardson extrapolation is used to improve the estimates. This is + // preferred over TrapezoidRule. The 'order' must be positive. + static Real Romberg(int order, Real a, Real b, + std::function const& integrand) + { + Real const half = (Real)0.5; + std::vector> rom(order); + Real h = b - a; + rom[0][0] = half * h * (integrand(a) + integrand(b)); + for (int i0 = 2, p0 = 1; i0 <= order; ++i0, p0 *= 2, h *= half) + { + // Approximations via the trapezoid rule. + Real sum = (Real)0; + int i1; + for (i1 = 1; i1 <= p0; ++i1) + { + sum += integrand(a + h * (i1 - half)); + } + + // Richardson extrapolation. + rom[0][1] = half * (rom[0][0] + h * sum); + for (int i2 = 1, p2 = 4; i2 < i0; ++i2, p2 *= 4) + { + rom[i2][1] = (p2 * rom[i2 - 1][1] - rom[i2 - 1][0]) / (p2 - 1); + } + + for (i1 = 0; i1 < i0; ++i1) + { + rom[i1][0] = rom[i1][1]; + } + } + + Real result = rom[order - 1][0]; + return result; + } + + // Gaussian quadrature estimates the integral of a function f(x) + // defined on [-1,1] using + // integral_{-1}^{1} f(t) dt = sum_{i=0}^{n-1} c[i]*f(r[i]) + // where r[i] are the roots to the Legendre polynomial p(t) of degree + // n and + // c[i] = integral_{-1}^{1} prod_{j=0,j!=i} (t-r[j]/(r[i]-r[j]) dt + // To integrate over [a,b], a transformation to [-1,1] is applied + // internally: x - ((b-a)*t + (b+a))/2. The Legendre polynomials are + // generated by + // P[0](x) = 1, P[1](x) = x, + // P[k](x) = ((2*k-1)*x*P[k-1](x) - (k-1)*P[k-2](x))/k, k >= 2 + // Implementing the polynomial generation is simple, and computing the + // roots requires a numerical method for finding polynomial roots. + // The challenging task is to develop an efficient algorithm for + // computing the coefficients c[i] for a specified degree. The + // 'degree' must be two or larger. + + static void ComputeQuadratureInfo(int degree, std::vector& roots, + std::vector& coefficients) + { + Real const zero = (Real)0; + Real const one = (Real)1; + Real const half = (Real)0.5; + + std::vector> poly(degree + 1); + + poly[0].resize(1); + poly[0][0] = one; + + poly[1].resize(2); + poly[1][0] = zero; + poly[1][1] = one; + + for (int n = 2; n <= degree; ++n) + { + Real mult0 = (Real)(n - 1) / (Real)n; + Real mult1 = (Real)(2 * n - 1) / (Real)n; + + poly[n].resize(n + 1); + poly[n][0] = -mult0 * poly[n - 2][0]; + for (int i = 1; i <= n - 2; ++i) + { + poly[n][i] = mult1 * poly[n - 1][i - 1] - mult0 * poly[n - 2][i]; + } + poly[n][n - 1] = mult1 * poly[n - 1][n - 2]; + poly[n][n] = mult1 * poly[n - 1][n - 1]; + } + + roots.resize(degree); + RootsPolynomial::Find(degree, &poly[degree][0], 2048, &roots[0]); + + coefficients.resize(roots.size()); + size_t n = roots.size() - 1; + std::vector subroots(n); + for (size_t i = 0; i < roots.size(); ++i) + { + Real denominator = (Real)1; + for (size_t j = 0, k = 0; j < roots.size(); ++j) + { + if (j != i) + { + subroots[k++] = roots[j]; + denominator *= roots[i] - roots[j]; + } + } + + std::array delta = + { + -one - subroots.back(), + +one - subroots.back() + }; + + std::vector> weights(n); + weights[0][0] = half * delta[0] * delta[0]; + weights[0][1] = half * delta[1] * delta[1]; + for (size_t k = 1; k < n; ++k) + { + Real dk = (Real)k; + Real mult = -dk / (dk + (Real)2); + weights[k][0] = mult * delta[0] * weights[k - 1][0]; + weights[k][1] = mult * delta[1] * weights[k - 1][1]; + } + + struct Info + { + int numBits; + std::array product; + }; + + int numElements = (1 << static_cast(n - 1)); + std::vector info(numElements); + info[0].numBits = 0; + info[0].product[0] = one; + info[0].product[1] = one; + for (int ipow = 1, r = 0; ipow < numElements; ipow <<= 1, ++r) + { + info[ipow].numBits = 1; + info[ipow].product[0] = -one - subroots[r]; + info[ipow].product[1] = +one - subroots[r]; + for (int m = 1, j = ipow + 1; m < ipow; ++m, ++j) + { + info[j].numBits = info[m].numBits + 1; + info[j].product[0] = + info[ipow].product[0] * info[m].product[0]; + info[j].product[1] = + info[ipow].product[1] * info[m].product[1]; + } + } + + std::vector> sum(n); + std::array zero2 = { zero, zero }; + std::fill(sum.begin(), sum.end(), zero2); + for (size_t k = 0; k < info.size(); ++k) + { + sum[info[k].numBits][0] += info[k].product[0]; + sum[info[k].numBits][1] += info[k].product[1]; + } + + std::array total = zero2; + for (size_t k = 0; k < n; ++k) + { + total[0] += weights[n - 1 - k][0] * sum[k][0]; + total[1] += weights[n - 1 - k][1] * sum[k][1]; + } + + coefficients[i] = (total[1] - total[0]) / denominator; + } + } + + static Real GaussianQuadrature(std::vector const& roots, + std::vectorconst& coefficients, Real a, Real b, + std::function const& integrand) + { + Real const half = (Real)0.5; + Real radius = half * (b - a); + Real center = half * (b + a); + Real result = (Real)0; + for (size_t i = 0; i < roots.size(); ++i) + { + result += coefficients[i] * integrand(radius * roots[i] + center); + } + result *= radius; + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpAkima1.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpAkima1.h new file mode 100644 index 0000000..548c64d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpAkima1.h @@ -0,0 +1,154 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class IntpAkima1 + { + protected: + // Construction (abstract base class). + IntpAkima1(int quantity, Real const* F) + : + mQuantity(quantity), + mF(F) + { + // At least three data points are needed to construct the + // estimates of the boundary derivatives. + LogAssert(mQuantity >= 3, "Invalid input to IntpAkima1 constructor."); + + mPoly.resize(mQuantity - 1); + } + + public: + // Abstract base class. + virtual ~IntpAkima1() = default; + + // Member access. + inline int GetQuantity() const + { + return mQuantity; + } + + inline Real const* GetF() const + { + return mF; + } + + virtual Real GetXMin() const = 0; + + virtual Real GetXMax() const = 0; + + // Evaluate the function and its derivatives. The functions clamp the + // inputs to xmin <= x <= xmax. The first operator is for function + // evaluation. The second operator is for function or derivative + // evaluations. The 'order' argument is the order of the derivative + // or zero for the function itself. + Real operator()(Real x) const + { + x = std::min(std::max(x, GetXMin()), GetXMax()); + int index; + Real dx; + Lookup(x, index, dx); + return mPoly[index](dx); + } + + Real operator()(int order, Real x) const + { + x = std::min(std::max(x, GetXMin()), GetXMax()); + int index; + Real dx; + Lookup(x, index, dx); + return mPoly[index](order, dx); + } + + protected: + class Polynomial + { + public: + // P(x) = c[0] + c[1]*x + c[2]*x^2 + c[3]*x^3 + inline Real& operator[](int i) + { + return mCoeff[i]; + } + + Real operator()(Real x) const + { + return mCoeff[0] + x * (mCoeff[1] + x * (mCoeff[2] + x * mCoeff[3])); + } + + Real operator()(int order, Real x) const + { + switch (order) + { + case 0: + return mCoeff[0] + x * (mCoeff[1] + x * (mCoeff[2] + x * mCoeff[3])); + case 1: + return mCoeff[1] + x * ((Real)2 * mCoeff[2] + x * (Real)3 * mCoeff[3]); + case 2: + return (Real)2 * mCoeff[2] + x * (Real)6 * mCoeff[3]; + case 3: + return (Real)6 * mCoeff[3]; + } + + return (Real)0; + } + + private: + std::array mCoeff; + }; + + Real ComputeDerivative(Real* slope) const + { + if (slope[1] != slope[2]) + { + if (slope[0] != slope[1]) + { + if (slope[2] != slope[3]) + { + Real ad0 = std::fabs(slope[3] - slope[2]); + Real ad1 = std::fabs(slope[0] - slope[1]); + return (ad0 * slope[1] + ad1 * slope[2]) / (ad0 + ad1); + } + else + { + return slope[2]; + } + } + else + { + if (slope[2] != slope[3]) + { + return slope[1]; + } + else + { + return ((Real)0.5)* (slope[1] + slope[2]); + } + } + } + else + { + return slope[1]; + } + } + + virtual void Lookup(Real x, int& index, Real& dx) const = 0; + + int mQuantity; + Real const* mF; + std::vector mPoly; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpAkimaNonuniform1.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpAkimaNonuniform1.h new file mode 100644 index 0000000..995d708 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpAkimaNonuniform1.h @@ -0,0 +1,112 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + template + class IntpAkimaNonuniform1 : public IntpAkima1 + { + public: + // Construction. The interpolator is for arbitrarily spaced x-values. + // The input arrays must have 'quantity' elements and the X[] array + // must store increasing values: X[i + 1] > X[i] for all i. + IntpAkimaNonuniform1(int quantity, Real const* X, Real const* F) + : + IntpAkima1(quantity, F), + mX(X) + { + LogAssert(X != nullptr, "Invalid input."); + for (int j0 = 0, j1 = 1; j1 < quantity; ++j0, ++j1) + { + LogAssert(X[j1] > X[j0], "Invalid input."); + } + + // Compute slopes. + std::vector slope(quantity + 3); + int i, ip1, ip2; + for (i = 0, ip1 = 1, ip2 = 2; i < quantity - 1; ++i, ++ip1, ++ip2) + { + Real dx = X[ip1] - X[i]; + Real df = F[ip1] - F[i]; + slope[ip2] = df / dx; + } + + slope[1] = (Real)2 * slope[2] - slope[3]; + slope[0] = (Real)2 * slope[1] - slope[2]; + slope[quantity + 1] = (Real)2 * slope[quantity] - slope[quantity - 1]; + slope[quantity + 2] = (Real)2 * slope[quantity + 1] - slope[quantity]; + + // Construct derivatives. + std::vector FDer(quantity); + for (i = 0; i < quantity; ++i) + { + FDer[i] = this->ComputeDerivative(&slope[i]); + } + + // Construct polynomials. + for (i = 0, ip1 = 1; i < quantity - 1; ++i, ++ip1) + { + auto& poly = this->mPoly[i]; + + Real F0 = F[i]; + Real F1 = F[ip1]; + Real FDer0 = FDer[i]; + Real FDer1 = FDer[ip1]; + Real df = F1 - F0; + Real dx = X[ip1] - X[i]; + Real dx2 = dx * dx; + Real dx3 = dx2 * dx; + + poly[0] = F0; + poly[1] = FDer0; + poly[2] = ((Real)3 * df - dx * (FDer1 + (Real)2 * FDer0)) / dx2; + poly[3] = (dx * (FDer0 + FDer1) - (Real)2 * df) / dx3; + } + } + + virtual ~IntpAkimaNonuniform1() = default; + + // Member access. + Real const* GetX() const + { + return mX; + } + + virtual Real GetXMin() const override + { + return mX[0]; + } + + virtual Real GetXMax() const override + { + return mX[this->mQuantity - 1]; + } + + protected: + virtual void Lookup(Real x, int& index, Real& dx) const override + { + // The caller has ensured that mXMin <= x <= mXMax. + for (index = 0; index + 1 < this->mQuantity; ++index) + { + if (x < mX[index + 1]) + { + dx = x - mX[index]; + return; + } + } + + --index; + dx = x - mX[index]; + } + + Real const* mX; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpAkimaUniform1.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpAkimaUniform1.h new file mode 100644 index 0000000..e978537 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpAkimaUniform1.h @@ -0,0 +1,108 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + template + class IntpAkimaUniform1 : public IntpAkima1 + { + public: + // Construction and destruction. The interpolator is for uniformly + // spaced x-values. + IntpAkimaUniform1(int quantity, Real xMin, Real xSpacing, Real const* F) + : + IntpAkima1(quantity, F), + mXMin(xMin), + mXSpacing(xSpacing) + { + LogAssert(mXSpacing > (Real)0, "Spacing must be positive."); + + mXMax = mXMin + mXSpacing * static_cast(quantity - 1); + + // Compute slopes. + Real invDX = (Real)1 / mXSpacing; + std::vector slope(quantity + 3); + int i, ip1, ip2; + for (i = 0, ip1 = 1, ip2 = 2; i < quantity - 1; ++i, ++ip1, ++ip2) + { + slope[ip2] = (this->mF[ip1] - this->mF[i]) * invDX; + } + + slope[1] = (Real)2 * slope[2] - slope[3]; + slope[0] = (Real)2 * slope[1] - slope[2]; + slope[quantity + 1] = (Real)2 * slope[quantity] - slope[quantity - 1]; + slope[quantity + 2] = (Real)2 * slope[quantity + 1] - slope[quantity]; + + // Construct derivatives. + std::vector FDer(quantity); + for (i = 0; i < quantity; ++i) + { + FDer[i] = this->ComputeDerivative(&slope[i]); + } + + // Construct polynomials. + Real invDX2 = (Real)1 / (mXSpacing * mXSpacing); + Real invDX3 = invDX2 / mXSpacing; + for (i = 0, ip1 = 1; i < quantity - 1; ++i, ++ip1) + { + auto& poly = this->mPoly[i]; + + Real F0 = F[i]; + Real F1 = F[ip1]; + Real df = F1 - F0; + Real FDer0 = FDer[i]; + Real FDer1 = FDer[ip1]; + + poly[0] = F0; + poly[1] = FDer0; + poly[2] = ((Real)3 * df - mXSpacing * (FDer1 + (Real)2 * FDer0)) * invDX2; + poly[3] = (mXSpacing * (FDer0 + FDer1) - (Real)2 * df) * invDX3; + } + } + + virtual ~IntpAkimaUniform1() = default; + + // Member access. + inline virtual Real GetXMin() const override + { + return mXMin; + } + + inline virtual Real GetXMax() const override + { + return mXMax; + } + + inline Real GetXSpacing() const + { + return mXSpacing; + } + + protected: + virtual void Lookup(Real x, int& index, Real& dx) const override + { + // The caller has ensured that mXMin <= x <= mXMax. + for (index = 0; index + 1 < this->mQuantity; ++index) + { + if (x < mXMin + mXSpacing * (index + 1)) + { + dx = x - (mXMin + mXSpacing * index); + return; + } + } + + --index; + dx = x - (mXMin + mXSpacing * index); + } + + Real mXMin, mXMax, mXSpacing; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpAkimaUniform2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpAkimaUniform2.h new file mode 100644 index 0000000..0d0123b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpAkimaUniform2.h @@ -0,0 +1,563 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The interpolator is for uniformly spaced (x,y)-values. The input samples +// F must be stored in row-major order to represent f(x,y); that is, +// F[c + xBound*r] corresponds to f(x,y), where c is the index corresponding +// to x and r is the index corresponding to y. + +namespace WwiseGTE +{ + template + class IntpAkimaUniform2 + { + public: + // Construction and destruction. + IntpAkimaUniform2(int xBound, int yBound, Real xMin, Real xSpacing, + Real yMin, Real ySpacing, Real const* F) + : + mXBound(xBound), + mYBound(yBound), + mQuantity(xBound* yBound), + mXMin(xMin), + mXSpacing(xSpacing), + mYMin(yMin), + mYSpacing(ySpacing), + mF(F), + mPoly(xBound - 1, mYBound - 1) + { + // At least a 3x3 block of data points is needed to construct the + // estimates of the boundary derivatives. + LogAssert(mXBound >= 3 && mYBound >= 3 && mF != nullptr, "Invalid input."); + LogAssert(mXSpacing > (Real)0 && mYSpacing > (Real)0, "Invalid input."); + + mXMax = mXMin + mXSpacing * static_cast(mXBound - 1); + mYMax = mYMin + mYSpacing * static_cast(mYBound - 1); + + // Create a 2D wrapper for the 1D samples. + Array2 Fmap(mXBound, mYBound, const_cast(mF)); + + // Construct first-order derivatives. + Array2 FX(mXBound, mYBound), FY(mXBound, mYBound); + GetFX(Fmap, FX); + GetFY(Fmap, FY); + + // Construct second-order derivatives. + Array2 FXY(mXBound, mYBound); + GetFXY(Fmap, FXY); + + // Construct polynomials. + GetPolynomials(Fmap, FX, FY, FXY); + } + + ~IntpAkimaUniform2() = default; + + // Member access. + inline int GetXBound() const + { + return mXBound; + } + + inline int GetYBound() const + { + return mYBound; + } + + inline int GetQuantity() const + { + return mQuantity; + } + + inline Real const* GetF() const + { + return mF; + } + + inline Real GetXMin() const + { + return mXMin; + } + + inline Real GetXMax() const + { + return mXMax; + } + + inline Real GetXSpacing() const + { + return mXSpacing; + } + + inline Real GetYMin() const + { + return mYMin; + } + + inline Real GetYMax() const + { + return mYMax; + } + + inline Real GetYSpacing() const + { + return mYSpacing; + } + + // Evaluate the function and its derivatives. The functions clamp the + // inputs to xmin <= x <= xmax and ymin <= y <= ymax. The first + // operator is for function evaluation. The second operator is for + // function or derivative evaluations. The xOrder argument is the + // order of the x-derivative and the yOrder argument is the order of + // the y-derivative. Both orders are zero to get the function value + // itself. + Real operator()(Real x, Real y) const + { + x = std::min(std::max(x, mXMin), mXMax); + y = std::min(std::max(y, mYMin), mYMax); + int ix, iy; + Real dx, dy; + XLookup(x, ix, dx); + YLookup(y, iy, dy); + return mPoly[iy][ix](dx, dy); + } + + Real operator()(int xOrder, int yOrder, Real x, Real y) const + { + x = std::min(std::max(x, mXMin), mXMax); + y = std::min(std::max(y, mYMin), mYMax); + int ix, iy; + Real dx, dy; + XLookup(x, ix, dx); + YLookup(y, iy, dy); + return mPoly[iy][ix](xOrder, yOrder, dx, dy); + } + + private: + class Polynomial + { + public: + Polynomial() + { + for (size_t i = 0; i < 4; ++i) + { + mCoeff[i].fill((Real)0); + } + } + + // P(x,y) = (1,x,x^2,x^3)*A*(1,y,y^2,y^3). The matrix term A[ix][iy] + // corresponds to the polynomial term x^{ix} y^{iy}. + Real& A(int ix, int iy) + { + return mCoeff[ix][iy]; + } + + Real operator()(Real x, Real y) const + { + std::array B; + for (int i = 0; i <= 3; ++i) + { + B[i] = mCoeff[i][0] + y * (mCoeff[i][1] + y * (mCoeff[i][2] + y * mCoeff[i][3])); + } + + return B[0] + x * (B[1] + x * (B[2] + x * B[3])); + } + + Real operator()(int xOrder, int yOrder, Real x, Real y) const + { + std::array xPow; + switch (xOrder) + { + case 0: + xPow[0] = (Real)1; + xPow[1] = x; + xPow[2] = x * x; + xPow[3] = x * x * x; + break; + case 1: + xPow[0] = (Real)0; + xPow[1] = (Real)1; + xPow[2] = (Real)2 * x; + xPow[3] = (Real)3 * x * x; + break; + case 2: + xPow[0] = (Real)0; + xPow[1] = (Real)0; + xPow[2] = (Real)2; + xPow[3] = (Real)6 * x; + break; + case 3: + xPow[0] = (Real)0; + xPow[1] = (Real)0; + xPow[2] = (Real)0; + xPow[3] = (Real)6; + break; + default: + return (Real)0; + } + + std::array yPow; + switch (yOrder) + { + case 0: + yPow[0] = (Real)1; + yPow[1] = y; + yPow[2] = y * y; + yPow[3] = y * y * y; + break; + case 1: + yPow[0] = (Real)0; + yPow[1] = (Real)1; + yPow[2] = (Real)2 * y; + yPow[3] = (Real)3 * y * y; + break; + case 2: + yPow[0] = (Real)0; + yPow[1] = (Real)0; + yPow[2] = (Real)2; + yPow[3] = (Real)6 * y; + break; + case 3: + yPow[0] = (Real)0; + yPow[1] = (Real)0; + yPow[2] = (Real)0; + yPow[3] = (Real)6; + break; + default: + return (Real)0; + } + + Real p = (Real)0; + for (size_t iy = 0; iy <= 3; ++iy) + { + for (size_t ix = 0; ix <= 3; ++ix) + { + p += mCoeff[ix][iy] * xPow[ix] * yPow[iy]; + } + } + + return p; + } + + private: + std::array, 4> mCoeff; + }; + + // Support for construction. + void GetFX(Array2 const& F, Array2& FX) + { + Array2 slope(mXBound + 3, mYBound); + Real invDX = (Real)1 / mXSpacing; + int ix, iy; + for (iy = 0; iy < mYBound; ++iy) + { + for (ix = 0; ix < mXBound - 1; ++ix) + { + slope[iy][ix + 2] = (F[iy][ix + 1] - F[iy][ix]) * invDX; + } + + slope[iy][1] = (Real)2 * slope[iy][2] - slope[iy][3]; + slope[iy][0] = (Real)2 * slope[iy][1] - slope[iy][2]; + slope[iy][mXBound + 1] = (Real)2 * slope[iy][mXBound] - slope[iy][mXBound - 1]; + slope[iy][mXBound + 2] = (Real)2 * slope[iy][mXBound + 1] - slope[iy][mXBound]; + } + + for (iy = 0; iy < mYBound; ++iy) + { + for (ix = 0; ix < mXBound; ++ix) + { + FX[iy][ix] = ComputeDerivative(slope[iy] + ix); + } + } + } + + void GetFY(Array2 const& F, Array2& FY) + { + Array2 slope(mYBound + 3, mXBound); + Real invDY = (Real)1 / mYSpacing; + int ix, iy; + for (ix = 0; ix < mXBound; ++ix) + { + for (iy = 0; iy < mYBound - 1; ++iy) + { + slope[ix][iy + 2] = (F[iy + 1][ix] - F[iy][ix]) * invDY; + } + + slope[ix][1] = (Real)2 * slope[ix][2] - slope[ix][3]; + slope[ix][0] = (Real)2 * slope[ix][1] - slope[ix][2]; + slope[ix][mYBound + 1] = (Real)2 * slope[ix][mYBound] - slope[ix][mYBound - 1]; + slope[ix][mYBound + 2] = (Real)2 * slope[ix][mYBound + 1] - slope[ix][mYBound]; + } + + for (ix = 0; ix < mXBound; ++ix) + { + for (iy = 0; iy < mYBound; ++iy) + { + FY[iy][ix] = ComputeDerivative(slope[ix] + iy); + } + } + } + + void GetFXY(Array2 const& F, Array2& FXY) + { + int xBoundM1 = mXBound - 1; + int yBoundM1 = mYBound - 1; + int ix0 = xBoundM1, ix1 = ix0 - 1, ix2 = ix1 - 1; + int iy0 = yBoundM1, iy1 = iy0 - 1, iy2 = iy1 - 1; + int ix, iy; + + Real invDXDY = (Real)1 / (mXSpacing * mYSpacing); + + // corners + FXY[0][0] = (Real)0.25 * invDXDY * ( + (Real)9 * F[0][0] + - (Real)12 * F[0][1] + + (Real)3 * F[0][2] + - (Real)12 * F[1][0] + + (Real)16 * F[1][1] + - (Real)4 * F[1][2] + + (Real)3 * F[2][0] + - (Real)4 * F[2][1] + + F[2][2]); + + FXY[0][xBoundM1] = (Real)0.25 * invDXDY * ( + (Real)9 * F[0][ix0] + - (Real)12 * F[0][ix1] + + (Real)3 * F[0][ix2] + - (Real)12 * F[1][ix0] + + (Real)16 * F[1][ix1] + - (Real)4 * F[1][ix2] + + (Real)3 * F[2][ix0] + - (Real)4 * F[2][ix1] + + F[2][ix2]); + + FXY[yBoundM1][0] = (Real)0.25 * invDXDY * ( + (Real)9 * F[iy0][0] + - (Real)12 * F[iy0][1] + + (Real)3 * F[iy0][2] + - (Real)12 * F[iy1][0] + + (Real)16 * F[iy1][1] + - (Real)4 * F[iy1][2] + + (Real)3 * F[iy2][0] + - (Real)4 * F[iy2][1] + + F[iy2][2]); + + FXY[yBoundM1][xBoundM1] = (Real)0.25 * invDXDY * ( + (Real)9 * F[iy0][ix0] + - (Real)12 * F[iy0][ix1] + + (Real)3 * F[iy0][ix2] + - (Real)12 * F[iy1][ix0] + + (Real)16 * F[iy1][ix1] + - (Real)4 * F[iy1][ix2] + + (Real)3 * F[iy2][ix0] + - (Real)4 * F[iy2][ix1] + + F[iy2][ix2]); + + // x-edges + for (ix = 1; ix < xBoundM1; ++ix) + { + FXY[0][ix] = (Real)0.25 * invDXDY * ( + (Real)3 * (F[0][ix - 1] - F[0][ix + 1]) + - (Real)4 * (F[1][ix - 1] - F[1][ix + 1]) + + (F[2][ix - 1] - F[2][ix + 1])); + + FXY[yBoundM1][ix] = (Real)0.25 * invDXDY * ( + (Real)3 * (F[iy0][ix - 1] - F[iy0][ix + 1]) + - (Real)4 * (F[iy1][ix - 1] - F[iy1][ix + 1]) + + (F[iy2][ix - 1] - F[iy2][ix + 1])); + } + + // y-edges + for (iy = 1; iy < yBoundM1; ++iy) + { + FXY[iy][0] = (Real)0.25 * invDXDY * ( + (Real)3 * (F[iy - 1][0] - F[iy + 1][0]) + - (Real)4 * (F[iy - 1][1] - F[iy + 1][1]) + + (F[iy - 1][2] - F[iy + 1][2])); + + FXY[iy][xBoundM1] = (Real)0.25 * invDXDY * ( + (Real)3 * (F[iy - 1][ix0] - F[iy + 1][ix0]) + - (Real)4 * (F[iy - 1][ix1] - F[iy + 1][ix1]) + + (F[iy - 1][ix2] - F[iy + 1][ix2])); + } + + // interior + for (iy = 1; iy < yBoundM1; ++iy) + { + for (ix = 1; ix < xBoundM1; ++ix) + { + FXY[iy][ix] = (Real)0.25 * invDXDY * (F[iy - 1][ix - 1] - + F[iy - 1][ix + 1] - F[iy + 1][ix - 1] + F[iy + 1][ix + 1]); + } + } + } + + void GetPolynomials(Array2 const& F, Array2 const& FX, + Array2 const& FY, Array2 const& FXY) + { + int xBoundM1 = mXBound - 1; + int yBoundM1 = mYBound - 1; + for (int iy = 0; iy < yBoundM1; ++iy) + { + for (int ix = 0; ix < xBoundM1; ++ix) + { + // Note the 'transposing' of the 2x2 blocks (to match + // notation used in the polynomial definition). + Real G[2][2] = + { + { F[iy][ix], F[iy + 1][ix] }, + { F[iy][ix + 1], F[iy + 1][ix + 1] } + }; + + Real GX[2][2] = + { + { FX[iy][ix], FX[iy + 1][ix] }, + { FX[iy][ix + 1], FX[iy + 1][ix + 1] } + }; + + Real GY[2][2] = + { + { FY[iy][ix], FY[iy + 1][ix] }, + { FY[iy][ix + 1], FY[iy + 1][ix + 1] } + }; + + Real GXY[2][2] = + { + { FXY[iy][ix], FXY[iy + 1][ix] }, + { FXY[iy][ix + 1], FXY[iy + 1][ix + 1] } + }; + + Construct(mPoly[iy][ix], G, GX, GY, GXY); + } + } + } + + Real ComputeDerivative(Real const* slope) const + { + if (slope[1] != slope[2]) + { + if (slope[0] != slope[1]) + { + if (slope[2] != slope[3]) + { + Real ad0 = std::fabs(slope[3] - slope[2]); + Real ad1 = std::fabs(slope[0] - slope[1]); + return (ad0 * slope[1] + ad1 * slope[2]) / (ad0 + ad1); + } + else + { + return slope[2]; + } + } + else + { + if (slope[2] != slope[3]) + { + return slope[1]; + } + else + { + return (Real)0.5 * (slope[1] + slope[2]); + } + } + } + else + { + return slope[1]; + } + } + + void Construct(Polynomial& poly, Real const F[2][2], Real const FX[2][2], + Real const FY[2][2], Real const FXY[2][2]) + { + Real dx = mXSpacing; + Real dy = mYSpacing; + Real invDX = (Real)1 / dx, invDX2 = invDX * invDX; + Real invDY = (Real)1 / dy, invDY2 = invDY * invDY; + Real b0, b1, b2, b3; + + poly.A(0, 0) = F[0][0]; + poly.A(1, 0) = FX[0][0]; + poly.A(0, 1) = FY[0][0]; + poly.A(1, 1) = FXY[0][0]; + + b0 = (F[1][0] - poly(0, 0, dx, (Real)0)) * invDX2; + b1 = (FX[1][0] - poly(1, 0, dx, (Real)0)) * invDX; + poly.A(2, 0) = (Real)3 * b0 - b1; + poly.A(3, 0) = ((Real)-2 * b0 + b1) * invDX; + + b0 = (F[0][1] - poly(0, 0, (Real)0, dy)) * invDY2; + b1 = (FY[0][1] - poly(0, 1, (Real)0, dy)) * invDY; + poly.A(0, 2) = (Real)3 * b0 - b1; + poly.A(0, 3) = ((Real)-2 * b0 + b1) * invDY; + + b0 = (FY[1][0] - poly(0, 1, dx, (Real)0)) * invDX2; + b1 = (FXY[1][0] - poly(1, 1, dx, (Real)0)) * invDX; + poly.A(2, 1) = (Real)3 * b0 - b1; + poly.A(3, 1) = ((Real)-2 * b0 + b1) * invDX; + + b0 = (FX[0][1] - poly(1, 0, (Real)0, dy)) * invDY2; + b1 = (FXY[0][1] - poly(1, 1, (Real)0, dy)) * invDY; + poly.A(1, 2) = (Real)3 * b0 - b1; + poly.A(1, 3) = ((Real)-2 * b0 + b1) * invDY; + + b0 = (F[1][1] - poly(0, 0, dx, dy)) * invDX2 * invDY2; + b1 = (FX[1][1] - poly(1, 0, dx, dy)) * invDX * invDY2; + b2 = (FY[1][1] - poly(0, 1, dx, dy)) * invDX2 * invDY; + b3 = (FXY[1][1] - poly(1, 1, dx, dy)) * invDX * invDY; + poly.A(2, 2) = (Real)9 * b0 - (Real)3 * b1 - (Real)3 * b2 + b3; + poly.A(3, 2) = ((Real)-6 * b0 + (Real)3 * b1 + (Real)2 * b2 - b3) * invDX; + poly.A(2, 3) = ((Real)-6 * b0 + (Real)2 * b1 + (Real)3 * b2 - b3) * invDY; + poly.A(3, 3) = ((Real)4 * b0 - (Real)2 * b1 - (Real)2 * b2 + b3) * invDX * invDY; + } + + // Support for evaluation. + void XLookup(Real x, int& xIndex, Real& dx) const + { + for (xIndex = 0; xIndex + 1 < mXBound; ++xIndex) + { + if (x < mXMin + mXSpacing * (xIndex + 1)) + { + dx = x - (mXMin + mXSpacing * xIndex); + return; + } + } + + --xIndex; + dx = x - (mXMin + mXSpacing * xIndex); + } + + void YLookup(Real y, int& yIndex, Real& dy) const + { + for (yIndex = 0; yIndex + 1 < mYBound; ++yIndex) + { + if (y < mYMin + mYSpacing * (yIndex + 1)) + { + dy = y - (mYMin + mYSpacing * yIndex); + return; + } + } + + yIndex--; + dy = y - (mYMin + mYSpacing * yIndex); + } + + int mXBound, mYBound, mQuantity; + Real mXMin, mXMax, mXSpacing; + Real mYMin, mYMax, mYSpacing; + Real const* mF; + Array2 mPoly; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpAkimaUniform3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpAkimaUniform3.h new file mode 100644 index 0000000..056fc00 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpAkimaUniform3.h @@ -0,0 +1,1415 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The interpolator is for uniformly spaced(x,y z)-values. The input samples +// must be stored in lexicographical order to represent f(x,y,z); that is, +// F[c + xBound*(r + yBound*s)] corresponds to f(x,y,z), where c is the index +// corresponding to x, r is the index corresponding to y, and s is the index +// corresponding to z. + +namespace WwiseGTE +{ + template + class IntpAkimaUniform3 + { + public: + // Construction and destruction. + IntpAkimaUniform3(int xBound, int yBound, int zBound, Real xMin, + Real xSpacing, Real yMin, Real ySpacing, Real zMin, Real zSpacing, + Real const* F) + : + mXBound(xBound), + mYBound(yBound), + mZBound(zBound), + mQuantity(xBound* yBound* zBound), + mXMin(xMin), + mXSpacing(xSpacing), + mYMin(yMin), + mYSpacing(ySpacing), + mZMin(zMin), + mZSpacing(zSpacing), + mF(F), + mPoly(xBound - 1, yBound - 1, zBound - 1) + { + // At least a 3x3x3 block of data points is needed to construct + // the estimates of the boundary derivatives. + LogAssert(mXBound >= 3 && mYBound >= 3 && mZBound >= 3 && mF != nullptr, "Invalid input."); + LogAssert(mXSpacing > (Real)0 && mYSpacing > (Real)0 && mZSpacing > (Real)0, "Invalid input."); + + mXMax = mXMin + mXSpacing * static_cast(mXBound - 1); + mYMax = mYMin + mYSpacing * static_cast(mYBound - 1); + mZMax = mZMin + mZSpacing * static_cast(mZBound - 1); + + // Create a 3D wrapper for the 1D samples. + Array3 Fmap(mXBound, mYBound, mZBound, const_cast(mF)); + + // Construct first-order derivatives. + Array3 FX(mXBound, mYBound, mZBound); + Array3 FY(mXBound, mYBound, mZBound); + Array3 FZ(mXBound, mYBound, mZBound); + GetFX(Fmap, FX); + GetFX(Fmap, FY); + GetFX(Fmap, FZ); + + // Construct second-order derivatives. + Array3 FXY(mXBound, mYBound, mZBound); + Array3 FXZ(mXBound, mYBound, mZBound); + Array3 FYZ(mXBound, mYBound, mZBound); + GetFX(Fmap, FXY); + GetFX(Fmap, FXZ); + GetFX(Fmap, FYZ); + + // Construct third-order derivatives. + Array3 FXYZ(mXBound, mYBound, mZBound); + GetFXYZ(Fmap, FXYZ); + + // Construct polynomials. + GetPolynomials(Fmap, FX, FY, FZ, FXY, FXZ, FYZ, FXYZ); + } + + ~IntpAkimaUniform3() = default; + + // Member access. + inline int GetXBound() const + { + return mXBound; + } + + inline int GetYBound() const + { + return mYBound; + } + + inline int GetZBound() const + { + return mZBound; + } + + inline int GetQuantity() const + { + return mQuantity; + } + + inline Real const* GetF() const + { + return mF; + } + + inline Real GetXMin() const + { + return mXMin; + } + + inline Real GetXMax() const + { + return mXMax; + } + + inline Real GetXSpacing() const + { + return mXSpacing; + } + + inline Real GetYMin() const + { + return mYMin; + } + + inline Real GetYMax() const + { + return mYMax; + } + + inline Real GetYSpacing() const + { + return mYSpacing; + } + + inline Real GetZMin() const + { + return mZMin; + } + + inline Real GetZMax() const + { + return mZMax; + } + + inline Real GetZSpacing() const + { + return mZSpacing; + } + + // Evaluate the function and its derivatives. The functions clamp the + // inputs to xmin <= x <= xmax, ymin <= y <= ymax and + // zmin <= z <= zmax. The first operator is for function evaluation. + // The second operator is for function or derivative evaluations. The + // xOrder argument is the order of the x-derivative, the yOrder + // argument is the order of the y-derivative, and the zOrder argument + // is the order of the z-derivative. All orders are zero to get the + // function value itself. + Real operator()(Real x, Real y, Real z) const + { + x = std::min(std::max(x, mXMin), mXMax); + y = std::min(std::max(y, mYMin), mYMax); + z = std::min(std::max(z, mZMin), mZMax); + int ix, iy, iz; + Real dx, dy, dz; + XLookup(x, ix, dx); + YLookup(y, iy, dy); + ZLookup(z, iz, dz); + return mPoly[iz][iy][ix](dx, dy, dz); + } + + Real operator()(int xOrder, int yOrder, int zOrder, Real x, Real y, Real z) const + { + x = std::min(std::max(x, mXMin), mXMax); + y = std::min(std::max(y, mYMin), mYMax); + z = std::min(std::max(z, mZMin), mZMax); + int ix, iy, iz; + Real dx, dy, dz; + XLookup(x, ix, dx); + YLookup(y, iy, dy); + ZLookup(z, iz, dz); + return mPoly[iz][iy][ix](xOrder, yOrder, zOrder, dx, dy, dz); + } + + private: + class Polynomial + { + public: + Polynomial() + { + for (size_t ix = 0; ix < 4; ++ix) + { + for (size_t iy = 0; iy < 4; ++iy) + { + mCoeff[ix][iy].fill((Real)0); + } + } + } + + // P(x,y,z) = sum_{i=0}^3 sum_{j=0}^3 sum_{k=0}^3 a_{ijk} x^i y^j z^k. + // The tensor term A[ix][iy][iz] corresponds to the polynomial term + // x^{ix} y^{iy} z^{iz}. + Real& A(int ix, int iy, int iz) + { + return mCoeff[ix][iy][iz]; + } + + Real operator()(Real x, Real y, Real z) const + { + std::array xPow = { (Real)1, x, x * x, x * x * x }; + std::array yPow = { (Real)1, y, y * y, y * y * y }; + std::array zPow = { (Real)1, z, z * z, z * z * z }; + + Real p = (Real)0; + for (size_t iz = 0; iz <= 3; ++iz) + { + for (size_t iy = 0; iy <= 3; ++iy) + { + for (size_t ix = 0; ix <= 3; ++ix) + { + p += mCoeff[ix][iy][iz] * xPow[ix] * yPow[iy] * zPow[iz]; + } + } + } + + return p; + } + + Real operator()(int xOrder, int yOrder, int zOrder, Real x, Real y, Real z) const + { + std::array xPow; + switch (xOrder) + { + case 0: + xPow[0] = (Real)1; + xPow[1] = x; + xPow[2] = x * x; + xPow[3] = x * x * x; + break; + case 1: + xPow[0] = (Real)0; + xPow[1] = (Real)1; + xPow[2] = (Real)2 * x; + xPow[3] = (Real)3 * x * x; + break; + case 2: + xPow[0] = (Real)0; + xPow[1] = (Real)0; + xPow[2] = (Real)2; + xPow[3] = (Real)6 * x; + break; + case 3: + xPow[0] = (Real)0; + xPow[1] = (Real)0; + xPow[2] = (Real)0; + xPow[3] = (Real)6; + break; + default: + return (Real)0; + } + + std::array yPow; + switch (yOrder) + { + case 0: + yPow[0] = (Real)1; + yPow[1] = y; + yPow[2] = y * y; + yPow[3] = y * y * y; + break; + case 1: + yPow[0] = (Real)0; + yPow[1] = (Real)1; + yPow[2] = (Real)2 * y; + yPow[3] = (Real)3 * y * y; + break; + case 2: + yPow[0] = (Real)0; + yPow[1] = (Real)0; + yPow[2] = (Real)2; + yPow[3] = (Real)6 * y; + break; + case 3: + yPow[0] = (Real)0; + yPow[1] = (Real)0; + yPow[2] = (Real)0; + yPow[3] = (Real)6; + break; + default: + return (Real)0; + } + + std::array zPow; + switch (zOrder) + { + case 0: + zPow[0] = (Real)1; + zPow[1] = z; + zPow[2] = z * z; + zPow[3] = z * z * z; + break; + case 1: + zPow[0] = (Real)0; + zPow[1] = (Real)1; + zPow[2] = (Real)2 * z; + zPow[3] = (Real)3 * z * z; + break; + case 2: + zPow[0] = (Real)0; + zPow[1] = (Real)0; + zPow[2] = (Real)2; + zPow[3] = (Real)6 * z; + break; + case 3: + zPow[0] = (Real)0; + zPow[1] = (Real)0; + zPow[2] = (Real)0; + zPow[3] = (Real)6; + break; + default: + return (Real)0; + } + + Real p = (Real)0; + + for (size_t iz = 0; iz <= 3; ++iz) + { + for (size_t iy = 0; iy <= 3; ++iy) + { + for (size_t ix = 0; ix <= 3; ++ix) + { + p += mCoeff[ix][iy][iz] * xPow[ix] * yPow[iy] * zPow[iz]; + } + } + } + + return p; + } + + private: + std::array, 4>, 4> mCoeff; + }; + + // Support for construction. + void GetFX(Array3 const& F, Array3& FX) + { + Array3 slope(mXBound + 3, mYBound, mZBound); + Real invDX = (Real)1 / mXSpacing; + int ix, iy, iz; + for (iz = 0; iz < mZBound; ++iz) + { + for (iy = 0; iy < mYBound; ++iy) + { + for (ix = 0; ix < mXBound - 1; ++ix) + { + slope[iz][iy][ix + 2] = (F[iz][iy][ix + 1] - F[iz][iy][ix]) * invDX; + } + + slope[iz][iy][1] = (Real)2 * slope[iz][iy][2] - slope[iz][iy][3]; + slope[iz][iy][0] = (Real)2 * slope[iz][iy][1] - slope[iz][iy][2]; + slope[iz][iy][mXBound + 1] = (Real)2 * slope[iz][iy][mXBound] - slope[iz][iy][mXBound - 1]; + slope[iz][iy][mXBound + 2] = (Real)2 * slope[iz][iy][mXBound + 1] - slope[iz][iy][mXBound]; + } + } + + for (iz = 0; iz < mZBound; ++iz) + { + for (iy = 0; iy < mYBound; ++iy) + { + for (ix = 0; ix < mXBound; ++ix) + { + FX[iz][iy][ix] = ComputeDerivative(slope[iz][iy] + ix); + } + } + } + } + + void GetFY(Array3 const& F, Array3& FY) + { + Array3 slope(mYBound + 3, mXBound, mZBound); + Real invDY = (Real)1 / mYSpacing; + int ix, iy, iz; + for (iz = 0; iz < mZBound; ++iz) + { + for (ix = 0; ix < mXBound; ++ix) + { + for (iy = 0; iy < mYBound - 1; ++iy) + { + slope[iz][ix][iy + 2] = (F[iz][iy + 1][ix] - F[iz][iy][ix]) * invDY; + } + + slope[iz][ix][1] = (Real)2 * slope[iz][ix][2] - slope[iz][ix][3]; + slope[iz][ix][0] = (Real)2 * slope[iz][ix][1] - slope[iz][ix][2]; + slope[iz][ix][mYBound + 1] = (Real)2 * slope[iz][ix][mYBound] - slope[iz][ix][mYBound - 1]; + slope[iz][ix][mYBound + 2] = (Real)2 * slope[iz][ix][mYBound + 1] - slope[iz][ix][mYBound]; + } + } + + for (iz = 0; iz < mZBound; ++iz) + { + for (ix = 0; ix < mXBound; ++ix) + { + for (iy = 0; iy < mYBound; ++iy) + { + FY[iz][iy][ix] = ComputeDerivative(slope[iz][ix] + iy); + } + } + } + } + + void GetFZ(Array3 const& F, Array3& FZ) + { + Array3 slope(mZBound + 3, mXBound, mYBound); + Real invDZ = (Real)1 / mZSpacing; + int ix, iy, iz; + for (iy = 0; iy < mYBound; ++iy) + { + for (ix = 0; ix < mXBound; ++ix) + { + for (iz = 0; iz < mZBound - 1; ++iz) + { + slope[iy][ix][iz + 2] = (F[iz + 1][iy][ix] - F[iz][iy][ix]) * invDZ; + } + + slope[iy][ix][1] = (Real)2 * slope[iy][ix][2] - slope[iy][ix][3]; + slope[iy][ix][0] = (Real)2 * slope[iy][ix][1] - slope[iy][ix][2]; + slope[iy][ix][mZBound + 1] = (Real)2 * slope[iy][ix][mZBound] - slope[iy][ix][mZBound - 1]; + slope[iy][ix][mZBound + 2] = (Real)2 * slope[iy][ix][mZBound + 1] - slope[iy][ix][mZBound]; + } + } + + for (iy = 0; iy < mYBound; ++iy) + { + for (ix = 0; ix < mXBound; ++ix) + { + for (iz = 0; iz < mZBound; ++iz) + { + FZ[iz][iy][ix] = ComputeDerivative(slope[iy][ix] + iz); + } + } + } + } + + void GetFXY(Array3 const& F, Array3& FXY) + { + int xBoundM1 = mXBound - 1; + int yBoundM1 = mYBound - 1; + int ix0 = xBoundM1, ix1 = ix0 - 1, ix2 = ix1 - 1; + int iy0 = yBoundM1, iy1 = iy0 - 1, iy2 = iy1 - 1; + int ix, iy, iz; + + Real invDXDY = (Real)1 / (mXSpacing * mYSpacing); + for (iz = 0; iz < mZBound; ++iz) + { + // corners of z-slice + FXY[iz][0][0] = (Real)0.25 * invDXDY * ( + (Real)9 * F[iz][0][0] + - (Real)12 * F[iz][0][1] + + (Real)3 * F[iz][0][2] + - (Real)12 * F[iz][1][0] + + (Real)16 * F[iz][1][1] + - (Real)4 * F[iz][1][2] + + (Real)3 * F[iz][2][0] + - (Real)4 * F[iz][2][1] + + F[iz][2][2]); + + FXY[iz][0][xBoundM1] = (Real)0.25 * invDXDY * ( + (Real)9 * F[iz][0][ix0] + - (Real)12 * F[iz][0][ix1] + + (Real)3 * F[iz][0][ix2] + - (Real)12 * F[iz][1][ix0] + + (Real)16 * F[iz][1][ix1] + - (Real)4 * F[iz][1][ix2] + + (Real)3 * F[iz][2][ix0] + - (Real)4 * F[iz][2][ix1] + + F[iz][2][ix2]); + + FXY[iz][yBoundM1][0] = (Real)0.25 * invDXDY * ( + (Real)9 * F[iz][iy0][0] + - (Real)12 * F[iz][iy0][1] + + (Real)3 * F[iz][iy0][2] + - (Real)12 * F[iz][iy1][0] + + (Real)16 * F[iz][iy1][1] + - (Real)4 * F[iz][iy1][2] + + (Real)3 * F[iz][iy2][0] + - (Real)4 * F[iz][iy2][1] + + F[iz][iy2][2]); + + FXY[iz][yBoundM1][xBoundM1] = (Real)0.25 * invDXDY * ( + (Real)9 * F[iz][iy0][ix0] + - (Real)12 * F[iz][iy0][ix1] + + (Real)3 * F[iz][iy0][ix2] + - (Real)12 * F[iz][iy1][ix0] + + (Real)16 * F[iz][iy1][ix1] + - (Real)4 * F[iz][iy1][ix2] + + (Real)3 * F[iz][iy2][ix0] + - (Real)4 * F[iz][iy2][ix1] + + F[iz][iy2][ix2]); + + // x-edges of z-slice + for (ix = 1; ix < xBoundM1; ++ix) + { + FXY[iz][0][ix] = (Real)0.25 * invDXDY * ( + (Real)3 * (F[iz][0][ix - 1] - F[iz][0][ix + 1]) - + (Real)4 * (F[iz][1][ix - 1] - F[iz][1][ix + 1]) + + (F[iz][2][ix - 1] - F[iz][2][ix + 1])); + + FXY[iz][yBoundM1][ix] = (Real)0.25 * invDXDY * ( + (Real)3 * (F[iz][iy0][ix - 1] - F[iz][iy0][ix + 1]) + - (Real)4 * (F[iz][iy1][ix - 1] - F[iz][iy1][ix + 1]) + + (F[iz][iy2][ix - 1] - F[iz][iy2][ix + 1])); + } + + // y-edges of z-slice + for (iy = 1; iy < yBoundM1; ++iy) + { + FXY[iz][iy][0] = (Real)0.25 * invDXDY * ( + (Real)3 * (F[iz][iy - 1][0] - F[iz][iy + 1][0]) - + (Real)4 * (F[iz][iy - 1][1] - F[iz][iy + 1][1]) + + (F[iz][iy - 1][2] - F[iz][iy + 1][2])); + + FXY[iz][iy][xBoundM1] = (Real)0.25 * invDXDY * ( + (Real)3 * (F[iz][iy - 1][ix0] - F[iz][iy + 1][ix0]) + - (Real)4 * (F[iz][iy - 1][ix1] - F[iz][iy + 1][ix1]) + + (F[iz][iy - 1][ix2] - F[iz][iy + 1][ix2])); + } + + // interior of z-slice + for (iy = 1; iy < yBoundM1; ++iy) + { + for (ix = 1; ix < xBoundM1; ++ix) + { + FXY[iz][iy][ix] = (Real)0.25 * invDXDY * ( + F[iz][iy - 1][ix - 1] - F[iz][iy - 1][ix + 1] - + F[iz][iy + 1][ix - 1] + F[iz][iy + 1][ix + 1]); + } + } + } + } + + void GetFXZ(Array3 const& F, Array3 & FXZ) + { + int xBoundM1 = mXBound - 1; + int zBoundM1 = mZBound - 1; + int ix0 = xBoundM1, ix1 = ix0 - 1, ix2 = ix1 - 1; + int iz0 = zBoundM1, iz1 = iz0 - 1, iz2 = iz1 - 1; + int ix, iy, iz; + + Real invDXDZ = (Real)1 / (mXSpacing * mZSpacing); + for (iy = 0; iy < mYBound; ++iy) + { + // corners of z-slice + FXZ[0][iy][0] = (Real)0.25 * invDXDZ * ( + (Real)9 * F[0][iy][0] + - (Real)12 * F[0][iy][1] + + (Real)3 * F[0][iy][2] + - (Real)12 * F[1][iy][0] + + (Real)16 * F[1][iy][1] + - (Real)4 * F[1][iy][2] + + (Real)3 * F[2][iy][0] + - (Real)4 * F[2][iy][1] + + F[2][iy][2]); + + FXZ[0][iy][xBoundM1] = (Real)0.25 * invDXDZ * ( + (Real)9 * F[0][iy][ix0] + - (Real)12 * F[0][iy][ix1] + + (Real)3 * F[0][iy][ix2] + - (Real)12 * F[1][iy][ix0] + + (Real)16 * F[1][iy][ix1] + - (Real)4 * F[1][iy][ix2] + + (Real)3 * F[2][iy][ix0] + - (Real)4 * F[2][iy][ix1] + + F[2][iy][ix2]); + + FXZ[zBoundM1][iy][0] = (Real)0.25 * invDXDZ * ( + (Real)9 * F[iz0][iy][0] + - (Real)12 * F[iz0][iy][1] + + (Real)3 * F[iz0][iy][2] + - (Real)12 * F[iz1][iy][0] + + (Real)16 * F[iz1][iy][1] + - (Real)4 * F[iz1][iy][2] + + (Real)3 * F[iz2][iy][0] + - (Real)4 * F[iz2][iy][1] + + F[iz2][iy][2]); + + FXZ[zBoundM1][iy][xBoundM1] = (Real)0.25 * invDXDZ * ( + (Real)9 * F[iz0][iy][ix0] + - (Real)12 * F[iz0][iy][ix1] + + (Real)3 * F[iz0][iy][ix2] + - (Real)12 * F[iz1][iy][ix0] + + (Real)16 * F[iz1][iy][ix1] + - (Real)4 * F[iz1][iy][ix2] + + (Real)3 * F[iz2][iy][ix0] + - (Real)4 * F[iz2][iy][ix1] + + F[iz2][iy][ix2]); + + // x-edges of y-slice + for (ix = 1; ix < xBoundM1; ++ix) + { + FXZ[0][iy][ix] = (Real)0.25 * invDXDZ * ( + (Real)3 * (F[0][iy][ix - 1] - F[0][iy][ix + 1]) - + (Real)4 * (F[1][iy][ix - 1] - F[1][iy][ix + 1]) + + (F[2][iy][ix - 1] - F[2][iy][ix + 1])); + + FXZ[zBoundM1][iy][ix] = (Real)0.25 * invDXDZ * ( + (Real)3 * (F[iz0][iy][ix - 1] - F[iz0][iy][ix + 1]) + - (Real)4 * (F[iz1][iy][ix - 1] - F[iz1][iy][ix + 1]) + + (F[iz2][iy][ix - 1] - F[iz2][iy][ix + 1])); + } + + // z-edges of y-slice + for (iz = 1; iz < zBoundM1; ++iz) + { + FXZ[iz][iy][0] = (Real)0.25 * invDXDZ * ( + (Real)3 * (F[iz - 1][iy][0] - F[iz + 1][iy][0]) - + (Real)4 * (F[iz - 1][iy][1] - F[iz + 1][iy][1]) + + (F[iz - 1][iy][2] - F[iz + 1][iy][2])); + + FXZ[iz][iy][xBoundM1] = (Real)0.25 * invDXDZ * ( + (Real)3 * (F[iz - 1][iy][ix0] - F[iz + 1][iy][ix0]) + - (Real)4 * (F[iz - 1][iy][ix1] - F[iz + 1][iy][ix1]) + + (F[iz - 1][iy][ix2] - F[iz + 1][iy][ix2])); + } + + // interior of y-slice + for (iz = 1; iz < zBoundM1; ++iz) + { + for (ix = 1; ix < xBoundM1; ++ix) + { + FXZ[iz][iy][ix] = ((Real)0.25) * invDXDZ * ( + F[iz - 1][iy][ix - 1] - F[iz - 1][iy][ix + 1] - + F[iz + 1][iy][ix - 1] + F[iz + 1][iy][ix + 1]); + } + } + } + } + + void GetFYZ(Array3 const& F, Array3 & FYZ) + { + int yBoundM1 = mYBound - 1; + int zBoundM1 = mZBound - 1; + int iy0 = yBoundM1, iy1 = iy0 - 1, iy2 = iy1 - 1; + int iz0 = zBoundM1, iz1 = iz0 - 1, iz2 = iz1 - 1; + int ix, iy, iz; + + Real invDYDZ = (Real)1 / (mYSpacing * mZSpacing); + for (ix = 0; ix < mXBound; ++ix) + { + // corners of x-slice + FYZ[0][0][ix] = (Real)0.25 * invDYDZ * ( + (Real)9 * F[0][0][ix] + - (Real)12 * F[0][1][ix] + + (Real)3 * F[0][2][ix] + - (Real)12 * F[1][0][ix] + + (Real)16 * F[1][1][ix] + - (Real)4 * F[1][2][ix] + + (Real)3 * F[2][0][ix] + - (Real)4 * F[2][1][ix] + + F[2][2][ix]); + + FYZ[0][yBoundM1][ix] = (Real)0.25 * invDYDZ * ( + (Real)9 * F[0][iy0][ix] + - (Real)12 * F[0][iy1][ix] + + (Real)3 * F[0][iy2][ix] + - (Real)12 * F[1][iy0][ix] + + (Real)16 * F[1][iy1][ix] + - (Real)4 * F[1][iy2][ix] + + (Real)3 * F[2][iy0][ix] + - (Real)4 * F[2][iy1][ix] + + F[2][iy2][ix]); + + FYZ[zBoundM1][0][ix] = (Real)0.25 * invDYDZ * ( + (Real)9 * F[iz0][0][ix] + - (Real)12 * F[iz0][1][ix] + + (Real)3 * F[iz0][2][ix] + - (Real)12 * F[iz1][0][ix] + + (Real)16 * F[iz1][1][ix] + - (Real)4 * F[iz1][2][ix] + + (Real)3 * F[iz2][0][ix] + - (Real)4 * F[iz2][1][ix] + + F[iz2][2][ix]); + + FYZ[zBoundM1][yBoundM1][ix] = (Real)0.25 * invDYDZ * ( + (Real)9 * F[iz0][iy0][ix] + - (Real)12 * F[iz0][iy1][ix] + + (Real)3 * F[iz0][iy2][ix] + - (Real)12 * F[iz1][iy0][ix] + + (Real)16 * F[iz1][iy1][ix] + - (Real)4 * F[iz1][iy2][ix] + + (Real)3 * F[iz2][iy0][ix] + - (Real)4 * F[iz2][iy1][ix] + + F[iz2][iy2][ix]); + + // y-edges of x-slice + for (iy = 1; iy < yBoundM1; ++iy) + { + FYZ[0][iy][ix] = (Real)0.25 * invDYDZ * ( + (Real)3 * (F[0][iy - 1][ix] - F[0][iy + 1][ix]) - + (Real)4 * (F[1][iy - 1][ix] - F[1][iy + 1][ix]) + + (F[2][iy - 1][ix] - F[2][iy + 1][ix])); + + FYZ[zBoundM1][iy][ix] = (Real)0.25 * invDYDZ * ( + (Real)3 * (F[iz0][iy - 1][ix] - F[iz0][iy + 1][ix]) + - (Real)4 * (F[iz1][iy - 1][ix] - F[iz1][iy + 1][ix]) + + (F[iz2][iy - 1][ix] - F[iz2][iy + 1][ix])); + } + + // z-edges of x-slice + for (iz = 1; iz < zBoundM1; ++iz) + { + FYZ[iz][0][ix] = (Real)0.25 * invDYDZ * ( + (Real)3 * (F[iz - 1][0][ix] - F[iz + 1][0][ix]) - + (Real)4 * (F[iz - 1][1][ix] - F[iz + 1][1][ix]) + + (F[iz - 1][2][ix] - F[iz + 1][2][ix])); + + FYZ[iz][yBoundM1][ix] = (Real)0.25 * invDYDZ * ( + (Real)3 * (F[iz - 1][iy0][ix] - F[iz + 1][iy0][ix]) + - (Real)4 * (F[iz - 1][iy1][ix] - F[iz + 1][iy1][ix]) + + (F[iz - 1][iy2][ix] - F[iz + 1][iy2][ix])); + } + + // interior of x-slice + for (iz = 1; iz < zBoundM1; ++iz) + { + for (iy = 1; iy < yBoundM1; ++iy) + { + FYZ[iz][iy][ix] = (Real)0.25 * invDYDZ * ( + F[iz - 1][iy - 1][ix] - F[iz - 1][iy + 1][ix] - + F[iz + 1][iy - 1][ix] + F[iz + 1][iy + 1][ix]); + } + } + } + } + + void GetFXYZ(Array3 const& F, Array3 & FXYZ) + { + int xBoundM1 = mXBound - 1; + int yBoundM1 = mYBound - 1; + int zBoundM1 = mZBound - 1; + int ix, iy, iz, ix0, iy0, iz0; + + Real invDXDYDZ = ((Real)1) / (mXSpacing * mYSpacing * mZSpacing); + + // convolution masks + // centered difference, O(h^2) + Real CDer[3] = { -(Real)0.5, (Real)0, (Real)0.5 }; + // one-sided difference, O(h^2) + Real ODer[3] = { -(Real)1.5, (Real)2, -(Real)0.5 }; + Real mask; + + // corners + FXYZ[0][0][0] = (Real)0; + FXYZ[0][0][xBoundM1] = (Real)0; + FXYZ[0][yBoundM1][0] = (Real)0; + FXYZ[0][yBoundM1][xBoundM1] = (Real)0; + FXYZ[zBoundM1][0][0] = (Real)0; + FXYZ[zBoundM1][0][xBoundM1] = (Real)0; + FXYZ[zBoundM1][yBoundM1][0] = (Real)0; + FXYZ[zBoundM1][yBoundM1][xBoundM1] = (Real)0; + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ * ODer[ix] * ODer[iy] * ODer[iz]; + FXYZ[0][0][0] += mask * F[iz][iy][ix]; + FXYZ[0][0][xBoundM1] += mask * F[iz][iy][xBoundM1 - ix]; + FXYZ[0][yBoundM1][0] += mask * F[iz][yBoundM1 - iy][ix]; + FXYZ[0][yBoundM1][xBoundM1] += mask * F[iz][yBoundM1 - iy][xBoundM1 - ix]; + FXYZ[zBoundM1][0][0] += mask * F[zBoundM1 - iz][iy][ix]; + FXYZ[zBoundM1][0][xBoundM1] += mask * F[zBoundM1 - iz][iy][xBoundM1 - ix]; + FXYZ[zBoundM1][yBoundM1][0] += mask * F[zBoundM1 - iz][yBoundM1 - iy][ix]; + FXYZ[zBoundM1][yBoundM1][xBoundM1] += mask * F[zBoundM1 - iz][yBoundM1 - iy][xBoundM1 - ix]; + } + } + } + + // x-edges + for (ix0 = 1; ix0 < xBoundM1; ++ix0) + { + FXYZ[0][0][ix0] = (Real)0; + FXYZ[0][yBoundM1][ix0] = (Real)0; + FXYZ[zBoundM1][0][ix0] = (Real)0; + FXYZ[zBoundM1][yBoundM1][ix0] = (Real)0; + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ * CDer[ix] * ODer[iy] * ODer[iz]; + FXYZ[0][0][ix0] += mask * F[iz][iy][ix0 + ix - 1]; + FXYZ[0][yBoundM1][ix0] += mask * F[iz][yBoundM1 - iy][ix0 + ix - 1]; + FXYZ[zBoundM1][0][ix0] += mask * F[zBoundM1 - iz][iy][ix0 + ix - 1]; + FXYZ[zBoundM1][yBoundM1][ix0] += mask * F[zBoundM1 - iz][yBoundM1 - iy][ix0 + ix - 1]; + } + } + } + } + + // y-edges + for (iy0 = 1; iy0 < yBoundM1; ++iy0) + { + FXYZ[0][iy0][0] = (Real)0; + FXYZ[0][iy0][xBoundM1] = (Real)0; + FXYZ[zBoundM1][iy0][0] = (Real)0; + FXYZ[zBoundM1][iy0][xBoundM1] = (Real)0; + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ * ODer[ix] * CDer[iy] * ODer[iz]; + FXYZ[0][iy0][0] += mask * F[iz][iy0 + iy - 1][ix]; + FXYZ[0][iy0][xBoundM1] += mask * F[iz][iy0 + iy - 1][xBoundM1 - ix]; + FXYZ[zBoundM1][iy0][0] += mask * F[zBoundM1 - iz][iy0 + iy - 1][ix]; + FXYZ[zBoundM1][iy0][xBoundM1] += mask * F[zBoundM1 - iz][iy0 + iy - 1][xBoundM1 - ix]; + } + } + } + } + + // z-edges + for (iz0 = 1; iz0 < zBoundM1; ++iz0) + { + FXYZ[iz0][0][0] = (Real)0; + FXYZ[iz0][0][xBoundM1] = (Real)0; + FXYZ[iz0][yBoundM1][0] = (Real)0; + FXYZ[iz0][yBoundM1][xBoundM1] = (Real)0; + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ * ODer[ix] * ODer[iy] * CDer[iz]; + FXYZ[iz0][0][0] += mask * F[iz0 + iz - 1][iy][ix]; + FXYZ[iz0][0][xBoundM1] += mask * F[iz0 + iz - 1][iy][xBoundM1 - ix]; + FXYZ[iz0][yBoundM1][0] += mask * F[iz0 + iz - 1][yBoundM1 - iy][ix]; + FXYZ[iz0][yBoundM1][xBoundM1] += mask * F[iz0 + iz - 1][yBoundM1 - iy][xBoundM1 - ix]; + } + } + } + } + + // xy-faces + for (iy0 = 1; iy0 < yBoundM1; ++iy0) + { + for (ix0 = 1; ix0 < xBoundM1; ++ix0) + { + FXYZ[0][iy0][ix0] = (Real)0; + FXYZ[zBoundM1][iy0][ix0] = (Real)0; + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ * CDer[ix] * CDer[iy] * ODer[iz]; + FXYZ[0][iy0][ix0] += mask * F[iz][iy0 + iy - 1][ix0 + ix - 1]; + FXYZ[zBoundM1][iy0][ix0] += mask * F[zBoundM1 - iz][iy0 + iy - 1][ix0 + ix - 1]; + } + } + } + } + } + + // xz-faces + for (iz0 = 1; iz0 < zBoundM1; ++iz0) + { + for (ix0 = 1; ix0 < xBoundM1; ++ix0) + { + FXYZ[iz0][0][ix0] = (Real)0; + FXYZ[iz0][yBoundM1][ix0] = (Real)0; + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ * CDer[ix] * ODer[iy] * CDer[iz]; + FXYZ[iz0][0][ix0] += mask * F[iz0 + iz - 1][iy][ix0 + ix - 1]; + FXYZ[iz0][yBoundM1][ix0] += mask * F[iz0 + iz - 1][yBoundM1 - iy][ix0 + ix - 1]; + } + } + } + } + } + + // yz-faces + for (iz0 = 1; iz0 < zBoundM1; ++iz0) + { + for (iy0 = 1; iy0 < yBoundM1; ++iy0) + { + FXYZ[iz0][iy0][0] = (Real)0; + FXYZ[iz0][iy0][xBoundM1] = (Real)0; + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ * ODer[ix] * CDer[iy] * CDer[iz]; + FXYZ[iz0][iy0][0] += mask * F[iz0 + iz - 1][iy0 + iy - 1][ix]; + FXYZ[iz0][iy0][xBoundM1] += mask * F[iz0 + iz - 1][iy0 + iy - 1][xBoundM1 - ix]; + } + } + } + } + } + + // interiors + for (iz0 = 1; iz0 < zBoundM1; ++iz0) + { + for (iy0 = 1; iy0 < yBoundM1; ++iy0) + { + for (ix0 = 1; ix0 < xBoundM1; ++ix0) + { + FXYZ[iz0][iy0][ix0] = (Real)0; + + for (iz = 0; iz <= 2; ++iz) + { + for (iy = 0; iy <= 2; ++iy) + { + for (ix = 0; ix <= 2; ++ix) + { + mask = invDXDYDZ * CDer[ix] * CDer[iy] * CDer[iz]; + FXYZ[iz0][iy0][ix0] += mask * F[iz0 + iz - 1][iy0 + iy - 1][ix0 + ix - 1]; + } + } + } + } + } + } + } + + void GetPolynomials(Array3 const& F, Array3 const& FX, + Array3 const& FY, Array3 const& FZ, Array3 const& FXY, + Array3 const& FXZ, Array3 const& FYZ, Array3 const& FXYZ) + { + int xBoundM1 = mXBound - 1; + int yBoundM1 = mYBound - 1; + int zBoundM1 = mZBound - 1; + for (int iz = 0; iz < zBoundM1; ++iz) + { + for (int iy = 0; iy < yBoundM1; ++iy) + { + for (int ix = 0; ix < xBoundM1; ++ix) + { + // Note the 'transposing' of the 2x2x2 blocks (to match + // notation used in the polynomial definition). + Real G[2][2][2] = + { + { + { + F[iz][iy][ix], + F[iz + 1][iy][ix] + }, + { + F[iz][iy + 1][ix], + F[iz + 1][iy + 1][ix] + } + }, + { + { + F[iz][iy][ix + 1], + F[iz + 1][iy][ix + 1] + }, + { + F[iz][iy + 1][ix + 1], + F[iz + 1][iy + 1][ix + 1] + } + } + }; + + Real GX[2][2][2] = + { + { + { + FX[iz][iy][ix], + FX[iz + 1][iy][ix] + }, + { + FX[iz][iy + 1][ix], + FX[iz + 1][iy + 1][ix] + } + }, + { + { + FX[iz][iy][ix + 1], + FX[iz + 1][iy][ix + 1] + }, + { + FX[iz][iy + 1][ix + 1], + FX[iz + 1][iy + 1][ix + 1] + } + } + }; + + Real GY[2][2][2] = + { + { + { + FY[iz][iy][ix], + FY[iz + 1][iy][ix] + }, + { + FY[iz][iy + 1][ix], + FY[iz + 1][iy + 1][ix] + } + }, + { + { + FY[iz][iy][ix + 1], + FY[iz + 1][iy][ix + 1] + }, + { + FY[iz][iy + 1][ix + 1], + FY[iz + 1][iy + 1][ix + 1] + } + } + }; + + Real GZ[2][2][2] = + { + { + { + FZ[iz][iy][ix], + FZ[iz + 1][iy][ix] + }, + { + FZ[iz][iy + 1][ix], + FZ[iz + 1][iy + 1][ix] + } + }, + { + { + FZ[iz][iy][ix + 1], + FZ[iz + 1][iy][ix + 1] + }, + { + FZ[iz][iy + 1][ix + 1], + FZ[iz + 1][iy + 1][ix + 1] + } + } + }; + + Real GXY[2][2][2] = + { + { + { + FXY[iz][iy][ix], + FXY[iz + 1][iy][ix] + }, + { + FXY[iz][iy + 1][ix], + FXY[iz + 1][iy + 1][ix] + } + }, + { + { + FXY[iz][iy][ix + 1], + FXY[iz + 1][iy][ix + 1] + }, + { + FXY[iz][iy + 1][ix + 1], + FXY[iz + 1][iy + 1][ix + 1] + } + } + }; + + Real GXZ[2][2][2] = + { + { + { + FXZ[iz][iy][ix], + FXZ[iz + 1][iy][ix] + }, + { + FXZ[iz][iy + 1][ix], + FXZ[iz + 1][iy + 1][ix] + } + }, + { + { + FXZ[iz][iy][ix + 1], + FXZ[iz + 1][iy][ix + 1] + }, + { + FXZ[iz][iy + 1][ix + 1], + FXZ[iz + 1][iy + 1][ix + 1] + } + } + }; + + Real GYZ[2][2][2] = + { + { + { + FYZ[iz][iy][ix], + FYZ[iz + 1][iy][ix] + }, + { + FYZ[iz][iy + 1][ix], + FYZ[iz + 1][iy + 1][ix] + } + }, + { + { + FYZ[iz][iy][ix + 1], + FYZ[iz + 1][iy][ix + 1] + }, + { + FYZ[iz][iy + 1][ix + 1], + FYZ[iz + 1][iy + 1][ix + 1] + } + } + }; + + Real GXYZ[2][2][2] = + { + { + { + FXYZ[iz][iy][ix], + FXYZ[iz + 1][iy][ix] + }, + { + FXYZ[iz][iy + 1][ix], + FXYZ[iz + 1][iy + 1][ix] + } + }, + { + { + FXYZ[iz][iy][ix + 1], + FXYZ[iz + 1][iy][ix + 1] + }, + { + FXYZ[iz][iy + 1][ix + 1], + FXYZ[iz + 1][iy + 1][ix + 1] + } + } + }; + + Construct(mPoly[iz][iy][ix], G, GX, GY, GZ, GXY, GXZ, GYZ, GXYZ); + } + } + } + } + + Real ComputeDerivative(Real const* slope) const + { + if (slope[1] != slope[2]) + { + if (slope[0] != slope[1]) + { + if (slope[2] != slope[3]) + { + Real ad0 = std::fabs(slope[3] - slope[2]); + Real ad1 = std::fabs(slope[0] - slope[1]); + return (ad0 * slope[1] + ad1 * slope[2]) / (ad0 + ad1); + } + else + { + return slope[2]; + } + } + else + { + if (slope[2] != slope[3]) + { + return slope[1]; + } + else + { + return (Real)0.5 * (slope[1] + slope[2]); + } + } + } + else + { + return slope[1]; + } + } + + void Construct(Polynomial& poly, + Real const F[2][2][2], Real const FX[2][2][2], Real const FY[2][2][2], + Real const FZ[2][2][2], Real const FXY[2][2][2], Real const FXZ[2][2][2], + Real const FYZ[2][2][2], Real const FXYZ[2][2][2]) + { + Real dx = mXSpacing, dy = mYSpacing, dz = mZSpacing; + Real invDX = (Real)1 / dx, invDX2 = invDX * invDX; + Real invDY = (Real)1 / dy, invDY2 = invDY * invDY; + Real invDZ = (Real)1 / dz, invDZ2 = invDZ * invDZ; + Real b0, b1, b2, b3, b4, b5, b6, b7; + + poly.A(0, 0, 0) = F[0][0][0]; + poly.A(1, 0, 0) = FX[0][0][0]; + poly.A(0, 1, 0) = FY[0][0][0]; + poly.A(0, 0, 1) = FZ[0][0][0]; + poly.A(1, 1, 0) = FXY[0][0][0]; + poly.A(1, 0, 1) = FXZ[0][0][0]; + poly.A(0, 1, 1) = FYZ[0][0][0]; + poly.A(1, 1, 1) = FXYZ[0][0][0]; + + // solve for Aij0 + b0 = (F[1][0][0] - poly(0, 0, 0, dx, (Real)0, (Real)0)) * invDX2; + b1 = (FX[1][0][0] - poly(1, 0, 0, dx, (Real)0, (Real)0)) * invDX; + poly.A(2, 0, 0) = (Real)3 * b0 - b1; + poly.A(3, 0, 0) = ((Real)-2 * b0 + b1) * invDX; + + b0 = (F[0][1][0] - poly(0, 0, 0, (Real)0, dy, (Real)0)) * invDY2; + b1 = (FY[0][1][0] - poly(0, 1, 0, (Real)0, dy, (Real)0)) * invDY; + poly.A(0, 2, 0) = (Real)3 * b0 - b1; + poly.A(0, 3, 0) = ((Real)-2 * b0 + b1) * invDY; + + b0 = (FY[1][0][0] - poly(0, 1, 0, dx, (Real)0, (Real)0)) * invDX2; + b1 = (FXY[1][0][0] - poly(1, 1, 0, dx, (Real)0, (Real)0)) * invDX; + poly.A(2, 1, 0) = (Real)3 * b0 - b1; + poly.A(3, 1, 0) = ((Real)-2 * b0 + b1) * invDX; + + b0 = (FX[0][1][0] - poly(1, 0, 0, (Real)0, dy, (Real)0)) * invDY2; + b1 = (FXY[0][1][0] - poly(1, 1, 0, (Real)0, dy, (Real)0)) * invDY; + poly.A(1, 2, 0) = (Real)3 * b0 - b1; + poly.A(1, 3, 0) = ((Real)-2 * b0 + b1) * invDY; + + b0 = (F[1][1][0] - poly(0, 0, 0, dx, dy, (Real)0)) * invDX2 * invDY2; + b1 = (FX[1][1][0] - poly(1, 0, 0, dx, dy, (Real)0)) * invDX * invDY2; + b2 = (FY[1][1][0] - poly(0, 1, 0, dx, dy, (Real)0)) * invDX2 * invDY; + b3 = (FXY[1][1][0] - poly(1, 1, 0, dx, dy, (Real)0)) * invDX * invDY; + poly.A(2, 2, 0) = (Real)9 * b0 - (Real)3 * b1 - (Real)3 * b2 + b3; + poly.A(3, 2, 0) = ((Real)-6 * b0 + (Real)3 * b1 + (Real)2 * b2 - b3) * invDX; + poly.A(2, 3, 0) = ((Real)-6 * b0 + (Real)2 * b1 + (Real)3 * b2 - b3) * invDY; + poly.A(3, 3, 0) = ((Real)4 * b0 - (Real)2 * b1 - (Real)2 * b2 + b3) * invDX * invDY; + + // solve for Ai0k + b0 = (F[0][0][1] - poly(0, 0, 0, (Real)0, (Real)0, dz)) * invDZ2; + b1 = (FZ[0][0][1] - poly(0, 0, 1, (Real)0, (Real)0, dz)) * invDZ; + poly.A(0, 0, 2) = (Real)3 * b0 - b1; + poly.A(0, 0, 3) = ((Real)-2 * b0 + b1) * invDZ; + + b0 = (FZ[1][0][0] - poly(0, 0, 1, dx, (Real)0, (Real)0)) * invDX2; + b1 = (FXZ[1][0][0] - poly(1, 0, 1, dx, (Real)0, (Real)0)) * invDX; + poly.A(2, 0, 1) = (Real)3 * b0 - b1; + poly.A(3, 0, 1) = ((Real)-2 * b0 + b1) * invDX; + + b0 = (FX[0][0][1] - poly(1, 0, 0, (Real)0, (Real)0, dz)) * invDZ2; + b1 = (FXZ[0][0][1] - poly(1, 0, 1, (Real)0, (Real)0, dz)) * invDZ; + poly.A(1, 0, 2) = (Real)3 * b0 - b1; + poly.A(1, 0, 3) = ((Real)-2 * b0 + b1) * invDZ; + + b0 = (F[1][0][1] - poly(0, 0, 0, dx, (Real)0, dz)) * invDX2 * invDZ2; + b1 = (FX[1][0][1] - poly(1, 0, 0, dx, (Real)0, dz)) * invDX * invDZ2; + b2 = (FZ[1][0][1] - poly(0, 0, 1, dx, (Real)0, dz)) * invDX2 * invDZ; + b3 = (FXZ[1][0][1] - poly(1, 0, 1, dx, (Real)0, dz)) * invDX * invDZ; + poly.A(2, 0, 2) = (Real)9 * b0 - (Real)3 * b1 - (Real)3 * b2 + b3; + poly.A(3, 0, 2) = ((Real)-6 * b0 + (Real)3 * b1 + (Real)2 * b2 - b3) * invDX; + poly.A(2, 0, 3) = ((Real)-6 * b0 + (Real)2 * b1 + (Real)3 * b2 - b3) * invDZ; + poly.A(3, 0, 3) = ((Real)4 * b0 - (Real)2 * b1 - (Real)2 * b2 + b3) * invDX * invDZ; + + // solve for A0jk + b0 = (FZ[0][1][0] - poly(0, 0, 1, (Real)0, dy, (Real)0)) * invDY2; + b1 = (FYZ[0][1][0] - poly(0, 1, 1, (Real)0, dy, (Real)0)) * invDY; + poly.A(0, 2, 1) = (Real)3 * b0 - b1; + poly.A(0, 3, 1) = ((Real)-2 * b0 + b1) * invDY; + + b0 = (FY[0][0][1] - poly(0, 1, 0, (Real)0, (Real)0, dz)) * invDZ2; + b1 = (FYZ[0][0][1] - poly(0, 1, 1, (Real)0, (Real)0, dz)) * invDZ; + poly.A(0, 1, 2) = (Real)3 * b0 - b1; + poly.A(0, 1, 3) = ((Real)-2 * b0 + b1) * invDZ; + + b0 = (F[0][1][1] - poly(0, 0, 0, (Real)0, dy, dz)) * invDY2 * invDZ2; + b1 = (FY[0][1][1] - poly(0, 1, 0, (Real)0, dy, dz)) * invDY * invDZ2; + b2 = (FZ[0][1][1] - poly(0, 0, 1, (Real)0, dy, dz)) * invDY2 * invDZ; + b3 = (FYZ[0][1][1] - poly(0, 1, 1, (Real)0, dy, dz)) * invDY * invDZ; + poly.A(0, 2, 2) = (Real)9 * b0 - (Real)3 * b1 - (Real)3 * b2 + b3; + poly.A(0, 3, 2) = ((Real)-6 * b0 + (Real)3 * b1 + (Real)2 * b2 - b3) * invDY; + poly.A(0, 2, 3) = ((Real)-6 * b0 + (Real)2 * b1 + (Real)3 * b2 - b3) * invDZ; + poly.A(0, 3, 3) = ((Real)4 * b0 - (Real)2 * b1 - (Real)2 * b2 + b3) * invDY * invDZ; + + // solve for Aij1 + b0 = (FYZ[1][0][0] - poly(0, 1, 1, dx, (Real)0, (Real)0)) * invDX2; + b1 = (FXYZ[1][0][0] - poly(1, 1, 1, dx, (Real)0, (Real)0)) * invDX; + poly.A(2, 1, 1) = (Real)3 * b0 - b1; + poly.A(3, 1, 1) = ((Real)-2 * b0 + b1) * invDX; + + b0 = (FXZ[0][1][0] - poly(1, 0, 1, (Real)0, dy, (Real)0)) * invDY2; + b1 = (FXYZ[0][1][0] - poly(1, 1, 1, (Real)0, dy, (Real)0)) * invDY; + poly.A(1, 2, 1) = (Real)3 * b0 - b1; + poly.A(1, 3, 1) = ((Real)-2 * b0 + b1) * invDY; + + b0 = (FZ[1][1][0] - poly(0, 0, 1, dx, dy, (Real)0)) * invDX2 * invDY2; + b1 = (FXZ[1][1][0] - poly(1, 0, 1, dx, dy, (Real)0)) * invDX * invDY2; + b2 = (FYZ[1][1][0] - poly(0, 1, 1, dx, dy, (Real)0)) * invDX2 * invDY; + b3 = (FXYZ[1][1][0] - poly(1, 1, 1, dx, dy, (Real)0)) * invDX * invDY; + poly.A(2, 2, 1) = (Real)9 * b0 - (Real)3 * b1 - (Real)3 * b2 + b3; + poly.A(3, 2, 1) = ((Real)-6 * b0 + (Real)3 * b1 + (Real)2 * b2 - b3) * invDX; + poly.A(2, 3, 1) = ((Real)-6 * b0 + (Real)2 * b1 + (Real)3 * b2 - b3) * invDY; + poly.A(3, 3, 1) = ((Real)4 * b0 - (Real)2 * b1 - (Real)2 * b2 + b3) * invDX * invDY; + + // solve for Ai1k + b0 = (FXY[0][0][1] - poly(1, 1, 0, (Real)0, (Real)0, dz)) * invDZ2; + b1 = (FXYZ[0][0][1] - poly(1, 1, 1, (Real)0, (Real)0, dz)) * invDZ; + poly.A(1, 1, 2) = (Real)3 * b0 - b1; + poly.A(1, 1, 3) = ((Real)-2 * b0 + b1) * invDZ; + + b0 = (FY[1][0][1] - poly(0, 1, 0, dx, (Real)0, dz)) * invDX2 * invDZ2; + b1 = (FXY[1][0][1] - poly(1, 1, 0, dx, (Real)0, dz)) * invDX * invDZ2; + b2 = (FYZ[1][0][1] - poly(0, 1, 1, dx, (Real)0, dz)) * invDX2 * invDZ; + b3 = (FXYZ[1][0][1] - poly(1, 1, 1, dx, (Real)0, dz)) * invDX * invDZ; + poly.A(2, 1, 2) = (Real)9 * b0 - (Real)3 * b1 - (Real)3 * b2 + b3; + poly.A(3, 1, 2) = ((Real)-6 * b0 + (Real)3 * b1 + (Real)2 * b2 - b3) * invDX; + poly.A(2, 1, 3) = ((Real)-6 * b0 + (Real)2 * b1 + (Real)3 * b2 - b3) * invDZ; + poly.A(3, 1, 3) = ((Real)4 * b0 - (Real)2 * b1 - (Real)2 * b2 + b3) * invDX * invDZ; + + // solve for A1jk + b0 = (FX[0][1][1] - poly(1, 0, 0, (Real)0, dy, dz)) * invDY2 * invDZ2; + b1 = (FXY[0][1][1] - poly(1, 1, 0, (Real)0, dy, dz)) * invDY * invDZ2; + b2 = (FXZ[0][1][1] - poly(1, 0, 1, (Real)0, dy, dz)) * invDY2 * invDZ; + b3 = (FXYZ[0][1][1] - poly(1, 1, 1, (Real)0, dy, dz)) * invDY * invDZ; + poly.A(1, 2, 2) = (Real)9 * b0 - (Real)3 * b1 - (Real)3 * b2 + b3; + poly.A(1, 3, 2) = ((Real)-6 * b0 + (Real)3 * b1 + (Real)2 * b2 - b3) * invDY; + poly.A(1, 2, 3) = ((Real)-6 * b0 + (Real)2 * b1 + (Real)3 * b2 - b3) * invDZ; + poly.A(1, 3, 3) = ((Real)4 * b0 - (Real)2 * b1 - (Real)2 * b2 + b3) * invDY * invDZ; + + // solve for remaining Aijk with i >= 2, j >= 2, k >= 2 + b0 = (F[1][1][1] - poly(0, 0, 0, dx, dy, dz)) * invDX2 * invDY2 * invDZ2; + b1 = (FX[1][1][1] - poly(1, 0, 0, dx, dy, dz)) * invDX * invDY2 * invDZ2; + b2 = (FY[1][1][1] - poly(0, 1, 0, dx, dy, dz)) * invDX2 * invDY * invDZ2; + b3 = (FZ[1][1][1] - poly(0, 0, 1, dx, dy, dz)) * invDX2 * invDY2 * invDZ; + b4 = (FXY[1][1][1] - poly(1, 1, 0, dx, dy, dz)) * invDX * invDY * invDZ2; + b5 = (FXZ[1][1][1] - poly(1, 0, 1, dx, dy, dz)) * invDX * invDY2 * invDZ; + b6 = (FYZ[1][1][1] - poly(0, 1, 1, dx, dy, dz)) * invDX2 * invDY * invDZ; + b7 = (FXYZ[1][1][1] - poly(1, 1, 1, dx, dy, dz)) * invDX * invDY * invDZ; + poly.A(2, 2, 2) = (Real)27 * b0 - (Real)9 * b1 - (Real)9 * b2 - + (Real)9 * b3 + (Real)3 * b4 + (Real)3 * b5 + (Real)3 * b6 - b7; + poly.A(3, 2, 2) = ((Real)-18 * b0 + (Real)9 * b1 + (Real)6 * b2 + + (Real)6 * b3 - (Real)3 * b4 - (Real)3 * b5 - (Real)2 * b6 + b7) * invDX; + poly.A(2, 3, 2) = ((Real)-18 * b0 + (Real)6 * b1 + (Real)9 * b2 + + (Real)6 * b3 - (Real)3 * b4 - (Real)2 * b5 - (Real)3 * b6 + b7) * invDY; + poly.A(2, 2, 3) = ((Real)-18 * b0 + (Real)6 * b1 + (Real)6 * b2 + + (Real)9 * b3 - (Real)2 * b4 - (Real)3 * b5 - (Real)3 * b6 + b7) * invDZ; + poly.A(3, 3, 2) = ((Real)12 * b0 - (Real)6 * b1 - (Real)6 * b2 - + (Real)4 * b3 + (Real)3 * b4 + (Real)2 * b5 + (Real)2 * b6 - b7) * + invDX * invDY; + poly.A(3, 2, 3) = ((Real)12 * b0 - (Real)6 * b1 - (Real)4 * b2 - + (Real)6 * b3 + (Real)2 * b4 + (Real)3 * b5 + (Real)2 * b6 - b7) * + invDX * invDZ; + poly.A(2, 3, 3) = ((Real)12 * b0 - (Real)4 * b1 - (Real)6 * b2 - + (Real)6 * b3 + (Real)2 * b4 + (Real)2 * b5 + (Real)3 * b6 - b7) * + invDY * invDZ; + poly.A(3, 3, 3) = ((Real)-8 * b0 + (Real)4 * b1 + (Real)4 * b2 + + (Real)4 * b3 - (Real)2 * b4 - (Real)2 * b5 - (Real)2 * b6 + b7) * + invDX * invDY * invDZ; + } + + void XLookup(Real x, int& xIndex, Real& dx) const + { + for (xIndex = 0; xIndex + 1 < mXBound; ++xIndex) + { + if (x < mXMin + mXSpacing * (xIndex + 1)) + { + dx = x - (mXMin + mXSpacing * xIndex); + return; + } + } + + --xIndex; + dx = x - (mXMin + mXSpacing * xIndex); + } + + void YLookup(Real y, int& yIndex, Real & dy) const + { + for (yIndex = 0; yIndex + 1 < mYBound; ++yIndex) + { + if (y < mYMin + mYSpacing * (yIndex + 1)) + { + dy = y - (mYMin + mYSpacing * yIndex); + return; + } + } + + --yIndex; + dy = y - (mYMin + mYSpacing * yIndex); + } + + void ZLookup(Real z, int& zIndex, Real & dz) const + { + for (zIndex = 0; zIndex + 1 < mZBound; ++zIndex) + { + if (z < mZMin + mZSpacing * (zIndex + 1)) + { + dz = z - (mZMin + mZSpacing * zIndex); + return; + } + } + + --zIndex; + dz = z - (mZMin + mZSpacing * zIndex); + } + + int mXBound, mYBound, mZBound, mQuantity; + Real mXMin, mXMax, mXSpacing; + Real mYMin, mYMax, mYSpacing; + Real mZMin, mZMax, mZSpacing; + Real const* mF; + Array3 mPoly; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpBSplineUniform.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpBSplineUniform.h new file mode 100644 index 0000000..64b82fd --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpBSplineUniform.h @@ -0,0 +1,1440 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +// IntpBSplineUniform is the class for B-spline interpolation of uniformly +// spaced N-dimensional data. The algorithm is described in +// https://www.geometrictools.com/Documentation/BSplineInterpolation.pdf +// A sample application for this topic is +// GeometricTools/GTEngine/Samples/Imagics/BSplineInterpolation +// +// The Controls adapter allows access to your control points without regard +// to how you organize your data. You can even defer the computation of a +// control point until it is needed via the operator()(...) calls that +// Controls must provide, and you can cache the points according to your own +// needs. The minimal interface for a Controls adapter is +// +// struct Controls +// { +// // The control_point_type is of your choosing. It must support +// // assignment, scalar multiplication and addition. Specifically, if +// // C0, C1 and C2 are control points and s is a scalar, the interpolator +// // needs to perform operations +// // C1 = C0; +// // C1 = C0 * s; +// // C2 = C0 + C1; +// typedef control_point_type Type; +// +// // The number of elements in the specified dimension. +// int GetSize(int dimension) const; +// +// // Get a control point based on an n-tuple lookup. The interpolator +// // does not need to know your organization; all it needs is the +// // desired control point. The 'tuple' input must have n elements. +// Type operator() (int const* tuple) const; +// +// // If you choose to use the specialized interpolators for dimensions +// // 1, 2 or 3, you must also provide the following accessor, where +// // the input is an n-tuple listed component-by-component (1, 2 or 3 +// // components). +// Type operator() (int i0, int i1, ..., int inm1) const; +// } + +namespace WwiseGTE +{ + template + class IntpBSplineUniformShared + { + protected: + // Abstract base class construction. A virtual destructor is not + // provided because there are no required side effects in the base + // class when destroying objects from the derived classes. + IntpBSplineUniformShared(int numDimensions, int const* degrees, + Controls const& controls, typename Controls::Type ctZero, int cacheMode) + : + mNumDimensions(numDimensions), + mDegree(numDimensions), + mControls(&controls), + mCTZero(ctZero), + mCacheMode(cacheMode), + mNumLocalControls(0), + mDegreeP1(numDimensions), + mNumControls(numDimensions), + mTMin(numDimensions), + mTMax(numDimensions), + mBlender(numDimensions), + mDCoefficient(numDimensions), + mLMax(numDimensions), + mPowerDSDT(numDimensions), + mITuple(numDimensions), + mJTuple(numDimensions), + mKTuple(numDimensions), + mLTuple(numDimensions), + mSumIJTuple(numDimensions), + mUTuple(numDimensions), + mPTuple(numDimensions) + { + // The condition c+1 > d+1 is required so that when s = c+1-d, its + // maximum value, we have at least two s-knots (d and d + 1). + for (int dim = 0; dim < mNumDimensions; ++dim) + { + if (mControls->GetSize(dim) <= degrees[dim] + 1) + { + LogError("Incompatible degree and number of controls."); + } + } + + mNumLocalControls = 1; + for (int dim = 0; dim < mNumDimensions; ++dim) + { + mDegree[dim] = degrees[dim]; + mDegreeP1[dim] = degrees[dim] + 1; + mNumLocalControls *= mDegreeP1[dim]; + mNumControls[dim] = controls.GetSize(dim); + mTMin[dim] = (Real)-0.5; + mTMax[dim] = static_cast(mNumControls[dim]) - (Real)0.5; + ComputeBlendingMatrix(mDegree[dim], mBlender[dim]); + ComputeDCoefficients(mDegree[dim], mDCoefficient[dim], mLMax[dim]); + ComputePowers(mDegree[dim], mNumControls[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim]); + } + + if (mCacheMode == NO_CACHING) + { + mPhi.resize(mNumDimensions); + for (int dim = 0; dim < mNumDimensions; ++dim) + { + mPhi[dim].resize(mDegreeP1[dim]); + } + } + else + { + InitializeTensors(); + } + } + +#if !defined(GTE_INTP_BSPLINE_UNIFORM_NO_SPECIALIZATION) + IntpBSplineUniformShared() + : + mNumDimensions(0), + mControls(nullptr), + mCTZero(), + mCacheMode(0), + mNumLocalControls(0) + { + } +#endif + + public: + // Support for caching the intermediate tensor product of control + // points with the blending matrices. A precached container has all + // elements precomputed before any Evaluate(...) calls. The 'bool' + // flags are all set to 'true'. A cached container fills the elements + // on demand. The 'bool' flags are initially 'false', indicating the + // EvalType component has not yet been computed. After it is computed + // and stored, the flag is set to 'true'. + enum + { + NO_CACHING, + PRE_CACHING, + ON_DEMAND_CACHING + }; + + // Member access. + inline int GetDegree(int dim) const + { + return mDegree[dim]; + } + + inline int GetNumControls(int dim) const + { + return mNumControls[dim]; + } + + inline Real GetTMin(int dim) const + { + return mTMin[dim]; + } + + inline Real GetTMax(int dim) const + { + return mTMax[dim]; + } + + inline int GetCacheMode() const + { + return mCacheMode; + } + + protected: + // Disallow copying and moving. + IntpBSplineUniformShared(IntpBSplineUniformShared const&) = delete; + IntpBSplineUniformShared& operator=(IntpBSplineUniformShared const&) = delete; + IntpBSplineUniformShared(IntpBSplineUniformShared&&) = delete; + IntpBSplineUniformShared& operator=(IntpBSplineUniformShared&&) = delete; + + // ComputeBlendingMatrix, ComputeDCoefficients, ComputePowers and + // GetKey are used by the general-dimension derived classes and by + // the specializations for dimensions 1, 2 and 3. + + // Compute the blending matrix that combines the control points and + // the polynomial vector. The matrix A is stored in row-major order. + static void ComputeBlendingMatrix(int degree, std::vector& A) + { + int const degreeP1 = degree + 1; + A.resize(degreeP1 * degreeP1); + + if (degree == 0) + { + A[0] = (Real)1; + return; + } + + // P_{0,0}(s) + std::vector> P(degreeP1); + P[0][0] = (Real)1; + + // L0 = s/j + Polynomial1 L0(1); + L0[0] = (Real)0; + + // L1(s) = (j + 1 - s)/j + Polynomial1 L1(1); + + // s-1 is used in computing translated P_{j-1,k-1}(s-1) + Polynomial1 sm1 = { (Real)-1, (Real)1 }; + + // Compute + // P_{j,k}(s) = L0(s)*P_{j-1,k}(s) + L1(s)*P_{j-1,k-1}(s-1) + // for 0 <= k <= j where 1 <= j <= degree. When k = 0, + // P_{j-1,-1}(s) = 0, so P_{j,0}(s) = L0(s)*P_{j-1,0}(s). When + // k = j, P_{j-1,j}(s) = 0, so P_{j,j}(s) = L1(s)*P_{j-1,j-1}(s). + // The polynomials at level j-1 are currently stored in P[0] + // through P[j-1]. The polynomials at level j are computed and + // stored in P[0] through P[j]; that is, they are computed in + // place to reduce memory usage and copying. This requires + // computing P[k] (level j) from P[k] (level j-1) and P[k-1] + // (level j-1), which means we have to process k = j down to + // k = 0. + for (int j = 1; j <= degree; ++j) + { + Real invJ = (Real)1 / (Real)j; + L0[1] = invJ; + L1[0] = (Real)1 + invJ; + L1[1] = -invJ; + + for (int k = j; k >= 0; --k) + { + Polynomial1 result = { (Real)0 }; + + if (k > 0) + { + result += L1 * P[k - 1].GetTranslation((Real)1); + } + + if (k < j) + { + result += L0 * P[k]; + } + + P[k] = result; + } + } + + // Compute Q_{d,k}(s) = P_{d,k}(s + k). + std::vector> Q(degreeP1); + for (int k = 0; k <= degree; ++k) + { + Q[k] = P[k].GetTranslation(static_cast(-k)); + } + + // Extract the matrix A from the Q-polynomials. Row r of A + // contains the coefficients of Q_{d,d-r}(s). + for (int k = 0, row = degree; k <= degree; ++k, --row) + { + for (int col = 0; col <= degree; ++col) + { + A[col + degreeP1 * row] = Q[k][col]; + } + } + } + + // Compute the coefficients for the derivative polynomial terms. + static void ComputeDCoefficients(int degree, std::vector& dCoefficients, + std::vector& ellMax) + { + int numDCoefficients = (degree + 1) * (degree + 2) / 2; + dCoefficients.resize(numDCoefficients); + for (int i = 0; i < numDCoefficients; ++i) + { + dCoefficients[i] = 1; + } + + for (int order = 1, col0 = 0, col1 = degree + 1; order <= degree; ++order) + { + ++col0; + for (int c = order, m = 1; c <= degree; ++c, ++m, ++col0, ++col1) + { + dCoefficients[col1] = dCoefficients[col0] * m; + } + } + + ellMax.resize(degree + 1); + ellMax[0] = degree; + for (int i0 = 0, i1 = 1; i1 <= degree; i0 = i1++) + { + ellMax[i1] = ellMax[i0] + degree - i0; + } + } + + // Compute powers of ds/dt. + void ComputePowers(int degree, int numControls, Real tmin, Real tmax, + std::vector& powerDSDT) + { + Real dsdt = static_cast(numControls - degree) / (tmax - tmin); + powerDSDT.resize(degree + 1); + powerDSDT[0] = (Real)1; + powerDSDT[1] = dsdt; + for (int i = 2; i <= degree; ++i) + { + powerDSDT[i] = powerDSDT[i - 1] * dsdt; + } + } + + // Determine the interval [index,index+1) corresponding to the + // specified value of t and compute u in that interval. + static void GetKey(Real t, Real tmin, Real tmax, Real dsdt, int numControls, + int degree, int& index, Real& u) + { + // Compute s - d = ((c + 1 - d)/(c + 1))(t + 1/2), the index for + // which d + index <= s < d + index + 1. Let u = s - d - index so + // that 0 <= u < 1. + if (t > tmin) + { + if (t < tmax) + { + Real smd = dsdt * (t - tmin); + index = static_cast(std::floor(smd)); + u = smd - static_cast(index); + } + else + { + // In the evaluation, s = c + 1 - d and i = c - d. This + // causes s-d-i to be 1 in G_c(c+1-d). Effectively, the + // selection of i extends the s-domain [d,c+1) to its + // support [d,c+1]. + index = numControls - 1 - degree; + u = (Real)1; + } + } + else + { + index = 0; + u = (Real)0; + } + } + + // The remaining functions are used only by the general-dimension + // derived classes when caching is enabled. + + // For the multidimensional tensor Phi{iTuple, kTuple), compute the + // portion of the 1-dimensional index that corresponds to iTuple. + int GetRowIndex(std::vector const& i) const + { + int rowIndex = i[mNumDimensions - 1]; + int j1 = 2 * mNumDimensions - 2; + for (int j0 = mNumDimensions - 2; j0 >= 0; --j0, --j1) + { + rowIndex = mTBound[j1] * rowIndex + i[j0]; + } + rowIndex = mTBound[j1] * rowIndex; + return rowIndex; + } + + // For the multidimensional tensor Phi{iTuple, kTuple), combine the + // GetRowIndex(...) output with kTuple to produce the full + // 1-dimensional index. + int GetIndex(int rowIndex, std::vector const& k) const + { + int index = rowIndex + k[mNumDimensions - 1]; + for (int j = mNumDimensions - 2; j >= 0; --j) + { + index = mTBound[j] * index + k[j]; + } + return index; + } + + // Compute Phi(iTuple, kTuple). The 'index' value is an already + // computed 1-dimensional index for the tensor. + void ComputeTensor(int const* i, int const* k, int index) + { + auto element = mCTZero; + for (int dim = 0; dim < mNumDimensions; ++dim) + { + mComputeJTuple[dim] = 0; + } + for (int iterate = 0; iterate < mNumLocalControls; ++iterate) + { + Real blend(1); + for (int dim = 0; dim < mNumDimensions; ++dim) + { + blend *= mBlender[dim][k[dim] + mDegreeP1[dim] * mComputeJTuple[dim]]; + mComputeSumIJTuple[dim] = i[dim] + mComputeJTuple[dim]; + } + element = element + (*mControls)(mComputeSumIJTuple.data()) * blend; + + for (int dim = 0; dim < mNumDimensions; ++dim) + { + if (++mComputeJTuple[dim] < mDegreeP1[dim]) + { + break; + } + mComputeJTuple[dim] = 0; + } + } + mTensor[index] = element; + } + + // Allocate the containers used for caching and fill in the tensor + // for precaching when that mode is selected. + void InitializeTensors() + { + mTBound.resize(2 * mNumDimensions); + mComputeJTuple.resize(mNumDimensions); + mComputeSumIJTuple.resize(mNumDimensions); + mDegreeMinusOrder.resize(mNumDimensions); + mTerm.resize(mNumDimensions); + + int current = 0; + int numCached = 1; + for (int dim = 0; dim < mNumDimensions; ++dim, ++current) + { + mTBound[current] = mDegreeP1[dim]; + numCached *= mTBound[current]; + } + for (int dim = 0; dim < mNumDimensions; ++dim, ++current) + { + mTBound[current] = mNumControls[dim] - mDegree[dim]; + numCached *= mTBound[current]; + } + mTensor.resize(numCached); + mCached.resize(numCached); + if (mCacheMode == PRE_CACHING) + { + std::vector tuple(2 * mNumDimensions, 0); + for (int index = 0; index < numCached; ++index) + { + ComputeTensor(&tuple[mNumDimensions], &tuple[0], index); + for (int i = 0; i < 2 * mNumDimensions; ++i) + { + if (++tuple[i] < mTBound[i]) + { + break; + } + tuple[i] = 0; + } + } + std::fill(mCached.begin(), mCached.end(), true); + } + else + { + std::fill(mCached.begin(), mCached.end(), false); + } + } + + // Evaluate the interpolator. Each element of 'order' indicates the + // order of the derivative you want to compute. For the function + // value itself, pass in 'order' that has all 0 elements. + typename Controls::Type EvaluateNoCaching(int const* order, Real const* t) + { + auto result = mCTZero; + for (int dim = 0; dim < mNumDimensions; ++dim) + { + if (order[dim] < 0 || order[dim] > mDegree[dim]) + { + return result; + } + } + + for (int dim = 0; dim < mNumDimensions; ++dim) + { + GetKey(t[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim][1], + mNumControls[dim], mDegree[dim], mITuple[dim], mUTuple[dim]); + } + + for (int dim = 0; dim < mNumDimensions; ++dim) + { + int jIndex = 0; + for (int j = 0; j <= mDegree[dim]; ++j) + { + int kjIndex = mDegree[dim] + jIndex; + int ell = mLMax[dim][order[dim]]; + mPhi[dim][j] = (Real)0; + for (int k = mDegree[dim]; k >= order[dim]; --k) + { + mPhi[dim][j] = mPhi[dim][j] * mUTuple[dim] + + mBlender[dim][kjIndex--] * mDCoefficient[dim][ell--]; + } + jIndex += mDegreeP1[dim]; + } + } + + for (int dim = 0; dim < mNumDimensions; ++dim) + { + mJTuple[dim] = 0; + mSumIJTuple[dim] = mITuple[dim]; + mPTuple[dim] = mPhi[dim][0]; + } + for (int iterate = 0; iterate < mNumLocalControls; ++iterate) + { + Real product(1); + for (int dim = 0; dim < mNumDimensions; ++dim) + { + product *= mPTuple[dim]; + } + + result = result + (*mControls)(mSumIJTuple.data()) * product; + + for (int dim = 0; dim < mNumDimensions; ++dim) + { + if (++mJTuple[dim] <= mDegree[dim]) + { + mSumIJTuple[dim] = mITuple[dim] + mJTuple[dim]; + mPTuple[dim] = mPhi[dim][mJTuple[dim]]; + break; + } + mJTuple[dim] = 0; + mSumIJTuple[dim] = mITuple[dim]; + mPTuple[dim] = mPhi[dim][0]; + } + } + + Real adjust(1); + for (int dim = 0; dim < mNumDimensions; ++dim) + { + adjust *= mPowerDSDT[dim][order[dim]]; + } + result = result * adjust; + return result; + } + + typename Controls::Type EvaluateCaching(int const* order, Real const* t) + { + int numIterates = 1; + for (int dim = 0; dim < mNumDimensions; ++dim) + { + mDegreeMinusOrder[dim] = mDegree[dim] - order[dim]; + if (mDegreeMinusOrder[dim] < 0 || mDegreeMinusOrder[dim] > mDegree[dim]) + { + return mCTZero; + } + numIterates *= mDegreeMinusOrder[dim] + 1; + } + + for (int dim = 0; dim < mNumDimensions; ++dim) + { + GetKey(t[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim][1], + mNumControls[dim], mDegree[dim], mITuple[dim], mUTuple[dim]); + } + + int rowIndex = GetRowIndex(mITuple); + for (int dim = 0; dim < mNumDimensions; ++dim) + { + mJTuple[dim] = 0; + mKTuple[dim] = mDegree[dim]; + mLTuple[dim] = mLMax[dim][order[dim]]; + mTerm[dim] = mCTZero; + } + for (int iterate = 0; iterate < numIterates; ++iterate) + { + int index = GetIndex(rowIndex, mKTuple); + if (mCacheMode == ON_DEMAND_CACHING && !mCached[index]) + { + ComputeTensor(mITuple.data(), mKTuple.data(), index); + mCached[index] = true; + } + mTerm[0] = mTerm[0] * mUTuple[0] + mTensor[index] * mDCoefficient[0][mLTuple[0]]; + for (int dim = 0; dim < mNumDimensions; ++dim) + { + if (++mJTuple[dim] <= mDegreeMinusOrder[dim]) + { + --mKTuple[dim]; + --mLTuple[dim]; + break; + } + int dimp1 = dim + 1; + if (dimp1 < mNumDimensions) + { + mTerm[dimp1] = mTerm[dimp1] * mUTuple[dimp1] + mTerm[dim] * mDCoefficient[dimp1][mLTuple[dimp1]]; + mTerm[dim] = mCTZero; + mJTuple[dim] = 0; + mKTuple[dim] = mDegree[dim]; + mLTuple[dim] = mLMax[dim][order[dim]]; + } + } + } + auto result = mTerm[mNumDimensions - 1]; + + Real adjust(1); + for (int dim = 0; dim < mNumDimensions; ++dim) + { + adjust *= mPowerDSDT[dim][order[dim]]; + } + result = result * adjust; + return result; + } + + // Constructor inputs. + int const mNumDimensions; // N + std::vector mDegree; // degree[N] + Controls const* mControls; + typename Controls::Type const mCTZero; + int const mCacheMode; + + // Parameters for B-spline evaluation. All std::vector containers + // have N elements. + int mNumLocalControls; // product of (degree[]+1) + std::vector mDegreeP1; + std::vector mNumControls; + std::vector mTMin; + std::vector mTMax; + std::vector> mBlender; + std::vector> mDCoefficient; + std::vector> mLMax; + std::vector> mPowerDSDT; + std::vector mITuple; + std::vector mJTuple; + std::vector mKTuple; + std::vector mLTuple; + std::vector mSumIJTuple; + std::vector mUTuple; + std::vector mPTuple; + + // Support for no-cached B-spline evaluation. The std::vector + // container has N elements. + std::vector> mPhi; + + // Support for cached B-spline evaluation. + std::vector mTBound; // tbound[2*N] + std::vector mComputeJTuple; // computejtuple[N] + std::vector mComputeSumIJTuple; // computesumijtuple[N] + std::vector mDegreeMinusOrder; // degreeminusorder[N] + std::vector mTerm; // mTerm[N] + std::vector mTensor; // depends on numcontrols + std::vector mCached; // same size as mTensor + }; +} + +// Implementation for B-spline interpolation whose dimension is known at +// compile time. +namespace WwiseGTE +{ + template + class IntpBSplineUniform : public IntpBSplineUniformShared + { + public: + // The caller is responsible for ensuring that the IntpBSplineUniform + // object persists as long as the input 'controls' exists. + IntpBSplineUniform(std::array const& degrees, Controls const& controls, + typename Controls::Type ctZero, int cacheMode) + : + IntpBSplineUniformShared(N, degrees.data(), controls, + ctZero, cacheMode) + { + } + + // Disallow copying and moving. + IntpBSplineUniform(IntpBSplineUniform const&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform const&) = delete; + IntpBSplineUniform(IntpBSplineUniform&&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform&&) = delete; + + // Evaluate the interpolator. Each element of 'order' indicates the + // order of the derivative you want to compute. For the function + // value itself, pass in 'order' that has all 0 elements. + typename Controls::Type Evaluate(std::array const& order, + std::array const& t) + { + if (this->mCacheMode == this->NO_CACHING) + { + return this->EvaluateNoCaching(order.data(), t.data()); + } + else + { + return this->EvaluateCaching(order.data(), t.data()); + } + } + }; +} + +// Implementation for B-spline interpolation whose dimension is known only +// at run time. +namespace WwiseGTE +{ + template + class IntpBSplineUniform : public IntpBSplineUniformShared + { + public: + // The caller is responsible for ensuring that the IntpBSplineUniform + // object persists as long as the input 'controls' exists. + IntpBSplineUniform(std::vector const& degrees, Controls const& controls, + typename Controls::Type ctZero, int cacheMode) + : + IntpBSplineUniformShared(static_cast(degrees.size()), + degrees.data(), controls, ctZero, cacheMode) + { + } + + // Disallow copying and moving. + IntpBSplineUniform(IntpBSplineUniform const&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform const&) = delete; + IntpBSplineUniform(IntpBSplineUniform&&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform&&) = delete; + + // Evaluate the interpolator. Each element of 'order' indicates the + // order of the derivative you want to compute. For the function + // value itself, pass in 'order' that has all 0 elements. + typename Controls::Type Evaluate(std::vector const& order, + std::vector const& t) + { + if (static_cast(order.size()) >= this->mNumDimensions + && static_cast(t.size()) >= this->mNumDimensions) + { + if (this->mCacheMode == this->NO_CACHING) + { + return this->EvaluateNoCaching(order.data(), t.data()); + } + else + { + return this->EvaluateCaching(order.data(), t.data()); + } + } + else + { + return this->mCTZero; + } + } + }; +} + +// To use only the N-dimensional template code above, define the symbol +// GTE_INTP_BSPLINE_UNIFORM_NO_SPECIALIZATION to disable the specializations +// that occur below. Notice that the interfaces are different between the +// specializations and the general code. +#if !defined(GTE_INTP_BSPLINE_UNIFORM_NO_SPECIALIZATION) + +// Specialization for 1-dimensional data. +namespace WwiseGTE +{ + template + class IntpBSplineUniform : public IntpBSplineUniformShared + { + public: + // The caller is responsible for ensuring that the IntpBSplineUniform + // object persists as long as the input 'controls' exists. + IntpBSplineUniform(int degree, Controls const& controls, + typename Controls::Type ctZero, int cacheMode) + : + IntpBSplineUniformShared(), + mDegree(degree), + mControls(&controls), + mCTZero(ctZero), + mCacheMode(cacheMode) + { + // The condition c+1 > d+1 is required so that when s = c+1-d, its + // maximum value, we have at least two s-knots (d and d + 1). + if (mControls->GetSize(0) <= mDegree + 1) + { + LogError("Incompatible degree and number of controls."); + } + + mDegreeP1 = mDegree + 1; + mNumControls = mControls->GetSize(0); + mTMin = (Real)-0.5; + mTMax = static_cast(mNumControls) - (Real)0.5; + mNumTRows = 0; + mNumTCols = 0; + this->ComputeBlendingMatrix(mDegree, mBlender); + this->ComputeDCoefficients(mDegree, mDCoefficient, mLMax); + this->ComputePowers(mDegree, mNumControls, mTMin, mTMax, mPowerDSDT); + if (mCacheMode == this->NO_CACHING) + { + mPhi.resize(mDegreeP1); + } + else + { + InitializeTensors(); + } + } + + // Disallow copying and moving. + IntpBSplineUniform(IntpBSplineUniform const&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform const&) = delete; + IntpBSplineUniform(IntpBSplineUniform&&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform&&) = delete; + + // Member access. + inline int GetDegree(int) const + { + return mDegree; + } + + inline int GetNumControls(int) const + { + return mNumControls; + } + + inline Real GetTMin(int) const + { + return mTMin; + } + + inline Real GetTMax(int) const + { + return mTMax; + } + + inline int GetCacheMode() const + { + return mCacheMode; + } + + // Evaluate the interpolator. The order is 0 when you want the B-spline + // function value itself. The order is 1 for the first derivative of the + // function, and so on. + typename Controls::Type Evaluate(std::array const& order, + std::array const& t) + { + auto result = mCTZero; + if (0 <= order[0] && order[0] <= mDegree) + { + int i; + Real u; + this->GetKey(t[0], mTMin, mTMax, mPowerDSDT[1], mNumControls, mDegree, i, u); + + if (mCacheMode == this->NO_CACHING) + { + int jIndex = 0; + for (int j = 0; j <= mDegree; ++j) + { + int kjIndex = mDegree + jIndex; + int ell = mLMax[order[0]]; + mPhi[j] = (Real)0; + for (int k = mDegree; k >= order[0]; --k) + { + mPhi[j] = mPhi[j] * u + mBlender[kjIndex--] * mDCoefficient[ell--]; + } + jIndex += mDegreeP1; + } + + for (int j = 0; j <= mDegree; ++j) + { + result = result + (*mControls)(i + j) * mPhi[j]; + } + } + else + { + int iIndex = mNumTCols * i; + int kiIndex = mDegree + iIndex; + int ell = mLMax[order[0]]; + for (int k = mDegree; k >= order[0]; --k) + { + if (mCacheMode == this->ON_DEMAND_CACHING && !mCached[kiIndex]) + { + ComputeTensor(i, k, kiIndex); + mCached[kiIndex] = true; + } + + result = result * u + mTensor[kiIndex--] * mDCoefficient[ell--]; + } + } + + result = result * mPowerDSDT[order[0]]; + } + return result; + } + + protected: + void ComputeTensor(int r, int c, int index) + { + auto element = mCTZero; + for (int j = 0; j <= mDegree; ++j) + { + element = element + (*mControls)(r + j) * mBlender[c + mDegreeP1 * j]; + } + mTensor[index] = element; + } + + void InitializeTensors() + { + mNumTRows = mNumControls - mDegree; + mNumTCols = mDegreeP1; + int numCached = mNumTRows * mNumTCols; + mTensor.resize(numCached); + mCached.resize(numCached); + if (mCacheMode == this->PRE_CACHING) + { + for (int r = 0, index = 0; r < mNumTRows; ++r) + { + for (int c = 0; c < mNumTCols; ++c, ++index) + { + ComputeTensor(r, c, index); + } + } + std::fill(mCached.begin(), mCached.end(), true); + } + else + { + std::fill(mCached.begin(), mCached.end(), false); + } + } + + // Constructor inputs. + int mDegree; + Controls const* mControls; + typename Controls::Type mCTZero; + int mCacheMode; + + // Parameters for B-spline evaluation. + int mDegreeP1; + int mNumControls; + Real mTMin, mTMax; + std::vector mBlender; + std::vector mDCoefficient; + std::vector mLMax; + std::vector mPowerDSDT; + + // Support for no-cached B-spline evaluation. + std::vector mPhi; + + // Support for cached B-spline evaluation. + int mNumTRows, mNumTCols; + std::vector mTensor; + std::vector mCached; + }; +} + +// Specialization for 2-dimensional data. +namespace WwiseGTE +{ + template + class IntpBSplineUniform : public IntpBSplineUniformShared + { + public: + // The caller is responsible for ensuring that the IntpBSplineUniform2 + // object persists as long as the input 'controls' exists. + IntpBSplineUniform(std::array const& degrees, Controls const& controls, + typename Controls::Type ctZero, int cacheMode) + : + IntpBSplineUniformShared(), + mDegree(degrees), + mControls(&controls), + mCTZero(ctZero), + mCacheMode(cacheMode) + { + // The condition c+1 > d+1 is required so that when s = c+1-d, its + // maximum value, we have at least two s-knots (d and d + 1). + for (int dim = 0; dim < 2; ++dim) + { + if (mControls->GetSize(dim) <= mDegree[dim] + 1) + { + LogError("Incompatible degree and number of controls."); + } + } + + for (int dim = 0; dim < 2; ++dim) + { + mDegreeP1[dim] = mDegree[dim] + 1; + mNumControls[dim] = mControls->GetSize(dim); + mTMin[dim] = (Real)-0.5; + mTMax[dim] = static_cast(mNumControls[dim]) - (Real)0.5; + mNumTRows[dim] = 0; + mNumTCols[dim] = 0; + this->ComputeBlendingMatrix(mDegree[dim], mBlender[dim]); + this->ComputeDCoefficients(mDegree[dim], mDCoefficient[dim], mLMax[dim]); + this->ComputePowers(mDegree[dim], mNumControls[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim]); + } + + if (mCacheMode == this->NO_CACHING) + { + for (int dim = 0; dim < 2; ++dim) + { + mPhi[dim].resize(mDegreeP1[dim]); + } + } + else + { + InitializeTensors(); + } + } + + // Disallow copying and moving. + IntpBSplineUniform(IntpBSplineUniform const&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform const&) = delete; + IntpBSplineUniform(IntpBSplineUniform&&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform&&) = delete; + + // Member access. + inline int GetDegree(int dim) const + { + return mDegree[dim]; + } + + inline int GetNumControls(int dim) const + { + return mNumControls[dim]; + } + + inline Real GetTMin(int dim) const + { + return mTMin[dim]; + } + + inline Real GetTMax(int dim) const + { + return mTMax[dim]; + } + + inline int GetCacheMode() const + { + return mCacheMode; + } + + // Evaluate the interpolator. The order is (0,0) when you want the + // B-spline function value itself. The order0 is 1 for the first + // derivative with respect to t0 and the order1 is 1 for the first + // derivative with respect to t1. Higher-order derivatives in other + // t-inputs are computed similarly. + typename Controls::Type Evaluate(std::array const& order, + std::array const& t) + { + auto result = mCTZero; + if (0 <= order[0] && order[0] <= mDegree[0] + && 0 <= order[1] && order[1] <= mDegree[1]) + { + std::array i; + std::array u; + for (int dim = 0; dim < 2; ++dim) + { + this->GetKey(t[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim][1], + mNumControls[dim], mDegree[dim], i[dim], u[dim]); + } + + if (mCacheMode == this->NO_CACHING) + { + for (int dim = 0; dim < 2; ++dim) + { + int jIndex = 0; + for (int j = 0; j <= mDegree[dim]; ++j) + { + int kjIndex = mDegree[dim] + jIndex; + int ell = mLMax[dim][order[dim]]; + mPhi[dim][j] = (Real)0; + for (int k = mDegree[dim]; k >= order[dim]; --k) + { + mPhi[dim][j] = mPhi[dim][j] * u[dim] + + mBlender[dim][kjIndex--] * mDCoefficient[dim][ell--]; + } + jIndex += mDegreeP1[dim]; + } + } + + for (int j1 = 0; j1 <= mDegree[1]; ++j1) + { + Real phi1 = mPhi[1][j1]; + for (int j0 = 0; j0 <= mDegree[0]; ++j0) + { + Real phi0 = mPhi[0][j0]; + Real phi01 = phi0 * phi1; + result = result + (*mControls)(i[0] + j0, i[1] + j1) * phi01; + } + } + } + else + { + int i0i1Index = mNumTCols[1] * (i[0] + mNumTRows[0] * i[1]); + int k1i0i1Index = mDegree[1] + i0i1Index; + int ell1 = mLMax[1][order[1]]; + for (int k1 = mDegree[1]; k1 >= order[1]; --k1) + { + int k0k1i0i1Index = mDegree[0] + mNumTCols[0] * k1i0i1Index; + int ell0 = mLMax[0][order[0]]; + auto term = mCTZero; + for (int k0 = mDegree[0]; k0 >= order[0]; --k0) + { + if (mCacheMode == this->ON_DEMAND_CACHING && !mCached[k0k1i0i1Index]) + { + ComputeTensor(i[0], i[1], k0, k1, k0k1i0i1Index); + mCached[k0k1i0i1Index] = true; + } + term = term * u[0] + mTensor[k0k1i0i1Index--] * mDCoefficient[0][ell0--]; + } + result = result * u[1] + term * mDCoefficient[1][ell1--]; + --k1i0i1Index; + } + } + + Real adjust(1); + for (int dim = 0; dim < 2; ++dim) + { + adjust *= mPowerDSDT[dim][order[dim]]; + } + result = result * adjust; + } + return result; + } + + void ComputeTensor(int r0, int r1, int c0, int c1, int index) + { + auto element = mCTZero; + for (int j1 = 0; j1 <= mDegree[1]; ++j1) + { + Real blend1 = mBlender[1][c1 + mDegreeP1[1] * j1]; + for (int j0 = 0; j0 <= mDegree[0]; ++j0) + { + Real blend0 = mBlender[0][c0 + mDegreeP1[0] * j0]; + Real blend01 = blend0 * blend1; + element = element + (*mControls)(r0 + j0, r1 + j1) * blend01; + } + } + mTensor[index] = element; + } + + void InitializeTensors() + { + int numCached = 1; + for (int dim = 0; dim < 2; ++dim) + { + mNumTRows[dim] = mNumControls[dim] - mDegree[dim]; + mNumTCols[dim] = mDegreeP1[dim]; + numCached *= mNumTRows[dim] * mNumTCols[dim]; + } + mTensor.resize(numCached); + mCached.resize(numCached); + if (mCacheMode == this->PRE_CACHING) + { + for (int r1 = 0, index = 0; r1 < mNumTRows[1]; ++r1) + { + for (int r0 = 0; r0 < mNumTRows[0]; ++r0) + { + for (int c1 = 0; c1 < mNumTCols[1]; ++c1) + { + for (int c0 = 0; c0 < mNumTCols[0]; ++c0, ++index) + { + ComputeTensor(r0, r1, c0, c1, index); + } + } + } + } + std::fill(mCached.begin(), mCached.end(), true); + } + else + { + std::fill(mCached.begin(), mCached.end(), false); + } + } + + // Constructor inputs. + std::array mDegree; + Controls const* mControls; + typename Controls::Type mCTZero; + int mCacheMode; + + // Parameters for B-spline evaluation. + std::array mDegreeP1; + std::array mNumControls; + std::array mTMin, mTMax; + std::array, 2> mBlender; + std::array, 2> mDCoefficient; + std::array, 2> mLMax; + std::array, 2> mPowerDSDT; + + // Support for no-cached B-spline evaluation. + std::array, 2> mPhi; + + // Support for cached B-spline evaluation. + std::array mNumTRows, mNumTCols; + std::vector mTensor; + std::vector mCached; + }; +} + +// Specialization for 3-dimensional data. +namespace WwiseGTE +{ + template + class IntpBSplineUniform : public IntpBSplineUniformShared + { + public: + // The caller is responsible for ensuring that the IntpBSplineUniform3 + // object persists as long as the input 'controls' exists. + IntpBSplineUniform(std::array const& degrees, Controls const& controls, + typename Controls::Type ctZero, int cacheMode) + : + IntpBSplineUniformShared(), + mDegree(degrees), + mControls(&controls), + mCTZero(ctZero), + mCacheMode(cacheMode) + { + // The condition c+1 > d+1 is required so that when s = c+1-d, its + // maximum value, we have at least two s-knots (d and d + 1). + for (int dim = 0; dim < 3; ++dim) + { + if (mControls->GetSize(dim) <= mDegree[dim] + 1) + { + LogError("Incompatible degree and number of controls."); + } + } + + for (int dim = 0; dim < 3; ++dim) + { + mDegreeP1[dim] = mDegree[dim] + 1; + mNumControls[dim] = mControls->GetSize(dim); + mTMin[dim] = (Real)-0.5; + mTMax[dim] = static_cast(mNumControls[dim]) - (Real)0.5; + mNumTRows[dim] = 0; + mNumTCols[dim] = 0; + this->ComputeBlendingMatrix(mDegree[dim], mBlender[dim]); + this->ComputeDCoefficients(mDegree[dim], mDCoefficient[dim], mLMax[dim]); + this->ComputePowers(mDegree[dim], mNumControls[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim]); + } + + if (mCacheMode == this->NO_CACHING) + { + for (int dim = 0; dim < 3; ++dim) + { + mPhi[dim].resize(mDegreeP1[dim]); + } + } + else + { + InitializeTensors(); + } + + } + + // Disallow copying and moving. + IntpBSplineUniform(IntpBSplineUniform const&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform const&) = delete; + IntpBSplineUniform(IntpBSplineUniform&&) = delete; + IntpBSplineUniform& operator=(IntpBSplineUniform&&) = delete; + + // Member access. The input i specifies the dimension (0, 1, 2). + inline int GetDegree(int dim) const + { + return mDegree[dim]; + } + + inline int GetNumControls(int dim) const + { + return mNumControls[dim]; + } + + inline Real GetTMin(int dim) const + { + return mTMin[dim]; + } + + inline Real GetTMax(int dim) const + { + return mTMax[dim]; + } + + // Evaluate the interpolator. The order is (0,0,0) when you want the + // B-spline function value itself. The order0 is 1 for the first + // derivative with respect to t0, the order1 is 1 for the first + // derivative with respect to t1 or the order2 is 1 for the first + // derivative with respect to t2. Higher-order derivatives in other + // t-inputs are computed similarly. + typename Controls::Type Evaluate(std::array const& order, + std::array const& t) + { + auto result = mCTZero; + if (0 <= order[0] && order[0] <= mDegree[0] + && 0 <= order[1] && order[1] <= mDegree[1] + && 0 <= order[2] && order[2] <= mDegree[2]) + { + std::array i; + std::array u; + for (int dim = 0; dim < 3; ++dim) + { + this->GetKey(t[dim], mTMin[dim], mTMax[dim], mPowerDSDT[dim][1], + mNumControls[dim], mDegree[dim], i[dim], u[dim]); + } + + if (mCacheMode == this->NO_CACHING) + { + for (int dim = 0; dim < 3; ++dim) + { + int jIndex = 0; + for (int j = 0; j <= mDegree[dim]; ++j) + { + int kjIndex = mDegree[dim] + jIndex; + int ell = mLMax[dim][order[dim]]; + mPhi[dim][j] = (Real)0; + for (int k = mDegree[dim]; k >= order[dim]; --k) + { + mPhi[dim][j] = mPhi[dim][j] * u[dim] + + mBlender[dim][kjIndex--] * mDCoefficient[dim][ell--]; + } + jIndex += mDegreeP1[dim]; + } + } + + for (int j2 = 0; j2 <= mDegree[2]; ++j2) + { + Real phi2 = mPhi[2][j2]; + for (int j1 = 0; j1 <= mDegree[1]; ++j1) + { + Real phi1 = mPhi[1][j1]; + Real phi12 = phi1 * phi2; + for (int j0 = 0; j0 <= mDegree[0]; ++j0) + { + Real phi0 = mPhi[0][j0]; + Real phi012 = phi0 * phi12; + result = result + (*mControls)(i[0] + j0, i[1] + j1, i[2] + j2) * phi012; + } + } + } + } + else + { + int i0i1i2Index = mNumTCols[2] * (i[0] + mNumTRows[0] * (i[1] + mNumTRows[1] * i[2])); + int k2i0i1i2Index = mDegree[2] + i0i1i2Index; + int ell2 = mLMax[2][order[2]]; + for (int k2 = mDegree[2]; k2 >= order[2]; --k2) + { + int k1k2i0i1i2Index = mDegree[1] + mNumTCols[1] * k2i0i1i2Index; + int ell1 = mLMax[1][order[1]]; + auto term1 = mCTZero; + for (int k1 = mDegree[1]; k1 >= order[1]; --k1) + { + int k0k1k2i0i1i2Index = mDegree[0] + mNumTCols[0] * k1k2i0i1i2Index; + int ell0 = mLMax[0][order[0]]; + auto term0 = mCTZero; + for (int k0 = mDegree[0]; k0 >= order[0]; --k0) + { + if (mCacheMode == this->ON_DEMAND_CACHING && !mCached[k0k1k2i0i1i2Index]) + { + ComputeTensor(i[0], i[1], i[2], k0, k1, k2, k0k1k2i0i1i2Index); + mCached[k0k1k2i0i1i2Index] = true; + } + + term0 = term0 * u[0] + mTensor[k0k1k2i0i1i2Index--] * mDCoefficient[0][ell0--]; + } + term1 = term1 * u[1] + term0 * mDCoefficient[1][ell1--]; + --k1k2i0i1i2Index; + } + result = result * u[2] + term1 * mDCoefficient[2][ell2--]; + --k2i0i1i2Index; + } + } + + Real adjust(1); + for (int dim = 0; dim < 3; ++dim) + { + adjust *= mPowerDSDT[dim][order[dim]]; + } + result = result * adjust; + } + return result; + } + + protected: + void ComputeTensor(int r0, int r1, int r2, int c0, int c1, int c2, int index) + { + auto element = mCTZero; + for (int j2 = 0; j2 <= mDegree[2]; ++j2) + { + Real blend2 = mBlender[2][c2 + mDegreeP1[2] * j2]; + for (int j1 = 0; j1 <= mDegree[1]; ++j1) + { + Real blend1 = mBlender[1][c1 + mDegreeP1[1] * j1]; + Real blend12 = blend1 * blend2; + for (int j0 = 0; j0 <= mDegree[0]; ++j0) + { + Real blend0 = mBlender[0][c0 + mDegreeP1[0] * j0]; + Real blend012 = blend0 * blend12; + element = element + (*mControls)(r0 + j0, r1 + j1, r2 + j2) * blend012; + } + } + } + mTensor[index] = element; + } + + void InitializeTensors() + { + int numCached = 1; + for (int dim = 0; dim < 3; ++dim) + { + mNumTRows[dim] = mNumControls[dim] - mDegree[dim]; + mNumTCols[dim] = mDegreeP1[dim]; + numCached *= mNumTRows[dim] * mNumTCols[dim]; + } + mTensor.resize(numCached); + mCached.resize(numCached); + if (mCacheMode == this->PRE_CACHING) + { + for (int r2 = 0, index = 0; r2 < mNumTRows[2]; ++r2) + { + for (int r1 = 0; r1 < mNumTRows[1]; ++r1) + { + for (int r0 = 0; r0 < mNumTRows[0]; ++r0) + { + for (int c2 = 0; c2 < mNumTCols[2]; ++c2) + { + for (int c1 = 0; c1 < mNumTCols[1]; ++c1) + { + for (int c0 = 0; c0 < mNumTCols[0]; ++c0, ++index) + { + ComputeTensor(r0, r1, r2, c0, c1, c2, index); + } + } + } + } + } + } + std::fill(mCached.begin(), mCached.end(), true); + } + else + { + std::fill(mCached.begin(), mCached.end(), false); + } + } + + // Constructor inputs. + std::array mDegree; + Controls const* mControls; + typename Controls::Type mCTZero; + int mCacheMode; + + // Parameters for B-spline evaluation. + std::array mDegreeP1; + std::array mNumControls; + std::array mTMin, mTMax; + std::array, 3> mBlender; + std::array, 3> mDCoefficient; + std::array, 3> mLMax; + std::array, 3> mPowerDSDT; + + // Support for no-cached B-spline evaluation. + std::array, 3> mPhi; + + // Support for cached B-spline evaluation. + std::array mNumTRows, mNumTCols; + std::vector mTensor; + std::vector mCached; + }; +} + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpBicubic2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpBicubic2.h new file mode 100644 index 0000000..f76711e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpBicubic2.h @@ -0,0 +1,395 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The interpolator is for uniformly spaced (x,y)-values. The input samples +// F must be stored in row-major order to represent f(x,y); that is, +// F[c + xBound*r] corresponds to f(x,y), where c is the index corresponding +// to x and r is the index corresponding to y. Exact interpolation is +// achieved by setting catmullRom to 'true', giving you the Catmull-Rom +// blending matrix. If a smooth interpolation is desired, set catmullRom to +// 'false' to obtain B-spline blending. + +namespace WwiseGTE +{ + template + class IntpBicubic2 + { + public: + // Construction. + IntpBicubic2(int xBound, int yBound, Real xMin, Real xSpacing, + Real yMin, Real ySpacing, Real const* F, bool catmullRom) + : + mXBound(xBound), + mYBound(yBound), + mQuantity(xBound* yBound), + mXMin(xMin), + mXSpacing(xSpacing), + mYMin(yMin), + mYSpacing(ySpacing), + mF(F) + { + // At least a 3x3 block of data points are needed to construct the + // estimates of the boundary derivatives. + LogAssert(mXBound >= 3 && mYBound >= 3 && mF != nullptr, "Invalid input."); + LogAssert(mXSpacing > (Real)0 && mYSpacing > (Real)0, "Invalid input."); + + mXMax = mXMin + mXSpacing * static_cast(mXBound - 1); + mInvXSpacing = (Real)1 / mXSpacing; + mYMax = mYMin + mYSpacing * static_cast(mYBound - 1); + mInvYSpacing = (Real)1 / mYSpacing; + + if (catmullRom) + { + mBlend[0][0] = (Real)0; + mBlend[0][1] = (Real)-0.5; + mBlend[0][2] = (Real)1; + mBlend[0][3] = (Real)-0.5; + mBlend[1][0] = (Real)1; + mBlend[1][1] = (Real)0; + mBlend[1][2] = (Real)-2.5; + mBlend[1][3] = (Real)1.5; + mBlend[2][0] = (Real)0; + mBlend[2][1] = (Real)0.5; + mBlend[2][2] = (Real)2; + mBlend[2][3] = (Real)-1.5; + mBlend[3][0] = (Real)0; + mBlend[3][1] = (Real)0; + mBlend[3][2] = (Real)-0.5; + mBlend[3][3] = (Real)0.5; + } + else + { + mBlend[0][0] = (Real)1 / (Real)6; + mBlend[0][1] = (Real)-3 / (Real)6; + mBlend[0][2] = (Real)3 / (Real)6; + mBlend[0][3] = (Real)-1 / (Real)6;; + mBlend[1][0] = (Real)4 / (Real)6; + mBlend[1][1] = (Real)0 / (Real)6; + mBlend[1][2] = (Real)-6 / (Real)6; + mBlend[1][3] = (Real)3 / (Real)6; + mBlend[2][0] = (Real)1 / (Real)6; + mBlend[2][1] = (Real)3 / (Real)6; + mBlend[2][2] = (Real)3 / (Real)6; + mBlend[2][3] = (Real)-3 / (Real)6; + mBlend[3][0] = (Real)0 / (Real)6; + mBlend[3][1] = (Real)0 / (Real)6; + mBlend[3][2] = (Real)0 / (Real)6; + mBlend[3][3] = (Real)1 / (Real)6; + } + } + + // Member access. + inline int GetXBound() const + { + return mXBound; + } + + inline int GetYBound() const + { + return mYBound; + } + + inline int GetQuantity() const + { + return mQuantity; + } + + inline Real const* GetF() const + { + return mF; + } + + inline Real GetXMin() const + { + return mXMin; + } + + inline Real GetXMax() const + { + return mXMax; + } + + inline Real GetXSpacing() const + { + return mXSpacing; + } + + inline Real GetYMin() const + { + return mYMin; + } + + inline Real GetYMax() const + { + return mYMax; + } + + inline Real GetYSpacing() const + { + return mYSpacing; + } + + // Evaluate the function and its derivatives. The functions clamp the + // inputs to xmin <= x <= xmax and ymin <= y <= ymax. The first + // operator is for function evaluation. The second operator is for + // function or derivative evaluations. The xOrder argument is the + // order of the x-derivative and the yOrder argument is the order of + // the y-derivative. Both orders are zero to get the function value + // itself. + Real operator()(Real x, Real y) const + { + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + std::array U; + U[0] = (Real)1; + U[1] = xIndex - ix; + U[2] = U[1] * U[1]; + U[3] = U[1] * U[2]; + + std::array V; + V[0] = (Real)1; + V[1] = yIndex - iy; + V[2] = V[1] * V[1]; + V[3] = V[1] * V[2]; + + // Compute P = M*U and Q = M*V. + std::array P, Q; + for (int row = 0; row < 4; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + for (int col = 0; col < 4; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + } + } + + // Compute (M*U)^t D (M*V) where D is the 4x4 subimage + // containing (x,y). + --ix; + --iy; + Real result = (Real)0; + for (int row = 0; row < 4; ++row) + { + int yClamp = iy + row; + if (yClamp < 0) + { + yClamp = 0; + } + else if (yClamp > mYBound - 1) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 4; ++col) + { + int xClamp = ix + col; + if (xClamp < 0) + { + xClamp = 0; + } + else if (xClamp > mXBound - 1) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * mF[xClamp + mXBound * yClamp]; + } + } + + return result; + } + + Real operator()(int xOrder, int yOrder, Real x, Real y) const + { + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + std::array U; + Real dx, xMult; + switch (xOrder) + { + case 0: + dx = xIndex - ix; + U[0] = (Real)1; + U[1] = dx; + U[2] = dx * U[1]; + U[3] = dx * U[2]; + xMult = (Real)1; + break; + case 1: + dx = xIndex - ix; + U[0] = (Real)0; + U[1] = (Real)1; + U[2] = (Real)2 * dx; + U[3] = (Real)3 * dx * dx; + xMult = mInvXSpacing; + break; + case 2: + dx = xIndex - ix; + U[0] = (Real)0; + U[1] = (Real)0; + U[2] = (Real)2; + U[3] = (Real)6 * dx; + xMult = mInvXSpacing * mInvXSpacing; + break; + case 3: + U[0] = (Real)0; + U[1] = (Real)0; + U[2] = (Real)0; + U[3] = (Real)6; + xMult = mInvXSpacing * mInvXSpacing * mInvXSpacing; + break; + default: + return (Real)0; + } + + std::array V; + Real dy, yMult; + switch (yOrder) + { + case 0: + dy = yIndex - iy; + V[0] = (Real)1; + V[1] = dy; + V[2] = dy * V[1]; + V[3] = dy * V[2]; + yMult = (Real)1; + break; + case 1: + dy = yIndex - iy; + V[0] = (Real)0; + V[1] = (Real)1; + V[2] = (Real)2 * dy; + V[3] = (Real)3 * dy * dy; + yMult = mInvYSpacing; + break; + case 2: + dy = yIndex - iy; + V[0] = (Real)0; + V[1] = (Real)0; + V[2] = (Real)2; + V[3] = (Real)6 * dy; + yMult = mInvYSpacing * mInvYSpacing; + break; + case 3: + V[0] = (Real)0; + V[1] = (Real)0; + V[2] = (Real)0; + V[3] = (Real)6; + yMult = mInvYSpacing * mInvYSpacing * mInvYSpacing; + break; + default: + return (Real)0; + } + + // Compute P = M*U and Q = M*V. + std::array P, Q; + for (int row = 0; row < 4; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + for (int col = 0; col < 4; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + } + } + + // Compute (M*U)^t D (M*V) where D is the 4x4 subimage containing (x,y). + --ix; + --iy; + Real result = (Real)0; + for (int row = 0; row < 4; ++row) + { + int yClamp = iy + row; + if (yClamp < 0) + { + yClamp = 0; + } + else if (yClamp > mYBound - 1) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 4; ++col) + { + int xClamp = ix + col; + if (xClamp < 0) + { + xClamp = 0; + } + else if (xClamp > mXBound - 1) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * mF[xClamp + mXBound * yClamp]; + } + } + result *= xMult * yMult; + + return result; + } + + private: + int mXBound, mYBound, mQuantity; + Real mXMin, mXMax, mXSpacing, mInvXSpacing; + Real mYMin, mYMax, mYSpacing, mInvYSpacing; + Real const* mF; + std::array, 4> mBlend; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpBilinear2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpBilinear2.h new file mode 100644 index 0000000..1740b4c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpBilinear2.h @@ -0,0 +1,296 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The interpolator is for uniformly spaced (x,y)-values. The input samples +// F must be stored in row-major order to represent f(x,y); that is, +// F[c + xBound*r] corresponds to f(x,y), where c is the index corresponding +// to x and r is the index corresponding to y. + +namespace WwiseGTE +{ + template + class IntpBilinear2 + { + public: + // Construction. + IntpBilinear2(int xBound, int yBound, Real xMin, Real xSpacing, + Real yMin, Real ySpacing, Real const* F) + : + mXBound(xBound), + mYBound(yBound), + mQuantity(xBound* yBound), + mXMin(xMin), + mXSpacing(xSpacing), + mYMin(yMin), + mYSpacing(ySpacing), + mF(F) + { + // At least a 3x3 block of data points are needed to construct the + // estimates of the boundary derivatives. + LogAssert(mXBound >= 2 && mYBound >= 2 && mF != nullptr, "Invalid input."); + LogAssert(mXSpacing > (Real)0 && mYSpacing > (Real)0, "Invalid input."); + + mXMax = mXMin + mXSpacing * static_cast(mXBound - 1); + mInvXSpacing = (Real)1 / mXSpacing; + mYMax = mYMin + mYSpacing * static_cast(mYBound - 1); + mInvYSpacing = (Real)1 / mYSpacing; + + mBlend[0][0] = (Real)1; + mBlend[0][1] = (Real)-1; + mBlend[1][0] = (Real)0; + mBlend[1][1] = (Real)1; + } + + // Member access. + inline int GetXBound() const + { + return mXBound; + } + + inline int GetYBound() const + { + return mYBound; + } + + inline int GetQuantity() const + { + return mQuantity; + } + + inline Real const* GetF() const + { + return mF; + } + + inline Real GetXMin() const + { + return mXMin; + } + + inline Real GetXMax() const + { + return mXMax; + } + + inline Real GetXSpacing() const + { + return mXSpacing; + } + + inline Real GetYMin() const + { + return mYMin; + } + + inline Real GetYMax() const + { + return mYMax; + } + + inline Real GetYSpacing() const + { + return mYSpacing; + } + + // Evaluate the function and its derivatives. The functions clamp the + // inputs to xmin <= x <= xmax and ymin <= y <= ymax. The first + // operator is for function evaluation. The second operator is for + // function or derivative evaluations. The xOrder argument is the + // order of the x-derivative and the yOrder argument is the order of + // the y-derivative. Both orders are zero to get the function value + // itself. + Real operator()(Real x, Real y) const + { + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + std::array U; + U[0] = (Real)1; + U[1] = xIndex - ix; + + std::array V; + V[0] = (Real)1; + V[1] = yIndex - iy; + + // Compute P = M*U and Q = M*V. + std::array P, Q; + for (int row = 0; row < 2; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + for (int col = 0; col < 2; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + } + } + + // Compute (M*U)^t D (M*V) where D is the 2x2 subimage + // containing (x,y). + Real result = (Real)0; + for (int row = 0; row < 2; ++row) + { + int yClamp = iy + row; + if (yClamp >= mYBound) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 2; ++col) + { + int xClamp = ix + col; + if (xClamp >= mXBound) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * mF[xClamp + mXBound * yClamp]; + } + } + + return result; + } + + Real operator()(int xOrder, int yOrder, Real x, Real y) const + { + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + std::array U; + Real dx, xMult; + switch (xOrder) + { + case 0: + dx = xIndex - ix; + U[0] = (Real)1; + U[1] = dx; + xMult = (Real)1; + break; + case 1: + dx = xIndex - ix; + U[0] = (Real)0; + U[1] = (Real)1; + xMult = mInvXSpacing; + break; + default: + return (Real)0; + } + + std::array V; + Real dy, yMult; + switch (yOrder) + { + case 0: + dy = yIndex - iy; + V[0] = (Real)1; + V[1] = dy; + yMult = (Real)1; + break; + case 1: + dy = yIndex - iy; + V[0] = (Real)0; + V[1] = (Real)1; + yMult = mInvYSpacing; + break; + default: + return (Real)0; + } + + // Compute P = M*U and Q = M*V. + std::array P, Q; + for (int row = 0; row < 2; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + for (int col = 0; col < 2; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + } + } + + // Compute (M*U)^t D (M*V) where D is the 2x2 subimage containing (x,y). + Real result = (Real)0; + for (int row = 0; row < 2; ++row) + { + int yClamp = iy + row; + if (yClamp >= mYBound) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 2; ++col) + { + int xClamp = ix + col; + if (xClamp >= mXBound) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * mF[xClamp + mXBound * yClamp]; + } + } + result *= xMult * yMult; + + return result; + } + + private: + int mXBound, mYBound, mQuantity; + Real mXMin, mXMax, mXSpacing, mInvXSpacing; + Real mYMin, mYMax, mYSpacing, mInvYSpacing; + Real const* mF; + std::array, 2> mBlend; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpLinearNonuniform2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpLinearNonuniform2.h new file mode 100644 index 0000000..3084938 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpLinearNonuniform2.h @@ -0,0 +1,72 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// Linear interpolation of a network of triangles whose vertices are of the +// form (x,y,f(x,y)). The function samples are F[i] and represent +// f(x[i],y[i]), where i is the index of the input vertex (x[i],y[i]) to +// Delaunay2. +// +// The TriangleMesh interface must support the following: +// bool GetIndices(int, std::array&) const; +// bool GetBarycentrics(int, Vector2 const&, +// std::array&) const; +// int GetContainingTriangle(Vector2 const&) const; + +namespace WwiseGTE +{ + template + class IntpLinearNonuniform2 + { + public: + // Construction. + IntpLinearNonuniform2(TriangleMesh const& mesh, Real const* F) + : + mMesh(&mesh), + mF(F) + { + LogAssert(mF != nullptr, "Invalid input."); + } + + // Linear interpolation. The return value is 'true' if and only if + // the input point P is in the convex hull of the input vertices, in + // which case the interpolation is valid. + bool operator()(Vector2 const& P, Real& F) const + { + int t = mMesh->GetContainingTriangle(P); + if (t == -1) + { + // The point is outside the triangulation. + return false; + } + + // Get the barycentric coordinates of P with respect to the triangle, + // P = b0*V0 + b1*V1 + b2*V2, where b0 + b1 + b2 = 1. + std::array bary; + if (!mMesh->GetBarycentrics(t, P, bary)) + { + // TODO: Throw an exception or allow this as valid behavior? + // P is in a needle-like or degenerate triangle. + return false; + } + + // The result is a barycentric combination of function values. + std::array indices; + mMesh->GetIndices(t, indices); + F = bary[0] * mF[indices[0]] + bary[1] * mF[indices[1]] + bary[2] * mF[indices[2]]; + return true; + } + + private: + TriangleMesh const* mMesh; + Real const* mF; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpLinearNonuniform3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpLinearNonuniform3.h new file mode 100644 index 0000000..a823652 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpLinearNonuniform3.h @@ -0,0 +1,72 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// Linear interpolation of a network of triangles whose vertices are of the +// form (x,y,z,f(x,y,z)). The function samples are F[i] and represent +// f(x[i],y[i],z[i]), where i is the index of the input vertex +// (x[i],y[i],z[i]) to Delaunay3. +// +// The TetrahedronMesh interface must support the following: +// int GetContainingTetrahedron(Vector3 const&) const; +// bool GetIndices(int, std::array&) const; +// bool GetBarycentrics(int, Vector3 const&, Real[4]) const; + +namespace WwiseGTE +{ + template + class IntpLinearNonuniform3 + { + public: + // Construction. + IntpLinearNonuniform3(TetrahedronMesh const& mesh, Real const* F) + : + mMesh(&mesh), + mF(F) + { + LogAssert(mF != nullptr, "Invalid input."); + } + + // Linear interpolation. The return value is 'true' if and only if + // the input point is in the convex hull of the input vertices, in + // which case the interpolation is valid. + bool operator()(Vector3 const& P, Real& F) const + { + int t = mMesh->GetContainingTetrahedron(P); + if (t == -1) + { + // The point is outside the tetrahedralization. + return false; + } + + // Get the barycentric coordinates of P with respect to the tetrahedron, + // P = b0*V0 + b1*V1 + b2*V2 + b3*V3, where b0 + b1 + b2 + b3 = 1. + std::array bary; + if (!mMesh->GetBarycentrics(t, P, bary)) + { + // TODO: Throw an exception or allow this as valid behavior? + // P is in a needle-like, flat, or degenerate tetrahedron. + return false; + } + + // The result is a barycentric combination of function values. + std::array indices; + mMesh->GetIndices(t, indices); + F = bary[0] * mF[indices[0]] + bary[1] * mF[indices[1]] + + bary[2] * mF[indices[2]] + bary[3] * mF[indices[4]]; + return true; + } + + private: + TetrahedronMesh const* mMesh; + Real const* mF; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpQuadraticNonuniform2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpQuadraticNonuniform2.h new file mode 100644 index 0000000..fd20206 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpQuadraticNonuniform2.h @@ -0,0 +1,459 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// Quadratic interpolation of a network of triangles whose vertices are of +// the form (x,y,f(x,y)). This code is an implementation of the algorithm +// found in +// +// Zoltan J. Cendes and Steven H. Wong, +// C1 quadratic interpolation over arbitrary point sets, +// IEEE Computer Graphics & Applications, +// pp. 8-16, 1987 +// +// The TriangleMesh interface must support the following: +// int GetNumVertices() const; +// int GetNumTriangles() const; +// Vector2 const* GetVertices() const; +// int const* GetIndices() const; +// bool GetVertices(int, std::array, 3>&) const; +// bool GetIndices(int, std::array&) const; +// bool GetAdjacencies(int, std::array&) const; +// bool GetBarycentrics(int, Vector2 const&, +// std::array&) const; +// int GetContainingTriangle(Vector2 const&) const; + +namespace WwiseGTE +{ + template + class IntpQuadraticNonuniform2 + { + public: + // Construction. + // + // The first constructor requires only F and a measure of the rate of + // change of the function values relative to changes in the spatial + // variables. The df/dx and df/dy values are estimated at the sample + // points using mesh normals and spatialDelta. + // + // The second constructor requires you to specify function values F + // and first-order partial derivative values df/dx and df/dy. + + IntpQuadraticNonuniform2(TriangleMesh const& mesh, Real const* F, Real spatialDelta) + : + mMesh(&mesh), + mF(F), + mFX(nullptr), + mFY(nullptr) + { + EstimateDerivatives(spatialDelta); + ProcessTriangles(); + } + + IntpQuadraticNonuniform2(TriangleMesh const& mesh, Real const* F, Real const* FX, Real const* FY) + : + mMesh(&mesh), + mF(F), + mFX(FX), + mFY(FY) + { + ProcessTriangles(); + } + + // Quadratic interpolation. The return value is 'true' if and only if + // the input point is in the convex hull of the input vertices, in + // which case the interpolation is valid. + bool operator()(Vector2 const& P, Real& F, Real& FX, Real& FY) const + { + int t = mMesh->GetContainingTriangle(P); + if (t == -1) + { + // The point is outside the triangulation. + return false; + } + + // Get the vertices of the triangle. + std::array, 3> V; + mMesh->GetVertices(t, V); + + // Get the additional information for the triangle. + TriangleData const& tData = mTData[t]; + + // Determine which of the six subtriangles contains the target + // point. Theoretically, P must be in one of these subtriangles. + Vector2 sub0 = tData.center; + Vector2 sub1; + Vector2 sub2 = tData.intersect[2]; + Vector3 bary; + int index; + + Real const zero = (Real)0, one = (Real)1; + AlignedBox3 barybox({ zero, zero, zero }, { one, one, one }); + DCPQuery, AlignedBox3> pbQuery; + int minIndex = 0; + Real minDistance = (Real)-1; + Vector3 minBary{ (Real)0, (Real)0, (Real)0 }; + Vector2 minSub0{ (Real)0, (Real)0 }; + Vector2 minSub1{ (Real)0, (Real)0 }; + Vector2 minSub2{ (Real)0, (Real)0 }; + + for (index = 1; index <= 6; ++index) + { + sub1 = sub2; + if (index % 2) + { + sub2 = V[index / 2]; + } + else + { + sub2 = tData.intersect[index / 2 - 1]; + } + + bool valid = ComputeBarycentrics(P, sub0, sub1, sub2, &bary[0]); + if (valid + && zero <= bary[0] && bary[0] <= one + && zero <= bary[1] && bary[1] <= one + && zero <= bary[2] && bary[2] <= one) + { + // P is in triangle + break; + } + + // When computing with floating-point arithmetic, rounding + // errors can cause us to reach this code when, theoretically, + // the point is in the subtriangle. Keep track of the + // (b0,b1,b2) that is closest to the barycentric cube [0,1]^3 + // and choose the triangle corresponding to it when all 6 + // tests previously fail. + Real distance = pbQuery(bary, barybox).distance; + if (minIndex == 0 || distance < minDistance) + { + minDistance = distance; + minIndex = index; + minBary = bary; + minSub0 = sub0; + minSub1 = sub1; + minSub2 = sub2; + } + } + + // If the subtriangle was not found, rounding errors caused + // problems. Choose the barycentric point closest to the box. + if (index > 6) + { + index = minIndex; + bary = minBary; + sub0 = minSub0; + sub1 = minSub1; + sub2 = minSub2; + } + + // Fetch Bezier control points. + Real bez[6] = + { + tData.coeff[0], + tData.coeff[12 + index], + tData.coeff[13 + (index % 6)], + tData.coeff[index], + tData.coeff[6 + index], + tData.coeff[1 + (index % 6)] + }; + + // Evaluate Bezier quadratic. + F = bary[0] * (bez[0] * bary[0] + bez[1] * bary[1] + bez[2] * bary[2]) + + bary[1] * (bez[1] * bary[0] + bez[3] * bary[1] + bez[4] * bary[2]) + + bary[2] * (bez[2] * bary[0] + bez[4] * bary[1] + bez[5] * bary[2]); + + // Evaluate barycentric derivatives of F. + Real FU = ((Real)2) * (bez[0] * bary[0] + bez[1] * bary[1] + + bez[2] * bary[2]); + Real FV = ((Real)2) * (bez[1] * bary[0] + bez[3] * bary[1] + + bez[4] * bary[2]); + Real FW = ((Real)2) * (bez[2] * bary[0] + bez[4] * bary[1] + + bez[5] * bary[2]); + Real duw = FU - FW; + Real dvw = FV - FW; + + // Convert back to (x,y) coordinates. + Real m00 = sub0[0] - sub2[0]; + Real m10 = sub0[1] - sub2[1]; + Real m01 = sub1[0] - sub2[0]; + Real m11 = sub1[1] - sub2[1]; + Real inv = ((Real)1) / (m00 * m11 - m10 * m01); + + FX = inv * (m11 * duw - m10 * dvw); + FY = inv * (m00 * dvw - m01 * duw); + return true; + } + + private: + void EstimateDerivatives(Real spatialDelta) + { + int numVertices = mMesh->GetNumVertices(); + Vector2 const* vertices = mMesh->GetVertices(); + int numTriangles = mMesh->GetNumTriangles(); + int const* indices = mMesh->GetIndices(); + + mFXStorage.resize(numVertices); + mFYStorage.resize(numVertices); + std::vector FZ(numVertices); + std::fill(mFXStorage.begin(), mFXStorage.end(), (Real)0); + std::fill(mFYStorage.begin(), mFYStorage.end(), (Real)0); + std::fill(FZ.begin(), FZ.end(), (Real)0); + + mFX = &mFXStorage[0]; + mFY = &mFYStorage[0]; + + // Accumulate normals at spatial locations (averaging process). + for (int t = 0; t < numTriangles; ++t) + { + // Get three vertices of triangle. + int v0 = *indices++; + int v1 = *indices++; + int v2 = *indices++; + + // Compute normal vector of triangle (with positive + // z-component). + Real dx1 = vertices[v1][0] - vertices[v0][0]; + Real dy1 = vertices[v1][1] - vertices[v0][1]; + Real dz1 = mF[v1] - mF[v0]; + Real dx2 = vertices[v2][0] - vertices[v0][0]; + Real dy2 = vertices[v2][1] - vertices[v0][1]; + Real dz2 = mF[v2] - mF[v0]; + Real nx = dy1 * dz2 - dy2 * dz1; + Real ny = dz1 * dx2 - dz2 * dx1; + Real nz = dx1 * dy2 - dx2 * dy1; + if (nz < (Real)0) + { + nx = -nx; + ny = -ny; + nz = -nz; + } + + mFXStorage[v0] += nx; mFYStorage[v0] += ny; FZ[v0] += nz; + mFXStorage[v1] += nx; mFYStorage[v1] += ny; FZ[v1] += nz; + mFXStorage[v2] += nx; mFYStorage[v2] += ny; FZ[v2] += nz; + } + + // Scale the normals to form (x,y,-1). + for (int i = 0; i < numVertices; ++i) + { + if (FZ[i] != (Real)0) + { + Real inv = -spatialDelta / FZ[i]; + mFXStorage[i] *= inv; + mFYStorage[i] *= inv; + } + else + { + mFXStorage[i] = (Real)0; + mFYStorage[i] = (Real)0; + } + } + } + + void ProcessTriangles() + { + // Add degenerate triangles to boundary triangles so that + // interpolation at the boundary can be treated in the same way + // as interpolation in the interior. + + // Compute centers of inscribed circles for triangles. + Vector2 const* vertices = mMesh->GetVertices(); + int numTriangles = mMesh->GetNumTriangles(); + int const* indices = mMesh->GetIndices(); + mTData.resize(numTriangles); + int t; + for (t = 0; t < numTriangles; ++t) + { + int v0 = *indices++; + int v1 = *indices++; + int v2 = *indices++; + Circle2 circle; + Inscribe(vertices[v0], vertices[v1], vertices[v2], circle); + mTData[t].center = circle.center; + } + + // Compute cross-edge intersections. + for (t = 0; t < numTriangles; ++t) + { + ComputeCrossEdgeIntersections(t); + } + + // Compute Bezier coefficients. + for (t = 0; t < numTriangles; ++t) + { + ComputeCoefficients(t); + } + } + + void ComputeCrossEdgeIntersections(int t) + { + // Get the vertices of the triangle. + std::array, 3> V; + mMesh->GetVertices(t, V); + + // Get the centers of adjacent triangles. + TriangleData& tData = mTData[t]; + std::array adjacencies; + mMesh->GetAdjacencies(t, adjacencies); + for (int j0 = 2, j1 = 0; j1 < 3; j0 = j1++) + { + int a = adjacencies[j0]; + if (a >= 0) + { + // Get center of adjacent triangle's inscribing circle. + Vector2 U = mTData[a].center; + Real m00 = V[j0][1] - V[j1][1]; + Real m01 = V[j1][0] - V[j0][0]; + Real m10 = tData.center[1] - U[1]; + Real m11 = U[0] - tData.center[0]; + Real r0 = m00 * V[j0][0] + m01 * V[j0][1]; + Real r1 = m10 * tData.center[0] + m11 * tData.center[1]; + Real invDet = ((Real)1) / (m00 * m11 - m01 * m10); + tData.intersect[j0][0] = (m11 * r0 - m01 * r1) * invDet; + tData.intersect[j0][1] = (m00 * r1 - m10 * r0) * invDet; + } + else + { + // No adjacent triangle, use center of edge. + tData.intersect[j0] = (Real)0.5 * (V[j0] + V[j1]); + } + } + } + + void ComputeCoefficients(int t) + { + // Get the vertices of the triangle. + std::array, 3> V; + mMesh->GetVertices(t, V); + + // Get the additional information for the triangle. + TriangleData& tData = mTData[t]; + + // Get the sample data at main triangle vertices. + std::array indices; + mMesh->GetIndices(t, indices); + Jet jet[3]; + for (int j = 0; j < 3; ++j) + { + int k = indices[j]; + jet[j].F = mF[k]; + jet[j].FX = mFX[k]; + jet[j].FY = mFY[k]; + } + + // Get centers of adjacent triangles. + std::array adjacencies; + mMesh->GetAdjacencies(t, adjacencies); + Vector2 U[3]; + for (int j0 = 2, j1 = 0; j1 < 3; j0 = j1++) + { + int a = adjacencies[j0]; + if (a >= 0) + { + // Get center of adjacent triangle's circumscribing + // circle. + U[j0] = mTData[a].center; + } + else + { + // No adjacent triangle, use center of edge. + U[j0] = ((Real)0.5) * (V[j0] + V[j1]); + } + } + + // Compute intermediate terms. + std::array cenT, cen0, cen1, cen2; + mMesh->GetBarycentrics(t, tData.center, cenT); + mMesh->GetBarycentrics(t, U[0], cen0); + mMesh->GetBarycentrics(t, U[1], cen1); + mMesh->GetBarycentrics(t, U[2], cen2); + + Real alpha = (cenT[1] * cen1[0] - cenT[0] * cen1[1]) / (cen1[0] - cenT[0]); + Real beta = (cenT[2] * cen2[1] - cenT[1] * cen2[2]) / (cen2[1] - cenT[1]); + Real gamma = (cenT[0] * cen0[2] - cenT[2] * cen0[0]) / (cen0[2] - cenT[2]); + Real oneMinusAlpha = (Real)1 - alpha; + Real oneMinusBeta = (Real)1 - beta; + Real oneMinusGamma = (Real)1 - gamma; + + Real tmp, A[9], B[9]; + + tmp = cenT[0] * V[0][0] + cenT[1] * V[1][0] + cenT[2] * V[2][0]; + A[0] = (Real)0.5 * (tmp - V[0][0]); + A[1] = (Real)0.5 * (tmp - V[1][0]); + A[2] = (Real)0.5 * (tmp - V[2][0]); + A[3] = (Real)0.5 * beta * (V[2][0] - V[0][0]); + A[4] = (Real)0.5 * oneMinusGamma * (V[1][0] - V[0][0]); + A[5] = (Real)0.5 * gamma * (V[0][0] - V[1][0]); + A[6] = (Real)0.5 * oneMinusAlpha * (V[2][0] - V[1][0]); + A[7] = (Real)0.5 * alpha * (V[1][0] - V[2][0]); + A[8] = (Real)0.5 * oneMinusBeta * (V[0][0] - V[2][0]); + + tmp = cenT[0] * V[0][1] + cenT[1] * V[1][1] + cenT[2] * V[2][1]; + B[0] = (Real)0.5 * (tmp - V[0][1]); + B[1] = (Real)0.5 * (tmp - V[1][1]); + B[2] = (Real)0.5 * (tmp - V[2][1]); + B[3] = (Real)0.5 * beta * (V[2][1] - V[0][1]); + B[4] = (Real)0.5 * oneMinusGamma * (V[1][1] - V[0][1]); + B[5] = (Real)0.5 * gamma * (V[0][1] - V[1][1]); + B[6] = (Real)0.5 * oneMinusAlpha * (V[2][1] - V[1][1]); + B[7] = (Real)0.5 * alpha * (V[1][1] - V[2][1]); + B[8] = (Real)0.5 * oneMinusBeta * (V[0][1] - V[2][1]); + + // Compute Bezier coefficients. + tData.coeff[2] = jet[0].F; + tData.coeff[4] = jet[1].F; + tData.coeff[6] = jet[2].F; + + tData.coeff[14] = jet[0].F + A[0] * jet[0].FX + B[0] * jet[0].FY; + tData.coeff[7] = jet[0].F + A[3] * jet[0].FX + B[3] * jet[0].FY; + tData.coeff[8] = jet[0].F + A[4] * jet[0].FX + B[4] * jet[0].FY; + tData.coeff[16] = jet[1].F + A[1] * jet[1].FX + B[1] * jet[1].FY; + tData.coeff[9] = jet[1].F + A[5] * jet[1].FX + B[5] * jet[1].FY; + tData.coeff[10] = jet[1].F + A[6] * jet[1].FX + B[6] * jet[1].FY; + tData.coeff[18] = jet[2].F + A[2] * jet[2].FX + B[2] * jet[2].FY; + tData.coeff[11] = jet[2].F + A[7] * jet[2].FX + B[7] * jet[2].FY; + tData.coeff[12] = jet[2].F + A[8] * jet[2].FX + B[8] * jet[2].FY; + + tData.coeff[5] = alpha * tData.coeff[10] + oneMinusAlpha * tData.coeff[11]; + tData.coeff[17] = alpha * tData.coeff[16] + oneMinusAlpha * tData.coeff[18]; + tData.coeff[1] = beta * tData.coeff[12] + oneMinusBeta * tData.coeff[7]; + tData.coeff[13] = beta * tData.coeff[18] + oneMinusBeta * tData.coeff[14]; + tData.coeff[3] = gamma * tData.coeff[8] + oneMinusGamma * tData.coeff[9]; + tData.coeff[15] = gamma * tData.coeff[14] + oneMinusGamma * tData.coeff[16]; + tData.coeff[0] = cenT[0] * tData.coeff[14] + cenT[1] * tData.coeff[16] + cenT[2] * tData.coeff[18]; + } + + class TriangleData + { + public: + Vector2 center; + Vector2 intersect[3]; + Real coeff[19]; + }; + + class Jet + { + public: + Real F, FX, FY; + }; + + TriangleMesh const* mMesh; + Real const* mF; + Real const* mFX; + Real const* mFY; + std::vector mFXStorage; + std::vector mFYStorage; + std::vector mTData; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpSphere2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpSphere2.h new file mode 100644 index 0000000..3381594 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpSphere2.h @@ -0,0 +1,116 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// Interpolation of a scalar-valued function defined on a sphere. Although +// the sphere lives in 3D, the interpolation is a 2D method whose input +// points are angles (theta,phi) from spherical coordinates. The domains of +// the angles are -pi <= theta <= pi and 0 <= phi <= pi. + +namespace WwiseGTE +{ + template + class IntpSphere2 + { + public: + // Construction and destruction. For complete spherical coverage, + // include the two antipodal (theta,phi) points (-pi,0,F(-pi,0)) and + // (-pi,pi,F(-pi,pi)) in the input data. These correspond to the + // sphere poles x = 0, y = 0, and |z| = 1. + ~IntpSphere2() = default; + + IntpSphere2(int numPoints, InputType const* theta, InputType const* phi, InputType const* F) + : + mMesh(mDelaunay) + { + // Copy the input data. The larger arrays are used to support + // wrap-around in the Delaunay triangulation for the interpolator. + int totalPoints = 3 * numPoints; + mWrapAngles.resize(totalPoints); + mWrapF.resize(totalPoints); + for (int i = 0; i < numPoints; ++i) + { + mWrapAngles[i][0] = theta[i]; + mWrapAngles[i][1] = phi[i]; + mWrapF[i] = F[i]; + } + + // Use periodicity to get wrap-around in the Delaunay + // triangulation. + int i0 = 0, i1 = numPoints, i2 = 2 * numPoints; + for (/**/; i0 < numPoints; ++i0, ++i1, ++i2) + { + mWrapAngles[i1][0] = mWrapAngles[i0][0] + (InputType)GTE_C_TWO_PI; + mWrapAngles[i2][0] = mWrapAngles[i0][0] - (InputType)GTE_C_TWO_PI; + mWrapAngles[i1][1] = mWrapAngles[i0][1]; + mWrapAngles[i2][1] = mWrapAngles[i0][1]; + mWrapF[i1] = mWrapF[i0]; + mWrapF[i2] = mWrapF[i0]; + } + + mDelaunay(totalPoints, &mWrapAngles[0], (ComputeType)0); + mInterp = std::make_unique>( + mMesh, &mWrapF[0], (InputType)1); + } + + // Spherical coordinates are + // x = cos(theta)*sin(phi) + // y = sin(theta)*sin(phi) + // z = cos(phi) + // for -pi <= theta <= pi, 0 <= phi <= pi. The application can use + // this function to convert unit length vectors (x,y,z) to (theta,phi). + static void GetSphericalCoordinates(InputType x, InputType y, InputType z, + InputType& theta, InputType& phi) + { + // Assumes (x,y,z) is unit length. Returns -pi <= theta <= pi and + // 0 <= phiAngle <= pi. + + if (z < (InputType)1) + { + if (z > -(InputType)1) + { + theta = std::atan2(y, x); + phi = std::acos(z); + } + else + { + theta = -(InputType)GTE_C_PI; + phi = (InputType)GTE_C_PI; + } + } + else + { + theta = -(InputType)GTE_C_PI; + phi = (InputType)0; + } + } + + // The return value is 'true' if and only if the input point is in the + // convex hull of the input (theta,pi) array, in which case the + // interpolation is valid. + bool operator()(InputType theta, InputType phi, InputType& F) const + { + Vector2 angles{ theta, phi }; + InputType thetaDeriv, phiDeriv; + return (*mInterp)(angles, F, thetaDeriv, phiDeriv); + } + + private: + typedef Delaunay2Mesh TriangleMesh; + + std::vector> mWrapAngles; + Delaunay2 mDelaunay; + TriangleMesh mMesh; + std::vector mWrapF; + std::unique_ptr> mInterp; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpThinPlateSpline2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpThinPlateSpline2.h new file mode 100644 index 0000000..d143c50 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpThinPlateSpline2.h @@ -0,0 +1,273 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// WARNING. The implementation allows you to transform the inputs (x,y) to +// the unit square and perform the interpolation in that space. The idea is +// to keep the floating-point numbers to order 1 for numerical stability of +// the algorithm. The classical thin-plate spline algorithm does not include +// this transformation. The interpolation is invariant to translations and +// rotations of (x,y) but not to scaling. The following document is about +// thin plate splines. +// https://www.geometrictools.com/Documentation/ThinPlateSplines.pdf + +namespace WwiseGTE +{ + template + class IntpThinPlateSpline2 + { + public: + // Construction. Data points are (x,y,f(x,y)). The smoothing + // parameter must be nonnegative. + IntpThinPlateSpline2(int numPoints, Real const* X, Real const* Y, + Real const* F, Real smooth, bool transformToUnitSquare) + : + mNumPoints(numPoints), + mX(numPoints), + mY(numPoints), + mSmooth(smooth), + mA(numPoints), + mInitialized(false) + { + LogAssert(numPoints >= 3 && X != nullptr && Y != nullptr && + F != nullptr && smooth >= (Real)0, "Invalid input."); + + int i, row, col; + + if (transformToUnitSquare) + { + // Map input (x,y) to unit square. This is not part of the + // classical thin-plate spline algorithm because the + // interpolation is not invariant to scalings. + auto extreme = std::minmax_element(X, X + mNumPoints); + mXMin = *extreme.first; + mXMax = *extreme.second; + mXInvRange = (Real)1 / (mXMax - mXMin); + for (i = 0; i < mNumPoints; ++i) + { + mX[i] = (X[i] - mXMin) * mXInvRange; + } + + extreme = std::minmax_element(Y, Y + mNumPoints); + mYMin = *extreme.first; + mYMax = *extreme.second; + mYInvRange = (Real)1 / (mYMax - mYMin); + for (i = 0; i < mNumPoints; ++i) + { + mY[i] = (Y[i] - mYMin) * mYInvRange; + } + } + else + { + // The classical thin-plate spline uses the data as is. The + // values mXMax and mYMax are not used, but they are + // initialized anyway (to irrelevant numbers). + mXMin = (Real)0; + mXMax = (Real)1; + mXInvRange = (Real)1; + mYMin = (Real)0; + mYMax = (Real)1; + mYInvRange = (Real)1; + std::copy(X, X + mNumPoints, mX.begin()); + std::copy(Y, Y + mNumPoints, mY.begin()); + } + + // Compute matrix A = M + lambda*I [NxN matrix]. + GMatrix AMat(mNumPoints, mNumPoints); + for (row = 0; row < mNumPoints; ++row) + { + for (col = 0; col < mNumPoints; ++col) + { + if (row == col) + { + AMat(row, col) = mSmooth; + } + else + { + Real dx = mX[row] - mX[col]; + Real dy = mY[row] - mY[col]; + Real t = std::sqrt(dx * dx + dy * dy); + AMat(row, col) = Kernel(t); + } + } + } + + // Compute matrix B [Nx3 matrix]. + GMatrix BMat(mNumPoints, 3); + for (row = 0; row < mNumPoints; ++row) + { + BMat(row, 0) = (Real)1; + BMat(row, 1) = mX[row]; + BMat(row, 2) = mY[row]; + } + + // Compute A^{-1}. + bool invertible; + GMatrix invAMat = Inverse(AMat, &invertible); + if (!invertible) + { + return; + } + + // Compute P = B^T A^{-1} [3xN matrix]. + GMatrix PMat = MultiplyATB(BMat, invAMat); + + // Compute Q = P B = B^T A^{-1} B [3x3 matrix]. + GMatrix QMat = PMat * BMat; + + // Compute Q^{-1}. + GMatrix invQMat = Inverse(QMat, &invertible); + if (!invertible) + { + return; + } + + // Compute P*z. + std::array prod; + for (row = 0; row < 3; ++row) + { + prod[row] = (Real)0; + for (i = 0; i < mNumPoints; ++i) + { + prod[row] += PMat(row, i) * F[i]; + } + } + + // Compute 'b' vector for smooth thin plate spline. + for (row = 0; row < 3; ++row) + { + mB[row] = (Real)0; + for (i = 0; i < 3; ++i) + { + mB[row] += invQMat(row, i) * prod[i]; + } + } + + // Compute z-B*b. + std::vector tmp(mNumPoints); + for (row = 0; row < mNumPoints; ++row) + { + tmp[row] = F[row]; + for (i = 0; i < 3; ++i) + { + tmp[row] -= BMat(row, i) * mB[i]; + } + } + + // Compute 'a' vector for smooth thin plate spline. + for (row = 0; row < mNumPoints; ++row) + { + mA[row] = (Real)0; + for (i = 0; i < mNumPoints; ++i) + { + mA[row] += invAMat(row, i) * tmp[i]; + } + } + + mInitialized = true; + } + + // Check this after the constructor call to see whether the thin plate + // spline coefficients were successfully computed. If so, then calls + // to operator()(Real,Real) will work properly. TODO: This needs to + // be removed because the constructor now throws exceptions? + inline bool IsInitialized() const + { + return mInitialized; + } + + // Evaluate the interpolator. If IsInitialized() returns 'false', the + // operator will return std::numeric_limits::max(). + Real operator()(Real x, Real y) const + { + if (mInitialized) + { + // Map (x,y) to the unit square. + x = (x - mXMin) * mXInvRange; + y = (y - mYMin) * mYInvRange; + + Real result = mB[0] + mB[1] * x + mB[2] * y; + for (int i = 0; i < mNumPoints; ++i) + { + Real dx = x - mX[i]; + Real dy = y - mY[i]; + Real t = std::sqrt(dx * dx + dy * dy); + result += mA[i] * Kernel(t); + } + return result; + } + + return std::numeric_limits::max(); + } + + // Compute the functional value a^T*M*a when lambda is zero or + // lambda*w^T*(M+lambda*I)*w when lambda is positive. See the thin + // plate splines PDF for a description of these quantities. + Real ComputeFunctional() const + { + Real functional = (Real)0; + for (int row = 0; row < mNumPoints; ++row) + { + for (int col = 0; col < mNumPoints; ++col) + { + if (row == col) + { + functional += mSmooth * mA[row] * mA[col]; + } + else + { + Real dx = mX[row] - mX[col]; + Real dy = mY[row] - mY[col]; + Real t = std::sqrt(dx * dx + dy * dy); + functional += Kernel(t) * mA[row] * mA[col]; + } + } + } + + if (mSmooth > (Real)0) + { + functional *= mSmooth; + } + + return functional; + } + + private: + // Kernel(t) = t^2 * log(t^2) + static Real Kernel(Real t) + { + if (t > (Real)0) + { + Real t2 = t * t; + return t2 * std::log(t2); + } + return (Real)0; + } + + // Input data. + int mNumPoints; + std::vector mX; + std::vector mY; + Real mSmooth; + + // Thin plate spline coefficients. The A[] coefficients are associated + // with the Green's functions G(x,y,*) and the B[] coefficients are + // associated with the affine term B[0] + B[1]*x + B[2]*y. + std::vector mA; // mNumPoints elements + std::array mB; + + // Extent of input data. + Real mXMin, mXMax, mXInvRange; + Real mYMin, mYMax, mYInvRange; + + bool mInitialized; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpThinPlateSpline3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpThinPlateSpline3.h new file mode 100644 index 0000000..8ee3e41 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpThinPlateSpline3.h @@ -0,0 +1,289 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// WARNING. The implementation allows you to transform the inputs (x,y,z) to +// the unit cube and perform the interpolation in that space. The idea is +// to keep the floating-point numbers to order 1 for numerical stability of +// the algorithm. The classical thin-plate spline algorithm does not include +// this transformation. The interpolation is invariant to translations and +// rotations of (x,y,z) but not to scaling. The following document is about +// thin plate splines. +// https://www.geometrictools.com/Documentation/ThinPlateSplines.pdf + +namespace WwiseGTE +{ + template + class IntpThinPlateSpline3 + { + public: + // Construction. Data points are (x,y,z,f(x,y,z)). The smoothing + // parameter must be nonnegative + IntpThinPlateSpline3(int numPoints, Real const* X, Real const* Y, + Real const* Z, Real const* F, Real smooth, bool transformToUnitCube) + : + mNumPoints(numPoints), + mX(numPoints), + mY(numPoints), + mZ(numPoints), + mSmooth(smooth), + mA(numPoints), + mInitialized(false) + { + LogAssert(numPoints >= 4 && X != nullptr && Y != nullptr + && Z != nullptr && F != nullptr && smooth >= (Real)0, "Invalid input."); + + int i, row, col; + + if (transformToUnitCube) + { + // Map input (x,y,z) to unit cube. This is not part of the + // classical thin-plate spline algorithm, because the + // interpolation is not invariant to scalings. + auto extreme = std::minmax_element(X, X + mNumPoints); + mXMin = *extreme.first; + mXMax = *extreme.second; + mXInvRange = (Real)1 / (mXMax - mXMin); + for (i = 0; i < mNumPoints; ++i) + { + mX[i] = (X[i] - mXMin) * mXInvRange; + } + + extreme = std::minmax_element(Y, Y + mNumPoints); + mYMin = *extreme.first; + mYMax = *extreme.second; + mYInvRange = (Real)1 / (mYMax - mYMin); + for (i = 0; i < mNumPoints; ++i) + { + mY[i] = (Y[i] - mYMin) * mYInvRange; + } + + extreme = std::minmax_element(Z, Z + mNumPoints); + mZMin = *extreme.first; + mZMax = *extreme.second; + mZInvRange = (Real)1 / (mZMax - mZMin); + for (i = 0; i < mNumPoints; ++i) + { + mZ[i] = (Z[i] - mZMin) * mZInvRange; + } + } + else + { + // The classical thin-plate spline uses the data as is. The + // values mXMax, mYMax, and mZMax are not used, but they are + // initialized anyway (to irrelevant numbers). + mXMin = (Real)0; + mXMax = (Real)1; + mXInvRange = (Real)1; + mYMin = (Real)0; + mYMax = (Real)1; + mYInvRange = (Real)1; + mZMin = (Real)0; + mZMax = (Real)1; + mZInvRange = (Real)1; + std::copy(X, X + mNumPoints, mX.begin()); + std::copy(Y, Y + mNumPoints, mY.begin()); + std::copy(Z, Z + mNumPoints, mZ.begin()); + } + + // Compute matrix A = M + lambda*I [NxN matrix]. + GMatrix AMat(mNumPoints, mNumPoints); + for (row = 0; row < mNumPoints; ++row) + { + for (col = 0; col < mNumPoints; ++col) + { + if (row == col) + { + AMat(row, col) = mSmooth; + } + else + { + Real dx = mX[row] - mX[col]; + Real dy = mY[row] - mY[col]; + Real dz = mZ[row] - mZ[col]; + Real t = std::sqrt(dx * dx + dy * dy + dz * dz); + AMat(row, col) = Kernel(t); + } + } + } + + // Compute matrix B [Nx4 matrix]. + GMatrix BMat(mNumPoints, 4); + for (row = 0; row < mNumPoints; ++row) + { + BMat(row, 0) = (Real)1; + BMat(row, 1) = mX[row]; + BMat(row, 2) = mY[row]; + BMat(row, 3) = mZ[row]; + } + + // Compute A^{-1}. + bool invertible; + GMatrix invAMat = Inverse(AMat, &invertible); + if (!invertible) + { + return; + } + + // Compute P = B^t A^{-1} [4xN matrix]. + GMatrix PMat = MultiplyATB(BMat, invAMat); + + // Compute Q = P B = B^t A^{-1} B [4x4 matrix]. + GMatrix QMat = PMat * BMat; + + // Compute Q^{-1}. + GMatrix invQMat = Inverse(QMat, &invertible); + if (!invertible) + { + return; + } + + // Compute P*w. + std::array prod; + for (row = 0; row < 4; ++row) + { + prod[row] = (Real)0; + for (i = 0; i < mNumPoints; ++i) + { + prod[row] += PMat(row, i) * F[i]; + } + } + + // Compute 'b' vector for smooth thin plate spline. + for (row = 0; row < 4; ++row) + { + mB[row] = (Real)0; + for (i = 0; i < 4; ++i) + { + mB[row] += invQMat(row, i) * prod[i]; + } + } + + // Compute w-B*b. + std::vector tmp(mNumPoints); + for (row = 0; row < mNumPoints; ++row) + { + tmp[row] = F[row]; + for (i = 0; i < 4; ++i) + { + tmp[row] -= BMat(row, i) * mB[i]; + } + } + + // Compute 'a' vector for smooth thin plate spline. + for (row = 0; row < mNumPoints; ++row) + { + mA[row] = (Real)0; + for (i = 0; i < mNumPoints; ++i) + { + mA[row] += invAMat(row, i) * tmp[i]; + } + } + + mInitialized = true; + } + + // Check this after the constructor call to see whether the thin plate + // spline coefficients were successfully computed. If so, then calls + // to operator()(Real,Real,Real) will work properly. TODO: This + // needs to be removed because the constructor now throws exceptions? + inline bool IsInitialized() const + { + return mInitialized; + } + + // Evaluate the interpolator. If IsInitialized()returns 'false', the + // operator will return std::numeric_limits::max(). + Real operator()(Real x, Real y, Real z) const + { + if (mInitialized) + { + // Map (x,y,z) to the unit cube. + x = (x - mXMin) * mXInvRange; + y = (y - mYMin) * mYInvRange; + z = (z - mZMin) * mZInvRange; + + Real result = mB[0] + mB[1] * x + mB[2] * y + mB[3] * z; + for (int i = 0; i < mNumPoints; ++i) + { + Real dx = x - mX[i]; + Real dy = y - mY[i]; + Real dz = z - mZ[i]; + Real t = std::sqrt(dx * dx + dy * dy + dz * dz); + result += mA[i] * Kernel(t); + } + return result; + } + + return std::numeric_limits::max(); + } + + // Compute the functional value a^T*M*a when lambda is zero or + // lambda*w^T*(M+lambda*I)*w when lambda is positive. See the thin + // plate splines PDF for a description of these quantities. + Real ComputeFunctional() const + { + Real functional = (Real)0; + for (int row = 0; row < mNumPoints; ++row) + { + for (int col = 0; col < mNumPoints; ++col) + { + if (row == col) + { + functional += mSmooth * mA[row] * mA[col]; + } + else + { + Real dx = mX[row] - mX[col]; + Real dy = mY[row] - mY[col]; + Real dz = mZ[row] - mZ[col]; + Real t = std::sqrt(dx * dx + dy * dy + dz * dz); + functional += Kernel(t) * mA[row] * mA[col]; + } + } + } + + if (mSmooth > (Real)0) + { + functional *= mSmooth; + } + + return functional; + } + + private: + // Kernel(t) = -|t| + static Real Kernel(Real t) + { + return -std::fabs(t); + } + + // Input data. + int mNumPoints; + std::vector mX; + std::vector mY; + std::vector mZ; + Real mSmooth; + + // Thin plate spline coefficients. The A[] coefficients are associated + // with the Green's functions G(x,y,z,*) and the B[] coefficients are + // associated with the affine term B[0] + B[1]*x + B[2]*y + B[3]*z. + std::vector mA; // mNumPoints elements + std::array mB; + + // Extent of input data. + Real mXMin, mXMax, mXInvRange; + Real mYMin, mYMax, mYInvRange; + Real mZMin, mZMax, mZInvRange; + + bool mInitialized; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpTricubic3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpTricubic3.h new file mode 100644 index 0000000..89f0cef --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpTricubic3.h @@ -0,0 +1,528 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The interpolator is for uniformly spaced(x,y z)-values. The input samples +// must be stored in lexicographical order to represent f(x,y,z); that is, +// F[c + xBound*(r + yBound*s)] corresponds to f(x,y,z), where c is the index +// corresponding to x, r is the index corresponding to y, and s is the index +// corresponding to z. Exact interpolation is achieved by setting catmullRom +// to 'true', giving you the Catmull-Rom blending matrix. If a smooth +// interpolation is desired, set catmullRom to 'false' to obtain B-spline +// blending. + +namespace WwiseGTE +{ + template + class IntpTricubic3 + { + public: + // Construction. + IntpTricubic3(int xBound, int yBound, int zBound, Real xMin, + Real xSpacing, Real yMin, Real ySpacing, Real zMin, Real zSpacing, + Real const* F, bool catmullRom) + : + mXBound(xBound), + mYBound(yBound), + mZBound(zBound), + mQuantity(xBound * yBound * zBound), + mXMin(xMin), + mXSpacing(xSpacing), + mYMin(yMin), + mYSpacing(ySpacing), + mZMin(zMin), + mZSpacing(zSpacing), + mF(F) + { + // At least a 4x4x4 block of data points are needed to construct + // the tricubic interpolation. + LogAssert(xBound >= 4 && yBound >= 4 && zBound >= 4 && F != nullptr + && xSpacing > (Real)0 && ySpacing > (Real)0 && zSpacing > (Real)0, + "Invalid input."); + + mXMax = mXMin + mXSpacing * static_cast(mXBound - 1); + mInvXSpacing = (Real)1 / mXSpacing; + mYMax = mYMin + mYSpacing * static_cast(mYBound - 1); + mInvYSpacing = (Real)1 / mYSpacing; + mZMax = mZMin + mZSpacing * static_cast(mZBound - 1); + mInvZSpacing = (Real)1 / mZSpacing; + + if (catmullRom) + { + mBlend[0][0] = (Real)0; + mBlend[0][1] = (Real)-0.5; + mBlend[0][2] = (Real)1; + mBlend[0][3] = (Real)-0.5; + mBlend[1][0] = (Real)1; + mBlend[1][1] = (Real)0; + mBlend[1][2] = (Real)-2.5; + mBlend[1][3] = (Real)1.5; + mBlend[2][0] = (Real)0; + mBlend[2][1] = (Real)0.5; + mBlend[2][2] = (Real)2; + mBlend[2][3] = (Real)-1.5; + mBlend[3][0] = (Real)0; + mBlend[3][1] = (Real)0; + mBlend[3][2] = (Real)-0.5; + mBlend[3][3] = (Real)0.5; + } + else + { + mBlend[0][0] = (Real)1 / (Real)6; + mBlend[0][1] = (Real)-3 / (Real)6; + mBlend[0][2] = (Real)3 / (Real)6; + mBlend[0][3] = (Real)-1 / (Real)6;; + mBlend[1][0] = (Real)4 / (Real)6; + mBlend[1][1] = (Real)0 / (Real)6; + mBlend[1][2] = (Real)-6 / (Real)6; + mBlend[1][3] = (Real)3 / (Real)6; + mBlend[2][0] = (Real)1 / (Real)6; + mBlend[2][1] = (Real)3 / (Real)6; + mBlend[2][2] = (Real)3 / (Real)6; + mBlend[2][3] = (Real)-3 / (Real)6; + mBlend[3][0] = (Real)0 / (Real)6; + mBlend[3][1] = (Real)0 / (Real)6; + mBlend[3][2] = (Real)0 / (Real)6; + mBlend[3][3] = (Real)1 / (Real)6; + } + } + + // Member access. + inline int GetXBound() const + { + return mXBound; + } + + inline int GetYBound() const + { + return mYBound; + } + + inline int GetZBound() const + { + return mZBound; + } + + inline int GetQuantity() const + { + return mQuantity; + } + + inline Real const* GetF() const + { + return mF; + } + + inline Real GetXMin() const + { + return mXMin; + } + + inline Real GetXMax() const + { + return mXMax; + } + + inline Real GetXSpacing() const + { + return mXSpacing; + } + + inline Real GetYMin() const + { + return mYMin; + } + + inline Real GetYMax() const + { + return mYMax; + } + + inline Real GetYSpacing() const + { + return mYSpacing; + } + + inline Real GetZMin() const + { + return mZMin; + } + + inline Real GetZMax() const + { + return mZMax; + } + + inline Real GetZSpacing() const + { + return mZSpacing; + } + + // Evaluate the function and its derivatives. The functions clamp the + // inputs to xmin <= x <= xmax, ymin <= y <= ymax, and zmin <= z <= zmax. + // The first operator is for function evaluation. The second operator is + // for function or derivative evaluations. The xOrder argument is the + // order of the x-derivative, the yOrder argument is the order of the + // y-derivative, and the zOrder argument is the order of the z-derivative. + // All orders are zero to get the function value itself. + Real operator()(Real x, Real y, Real z) const + { + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + // Compute z-index and clamp to image. + Real zIndex = (z - mZMin) * mInvZSpacing; + int iz = static_cast(zIndex); + if (iz < 0) + { + iz = 0; + } + else if (iz >= mZBound) + { + iz = mZBound - 1; + } + + std::array U; + U[0] = (Real)1; + U[1] = xIndex - ix; + U[2] = U[1] * U[1]; + U[3] = U[1] * U[2]; + + std::array V; + V[0] = (Real)1; + V[1] = yIndex - iy; + V[2] = V[1] * V[1]; + V[3] = V[1] * V[2]; + + std::array W; + W[0] = (Real)1; + W[1] = zIndex - iz; + W[2] = W[1] * W[1]; + W[3] = W[1] * W[2]; + + // Compute P = M*U, Q = M*V, R = M*W. + std::array P, Q, R; + for (int row = 0; row < 4; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + R[row] = (Real)0; + for (int col = 0; col < 4; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + R[row] += mBlend[row][col] * W[col]; + } + } + + // Compute the tensor product (M*U)(M*V)(M*W)*D where D is the 4x4x4 + // subimage containing (x,y,z). + --ix; + --iy; + --iz; + Real result = (Real)0; + for (int slice = 0; slice < 4; ++slice) + { + int zClamp = iz + slice; + if (zClamp < 0) + { + zClamp = 0; + } + else if (zClamp > mZBound - 1) + { + zClamp = mZBound - 1; + } + + for (int row = 0; row < 4; ++row) + { + int yClamp = iy + row; + if (yClamp < 0) + { + yClamp = 0; + } + else if (yClamp > mYBound - 1) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 4; ++col) + { + int xClamp = ix + col; + if (xClamp < 0) + { + xClamp = 0; + } + else if (xClamp > mXBound - 1) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * R[slice] * + mF[xClamp + mXBound * (yClamp + mYBound * zClamp)]; + } + } + } + + return result; + } + + Real operator()(int xOrder, int yOrder, int zOrder, Real x, Real y, Real z) const + { + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + // Compute z-index and clamp to image. + Real zIndex = (z - mZMin) * mInvZSpacing; + int iz = static_cast(zIndex); + if (iz < 0) + { + iz = 0; + } + else if (iz >= mZBound) + { + iz = mZBound - 1; + } + + std::array U; + Real dx, xMult; + switch (xOrder) + { + case 0: + dx = xIndex - ix; + U[0] = (Real)1; + U[1] = dx; + U[2] = dx * U[1]; + U[3] = dx * U[2]; + xMult = (Real)1; + break; + case 1: + dx = xIndex - ix; + U[0] = (Real)0; + U[1] = (Real)1; + U[2] = (Real)2 * dx; + U[3] = (Real)3 * dx * dx; + xMult = mInvXSpacing; + break; + case 2: + dx = xIndex - ix; + U[0] = (Real)0; + U[1] = (Real)0; + U[2] = (Real)2; + U[3] = (Real)6 * dx; + xMult = mInvXSpacing * mInvXSpacing; + break; + case 3: + U[0] = (Real)0; + U[1] = (Real)0; + U[2] = (Real)0; + U[3] = (Real)6; + xMult = mInvXSpacing * mInvXSpacing * mInvXSpacing; + break; + default: + return (Real)0; + } + + std::array V; + Real dy, yMult; + switch (yOrder) + { + case 0: + dy = yIndex - iy; + V[0] = (Real)1; + V[1] = dy; + V[2] = dy * V[1]; + V[3] = dy * V[2]; + yMult = (Real)1; + break; + case 1: + dy = yIndex - iy; + V[0] = (Real)0; + V[1] = (Real)1; + V[2] = (Real)2 * dy; + V[3] = (Real)3 * dy * dy; + yMult = mInvYSpacing; + break; + case 2: + dy = yIndex - iy; + V[0] = (Real)0; + V[1] = (Real)0; + V[2] = (Real)2; + V[3] = (Real)6 * dy; + yMult = mInvYSpacing * mInvYSpacing; + break; + case 3: + V[0] = (Real)0; + V[1] = (Real)0; + V[2] = (Real)0; + V[3] = (Real)6; + yMult = mInvYSpacing * mInvYSpacing * mInvYSpacing; + break; + default: + return (Real)0; + } + + std::array W; + Real dz, zMult; + switch (zOrder) + { + case 0: + dz = zIndex - iz; + W[0] = (Real)1; + W[1] = dz; + W[2] = dz * W[1]; + W[3] = dz * W[2]; + zMult = (Real)1; + break; + case 1: + dz = zIndex - iz; + W[0] = (Real)0; + W[1] = (Real)1; + W[2] = (Real)2 * dz; + W[3] = (Real)3 * dz * dz; + zMult = mInvZSpacing; + break; + case 2: + dz = zIndex - iz; + W[0] = (Real)0; + W[1] = (Real)0; + W[2] = (Real)2; + W[3] = (Real)6 * dz; + zMult = mInvZSpacing * mInvZSpacing; + break; + case 3: + W[0] = (Real)0; + W[1] = (Real)0; + W[2] = (Real)0; + W[3] = (Real)6; + zMult = mInvZSpacing * mInvZSpacing * mInvZSpacing; + break; + default: + return (Real)0; + } + + // Compute P = M*U, Q = M*V, and R = M*W. + std::array P, Q, R; + for (int row = 0; row < 4; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + R[row] = (Real)0; + for (int col = 0; col < 4; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + R[row] += mBlend[row][col] * W[col]; + } + } + + // Compute the tensor product (M*U)(M*V)(M*W)*D where D is the 4x4x4 + // subimage containing (x,y,z). + --ix; + --iy; + --iz; + Real result = (Real)0; + for (int slice = 0; slice < 4; ++slice) + { + int zClamp = iz + slice; + if (zClamp < 0) + { + zClamp = 0; + } + else if (zClamp > mZBound - 1) + { + zClamp = mZBound - 1; + } + + for (int row = 0; row < 4; ++row) + { + int yClamp = iy + row; + if (yClamp < 0) + { + yClamp = 0; + } + else if (yClamp > mYBound - 1) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 4; ++col) + { + int xClamp = ix + col; + if (xClamp < 0) + { + xClamp = 0; + } + else if (xClamp > mXBound - 1) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * R[slice] * + mF[xClamp + mXBound * (yClamp + mYBound * zClamp)]; + } + } + } + result *= xMult * yMult * zMult; + + return result; + } + + private: + int mXBound, mYBound, mZBound, mQuantity; + Real mXMin, mXMax, mXSpacing, mInvXSpacing; + Real mYMin, mYMax, mYSpacing, mInvYSpacing; + Real mZMin, mZMax, mZSpacing, mInvZSpacing; + Real const* mF; + std::array, 4> mBlend; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpTrilinear3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpTrilinear3.h new file mode 100644 index 0000000..713f365 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpTrilinear3.h @@ -0,0 +1,398 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The interpolator is for uniformly spaced(x,y z)-values. The input samples +// must be stored in lexicographical order to represent f(x,y,z); that is, +// F[c + xBound*(r + yBound*s)] corresponds to f(x,y,z), where c is the index +// corresponding to x, r is the index corresponding to y, and s is the index +// corresponding to z. + +namespace WwiseGTE +{ + template + class IntpTrilinear3 + { + public: + // Construction. + IntpTrilinear3(int xBound, int yBound, int zBound, Real xMin, + Real xSpacing, Real yMin, Real ySpacing, Real zMin, Real zSpacing, Real const* F) + : + mXBound(xBound), + mYBound(yBound), + mZBound(zBound), + mQuantity(xBound* yBound* zBound), + mXMin(xMin), + mXSpacing(xSpacing), + mYMin(yMin), + mYSpacing(ySpacing), + mZMin(zMin), + mZSpacing(zSpacing), + mF(F) + { + // At least a 2x2x2 block of data points are needed to construct + // the trilinear interpolation. + LogAssert(xBound >= 2 && yBound >= 2 && zBound >= 2 && F != nullptr + && xSpacing > (Real)0 && ySpacing > (Real)0 && zSpacing > (Real)0, + "Invalid input."); + + mXMax = mXMin + mXSpacing * static_cast(mXBound - 1); + mInvXSpacing = (Real)1 / mXSpacing; + mYMax = mYMin + mYSpacing * static_cast(mYBound - 1); + mInvYSpacing = (Real)1 / mYSpacing; + mZMax = mZMin + mZSpacing * static_cast(mZBound - 1); + mInvZSpacing = (Real)1 / mZSpacing; + + mBlend[0][0] = (Real)1; + mBlend[0][1] = (Real)-1; + mBlend[1][0] = (Real)0; + mBlend[1][1] = (Real)1; + } + + // Member access. + inline int GetXBound() const + { + return mXBound; + } + + inline int GetYBound() const + { + return mYBound; + } + + inline int GetZBound() const + { + return mZBound; + } + + inline int GetQuantity() const + { + return mQuantity; + } + + inline Real const* GetF() const + { + return mF; + } + + inline Real GetXMin() const + { + return mXMin; + } + + inline Real GetXMax() const + { + return mXMax; + } + + inline Real GetXSpacing() const + { + return mXSpacing; + } + + inline Real GetYMin() const + { + return mYMin; + } + + inline Real GetYMax() const + { + return mYMax; + } + + inline Real GetYSpacing() const + { + return mYSpacing; + } + + inline Real GetZMin() const + { + return mZMin; + } + + inline Real GetZMax() const + { + return mZMax; + } + + inline Real GetZSpacing() const + { + return mZSpacing; + } + + // Evaluate the function and its derivatives. The functions clamp the + // inputs to xmin <= x <= xmax, ymin <= y <= ymax and + // zmin <= z <= zmax. The first operator is for function evaluation. + // The second operator is for function or derivative evaluations. The + // xOrder argument is the order of the x-derivative, the yOrder + // argument is the order of the y-derivative, and the zOrder argument + // is the order of the z-derivative. All orders are zero to get the + // function value itself. + Real operator()(Real x, Real y, Real z) const + { + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + // Compute z-index and clamp to image. + Real zIndex = (z - mZMin) * mInvZSpacing; + int iz = static_cast(zIndex); + if (iz < 0) + { + iz = 0; + } + else if (iz >= mZBound) + { + iz = mZBound - 1; + } + + std::array U; + U[0] = (Real)1; + U[1] = xIndex - ix; + + std::array V; + V[0] = (Real)1; + V[1] = yIndex - iy; + + std::array W; + W[0] = (Real)1; + W[1] = zIndex - iz; + + // Compute P = M*U, Q = M*V, R = M*W. + std::array P, Q, R; + for (int row = 0; row < 2; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + R[row] = (Real)0; + for (int col = 0; col < 2; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + R[row] += mBlend[row][col] * W[col]; + } + } + + // compute the tensor product (M*U)(M*V)(M*W)*D where D is the 2x2x2 + // subimage containing (x,y,z) + Real result = (Real)0; + for (int slice = 0; slice < 2; ++slice) + { + int zClamp = iz + slice; + if (zClamp >= mZBound) + { + zClamp = mZBound - 1; + } + + for (int row = 0; row < 2; ++row) + { + int yClamp = iy + row; + if (yClamp >= mYBound) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 2; ++col) + { + int xClamp = ix + col; + if (xClamp >= mXBound) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * R[slice] * + mF[xClamp + mXBound * (yClamp + mYBound * zClamp)]; + } + } + } + + return result; + } + + Real operator()(int xOrder, int yOrder, int zOrder, Real x, Real y, Real z) const + { + // Compute x-index and clamp to image. + Real xIndex = (x - mXMin) * mInvXSpacing; + int ix = static_cast(xIndex); + if (ix < 0) + { + ix = 0; + } + else if (ix >= mXBound) + { + ix = mXBound - 1; + } + + // Compute y-index and clamp to image. + Real yIndex = (y - mYMin) * mInvYSpacing; + int iy = static_cast(yIndex); + if (iy < 0) + { + iy = 0; + } + else if (iy >= mYBound) + { + iy = mYBound - 1; + } + + // Compute z-index and clamp to image. + Real zIndex = (z - mZMin) * mInvZSpacing; + int iz = static_cast(zIndex); + if (iz < 0) + { + iz = 0; + } + else if (iz >= mZBound) + { + iz = mZBound - 1; + } + + std::array U; + Real dx, xMult; + switch (xOrder) + { + case 0: + dx = xIndex - ix; + U[0] = (Real)1; + U[1] = dx; + xMult = (Real)1; + break; + case 1: + dx = xIndex - ix; + U[0] = (Real)0; + U[1] = (Real)1; + xMult = mInvXSpacing; + break; + default: + return (Real)0; + } + + std::array V; + Real dy, yMult; + switch (yOrder) + { + case 0: + dy = yIndex - iy; + V[0] = (Real)1; + V[1] = dy; + yMult = (Real)1; + break; + case 1: + dy = yIndex - iy; + V[0] = (Real)0; + V[1] = (Real)1; + yMult = mInvYSpacing; + break; + default: + return (Real)0; + } + + std::array W; + Real dz, zMult; + switch (zOrder) + { + case 0: + dz = zIndex - iz; + W[0] = (Real)1; + W[1] = dz; + zMult = (Real)1; + break; + case 1: + dz = zIndex - iz; + W[0] = (Real)0; + W[1] = (Real)1; + zMult = mInvZSpacing; + break; + default: + return (Real)0; + } + + // Compute P = M*U, Q = M*V, and R = M*W. + std::array P, Q, R; + for (int row = 0; row < 2; ++row) + { + P[row] = (Real)0; + Q[row] = (Real)0; + R[row] = (Real)0; + for (int col = 0; col < 2; ++col) + { + P[row] += mBlend[row][col] * U[col]; + Q[row] += mBlend[row][col] * V[col]; + R[row] += mBlend[row][col] * W[col]; + } + } + + // Compute the tensor product (M*U)(M*V)(M*W)*D where D is the 2x2x2 + // subimage containing (x,y,z). + Real result = (Real)0; + for (int slice = 0; slice < 2; ++slice) + { + int zClamp = iz + slice; + if (zClamp >= mZBound) + { + zClamp = mZBound - 1; + } + + for (int row = 0; row < 2; ++row) + { + int yClamp = iy + row; + if (yClamp >= mYBound) + { + yClamp = mYBound - 1; + } + + for (int col = 0; col < 2; ++col) + { + int xClamp = ix + col; + if (xClamp >= mXBound) + { + xClamp = mXBound - 1; + } + + result += P[col] * Q[row] * R[slice] * + mF[xClamp + mXBound * (yClamp + mYBound * zClamp)]; + } + } + } + result *= xMult * yMult * zMult; + + return result; + } + + private: + int mXBound, mYBound, mZBound, mQuantity; + Real mXMin, mXMax, mXSpacing, mInvXSpacing; + Real mYMin, mYMax, mYSpacing, mInvYSpacing; + Real mZMin, mZMax, mZSpacing, mInvZSpacing; + Real const* mF; + std::array, 2> mBlend; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpVectorField2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpVectorField2.h new file mode 100644 index 0000000..ff6907c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntpVectorField2.h @@ -0,0 +1,75 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// Given points (x0[i],y0[i]) which are mapped to (x1[i],y1[i]) for +// 0 <= i < N, interpolate positions (xIn,yIn) to (xOut,yOut). + +namespace WwiseGTE +{ + template + class IntpVectorField2 + { + public: + // Construction and destruction. + ~IntpVectorField2() = default; + + IntpVectorField2(int numPoints, Vector2 const* domain, + Vector2 const* range) + : + mMesh(mDelaunay) + { + // Repackage the output vectors into individual components. This + // is required because of the format that the quadratic + // interpolator expects for its input data. + mXRange.resize(numPoints); + mYRange.resize(numPoints); + for (int i = 0; i < numPoints; ++i) + { + mXRange[i] = range[i][0]; + mYRange[i] = range[i][1]; + } + + // Common triangulator for the interpolators. + mDelaunay(numPoints, &domain[0], (ComputeType)0); + + // Create interpolator for x-coordinate of vector field. + mXInterp = std::make_unique>( + mMesh, &mXRange[0], (InputType)1); + + // Create interpolator for y-coordinate of vector field, but share the + // already created triangulation for the x-interpolator. + mYInterp = std::make_unique>( + mMesh, &mYRange[0], (InputType)1); + } + + // The return value is 'true' if and only if (xIn,yIn) is in the + // convex hull of the input domain points, in which case the + // interpolation is valid. + bool operator()(Vector2 const& input, Vector2& output) const + { + InputType xDeriv, yDeriv; + return (*mXInterp)(input, output[0], xDeriv, yDeriv) + && (*mYInterp)(input, output[1], xDeriv, yDeriv); + } + + protected: + typedef Delaunay2Mesh TriangleMesh; + Delaunay2 mDelaunay; + TriangleMesh mMesh; + + std::vector mXRange; + std::vector mYRange; + std::unique_ptr> mXInterp; + std::unique_ptr> mYInterp; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox2AlignedBox2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox2AlignedBox2.h new file mode 100644 index 0000000..a0dd874 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox2AlignedBox2.h @@ -0,0 +1,93 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The aligned-aligned queries use simple min-max comparisions. The +// interesection of aligned boxes is an aligned box, possibly degenerate, +// where min[d] == max[d] for at least one dimension d. + +namespace WwiseGTE +{ + template + class TIQuery, AlignedBox2> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(AlignedBox2 const& box0, AlignedBox2 const& box1) + { + Result result; + for (int i = 0; i < 2; i++) + { + if (box0.max[i] < box1.min[i] || box0.min[i] > box1.max[i]) + { + result.intersect = false; + return result; + } + } + result.intersect = true; + return result; + } + }; + + template + class FIQuery, AlignedBox2> + { + public: + struct Result + { + bool intersect; + AlignedBox2 box; + }; + + Result operator()(AlignedBox2 const& box0, AlignedBox2 const& box1) + { + Result result; + for (int i = 0; i < 2; i++) + { + if (box0.max[i] < box1.min[i] || box0.min[i] > box1.max[i]) + { + result.intersect = false; + return result; + } + } + + for (int i = 0; i < 2; i++) + { + if (box0.max[i] <= box1.max[i]) + { + result.box.max[i] = box0.max[i]; + } + else + { + result.box.max[i] = box1.max[i]; + } + + if (box0.min[i] <= box1.min[i]) + { + result.box.min[i] = box1.min[i]; + } + else + { + result.box.min[i] = box0.min[i]; + } + } + result.intersect = true; + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox2Circle2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox2Circle2.h new file mode 100644 index 0000000..b9957c4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox2Circle2.h @@ -0,0 +1,348 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// The find-intersection query is based on the document +// https://www.geometrictools.com/Documentation/IntersectionMovingCircleRectangle.pdf + +namespace WwiseGTE +{ + template + class TIQuery, Circle2> + { + public: + // The intersection query considers the box and circle to be solids; + // that is, the circle object includes the region inside the circular + // boundary and the box object includes the region inside the + // rectangular boundary. If the circle object and box object + // overlap, the objects intersect. + struct Result + { + bool intersect; + }; + + Result operator()(AlignedBox2 const& box, Circle2 const& circle) + { + DCPQuery, AlignedBox2> pbQuery; + auto pbResult = pbQuery(circle.center, box); + Result result; + result.intersect = (pbResult.sqrDistance <= circle.radius * circle.radius); + return result; + } + }; + + template + class FIQuery, Circle2> + { + public: + // Currently, only a dynamic query is supported. A static query will + // need to compute the intersection set of (solid) box and circle. + struct Result + { + // The cases are + // 1. Objects initially overlapping. The contactPoint is only one + // of infinitely many points in the overlap. + // intersectionType = -1 + // contactTime = 0 + // contactPoint = circle.center + // 2. Objects initially separated but do not intersect later. The + // contactTime and contactPoint are invalid. + // intersectionType = 0 + // contactTime = 0 + // contactPoint = (0,0) + // 3. Objects initially separated but intersect later. + // intersectionType = +1 + // contactTime = first time T > 0 + // contactPoint = corresponding first contact + int intersectionType; + Real contactTime; + Vector2 contactPoint; + + // TODO: To support arbitrary precision for the contactTime, + // return q0, q1 and q2 where contactTime = (q0 - sqrt(q1)) / q2. + // The caller can compute contactTime to desired number of digits + // of precision. These are valid when intersectionType is +1 but + // are set to zero (invalid) in the other cases. Do the same for + // the contactPoint. + }; + + Result operator()(AlignedBox2 const& box, Vector2 const& boxVelocity, + Circle2 const& circle, Vector2 const& circleVelocity) + { + Result result = { 0, (Real)0, { (Real)0, (Real)0 } }; + + // Translate the circle and box so that the box center becomes + // the origin. Compute the velocity of the circle relative to + // the box. + Vector2 boxCenter = (box.max + box.min) * (Real)0.5; + Vector2 extent = (box.max - box.min) * (Real)0.5; + Vector2 C = circle.center - boxCenter; + Vector2 V = circleVelocity - boxVelocity; + + // Change signs on components, if necessary, to transform C to the + // first quadrant. Adjust the velocity accordingly. + Real sign[2]; + for (int i = 0; i < 2; ++i) + { + if (C[i] >= (Real)0) + { + sign[i] = (Real)1; + } + else + { + C[i] = -C[i]; + V[i] = -V[i]; + sign[i] = (Real)-1; + } + } + + DoQuery(extent, C, circle.radius, V, result); + + if (result.intersectionType != 0) + { + // Translate back to the original coordinate system. + for (int i = 0; i < 2; ++i) + { + if (sign[i] < (Real)0) + { + result.contactPoint[i] = -result.contactPoint[i]; + } + } + + result.contactPoint += boxCenter; + } + return result; + } + + protected: + void DoQuery(Vector2 const& K, Vector2 const& C, + Real radius, Vector2 const& V, Result& result) + { + Vector2 delta = C - K; + if (delta[1] <= radius) + { + if (delta[0] <= radius) + { + if (delta[1] <= (Real)0) + { + if (delta[0] <= (Real)0) + { + InteriorOverlap(C, result); + } + else + { + EdgeOverlap(0, 1, K, C, delta, radius, result); + } + } + else + { + if (delta[0] <= (Real)0) + { + EdgeOverlap(1, 0, K, C, delta, radius, result); + } + else + { + if (Dot(delta, delta) <= radius * radius) + { + VertexOverlap(K, delta, radius, result); + } + else + { + VertexSeparated(K, delta, V, radius, result); + } + } + + } + } + else + { + EdgeUnbounded(0, 1, K, C, radius, delta, V, result); + } + } + else + { + if (delta[0] <= radius) + { + EdgeUnbounded(1, 0, K, C, radius, delta, V, result); + } + else + { + VertexUnbounded(K, C, radius, delta, V, result); + } + } + } + + private: + void InteriorOverlap(Vector2 const& C, Result& result) + { + result.intersectionType = -1; + result.contactTime = (Real)0; + result.contactPoint = C; + } + + void EdgeOverlap(int i0, int i1, Vector2 const& K, Vector2 const& C, + Vector2 const& delta, Real radius, Result& result) + { + result.intersectionType = (delta[i0] < radius ? -1 : 1); + result.contactTime = (Real)0; + result.contactPoint[i0] = K[i0]; + result.contactPoint[i1] = C[i1]; + } + + void VertexOverlap(Vector2 const& K0, Vector2 const& delta, + Real radius, Result& result) + { + Real sqrDistance = delta[0] * delta[0] + delta[1] * delta[1]; + Real sqrRadius = radius * radius; + result.intersectionType = (sqrDistance < sqrRadius ? -1 : 1); + result.contactTime = (Real)0; + result.contactPoint = K0; + } + + void VertexSeparated(Vector2 const& K0, Vector2 const& delta0, + Vector2 const& V, Real radius, Result& result) + { + Real q0 = -Dot(V, delta0); + if (q0 > (Real)0) + { + Real dotVPerpD0 = Dot(V, Perp(delta0)); + Real q2 = Dot(V, V); + Real q1 = radius * radius * q2 - dotVPerpD0 * dotVPerpD0; + if (q1 >= (Real)0) + { + IntersectsVertex(0, 1, K0, q0, q1, q2, result); + } + } + } + + void EdgeUnbounded(int i0, int i1, Vector2 const& K0, Vector2 const& C, + Real radius, Vector2 const& delta0, Vector2 const& V, Result& result) + { + if (V[i0] < (Real)0) + { + Real dotVPerpD0 = V[i0] * delta0[i1] - V[i1] * delta0[i0]; + if (radius * V[i1] + dotVPerpD0 >= (Real)0) + { + Vector2 K1, delta1; + K1[i0] = K0[i0]; + K1[i1] = -K0[i1]; + delta1[i0] = C[i0] - K1[i0]; + delta1[i1] = C[i1] - K1[i1]; + Real dotVPerpD1 = V[i0] * delta1[i1] - V[i1] * delta1[i0]; + if (radius * V[i1] + dotVPerpD1 <= (Real)0) + { + IntersectsEdge(i0, i1, K0, C, radius, V, result); + } + else + { + Real q2 = Dot(V, V); + Real q1 = radius * radius * q2 - dotVPerpD1 * dotVPerpD1; + if (q1 >= (Real)0) + { + Real q0 = -(V[i0] * delta1[i0] + V[i1] * delta1[i1]); + IntersectsVertex(i0, i1, K1, q0, q1, q2, result); + } + } + } + else + { + Real q2 = Dot(V, V); + Real q1 = radius * radius * q2 - dotVPerpD0 * dotVPerpD0; + if (q1 >= (Real)0) + { + Real q0 = -(V[i0] * delta0[i0] + V[i1] * delta0[i1]); + IntersectsVertex(i0, i1, K0, q0, q1, q2, result); + } + } + } + } + + void VertexUnbounded(Vector2 const& K0, Vector2 const& C, Real radius, + Vector2 const& delta0, Vector2 const& V, Result& result) + { + if (V[0] < (Real)0 && V[1] < (Real)0) + { + Real dotVPerpD0 = Dot(V, Perp(delta0)); + if (radius * V[0] - dotVPerpD0 <= (Real)0) + { + if (-radius * V[1] - dotVPerpD0 >= (Real)0) + { + Real q2 = Dot(V, V); + Real q1 = radius * radius * q2 - dotVPerpD0 * dotVPerpD0; + Real q0 = -Dot(V, delta0); + IntersectsVertex(0, 1, K0, q0, q1, q2, result); + } + else + { + Vector2 K1{ K0[0], -K0[1] }; + Vector2 delta1 = C - K1; + Real dotVPerpD1 = Dot(V, Perp(delta1)); + if (-radius * V[1] - dotVPerpD1 >= (Real)0) + { + IntersectsEdge(0, 1, K0, C, radius, V, result); + } + else + { + Real q2 = Dot(V, V); + Real q1 = radius * radius * q2 - dotVPerpD1 * dotVPerpD1; + if (q1 >= (Real)0) + { + Real q0 = -Dot(V, delta1); + IntersectsVertex(0, 1, K1, q0, q1, q2, result); + } + } + } + } + else + { + Vector2 K2{ -K0[0], K0[1] }; + Vector2 delta2 = C - K2; + Real dotVPerpD2 = Dot(V, Perp(delta2)); + if (radius * V[0] - dotVPerpD2 <= (Real)0) + { + IntersectsEdge(1, 0, K0, C, radius, V, result); + } + else + { + Real q2 = Dot(V, V); + Real q1 = radius * radius * q2 - dotVPerpD2 * dotVPerpD2; + if (q1 >= (Real)0) + { + Real q0 = -Dot(V, delta2); + IntersectsVertex(1, 0, K2, q0, q1, q2, result); + } + } + } + } + } + + void IntersectsVertex(int i0, int i1, Vector2 const& K, + Real q0, Real q1, Real q2, Result& result) + { + result.intersectionType = +1; + result.contactTime = (q0 - std::sqrt(q1)) / q2; + result.contactPoint[i0] = K[i0]; + result.contactPoint[i1] = K[i1]; + } + + void IntersectsEdge(int i0, int i1, Vector2 const& K0, Vector2 const& C, + Real radius, Vector2 const& V, Result& result) + { + result.intersectionType = +1; + result.contactTime = (K0[i0] + radius - C[i0]) / V[i0]; + result.contactPoint[i0] = K0[i0]; + result.contactPoint[i1] = C[i1] + result.contactTime * V[i1]; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox2OrientedBox2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox2OrientedBox2.h new file mode 100644 index 0000000..7291eb4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox2OrientedBox2.h @@ -0,0 +1,102 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection query uses the method of separating axes. +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The set of potential separating directions includes the 2 edge normals +// of box0 and the 2 edge normals of box1. The integer 'separating' +// identifies the axis that reported separation; there may be more than one +// but only one is reported. The value is 0 when box0.axis[0] separates, +// 1 when box0.axis[1] separates, 2 when box1.axis[0] separates, or 3 when +// box1.axis[1] separates. + +namespace WwiseGTE +{ + template + class TIQuery, OrientedBox2> + { + public: + struct Result + { + bool intersect; + int separating; + }; + + Result operator()(AlignedBox2 const& box0, OrientedBox2 const& box1) + { + Result result; + + // Get the centered form of the aligned box. The axes are + // implicitly A0[0] = (1,0) and A0[1] = (0,1). + Vector2 C0, E0; + box0.GetCenteredForm(C0, E0); + + // Convenience variables. + Vector2 const& C1 = box1.center; + Vector2 const* A1 = &box1.axis[0]; + Vector2 const& E1 = box1.extent; + + // Compute difference of box centers. + Vector2 D = C1 - C0; + + Real absDot01[2][2], rSum; + + // Test box0.axis[0] = (1,0). + absDot01[0][0] = std::fabs(A1[0][0]); + absDot01[0][1] = std::fabs(A1[1][0]); + rSum = E0[0] + E1[0] * absDot01[0][0] + E1[1] * absDot01[0][1]; + if (std::fabs(D[0]) > rSum) + { + result.intersect = false; + result.separating = 0; + return result; + } + + // Test axis box0.axis[1] = (0,1). + absDot01[1][0] = std::fabs(A1[0][1]); + absDot01[1][1] = std::fabs(A1[1][1]); + rSum = E0[1] + E1[0] * absDot01[1][0] + E1[1] * absDot01[1][1]; + if (std::fabs(D[1]) > rSum) + { + result.intersect = false; + result.separating = 1; + return result; + } + + // Test axis box1.axis[0]. + rSum = E1[0] + E0[0] * absDot01[0][0] + E0[1] * absDot01[1][0]; + if (std::fabs(Dot(A1[0], D)) > rSum) + { + result.intersect = false; + result.separating = 2; + return result; + } + + // Test axis box1.axis[1]. + rSum = E1[1] + E0[0] * absDot01[0][1] + E0[1] * absDot01[1][1]; + if (std::fabs(Dot(A1[1], D)) > rSum) + { + result.intersect = false; + result.separating = 3; + return result; + } + + result.intersect = true; + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox3AlignedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox3AlignedBox3.h new file mode 100644 index 0000000..3fc28de --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox3AlignedBox3.h @@ -0,0 +1,93 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The aligned-aligned queries use simple min-max comparisions. The +// interesection of aligned boxes is an aligned box, possibly degenerate, +// where min[d] == max[d] for at least one dimension d. + +namespace WwiseGTE +{ + template + class TIQuery, AlignedBox3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(AlignedBox3 const& box0, AlignedBox3 const& box1) + { + Result result; + for (int i = 0; i < 3; i++) + { + if (box0.max[i] < box1.min[i] || box0.min[i] > box1.max[i]) + { + result.intersect = false; + return result; + } + } + result.intersect = true; + return result; + } + }; + + template + class FIQuery, AlignedBox3> + { + public: + struct Result + { + bool intersect; + AlignedBox3 box; + }; + + Result operator()(AlignedBox3 const& box0, AlignedBox3 const& box1) + { + Result result; + for (int i = 0; i < 3; i++) + { + if (box0.max[i] < box1.min[i] || box0.min[i] > box1.max[i]) + { + result.intersect = false; + return result; + } + } + + for (int i = 0; i < 3; i++) + { + if (box0.max[i] <= box1.max[i]) + { + result.box.max[i] = box0.max[i]; + } + else + { + result.box.max[i] = box1.max[i]; + } + + if (box0.min[i] <= box1.min[i]) + { + result.box.min[i] = box1.min[i]; + } + else + { + result.box.min[i] = box0.min[i]; + } + } + result.intersect = true; + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox3Cone3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox3Cone3.h new file mode 100644 index 0000000..a7ae922 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox3Cone3.h @@ -0,0 +1,997 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +// Test for intersection of a box and a cone. The cone can be infinite +// 0 <= minHeight < maxHeight = std::numeric_limits::max() +// or finite (cone frustum) +// 0 <= minHeight < maxHeight < std::numeric_limits::max(). +// The algorithm is described in +// https://www.geometrictools.com/Documentation/IntersectionBoxCone.pdf +// and reports an intersection only when the intersection set has positive +// volume. For example, let the box be outside the cone. If the box is +// below the minHeight plane at the cone vertex and just touches the cone +// vertex, no intersection is reported. If the box is above the maxHeight +// plane and just touches the disk capping the cone, either at a single +// point, a line segment of points or a polygon of points, no intersection +// is reported. + +// TODO: These queries were designed when an infinite cone was defined +// by choosing maxHeight of std::numeric_limits::max(). The Cone +// class has been redesigned not to use std::numeric_limits to allow for +// arithmetic systems that do not have representations for infinities +// (such as BSNumber and BSRational). The intersection queries need to be +// rewritten for the new class design. FOR NOW, the queries will work with +// float/double when you create a cone using the cone-frustum constructor +// Cone(ray, angle, minHeight, std::numeric_limits::max()). + +namespace WwiseGTE +{ + template + class TIQuery, Cone<3, Real>> + { + public: + struct Result + { + bool intersect; + }; + + TIQuery() + : + mNumCandidateEdges(0) + { + // An edge is { v0, v1 }, where v0 and v1 are relative to mVertices + // with v0 < v1. + mEdges[0] = { 0, 1 }; + mEdges[1] = { 1, 3 }; + mEdges[2] = { 2, 3 }; + mEdges[3] = { 0, 2 }; + mEdges[4] = { 4, 5 }; + mEdges[5] = { 5, 7 }; + mEdges[6] = { 6, 7 }; + mEdges[7] = { 4, 6 }; + mEdges[8] = { 0, 4 }; + mEdges[9] = { 1, 5 }; + mEdges[10] = { 3, 7 }; + mEdges[11] = { 2, 6 }; + + // A face is { { v0, v1, v2, v3 }, { e0, e1, e2, e3 } }, where + // { v0, v1, v2, v3 } are relative to mVertices with + // v0 = min(v0,v1,v2,v3) and where { e0, e1, e2, e3 } are relative + // to mEdges. For example, mFaces[0] has vertices { 0, 4, 6, 2 }. + // The edge { 0, 4 } is mEdges[8], the edge { 4, 6 } is mEdges[7], + // the edge { 6, 2 } is mEdges[11] and the edge { 2, 0 } is + // mEdges[3]; thus, the edge indices are { 8, 7, 11, 3 }. + mFaces[0] = { { 0, 4, 6, 2 }, { 8, 7, 11, 3 } }; + mFaces[1] = { { 1, 3, 7, 5 }, { 1, 10, 5, 9 } }; + mFaces[2] = { { 0, 1, 5, 4 }, { 0, 9, 4, 8 } }; + mFaces[3] = { { 2, 6, 7, 3 }, { 11, 6, 10, 2 } }; + mFaces[4] = { { 0, 2, 3, 1 }, { 3, 2, 1, 0 } }; + mFaces[5] = { { 4, 5, 7, 6 }, { 4, 5, 6, 7 } }; + + // Clear the edges. + std::array ezero = { 0, 0 }; + mCandidateEdges.fill(ezero); + for (size_t r = 0; r < MAX_VERTICES; ++r) + { + mAdjacencyMatrix[r].fill(0); + } + + mConfiguration[0] = &TIQuery::NNNN_0; + mConfiguration[1] = &TIQuery::NNNZ_1; + mConfiguration[2] = &TIQuery::NNNP_2; + mConfiguration[3] = &TIQuery::NNZN_3; + mConfiguration[4] = &TIQuery::NNZZ_4; + mConfiguration[5] = &TIQuery::NNZP_5; + mConfiguration[6] = &TIQuery::NNPN_6; + mConfiguration[7] = &TIQuery::NNPZ_7; + mConfiguration[8] = &TIQuery::NNPP_8; + mConfiguration[9] = &TIQuery::NZNN_9; + mConfiguration[10] = &TIQuery::NZNZ_10; + mConfiguration[11] = &TIQuery::NZNP_11; + mConfiguration[12] = &TIQuery::NZZN_12; + mConfiguration[13] = &TIQuery::NZZZ_13; + mConfiguration[14] = &TIQuery::NZZP_14; + mConfiguration[15] = &TIQuery::NZPN_15; + mConfiguration[16] = &TIQuery::NZPZ_16; + mConfiguration[17] = &TIQuery::NZPP_17; + mConfiguration[18] = &TIQuery::NPNN_18; + mConfiguration[19] = &TIQuery::NPNZ_19; + mConfiguration[20] = &TIQuery::NPNP_20; + mConfiguration[21] = &TIQuery::NPZN_21; + mConfiguration[22] = &TIQuery::NPZZ_22; + mConfiguration[23] = &TIQuery::NPZP_23; + mConfiguration[24] = &TIQuery::NPPN_24; + mConfiguration[25] = &TIQuery::NPPZ_25; + mConfiguration[26] = &TIQuery::NPPP_26; + mConfiguration[27] = &TIQuery::ZNNN_27; + mConfiguration[28] = &TIQuery::ZNNZ_28; + mConfiguration[29] = &TIQuery::ZNNP_29; + mConfiguration[30] = &TIQuery::ZNZN_30; + mConfiguration[31] = &TIQuery::ZNZZ_31; + mConfiguration[32] = &TIQuery::ZNZP_32; + mConfiguration[33] = &TIQuery::ZNPN_33; + mConfiguration[34] = &TIQuery::ZNPZ_34; + mConfiguration[35] = &TIQuery::ZNPP_35; + mConfiguration[36] = &TIQuery::ZZNN_36; + mConfiguration[37] = &TIQuery::ZZNZ_37; + mConfiguration[38] = &TIQuery::ZZNP_38; + mConfiguration[39] = &TIQuery::ZZZN_39; + mConfiguration[40] = &TIQuery::ZZZZ_40; + mConfiguration[41] = &TIQuery::ZZZP_41; + mConfiguration[42] = &TIQuery::ZZPN_42; + mConfiguration[43] = &TIQuery::ZZPZ_43; + mConfiguration[44] = &TIQuery::ZZPP_44; + mConfiguration[45] = &TIQuery::ZPNN_45; + mConfiguration[46] = &TIQuery::ZPNZ_46; + mConfiguration[47] = &TIQuery::ZPNP_47; + mConfiguration[48] = &TIQuery::ZPZN_48; + mConfiguration[49] = &TIQuery::ZPZZ_49; + mConfiguration[50] = &TIQuery::ZPZP_50; + mConfiguration[51] = &TIQuery::ZPPN_51; + mConfiguration[52] = &TIQuery::ZPPZ_52; + mConfiguration[53] = &TIQuery::ZPPP_53; + mConfiguration[54] = &TIQuery::PNNN_54; + mConfiguration[55] = &TIQuery::PNNZ_55; + mConfiguration[56] = &TIQuery::PNNP_56; + mConfiguration[57] = &TIQuery::PNZN_57; + mConfiguration[58] = &TIQuery::PNZZ_58; + mConfiguration[59] = &TIQuery::PNZP_59; + mConfiguration[60] = &TIQuery::PNPN_60; + mConfiguration[61] = &TIQuery::PNPZ_61; + mConfiguration[62] = &TIQuery::PNPP_62; + mConfiguration[63] = &TIQuery::PZNN_63; + mConfiguration[64] = &TIQuery::PZNZ_64; + mConfiguration[65] = &TIQuery::PZNP_65; + mConfiguration[66] = &TIQuery::PZZN_66; + mConfiguration[67] = &TIQuery::PZZZ_67; + mConfiguration[68] = &TIQuery::PZZP_68; + mConfiguration[69] = &TIQuery::PZPN_69; + mConfiguration[70] = &TIQuery::PZPZ_70; + mConfiguration[71] = &TIQuery::PZPP_71; + mConfiguration[72] = &TIQuery::PPNN_72; + mConfiguration[73] = &TIQuery::PPNZ_73; + mConfiguration[74] = &TIQuery::PPNP_74; + mConfiguration[75] = &TIQuery::PPZN_75; + mConfiguration[76] = &TIQuery::PPZZ_76; + mConfiguration[77] = &TIQuery::PPZP_77; + mConfiguration[78] = &TIQuery::PPPN_78; + mConfiguration[79] = &TIQuery::PPPZ_79; + mConfiguration[80] = &TIQuery::PPPP_80; + } + + Result operator()(AlignedBox<3, Real> const& box, Cone<3, Real> const& cone) + { + Result result; + + // Quick-rejectance test. Determine whether the box is outside + // the slab bounded by the minimum and maximum height planes. + // When outside the slab, the box vertices are not required by the + // cone-box intersection query, so the vertices are not yet + // computed. + Real boxMinHeight(0), boxMaxHeight(0); + ComputeBoxHeightInterval(box, cone, boxMinHeight, boxMaxHeight); + // TODO: See the comments at the beginning of this file. + Real coneMaxHeight = (cone.IsFinite() ? cone.GetMaxHeight() : std::numeric_limits::max()); + if (boxMaxHeight <= cone.GetMinHeight() || boxMinHeight >= coneMaxHeight) + { + // There is no volumetric overlap of the box and the cone. The + // box is clipped entirely. + result.intersect = false; + return result; + } + + // Quick-acceptance test. Determine whether the cone axis + // intersects the box. + if (ConeAxisIntersectsBox(box, cone)) + { + result.intersect = true; + return result; + } + + // Test for box fully inside the slab. When inside the slab, the + // box vertices are required by the cone-box intersection query, + // so they are computed here; they are also required in the + // remaining cases. Also when inside the slab, the box edges are + // the candidates, so they are copied to mCandidateEdges. + if (BoxFullyInConeSlab(box, boxMinHeight, boxMaxHeight, cone)) + { + result.intersect = CandidatesHavePointInsideCone(cone); + return result; + } + + // Clear the candidates array and adjacency matrix. + ClearCandidates(); + + // The box intersects at least one plane. Compute the box-plane + // edge-interior intersection points. Insert the box subedges into + // the array of candidate edges. + ComputeCandidatesOnBoxEdges(cone); + + // Insert any relevant box face-interior clipped edges into the array + // of candidate edges. + ComputeCandidatesOnBoxFaces(); + + result.intersect = CandidatesHavePointInsideCone(cone); + return result; + } + + protected: + // The constants here are described in the comments below. + enum + { + NUM_BOX_VERTICES = 8, + NUM_BOX_EDGES = 12, + NUM_BOX_FACES = 6, + MAX_VERTICES = 32, + VERTEX_MIN_BASE = 8, + VERTEX_MAX_BASE = 20, + MAX_CANDIDATE_EDGES = 496, + NUM_CONFIGURATIONS = 81 + }; + + // The box topology is that of a cube whose vertices have components + // in {0,1}. The cube vertices are indexed by + // 0: (0,0,0), 1: (1,0,0), 2: (1,1,0), 3: (0,1,0) + // 4: (0,0,1), 5: (1,0,1), 6: (1,1,1), 7: (0,1,1) + + // The first 8 vertices are the box corners, the next 12 vertices are + // reserved for hmin-edge points and the final 12 vertices are reserved + // for the hmax-edge points. The conservative upper bound of the number + // of vertices is 8 + 12 + 12 = 32. + std::array, MAX_VERTICES> mVertices; + + // The box has 12 edges stored in mEdges. An edge is mEdges[i] = + // { v0, v1 }, where the indices v0 and v1 are relative to mVertices + // with v0 < v1. + std::array, NUM_BOX_EDGES> mEdges; + + // The box has 6 faces stored in mFaces. A face is mFaces[i] = + // { { v0, v1, v2, v3 }, { e0, e1, e2, e3 } }, where the face corner + // vertices are { v0, v1, v2, v3 }. These indices are relative to + // mVertices. The indices { e0, e1, e2, e3 } are relative to mEdges. + // The index e0 refers to edge { v0, v1 }, the index e1 refers to edge + // { v1, v2 }, the index e2 refers to edge { v2, v3 } and the index e3 + // refers to edge { v3, v0 }. The ordering of vertices for the faces + // is/ counterclockwise when viewed from outside the box. The choice + // of initial vertex affects how you implement the graph data + // structure. In this implementation, the initial vertex has minimum + // index for all vertices of that face. The faces themselves are + // listed as -x face, +x face, -y face, +y face, -z face and +z face. + struct Face + { + std::array v, e; + }; + std::array mFaces; + + // Store the signed distances from the minimum and maximum height + // planes for the cone to the projection of the box vertices onto the + // cone axis. + std::array mProjectionMin, mProjectionMax; + + // The mCandidateEdges array stores the edges of the clipped box that + // are candidates for containing the optimizing point. The maximum + // number of candidate edges is 1 + 2 + ... + 31 = 496, which is a + // conservative bound because not all pairs of vertices form edges on + // box faces. The candidate edges are stored as (v0,v1) with v0 < v1. + // The implementation is designed so that during a single query, the + // number of candidate edges can only grow. + size_t mNumCandidateEdges; + std::array, MAX_CANDIDATE_EDGES> mCandidateEdges; + + // The mAdjancencyMatrix is a simple representation of edges in the + // graph G = (V,E) that represents the (wireframe) clipped box. An + // edge (r,c) does not exist when mAdjancencyMatrix[r][c] = 0. If an + // edge (r,c) does exist, it is appended to mCandidateEdges at index k + // and the adjacency matrix is set to mAdjacencyMatrix[r][c] = 1. + // This allows for a fast edge-in-graph test and a fast and efficient + // clear of mCandidateEdges and mAdjacencyMatrix. + std::array, MAX_VERTICES> mAdjacencyMatrix; + + typedef void (TIQuery::* ConfigurationFunction)(size_t, Face const&); + std::array mConfiguration; + + static void ComputeBoxHeightInterval(AlignedBox<3, Real> const& box, Cone<3, Real> const& cone, + Real& boxMinHeight, Real& boxMaxHeight) + { + Vector<3, Real> C, e; + box.GetCenteredForm(C, e); + Vector<3, Real> const& V = cone.ray.origin; + Vector<3, Real> const& U = cone.ray.direction; + Vector<3, Real> CmV = C - V; + Real DdCmV = Dot(U, CmV); + Real radius = e[0] * std::abs(U[0]) + e[1] * std::abs(U[1]) + e[2] * std::abs(U[2]); + boxMinHeight = DdCmV - radius; + boxMaxHeight = DdCmV + radius; + } + + static bool ConeAxisIntersectsBox(AlignedBox<3, Real> const& box, Cone<3, Real> const& cone) + { + if (cone.IsFinite()) + { + Segment<3, Real> segment; + segment.p[0] = cone.ray.origin + cone.GetMinHeight() * cone.ray.direction; + segment.p[1] = cone.ray.origin + cone.GetMaxHeight() * cone.ray.direction; + auto sbResult = TIQuery, AlignedBox<3, Real>>()(segment, box); + if (sbResult.intersect) + { + return true; + } + } + else + { + Ray<3, Real> ray; + ray.origin = cone.ray.origin + cone.GetMinHeight() * cone.ray.direction; + ray.direction = cone.ray.direction; + auto rbResult = TIQuery, AlignedBox<3, Real>>()(ray, box); + if (rbResult.intersect) + { + return true; + } + } + return false; + } + + bool BoxFullyInConeSlab(AlignedBox<3, Real> const& box, Real boxMinHeight, Real boxMaxHeight, Cone<3, Real> const& cone) + { + // Compute the box vertices relative to cone vertex as origin. + mVertices[0] = { box.min[0], box.min[1], box.min[2] }; + mVertices[1] = { box.max[0], box.min[1], box.min[2] }; + mVertices[2] = { box.min[0], box.max[1], box.min[2] }; + mVertices[3] = { box.max[0], box.max[1], box.min[2] }; + mVertices[4] = { box.min[0], box.min[1], box.max[2] }; + mVertices[5] = { box.max[0], box.min[1], box.max[2] }; + mVertices[6] = { box.min[0], box.max[1], box.max[2] }; + mVertices[7] = { box.max[0], box.max[1], box.max[2] }; + for (int i = 0; i < NUM_BOX_VERTICES; ++i) + { + mVertices[i] -= cone.ray.origin; + } + + Real coneMaxHeight = (cone.IsFinite() ? cone.GetMaxHeight() : std::numeric_limits::max()); + if (cone.GetMinHeight() <= boxMinHeight && boxMaxHeight <= coneMaxHeight) + { + // The box is fully inside, so no clipping is necessary. + std::copy(mEdges.begin(), mEdges.end(), mCandidateEdges.begin()); + mNumCandidateEdges = 12; + return true; + } + return false; + } + + static bool HasPointInsideCone(Vector<3, Real> const& P0, Vector<3, Real> const& P1, + Cone<3, Real> const& cone) + { + // Define F(X) = Dot(U,X - V)/|X - V|, where U is the unit-length + // cone axis direction and V is the cone vertex. The incoming + // points P0 and P1 are relative to V; that is, the original + // points are X0 = P0 + V and X1 = P1 + V. The segment + // and cone intersect when a segment point X is inside the cone; + // that is, when F(X) > cosAngle. The comparison is converted to + // an equivalent one that does not involve divisions in order to + // avoid a division by zero if a vertex or edge contain (0,0,0). + // The function is G(X) = Dot(U,X-V) - cosAngle*Length(X-V). + Vector<3, Real> const& U = cone.ray.direction; + + // Test whether P0 or P1 is inside the cone. + Real g = Dot(U, P0) - cone.cosAngle * Length(P0); + if (g > (Real)0) + { + // X0 = P0 + V is inside the cone. + return true; + } + + g = Dot(U, P1) - cone.cosAngle * Length(P1); + if (g > (Real)0) + { + // X1 = P1 + V is inside the cone. + return true; + } + + // Test whether an interior segment point is inside the cone. + Vector<3, Real> E = P1 - P0; + Vector<3, Real> crossP0U = Cross(P0, U); + Vector<3, Real> crossP0E = Cross(P0, E); + Real dphi0 = Dot(crossP0E, crossP0U); + if (dphi0 > (Real)0) + { + Vector3 crossP1U = Cross(P1, U); + Real dphi1 = Dot(crossP0E, crossP1U); + if (dphi1 < (Real)0) + { + Real t = dphi0 / (dphi0 - dphi1); + Vector<3, Real> PMax = P0 + t * E; + g = Dot(U, PMax) - cone.cosAngle * Length(PMax); + if (g > (Real)0) + { + // The edge point XMax = Pmax + V is inside the cone. + return true; + } + } + } + + return false; + } + + bool CandidatesHavePointInsideCone(Cone<3, Real> const& cone) const + { + for (size_t i = 0; i < mNumCandidateEdges; ++i) + { + auto const& edge = mCandidateEdges[i]; + Vector<3, Real> const& P0 = mVertices[edge[0]]; + Vector<3, Real> const& P1 = mVertices[edge[1]]; + if (HasPointInsideCone(P0, P1, cone)) + { + return true; + } + } + return false; + } + + void ComputeCandidatesOnBoxEdges(Cone<3, Real> const& cone) + { + for (size_t i = 0; i < NUM_BOX_VERTICES; ++i) + { + Real h = Dot(cone.ray.direction, mVertices[i]); + Real coneMaxHeight = (cone.IsFinite() ? cone.GetMaxHeight() : std::numeric_limits::max()); + mProjectionMin[i] = cone.GetMinHeight() - h; + mProjectionMax[i] = h - coneMaxHeight; + } + + size_t v0 = VERTEX_MIN_BASE, v1 = VERTEX_MAX_BASE; + for (size_t i = 0; i < NUM_BOX_EDGES; ++i, ++v0, ++v1) + { + auto const& edge = mEdges[i]; + + // In the next blocks, the sign comparisons can be expressed + // instead as "s0 * s1 < 0". The multiplication could lead to + // floating-point underflow where the product becomes 0, so I + // avoid that approach. + + // Process the hmin-plane. + Real p0Min = mProjectionMin[edge[0]]; + Real p1Min = mProjectionMin[edge[1]]; + bool clipMin = (p0Min < (Real)0 && p1Min >(Real)0) || (p0Min > (Real)0 && p1Min < (Real)0); + if (clipMin) + { + mVertices[v0] = (p1Min * mVertices[edge[0]] - p0Min * mVertices[edge[1]]) / (p1Min - p0Min); + } + + // Process the hmax-plane. + Real p0Max = mProjectionMax[edge[0]]; + Real p1Max = mProjectionMax[edge[1]]; + bool clipMax = (p0Max < (Real)0 && p1Max >(Real)0) || (p0Max > (Real)0 && p1Max < (Real)0); + if (clipMax) + { + mVertices[v1] = (p1Max * mVertices[edge[0]] - p0Max * mVertices[edge[1]]) / (p1Max - p0Max); + } + + // Get the candidate edges that are contained by the box edges. + if (clipMin) + { + if (clipMax) + { + InsertEdge(v0, v1); + } + else + { + if (p0Min < (Real)0) + { + InsertEdge(edge[0], v0); + } + else // p1Min < 0 + { + InsertEdge(edge[1], v0); + } + } + } + else if (clipMax) + { + if (p0Max < (Real)0) + { + InsertEdge(edge[0], v1); + } + else // p1Max < 0 + { + InsertEdge(edge[1], v1); + } + } + else + { + // No clipping has occurred. If the edge is inside the box, + // it is a candidate edge. To be inside the box, the p*min + // and p*max values must be nonpositive. + if (p0Min <= (Real)0 && p1Min <= (Real)0 && p0Max <= (Real)0 && p1Max <= (Real)0) + { + InsertEdge(edge[0], edge[1]); + } + } + } + } + + void ComputeCandidatesOnBoxFaces() + { + Real p0, p1, p2, p3; + size_t b0, b1, b2, b3, index; + for (size_t i = 0; i < NUM_BOX_FACES; ++i) + { + auto const& face = mFaces[i]; + + // Process the hmin-plane. + p0 = mProjectionMin[face.v[0]]; + p1 = mProjectionMin[face.v[1]]; + p2 = mProjectionMin[face.v[2]]; + p3 = mProjectionMin[face.v[3]]; + b0 = (p0 < (Real)0 ? 0 : (p0 > (Real)0 ? 2 : 1)); + b1 = (p1 < (Real)0 ? 0 : (p1 > (Real)0 ? 2 : 1)); + b2 = (p2 < (Real)0 ? 0 : (p2 > (Real)0 ? 2 : 1)); + b3 = (p3 < (Real)0 ? 0 : (p3 > (Real)0 ? 2 : 1)); + index = b3 + 3 * (b2 + 3 * (b1 + 3 * b0)); + (this->*mConfiguration[index])(VERTEX_MIN_BASE, face); + + // Process the hmax-plane. + p0 = mProjectionMax[face.v[0]]; + p1 = mProjectionMax[face.v[1]]; + p2 = mProjectionMax[face.v[2]]; + p3 = mProjectionMax[face.v[3]]; + b0 = (p0 < (Real)0 ? 0 : (p0 > (Real)0 ? 2 : 1)); + b1 = (p1 < (Real)0 ? 0 : (p1 > (Real)0 ? 2 : 1)); + b2 = (p2 < (Real)0 ? 0 : (p2 > (Real)0 ? 2 : 1)); + b3 = (p3 < (Real)0 ? 0 : (p3 > (Real)0 ? 2 : 1)); + index = b3 + 3 * (b2 + 3 * (b1 + 3 * b0)); + (this->*mConfiguration[index])(VERTEX_MAX_BASE, face); + } + } + + void ClearCandidates() + { + for (size_t i = 0; i < mNumCandidateEdges; ++i) + { + auto const& edge = mCandidateEdges[i]; + mAdjacencyMatrix[edge[0]][edge[1]] = 0; + mAdjacencyMatrix[edge[1]][edge[0]] = 0; + } + mNumCandidateEdges = 0; + } + + void InsertEdge(size_t v0, size_t v1) + { + if (mAdjacencyMatrix[v0][v1] == 0) + { + mAdjacencyMatrix[v0][v1] = 1; + mAdjacencyMatrix[v1][v0] = 1; + mCandidateEdges[mNumCandidateEdges] = { v0, v1 }; + ++mNumCandidateEdges; + } + } + + // The 81 possible configurations for a box face. The N stands for a + // '-', the Z stands for '0' and the P stands for '+'. These are + // listed in the order that maps to the array mConfiguration. Thus, + // NNNN maps to mConfiguration[0], NNNZ maps to mConfiguration[1], and + // so on. + void NNNN_0(size_t, Face const&) + { + } + + void NNNZ_1(size_t, Face const&) + { + } + + void NNNP_2(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], base + face.e[3]); + } + + void NNZN_3(size_t, Face const&) + { + } + + void NNZZ_4(size_t, Face const&) + { + } + + void NNZP_5(size_t base, Face const& face) + { + InsertEdge(face.v[2], base + face.e[3]); + } + + void NNPN_6(size_t base, Face const& face) + { + InsertEdge(base + face.e[1], base + face.e[2]); + } + + void NNPZ_7(size_t base, Face const& face) + { + InsertEdge(base + face.e[1], face.v[3]); + } + + void NNPP_8(size_t base, Face const& face) + { + InsertEdge(base + face.e[1], base + face.e[3]); + } + + void NZNN_9(size_t, Face const&) + { + } + + void NZNZ_10(size_t, Face const&) + { + } + + void NZNP_11(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], face.v[3]); + InsertEdge(base + face.e[3], face.v[3]); + } + + void NZZN_12(size_t, Face const&) + { + } + + void NZZZ_13(size_t, Face const&) + { + } + + void NZZP_14(size_t base, Face const& face) + { + InsertEdge(face.v[2], face.v[3]); + InsertEdge(base + face.e[3], face.v[3]); + } + + void NZPN_15(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], face.v[1]); + } + + void NZPZ_16(size_t, Face const& face) + { + InsertEdge(face.v[1], face.v[3]); + } + + void NZPP_17(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], face.v[1]); + } + + void NPNN_18(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], base + face.e[1]); + } + + void NPNZ_19(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], face.v[1]); + InsertEdge(base + face.e[1], face.v[1]); + } + + void NPNP_20(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], face.v[1]); + InsertEdge(base + face.e[1], face.v[1]); + InsertEdge(base + face.e[2], face.v[3]); + InsertEdge(base + face.e[3], face.v[3]); + } + + void NPZN_21(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], face.v[2]); + } + + void NPZZ_22(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], face.v[1]); + InsertEdge(face.v[1], face.v[2]); + } + + void NPZP_23(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], face.v[1]); + InsertEdge(face.v[1], face.v[2]); + InsertEdge(base + face.e[3], face.v[2]); + InsertEdge(face.v[2], face.v[3]); + } + + void NPPN_24(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], base + face.e[2]); + } + + void NPPZ_25(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], face.v[3]); + } + + void NPPP_26(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], base + face.e[3]); + } + + void ZNNN_27(size_t, Face const&) + { + } + + void ZNNZ_28(size_t, Face const&) + { + } + + void ZNNP_29(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], face.v[0]); + } + + void ZNZN_30(size_t, Face const&) + { + } + + void ZNZZ_31(size_t, Face const&) + { + } + + void ZNZP_32(size_t, Face const& face) + { + InsertEdge(face.v[0], face.v[2]); + } + + void ZNPN_33(size_t base, Face const& face) + { + InsertEdge(base + face.e[1], face.v[2]); + InsertEdge(base + face.e[2], face.v[2]); + } + + void ZNPZ_34(size_t base, Face const& face) + { + InsertEdge(base + face.e[1], face.v[2]); + InsertEdge(face.v[2], face.v[3]); + } + + void ZNPP_35(size_t base, Face const& face) + { + InsertEdge(face.v[0], base + face.e[1]); + } + + void ZZNN_36(size_t, Face const&) + { + } + + void ZZNZ_37(size_t, Face const&) + { + } + + void ZZNP_38(size_t base, Face const& face) + { + InsertEdge(face.v[0], face.v[3]); + InsertEdge(face.v[3], base + face.e[2]); + } + + void ZZZN_39(size_t, Face const&) + { + } + + void ZZZZ_40(size_t, Face const&) + { + } + + void ZZZP_41(size_t, Face const& face) + { + InsertEdge(face.v[0], face.v[3]); + InsertEdge(face.v[3], face.v[2]); + } + + void ZZPN_42(size_t base, Face const& face) + { + InsertEdge(face.v[1], face.v[2]); + InsertEdge(face.v[2], base + face.e[2]); + } + + void ZZPZ_43(size_t, Face const& face) + { + InsertEdge(face.v[1], face.v[2]); + InsertEdge(face.v[2], face.v[3]); + } + + void ZZPP_44(size_t, Face const&) + { + } + + void ZPNN_45(size_t base, Face const& face) + { + InsertEdge(face.v[0], base + face.e[1]); + } + + void ZPNZ_46(size_t base, Face const& face) + { + InsertEdge(face.v[0], face.v[1]); + InsertEdge(face.v[1], base + face.e[1]); + } + + void ZPNP_47(size_t base, Face const& face) + { + InsertEdge(face.v[0], face.v[1]); + InsertEdge(face.v[1], base + face.e[1]); + InsertEdge(base + face.e[2], face.v[3]); + InsertEdge(face.v[3], face.v[0]); + } + + void ZPZN_48(size_t, Face const& face) + { + InsertEdge(face.v[0], face.v[2]); + } + + void ZPZZ_49(size_t, Face const& face) + { + InsertEdge(face.v[0], face.v[1]); + InsertEdge(face.v[1], face.v[2]); + } + + void ZPZP_50(size_t, Face const&) + { + } + + void ZPPN_51(size_t base, Face const& face) + { + InsertEdge(face.v[0], base + face.e[2]); + } + + void ZPPZ_52(size_t, Face const&) + { + } + + void ZPPP_53(size_t, Face const&) + { + } + + void PNNN_54(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], base + face.e[0]); + } + + void PNNZ_55(size_t base, Face const& face) + { + InsertEdge(face.v[3], base + face.e[0]); + } + + void PNNP_56(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], base + face.e[0]); + } + + void PNZN_57(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], face.v[0]); + InsertEdge(face.v[0], base + face.e[0]); + } + + void PNZZ_58(size_t base, Face const& face) + { + InsertEdge(face.v[3], face.v[0]); + InsertEdge(face.v[0], base + face.e[0]); + } + + void PNZP_59(size_t base, Face const& face) + { + InsertEdge(face.v[2], base + face.e[0]); + } + + void PNPN_60(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], face.v[0]); + InsertEdge(face.v[0], base + face.e[0]); + InsertEdge(base + face.e[1], face.v[2]); + InsertEdge(face.v[2], base + face.e[2]); + } + + void PNPZ_61(size_t base, Face const& face) + { + InsertEdge(face.v[3], face.v[0]); + InsertEdge(face.v[0], base + face.e[0]); + InsertEdge(base + face.e[1], face.v[2]); + InsertEdge(face.v[2], face.v[3]); + } + + void PNPP_62(size_t base, Face const& face) + { + InsertEdge(base + face.e[0], base + face.e[1]); + } + + void PZNN_63(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], face.v[1]); + } + + void PZNZ_64(size_t, Face const& face) + { + InsertEdge(face.v[3], face.v[1]); + } + + void PZNP_65(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], face.v[1]); + } + + void PZZN_66(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], face.v[0]); + InsertEdge(face.v[0], face.v[1]); + } + + void PZZZ_67(size_t, Face const&) + { + } + + void PZZP_68(size_t, Face const&) + { + } + + void PZPN_69(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], face.v[0]); + InsertEdge(face.v[0], face.v[1]); + InsertEdge(face.v[1], face.v[2]); + InsertEdge(face.v[2], base + face.e[2]); + } + + void PZPZ_70(size_t, Face const&) + { + } + + void PZPP_71(size_t, Face const&) + { + } + + void PPNN_72(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], base + face.e[1]); + } + + void PPNZ_73(size_t base, Face const& face) + { + InsertEdge(face.v[3], base + face.e[1]); + } + + void PPNP_74(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], base + face.e[1]); + } + + void PPZN_75(size_t base, Face const& face) + { + InsertEdge(base + face.e[2], face.v[2]); + } + + void PPZZ_76(size_t, Face const&) + { + } + + void PPZP_77(size_t, Face const&) + { + } + + void PPPN_78(size_t base, Face const& face) + { + InsertEdge(base + face.e[3], base + face.e[2]); + } + + void PPPZ_79(size_t, Face const&) + { + } + + void PPPP_80(size_t, Face const&) + { + } + }; + + // Template alias for convenience. + template + using TIAlignedBox3Cone3 = TIQuery, Cone<3, Real>>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox3Cylinder3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox3Cylinder3.h new file mode 100644 index 0000000..12c3939 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox3Cylinder3.h @@ -0,0 +1,138 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.10.23 + +#pragma once + +#include +#include +#include +#include +#include + +// The query considers the cylinder and box to be solids. + +namespace WwiseGTE +{ + template + class TIQuery, Cylinder3> + { + public: + struct Result + { + // The outcome of the mLCP.Solve(...) call. + typename LCPSolverShared::Result outcome; + + bool intersect; + + // The number of iterations used by LCPSolver regardless of + // whether the query is successful. + int numLCPIterations; + }; + + // Default maximum iterations is 64 (n = 8, maxIterations = n*n). + // If the solver fails to converge, try increasing the maximum number + // of iterations. + void SetMaxLCPIterations(int maxLCPIterations) + { + mLCP.SetMaxIterations(maxLCPIterations); + } + + Result operator()(AlignedBox3 const& box, Cylinder3 const& cylinder) + { + Result result; + + // Translate the box and cylinder so that the box is in the first + // octant where all points in the box have nonnegative components. + Vector3 corner = box.max - box.min; + Vector3 origin = cylinder.axis.origin - box.min; + Vector3 direction = cylinder.axis.direction; + + // Compute quantities to initialize q and M in the LCP. + Real halfHeight = cylinder.height * (Real)0.5; + Matrix3x3 P = (Matrix3x3::Identity() - OuterProduct(direction, direction)); + Vector3 C = -(P * origin); + Real originDotDirection = Dot(origin, direction); + + Matrix<5, 3, Real> A; + A.SetRow(0, { (Real)-1, (Real)0, (Real)0 }); + A.SetRow(1, { (Real)0, (Real)-1, (Real)0 }); + A.SetRow(2, { (Real)0, (Real)0, (Real)-1 }); + A.SetRow(3, direction); + A.SetRow(4, -direction); + + Vector<5, Real> B = + { + -corner[0], + -corner[1], + -corner[2], + originDotDirection - halfHeight, + -originDotDirection - halfHeight + }; + + std::array, 8> M; + for (int r = 0; r < 3; ++r) + { + for (int c = 0; c < 3; ++c) + { + M[r][c] = P(r, c); + } + + for (int c = 3, i = 0; c < 8; ++c, ++i) + { + M[r][c] = -A(i, r); + } + } + + for (int r = 3, i = 0; r < 8; ++r, ++i) + { + for (int c = 0; c < 3; ++c) + { + M[r][c] = A(i, c); + } + + for (int c = 3; c < 8; ++c) + { + M[r][c] = (Real)0; + } + } + + std::array q; + for (int r = 0; r < 3; ++r) + { + q[r] = C[r]; + } + + for (int r = 3, i = 0; r < 8; ++r, ++i) + { + q[r] = -B[i]; + } + + std::array w, z; + if (mLCP.Solve(q, M, w, z, &result.outcome)) + { + Vector3 zSolution{ z[0], z[1], z[2] }; + Vector3 diff = zSolution - origin; + Real qform = Dot(diff, P * diff); + result.intersect = (qform <= cylinder.radius * cylinder.radius); + } + else + { + // You should examine result.outcome. The query is valid when + // the outcome is NO_SOLUTION. It is possible, however, that + // the solver did not have a large enough iteration budget + // (FAILED_TO_CONVERGE) or it has invalid input + // (INVALID_INPUT). + result.intersect = false; + } + + result.numLCPIterations = mLCP.GetNumIterations(); + return result; + } + private: + LCPSolver mLCP; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox3OrientedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox3OrientedBox3.h new file mode 100644 index 0000000..ba6a590 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox3OrientedBox3.h @@ -0,0 +1,325 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection query uses the method of separating axes. +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The set of potential separating directions includes the 3 face normals of +// box0, the 3 face normals of box1, and 9 directions, each of which is the +// cross product of an edge of box0 and and an edge of box1. +// +// The separating axes involving cross products of edges has numerical +// robustness problems when the two edges are nearly parallel. The cross +// product of the edges is nearly the zero vector, so normalization of the +// cross product may produce unit-length directions that are not close to the +// true direction. Such a pair of edges occurs when a box0 face normal N0 and +// a box1 face normal N1 are nearly parallel. In this case, you may skip the +// edge-edge directions, which is equivalent to projecting the boxes onto the +// plane with normal N0 and applying a 2D separating axis test. The ability +// to do so involves choosing a small nonnegative epsilon . It is used to +// determine whether two face normals, one from each box, are nearly parallel: +// |Dot(N0,N1)| >= 1 - epsilon. If the input is negative, it is clamped to +// zero. +// +// The pair of integers 'separating', say, (i0,i1), identify the axis that +// reported separation; there may be more than one but only one is +// reported. If the separating axis is a face normal N[i0] of the aligned +// box0 in dimension i0, then (i0,-1) is returned. If the axis is a face +// normal box1.Axis[i1], then (-1,i1) is returned. If the axis is a cross +// product of edges, Cross(N[i0],box1.Axis[i1]), then (i0,i1) is returned. + +namespace WwiseGTE +{ + template + class TIQuery, OrientedBox3> + { + public: + struct Result + { + // The 'epsilon' value must be nonnegative. + Result(Real inEpsilon = (Real)0) + : + epsilon(inEpsilon >= (Real)0 ? inEpsilon : (Real)0) + { + } + + bool intersect; + Real epsilon; + int separating[2]; + }; + + Result operator()(AlignedBox3 const& box0, OrientedBox3 const& box1) + { + Result result; + + // Get the centered form of the aligned box. The axes are + // implicitly A0[0] = (1,0,0), A0[1] = (0,1,0) and + // A0[2] = (0,0,1). + Vector3 C0, E0; + box0.GetCenteredForm(C0, E0); + + // Convenience variables. + Vector3 const& C1 = box1.center; + Vector3 const* A1 = &box1.axis[0]; + Vector3 const& E1 = box1.extent; + + Real const cutoff = (Real)1 - result.epsilon; + bool existsParallelPair = false; + + // Compute the difference of box centers. + Vector3 D = C1 - C0; + + // dot01[i][j] = Dot(A0[i],A1[j]) = A1[j][i] + Real dot01[3][3]; + + // |dot01[i][j]| + Real absDot01[3][3]; + + // interval radii and distance between centers + Real r0, r1, r; + + // r0 + r1 + Real r01; + + // Test for separation on the axis C0 + t*A0[0]. + for (int i = 0; i < 3; ++i) + { + dot01[0][i] = A1[i][0]; + absDot01[0][i] = std::fabs(A1[i][0]); + if (absDot01[0][i] >= cutoff) + { + existsParallelPair = true; + } + } + r = std::fabs(D[0]); + r1 = E1[0] * absDot01[0][0] + E1[1] * absDot01[0][1] + E1[2] * absDot01[0][2]; + r01 = E0[0] + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = -1; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]. + for (int i = 0; i < 3; ++i) + { + dot01[1][i] = A1[i][1]; + absDot01[1][i] = std::fabs(A1[i][1]); + if (absDot01[1][i] >= cutoff) + { + existsParallelPair = true; + } + } + r = std::fabs(D[1]); + r1 = E1[0] * absDot01[1][0] + E1[1] * absDot01[1][1] + E1[2] * absDot01[1][2]; + r01 = E0[1] + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = -1; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]. + for (int i = 0; i < 3; ++i) + { + dot01[2][i] = A1[i][2]; + absDot01[2][i] = std::fabs(A1[i][2]); + if (absDot01[2][i] >= cutoff) + { + existsParallelPair = true; + } + } + r = std::fabs(D[2]); + r1 = E1[0] * absDot01[2][0] + E1[1] * absDot01[2][1] + E1[2] * absDot01[2][2]; + r01 = E0[2] + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = -1; + return result; + } + + // Test for separation on the axis C0 + t*A1[0]. + r = std::fabs(Dot(D, A1[0])); + r0 = E0[0] * absDot01[0][0] + E0[1] * absDot01[1][0] + E0[2] * absDot01[2][0]; + r01 = r0 + E1[0]; + if (r > r01) + { + result.intersect = false; + result.separating[0] = -1; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A1[1]. + r = std::fabs(Dot(D, A1[1])); + r0 = E0[0] * absDot01[0][1] + E0[1] * absDot01[1][1] + E0[2] * absDot01[2][1]; + r01 = r0 + E1[1]; + if (r > r01) + { + result.intersect = false; + result.separating[0] = -1; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A1[2]. + r = std::fabs(Dot(D, A1[2])); + r0 = E0[0] * absDot01[0][2] + E0[1] * absDot01[1][2] + E0[2] * absDot01[2][2]; + r01 = r0 + E1[2]; + if (r > r01) + { + result.intersect = false; + result.separating[0] = -1; + result.separating[1] = 2; + return result; + } + + // At least one pair of box axes was parallel, so the separation is + // effectively in 2D. The edge-edge axes do not need to be tested. + if (existsParallelPair) + { + result.intersect = true; + return result; + } + + // Test for separation on the axis C0 + t*A0[0]xA1[0]. + r = std::fabs(D[2] * dot01[1][0] - D[1] * dot01[2][0]); + r0 = E0[1] * absDot01[2][0] + E0[2] * absDot01[1][0]; + r1 = E1[1] * absDot01[0][2] + E1[2] * absDot01[0][1]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A0[0]xA1[1]. + r = std::fabs(D[2] * dot01[1][1] - D[1] * dot01[2][1]); + r0 = E0[1] * absDot01[2][1] + E0[2] * absDot01[1][1]; + r1 = E1[0] * absDot01[0][2] + E1[2] * absDot01[0][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A0[0]xA1[2]. + r = std::fabs(D[2] * dot01[1][2] - D[1] * dot01[2][2]); + r0 = E0[1] * absDot01[2][2] + E0[2] * absDot01[1][2]; + r1 = E1[0] * absDot01[0][1] + E1[1] * absDot01[0][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = 2; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]xA1[0]. + r = std::fabs(D[0] * dot01[2][0] - D[2] * dot01[0][0]); + r0 = E0[0] * absDot01[2][0] + E0[2] * absDot01[0][0]; + r1 = E1[1] * absDot01[1][2] + E1[2] * absDot01[1][1]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]xA1[1]. + r = std::fabs(D[0] * dot01[2][1] - D[2] * dot01[0][1]); + r0 = E0[0] * absDot01[2][1] + E0[2] * absDot01[0][1]; + r1 = E1[0] * absDot01[1][2] + E1[2] * absDot01[1][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]xA1[2]. + r = std::fabs(D[0] * dot01[2][2] - D[2] * dot01[0][2]); + r0 = E0[0] * absDot01[2][2] + E0[2] * absDot01[0][2]; + r1 = E1[0] * absDot01[1][1] + E1[1] * absDot01[1][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = 2; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]xA1[0]. + r = std::fabs(D[1] * dot01[0][0] - D[0] * dot01[1][0]); + r0 = E0[0] * absDot01[1][0] + E0[1] * absDot01[0][0]; + r1 = E1[1] * absDot01[2][2] + E1[2] * absDot01[2][1]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]xA1[1]. + r = std::fabs(D[1] * dot01[0][1] - D[0] * dot01[1][1]); + r0 = E0[0] * absDot01[1][1] + E0[1] * absDot01[0][1]; + r1 = E1[0] * absDot01[2][2] + E1[2] * absDot01[2][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]xA1[2]. + r = std::fabs(D[1] * dot01[0][2] - D[0] * dot01[1][2]); + r0 = E0[0] * absDot01[1][2] + E0[1] * absDot01[0][2]; + r1 = E1[0] * absDot01[2][1] + E1[1] * absDot01[2][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = 2; + return result; + } + + result.intersect = true; + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox3Sphere3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox3Sphere3.h new file mode 100644 index 0000000..c0ca7af --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrAlignedBox3Sphere3.h @@ -0,0 +1,596 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The find-intersection query is based on the document +// https://www.geometrictools.com/Documentation/IntersectionMovingSphereBox.pdf +// and also uses the method of separating axes. +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf + +namespace WwiseGTE +{ + template + class TIQuery, Sphere3> + { + public: + // The intersection query considers the box and sphere to be solids; + // that is, the sphere object includes the region inside the spherical + // boundary and the box object includes the region inside the cuboid + // boundary. If the sphere object and box object object overlap, the + // objects intersect. + struct Result + { + bool intersect; + }; + + Result operator()(AlignedBox3 const& box, Sphere3 const& sphere) + { + DCPQuery, AlignedBox3> pbQuery; + auto pbResult = pbQuery(sphere.center, box); + Result result; + result.intersect = (pbResult.sqrDistance <= sphere.radius * sphere.radius); + return result; + } + }; + + template + class FIQuery, Sphere3> + { + public: + // Currently, only a dynamic query is supported. A static query will + // need to compute the intersection set of (solid) box and sphere. + struct Result + { + // The cases are + // 1. Objects initially overlapping. The contactPoint is only one + // of infinitely many points in the overlap. + // intersectionType = -1 + // contactTime = 0 + // contactPoint = sphere.center + // 2. Objects initially separated but do not intersect later. The + // contactTime and contactPoint are invalid. + // intersectionType = 0 + // contactTime = 0 + // contactPoint = (0,0,0) + // 3. Objects initially separated but intersect later. + // intersectionType = +1 + // contactTime = first time T > 0 + // contactPoint = corresponding first contact + int intersectionType; + Real contactTime; + Vector3 contactPoint; + + // TODO: To support arbitrary precision for the contactTime, + // return q0, q1 and q2 where contactTime = (q0 - sqrt(q1)) / q2. + // The caller can compute contactTime to desired number of digits + // of precision. These are valid when intersectionType is +1 but + // are set to zero (invalid) in the other cases. Do the same for + // the contactPoint. + }; + + Result operator()(AlignedBox3 const& box, Vector3 const& boxVelocity, + Sphere3 const& sphere, Vector3 const& sphereVelocity) + { + Result result = { 0, (Real)0, { (Real)0, (Real)0, (Real)0 } }; + + // Translate the sphere and box so that the box center becomes + // the origin. Compute the velocity of the sphere relative to + // the box. + Vector3 boxCenter = (box.max + box.min) * (Real)0.5; + Vector3 extent = (box.max - box.min) * (Real)0.5; + Vector3 C = sphere.center - boxCenter; + Vector3 V = sphereVelocity - boxVelocity; + + // Test for no-intersection that leads to an early exit. The test + // is fast, using the method of separating axes. + AlignedBox3 superBox; + for (int i = 0; i < 3; ++i) + { + superBox.max[i] = extent[i] + sphere.radius; + superBox.min[i] = -superBox.max[i]; + } + TIQuery, AlignedBox3> rbQuery; + auto rbResult = rbQuery(Ray3(C, V), superBox); + if (!rbResult.intersect) + { + return result; + } + + // Change signs on components, if necessary, to transform C to the + // first quadrant. Adjust the velocity accordingly. + Real sign[3]; + for (int i = 0; i < 3; ++i) + { + if (C[i] >= (Real)0) + { + sign[i] = (Real)1; + } + else + { + C[i] = -C[i]; + V[i] = -V[i]; + sign[i] = (Real)-1; + } + } + + DoQuery(extent, C, sphere.radius, V, result); + + if (result.intersectionType != 0) + { + // Translate back to the original coordinate system. + for (int i = 0; i < 3; ++i) + { + if (sign[i] < (Real)0) + { + result.contactPoint[i] = -result.contactPoint[i]; + } + } + + result.contactPoint += boxCenter; + } + + return result; + } + + protected: + // The query assumes the box is axis-aligned with center at the + // origin. Callers need to convert the results back to the original + // coordinate system of the query. + void DoQuery(Vector3 const& K, Vector3 const& C, + Real radius, Vector3 const& V, Result& result) + { + Vector3 delta = C - K; + if (delta[2] <= radius) + { + if (delta[1] <= radius) + { + if (delta[0] <= radius) + { + if (delta[2] <= (Real)0) + { + if (delta[1] <= (Real)0) + { + if (delta[0] <= (Real)0) + { + InteriorOverlap(C, result); + } + else + { + // x-face + FaceOverlap(0, 1, 2, K, C, radius, delta, result); + } + } + else + { + if (delta[0] <= (Real)0) + { + // y-face + FaceOverlap(1, 2, 0, K, C, radius, delta, result); + } + else + { + // xy-edge + if (delta[0] * delta[0] + delta[1] * delta[1] <= radius * radius) + { + EdgeOverlap(0, 1, 2, K, C, radius, delta, result); + } + else + { + EdgeSeparated(0, 1, 2, K, C, radius, delta, V, result); + } + } + } + } + else + { + if (delta[1] <= (Real)0) + { + if (delta[0] <= (Real)0) + { + // z-face + FaceOverlap(2, 0, 1, K, C, radius, delta, result); + } + else + { + // xz-edge + if (delta[0] * delta[0] + delta[2] * delta[2] <= radius * radius) + { + EdgeOverlap(2, 0, 1, K, C, radius, delta, result); + } + else + { + EdgeSeparated(2, 0, 1, K, C, radius, delta, V, result); + } + } + } + else + { + if (delta[0] <= (Real)0) + { + // yz-edge + if (delta[1] * delta[1] + delta[2] * delta[2] <= radius * radius) + { + EdgeOverlap(1, 2, 0, K, C, radius, delta, result); + } + else + { + EdgeSeparated(1, 2, 0, K, C, radius, delta, V, result); + } + } + else + { + // xyz-vertex + if (Dot(delta, delta) <= radius * radius) + { + VertexOverlap(K, radius, delta, result); + } + else + { + VertexSeparated(K, radius, delta, V, result); + } + } + } + } + } + else + { + // x-face + FaceUnbounded(0, 1, 2, K, C, radius, delta, V, result); + } + } + else + { + if (delta[0] <= radius) + { + // y-face + FaceUnbounded(1, 2, 0, K, C, radius, delta, V, result); + } + else + { + // xy-edge + EdgeUnbounded(0, 1, 2, K, C, radius, delta, V, result); + } + } + } + else + { + if (delta[1] <= radius) + { + if (delta[0] <= radius) + { + // z-face + FaceUnbounded(2, 0, 1, K, C, radius, delta, V, result); + } + else + { + // xz-edge + EdgeUnbounded(2, 0, 1, K, C, radius, delta, V, result); + } + } + else + { + if (delta[0] <= radius) + { + // yz-edge + EdgeUnbounded(1, 2, 0, K, C, radius, delta, V, result); + } + else + { + // xyz-vertex + VertexUnbounded(K, C, radius, delta, V, result); + } + } + } + } + + private: + void InteriorOverlap(Vector3 const& C, Result& result) + { + result.intersectionType = -1; + result.contactTime = (Real)0; + result.contactPoint = C; + } + + void VertexOverlap(Vector3 const& K, Real radius, + Vector3 const& delta, Result& result) + { + result.intersectionType = (Dot(delta, delta) < radius * radius ? -1 : 1); + result.contactTime = (Real)0; + result.contactPoint = K; + } + + void EdgeOverlap(int i0, int i1, int i2, Vector3 const& K, + Vector3 const& C, Real radius, Vector3 const& delta, + Result& result) + { + result.intersectionType = (delta[i0] * delta[i0] + delta[i1] * delta[i1] < radius * radius ? -1 : 1); + result.contactTime = (Real)0; + result.contactPoint[i0] = K[i0]; + result.contactPoint[i1] = K[i1]; + result.contactPoint[i2] = C[i2]; + } + + void FaceOverlap(int i0, int i1, int i2, Vector3 const& K, + Vector3 const& C, Real radius, Vector3 const& delta, + Result& result) + { + result.intersectionType = (delta[i0] < radius ? -1 : 1); + result.contactTime = (Real)0; + result.contactPoint[i0] = K[i0]; + result.contactPoint[i1] = C[i1]; + result.contactPoint[i2] = C[i2]; + } + + void VertexSeparated(Vector3 const& K, Real radius, + Vector3 const& delta, Vector3 const& V, Result& result) + { + if (V[0] < (Real)0 || V[1] < (Real)0 || V[2] < (Real)0) + { + DoQueryRayRoundedVertex(K, radius, delta, V, result); + } + } + + void EdgeSeparated(int i0, int i1, int i2, Vector3 const& K, + Vector3 const& C, Real radius, Vector3 const& delta, + Vector3 const& V, Result& result) + { + if (V[i0] < (Real)0 || V[i1] < (Real)0) + { + DoQueryRayRoundedEdge(i0, i1, i2, K, C, radius, delta, V, result); + } + } + + void VertexUnbounded(Vector3 const& K, Vector3 const& C, Real radius, + Vector3 const& delta, Vector3 const& V, Result& result) + { + if (V[0] < (Real)0 && V[1] < (Real)0 && V[2] < (Real)0) + { + // Determine the face of the rounded box that is intersected + // by the ray C+T*V. + Real T = (radius - delta[0]) / V[0]; + int j0 = 0; + Real temp = (radius - delta[1]) / V[1]; + if (temp > T) + { + T = temp; + j0 = 1; + } + temp = (radius - delta[2]) / V[2]; + if (temp > T) + { + T = temp; + j0 = 2; + } + + // The j0-rounded face is the candidate for intersection. + int j1 = (j0 + 1) % 3; + int j2 = (j1 + 1) % 3; + DoQueryRayRoundedFace(j0, j1, j2, K, C, radius, delta, V, result); + } + } + + void EdgeUnbounded(int i0, int i1, int /* i2 */, Vector3 const& K, + Vector3 const& C, Real radius, Vector3 const& delta, + Vector3 const& V, Result& result) + { + if (V[i0] < (Real)0 && V[i1] < (Real)0) + { + // Determine the face of the rounded box that is intersected + // by the ray C+T*V. + Real T = (radius - delta[i0]) / V[i0]; + int j0 = i0; + Real temp = (radius - delta[i1]) / V[i1]; + if (temp > T) + { + T = temp; + j0 = i1; + } + + // The j0-rounded face is the candidate for intersection. + int j1 = (j0 + 1) % 3; + int j2 = (j1 + 1) % 3; + DoQueryRayRoundedFace(j0, j1, j2, K, C, radius, delta, V, result); + } + } + + void FaceUnbounded(int i0, int i1, int i2, Vector3 const& K, + Vector3 const& C, Real radius, Vector3 const& delta, + Vector3 const& V, Result& result) + { + if (V[i0] < (Real)0) + { + DoQueryRayRoundedFace(i0, i1, i2, K, C, radius, delta, V, result); + } + } + + void DoQueryRayRoundedVertex(Vector3 const& K, Real radius, + Vector3 const& delta, Vector3 const& V, Result& result) + { + Real a1 = Dot(V, delta); + if (a1 < (Real)0) + { + // The caller must ensure that a0 > 0 and a2 > 0. + Real a0 = Dot(delta, delta) - radius * radius; + Real a2 = Dot(V, V); + Real adiscr = a1 * a1 - a2 * a0; + if (adiscr >= (Real)0) + { + // The ray intersects the rounded vertex, so the sphere-box + // contact point is the vertex. + result.intersectionType = 1; + result.contactTime = -(a1 + std::sqrt(adiscr)) / a2; + result.contactPoint = K; + } + } + } + + void DoQueryRayRoundedEdge(int i0, int i1, int i2, Vector3 const& K, + Vector3 const& C, Real radius, Vector3 const& delta, + Vector3 const& V, Result& result) + { + Real b1 = V[i0] * delta[i0] + V[i1] * delta[i1]; + if (b1 < (Real)0) + { + // The caller must ensure that b0 > 0 and b2 > 0. + Real b0 = delta[i0] * delta[i0] + delta[i1] * delta[i1] - radius * radius; + Real b2 = V[i0] * V[i0] + V[i1] * V[i1]; + Real bdiscr = b1 * b1 - b2 * b0; + if (bdiscr >= (Real)0) + { + Real T = -(b1 + std::sqrt(bdiscr)) / b2; + Real p2 = C[i2] + T * V[i2]; + if (-K[i2] <= p2) + { + if (p2 <= K[i2]) + { + // The ray intersects the finite cylinder of the + // rounded edge, so the sphere-box contact point + // is on the corresponding box edge. + result.intersectionType = 1; + result.contactTime = T; + result.contactPoint[i0] = K[i0]; + result.contactPoint[i1] = K[i1]; + result.contactPoint[i2] = p2; + } + else + { + // The ray intersects the infinite cylinder but + // not the finite cylinder of the rounded edge. + // It is possible the ray intersects the rounded + // vertex for K. + DoQueryRayRoundedVertex(K, radius, delta, V, result); + } + } + else + { + // The ray intersects the infinite cylinder but + // not the finite cylinder of the rounded edge. + // It is possible the ray intersects the rounded + // vertex for otherK. + Vector3 otherK, otherDelta; + otherK[i0] = K[i0]; + otherK[i1] = K[i1]; + otherK[i2] = -K[i2]; + otherDelta[i0] = C[i0] - otherK[i0]; + otherDelta[i1] = C[i1] - otherK[i1]; + otherDelta[i2] = C[i2] - otherK[i2]; + DoQueryRayRoundedVertex(otherK, radius, otherDelta, V, result); + } + } + } + } + + void DoQueryRayRoundedFace(int i0, int i1, int i2, Vector3 const& K, + Vector3 const& C, Real radius, Vector3 const& delta, + Vector3 const& V, Result& result) + { + Vector3 otherK, otherDelta; + + Real T = (radius - delta[i0]) / V[i0]; + Real p1 = C[i1] + T * V[i1]; + Real p2 = C[i2] + T * V[i2]; + + if (p1 < -K[i1]) + { + // The ray potentially intersects the rounded (i0,i1)-edge + // whose top-most vertex is otherK. + otherK[i0] = K[i0]; + otherK[i1] = -K[i1]; + otherK[i2] = K[i2]; + otherDelta[i0] = C[i0] - otherK[i0]; + otherDelta[i1] = C[i1] - otherK[i1]; + otherDelta[i2] = C[i2] - otherK[i2]; + DoQueryRayRoundedEdge(i0, i1, i2, otherK, C, radius, otherDelta, V, result); + if (result.intersectionType == 0) + { + if (p2 < -K[i2]) + { + // The ray potentially intersects the rounded + // (i2,i0)-edge whose right-most vertex is otherK. + otherK[i0] = K[i0]; + otherK[i1] = K[i1]; + otherK[i2] = -K[i2]; + otherDelta[i0] = C[i0] - otherK[i0]; + otherDelta[i1] = C[i1] - otherK[i1]; + otherDelta[i2] = C[i2] - otherK[i2]; + DoQueryRayRoundedEdge(i2, i0, i1, otherK, C, radius, otherDelta, V, result); + } + else if (p2 > K[i2]) + { + // The ray potentially intersects the rounded + // (i2,i0)-edge whose right-most vertex is K. + DoQueryRayRoundedEdge(i2, i0, i1, K, C, radius, delta, V, result); + } + } + } + else if (p1 <= K[i1]) + { + if (p2 < -K[i2]) + { + // The ray potentially intersects the rounded + // (i2,i0)-edge whose right-most vertex is otherK. + otherK[i0] = K[i0]; + otherK[i1] = K[i1]; + otherK[i2] = -K[i2]; + otherDelta[i0] = C[i0] - otherK[i0]; + otherDelta[i1] = C[i1] - otherK[i1]; + otherDelta[i2] = C[i2] - otherK[i2]; + DoQueryRayRoundedEdge(i2, i0, i1, otherK, C, radius, otherDelta, V, result); + } + else if (p2 <= K[i2]) + { + // The ray intersects the i0-face of the rounded box, so + // the sphere-box contact point is on the corresponding + // box face. + result.intersectionType = 1; + result.contactTime = T; + result.contactPoint[i0] = K[i0]; + result.contactPoint[i1] = p1; + result.contactPoint[i2] = p2; + } + else // p2 > K[i2] + { + // The ray potentially intersects the rounded + // (i2,i0)-edge whose right-most vertex is K. + DoQueryRayRoundedEdge(i2, i0, i1, K, C, radius, delta, V, result); + } + } + else // p1 > K[i1] + { + // The ray potentially intersects the rounded (i0,i1)-edge + // whose top-most vertex is K. + DoQueryRayRoundedEdge(i0, i1, i2, K, C, radius, delta, V, result); + if (result.intersectionType == 0) + { + if (p2 < -K[i2]) + { + // The ray potentially intersects the rounded + // (i2,i0)-edge whose right-most vertex is otherK. + otherK[i0] = K[i0]; + otherK[i1] = K[i1]; + otherK[i2] = -K[i2]; + otherDelta[i0] = C[i0] - otherK[i0]; + otherDelta[i1] = C[i1] - otherK[i1]; + otherDelta[i2] = C[i2] - otherK[i2]; + DoQueryRayRoundedEdge(i2, i0, i1, otherK, C, radius, otherDelta, V, result); + } + else if (p2 > K[i2]) + { + // The ray potentially intersects the rounded + // (i2,i0)-edge whose right-most vertex is K. + DoQueryRayRoundedEdge(i2, i0, i1, K, C, radius, delta, V, result); + } + } + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrArc2Arc2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrArc2Arc2.h new file mode 100644 index 0000000..e1b7e5b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrArc2Arc2.h @@ -0,0 +1,217 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class FIQuery, Arc2> + { + public: + // The possible 'configuration' in Result are listed as an + // enumeration. The valid array elements are listed in the comments. + enum + { + NO_INTERSECTION, + NONCOCIRCULAR_ONE_POINT, // point[0] + NONCOCIRCULAR_TWO_POINTS, // point[0], point[1] + COCIRCULAR_ONE_POINT, // point[0] + COCIRCULAR_TWO_POINTS, // point[0], point[1] + COCIRCULAR_ONE_POINT_ONE_ARC, // point[0], arc[0] + COCIRCULAR_ONE_ARC, // arc[0] + COCIRCULAR_TWO_ARCS // arc[0], arc[1] + }; + + struct Result + { + // 'true' iff configuration != NO_INTERSECTION + bool intersect; + + // one of the enumerations listed previously + int configuration; + + Vector2 point[2]; + Arc2 arc[2]; + }; + + Result operator()(Arc2 const& arc0, Arc2 const& arc1) + { + // Assume initially there are no intersections. If we find at + // least one intersection, we will set result.intersect to 'true'. + Result result; + result.intersect = false; + result.configuration = NO_INTERSECTION; + result.point[0] = { (Real)0, (Real)0 }; + result.point[1] = { (Real)0, (Real)0 }; + + Circle2 circle0(arc0.center, arc0.radius); + Circle2 circle1(arc1.center, arc1.radius); + FIQuery, Circle2> ccQuery; + auto ccResult = ccQuery(circle0, circle1); + if (!ccResult.intersect) + { + // The arcs do not intersect. + result.configuration = NO_INTERSECTION; + return result; + } + + if (ccResult.numIntersections == std::numeric_limits::max()) + { + // The arcs are cocircular. Determine whether they overlap. + // Let arc0 be and arc1 be . The points are + // ordered counterclockwise around the circle of the arc. + if (arc1.Contains(arc0.end[0])) + { + result.intersect = true; + if (arc1.Contains(arc0.end[1])) + { + if (arc0.Contains(arc1.end[0]) && arc0.Contains(arc1.end[1])) + { + if (arc0.end[0] == arc1.end[0] && arc0.end[1] == arc1.end[1]) + { + // The arcs are the same. + result.configuration = COCIRCULAR_ONE_ARC; + result.arc[0] = arc0; + } + else + { + // arc0 and arc1 overlap in two disjoint + // subsets. + if (arc0.end[0] != arc1.end[1]) + { + if (arc1.end[0] != arc0.end[1]) + { + // The arcs overlap in two disjoint + // subarcs, each of positive subtended + // angle: , + result.configuration = COCIRCULAR_TWO_ARCS; + result.arc[0] = Arc2(arc0.center, arc0.radius, arc0.end[0], arc1.end[1]); + result.arc[1] = Arc2(arc0.center, arc0.radius, arc1.end[0], arc0.end[1]); + } + else // B0 = A1 + { + // The intersection is a point {A1} + // and an arc . + result.configuration = COCIRCULAR_ONE_POINT_ONE_ARC; + result.point[0] = arc0.end[1]; + result.arc[0] = Arc2(arc0.center, arc0.radius, arc0.end[0], arc1.end[1]); + } + } + else // A0 = B1 + { + if (arc1.end[0] != arc0.end[1]) + { + // The intersection is a point {A0} + // and an arc . + result.configuration = COCIRCULAR_ONE_POINT_ONE_ARC; + result.point[0] = arc0.end[0]; + result.arc[0] = Arc2(arc0.center, arc0.radius, arc1.end[0], arc0.end[1]); + } + else + { + // The arcs shared endpoints, so the + // union is a circle. + result.configuration = COCIRCULAR_TWO_POINTS; + result.point[0] = arc0.end[0]; + result.point[1] = arc0.end[1]; + } + } + } + } + else + { + // Arc0 inside arc1, . + result.configuration = COCIRCULAR_ONE_ARC; + result.arc[0] = arc0; + } + } + else + { + if (arc0.end[0] != arc1.end[1]) + { + // Arc0 and arc1 overlap, . + result.configuration = COCIRCULAR_ONE_ARC; + result.arc[0] = Arc2(arc0.center, arc0.radius, arc0.end[0], arc1.end[1]); + } + else + { + // Arc0 and arc1 share endpoint, + // with A0 = B1. + result.configuration = COCIRCULAR_ONE_POINT; + result.point[0] = arc0.end[0]; + } + } + return result; + } + + if (arc1.Contains(arc0.end[1])) + { + result.intersect = true; + if (arc0.end[1] != arc1.end[0]) + { + // Arc0 and arc1 overlap in a single arc, + // . + result.configuration = COCIRCULAR_ONE_ARC; + result.arc[0] = Arc2(arc0.center, arc0.radius, arc1.end[0], arc0.end[1]); + } + else + { + // Arc0 and arc1 share endpoint, + // with B0 = A1. + result.configuration = COCIRCULAR_ONE_POINT; + result.point[0] = arc1.end[0]; + } + return result; + } + + if (arc0.Contains(arc1.end[0])) + { + // Arc1 inside arc0, . + result.intersect = true; + result.configuration = COCIRCULAR_ONE_ARC; + result.arc[0] = arc1; + } + else + { + // Arcs do not overlap, . + result.configuration = NO_INTERSECTION; + } + return result; + } + + // Test whether circle-circle intersection points are on the arcs. + int numIntersections = 0; + for (int i = 0; i < ccResult.numIntersections; ++i) + { + if (arc0.Contains(ccResult.point[i]) && arc1.Contains(ccResult.point[i])) + { + result.point[numIntersections++] = ccResult.point[i]; + result.intersect = true; + } + } + + if (numIntersections == 2) + { + result.configuration = NONCOCIRCULAR_TWO_POINTS; + } + else if (numIntersections == 1) + { + result.configuration = NONCOCIRCULAR_ONE_POINT; + } + else + { + result.configuration = NO_INTERSECTION; + } + + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrCapsule3Capsule3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrCapsule3Capsule3.h new file mode 100644 index 0000000..b0b44a2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrCapsule3Capsule3.h @@ -0,0 +1,35 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Capsule3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Capsule3 const& capsule0, Capsule3 const& capsule1) + { + Result result; + DCPQuery, Segment3> ssQuery; + auto ssResult = ssQuery(capsule0.segment, capsule1.segment); + Real rSum = capsule0.radius + capsule1.radius; + result.intersect = (ssResult.distance <= rSum); + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrCircle2Arc2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrCircle2Arc2.h new file mode 100644 index 0000000..6049eaf --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrCircle2Arc2.h @@ -0,0 +1,73 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class FIQuery, Arc2> + { + public: + struct Result + { + bool intersect; + + // The number of intersections is 0, 1, 2 or maxInt = + // std::numeric_limits::max(). When 1, the arc and circle + // intersect in a single point. When 2, the arc is not on the + // circle and they intersect in two points. When maxInt, the + // arc is on the circle. + int numIntersections; + + // Valid only when numIntersections = 1 or 2. + Vector2 point[2]; + + // Valid only when numIntersections = maxInt. + Arc2 arc; + }; + + Result operator()(Circle2 const& circle, Arc2 const& arc) + { + Result result; + + Circle2 circleOfArc(arc.center, arc.radius); + FIQuery, Circle2> ccQuery; + auto ccResult = ccQuery(circle, circleOfArc); + if (!ccResult.intersect) + { + result.intersect = false; + result.numIntersections = 0; + return result; + } + + if (ccResult.numIntersections == std::numeric_limits::max()) + { + // The arc is on the circle. + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + result.arc = arc; + return result; + } + + // Test whether circle-circle intersection points are on the arc. + for (int i = 0; i < ccResult.numIntersections; ++i) + { + result.numIntersections = 0; + if (arc.Contains(ccResult.point[i])) + { + result.point[result.numIntersections++] = ccResult.point[i]; + result.intersect = true; + } + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrCircle2Circle2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrCircle2Circle2.h new file mode 100644 index 0000000..a7d93fd --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrCircle2Circle2.h @@ -0,0 +1,156 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Circle2> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Circle2 const& circle0, Circle2 const& circle1) + { + Result result; + Vector2 diff = circle0.center - circle1.center; + result.intersect = (Length(diff) <= circle0.radius + circle1.radius); + return result; + } + }; + + template + class FIQuery, Circle2> + { + public: + struct Result + { + bool intersect; + + // The number of intersections is 0, 1, 2 or maxInt = + // std::numeric_limits::max(). When 1, the circles are + // tangent and intersect in a single point. When 2, circles have + // two transverse intersection points. When maxInt, the circles + // are the same. + int numIntersections; + + // Valid only when numIntersections = 1 or 2. + Vector2 point[2]; + + // Valid only when numIntersections = maxInt. + Circle2 circle; + }; + + Result operator()(Circle2 const& circle0, Circle2 const& circle1) + { + // The two circles are |X-C0| = R0 and |X-C1| = R1. Define + // U = C1 - C0 and V = Perp(U) where Perp(x,y) = (y,-x). Note + // that Dot(U,V) = 0 and |V|^2 = |U|^2. The intersection points X + // can be written in the form X = C0+s*U+t*V and + // X = C1+(s-1)*U+t*V. Squaring the circle equations and + // substituting these formulas into them yields + // R0^2 = (s^2 + t^2)*|U|^2 + // R1^2 = ((s-1)^2 + t^2)*|U|^2. + // Subtracting and solving for s yields + // s = ((R0^2-R1^2)/|U|^2 + 1)/2 + // Then replace in the first equation and solve for t^2 + // t^2 = (R0^2/|U|^2) - s^2. + // In order for there to be solutions, the right-hand side must be + // nonnegative. Some algebra leads to the condition for existence + // of solutions, + // (|U|^2 - (R0+R1)^2)*(|U|^2 - (R0-R1)^2) <= 0. + // This reduces to + // |R0-R1| <= |U| <= |R0+R1|. + // If |U| = |R0-R1|, then the circles are side-by-side and just + // tangent. If |U| = |R0+R1|, then the circles are nested and + // just tangent. If |R0-R1| < |U| < |R0+R1|, then the two circles + // to intersect in two points. + + Result result; + + Vector2 U = circle1.center - circle0.center; + Real USqrLen = Dot(U, U); + Real R0 = circle0.radius, R1 = circle1.radius; + Real R0mR1 = R0 - R1; + if (USqrLen == (Real)0 && R0mR1 == (Real)0) + { + // Circles are the same. + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + result.circle = circle0; + return result; + } + + Real R0mR1Sqr = R0mR1 * R0mR1; + if (USqrLen < R0mR1Sqr) + { + // The circles do not intersect. + result.intersect = false; + result.numIntersections = 0; + return result; + } + + Real R0pR1 = R0 + R1; + Real R0pR1Sqr = R0pR1 * R0pR1; + if (USqrLen > R0pR1Sqr) + { + // The circles do not intersect. + result.intersect = false; + result.numIntersections = 0; + return result; + } + + if (USqrLen < R0pR1Sqr) + { + if (R0mR1Sqr < USqrLen) + { + Real invUSqrLen = (Real)1 / USqrLen; + Real s = (Real)0.5 * ((R0 * R0 - R1 * R1) * invUSqrLen + (Real)1); + Vector2 tmp = circle0.center + s * U; + + // In theory, discr is nonnegative. However, numerical round-off + // errors can make it slightly negative. Clamp it to zero. + Real discr = R0 * R0 * invUSqrLen - s * s; + if (discr < (Real)0) + { + discr = (Real)0; + } + Real t = std::sqrt(discr); + Vector2 V{ U[1], -U[0] }; + result.point[0] = tmp - t * V; + result.point[1] = tmp + t * V; + result.numIntersections = (t > (Real)0 ? 2 : 1); + } + else + { + // |U| = |R0-R1|, circles are tangent. + result.numIntersections = 1; + result.point[0] = circle0.center + (R0 / R0mR1) * U; + } + } + else + { + // |U| = |R0+R1|, circles are tangent. + result.numIntersections = 1; + result.point[0] = circle0.center + (R0 / R0pR1) * U; + } + + // The circles intersect in 1 or 2 points. + result.intersect = true; + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrConvexPolygonHyperplane.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrConvexPolygonHyperplane.h new file mode 100644 index 0000000..9588507 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrConvexPolygonHyperplane.h @@ -0,0 +1,401 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The intersection queries are based on the document +// https://www.geometrictools.com/Documentation/ClipConvexPolygonByHyperplane.pdf + +namespace WwiseGTE +{ + template + class TIQuery>, Hyperplane> + { + public: + enum class Configuration + { + SPLIT, + POSITIVE_SIDE_VERTEX, + POSITIVE_SIDE_EDGE, + POSITIVE_SIDE_STRICT, + NEGATIVE_SIDE_VERTEX, + NEGATIVE_SIDE_EDGE, + NEGATIVE_SIDE_STRICT, + CONTAINED, + INVALID_POLYGON + }; + + struct Result + { + bool intersect; + Configuration configuration; + }; + + Result operator()(std::vector> const& polygon, Hyperplane const& hyperplane) + { + Result result; + + size_t const numVertices = polygon.size(); + if (numVertices < 3) + { + // The convex polygon must have at least 3 vertices. + result.intersect = false; + result.configuration = Configuration::INVALID_POLYGON; + return result; + } + + // Determine on which side of the hyperplane each vertex lies. + size_t numPositive = 0, numNegative = 0, numZero = 0; + for (size_t i = 0; i < numVertices; ++i) + { + Real h = Dot(hyperplane.normal, polygon[i]) - hyperplane.constant; + if (h > (Real)0) + { + ++numPositive; + } + else if (h < (Real)0) + { + ++numNegative; + } + else + { + ++numZero; + } + } + + if (numPositive > 0) + { + if (numNegative > 0) + { + result.intersect = true; + result.configuration = Configuration::SPLIT; + } + else if (numZero == 0) + { + result.intersect = false; + result.configuration = Configuration::POSITIVE_SIDE_STRICT; + } + else if (numZero == 1) + { + result.intersect = true; + result.configuration = Configuration::POSITIVE_SIDE_VERTEX; + } + else // numZero > 1 + { + result.intersect = true; + result.configuration = Configuration::POSITIVE_SIDE_EDGE; + } + } + else if (numNegative > 0) + { + if (numZero == 0) + { + result.intersect = false; + result.configuration = Configuration::NEGATIVE_SIDE_STRICT; + } + else if (numZero == 1) + { + // The polygon touches the plane in a vertex or an edge. + result.intersect = true; + result.configuration = Configuration::NEGATIVE_SIDE_VERTEX; + } + else // numZero > 1 + { + result.intersect = true; + result.configuration = Configuration::NEGATIVE_SIDE_EDGE; + } + } + else // numZero == numVertices + { + result.intersect = true; + result.configuration = Configuration::CONTAINED; + } + + return result; + } + }; + + template + class FIQuery>, Hyperplane> + { + public: + enum class Configuration + { + SPLIT, + POSITIVE_SIDE_VERTEX, + POSITIVE_SIDE_EDGE, + POSITIVE_SIDE_STRICT, + NEGATIVE_SIDE_VERTEX, + NEGATIVE_SIDE_EDGE, + NEGATIVE_SIDE_STRICT, + CONTAINED, + INVALID_POLYGON + }; + + struct Result + { + // The intersection is either empty, a single vertex, a single + // edge or the polygon is contained by the hyperplane. + Configuration configuration; + std::vector> intersection; + + // If 'configuration' is POSITIVE_* or SPLIT, this polygon is the + // portion of the query input 'polygon' on the positive side of + // the hyperplane with possibly a vertex or edge on the hyperplane. + std::vector> positivePolygon; + + // If 'configuration' is NEGATIVE_* or SPLIT, this polygon is the + // portion of the query input 'polygon' on the negative side of + // the hyperplane with possibly a vertex or edge on the hyperplane. + std::vector> negativePolygon; + }; + + Result operator()(std::vector> const& polygon, Hyperplane const& hyperplane) + { + Result result; + + size_t const numVertices = polygon.size(); + if (numVertices < 3) + { + // The convex polygon must have at least 3 vertices. + result.configuration = Configuration::INVALID_POLYGON; + return result; + } + + // Determine on which side of the hyperplane the vertices live. + // The index maxPosIndex stores the index of the vertex on the + // positive side of the hyperplane that is farthest from the + // hyperplane. The index maxNegIndex stores the index of the + // vertex on the negative side of the hyperplane that is farthest + // from the hyperplane. If one or the other such vertex does not + // exist, the corresponding index will remain its initial value of + // max(size_t). + std::vector height(numVertices); + std::vector zeroHeightIndices; + zeroHeightIndices.reserve(numVertices); + size_t numPositive = 0, numNegative = 0; + Real maxPosHeight = -std::numeric_limits::max(); + Real maxNegHeight = std::numeric_limits::max(); + size_t maxPosIndex = std::numeric_limits::max(); + size_t maxNegIndex = std::numeric_limits::max(); + for (size_t i = 0; i < numVertices; ++i) + { + height[i] = Dot(hyperplane.normal, polygon[i]) - hyperplane.constant; + if (height[i] > (Real)0) + { + ++numPositive; + if (height[i] > maxPosHeight) + { + maxPosHeight = height[i]; + maxPosIndex = i; + } + } + else if (height[i] < (Real)0) + { + ++numNegative; + if (height[i] < maxNegHeight) + { + maxNegHeight = height[i]; + maxNegIndex = i; + } + } + else + { + zeroHeightIndices.push_back(i); + } + } + + if (numPositive > 0) + { + if (numNegative > 0) + { + result.configuration = Configuration::SPLIT; + + bool doSwap = (maxPosHeight < -maxNegHeight); + if (doSwap) + { + for (auto& h : height) + { + h = -h; + } + std::swap(maxPosIndex, maxNegIndex); + } + + SplitPolygon(polygon, height, maxPosIndex, result); + + if (doSwap) + { + std::swap(result.positivePolygon, result.negativePolygon); + } + } + else + { + size_t numZero = zeroHeightIndices.size(); + if (numZero == 0) + { + result.configuration = Configuration::POSITIVE_SIDE_STRICT; + } + else if (numZero == 1) + { + result.configuration = Configuration::POSITIVE_SIDE_VERTEX; + result.intersection = + { + polygon[zeroHeightIndices[0]] + }; + } + else // numZero > 1 + { + result.configuration = Configuration::POSITIVE_SIDE_EDGE; + result.intersection = + { + polygon[zeroHeightIndices[0]], + polygon[zeroHeightIndices[1]] + }; + } + result.positivePolygon = polygon; + } + } + else if (numNegative > 0) + { + size_t numZero = zeroHeightIndices.size(); + if (numZero == 0) + { + result.configuration = Configuration::NEGATIVE_SIDE_STRICT; + } + else if (numZero == 1) + { + result.configuration = Configuration::NEGATIVE_SIDE_VERTEX; + result.intersection = + { + polygon[zeroHeightIndices[0]] + }; + } + else // numZero > 1 + { + result.configuration = Configuration::NEGATIVE_SIDE_EDGE; + result.intersection = + { + polygon[zeroHeightIndices[0]], + polygon[zeroHeightIndices[1]] + }; + } + result.negativePolygon = polygon; + } + else // numZero == numVertices + { + result.configuration = Configuration::CONTAINED; + result.intersection = polygon; + } + + return result; + } + + protected: + void SplitPolygon(std::vector> const& polygon, + std::vector const& height, size_t maxPosIndex, Result& result) + { + // Find the largest contiguous subset of indices for which + // height[i] >= 0. + size_t const numVertices = polygon.size(); + std::list> positiveList; + positiveList.push_back(polygon[maxPosIndex]); + size_t end0 = maxPosIndex; + size_t end0prev = std::numeric_limits::max(); + for (size_t i = 0; i < numVertices; ++i) + { + end0prev = (end0 + numVertices - 1) % numVertices; + if (height[end0prev] >= (Real)0) + { + positiveList.push_front(polygon[end0prev]); + end0 = end0prev; + } + else + { + break; + } + } + + size_t end1 = maxPosIndex; + size_t end1next = std::numeric_limits::max(); + for (size_t i = 0; i < numVertices; ++i) + { + end1next = (end1 + 1) % numVertices; + if (height[end1next] >= (Real)0) + { + positiveList.push_back(polygon[end1next]); + end1 = end1next; + } + else + { + break; + } + } + + size_t index = end1next; + std::list> negativeList; + for (size_t i = 0; i < numVertices; ++i) + { + negativeList.push_back(polygon[index]); + index = (index + 1) % numVertices; + if (index == end0) + { + break; + } + } + + // Clip the polygon. + if (height[end0] > (Real)0) + { + Real t = -height[end0prev] / (height[end0] - height[end0prev]); + Real omt = (Real)1 - t; + Vector V = omt * polygon[end0prev] + t * polygon[end0]; + positiveList.push_front(V); + negativeList.push_back(V); + result.intersection.push_back(V); + } + else + { + negativeList.push_back(polygon[end0]); + result.intersection.push_back(polygon[end0]); + } + + if (height[end1] > (Real)0) + { + Real t = -height[end1next] / (height[end1] - height[end1next]); + Real omt = (Real)1 - t; + Vector V = omt * polygon[end1next] + t * polygon[end1]; + positiveList.push_back(V); + negativeList.push_front(V); + result.intersection.push_back(V); + } + else + { + negativeList.push_front(polygon[end1]); + result.intersection.push_back(polygon[end1]); + } + + result.positivePolygon.reserve(positiveList.size()); + for (auto const& p : positiveList) + { + result.positivePolygon.push_back(p); + } + + result.negativePolygon.reserve(negativeList.size()); + for (auto const& p : negativeList) + { + result.negativePolygon.push_back(p); + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrDisk2Sector2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrDisk2Sector2.h new file mode 100644 index 0000000..1b192b3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrDisk2Sector2.h @@ -0,0 +1,184 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The Circle2 object is considered to be a disk whose points X satisfy the +// constraint |X-C| <= R, where C is the disk center and R is the disk +// radius. The Sector2 object is also considered to be a solid. Also, +// the Sector2 object is required to be convex, so the sector angle must +// be in (0,pi/2], even though the Sector2 definition allows for angles +// larger than pi/2 (leading to nonconvex sectors). The sector vertex is +// V, the radius is L, the axis direction is D, and the angle is A. Sector +// points X satisfy |X-V| <= L and Dot(D,X-V) >= cos(A)|X-V| >= 0. +// +// A subproblem for the test-intersection query is to determine whether +// the disk intersects the cone of the sector. Although the query is in +// 2D, it is analogous to the 3D problem of determining whether a sphere +// and cone overlap. That algorithm is described in +// https://www.geometrictools.com/Documentation/IntersectionSphereCone.pdf +// The algorithm leads to coordinate-free pseudocode that applies to 2D +// as well as 3D. That function is the first SphereIntersectsCone on +// page 4 of the PDF. +// +// If the disk is outside the cone, there is no intersection. If the disk +// overlaps the cone, we then need to test whether the disk overlaps the +// disk of the sector. + +namespace WwiseGTE +{ + template + class TIQuery, Sector2> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Circle2 const& disk, Sector2 const& sector) + { + Result result; + + // Test whether the disk and the disk of the sector overlap. + Vector2 CmV = disk.center - sector.vertex; + Real sqrLengthCmV = Dot(CmV, CmV); + Real lengthCmV = std::sqrt(sqrLengthCmV); + if (lengthCmV > disk.radius + sector.radius) + { + // The disk is outside the disk of the sector. + result.intersect = false; + return result; + } + + // Test whether the disk and cone of the sector overlap. The + // comments about K, K', and K" refer to the PDF mentioned + // previously. + Vector2 U = sector.vertex - (disk.radius / sector.sinAngle) * sector.direction; + Vector2 CmU = disk.center - U; + Real lengthCmU = Length(CmU); + if (Dot(sector.direction, CmU) < lengthCmU * sector.cosAngle) + { + // The disk center is outside K" (in the white or gray + // regions). + result.intersect = false; + return result; + } + // The disk center is inside K" (in the red, orange, blue, or + // green regions). + Real dotDirCmV = Dot(sector.direction, CmV); + if (-dotDirCmV >= lengthCmV * sector.sinAngle) + { + // The disk center is inside K" and inside K' (in the blue + // or green regions). + if (lengthCmV <= disk.radius) + { + // The disk center is in the blue region, in which case + // the disk contains the sector's vertex. + result.intersect = true; + } + else + { + // The disk center is in the green region. + result.intersect = false; + } + return result; + } + + // To reach here, we know that the disk overlaps the sector's disk + // and the sector's cone. The disk center is in the orange region + // or in the red region (not including the segments that separate + // the red and blue regions). + + // Test whether the ray of the right boundary of the sector + // overlaps the disk. The ray direction U0 is a clockwise + // rotation of the cone axis by the cone angle. + Vector2 U0 + { + +sector.cosAngle * sector.direction[0] + sector.sinAngle * sector.direction[1], + -sector.sinAngle * sector.direction[0] + sector.cosAngle * sector.direction[1] + }; + Real dp0 = Dot(U0, CmV); + Real discr0 = disk.radius * disk.radius + dp0 * dp0 - sqrLengthCmV; + if (discr0 >= (Real)0) + { + // The ray intersects the disk. Now test whether the sector + // boundary segment contained by the ray overlaps the disk. + // The quadratic root tmin generates the ray-disk point of + // intersection closest to the sector vertex. + Real tmin = dp0 - std::sqrt(discr0); + if (sector.radius >= tmin) + { + // The segment overlaps the disk. + result.intersect = true; + return result; + } + else + { + // The segment does not overlap the disk. We know the + // disks overlap, so if the disk center is outside the + // sector cone or on the right-boundary ray, the overlap + // occurs outside the cone, which implies the disk and + // sector do not intersect. + if (dotDirCmV <= lengthCmV * sector.cosAngle) + { + // The disk center is not inside the sector cone. + result.intersect = false; + return result; + } + } + } + + // Test whether the ray of the left boundary of the sector + // overlaps the disk. The ray direction U1 is a counterclockwise + // rotation of the cone axis by the cone angle. + Vector2 U1 + { + +sector.cosAngle * sector.direction[0] - sector.sinAngle * sector.direction[1], + +sector.sinAngle * sector.direction[0] + sector.cosAngle * sector.direction[1] + }; + Real dp1 = Dot(U1, CmV); + Real discr1 = disk.radius * disk.radius + dp1 * dp1 - sqrLengthCmV; + if (discr1 >= (Real)0) + { + // The ray intersects the disk. Now test whether the sector + // boundary segment contained by the ray overlaps the disk. + // The quadratic root tmin generates the ray-disk point of + // intersection closest to the sector vertex. + Real tmin = dp1 - std::sqrt(discr1); + if (sector.radius >= tmin) + { + result.intersect = true; + return result; + } + else + { + // The segment does not overlap the disk. We know the + // disks overlap, so if the disk center is outside the + // sector cone or on the right-boundary ray, the overlap + // occurs outside the cone, which implies the disk and + // sector do not intersect. + if (dotDirCmV <= lengthCmV * sector.cosAngle) + { + // The disk center is not inside the sector cone. + result.intersect = false; + return result; + } + } + } + + // To reach here, a strict subset of the sector's arc boundary + // must intersect the disk. + result.intersect = true; + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrEllipse2Ellipse2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrEllipse2Ellipse2.h new file mode 100644 index 0000000..19f29c0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrEllipse2Ellipse2.h @@ -0,0 +1,1249 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +// The test-intersection and find-intersection queries implemented here are +// discussed in the document +// https://www.geometrictools.com/Documentation/IntersectionOfEllipses.pdf +// The Real type should support exact rational arithmetic in order for the +// polynomial root construction to be robust. The classification of the +// intersections depends on various sign tests of computed values. If these +// values are computed with floating-point arithmetic, the sign tests can +// lead to misclassification. +// +// The area-of-intersection query is discussed in the document +// https://www.geometrictools.com/Documentation/AreaIntersectingEllipses.pdf + +namespace WwiseGTE +{ + template + class TIQuery, Ellipse2> + { + public: + // The query tests the relationship between the ellipses as solid + // objects. + enum + { + ELLIPSES_SEPARATED, + ELLIPSES_OVERLAP, + ELLIPSE0_OUTSIDE_ELLIPSE1_BUT_TANGENT, + ELLIPSE0_STRICTLY_CONTAINS_ELLIPSE1, + ELLIPSE0_CONTAINS_ELLIPSE1_BUT_TANGENT, + ELLIPSE1_STRICTLY_CONTAINS_ELLIPSE0, + ELLIPSE1_CONTAINS_ELLIPSE0_BUT_TANGENT, + ELLIPSES_EQUAL + }; + + // The ellipse axes are already normalized, which most likely + // introduced rounding errors. + int operator()(Ellipse2 const& ellipse0, Ellipse2 const& ellipse1) + { + Real const zero = 0, one = 1; + + // Get the parameters of ellipe0. + Vector2 K0 = ellipse0.center; + Matrix2x2 R0; + R0.SetCol(0, ellipse0.axis[0]); + R0.SetCol(1, ellipse0.axis[1]); + Matrix2x2 D0{ + one / (ellipse0.extent[0] * ellipse0.extent[0]), zero, + zero, one / (ellipse0.extent[1] * ellipse0.extent[1]) }; + + // Get the parameters of ellipse1. + Vector2 K1 = ellipse1.center; + Matrix2x2 R1; + R1.SetCol(0, ellipse1.axis[0]); + R1.SetCol(1, ellipse1.axis[1]); + Matrix2x2 D1{ + one / (ellipse1.extent[0] * ellipse1.extent[0]), zero, + zero, one / (ellipse1.extent[1] * ellipse1.extent[1]) }; + + // Compute K2. + Matrix2x2 D0NegHalf{ + ellipse0.extent[0], zero, + zero, ellipse0.extent[1] }; + Matrix2x2 D0Half{ + one / ellipse0.extent[0], zero, + zero, one / ellipse0.extent[1] }; + Vector2 K2 = D0Half * ((K1 - K0) * R0); + + // Compute M2. + Matrix2x2 R1TR0D0NegHalf = MultiplyATB(R1, R0 * D0NegHalf); + Matrix2x2 M2 = MultiplyATB(R1TR0D0NegHalf, D1) * R1TR0D0NegHalf; + + // Factor M2 = R*D*R^T. + SymmetricEigensolver2x2 es; + std::array D; + std::array, 2> evec; + es(M2(0, 0), M2(0, 1), M2(1, 1), +1, D, evec); + Matrix2x2 R; + R.SetCol(0, evec[0]); + R.SetCol(1, evec[1]); + + // Compute K = R^T*K2. + Vector2 K = K2 * R; + + // Transformed ellipse0 is Z^T*Z = 1 and transformed ellipse1 is + // (Z-K)^T*D*(Z-K) = 0. + + // The minimum and maximum squared distances from the origin of + // points on transformed ellipse1 are used to determine whether + // the ellipses intersect, are separated or one contains the + // other. + Real minSqrDistance = std::numeric_limits::max(); + Real maxSqrDistance = zero; + + if (K == Vector2::Zero()) + { + // The special case of common centers must be handled + // separately. It is not possible for the ellipses to be + // separated. + for (int i = 0; i < 2; ++i) + { + Real invD = one / D[i]; + if (invD < minSqrDistance) + { + minSqrDistance = invD; + } + if (invD > maxSqrDistance) + { + maxSqrDistance = invD; + } + } + return Classify(minSqrDistance, maxSqrDistance, zero); + } + + // The closest point P0 and farthest point P1 are solutions to + // s0*D*(P0 - K) = P0 and s1*D1*(P1 - K) = P1 for some scalars s0 + // and s1 that are roots to the function + // f(s) = d0*k0^2/(d0*s-1)^2 + d1*k1^2/(d1*s-1)^2 - 1 + // where D = diagonal(d0,d1) and K = (k0,k1). + Real d0 = D[0], d1 = D[1]; + Real c0 = K[0] * K[0], c1 = K[1] * K[1]; + + // Sort the values so that d0 >= d1. This allows us to bound the + // roots of f(s), of which there are at most 4. + std::vector> param(2); + if (d0 >= d1) + { + param[0] = std::make_pair(d0, c0); + param[1] = std::make_pair(d1, c1); + } + else + { + param[0] = std::make_pair(d1, c1); + param[1] = std::make_pair(d0, c0); + } + + std::vector> valid; + valid.reserve(2); + if (param[0].first > param[1].first) + { + // d0 > d1 + for (int i = 0; i < 2; ++i) + { + if (param[i].second > zero) + { + valid.push_back(param[i]); + } + } + } + else + { + // d0 = d1 + param[0].second += param[1].second; + if (param[0].second > zero) + { + valid.push_back(param[0]); + } + } + + size_t numValid = valid.size(); + int numRoots = 0; + Real roots[4]; + if (numValid == 2) + { + GetRoots(valid[0].first, valid[1].first, valid[0].second, + valid[1].second, numRoots, roots); + } + else if (numValid == 1) + { + GetRoots(valid[0].first, valid[0].second, numRoots, roots); + } + // else: numValid cannot be zero because we already handled case + // K = 0 + + for (int i = 0; i < numRoots; ++i) + { + Real s = roots[i]; + Real p0 = d0 * K[0] * s / (d0 * s - (Real)1); + Real p1 = d1 * K[1] * s / (d1 * s - (Real)1); + Real sqrDistance = p0 * p0 + p1 * p1; + if (sqrDistance < minSqrDistance) + { + minSqrDistance = sqrDistance; + } + if (sqrDistance > maxSqrDistance) + { + maxSqrDistance = sqrDistance; + } + } + + return Classify(minSqrDistance, maxSqrDistance, d0 * c0 + d1 * c1); + } + + private: + void GetRoots(Real d0, Real c0, int& numRoots, Real* roots) + { + // f(s) = d0*c0/(d0*s-1)^2 - 1 + Real const one = (Real)1; + Real temp = std::sqrt(d0 * c0); + Real inv = one / d0; + numRoots = 2; + roots[0] = (one - temp) * inv; + roots[1] = (one + temp) * inv; + } + + void GetRoots(Real d0, Real d1, Real c0, Real c1, int& numRoots, Real* roots) + { + // f(s) = d0*c0/(d0*s-1)^2 + d1*c1/(d1*s-1)^2 - 1 with d0 > d1 + + Real const zero = 0, one = (Real)1; + Real d0c0 = d0 * c0; + Real d1c1 = d1 * c1; + Real sum = d0c0 + d1c1; + Real sqrtsum = std::sqrt(sum); + + std::function F = [&one, d0, d1, d0c0, d1c1](Real s) + { + Real invN0 = one / (d0 * s - one); + Real invN1 = one / (d1 * s - one); + Real term0 = d0c0 * invN0 * invN0; + Real term1 = d1c1 * invN1 * invN1; + Real f = term0 + term1 - one; + return f; + }; + + std::function DF = [&one, d0, d1, d0c0, d1c1](Real s) + { + Real const two = 2; + Real invN0 = one / (d0 * s - one); + Real invN1 = one / (d1 * s - one); + Real term0 = d0 * d0c0 * invN0 * invN0 * invN0; + Real term1 = d1 * d1c1 * invN1 * invN1 * invN1; + Real df = -two * (term0 + term1); + return df; + }; + + unsigned int const maxIterations = static_cast( + 3 + std::numeric_limits::digits - + std::numeric_limits::min_exponent); + unsigned int iterations; + numRoots = 0; + + Real invD0 = one / d0; + Real invD1 = one / d1; + Real smin, smax, s, fval; + + // Compute root in (-infinity,1/d0). Obtain a lower bound for the + // root better than -std::numeric_limits::max(). + smax = invD0; + fval = sum - one; + if (fval > zero) + { + smin = (one - sqrtsum) * invD1; // < 0 + fval = F(smin); + LogAssert(fval <= zero, "Unexpected condition."); + } + else + { + smin = zero; + } + iterations = RootsBisection::Find(F, smin, smax, -one, one, + maxIterations, s); + fval = F(s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + + // Compute roots (if any) in (1/d0,1/d1). It is the case that + // F(1/d0) = +infinity, F'(1/d0) = -infinity + // F(1/d1) = +infinity, F'(1/d1) = +infinity + // F"(s) > 0 for all s in the domain of F + // Compute the unique root r of F'(s) on (1/d0,1/d1). The + // bisector needs only the signs at the endpoints, so we pass -1 + // and +1 instead of the infinite values. If F(r) < 0, F(s) has + // two roots in the interval. If F(r) = 0, F(s) has only one root + // in the interval. + Real const oneThird = (Real)1 / (Real)3; + Real rho = std::pow(d0 * d0c0 / (d1 * d1c1), oneThird); + Real smid = (one + rho) / (d0 + rho * d1); + Real fmid = F(smid); + if (fmid <= zero) + { + // Pass in signs rather than infinities, because the bisector cares + // only about the signs. + iterations = RootsBisection::Find(F, invD0, smid, one, -one, + maxIterations, s); + fval = F(s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + iterations = RootsBisection::Find(F, smid, invD1, -one, one, + maxIterations, s); + fval = F(s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + } + else if (fmid == zero) + { + roots[numRoots++] = smid; + } + + // Compute root in (1/d1,+infinity). Obtain an upper bound for + // the root better than std::numeric_limits::max(). + smin = invD1; + smax = (one + sqrtsum) * invD1; // > 1/d1 + fval = F(smax); + LogAssert(fval <= zero, "Unexpected condition."); + iterations = RootsBisection::Find(F, smin, smax, one, -one, + maxIterations, s); + fval = F(s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + } + + int Classify(Real minSqrDistance, Real maxSqrDistance, Real d0c0pd1c1) + { + Real const one = (Real)1; + + if (maxSqrDistance < one) + { + return ELLIPSE0_STRICTLY_CONTAINS_ELLIPSE1; + } + else if (maxSqrDistance > one) + { + if (minSqrDistance < one) + { + return ELLIPSES_OVERLAP; + } + else if (minSqrDistance > one) + { + if (d0c0pd1c1 > one) + { + return ELLIPSES_SEPARATED; + } + else + { + return ELLIPSE1_STRICTLY_CONTAINS_ELLIPSE0; + } + } + else // minSqrDistance = 1 + { + if (d0c0pd1c1 > one) + { + return ELLIPSE0_OUTSIDE_ELLIPSE1_BUT_TANGENT; + } + else + { + return ELLIPSE1_CONTAINS_ELLIPSE0_BUT_TANGENT; + } + } + } + else // maxSqrDistance = 1 + { + if (minSqrDistance < one) + { + return ELLIPSE0_CONTAINS_ELLIPSE1_BUT_TANGENT; + } + else // minSqrDistance = 1 + { + return ELLIPSES_EQUAL; + } + } + } + }; + + template + class FIQuery, Ellipse2> + { + public: + // The queries find the intersections (if any) of the ellipses treated + // as hollow objects. The implementations use the same concepts. + FIQuery() + : + mZero((Real)0), + mOne((Real)1), + mTwo((Real)2), + mPi((Real)GTE_C_PI), + mTwoPi((Real)GTE_C_TWO_PI) + { + } + + struct Result + { + // This value is true when the ellipses intersect in at least one + // point. + bool intersect; + + // If the ellipses are not the same, numPoints is 0 through 4 and + // that number of elements of 'points' are valid. If the ellipses + // are the same, numPoints is set to maxInt and 'points' is + // invalid. + int numPoints; + std::array, 4> points; + std::array isTransverse; + }; + + // The ellipse axes are already normalized, which most likely + // introducedrounding errors. + Result operator()(Ellipse2 const& ellipse0, Ellipse2 const& ellipse1) + { + Vector2 rCenter, rAxis[2], rSqrExtent; + + rCenter = { ellipse0.center[0], ellipse0.center[1] }; + rAxis[0] = { ellipse0.axis[0][0], ellipse0.axis[0][1] }; + rAxis[1] = { ellipse0.axis[1][0], ellipse0.axis[1][1] }; + rSqrExtent = + { + ellipse0.extent[0] * ellipse0.extent[0], + ellipse0.extent[1] * ellipse0.extent[1] + }; + ToCoefficients(rCenter, rAxis, rSqrExtent, mA); + + rCenter = { ellipse1.center[0], ellipse1.center[1] }; + rAxis[0] = { ellipse1.axis[0][0], ellipse1.axis[0][1] }; + rAxis[1] = { ellipse1.axis[1][0], ellipse1.axis[1][1] }; + rSqrExtent = + { + ellipse1.extent[0] * ellipse1.extent[0], + ellipse1.extent[1] * ellipse1.extent[1] + }; + ToCoefficients(rCenter, rAxis, rSqrExtent, mB); + + Result result; + DoRootFinding(result); + return result; + } + + // The axis directions do not have to be unit length. The quadratic + // equations are constructed according to the details of the PDF + // document about the intersection of ellipses. + Result operator()(Vector2 const& center0, + Vector2 const axis0[2], Vector2 const& sqrExtent0, + Vector2 const& center1, Vector2 const axis1[2], + Vector2 const& sqrExtent1) + { + Vector2 rCenter, rAxis[2], rSqrExtent; + + rCenter = { center0[0], center0[1] }; + rAxis[0] = { axis0[0][0], axis0[0][1] }; + rAxis[1] = { axis0[1][0], axis0[1][1] }; + rSqrExtent = { sqrExtent0[0], sqrExtent0[1] }; + ToCoefficients(rCenter, rAxis, rSqrExtent, mA); + + rCenter = { center1[0], center1[1] }; + rAxis[0] = { axis1[0][0], axis1[0][1] }; + rAxis[1] = { axis1[1][0], axis1[1][1] }; + rSqrExtent = { sqrExtent1[0], sqrExtent1[1] }; + ToCoefficients(rCenter, rAxis, rSqrExtent, mB); + + Result result; + DoRootFinding(result); + return result; + } + + + // Compute the area of intersection of ellipses. + struct AreaResult + { + // The configuration of the two ellipses. + enum + { + ELLIPSES_ARE_EQUAL, + ELLIPSES_ARE_SEPARATED, + E0_CONTAINS_E1, + E1_CONTAINS_E0, + ONE_CHORD_REGION, + FOUR_CHORD_REGION + }; + + // One of the enumerates, determined in the call to AreaDispatch. + int configuration; + + // Information about the ellipse-ellipse intersection points. + Result findResult; + + // The area of intersection of the ellipses. + Real area; + }; + + // The ellipse axes are already normalized, which most likely + // introduced rounding errors. + AreaResult AreaOfIntersection(Ellipse2 const& ellipse0, + Ellipse2 const& ellipse1) + { + EllipseInfo E0; + E0.center = ellipse0.center; + E0.axis = ellipse0.axis; + E0.extent = ellipse0.extent; + E0.sqrExtent = ellipse0.extent * ellipse0.extent; + FinishEllipseInfo(E0); + + EllipseInfo E1; + E1.center = ellipse1.center; + E1.axis = ellipse1.axis; + E1.extent = ellipse1.extent; + E1.sqrExtent = ellipse1.extent * ellipse0.extent; + FinishEllipseInfo(E1); + + AreaResult ar; + ar.configuration = 0; + ar.findResult = operator()(ellipse0, ellipse1); + ar.area = mZero; + AreaDispatch(E0, E1, ar); + return ar; + } + + // The axis directions do not have to be unit length. The positive + // definite matrices are constructed according to the details of the + // PDF documentabout the intersection of ellipses. + AreaResult AreaOfIntersection(Vector2 const& center0, + Vector2 const axis0[2], Vector2 const& sqrExtent0, + Vector2 const& center1, Vector2 const axis1[2], + Vector2 const& sqrExtent1) + { + EllipseInfo E0; + E0.center = center0; + E0.axis = { axis0[0], axis0[1] }; + E0.extent = + { + std::sqrt(sqrExtent0[0]), + std::sqrt(sqrExtent0[1]) + }; + E0.sqrExtent = sqrExtent0; + FinishEllipseInfo(E0); + + EllipseInfo E1; + E1.center = center1; + E1.axis = { axis1[0], axis1[1] }; + E1.extent = + { + std::sqrt(sqrExtent1[0]), + std::sqrt(sqrExtent1[1]) + }; + E1.sqrExtent = sqrExtent1; + FinishEllipseInfo(E1); + + AreaResult ar; + ar.configuration = 0; + ar.findResult = operator()(center0, axis0, sqrExtent0, center1, axis1, sqrExtent1); + ar.area = mZero; + AreaDispatch(E0, E1, ar); + return ar; + } + + private: + // Compute the coefficients of the quadratic equation but with the + // y^2-coefficient of 1. + void ToCoefficients(Vector2 const& center, Vector2 const axis[2], + Vector2 const& sqrExtent, std::array& coeff) + { + Real denom0 = Dot(axis[0], axis[0]) * sqrExtent[0]; + Real denom1 = Dot(axis[1], axis[1]) * sqrExtent[1]; + Matrix2x2 outer0 = OuterProduct(axis[0], axis[0]); + Matrix2x2 outer1 = OuterProduct(axis[1], axis[1]); + Matrix2x2 A = outer0 / denom0 + outer1 / denom1; + Vector2 product = A * center; + Vector2 B = -mTwo * product; + Real const& denom = A(1, 1); + coeff[0] = (Dot(center, product) - mOne) / denom; + coeff[1] = B[0] / denom; + coeff[2] = B[1] / denom; + coeff[3] = A(0, 0) / denom; + coeff[4] = mTwo * A(0, 1) / denom; + // coeff[5] = A(1, 1) / denom = 1; + } + + void DoRootFinding(Result& result) + { + bool allZero = true; + for (int i = 0; i < 5; ++i) + { + mD[i] = mA[i] - mB[i]; + if (mD[i] != mZero) + { + allZero = false; + } + } + if (allZero) + { + result.intersect = false; + result.numPoints = std::numeric_limits::max(); + return; + } + + result.numPoints = 0; + + mA2Div2 = mA[2] / mTwo; + mA4Div2 = mA[4] / mTwo; + mC[0] = mA[0] - mA2Div2 * mA2Div2; + mC[1] = mA[1] - mA2Div2 * mA[4]; + mC[2] = mA[3] - mA4Div2 * mA4Div2; // c[2] > 0 + mE[0] = mD[0] - mA2Div2 * mD[2]; + mE[1] = mD[1] - mA2Div2 * mD[4] - mA4Div2 * mD[2]; + mE[2] = mD[3] - mA4Div2 * mD[4]; + + if (mD[4] != mZero) + { + Real xbar = -mD[2] / mD[4]; + Real ebar = mE[0] + xbar * (mE[1] + xbar * mE[2]); + if (ebar != mZero) + { + D4NotZeroEBarNotZero(result); + } + else + { + D4NotZeroEBarZero(xbar, result); + } + } + else if (mD[2] != mZero) // d[4] = 0 + { + if (mE[2] != mZero) + { + D4ZeroD2NotZeroE2NotZero(result); + } + else + { + D4ZeroD2NotZeroE2Zero(result); + } + } + else // d[2] = d[4] = 0 + { + D4ZeroD2Zero(result); + } + + result.intersect = (result.numPoints > 0); + } + + void D4NotZeroEBarNotZero(Result& result) + { + // The graph of w = -e(x)/d(x) is a hyperbola. + Real d2d2 = mD[2] * mD[2], d2d4 = mD[2] * mD[4], d4d4 = mD[4] * mD[4]; + Real e0e0 = mE[0] * mE[0], e0e1 = mE[0] * mE[1], e0e2 = mE[0] * mE[2]; + Real e1e1 = mE[1] * mE[1], e1e2 = mE[1] * mE[2], e2e2 = mE[2] * mE[2]; + std::array f = + { + mC[0] * d2d2 + e0e0, + mC[1] * d2d2 + mTwo * (mC[0] * d2d4 + e0e1), + mC[2] * d2d2 + mC[0] * d4d4 + e1e1 + mTwo * (mC[1] * d2d4 + e0e2), + mC[1] * d4d4 + mTwo * (mC[2] * d2d4 + e1e2), + mC[2] * d4d4 + e2e2 // > 0 + }; + + std::map rmMap; + RootsPolynomial::template SolveQuartic(f[0], f[1], f[2], + f[3], f[4], rmMap); + + // xbar cannot be a root of f(x), so d(x) != 0 and we can solve + // directly for w = -e(x)/d(x). + for (auto const& rm : rmMap) + { + Real const& x = rm.first; + Real w = -(mE[0] + x * (mE[1] + x * mE[2])) / (mD[2] + mD[4] * x); + Real y = w - (mA2Div2 + x * mA4Div2); + result.points[result.numPoints] = { x, y }; + result.isTransverse[result.numPoints++] = (rm.second == 1); + } + } + + void D4NotZeroEBarZero(Real const& xbar, Result& result) + { + // Factor e(x) = (d2 + d4*x)*(h0 + h1*x). The w-equation has + // two solution components, x = xbar and w = -(h0 + h1*x). + Real translate, w, y; + + // Compute intersection of x = xbar with ellipse. + Real ncbar = -(mC[0] + xbar * (mC[1] + xbar * mC[2])); + if (ncbar >= mZero) + { + translate = mA2Div2 + xbar * mA4Div2; + w = std::sqrt(ncbar); + y = w - translate; + result.points[result.numPoints] = { xbar, y }; + if (w > mZero) + { + result.isTransverse[result.numPoints++] = true; + w = -w; + y = w - translate; + result.points[result.numPoints] = { xbar, y }; + result.isTransverse[result.numPoints++] = true; + } + else + { + result.isTransverse[result.numPoints++] = false; + } + } + + // Compute intersections of w = -(h0 + h1*x) with ellipse. + std::array h; + h[1] = mE[2] / mD[4]; + h[0] = (mE[1] - mD[2] * h[1]) / mD[4]; + std::array f = + { + mC[0] + h[0] * h[0], + mC[1] + mTwo * h[0] * h[1], + mC[2] + h[1] * h[1] // > 0 + }; + + std::map rmMap; + RootsPolynomial::template SolveQuadratic(f[0], f[1], f[2], rmMap); + for (auto const& rm : rmMap) + { + Real const& x = rm.first; + translate = mA2Div2 + x * mA4Div2; + w = -(h[0] + x * h[1]); + y = w - translate; + result.points[result.numPoints] = { x, y }; + result.isTransverse[result.numPoints++] = (rm.second == 1); + } + } + + void D4ZeroD2NotZeroE2NotZero(Result& result) + { + Real d2d2 = mD[2] * mD[2]; + std::array f = + { + mC[0] * d2d2 + mE[0] * mE[0], + mC[1] * d2d2 + mTwo * mE[0] * mE[1], + mC[2] * d2d2 + mE[1] * mE[1] + mTwo * mE[0] * mE[2], + mTwo * mE[1] * mE[2], + mE[2] * mE[2] // > 0 + }; + + std::map rmMap; + RootsPolynomial::template SolveQuartic(f[0], f[1], f[2], f[3], + f[4], rmMap); + for (auto const& rm : rmMap) + { + Real const& x = rm.first; + Real translate = mA2Div2 + x * mA4Div2; + Real w = -(mE[0] + x * (mE[1] + x * mE[2])) / mD[2]; + Real y = w - translate; + result.points[result.numPoints] = { x, y }; + result.isTransverse[result.numPoints++] = (rm.second == 1); + } + } + + void D4ZeroD2NotZeroE2Zero(Result& result) + { + Real d2d2 = mD[2] * mD[2]; + std::array f = + { + mC[0] * d2d2 + mE[0] * mE[0], + mC[1] * d2d2 + mTwo * mE[0] * mE[1], + mC[2] * d2d2 + mE[1] * mE[1] + }; + + std::map rmMap; + RootsPolynomial::template SolveQuadratic(f[0], f[1], f[2], rmMap); + for (auto const& rm : rmMap) + { + Real const& x = rm.first; + Real translate = mA2Div2 + x * mA4Div2; + Real w = -(mE[0] + x * mE[1]) / mD[2]; + Real y = w - translate; + result.points[result.numPoints] = { x, y }; + result.isTransverse[result.numPoints++] = (rm.second == 1); + } + } + + void D4ZeroD2Zero(Result& result) + { + // e(x) cannot be identically zero, because if it were, then all + // d[i] = 0. But we tested that case previously and exited. + + if (mE[2] != mZero) + { + // Make e(x) monic, f(x) = e(x)/e2 = x^2 + (e1/e2)*x + (e0/e2) + // = x^2 + f1*x + f0. + std::array f = { mE[0] / mE[2], mE[1] / mE[2] }; + + Real mid = -f[1] / mTwo; + Real discr = mid * mid - f[0]; + if (discr > mZero) + { + // The theoretical roots of e(x) are + // x = -f1/2 + s*sqrt(discr) where s in {-1,+1}. For each + // root, determine exactly the sign of c(x). We need + // c(x) <= 0 in order to solve for w^2 = -c(x). At the + // root, + // c(x) = c0 + c1*x + c2*x^2 = c0 + c1*x - c2*(f0 + f1*x) + // = (c0 - c2*f0) + (c1 - c2*f1)*x + // = g0 + g1*x + // We need g0 + g1*x <= 0. + Real sqrtDiscr = std::sqrt(discr); + std::array g = + { + mC[0] - mC[2] * f[0], + mC[1] - mC[2] * f[1] + }; + + if (g[1] > mZero) + { + // We need s*sqrt(discr) <= -g[0]/g[1] + f1/2. + Real r = -g[0] / g[1] - mid; + + // s = +1: + if (r >= mZero) + { + Real rsqr = r * r; + if (discr < rsqr) + { + SpecialIntersection(mid + sqrtDiscr, true, result); + } + else if (discr == rsqr) + { + SpecialIntersection(mid + sqrtDiscr, false, result); + } + } + + // s = -1: + if (r > mZero) + { + SpecialIntersection(mid - sqrtDiscr, true, result); + } + else + { + Real rsqr = r * r; + if (discr > rsqr) + { + SpecialIntersection(mid - sqrtDiscr, true, result); + } + else if (discr == rsqr) + { + SpecialIntersection(mid - sqrtDiscr, false, result); + } + } + } + else if (g[1] < mZero) + { + // We need s*sqrt(discr) >= -g[0]/g[1] + f1/2. + Real r = -g[0] / g[1] - mid; + + // s = -1: + if (r <= mZero) + { + Real rsqr = r * r; + if (discr < rsqr) + { + SpecialIntersection(mid - sqrtDiscr, true, result); + } + else + { + SpecialIntersection(mid - sqrtDiscr, false, result); + } + } + + // s = +1: + if (r < mZero) + { + SpecialIntersection(mid + sqrtDiscr, true, result); + } + else + { + Real rsqr = r * r; + if (discr > rsqr) + { + SpecialIntersection(mid + sqrtDiscr, true, result); + } + else if (discr == rsqr) + { + SpecialIntersection(mid + sqrtDiscr, false, result); + } + } + } + else // g[1] = 0 + { + // The graphs of c(x) and f(x) are parabolas of the + // same shape. One is a vertical translation of the + // other. + if (g[0] < mZero) + { + // The graph of f(x) is above that of c(x). + SpecialIntersection(mid - sqrtDiscr, true, result); + SpecialIntersection(mid + sqrtDiscr, true, result); + } + else if (g[0] == mZero) + { + // The graphs of c(x) and f(x) are the same parabola. + SpecialIntersection(mid - sqrtDiscr, false, result); + SpecialIntersection(mid + sqrtDiscr, false, result); + } + } + } + else if (discr == mZero) + { + // The theoretical root of f(x) is x = -f1/2. + Real nchat = -(mC[0] + mid * (mC[1] + mid * mC[2])); + if (nchat > mZero) + { + SpecialIntersection(mid, true, result); + } + else if (nchat == mZero) + { + SpecialIntersection(mid, false, result); + } + } + } + else if (mE[1] != mZero) + { + Real xhat = -mE[0] / mE[1]; + Real nchat = -(mC[0] + xhat * (mC[1] + xhat * mC[2])); + if (nchat > mZero) + { + SpecialIntersection(xhat, true, result); + } + else if (nchat == mZero) + { + SpecialIntersection(xhat, false, result); + } + } + } + + // Helper function for D4ZeroD2Zero. + void SpecialIntersection(Real const& x, bool transverse, Result& result) + { + if (transverse) + { + Real translate = mA2Div2 + x * mA4Div2; + Real nc = -(mC[0] + x * (mC[1] + x * mC[2])); + if (nc < mZero) + { + // Clamp to eliminate the rounding error, but duplicate + // the point because we know that it is a transverse + // intersection. + nc = mZero; + } + + Real w = std::sqrt(nc); + Real y = w - translate; + result.points[result.numPoints] = { x, y }; + result.isTransverse[result.numPoints++] = true; + w = -w; + y = w - translate; + result.points[result.numPoints] = { x, y }; + result.isTransverse[result.numPoints++] = true; + } + else + { + // The vertical line at the root is tangent to the ellipse. + Real y = -(mA2Div2 + x * mA4Div2); // w = 0 + result.points[result.numPoints] = { x, y }; + result.isTransverse[result.numPoints++] = false; + } + } + + + // Helper functions for AreaOfIntersection. + struct EllipseInfo + { + Vector2 center; + std::array, 2> axis; + Vector2 extent, sqrExtent; + Matrix2x2 M; + Real AB; // extent[0] * extent[1] + Real halfAB; // extent[0] * extent[1] / 2 + Real BpA; // extent[1] + extent[0] + Real BmA; // extent[1] - extent[0] + }; + + // The axis, extent and sqrExtent members of E must be set before + // this function is called. + void FinishEllipseInfo(EllipseInfo& E) + { + E.M = OuterProduct(E.axis[0], E.axis[0]) / + (E.sqrExtent[0] * Dot(E.axis[0], E.axis[0])); + E.M += OuterProduct(E.axis[1], E.axis[1]) / + (E.sqrExtent[1] * Dot(E.axis[1], E.axis[1])); + E.AB = E.extent[0] * E.extent[1]; + E.halfAB = E.AB / mTwo; + E.BpA = E.extent[1] + E.extent[0]; + E.BmA = E.extent[1] - E.extent[0]; + } + + void AreaDispatch(EllipseInfo const& E0, EllipseInfo const& E1, AreaResult& ar) + { + if (ar.findResult.intersect) + { + if (ar.findResult.numPoints == 1) + { + // Containment or separation. + AreaCS(E0, E1, ar); + } + else if (ar.findResult.numPoints == 2) + { + if (ar.findResult.isTransverse[0]) + { + // Both intersection points are transverse. + Area2(E0, E1, 0, 1, ar); + } + else + { + // Both intersection points are tangential, so one + // ellipse is contained in the other. + AreaCS(E0, E1, ar); + } + } + else if (ar.findResult.numPoints == 3) + { + // The tangential intersection is irrelevant in the area + // computation. + if (!ar.findResult.isTransverse[0]) + { + Area2(E0, E1, 1, 2, ar); + } + else if (!ar.findResult.isTransverse[1]) + { + Area2(E0, E1, 2, 0, ar); + } + else // ar.findResult.isTransverse[2] == false + { + Area2(E0, E1, 0, 1, ar); + } + } + else // ar.findResult.numPoints == 4 + { + Area4(E0, E1, ar); + } + } + else + { + // Containment, separation, or same ellipse. + AreaCS(E0, E1, ar); + } + } + + void AreaCS(EllipseInfo const& E0, EllipseInfo const& E1, AreaResult& ar) + { + if (ar.findResult.numPoints <= 1) + { + Vector2 diff = E0.center - E1.center; + Real qform0 = Dot(diff, E0.M * diff); + Real qform1 = Dot(diff, E1.M * diff); + if (qform0 > mOne && qform1 > mOne) + { + // Each ellipse center is outside the other ellipse, so + // the ellipses are separated (numPoints == 0) or outside + // each other and just touching (numPoints == 1). + ar.configuration = AreaResult::ELLIPSES_ARE_SEPARATED; + ar.area = mZero; + } + else + { + // One ellipse is inside the other. Determine this + // indirectly by comparing areas. + if (E0.AB < E1.AB) + { + ar.configuration = AreaResult::E1_CONTAINS_E0; + ar.area = mPi * E0.AB; + } + else + { + ar.configuration = AreaResult::E0_CONTAINS_E1; + ar.area = mPi * E1.AB; + } + } + } + else + { + ar.configuration = AreaResult::ELLIPSES_ARE_EQUAL; + ar.area = mPi * E0.AB; + } + } + + void Area2(EllipseInfo const& E0, EllipseInfo const& E1, int i0, int i1, AreaResult& ar) + { + ar.configuration = AreaResult::ONE_CHORD_REGION; + + // The endpoints of the chord. + Vector2 const& P0 = ar.findResult.points[i0]; + Vector2 const& P1 = ar.findResult.points[i1]; + + // Compute locations relative to the ellipses. + Vector2 P0mC0 = P0 - E0.center, P0mC1 = P0 - E1.center; + Vector2 P1mC0 = P1 - E0.center, P1mC1 = P1 - E1.center; + + // Compute the ellipse normal vectors at endpoint P0. This is + // sufficient information to determine chord endpoint order. + Vector2 N0 = E0.M * P0mC0, N1 = E1.M * P0mC1; + Real dotperp = DotPerp(N1, N0); + + // Choose the endpoint order for the chord region associated + // with E0. + if (dotperp > mZero) + { + // The chord order for E0 is and for E1 is . + ar.area = + ComputeAreaChordRegion(E0, P0mC0, P1mC0) + + ComputeAreaChordRegion(E1, P1mC1, P0mC1); + } + else + { + // The chord order for E0 is and for E1 is . + ar.area = + ComputeAreaChordRegion(E0, P1mC0, P0mC0) + + ComputeAreaChordRegion(E1, P0mC1, P1mC1); + } + } + + void Area4(EllipseInfo const& E0, EllipseInfo const& E1, AreaResult& ar) + { + ar.configuration = AreaResult::FOUR_CHORD_REGION; + + // Select a counterclockwise ordering of the points of + // intersection. Use the polar coordinates for E0 to do this. + // The multimap is used in the event that computing the + // intersections involved numerical rounding errors that lead to + // a duplicate intersection, even though the intersections are all + // labeled as transverse. [See the comment in the function + // SpecialIntersection.] + std::multimap ordering; + int i; + for (i = 0; i < 4; ++i) + { + Vector2 PmC = ar.findResult.points[i] - E0.center; + Real x = Dot(E0.axis[0], PmC); + Real y = Dot(E0.axis[1], PmC); + Real theta = std::atan2(y, x); + ordering.insert(std::make_pair(theta, i)); + } + + std::array permute; + i = 0; + for (auto const& element : ordering) + { + permute[i++] = element.second; + } + + // Start with the area of the convex quadrilateral. + Vector2 diag20 = + ar.findResult.points[permute[2]] - ar.findResult.points[permute[0]]; + Vector2 diag31 = + ar.findResult.points[permute[3]] - ar.findResult.points[permute[1]]; + ar.area = std::fabs(DotPerp(diag20, diag31)) / mTwo; + + // Visit each pair of consecutive points. The selection of + // ellipse for the chord-region area calculation uses the "most + // counterclockwise" tangent vector. + for (int i0 = 3, i1 = 0; i1 < 4; i0 = i1++) + { + // Get a pair of consecutive points. + Vector2 const& P0 = ar.findResult.points[permute[i0]]; + Vector2 const& P1 = ar.findResult.points[permute[i1]]; + + // Compute locations relative to the ellipses. + Vector2 P0mC0 = P0 - E0.center, P0mC1 = P0 - E1.center; + Vector2 P1mC0 = P1 - E0.center, P1mC1 = P1 - E1.center; + + // Compute the ellipse normal vectors at endpoint P0. + Vector2 N0 = E0.M * P0mC0, N1 = E1.M * P0mC1; + Real dotperp = DotPerp(N1, N0); + if (dotperp > mZero) + { + // The chord goes with ellipse E0. + ar.area += ComputeAreaChordRegion(E0, P0mC0, P1mC0); + } + else + { + // The chord goes with ellipse E1. + ar.area += ComputeAreaChordRegion(E1, P0mC1, P1mC1); + } + } + } + + Real ComputeAreaChordRegion(EllipseInfo const& E, Vector2 const& P0mC, Vector2 const& P1mC) + { + // Compute polar coordinates for P0 and P1 on the ellipse. + Real x0 = Dot(E.axis[0], P0mC); + Real y0 = Dot(E.axis[1], P0mC); + Real theta0 = std::atan2(y0, x0); + Real x1 = Dot(E.axis[0], P1mC); + Real y1 = Dot(E.axis[1], P1mC); + Real theta1 = std::atan2(y1, x1); + + // The arc straddles the atan2 discontinuity on the negative + // x-axis. Wrap the second angle to be larger than the first + // angle. + if (theta1 < theta0) + { + theta1 += mTwoPi; + } + + // Compute the area portion of the sector due to the triangle. + Real triArea = std::fabs(DotPerp(P0mC, P1mC)) / mTwo; + + // Compute the chord region area. + Real dtheta = theta1 - theta0; + Real F0, F1, sectorArea; + if (dtheta <= mPi) + { + // Use the area formula directly. + // area(theta0,theta1) = F(theta1)-F(theta0)-area(triangle) + F0 = ComputeIntegral(E, theta0); + F1 = ComputeIntegral(E, theta1); + sectorArea = F1 - F0; + return sectorArea - triArea; + } + else + { + // The angle of the elliptical sector is larger than pi + // radians. Use the area formula + // area(theta0,theta1) = pi*a*b - area(theta1,theta0) + theta0 += mTwoPi; // ensure theta0 > theta1 + F0 = ComputeIntegral(E, theta0); + F1 = ComputeIntegral(E, theta1); + sectorArea = F0 - F1; + return mPi * E.AB - (sectorArea - triArea); + } + } + + Real ComputeIntegral(EllipseInfo const& E, Real const& theta) + { + Real twoTheta = mTwo * theta; + Real sn = std::sin(twoTheta); + Real cs = std::cos(twoTheta); + Real arg = E.BmA * sn / (E.BpA + E.BmA * cs); + return E.halfAB * (theta - std::atan(arg)); + } + + // Constants that are set up once (optimization for rational + // arithmetic). + Real mZero, mOne, mTwo, mPi, mTwoPi; + + // Various polynomial coefficients. The short names are meant to + // match the variable names used in the PDF documentation. + std::array mA, mB, mD, mF; + std::array mC, mE; + Real mA2Div2, mA4Div2; + }; + + // Template aliases for convenience. + template + using TIEllipses2 = TIQuery, Ellipse2>; + + template + using FIEllipses2 = FIQuery, Ellipse2>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrEllipsoid3Ellipsoid3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrEllipsoid3Ellipsoid3.h new file mode 100644 index 0000000..76b7b55 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrEllipsoid3Ellipsoid3.h @@ -0,0 +1,524 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Ellipsoid3> + { + public: + enum + { + ELLIPSOIDS_SEPARATED, + ELLIPSOIDS_INTERSECTING, + ELLIPSOID0_CONTAINS_ELLIPSOID1, + ELLIPSOID1_CONTAINS_ELLIPSOID0 + }; + + struct Result + { + // As solids, the ellipsoids intersect as long as they are not + // separated. + bool intersect; + + // This is one of the four enumerations listed above. + int relationship; + }; + + Result operator()(Ellipsoid3 const& ellipsoid0, Ellipsoid3 const& ellipsoid1) + { + Result result; + + Real const zero = (Real)0; + Real const one = (Real)1; + + // Get the parameters of ellipsoid0. + Vector3 K0 = ellipsoid0.center; + Matrix3x3 R0; + R0.SetCol(0, ellipsoid0.axis[0]); + R0.SetCol(1, ellipsoid0.axis[1]); + R0.SetCol(2, ellipsoid0.axis[2]); + Matrix3x3 D0{ + one / (ellipsoid0.extent[0] * ellipsoid0.extent[0]), zero, zero, + zero, one / (ellipsoid0.extent[1] * ellipsoid0.extent[1]), zero, + zero, zero, one / (ellipsoid0.extent[2] * ellipsoid0.extent[2]) }; + + // Get the parameters of ellipsoid1. + Vector3 K1 = ellipsoid1.center; + Matrix3x3 R1; + R1.SetCol(0, ellipsoid1.axis[0]); + R1.SetCol(1, ellipsoid1.axis[1]); + R1.SetCol(2, ellipsoid1.axis[2]); + Matrix3x3 D1{ + one / (ellipsoid1.extent[0] * ellipsoid1.extent[0]), zero, zero, + zero, one / (ellipsoid1.extent[1] * ellipsoid1.extent[1]), zero, + zero, zero, one / (ellipsoid1.extent[2] * ellipsoid1.extent[2]) }; + + // Compute K2. + Matrix3x3 D0NegHalf{ + ellipsoid0.extent[0], zero, zero, + zero, ellipsoid0.extent[1], zero, + zero, zero, ellipsoid0.extent[2] }; + Matrix3x3 D0Half{ + one / ellipsoid0.extent[0], zero, zero, + zero, one / ellipsoid0.extent[1], zero, + zero, zero, one / ellipsoid0.extent[2] }; + Vector3 K2 = D0Half * ((K1 - K0) * R0); + + // Compute M2. + Matrix3x3 R1TR0D0NegHalf = MultiplyATB(R1, R0 * D0NegHalf); + Matrix3x3 M2 = MultiplyATB(R1TR0D0NegHalf, D1) * R1TR0D0NegHalf; + + // Factor M2 = R*D*R^T. + SymmetricEigensolver3x3 es; + std::array D; + std::array, 3> evec; + es(M2(0, 0), M2(0, 1), M2(0, 2), M2(1, 1), M2(1, 2), M2(2, 2), false, +1, D, evec); + Matrix3x3 R; + R.SetCol(0, evec[0]); + R.SetCol(1, evec[1]); + R.SetCol(2, evec[2]); + + // Compute K = R^T*K2. + Vector3 K = K2 * R; + + // Transformed ellipsoid0 is Z^T*Z = 1 and transformed ellipsoid1 + // is (Z-K)^T*D*(Z-K) = 0. + + // The minimum and maximum squared distances from the origin of + // points on transformed ellipsoid1 are used to determine whether + // the ellipsoids intersect, are separated, or one contains the + // other. + Real minSqrDistance = std::numeric_limits::max(); + Real maxSqrDistance = zero; + int i; + + if (K == Vector3::Zero()) + { + // The special case of common centers must be handled + // separately. It is not possible for the ellipsoids to be + // separated. + for (i = 0; i < 3; ++i) + { + Real invD = one / D[i]; + if (invD < minSqrDistance) + { + minSqrDistance = invD; + } + if (invD > maxSqrDistance) + { + maxSqrDistance = invD; + } + } + + if (maxSqrDistance < one) + { + result.relationship = ELLIPSOID0_CONTAINS_ELLIPSOID1; + } + else if (minSqrDistance > (Real)1) + { + result.relationship = ELLIPSOID1_CONTAINS_ELLIPSOID0; + } + else + { + result.relationship = ELLIPSOIDS_INTERSECTING; + } + result.intersect = true; + return result; + } + + // The closest point P0 and farthest point P1 are solutions to + // s0*D*(P0 - K) = P0 and s1*D*(P1 - K) = P1 for some scalars s0 + // and s1 that are roots to the function + // f(s) = d0*k0^2/(d0*s-1)^2 + d1*k1^2/(d1*s-1)^2 + // + d2*k2^2/(d2*s-1)^2 - 1 + // where D = diagonal(d0,d1,d2) and K = (k0,k1,k2). + Real d0 = D[0], d1 = D[1], d2 = D[2]; + Real c0 = K[0] * K[0], c1 = K[1] * K[1], c2 = K[2] * K[2]; + + // Sort the values so that d0 >= d1 >= d2. This allows us to + // bound the roots of f(s), of which there are at most 6. + std::vector> param(3); + param[0] = std::make_pair(d0, c0); + param[1] = std::make_pair(d1, c1); + param[2] = std::make_pair(d2, c2); + std::sort(param.begin(), param.end(), std::greater>()); + + std::vector> valid; + valid.reserve(3); + if (param[0].first > param[1].first) + { + if (param[1].first > param[2].first) + { + // d0 > d1 > d2 + for (i = 0; i < 3; ++i) + { + if (param[i].second > (Real)0) + { + valid.push_back(param[i]); + } + } + } + else + { + // d0 > d1 = d2 + if (param[0].second > (Real)0) + { + valid.push_back(param[0]); + } + param[1].second += param[0].second; + if (param[1].second > (Real)0) + { + valid.push_back(param[1]); + } + } + } + else + { + if (param[1].first > param[2].first) + { + // d0 = d1 > d2 + param[0].second += param[1].second; + if (param[0].second > (Real)0) + { + valid.push_back(param[0]); + } + if (param[2].second > (Real)0) + { + valid.push_back(param[2]); + } + } + else + { + // d0 = d1 = d2 + param[0].second += param[1].second + param[2].second; + if (param[0].second > (Real)0) + { + valid.push_back(param[0]); + } + } + } + + size_t numValid = valid.size(); + int numRoots; + Real roots[6]; + if (numValid == 3) + { + GetRoots(valid[0].first, valid[1].first, valid[2].first, + valid[0].second, valid[1].second, valid[2].second, numRoots, roots); + } + else if (numValid == 2) + { + GetRoots(valid[0].first, valid[1].first, valid[0].second, + valid[1].second, numRoots, roots); + } + else if (numValid == 1) + { + GetRoots(valid[0].first, valid[0].second, numRoots, roots); + } + else + { + // numValid cannot be zero because we already handled case K = 0 + LogError("Unexpected condition."); + } + + for (i = 0; i < numRoots; ++i) + { + Real s = roots[i]; + Real p0 = d0 * K[0] * s / (d0 * s - (Real)1); + Real p1 = d1 * K[1] * s / (d1 * s - (Real)1); + Real p2 = d2 * K[2] * s / (d2 * s - (Real)1); + Real sqrDistance = p0 * p0 + p1 * p1 + p2 * p2; + if (sqrDistance < minSqrDistance) + { + minSqrDistance = sqrDistance; + } + if (sqrDistance > maxSqrDistance) + { + maxSqrDistance = sqrDistance; + } + } + + if (maxSqrDistance < one) + { + result.intersect = true; + result.relationship = ELLIPSOID0_CONTAINS_ELLIPSOID1; + } + else if (minSqrDistance > (Real)1) + { + if (d0 * c0 + d1 * c1 + d2 * c2 > one) + { + result.intersect = false; + result.relationship = ELLIPSOIDS_SEPARATED; + } + else + { + result.intersect = true; + result.relationship = ELLIPSOID1_CONTAINS_ELLIPSOID0; + } + } + else + { + result.intersect = true; + result.relationship = ELLIPSOIDS_INTERSECTING; + } + + return result; + } + + private: + void GetRoots(Real d0, Real c0, int& numRoots, Real* roots) + { + // f(s) = d0*c0/(d0*s-1)^2 - 1 + Real const one = (Real)1; + Real temp = std::sqrt(d0 * c0); + Real inv = one / d0; + numRoots = 2; + roots[0] = (one - temp) * inv; + roots[1] = (one + temp) * inv; + } + + void GetRoots(Real d0, Real d1, Real c0, Real c1, int& numRoots, Real* roots) + { + // f(s) = d0*c0/(d0*s-1)^2 + d1*c1/(d1*s-1)^2 - 1 + // with d0 > d1 + + Real const zero = (Real)0; + Real const one = (Real)1; + Real const two = (Real)2; + Real d0c0 = d0 * c0; + Real d1c1 = d1 * c1; + + std::function F = [&one, d0, d1, d0c0, d1c1](Real s) + { + Real invN0 = one / (d0 * s - one); + Real invN1 = one / (d1 * s - one); + Real term0 = d0c0 * invN0 * invN0; + Real term1 = d1c1 * invN1 * invN1; + Real f = term0 + term1 - one; + return f; + }; + + std::function DF = [&one, &two, d0, d1, d0c0, d1c1](Real s) + { + Real invN0 = one / (d0 * s - one); + Real invN1 = one / (d1 * s - one); + Real term0 = d0 * d0c0 * invN0 * invN0 * invN0; + Real term1 = d1 * d1c1 * invN1 * invN1 * invN1; + Real df = -two * (term0 + term1); + return df; + }; + + unsigned int const maxIterations = 1024; + unsigned int iterations; + numRoots = 0; + + // TODO: What role does epsilon play? + Real const epsilon = (Real)0.001; + Real multiplier0 = std::sqrt(two / (one - epsilon)); + Real multiplier1 = std::sqrt(one / (one + epsilon)); + Real sqrtd0c0 = std::sqrt(d0c0); + Real sqrtd1c1 = std::sqrt(d1c1); + Real invD0 = one / d0; + Real invD1 = one / d1; + Real temp0, temp1, smin, smax, s; + + // Compute root in (-infinity,1/d0). + temp0 = (one - multiplier0 * sqrtd0c0) * invD0; + temp1 = (one - multiplier0 * sqrtd1c1) * invD1; + + smin = std::min(temp0, temp1); + LogAssert(F(smin) < zero, "Unexpected condition."); + smax = (one - multiplier1 * sqrtd0c0) * invD0; + LogAssert(F(smax) > zero, "Unexpected condition."); + iterations = RootsBisection::Find(F, smin, smax, maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + + roots[numRoots++] = s; + + // Compute roots (if any) in (1/d0,1/d1). It is the case that + // F(1/d0) = +infinity, F'(1/d0) = -infinity + // F(1/d1) = +infinity, F'(1/d1) = +infinity + // F"(s) > 0 for all s in the domain of F + // Compute the unique root r of F'(s) on (1/d0,1/d1). The + // bisector needs only the signs at the endpoints, so we pass -1 + // and +1 instead of the infinite values. If F(r) < 0, F(s) has + // two roots in the interval. If F(r) = 0, F(s) has only one root + // in the interval. + Real smid; + iterations = RootsBisection::Find(DF, invD0, invD1, -one, one, + maxIterations, smid); + LogAssert(iterations > 0, "Unexpected condition."); + if (F(smid) < zero) + { + // Pass in signs rather than infinities, because the bisector + // cares only about the signs. + iterations = RootsBisection::Find(F, invD0, smid, one, -one, + maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + iterations = RootsBisection::Find(F, smid, invD1, -one, one, + maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + } + + // Compute root in (1/d1,+infinity). + temp0 = (one + multiplier0 * sqrtd0c0) * invD0; + temp1 = (one + multiplier0 * sqrtd1c1) * invD1; + smax = std::max(temp0, temp1); + LogAssert(F(smax) < zero, "Unexpected condition."); + smin = (one + multiplier1 * sqrtd1c1) * invD1; + LogAssert(F(smin) > zero, "Unexpected condition."); + iterations = RootsBisection::Find(F, smin, smax, maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + } + + void GetRoots(Real d0, Real d1, Real d2, Real c0, Real c1, Real c2, + int& numRoots, Real* roots) + { + // f(s) = d0*c0/(d0*s-1)^2 + d1*c1/(d1*s-1)^2 + // + d2*c2/(d2*s-1)^2 - 1 with d0 > d1 > d2 + + Real const zero = (Real)0; + Real const one = (Real)1; + Real const three = (Real)3; + Real d0c0 = d0 * c0; + Real d1c1 = d1 * c1; + Real d2c2 = d2 * c2; + + std::function F = [&one, d0, d1, d2, d0c0, d1c1, d2c2](Real s) + { + Real invN0 = one / (d0 * s - one); + Real invN1 = one / (d1 * s - one); + Real invN2 = one / (d2 * s - one); + Real term0 = d0c0 * invN0 * invN0; + Real term1 = d1c1 * invN1 * invN1; + Real term2 = d2c2 * invN2 * invN2; + Real f = term0 + term1 + term2 - one; + return f; + }; + + std::function DF = [&one, d0, d1, d2, d0c0, d1c1, d2c2](Real s) + { + Real const two = (Real)2; + Real invN0 = one / (d0 * s - one); + Real invN1 = one / (d1 * s - one); + Real invN2 = one / (d2 * s - one); + Real term0 = d0 * d0c0 * invN0 * invN0 * invN0; + Real term1 = d1 * d1c1 * invN1 * invN1 * invN1; + Real term2 = d2 * d2c2 * invN2 * invN2 * invN2; + Real df = -two * (term0 + term1 + term2); + return df; + }; + + unsigned int const maxIterations = 1024; + unsigned int iterations; + numRoots = 0; + + // TODO: What role does epsilon play? + Real epsilon = (Real)0.001; + Real multiplier0 = std::sqrt(three / (one - epsilon)); + Real multiplier1 = std::sqrt(one / (one + epsilon)); + Real sqrtd0c0 = std::sqrt(d0c0); + Real sqrtd1c1 = std::sqrt(d1c1); + Real sqrtd2c2 = std::sqrt(d2c2); + Real invD0 = one / d0; + Real invD1 = one / d1; + Real invD2 = one / d2; + Real temp0, temp1, temp2, smin, smax, s; + + // Compute root in (-infinity,1/d0). + temp0 = (one - multiplier0 * sqrtd0c0) * invD0; + temp1 = (one - multiplier0 * sqrtd1c1) * invD1; + temp2 = (one - multiplier0 * sqrtd2c2) * invD2; + smin = std::min(std::min(temp0, temp1), temp2); + LogAssert(F(smin) < zero, "Unexpected condition."); + smax = (one - multiplier1 * sqrtd0c0) * invD0; + LogAssert(F(smax) > zero, "Unexpected condition."); + iterations = RootsBisection::Find(F, smin, smax, maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + + // Compute roots (if any) in (1/d0,1/d1). It is the case that + // F(1/d0) = +infinity, F'(1/d0) = -infinity + // F(1/d1) = +infinity, F'(1/d1) = +infinity + // F"(s) > 0 for all s in the domain of F + // Compute the unique root r of F'(s) on (1/d0,1/d1). The + // bisector needs only the signs at the endpoints, so we pass -1 + // and +1 instead of the infinite values. If F(r) < 0, F(s) has + // two roots in the interval. If F(r) = 0, F(s) has only one root + // in the interval. + Real smid; + iterations = RootsBisection::Find(DF, invD0, invD1, -one, one, + maxIterations, smid); + LogAssert(iterations > 0, "Unexpected condition."); + if (F(smid) < zero) + { + // Pass in signs rather than infinities, because the bisector cares + // only about the signs. + iterations = RootsBisection::Find(F, invD0, smid, one, -one, + maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + iterations = RootsBisection::Find(F, smid, invD1, -one, one, + maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + } + + // Compute roots (if any) in (1/d1,1/d2). It is the case that + // F(1/d1) = +infinity, F'(1/d1) = -infinity + // F(1/d2) = +infinity, F'(1/d2) = +infinity + // F"(s) > 0 for all s in the domain of F + // Compute the unique root r of F'(s) on (1/d1,1/d2). The + // bisector needs only the signs at the endpoints, so we pass -1 + // and +1 instead of the infinite values. If F(r) < 0, F(s) has + // two roots in the interval. If F(r) = 0, F(s) has only one root + // in the interval. + iterations = RootsBisection::Find(DF, invD1, invD2, -one, one, + maxIterations, smid); + LogAssert(iterations > 0, "Unexpected condition."); + if (F(smid) < zero) + { + // Pass in signs rather than infinities, because the bisector + // cares only about the signs. + iterations = RootsBisection::Find(F, invD1, smid, one, -one, + maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + iterations = RootsBisection::Find(F, smid, invD2, -one, one, + maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + } + + // Compute root in (1/d2,+infinity). + temp0 = (one + multiplier0 * sqrtd0c0) * invD0; + temp1 = (one + multiplier0 * sqrtd1c1) * invD1; + temp2 = (one + multiplier0 * sqrtd2c2) * invD2; + smax = std::max(std::max(temp0, temp1), temp2); + LogAssert(F(smax) < zero, "Unexpected condition."); + smin = (one + multiplier1 * sqrtd2c2) * invD2; + LogAssert(F(smin) > zero, "Unexpected condition."); + iterations = RootsBisection::Find(F, smin, smax, maxIterations, s); + LogAssert(iterations > 0, "Unexpected condition."); + roots[numRoots++] = s; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace2Polygon2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace2Polygon2.h new file mode 100644 index 0000000..dc64052 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace2Polygon2.h @@ -0,0 +1,159 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the box to be a solid and the polygon to be a +// convex solid. + +namespace WwiseGTE +{ + template + class FIQuery, std::vector>> + { + public: + struct Result + { + bool intersect; + + // If 'intersect' is true, the halfspace and polygon intersect + // in a convex polygon. + std::vector> polygon; + }; + + Result operator()(Halfspace<2, Real> const& halfspace, + std::vector> const& polygon) + { + Result result; + + // Determine whether the polygon vertices are outside the + // halfspace, inside the halfspace, or on the halfspace boundary. + int const numVertices = static_cast(polygon.size()); + std::vector distance(numVertices); + int positive = 0, negative = 0, positiveIndex = -1; + for (int i = 0; i < numVertices; ++i) + { + distance[i] = Dot(halfspace.normal, polygon[i]) - halfspace.constant; + if (distance[i] > (Real)0) + { + ++positive; + if (positiveIndex == -1) + { + positiveIndex = i; + } + } + else if (distance[i] < (Real)0) + { + ++negative; + } + } + + if (positive == 0) + { + // The polygon is strictly outside the halfspace. + result.intersect = false; + return result; + } + + if (negative == 0) + { + // The polygon is contained in the closed halfspace, so it is + // fully visible and no clipping is necessary. + result.intersect = true; + return result; + } + + // The line transversely intersects the polygon. Clip the polygon. + Vector2 vertex; + int curr, prev; + Real t; + + if (positiveIndex > 0) + { + // Compute the first clip vertex on the line. + curr = positiveIndex; + prev = curr - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + result.polygon.push_back(vertex); + + // Include the vertices on the positive side of line. + while (curr < numVertices && distance[curr] >(Real)0) + { + result.polygon.push_back(polygon[curr++]); + } + + // Compute the kast clip vertex on the line. + if (curr < numVertices) + { + prev = curr - 1; + } + else + { + curr = 0; + prev = numVertices - 1; + } + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + result.polygon.push_back(vertex); + } + else // positiveIndex is 0 + { + // Include the vertices on the positive side of line. + curr = 0; + while (curr < numVertices && distance[curr] >(Real)0) + { + result.polygon.push_back(polygon[curr++]); + } + + // Compute the last clip vertex on the line. + prev = curr - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + result.polygon.push_back(vertex); + + // Skip the vertices on the negative side of the line. + while (curr < numVertices && distance[curr] <= (Real)0) + { + curr++; + } + + // Compute the first clip vertex on the line. + if (curr < numVertices) + { + prev = curr - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + result.polygon.push_back(vertex); + + // Keep the vertices on the positive side of the line. + while (curr < numVertices && distance[curr] >(Real)0) + { + result.polygon.push_back(polygon[curr++]); + } + } + else + { + curr = 0; + prev = numVertices - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + result.polygon.push_back(vertex); + } + } + + result.intersect = true; + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace3Capsule3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace3Capsule3.h new file mode 100644 index 0000000..7d48d35 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace3Capsule3.h @@ -0,0 +1,43 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// Queries for intersection of objects with halfspaces. These are useful for +// containment testing, object culling, and clipping. + +namespace WwiseGTE +{ + template + class TIQuery, Capsule3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Halfspace3 const& halfspace, Capsule3 const& capsule) + { + Result result; + + // Project the capsule onto the normal line. The plane of the + // halfspace occurs at the origin (zero) of the normal line. + Real e0 = Dot(halfspace.normal, capsule.segment.p[0]) - halfspace.constant; + Real e1 = Dot(halfspace.normal, capsule.segment.p[1]) - halfspace.constant; + + // The capsule and halfspace intersect when the projection + // interval maximum is nonnegative. + result.intersect = (std::max(e0, e1) + capsule.radius >= (Real)0); + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace3Cylinder3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace3Cylinder3.h new file mode 100644 index 0000000..9c1667e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace3Cylinder3.h @@ -0,0 +1,47 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// Queries for intersection of objects with halfspaces. These are useful for +// containment testing, object culling, and clipping. + +namespace WwiseGTE +{ + template + class TIQuery, Cylinder3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Halfspace3 const& halfspace, Cylinder3 const& cylinder) + { + Result result; + + // Compute extremes of signed distance Dot(N,X)-d for points on + // the cylinder. These are + // min = (Dot(N,C)-d) - r*sqrt(1-Dot(N,W)^2) - (h/2)*|Dot(N,W)| + // max = (Dot(N,C)-d) + r*sqrt(1-Dot(N,W)^2) + (h/2)*|Dot(N,W)| + Real center = Dot(halfspace.normal, cylinder.axis.origin) - halfspace.constant; + Real absNdW = std::fabs(Dot(halfspace.normal, cylinder.axis.direction)); + Real root = std::sqrt(std::max((Real)1, (Real)1 - absNdW * absNdW)); + Real tmax = center + cylinder.radius * root + (Real)0.5 * cylinder.height * absNdW; + + // The cylinder and halfspace intersect when the projection + // interval maximum is nonnegative. + result.intersect = (tmax >= (Real)0); + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace3Ellipsoid3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace3Ellipsoid3.h new file mode 100644 index 0000000..bb91e90 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace3Ellipsoid3.h @@ -0,0 +1,47 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +// Queries for intersection of objects with halfspaces. These are useful for +// containment testing, object culling, and clipping. + +namespace WwiseGTE +{ + template + class TIQuery, Ellipsoid3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Halfspace3 const& halfspace, Ellipsoid3 const& ellipsoid) + { + // Project the ellipsoid onto the normal line. The plane of the + // halfspace occurs at the origin (zero) of the normal line. + Result result; + Matrix3x3 MInverse; + ellipsoid.GetMInverse(MInverse); + Real discr = Dot(halfspace.normal, MInverse * halfspace.normal); + Real extent = std::sqrt(std::max(discr, (Real)0)); + Real center = Dot(halfspace.normal, ellipsoid.center) - halfspace.constant; + Real tmax = center + extent; + + // The ellipsoid and halfspace intersect when the projection + // interval maximum is nonnegative. + result.intersect = (tmax >= (Real)0); + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace3OrientedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace3OrientedBox3.h new file mode 100644 index 0000000..d1433c5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace3OrientedBox3.h @@ -0,0 +1,48 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// Queries for intersection of objects with halfspaces. These are useful for +// containment testing, object culling, and clipping. + +namespace WwiseGTE +{ + template + class TIQuery, OrientedBox3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Halfspace3 const& halfspace, OrientedBox3 const& box) + { + Result result; + + // Project the box center onto the normal line. The plane of the + // halfspace occurs at the origin (zero) of the normal line. + Real center = Dot(halfspace.normal, box.center) - halfspace.constant; + + // Compute the radius of the interval of projection. + Real radius = + std::fabs(box.extent[0] * Dot(halfspace.normal, box.axis[0])) + + std::fabs(box.extent[1] * Dot(halfspace.normal, box.axis[1])) + + std::fabs(box.extent[2] * Dot(halfspace.normal, box.axis[2])); + + // The box and halfspace intersect when the projection interval + // maximum is nonnegative. + result.intersect = (center + radius >= (Real)0); + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace3Segment3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace3Segment3.h new file mode 100644 index 0000000..784a88d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace3Segment3.h @@ -0,0 +1,142 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// Queries for intersection of objects with halfspaces. These are useful for +// containment testing, object culling, and clipping. + +namespace WwiseGTE +{ + template + class TIQuery, Segment3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Halfspace3 const& halfspace, Segment3 const& segment) + { + Result result; + + // Project the segment endpoints onto the normal line. The plane + // of the halfspace occurs at the origin (zero) of the normal + // line. + Real s[2]; + for (int i = 0; i < 2; ++i) + { + s[i] = Dot(halfspace.normal, segment.p[i]) - halfspace.constant; + } + + // The segment and halfspace intersect when the projection + // interval maximum is nonnegative. + result.intersect = (std::max(s[0], s[1]) >= (Real)0); + return result; + } + }; + + template + class FIQuery, Segment3> + { + public: + struct Result + { + bool intersect; + + // The segment is clipped against the plane defining the + // halfspace. The 'numPoints' is either 0 (no intersection), + // 1 (point), or 2 (segment). + int numPoints; + Vector3 point[2]; + }; + + Result operator()(Halfspace3 const& halfspace, Segment3 const& segment) + { + // Determine on which side of the plane the endpoints lie. The + // table of possibilities is listed next with n = numNegative, + // p = numPositive, and z = numZero. + // + // n p z intersection + // ------------------------- + // 0 2 0 segment (original) + // 0 1 1 segment (original) + // 0 0 2 segment (original) + // 1 1 0 segment (clipped) + // 1 0 1 point (endpoint) + // 2 0 0 none + + Real s[2]; + int numPositive = 0, numNegative = 0, numZero = 0; + for (int i = 0; i < 2; ++i) + { + s[i] = Dot(halfspace.normal, segment.p[i]) - halfspace.constant; + if (s[i] > (Real)0) + { + ++numPositive; + } + else if (s[i] < (Real)0) + { + ++numNegative; + } + else + { + ++numZero; + } + } + + Result result; + + if (numNegative == 0) + { + // The segment is in the halfspace. + result.intersect = true; + result.numPoints = 2; + result.point[0] = segment.p[0]; + result.point[1] = segment.p[1]; + } + else if (numNegative == 1) + { + result.intersect = true; + result.numPoints = 1; + if (numPositive == 1) + { + // The segment is intersected at an interior point. + result.point[0] = segment.p[0] + + (s[0] / (s[0] - s[1])) * (segment.p[1] - segment.p[0]); + } + else // numZero = 1 + { + // One segment endpoint is on the plane. + if (s[0] == (Real)0) + { + result.point[0] = segment.p[0]; + } + else + { + result.point[0] = segment.p[1]; + } + } + } + else // numNegative == 2 + { + // The segment is outside the halfspace. (numNegative == 2) + result.intersect = false; + result.numPoints = 0; + } + + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace3Sphere3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace3Sphere3.h new file mode 100644 index 0000000..07f3992 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace3Sphere3.h @@ -0,0 +1,42 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// Queries for intersection of objects with halfspaces. These are useful for +// containment testing, object culling, and clipping. + +namespace WwiseGTE +{ + template + class TIQuery, Sphere3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Halfspace3 const& halfspace, Sphere3 const& sphere) + { + Result result; + + // Project the sphere center onto the normal line. The plane of + // the halfspace occurs at the origin (zero) of the normal line. + Real center = Dot(halfspace.normal, sphere.center) - halfspace.constant; + + // The sphere and halfspace intersect when the projection interval + // maximum is nonnegative. + result.intersect = (center + sphere.radius >= (Real)0); + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace3Triangle3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace3Triangle3.h new file mode 100644 index 0000000..9498b4f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrHalfspace3Triangle3.h @@ -0,0 +1,228 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// Queries for intersection of objects with halfspaces. These are useful for +// containment testing, object culling, and clipping. + +namespace WwiseGTE +{ + template + class TIQuery, Triangle3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Halfspace3 const& halfspace, Triangle3 const& triangle) + { + Result result; + + // Project the triangle vertices onto the normal line. The plane + // of the halfspace occurs at the origin (zero) of the normal + // line. + Real s[3]; + for (int i = 0; i < 3; ++i) + { + s[i] = Dot(halfspace.normal, triangle.v[i]) - halfspace.constant; + } + + // The triangle and halfspace intersect when the projection + // interval maximum is nonnegative. + result.intersect = (std::max(std::max(s[0], s[1]), s[2]) >= (Real)0); + return result; + } + }; + + template + class FIQuery, Triangle3> + { + public: + struct Result + { + bool intersect; + + // The triangle is clipped against the plane defining the + // halfspace. The 'numPoints' is either 0 (no intersection), + // 1 (point), 2 (segment), 3 (triangle), or 4 (quadrilateral). + int numPoints; + Vector3 point[4]; + }; + + Result operator()(Halfspace3 const& halfspace, Triangle3 const& triangle) + { + Result result; + + // Determine on which side of the plane the vertices lie. The + // table of possibilities is listed next with n = numNegative, + // p = numPositive, and z = numZero. + // + // n p z intersection + // --------------------------------- + // 0 3 0 triangle (original) + // 0 2 1 triangle (original) + // 0 1 2 triangle (original) + // 0 0 3 triangle (original) + // 1 2 0 quad (2 edges clipped) + // 1 1 1 triangle (1 edge clipped) + // 1 0 2 edge + // 2 1 0 triangle (2 edges clipped) + // 2 0 1 vertex + // 3 0 0 none + + Real s[3]; + int numPositive = 0, numNegative = 0, numZero = 0; + for (int i = 0; i < 3; ++i) + { + s[i] = Dot(halfspace.normal, triangle.v[i]) - halfspace.constant; + if (s[i] > (Real)0) + { + ++numPositive; + } + else if (s[i] < (Real)0) + { + ++numNegative; + } + else + { + ++numZero; + } + } + + if (numNegative == 0) + { + // The triangle is in the halfspace. + result.intersect = true; + result.numPoints = 3; + result.point[0] = triangle.v[0]; + result.point[1] = triangle.v[1]; + result.point[2] = triangle.v[2]; + } + else if (numNegative == 1) + { + result.intersect = true; + if (numPositive == 2) + { + // The portion of the triangle in the halfspace is a + // quadrilateral. + result.numPoints = 4; + for (int i0 = 0; i0 < 3; ++i0) + { + if (s[i0] < (Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + result.point[0] = triangle.v[i1]; + result.point[1] = triangle.v[i2]; + Real t2 = s[i2] / (s[i2] - s[i0]); + Real t0 = s[i0] / (s[i0] - s[i1]); + result.point[2] = triangle.v[i2] + t2 * + (triangle.v[i0] - triangle.v[i2]); + result.point[3] = triangle.v[i0] + t0 * + (triangle.v[i1] - triangle.v[i0]); + break; + } + } + } + else if (numPositive == 1) + { + // The portion of the triangle in the halfspace is a + // triangle with one vertex on the plane. + result.numPoints = 3; + for (int i0 = 0; i0 < 3; ++i0) + { + if (s[i0] == (Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + result.point[0] = triangle.v[i0]; + Real t1 = s[i1] / (s[i1] - s[i2]); + Vector3 p = triangle.v[i1] + t1 * + (triangle.v[i2] - triangle.v[i1]); + if (s[i1] > (Real)0) + { + result.point[1] = triangle.v[i1]; + result.point[2] = p; + } + else + { + result.point[1] = p; + result.point[2] = triangle.v[i2]; + } + break; + } + } + } + else + { + // Only an edge of the triangle is in the halfspace. + result.numPoints = 0; + for (int i = 0; i < 3; ++i) + { + if (s[i] == (Real)0) + { + result.point[result.numPoints++] = triangle.v[i]; + } + } + } + } + else if (numNegative == 2) + { + result.intersect = true; + if (numPositive == 1) + { + // The portion of the triangle in the halfspace is a + // triangle. + result.numPoints = 3; + for (int i0 = 0; i0 < 3; ++i0) + { + if (s[i0] > (Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + result.point[0] = triangle.v[i0]; + Real t0 = s[i0] / (s[i0] - s[i1]); + Real t2 = s[i2] / (s[i2] - s[i0]); + result.point[1] = triangle.v[i0] + t0 * + (triangle.v[i1] - triangle.v[i0]); + result.point[2] = triangle.v[i2] + t2 * + (triangle.v[i0] - triangle.v[i2]); + break; + } + } + } + else + { + // Only a vertex of the triangle is in the halfspace. + result.numPoints = 1; + for (int i = 0; i < 3; ++i) + { + if (s[i] == (Real)0) + { + result.point[0] = triangle.v[i]; + break; + } + } + } + } + else // numNegative == 3 + { + // The triangle is outside the halfspace. (numNegative == 3) + result.intersect = false; + result.numPoints = 0; + } + + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrIntervals.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrIntervals.h new file mode 100644 index 0000000..0d3477e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrIntervals.h @@ -0,0 +1,562 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The intervals are of the form [t0,t1], [t0,+infinity) or (-infinity,t1]. +// Degenerate intervals are allowed (t0 = t1). The queries do not perform +// validation on the input intervals to test whether t0 <= t1. + +namespace WwiseGTE +{ + template + class TIQuery, std::array> + { + public: + // The query tests overlap, whether a single point or an entire + // interval. + struct Result + { + bool intersect; + + // Dynamic queries (intervals moving with constant speeds). If + // 'intersect' is true, the contact times are valid and + // 0 <= firstTime <= lastTime, firstTime <= maxTime + // If 'intersect' is false, there are two cases reported. If the + // intervals will intersect at firstTime > maxTime, the contact + // times are reported just as when 'intersect' is true. However, + // if the intervals will not intersect, then firstTime and + // lastTime are both set to zero (invalid because 'intersect' is + // false). + Real firstTime, lastTime; + }; + + // Static query. The firstTime and lastTime values are invalid + // regardless of the value of 'intersect'. + Result operator()(std::array const& interval0, std::array const& interval1) + { + Result result; + result.intersect = (interval0[0] <= interval1[1] && interval0[1] >= interval1[0]); + result.firstTime = (Real)0; + result.lastTime = (Real)0; + return result; + } + + // Static queries where at least one interval is semiinfinite. The + // two types of semiinfinite intervals are [a,+infinity), which I call + // a positive-infinite interval, and (-infinity,a], which I call a + // negative-infinite interval. + Result operator()(std::array const& finite, Real const& a, bool isPositiveInfinite) + { + Result result; + result.firstTime = (Real)0; + result.lastTime = (Real)0; + + if (isPositiveInfinite) + { + result.intersect = (finite[1] >= a); + } + else // is negative-infinite + { + result.intersect = (finite[0] <= a); + } + + return result; + } + + Result operator()(Real const& a0, bool isPositiveInfinite0, + Real const& a1, bool isPositiveInfinite1) + { + Result result; + result.firstTime = (Real)0; + result.lastTime = (Real)0; + + if (isPositiveInfinite0) + { + if (isPositiveInfinite1) + { + result.intersect = true; + } + else // interval1 is negative-infinite + { + result.intersect = (a0 <= a1); + } + } + else // interval0 is negative-infinite + { + if (isPositiveInfinite1) + { + result.intersect = (a0 >= a1); + } + else // interval1 is negative-infinite + { + result.intersect = true; + } + } + + return result; + } + + // Dynamic query. Current time is 0, maxTime > 0 is required. + Result operator()(Real maxTime, std::array const& interval0, + Real speed0, std::array const& interval1, Real speed1) + { + Result result; + + if (interval0[1] < interval1[0]) + { + // interval0 initially to the left of interval1. + Real diffSpeed = speed0 - speed1; + if (diffSpeed > (Real)0) + { + // The intervals must move towards each other. 'intersect' + // is true when the intervals will intersect by maxTime. + Real diffPos = interval1[0] - interval0[1]; + Real invDiffSpeed = (Real)1 / diffSpeed; + result.intersect = (diffPos <= maxTime * diffSpeed); + result.firstTime = diffPos * invDiffSpeed; + result.lastTime = (interval1[1] - interval0[0]) * invDiffSpeed; + return result; + } + } + else if (interval0[0] > interval1[1]) + { + // interval0 initially to the right of interval1. + Real diffSpeed = speed1 - speed0; + if (diffSpeed > (Real)0) + { + // The intervals must move towards each other. 'intersect' + // is true when the intervals will intersect by maxTime. + Real diffPos = interval0[0] - interval1[1]; + Real invDiffSpeed = (Real)1 / diffSpeed; + result.intersect = (diffPos <= maxTime * diffSpeed); + result.firstTime = diffPos * invDiffSpeed; + result.lastTime = (interval0[1] - interval1[0]) * invDiffSpeed; + return result; + } + } + else + { + // The intervals are initially intersecting. + result.intersect = true; + result.firstTime = (Real)0; + if (speed1 > speed0) + { + result.lastTime = (interval0[1] - interval1[0]) / (speed1 - speed0); + } + else if (speed1 < speed0) + { + result.lastTime = (interval1[1] - interval0[0]) / (speed0 - speed1); + } + else + { + result.lastTime = std::numeric_limits::max(); + } + return result; + } + + result.intersect = false; + result.firstTime = (Real)0; + result.lastTime = (Real)0; + return result; + } + }; + + template + class FIQuery, std::array> + { + public: + // The query finds overlap, whether a single point or an entire + // interval. + struct Result + { + Result() + : + intersect(false), + numIntersections(0), + overlap{ (Real)0, (Real)0 }, + type(isEmpty), + firstTime((Real)0), + lastTime((Real)0) + { + } + + bool intersect; + + // Static queries (no motion of intervals over time). The number + // of number of intersections is 0 (no overlap), 1 (intervals are + // just touching), or 2 (intervals overlap in an interval). If + // 'intersect' is false, numIntersections is 0 and 'overlap' is + // set to [0,0]. If 'intersect' is true, numIntersections is + // 1 or 2. When 1, 'overlap' is set to [x,x], which is degenerate + // and represents the single intersection point x. When 2, + // 'overlap' is the interval of intersection. + int numIntersections; + std::array overlap; + + // No intersection. + static int const isEmpty = 0; + + // Intervals touch at an endpoint, [t0,t0]. + static int const isPoint = 1; + + // Finite-length interval of intersection, [t0,t1]. + static int const isFinite = 2; + + // Smiinfinite interval of intersection, [t0,+infinity). The + // result.overlap[0] is t0 and result.overlap[1] is +1 as a + // message that the right endpoint is +infinity (you still need + // the result.type to know this interpretation). + static int const isPositiveInfinite = 3; + + // Semiinfinite interval of intersection, (-infinity,t1]. The + // result.overlap[0] is =1 as a message that the left endpoint is + // -infinity (you still need the result.type to know this + // interpretation). The result.overlap[1] is t1. + static int const isNegativeInfinite = 4; + + // The dynamic queries all set the type to isDynamicQuery because + // the queries look for time of first and last contact. + static int const isDynamicQuery = 5; + + // The type is one of isEmpty, isPoint, isFinite, + // isPositiveInfinite, isNegativeInfinite or isDynamicQuery. + int type; + + // Dynamic queries (intervals moving with constant speeds). If + // 'intersect' is true, the contact times are valid and + // 0 <= firstTime <= lastTime, firstTime <= maxTime + // If 'intersect' is false, there are two cases reported. If the + // intervals will intersect at firstTime > maxTime, the contact + // times are reported just as when 'intersect' is true. However, + // if the intervals will not intersect, then firstTime and + // lastTime are both set to zero (invalid because 'intersect' is + // false). + Real firstTime, lastTime; + }; + + // Static query. + Result operator()(std::array const& interval0, std::array const& interval1) + { + Result result; + + if (interval0[1] < interval1[0] || interval0[0] > interval1[1]) + { + result.numIntersections = 0; + result.overlap[0] = (Real)0; + result.overlap[1] = (Real)0; + result.type = Result::isEmpty; + } + else if (interval0[1] > interval1[0]) + { + if (interval0[0] < interval1[1]) + { + result.overlap[0] = (interval0[0] < interval1[0] ? interval1[0] : interval0[0]); + result.overlap[1] = (interval0[1] > interval1[1] ? interval1[1] : interval0[1]); + if (result.overlap[0] < result.overlap[1]) + { + result.numIntersections = 2; + result.type = Result::isFinite; + } + else + { + result.numIntersections = 1; + result.type = Result::isPoint; + } + } + else // interval0[0] == interval1[1] + { + result.numIntersections = 1; + result.overlap[0] = interval0[0]; + result.overlap[1] = result.overlap[0]; + result.type = Result::isPoint; + } + } + else // interval0[1] == interval1[0] + { + result.numIntersections = 1; + result.overlap[0] = interval0[1]; + result.overlap[1] = result.overlap[0]; + result.type = Result::isPoint; + } + + result.intersect = (result.numIntersections > 0); + return result; + } + + // Static queries where at least one interval is semiinfinite. The + // two types of semiinfinite intervals are [a,+infinity), which I call + // a positive-infinite interval, and (-infinity,a], which I call a + // negative-infinite interval. + Result operator()(std::array const& finite, Real const& a, bool isPositiveInfinite) + { + Result result; + + if (isPositiveInfinite) + { + if (finite[1] > a) + { + result.overlap[0] = std::max(finite[0], a); + result.overlap[1] = finite[1]; + if (result.overlap[0] < result.overlap[1]) + { + result.numIntersections = 2; + result.type = Result::isFinite; + } + else + { + result.numIntersections = 1; + result.type = Result::isPoint; + } + } + else if (finite[1] == a) + { + result.numIntersections = 1; + result.overlap[0] = a; + result.overlap[1] = result.overlap[0]; + result.type = Result::isPoint; + } + else + { + result.numIntersections = 0; + result.overlap[0] = (Real)0; + result.overlap[1] = (Real)0; + result.type = Result::isEmpty; + } + } + else // is negative-infinite + { + if (finite[0] < a) + { + result.overlap[0] = finite[0]; + result.overlap[1] = std::min(finite[1], a); + if (result.overlap[0] < result.overlap[1]) + { + result.numIntersections = 2; + result.type = Result::isFinite; + } + else + { + result.numIntersections = 1; + result.type = Result::isPoint; + } + } + else if (finite[0] == a) + { + result.numIntersections = 1; + result.overlap[0] = a; + result.overlap[1] = result.overlap[0]; + result.type = Result::isPoint; + } + else + { + result.numIntersections = 0; + result.overlap[0] = (Real)0; + result.overlap[1] = (Real)0; + result.type = Result::isEmpty; + } + } + + result.intersect = (result.numIntersections > 0); + return result; + } + + Result operator()(Real const& a0, bool isPositiveInfinite0, + Real const& a1, bool isPositiveInfinite1) + { + Result result; + + if (isPositiveInfinite0) + { + if (isPositiveInfinite1) + { + // overlap[1] is +infinity, but set it to +1 because Real + // might not have a representation for +infinity. The + // type indicates the interval is positive-infinite, so + // the +1 is a reminder that overlap[1] is +infinity. + result.numIntersections = 1; + result.overlap[0] = std::max(a0, a1); + result.overlap[1] = (Real)+1; + result.type = Result::isPositiveInfinite; + } + else // interval1 is negative-infinite + { + if (a0 > a1) + { + result.numIntersections = 0; + result.overlap[0] = (Real)0; + result.overlap[1] = (Real)0; + result.type = Result::isEmpty; + } + else if (a0 < a1) + { + result.numIntersections = 2; + result.overlap[0] = a0; + result.overlap[1] = a1; + result.type = Result::isFinite; + } + else // a0 == a1 + { + result.numIntersections = 1; + result.overlap[0] = a0; + result.overlap[1] = result.overlap[0]; + result.type = Result::isPoint; + } + } + } + else // interval0 is negative-infinite + { + if (isPositiveInfinite1) + { + if (a0 < a1) + { + result.numIntersections = 0; + result.overlap[0] = (Real)0; + result.overlap[1] = (Real)0; + result.type = Result::isEmpty; + } + else if (a0 > a1) + { + result.numIntersections = 2; + result.overlap[0] = a1; + result.overlap[1] = a0; + result.type = Result::isFinite; + } + else + { + result.numIntersections = 1; + result.overlap[0] = a1; + result.overlap[1] = result.overlap[0]; + result.type = Result::isPoint; + } + result.intersect = (a0 >= a1); + } + else // interval1 is negative-infinite + { + // overlap[0] is -infinity, but set it to -1 because Real + // might not have a representation for -infinity. The + // type indicates the interval is negative-infinite, so + // the -1 is a reminder that overlap[0] is -infinity. + result.numIntersections = 1; + result.overlap[0] = (Real)-1; + result.overlap[1] = std::min(a0, a1); + result.type = Result::isNegativeInfinite; + } + } + + result.intersect = (result.numIntersections > 0); + return result; + } + + // Dynamic query. Current time is 0, maxTime > 0 is required. + Result operator()(Real maxTime, std::array const& interval0, + Real speed0, std::array const& interval1, Real speed1) + { + Result result; + result.type = Result::isDynamicQuery; + + if (interval0[1] < interval1[0]) + { + // interval0 initially to the left of interval1. + Real diffSpeed = speed0 - speed1; + if (diffSpeed > (Real)0) + { + // The intervals must move towards each other. 'intersect' + // is true when the intervals will intersect by maxTime. + Real diffPos = interval1[0] - interval0[1]; + Real invDiffSpeed = (Real)1 / diffSpeed; + result.intersect = (diffPos <= maxTime * diffSpeed); + result.numIntersections = 1; + result.firstTime = diffPos * invDiffSpeed; + result.lastTime = (interval1[1] - interval0[0]) * invDiffSpeed; + result.overlap[0] = interval0[0] + result.firstTime * speed0; + result.overlap[1] = result.overlap[0]; + return result; + } + } + else if (interval0[0] > interval1[1]) + { + // interval0 initially to the right of interval1. + Real diffSpeed = speed1 - speed0; + if (diffSpeed > (Real)0) + { + // The intervals must move towards each other. 'intersect' + // is true when the intervals will intersect by maxTime. + Real diffPos = interval0[0] - interval1[1]; + Real invDiffSpeed = (Real)1 / diffSpeed; + result.intersect = (diffPos <= maxTime * diffSpeed); + result.numIntersections = 1; + result.firstTime = diffPos * invDiffSpeed; + result.lastTime = (interval0[1] - interval1[0]) * invDiffSpeed; + result.overlap[0] = interval1[1] + result.firstTime * speed1; + result.overlap[1] = result.overlap[0]; + return result; + } + } + else + { + // The intervals are initially intersecting. + result.intersect = true; + result.firstTime = (Real)0; + if (speed1 > speed0) + { + result.lastTime = (interval0[1] - interval1[0]) / (speed1 - speed0); + } + else if (speed1 < speed0) + { + result.lastTime = (interval1[1] - interval0[0]) / (speed0 - speed1); + } + else + { + result.lastTime = std::numeric_limits::max(); + } + + if (interval0[1] > interval1[0]) + { + if (interval0[0] < interval1[1]) + { + result.numIntersections = 2; + result.overlap[0] = (interval0[0] < interval1[0] ? interval1[0] : interval0[0]); + result.overlap[1] = (interval0[1] > interval1[1] ? interval1[1] : interval0[1]); + } + else // interval0[0] == interval1[1] + { + result.numIntersections = 1; + result.overlap[0] = interval0[0]; + result.overlap[1] = result.overlap[0]; + } + } + else // interval0[1] == interval1[0] + { + result.numIntersections = 1; + result.overlap[0] = interval0[1]; + result.overlap[1] = result.overlap[0]; + } + return result; + } + + result.intersect = false; + result.numIntersections = 0; + result.overlap[0] = (Real)0; + result.overlap[1] = (Real)0; + result.firstTime = (Real)0; + result.lastTime = (Real)0; + return result; + } + }; + + // Template aliases for convenience. + template + using TIIntervalInterval = TIQuery, std::array>; + + template + using FIIntervalInterval = FIQuery, std::array>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2AlignedBox2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2AlignedBox2.h new file mode 100644 index 0000000..9428d2e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2AlignedBox2.h @@ -0,0 +1,168 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection queries use the method of separating axes. +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The find-intersection queries use parametric clipping against the four +// edges of the box. + +namespace WwiseGTE +{ + template + class TIQuery, AlignedBox2> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Line2 const& line, AlignedBox2 const& box) + { + // Get the centered form of the aligned box. The axes are + // implicitly Axis[d] = Vector2::Unit(d). + Vector2 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the line to the aligned-box coordinate system. + Vector2 lineOrigin = line.origin - boxCenter; + + Result result; + DoQuery(lineOrigin, line.direction, boxExtent, result); + return result; + } + + protected: + void DoQuery(Vector2 const& lineOrigin, + Vector2 const& lineDirection, Vector2 const& boxExtent, + Result& result) + { + Real LHS = std::fabs(DotPerp(lineDirection, lineOrigin)); + Real RHS = + boxExtent[0] * std::fabs(lineDirection[1]) + + boxExtent[1] * std::fabs(lineDirection[0]); + result.intersect = (LHS <= RHS); + } + }; + + template + class FIQuery, AlignedBox2> + { + public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line2 const& line, AlignedBox2 const& box) + { + // Get the centered form of the aligned box. The axes are + // implicitly Axis[d] = Vector2::Unit(d). + Vector2 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the line to the aligned-box coordinate system. + Vector2 lineOrigin = line.origin - boxCenter; + + Result result; + DoQuery(lineOrigin, line.direction, boxExtent, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; + } + + protected: + void DoQuery(Vector2 const& lineOrigin, + Vector2 const& lineDirection, Vector2 const& boxExtent, + Result& result) + { + // The line t-values are in the interval (-infinity,+infinity). + // Clip the line against all four planes of an aligned box in + // centered form. The result.numPoints is + // 0, no intersection + // 1, intersect in a single point (t0 is line parameter of point) + // 2, intersect in a segment (line parameter interval is [t0,t1]) + Real t0 = -std::numeric_limits::max(); + Real t1 = std::numeric_limits::max(); + if (Clip(+lineDirection[0], -lineOrigin[0] - boxExtent[0], t0, t1) && + Clip(-lineDirection[0], +lineOrigin[0] - boxExtent[0], t0, t1) && + Clip(+lineDirection[1], -lineOrigin[1] - boxExtent[1], t0, t1) && + Clip(-lineDirection[1], +lineOrigin[1] - boxExtent[1], t0, t1)) + { + result.intersect = true; + if (t1 > t0) + { + result.numIntersections = 2; + result.parameter[0] = t0; + result.parameter[1] = t1; + } + else + { + result.numIntersections = 1; + result.parameter[0] = t0; + result.parameter[1] = t0; // Used by derived classes. + } + return; + } + + result.intersect = false; + result.numIntersections = 0; + } + + private: + // Test whether the current clipped segment intersects the current + // test plane. If the return value is 'true', the segment does + // intersect the plane and is clipped; otherwise, the segment is + // culled (no intersection with box). + static bool Clip(Real denom, Real numer, Real& t0, Real& t1) + { + if (denom > (Real)0) + { + if (numer > denom * t1) + { + return false; + } + if (numer > denom * t0) + { + t0 = numer / denom; + } + return true; + } + else if (denom < (Real)0) + { + if (numer > denom * t0) + { + return false; + } + if (numer > denom * t1) + { + t1 = numer / denom; + } + return true; + } + else + { + return numer <= (Real)0; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2Arc2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2Arc2.h new file mode 100644 index 0000000..9c794ef --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2Arc2.h @@ -0,0 +1,80 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The queries consider the arc to be a 1-dimensional object. + +namespace WwiseGTE +{ + template + class TIQuery, Arc2> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Line2 const& line, Arc2 const& arc) + { + Result result; + FIQuery, Arc2> laQuery; + auto laResult = laQuery(line, arc); + result.intersect = laResult.intersect; + return result; + } + }; + + template + class FIQuery, Arc2> + { + public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line2 const& line, Arc2 const& arc) + { + Result result; + + FIQuery, Circle2> lcQuery; + Circle2 circle(arc.center, arc.radius); + auto lcResult = lcQuery(line, circle); + if (lcResult.intersect) + { + // Test whether line-circle intersections are on the arc. + result.numIntersections = 0; + for (int i = 0; i < lcResult.numIntersections; ++i) + { + if (arc.Contains(lcResult.point[i])) + { + result.intersect = true; + result.parameter[result.numIntersections] + = lcResult.parameter[i]; + result.point[result.numIntersections++] + = lcResult.point[i]; + } + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2Circle2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2Circle2.h new file mode 100644 index 0000000..35235cd --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2Circle2.h @@ -0,0 +1,99 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the circle to be a solid (disk). + +namespace WwiseGTE +{ + template + class TIQuery, Circle2> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Line2 const& line, Circle2 const& circle) + { + Result result; + DCPQuery, Line2> plQuery; + auto plResult = plQuery(circle.center, line); + result.intersect = (plResult.distance <= circle.radius); + return result; + } + }; + + template + class FIQuery, Circle2> + { + public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line2 const& line, Circle2 const& circle) + { + Result result; + DoQuery(line.origin, line.direction, circle, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; + } + + protected: + void DoQuery(Vector2 const& lineOrigin, + Vector2 const& lineDirection, Circle2 const& circle, + Result& result) + { + // Intersection of a the line P+t*D and the circle |X-C| = R. + // The line direction is unit length. The t-value is a + // real-valued root to the quadratic equation + // 0 = |t*D+P-C|^2 - R^2 + // = t^2 + 2*Dot(D,P-C)*t + |P-C|^2-R^2 + // = t^2 + 2*a1*t + a0 + // If there are two distinct roots, the order is t0 < t1. + Vector2 diff = lineOrigin - circle.center; + Real a0 = Dot(diff, diff) - circle.radius * circle.radius; + Real a1 = Dot(lineDirection, diff); + Real discr = a1 * a1 - a0; + if (discr > (Real)0) + { + Real root = std::sqrt(discr); + result.intersect = true; + result.numIntersections = 2; + result.parameter[0] = -a1 - root; + result.parameter[1] = -a1 + root; + } + else if (discr < (Real)0) + { + result.intersect = false; + result.numIntersections = 0; + } + else // discr == 0 + { + result.intersect = true; + result.numIntersections = 1; + result.parameter[0] = -a1; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2Line2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2Line2.h new file mode 100644 index 0000000..6f40da0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2Line2.h @@ -0,0 +1,156 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Line2> + { + public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (lines intersect in a + // single point) or std::numeric_limits::max() (lines are + // the same). + int numIntersections; + }; + + Result operator()(Line2 const& line0, Line2 const& line1) + { + Result result; + + // The intersection of two lines is a solution to P0 + s0*D0 = + // P1 + s1*D1. Rewrite this as s0*D0 - s1*D1 = P1 - P0 = Q. If + // DotPerp(D0, D1)) = 0, the lines are parallel. Additionally, if + // DotPerp(Q, D1)) = 0, the lines are the same. If + // Dotperp(D0, D1)) is not zero, then + // s0 = DotPerp(Q, D1))/DotPerp(D0, D1)) + // produces the point of intersection. Also, + // s1 = DotPerp(Q, D0))/DotPerp(D0, D1)) + + Vector2 diff = line1.origin - line0.origin; + Real D0DotPerpD1 = DotPerp(line0.direction, line1.direction); + if (D0DotPerpD1 != (Real)0) + { + // The lines are not parallel. + result.intersect = true; + result.numIntersections = 1; + } + else + { + // The lines are parallel. + Normalize(diff); + Real diffNDotPerpD1 = DotPerp(diff, line1.direction); + if (diffNDotPerpD1 != (Real)0) + { + // The lines are parallel but distinct. + result.intersect = false; + result.numIntersections = 0; + } + else + { + // The lines are the same. + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + } + } + + return result; + } + }; + + template + class FIQuery, Line2> + { + public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (lines intersect in a + // single point) or std::numeric_limits::max() (lines are + // the same). + int numIntersections; + + // If numIntersections is 1, the intersection is + // point = line0.origin + line0parameter[0] * line0.direction + // = line1.origin + line1parameter[0] * line1.direction + // If numIntersections is maxInt, point is not valid but the + // intervals are + // line0Parameter[] = { -maxReal, +maxReal } + // line1Parameter[] = { -maxReal, +maxReal } + Real line0Parameter[2], line1Parameter[2]; + Vector2 point; + }; + + Result operator()(Line2 const& line0, Line2 const& line1) + { + Result result; + + // The intersection of two lines is a solution to P0 + s0*D0 = + // P1 + s1*D1. Rewrite this as s0*D0 - s1*D1 = P1 - P0 = Q. If + // DotPerp(D0, D1)) = 0, the lines are parallel. Additionally, if + // DotPerp(Q, D1)) = 0, the lines are the same. If + // Dotperp(D0, D1)) is not zero, then + // s0 = DotPerp(Q, D1))/DotPerp(D0, D1)) + // produces the point of intersection. Also, + // s1 = DotPerp(Q, D0))/DotPerp(D0, D1)) + + Vector2 diff = line1.origin - line0.origin; + Real D0DotPerpD1 = DotPerp(line0.direction, line1.direction); + if (D0DotPerpD1 != (Real)0) + { + // The lines are not parallel. + result.intersect = true; + result.numIntersections = 1; + Real invD0DotPerpD1 = (Real)1 / D0DotPerpD1; + Real diffDotPerpD0 = DotPerp(diff, line0.direction); + Real diffDotPerpD1 = DotPerp(diff, line1.direction); + Real s0 = diffDotPerpD1 * invD0DotPerpD1; + Real s1 = diffDotPerpD0 * invD0DotPerpD1; + result.line0Parameter[0] = s0; + result.line1Parameter[0] = s1; + result.point = line0.origin + s0 * line0.direction; + } + else + { + // The lines are parallel. + Normalize(diff); + Real diffNDotPerpD1 = DotPerp(diff, line1.direction); + if (std::fabs(diffNDotPerpD1) != (Real)0) + { + // The lines are parallel but distinct. + result.intersect = false; + result.numIntersections = 0; + } + else + { + // The lines are the same. + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + Real maxReal = std::numeric_limits::max(); + result.line0Parameter[0] = -maxReal; + result.line0Parameter[1] = +maxReal; + result.line1Parameter[0] = -maxReal; + result.line1Parameter[1] = +maxReal; + } + } + + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2OrientedBox2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2OrientedBox2.h new file mode 100644 index 0000000..27627ff --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2OrientedBox2.h @@ -0,0 +1,93 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection queries use the method of separating axes. +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The find-intersection queries use parametric clipping against the four +// edges of the box. + +namespace WwiseGTE +{ + template + class TIQuery, OrientedBox2> + : + public TIQuery, AlignedBox2> + { + public: + struct Result + : + public TIQuery, AlignedBox2>::Result + { + // No additional relevant information to compute. + }; + + Result operator()(Line2 const& line, OrientedBox2 const& box) + { + // Transform the line to the oriented-box coordinate system. + Vector2 diff = line.origin - box.center; + Vector2 lineOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]) + }; + Vector2 lineDirection + { + Dot(line.direction, box.axis[0]), + Dot(line.direction, box.axis[1]) + }; + + Result result; + this->DoQuery(lineOrigin, lineDirection, box.extent, result); + return result; + } + }; + + template + class FIQuery, OrientedBox2> + : + public FIQuery, AlignedBox2> + { + public: + struct Result + : + public FIQuery, AlignedBox2>::Result + { + // No additional relevant information to compute. + }; + + Result operator()(Line2 const& line, OrientedBox2 const& box) + { + // Transform the line to the oriented-box coordinate system. + Vector2 diff = line.origin - box.center; + Vector2 lineOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]) + }; + Vector2 lineDirection + { + Dot(line.direction, box.axis[0]), + Dot(line.direction, box.axis[1]) + }; + + Result result; + this->DoQuery(lineOrigin, lineDirection, box.extent, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2Ray2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2Ray2.h new file mode 100644 index 0000000..cad9873 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2Ray2.h @@ -0,0 +1,121 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Ray2> + { + public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (line and ray intersect + // in a single point) or std::numeric_limits::max() (line + // and ray are collinear). + int numIntersections; + }; + + Result operator()(Line2 const& line, Ray2 const& ray) + { + Result result; + FIQuery, Line2> llQuery; + auto llResult = llQuery(line, Line2(ray.origin, ray.direction)); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the ray. + if (llResult.line1Parameter[0] >= (Real)0) + { + result.intersect = true; + result.numIntersections = 1; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else + { + result.intersect = llResult.intersect; + result.numIntersections = llResult.numIntersections; + } + return result; + } + }; + + template + class FIQuery, Ray2> + { + public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (line and ray intersect + // in a single point) or std::numeric_limits::max() (line + // and ray are collinear). + int numIntersections; + + // If numIntersections is 1, the intersection is + // point = line.origin + lineParameter[0] * line.direction + // = ray.origin + rayParameter[0] * ray.direction + // If numIntersections is maxInt, point is not valid but the + // intervals are + // lineParameter[] = { -maxReal, +maxReal } + // rayParameter[] = { 0, +maxReal } + Real lineParameter[2], rayParameter[2]; + Vector2 point; + }; + + Result operator()(Line2 const& line, Ray2 const& ray) + { + Result result; + FIQuery, Line2> llQuery; + auto llResult = llQuery(line, Line2(ray.origin, ray.direction)); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the ray. + if (llResult.line1Parameter[0] >= (Real)0) + { + result.intersect = true; + result.numIntersections = 1; + result.lineParameter[0] = llResult.line0Parameter[0]; + result.rayParameter[0] = llResult.line1Parameter[0]; + result.point = llResult.point; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + Real maxReal = std::numeric_limits::max(); + result.lineParameter[0] = -maxReal; + result.lineParameter[1] = +maxReal; + result.rayParameter[0] = (Real)0; + result.rayParameter[1] = +maxReal; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2Segment2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2Segment2.h new file mode 100644 index 0000000..187c8b3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2Segment2.h @@ -0,0 +1,130 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Segment2> + { + public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (line and segment + // intersect in a single point) or std::numeric_limits::max() + // (line and segment are collinear). + int numIntersections; + }; + + Result operator()(Line2 const& line, Segment2 const& segment) + { + Result result; + Vector2 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + FIQuery, Line2> llQuery; + auto llResult = llQuery(line, Line2(segOrigin, segDirection)); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the segment. + if (std::fabs(llResult.line1Parameter[0]) <= segExtent) + { + result.intersect = true; + result.numIntersections = 1; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else + { + result.intersect = llResult.intersect; + result.numIntersections = llResult.numIntersections; + } + return result; + } + }; + + template + class FIQuery, Segment2> + { + public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (line and segment + // intersect in a single point) or std::numeric_limits::max() + // (line and segment are collinear). + int numIntersections; + + // If numIntersections is 1, the intersection is + // point = line.origin + lineParameter[0] * line.direction + // = segment.origin + + // segmentParameter[0] * segment.direction + // If numIntersections is maxInt, point is not valid but the + // intervals are + // lineParameter[] = { -maxReal, +maxReal } + // segmentParameter[] = { -segmentExtent, segmentExtent } + Real lineParameter[2], segmentParameter[2]; + Vector2 point; + }; + + Result operator()(Line2 const& line, Segment2 const& segment) + { + Result result; + Vector2 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + FIQuery, Line2> llQuery; + auto llResult = llQuery(line, Line2(segOrigin, segDirection)); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the ray. + if (std::fabs(llResult.line1Parameter[0]) <= segExtent) + { + result.intersect = true; + result.numIntersections = 1; + result.lineParameter[0] = llResult.line0Parameter[0]; + result.segmentParameter[0] = llResult.line1Parameter[0]; + result.point = llResult.point; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + Real maxReal = std::numeric_limits::max(); + result.lineParameter[0] = -maxReal; + result.lineParameter[1] = +maxReal; + result.segmentParameter[0] = -segExtent; + result.segmentParameter[1] = +segExtent; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2Triangle2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2Triangle2.h new file mode 100644 index 0000000..e260010 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine2Triangle2.h @@ -0,0 +1,222 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the triangle to be a solid. + +namespace WwiseGTE +{ + template + class TIQuery, Triangle2> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Line2 const& line, Triangle2 const& triangle) + { + Result result; + + // Determine on which side of the line the vertices lie. The + // table of possibilities is listed next with n = numNegative, + // p = numPositive and z = numZero. + // + // n p z intersection + // ------------------------------------ + // 0 3 0 none + // 0 2 1 vertex + // 0 1 2 edge + // 0 0 3 none (degenerate triangle) + // 1 2 0 segment (2 edges clipped) + // 1 1 1 segment (1 edge clipped) + // 1 0 2 edge + // 2 1 0 segment (2 edges clipped) + // 2 0 1 vertex + // 3 0 0 none + + Real s[3]; + int numPositive = 0, numNegative = 0, numZero = 0; + for (int i = 0; i < 3; ++i) + { + Vector2 diff = triangle.v[i] - line.origin; + s[i] = DotPerp(line.direction, diff); + if (s[i] > (Real)0) + { + ++numPositive; + } + else if (s[i] < (Real)0) + { + ++numNegative; + } + else + { + ++numZero; + } + } + + result.intersect = + (numZero == 0 && (numPositive == 0 || numNegative == 0)) || + (numZero == 3); + + return result; + } + }; + + template + class FIQuery, Triangle2> + { + public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line2 const& line, Triangle2 const& triangle) + { + Result result; + DoQuery(line.origin, line.direction, triangle, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; + } + + protected: + void DoQuery(Vector2 const& lineOrigin, + Vector2 const& lineDirection, Triangle2 const& triangle, + Result& result) + { + // Determine on which side of the line the vertices lie. The + // table of possibilities is listed next with n = numNegative, + // p = numPositive and z = numZero. + // + // n p z intersection + // ------------------------------------ + // 0 3 0 none + // 0 2 1 vertex + // 0 1 2 edge + // 0 0 3 none (degenerate triangle) + // 1 2 0 segment (2 edges clipped) + // 1 1 1 segment (1 edge clipped) + // 1 0 2 edge + // 2 1 0 segment (2 edges clipped) + // 2 0 1 vertex + // 3 0 0 none + + Real s[3]; + int numPositive = 0, numNegative = 0, numZero = 0; + for (int i = 0; i < 3; ++i) + { + Vector2 diff = triangle.v[i] - lineOrigin; + s[i] = DotPerp(lineDirection, diff); + if (s[i] > (Real)0) + { + ++numPositive; + } + else if (s[i] < (Real)0) + { + ++numNegative; + } + else + { + ++numZero; + } + } + + if (numZero == 0 && numPositive > 0 && numNegative > 0) + { + result.intersect = true; + result.numIntersections = 2; + Real sign = (Real)3 - numPositive * (Real)2; + for (int i0 = 0; i0 < 3; ++i0) + { + if (sign * s[i0] > (Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + Real s1 = s[i1] / (s[i1] - s[i0]); + Vector2 p1 = (triangle.v[i1] - lineOrigin) + + s1 * (triangle.v[i0] - triangle.v[i1]); + result.parameter[0] = Dot(lineDirection, p1); + Real s2 = s[i2] / (s[i2] - s[i0]); + Vector2 p2 = (triangle.v[i2] - lineOrigin) + + s2 * (triangle.v[i0] - triangle.v[i2]); + result.parameter[1] = Dot(lineDirection, p2); + break; + } + } + return; + } + + if (numZero == 1) + { + result.intersect = true; + for (int i0 = 0; i0 < 3; ++i0) + { + if (s[i0] == (Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + result.parameter[0] = + Dot(lineDirection, triangle.v[i0] - lineOrigin); + if (numPositive == 2 || numNegative == 2) + { + result.numIntersections = 1; + + // Used by derived classes. + result.parameter[1] = result.parameter[0]; + } + else + { + result.numIntersections = 2; + Real s1 = s[i1] / (s[i1] - s[i2]); + Vector2 p1 = (triangle.v[i1] - lineOrigin) + + s1 * (triangle.v[i2] - triangle.v[i1]); + result.parameter[1] = Dot(lineDirection, p1); + } + break; + } + } + return; + } + + if (numZero == 2) + { + result.intersect = true; + result.numIntersections = 2; + for (int i0 = 0; i0 < 3; ++i0) + { + if (s[i0] != (Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + result.parameter[0] = + Dot(lineDirection, triangle.v[i1] - lineOrigin); + result.parameter[1] = + Dot(lineDirection, triangle.v[i2] - lineOrigin); + break; + } + } + return; + } + + // (n,p,z) one of (3,0,0), (0,3,0), (0,0,3) + result.intersect = false; + result.numIntersections = 0; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3AlignedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3AlignedBox3.h new file mode 100644 index 0000000..865c6fb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3AlignedBox3.h @@ -0,0 +1,191 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// The test-intersection queries use the method of separating axes. +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The find-intersection queries use parametric clipping against the six +// faces of the box. The find-intersection queries use Liang-Barsky +// clipping. The queries consider the box to be a solid. The algorithms +// are described in +// https://www.geometrictools.com/Documentation/IntersectionLineBox.pdf + +namespace WwiseGTE +{ + template + class TIQuery, AlignedBox3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Line3 const& line, AlignedBox3 const& box) + { + // Get the centered form of the aligned box. The axes are + // implicitly Axis[d] = Vector3::Unit(d). + Vector3 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the line to the aligned-box coordinate system. + Vector3 lineOrigin = line.origin - boxCenter; + + Result result; + DoQuery(lineOrigin, line.direction, boxExtent, result); + return result; + } + + protected: + void DoQuery(Vector3 const& lineOrigin, Vector3 const& lineDirection, + Vector3 const& boxExtent, Result& result) + { + Vector3 WxD = Cross(lineDirection, lineOrigin); + Real absWdU[3] = + { + std::fabs(lineDirection[0]), + std::fabs(lineDirection[1]), + std::fabs(lineDirection[2]) + }; + + if (std::fabs(WxD[0]) > boxExtent[1] * absWdU[2] + boxExtent[2] * absWdU[1]) + { + result.intersect = false; + return; + } + + if (std::fabs(WxD[1]) > boxExtent[0] * absWdU[2] + boxExtent[2] * absWdU[0]) + { + result.intersect = false; + return; + } + + if (std::fabs(WxD[2]) > boxExtent[0] * absWdU[1] + boxExtent[1] * absWdU[0]) + { + result.intersect = false; + return; + } + + result.intersect = true; + } + }; + + template + class FIQuery, AlignedBox3> + { + public: + struct Result + { + bool intersect; + int numPoints; + Real lineParameter[2]; + Vector3 point[2]; + }; + + Result operator()(Line3 const& line, AlignedBox3 const& box) + { + // Get the centered form of the aligned box. The axes are + // implicitly Axis[d] = Vector3::Unit(d). + Vector3 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the line to the aligned-box coordinate system. + Vector3 lineOrigin = line.origin - boxCenter; + + Result result; + DoQuery(lineOrigin, line.direction, boxExtent, result); + for (int i = 0; i < result.numPoints; ++i) + { + result.point[i] = line.origin + result.lineParameter[i] * line.direction; + } + return result; + } + + protected: + void DoQuery(Vector3 const& lineOrigin, Vector3 const& lineDirection, + Vector3 const& boxExtent, Result& result) + { + // The line t-values are in the interval (-infinity,+infinity). + // Clip the line against all six planes of an aligned box in + // centered form. The result.numPoints is + // 0, no intersection + // 1, intersect in a single point (t0 is line parameter of point) + // 2, intersect in a segment (line parameter interval is [t0,t1]) + Real t0 = -std::numeric_limits::max(); + Real t1 = std::numeric_limits::max(); + if (Clip(+lineDirection[0], -lineOrigin[0] - boxExtent[0], t0, t1) && + Clip(-lineDirection[0], +lineOrigin[0] - boxExtent[0], t0, t1) && + Clip(+lineDirection[1], -lineOrigin[1] - boxExtent[1], t0, t1) && + Clip(-lineDirection[1], +lineOrigin[1] - boxExtent[1], t0, t1) && + Clip(+lineDirection[2], -lineOrigin[2] - boxExtent[2], t0, t1) && + Clip(-lineDirection[2], +lineOrigin[2] - boxExtent[2], t0, t1)) + { + result.intersect = true; + if (t1 > t0) + { + result.numPoints = 2; + result.lineParameter[0] = t0; + result.lineParameter[1] = t1; + } + else + { + result.numPoints = 1; + result.lineParameter[0] = t0; + result.lineParameter[1] = t0; // Used by derived classes. + } + return; + } + + result.intersect = false; + result.numPoints = 0; + } + + private: + // Test whether the current clipped segment intersects the current + // test plane. If the return value is 'true', the segment does + // intersect the plane and is clipped; otherwise, the segment is + // culled (no intersection with box). + static bool Clip(Real denom, Real numer, Real& t0, Real& t1) + { + if (denom > (Real)0) + { + if (numer > denom * t1) + { + return false; + } + if (numer > denom * t0) + { + t0 = numer / denom; + } + return true; + } + else if (denom < (Real)0) + { + if (numer > denom * t0) + { + return false; + } + if (numer > denom * t1) + { + t1 = numer / denom; + } + return true; + } + else + { + return numer <= (Real)0; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3Capsule3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3Capsule3.h new file mode 100644 index 0000000..2a4c4a0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3Capsule3.h @@ -0,0 +1,329 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the capsule to be a solid. +// +// The test-intersection queries are based on distance computations. + +namespace WwiseGTE +{ + template + class TIQuery, Capsule3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Line3 const& line, Capsule3 const& capsule) + { + Result result; + DCPQuery, Segment3> lsQuery; + auto lsResult = lsQuery(line, capsule.segment); + result.intersect = (lsResult.distance <= capsule.radius); + return result; + } + }; + + template + class FIQuery, Capsule3> + { + public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line3 const& line, Capsule3 const& capsule) + { + Result result; + DoQuery(line.origin, line.direction, capsule, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; + } + + protected: + void DoQuery(Vector3 const& lineOrigin, + Vector3 const& lineDirection, Capsule3 const& capsule, + Result& result) + { + // Initialize the result as if there is no intersection. If we + // discover an intersection, these values will be modified + // accordingly. + result.intersect = false; + result.numIntersections = 0; + + // Create a coordinate system for the capsule. In this system, + // the capsule segment center C is the origin and the capsule axis + // direction W is the z-axis. U and V are the other coordinate + // axis directions. If P = x*U+y*V+z*W, the cylinder containing + // the capsule wall is x^2 + y^2 = r^2, where r is the capsule + // radius. The finite cylinder that makes up the capsule minus + // its hemispherical end caps has z-values |z| <= e, where e is + // the extent of the capsule segment. The top hemisphere cap is + // x^2+y^2+(z-e)^2 = r^2 for z >= e, and the bottom hemisphere cap + // is x^2+y^2+(z+e)^2 = r^2 for z <= -e. + + Vector3 segOrigin, segDirection; + Real segExtent; + capsule.segment.GetCenteredForm(segOrigin, segDirection, segExtent); + Vector3 basis[3]; // {W, U, V} + basis[0] = segDirection; + ComputeOrthogonalComplement(1, basis); + Real rSqr = capsule.radius * capsule.radius; + + // Convert incoming line origin to capsule coordinates. + Vector3 diff = lineOrigin - segOrigin; + Vector3 P{ Dot(basis[1], diff), Dot(basis[2], diff), Dot(basis[0], diff) }; + + // Get the z-value, in capsule coordinates, of the incoming line's + // unit-length direction. + Real dz = Dot(basis[0], lineDirection); + if (std::fabs(dz) == (Real)1) + { + // The line is parallel to the capsule axis. Determine + // whether the line intersects the capsule hemispheres. + Real radialSqrDist = rSqr - P[0] * P[0] - P[1] * P[1]; + if (radialSqrDist >= (Real)0) + { + // The line intersects the hemispherical caps. + result.intersect = true; + result.numIntersections = 2; + Real zOffset = std::sqrt(radialSqrDist) + segExtent; + if (dz > (Real)0) + { + result.parameter[0] = -P[2] - zOffset; + result.parameter[1] = -P[2] + zOffset; + } + else + { + result.parameter[0] = P[2] - zOffset; + result.parameter[1] = P[2] + zOffset; + } + } + // else: The line outside the capsule's cylinder, no + // intersection. + return; + } + + // Convert the incoming line unit-length direction to capsule + // coordinates. + Vector3 D{ Dot(basis[1], lineDirection), Dot(basis[2], lineDirection), dz }; + + // Test intersection of line P+t*D with infinite cylinder + // x^2+y^2 = r^2. This reduces to computing the roots of a + // quadratic equation. If P = (px,py,pz) and D = (dx,dy,dz), then + // the quadratic equation is + // (dx^2+dy^2)*t^2 + 2*(px*dx+py*dy)*t + (px^2+py^2-r^2) = 0 + Real a0 = P[0] * P[0] + P[1] * P[1] - rSqr; + Real a1 = P[0] * D[0] + P[1] * D[1]; + Real a2 = D[0] * D[0] + D[1] * D[1]; + Real discr = a1 * a1 - a0 * a2; + if (discr < (Real)0) + { + // The line does not intersect the infinite cylinder, so it + // cannot intersect the capsule. + return; + } + + Real root, inv, tValue, zValue; + if (discr > (Real)0) + { + // The line intersects the infinite cylinder in two places. + root = std::sqrt(discr); + inv = (Real)1 / a2; + tValue = (-a1 - root) * inv; + zValue = P[2] + tValue * D[2]; + if (std::fabs(zValue) <= segExtent) + { + result.intersect = true; + result.parameter[result.numIntersections++] = tValue; + } + + tValue = (-a1 + root) * inv; + zValue = P[2] + tValue * D[2]; + if (std::fabs(zValue) <= segExtent) + { + result.intersect = true; + result.parameter[result.numIntersections++] = tValue; + } + + if (result.numIntersections == 2) + { + // The line intersects the capsule wall in two places. + return; + } + } + else + { + // The line is tangent to the infinite cylinder but intersects + // the cylinder in a single point. + tValue = -a1 / a2; + zValue = P[2] + tValue * D[2]; + if (std::fabs(zValue) <= segExtent) + { + result.intersect = true; + result.numIntersections = 1; + result.parameter[0] = tValue; + // Used by derived classes. + result.parameter[1] = result.parameter[0]; + return; + } + } + + // Test intersection with bottom hemisphere. The quadratic + // equation is + // t^2 + 2*(px*dx+py*dy+(pz+e)*dz)*t + // + (px^2+py^2+(pz+e)^2-r^2) = 0 + // Use the fact that currently a1 = px*dx+py*dy and + // a0 = px^2+py^2-r^2. The leading coefficient is a2 = 1, so no + // need to include in the construction. + Real PZpE = P[2] + segExtent; + a1 += PZpE * D[2]; + a0 += PZpE * PZpE; + discr = a1 * a1 - a0; + if (discr > (Real)0) + { + root = std::sqrt(discr); + tValue = -a1 - root; + zValue = P[2] + tValue * D[2]; + if (zValue <= -segExtent) + { + result.parameter[result.numIntersections++] = tValue; + if (result.numIntersections == 2) + { + result.intersect = true; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + return; + } + } + + tValue = -a1 + root; + zValue = P[2] + tValue * D[2]; + if (zValue <= -segExtent) + { + result.parameter[result.numIntersections++] = tValue; + if (result.numIntersections == 2) + { + result.intersect = true; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + return; + } + } + } + else if (discr == (Real)0) + { + tValue = -a1; + zValue = P[2] + tValue * D[2]; + if (zValue <= -segExtent) + { + result.parameter[result.numIntersections++] = tValue; + if (result.numIntersections == 2) + { + result.intersect = true; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + return; + } + } + } + + // Test intersection with top hemisphere. The quadratic equation + // is + // t^2 + 2*(px*dx+py*dy+(pz-e)*dz)*t + // + (px^2+py^2+(pz-e)^2-r^2) = 0 + // Use the fact that currently a1 = px*dx+py*dy+(pz+e)*dz and + // a0 = px^2+py^2+(pz+e)^2-r^2. The leading coefficient is a2 = 1, + // so no need to include in the construction. + a1 -= ((Real)2) * segExtent * D[2]; + a0 -= ((Real)4) * segExtent * P[2]; + discr = a1 * a1 - a0; + if (discr > (Real)0) + { + root = std::sqrt(discr); + tValue = -a1 - root; + zValue = P[2] + tValue * D[2]; + if (zValue >= segExtent) + { + result.parameter[result.numIntersections++] = tValue; + if (result.numIntersections == 2) + { + result.intersect = true; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + return; + } + } + + tValue = -a1 + root; + zValue = P[2] + tValue * D[2]; + if (zValue >= segExtent) + { + result.parameter[result.numIntersections++] = tValue; + if (result.numIntersections == 2) + { + result.intersect = true; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + return; + } + } + } + else if (discr == (Real)0) + { + tValue = -a1; + zValue = P[2] + tValue * D[2]; + if (zValue >= segExtent) + { + result.parameter[result.numIntersections++] = tValue; + if (result.numIntersections == 2) + { + result.intersect = true; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + return; + } + } + } + + if (result.numIntersections == 1) + { + // Used by derived classes. + result.parameter[1] = result.parameter[0]; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3Cone3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3Cone3.h new file mode 100644 index 0000000..3555f52 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3Cone3.h @@ -0,0 +1,480 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the cone to be single sided and solid. The +// cone height range is [hmin,hmax]. The cone can be infinite where +// hmin = 0 and hmax = +infinity, infinite truncated where hmin > 0 +// and hmax = +infinity, finite where hmin = 0 and hmax < +infinity, +// or a cone frustum where hmin > 0 and hmax < +infinity. The +// algorithm details are found in +// https://www.geometrictools.com/Documentation/IntersectionLineCone.pdf + +namespace WwiseGTE +{ + template + class FIQuery, Cone3> + { + public: + // The rational quadratic field type with elements x + y * sqrt(d). + // This type supports error-free computation. + using QFN1 = QFNumber; + + // Convenient naming for interval find-intersection queries. + using IIQuery = FIIntervalInterval; + + struct Result + { + // Because the intersection of line and cone with infinite height + // can be a ray or a line, we use a 'type' value that allows you + // to decide how to interpret the t[] and P[] values. + + // No interesection. + static int const isEmpty = 0; + + // t[0] is finite, t[1] is set to t[0], P[0] is the point of + // intersection, P[1] is set to P[0]. + static int const isPoint = 1; + + // t[0] and t[1] are finite with t[0] < t[1], P[0] and P[1] are + // the endpoints of the segment of intersection. + static int const isSegment = 2; + + // Dot(line.direction, cone.ray.direction) > 0: + // t[0] is finite, t[1] is +infinity (set to +1), P[0] is the ray + // origin, P[1] is the ray direction (set to line.direction). + // NOTE: The ray starts at P[0] and you walk away from it in the + // line direction. + static int const isRayPositive = 3; + + // Dot(line.direction, cone.ray.direction) < 0: + // t[0] is -infinity (set to -1), t[1] is finite, P[0] is the ray + // endpoint, P[1] is the ray direction (set to line.direction). + // NOTE: The ray ends at P[1] and you walk towards it in the line + // direction. + static int const isRayNegative = 4; + + Result() + : + intersect(false), + type(Result::isEmpty) + { + // t[], h[] and P[] are initialized to zero via QFN1 constructors + } + + void ComputePoints(Vector3 const& origin, Vector3 const& direction) + { + switch (type) + { + case Result::isEmpty: + for (int i = 0; i < 3; ++i) + { + P[0][i] = QFN1(); + P[1][i] = P[0][i]; + } + break; + case Result::isPoint: + for (int i = 0; i < 3; ++i) + { + P[0][i] = origin[i] + direction[i] * t[0]; + P[1][i] = P[0][i]; + } + break; + case Result::isSegment: + for (int i = 0; i < 3; ++i) + { + P[0][i] = origin[i] + direction[i] * t[0]; + P[1][i] = origin[i] + direction[i] * t[1]; + } + break; + case Result::isRayPositive: + for (int i = 0; i < 3; ++i) + { + P[0][i] = origin[i] + direction[i] * t[0]; + P[1][i] = QFN1(direction[i], 0, t[0].d); + } + break; + case Result::isRayNegative: + for (int i = 0; i < 3; ++i) + { + P[0][i] = origin[i] + direction[i] * t[1]; + P[1][i] = QFN1(direction[i], 0, t[1].d); + } + break; + default: + LogError("Invalid case."); + break; + } + } + + template + static void Convert(QFN1 const& input, OutputType& output) + { + output = static_cast(input); + } + + template + static void Convert(Vector3 const& input, Vector3& output) + { + for (int i = 0; i < 3; ++i) + { + output[i] = static_cast(input[i]); + } + } + + bool intersect; + int type; + std::array t; + std::array, 2> P; + }; + + Result operator()(Line3 const& line, Cone3 const& cone) + { + Result result; + DoQuery(line.origin, line.direction, cone, result); + result.ComputePoints(line.origin, line.direction); + result.intersect = (result.type != Result::isEmpty); + return result; + } + + protected: + // The result.type and result.t[] values are computed by DoQuery. The + // result.P[] and result.intersect values are computed from them in + // the operator()(...) function. + void DoQuery(Vector3 const& lineOrigin, Vector3 const& lineDirection, + Cone3 const& cone, Result& result) + { + // The algorithm implemented in DoQuery avoids extra branches if + // we choose a line whose direction forms an acute angle with the + // cone direction. + if (Dot(lineDirection, cone.ray.direction) >= (Real)0) + { + DoQuerySpecial(lineOrigin, lineDirection, cone, result); + } + else + { + DoQuerySpecial(lineOrigin, -lineDirection, cone, result); + result.t[0] = -result.t[0]; + result.t[1] = -result.t[1]; + std::swap(result.t[0], result.t[1]); + if (result.type == Result::isRayPositive) + { + result.type = Result::isRayNegative; + } + } + } + + void DoQuerySpecial(Vector3 const& lineOrigin, Vector3 const& lineDirection, + Cone3 const& cone, Result& result) + { + // Compute the number of real-valued roots and represent them + // using rational quadratic field elements to support when Real + // is an exact rational arithmetic type. TODO: Adjust by noting + // that we should use D/|D| because a normalized floating-point + // D still might not have |D| = 1 (although it is close to 1). + Vector3 PmV = lineOrigin - cone.ray.origin; + Real UdU = Dot(lineDirection, lineDirection); + Real DdU = Dot(cone.ray.direction, lineDirection); // >= 0 + Real DdPmV = Dot(cone.ray.direction, PmV); + Real UdPmV = Dot(lineDirection, PmV); + Real PmVdPmV = Dot(PmV, PmV); + Real c2 = DdU * DdU - cone.cosAngleSqr * UdU; + Real c1 = DdU * DdPmV - cone.cosAngleSqr * UdPmV; + Real c0 = DdPmV * DdPmV - cone.cosAngleSqr * PmVdPmV; + + if (c2 != (Real)0) + { + Real discr = c1 * c1 - c0 * c2; + if (discr < (Real)0) + { + CaseC2NotZeroDiscrNeg(result); + } + else if (discr > (Real)0) + { + CaseC2NotZeroDiscrPos(c1, c2, discr, DdU, DdPmV, cone, result); + } + else // discr == 0 + { + CaseC2NotZeroDiscrZero(c1, c2, UdU, UdPmV, DdU, DdPmV, cone, result); + } + } + else if (c1 != (Real)0) + { + CaseC2ZeroC1NotZero(c0, c1, DdU, DdPmV, cone, result); + } + else + { + CaseC2ZeroC1Zero(c0, UdU, UdPmV, DdU, DdPmV, cone, result); + } + } + + void CaseC2NotZeroDiscrNeg(Result& result) + { + // Block 0. The quadratic has no real-valued roots. The line does + // not intersect the double-sided cone. + SetEmpty(result); + } + + void CaseC2NotZeroDiscrPos(Real const& c1, Real const& c2, Real const& discr, + Real const& DdU, Real const& DdPmV, Cone3 const& cone, Result& result) + { + // The quadratic has two distinct real-valued roots, t[0] and t[1] + // with t[0] < t[1]. + Real x = -c1 / c2; + Real y = (c2 > (Real)0 ? (Real)1 / c2 : (Real)-1 / c2); + std::array t = { QFN1(x, -y, discr), QFN1(x, y, discr) }; + + // Compute the signed heights at the intersection points, h[0] and + // h[1] with h[0] <= h[1]. The ordering is guaranteed because we + // have arranged for the input line to satisfy Dot(D,U) >= 0. + std::array h = { t[0] * DdU + DdPmV, t[1] * DdU + DdPmV }; + + QFN1 zero(0, 0, discr); + if (h[0] >= zero) + { + // Block 1. The line intersects the positive cone in two + // points. + SetSegmentClamp(t, h, DdU, DdPmV, cone, result); + } + else if (h[1] <= zero) + { + // Block 2. The line intersects the negative cone in two + // points. + SetEmpty(result); + } + else // h[0] < 0 < h[1] + { + // Block 3. The line intersects the positive cone in a single + // point and the negative cone in a single point. + SetRayClamp(h[1], DdU, DdPmV, cone, result); + } + } + + void CaseC2NotZeroDiscrZero(Real const& c1, Real const& c2, + Real const& UdU, Real const& UdPmV, Real const& DdU, Real const& DdPmV, + Cone3 const& cone, Result& result) + { + Real t = -c1 / c2; + if (t * UdU + UdPmV == (Real)0) + { + // To get here, it must be that V = P + (-c1/c2) * U, where + // U is not necessarily a unit-length vector. The line + // intersects the cone vertex. + if (c2 < (Real)0) + { + // Block 4. The line is outside the double-sided cone and + // intersects it only at V. + SetPointClamp(QFN1(t, 0, 0), QFN1(0, 0, 0), cone, result); + } + else + { + // Block 5. The line is inside the double-sided cone, so + // the intersection is a ray with origin V. + SetRayClamp(QFN1(0, 0, 0), DdU, DdPmV, cone, result); + } + } + else + { + // The line is tangent to the cone at a point different from + // the vertex. + Real h = t * DdU + DdPmV; + if (h >= (Real)0) + { + // Block 6. The line is tangent to the positive cone. + SetPointClamp(QFN1(t, 0, 0), QFN1(h, 0, 0), cone, result); + } + else + { + // Block 7. The line is tangent to the negative cone. + SetEmpty(result); + } + } + } + + void CaseC2ZeroC1NotZero(Real const& c0, Real const& c1, Real const& DdU, + Real const& DdPmV, Cone3 const& cone, Result& result) + { + // U is a direction vector on the cone boundary. Compute the + // t-value for the intersection point and compute the + // corresponding height h to determine whether that point is on + // the positive cone or negative cone. + Real t = (Real)-0.5 * c0 / c1; + Real h = t * DdU + DdPmV; + if (h > (Real)0) + { + // Block 8. The line intersects the positive cone and the ray + // of intersection is interior to the positive cone. The + // intersection is a ray or segment. + SetRayClamp(QFN1(h, 0, 0), DdU, DdPmV, cone, result); + } + else + { + // Block 9. The line intersects the negative cone and the ray + // of intersection is interior to the negative cone. + SetEmpty(result); + } + } + + void CaseC2ZeroC1Zero(Real const& c0, Real const& UdU, Real const& UdPmV, + Real const& DdU, Real const& DdPmV, Cone3 const& cone, Result& result) + { + if (c0 != (Real)0) + { + // Block 10. The line does not intersect the double-sided + // cone. + SetEmpty(result); + } + else + { + // Block 11. The line is on the cone boundary. The + // intersection with the positive cone is a ray that contains + // the cone vertex. The intersection is either a ray or + // segment. + Real t = -UdPmV / UdU; + Real h = t * DdU + DdPmV; + SetRayClamp(QFN1(h, 0, 0), DdU, DdPmV, cone, result); + } + } + + void SetEmpty(Result& result) + { + result.type = Result::isEmpty; + result.t[0] = QFN1(); + result.t[1] = QFN1(); + } + + void SetPoint(QFN1 const& t, Result& result) + { + result.type = Result::isPoint; + result.t[0] = t; + result.t[1] = result.t[0]; + } + + void SetSegment(QFN1 const& t0, QFN1 const& t1, Result& result) + { + result.type = Result::isSegment; + result.t[0] = t0; + result.t[1] = t1; + } + + void SetRayPositive(QFN1 const& t, Result& result) + { + result.type = Result::isRayPositive; + result.t[0] = t; + result.t[1] = QFN1(+1, 0, t.d); // +infinity + } + + void SetRayNegative(QFN1 const& t, Result& result) + { + result.type = Result::isRayNegative; + result.t[0] = QFN1(-1, 0, t.d); // +infinity + result.t[1] = t; + } + + void SetPointClamp(QFN1 const& t, QFN1 const& h, + Cone3 const& cone, Result& result) + { + if (cone.HeightInRange(h.x[0])) + { + // P0. + SetPoint(t, result); + } + else + { + // P1. + SetEmpty(result); + } + } + + void SetSegmentClamp(std::array const& t, std::array const& h, + Real const& DdU, Real const& DdPmV, Cone3 const& cone, Result& result) + { + std::array hrange = + { + QFN1(cone.GetMinHeight(), 0, h[0].d), + QFN1(cone.GetMaxHeight(), 0, h[0].d) + }; + + if (h[1] > h[0]) + { + auto iir = (cone.IsFinite() ? IIQuery()(h, hrange) : IIQuery()(h, hrange[0], true)); + if (iir.numIntersections == 2) + { + // S0. + SetSegment((iir.overlap[0] - DdPmV) / DdU, (iir.overlap[1] - DdPmV) / DdU, result); + } + else if (iir.numIntersections == 1) + { + // S1. + SetPoint((iir.overlap[0] - DdPmV) / DdU, result); + } + else // iir.numIntersections == 0 + { + // S2. + SetEmpty(result); + } + } + else // h[1] == h[0] + { + if (hrange[0] <= h[0] && (cone.IsFinite() ? h[0] <= hrange[1] : true)) + { + // S3. DdU > 0 and the line is not perpendicular to the + // cone axis. + SetSegment(t[0], t[1], result); + } + else + { + // S4. DdU == 0 and the line is perpendicular to the + // cone axis. + SetEmpty(result); + } + } + } + + void SetRayClamp(QFN1 const& h, Real const& DdU, Real const& DdPmV, + Cone3 const& cone, Result& result) + { + std::array hrange = + { + QFN1(cone.GetMinHeight(), 0, h.d), + QFN1(cone.GetMaxHeight(), 0, h.d) + }; + + if (cone.IsFinite()) + { + auto iir = IIQuery()(hrange, h, true); + if (iir.numIntersections == 2) + { + // R0. + SetSegment((iir.overlap[0] - DdPmV) / DdU, (iir.overlap[1] - DdPmV) / DdU, result); + } + else if (iir.numIntersections == 1) + { + // R1. + SetPoint((iir.overlap[0] - DdPmV) / DdU, result); + } + else // iir.numIntersections == 0 + { + // R2. + SetEmpty(result); + } + } + else + { + // R3. + SetRayPositive((std::max(hrange[0], h) - DdPmV) / DdU, result); + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3Cylinder3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3Cylinder3.h new file mode 100644 index 0000000..2392e98 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3Cylinder3.h @@ -0,0 +1,249 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The queries consider the cylinder to be a solid. + +namespace WwiseGTE +{ + template + class FIQuery, Cylinder3> + { + public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line3 const& line, Cylinder3 const& cylinder) + { + Result result; + DoQuery(line.origin, line.direction, cylinder, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; + } + + protected: + void DoQuery(Vector3 const& lineOrigin, + Vector3 const& lineDirection, Cylinder3 const& cylinder, + Result& result) + { + // Initialize the result as if there is no intersection. If we + // discover an intersection, these values will be modified + // accordingly. + result.intersect = false; + result.numIntersections = 0; + + // Create a coordinate system for the cylinder. In this system, + // the cylinder segment center C is the origin and the cylinder + // axis direction W is the z-axis. U and V are the other + // coordinate axis directions. If P = x*U+y*V+z*W, the cylinder + // is x^2 + y^2 = r^2, where r is the cylinder radius. The end + // caps are |z| = h/2, where h is the cylinder height. + Vector3 basis[3]; // {W, U, V} + basis[0] = cylinder.axis.direction; + ComputeOrthogonalComplement(1, basis); + Real halfHeight = (Real)0.5 * cylinder.height; + Real rSqr = cylinder.radius * cylinder.radius; + + // Convert incoming line origin to capsule coordinates. + Vector3 diff = lineOrigin - cylinder.axis.origin; + Vector3 P{ Dot(basis[1], diff), Dot(basis[2], diff), Dot(basis[0], diff) }; + + // Get the z-value, in cylinder coordinates, of the incoming + // line's unit-length direction. + Real dz = Dot(basis[0], lineDirection); + if (std::fabs(dz) == (Real)1) + { + // The line is parallel to the cylinder axis. Determine + // whether the line intersects the cylinder end disks. + Real radialSqrDist = rSqr - P[0] * P[0] - P[1] * P[1]; + if (radialSqrDist >= (Real)0) + { + // The line intersects the cylinder end disks. + result.intersect = true; + result.numIntersections = 2; + if (dz > (Real)0) + { + result.parameter[0] = -P[2] - halfHeight; + result.parameter[1] = -P[2] + halfHeight; + } + else + { + result.parameter[0] = P[2] - halfHeight; + result.parameter[1] = P[2] + halfHeight; + } + } + // else: The line is outside the cylinder, no intersection. + return; + } + + // Convert the incoming line unit-length direction to cylinder + // coordinates. + Vector3 D{ Dot(basis[1], lineDirection), Dot(basis[2], lineDirection), dz }; + + Real a0, a1, a2, discr, root, inv, tValue; + + if (D[2] == (Real)0) + { + // The line is perpendicular to the cylinder axis. + if (std::fabs(P[2]) <= halfHeight) + { + // Test intersection of line P+t*D with infinite cylinder + // x^2+y^2 = r^2. This reduces to computing the roots of + // a quadratic equation. If P = (px,py,pz) and + // D = (dx,dy,dz), then the quadratic equation is + // (dx^2+dy^2)*t^2 + 2*(px*dx+py*dy)*t + // + (px^2+py^2-r^2) = 0 + a0 = P[0] * P[0] + P[1] * P[1] - rSqr; + a1 = P[0] * D[0] + P[1] * D[1]; + a2 = D[0] * D[0] + D[1] * D[1]; + discr = a1 * a1 - a0 * a2; + if (discr > (Real)0) + { + // The line intersects the cylinder in two places. + result.intersect = true; + result.numIntersections = 2; + root = std::sqrt(discr); + inv = ((Real)1) / a2; + result.parameter[0] = (-a1 - root) * inv; + result.parameter[1] = (-a1 + root) * inv; + } + else if (discr == (Real)0) + { + // The line is tangent to the cylinder. + result.intersect = true; + result.numIntersections = 1; + result.parameter[0] = -a1 / a2; + // Used by derived classes. + result.parameter[1] = result.parameter[0]; + } + // else: The line does not intersect the cylinder. + } + // else: The line is outside the planes of the cylinder end + // disks. + return; + } + + // Test for intersections with the planes of the end disks. + inv = (Real)1 / D[2]; + + Real t0 = (-halfHeight - P[2]) * inv; + Real xTmp = P[0] + t0 * D[0]; + Real yTmp = P[1] + t0 * D[1]; + if (xTmp * xTmp + yTmp * yTmp <= rSqr) + { + // Plane intersection inside the top cylinder end disk. + result.parameter[result.numIntersections++] = t0; + } + + Real t1 = (+halfHeight - P[2]) * inv; + xTmp = P[0] + t1 * D[0]; + yTmp = P[1] + t1 * D[1]; + if (xTmp * xTmp + yTmp * yTmp <= rSqr) + { + // Plane intersection inside the bottom cylinder end disk. + result.parameter[result.numIntersections++] = t1; + } + + if (result.numIntersections < 2) + { + // Test for intersection with the cylinder wall. + a0 = P[0] * P[0] + P[1] * P[1] - rSqr; + a1 = P[0] * D[0] + P[1] * D[1]; + a2 = D[0] * D[0] + D[1] * D[1]; + discr = a1 * a1 - a0 * a2; + if (discr > (Real)0) + { + root = std::sqrt(discr); + inv = (Real)1 / a2; + tValue = (-a1 - root) * inv; + if (t0 <= t1) + { + if (t0 <= tValue && tValue <= t1) + { + result.parameter[result.numIntersections++] = tValue; + } + } + else + { + if (t1 <= tValue && tValue <= t0) + { + result.parameter[result.numIntersections++] = tValue; + } + } + + if (result.numIntersections < 2) + { + tValue = (-a1 + root) * inv; + if (t0 <= t1) + { + if (t0 <= tValue && tValue <= t1) + { + result.parameter[result.numIntersections++] = tValue; + } + } + else + { + if (t1 <= tValue && tValue <= t0) + { + result.parameter[result.numIntersections++] = tValue; + } + } + } + // else: Line intersects end disk and cylinder wall. + } + else if (discr == (Real)0) + { + tValue = -a1 / a2; + if (t0 <= t1) + { + if (t0 <= tValue && tValue <= t1) + { + result.parameter[result.numIntersections++] = tValue; + } + } + else + { + if (t1 <= tValue && tValue <= t0) + { + result.parameter[result.numIntersections++] = tValue; + } + } + } + // else: Line does not intersect cylinder wall. + } + // else: Line intersects both top and bottom cylinder end disks. + + if (result.numIntersections == 2) + { + result.intersect = true; + if (result.parameter[0] > result.parameter[1]) + { + std::swap(result.parameter[0], result.parameter[1]); + } + } + else if (result.numIntersections == 1) + { + result.intersect = true; + // Used by derived classes. + result.parameter[1] = result.parameter[0]; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3Ellipsoid3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3Ellipsoid3.h new file mode 100644 index 0000000..ebf9f30 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3Ellipsoid3.h @@ -0,0 +1,125 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the ellipsoid to be a solid. + +namespace WwiseGTE +{ + template + class TIQuery, Ellipsoid3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Line3 const& line, Ellipsoid3 const& ellipsoid) + { + // The ellipsoid is (X-K)^T*M*(X-K)-1 = 0 and the line is + // X = P+t*D. Substitute the line equation into the ellipsoid + // equation to obtain a quadratic equation + // Q(t) = a2*t^2 + 2*a1*t + a0 = 0 + // where a2 = D^T*M*D, a1 = D^T*M*(P-K) and + // a0 = (P-K)^T*M*(P-K)-1. + Result result; + + Matrix3x3 M; + ellipsoid.GetM(M); + + Vector3 diff = line.origin - ellipsoid.center; + Vector3 matDir = M * line.direction; + Vector3 matDiff = M * diff; + Real a2 = Dot(line.direction, matDir); + Real a1 = Dot(line.direction, matDiff); + Real a0 = Dot(diff, matDiff) - (Real)1; + + // Intersection occurs when Q(t) has real roots. + Real discr = a1 * a1 - a0 * a2; + result.intersect = (discr >= (Real)0); + return result; + } + }; + + template + class FIQuery, Ellipsoid3> + { + public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line3 const& line, Ellipsoid3 const& ellipsoid) + { + Result result; + DoQuery(line.origin, line.direction, ellipsoid, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; + } + + + protected: + void DoQuery(Vector3 const& lineOrigin, + Vector3 const& lineDirection, Ellipsoid3 const& ellipsoid, + Result& result) + { + // The ellipsoid is (X-K)^T*M*(X-K)-1 = 0 and the line is + // X = P+t*D. Substitute the line equation into the ellipsoid + // equation to obtain a quadratic equation + // Q(t) = a2*t^2 + 2*a1*t + a0 = 0 + // where a2 = D^T*M*D, a1 = D^T*M*(P-K) and + // a0 = (P-K)^T*M*(P-K)-1. + Matrix3x3 M; + ellipsoid.GetM(M); + + Vector3 diff = lineOrigin - ellipsoid.center; + Vector3 matDir = M * lineDirection; + Vector3 matDiff = M * diff; + Real a2 = Dot(lineDirection, matDir); + Real a1 = Dot(lineDirection, matDiff); + Real a0 = Dot(diff, matDiff) - (Real)1; + + // Intersection occurs when Q(t) has real roots. + Real discr = a1 * a1 - a0 * a2; + if (discr > (Real)0) + { + result.intersect = true; + result.numIntersections = 2; + Real root = std::sqrt(discr); + Real inv = (Real)1 / a2; + result.parameter[0] = (-a1 - root) * inv; + result.parameter[1] = (-a1 + root) * inv; + } + else if (discr < (Real)0) + { + result.intersect = false; + result.numIntersections = 0; + } + else + { + result.intersect = true; + result.numIntersections = 1; + result.parameter[0] = -a1 / a2; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3OrientedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3OrientedBox3.h new file mode 100644 index 0000000..b9c27cd --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3OrientedBox3.h @@ -0,0 +1,99 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The test-intersection queries use the method of separating axes. +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The find-intersection queries use parametric clipping against the six +// faces of the box. The find-intersection queries use Liang-Barsky +// clipping. The queries consider the box to be a solid. The algorithms +// are described in +// https://www.geometrictools.com/Documentation/IntersectionLineBox.pdf + +namespace WwiseGTE +{ + template + class TIQuery, OrientedBox3> + : + public TIQuery, AlignedBox3> + { + public: + struct Result + : + public TIQuery, AlignedBox3>::Result + { + // No additional relevant information to compute. + }; + + Result operator()(Line3 const& line, OrientedBox3 const& box) + { + // Transform the line to the oriented-box coordinate system. + Vector3 diff = line.origin - box.center; + Vector3 lineOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]), + Dot(diff, box.axis[2]) + }; + Vector3 lineDirection + { + Dot(line.direction, box.axis[0]), + Dot(line.direction, box.axis[1]), + Dot(line.direction, box.axis[2]) + }; + + Result result; + this->DoQuery(lineOrigin, lineDirection, box.extent, result); + return result; + } + }; + + template + class FIQuery, OrientedBox3> + : + public FIQuery, AlignedBox3> + { + public: + struct Result + : + public FIQuery, AlignedBox3>::Result + { + // No additional relevant information to compute. + }; + + Result operator()(Line3 const& line, OrientedBox3 const& box) + { + // Transform the line to the oriented-box coordinate system. + Vector3 diff = line.origin - box.center; + Vector3 lineOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]), + Dot(diff, box.axis[2]) + }; + Vector3 lineDirection + { + Dot(line.direction, box.axis[0]), + Dot(line.direction, box.axis[1]), + Dot(line.direction, box.axis[2]) + }; + + Result result; + this->DoQuery(lineOrigin, lineDirection, box.extent, result); + for (int i = 0; i < result.numPoints; ++i) + { + result.point[i] = + line.origin + result.lineParameter[i] * line.direction; + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3Plane3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3Plane3.h new file mode 100644 index 0000000..0c50cb7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3Plane3.h @@ -0,0 +1,123 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Plane3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Line3 const& line, Plane3 const& plane) + { + Result result; + + Real DdN = Dot(line.direction, plane.normal); + if (DdN != (Real)0) + { + // The line is not parallel to the plane, so they must + // intersect. + result.intersect = true; + } + else + { + // The line and plane are parallel. + DCPQuery, Plane3> vpQuery; + result.intersect = (vpQuery(line.origin, plane).distance == (Real)0); + } + + return result; + } + }; + + template + class FIQuery, Plane3> + { + public: + struct Result + { + Result() + : + intersect(false), + parameter((Real)0), + point{ (Real)0, (Real)0, (Real)0 } + { + } + + bool intersect; + + // The number of intersections is 0 (no intersection), 1 (linear + // component and plane intersect in a point), or + // std::numeric_limits::max() (linear component is on the + // plane). If the linear component is on the plane, 'point' + // component's origin and 'parameter' is zero. + int numIntersections; + Real parameter; + Vector3 point; + }; + + Result operator()(Line3 const& line, Plane3 const& plane) + { + Result result; + DoQuery(line.origin, line.direction, plane, result); + if (result.intersect) + { + result.point = line.origin + result.parameter * line.direction; + } + return result; + } + + protected: + void DoQuery(Vector3 const& lineOrigin, + Vector3 const& lineDirection, Plane3 const& plane, + Result& result) + { + Real DdN = Dot(lineDirection, plane.normal); + DCPQuery, Plane3> vpQuery; + auto vpResult = vpQuery(lineOrigin, plane); + + if (DdN != (Real)0) + { + // The line is not parallel to the plane, so they must + // intersect. + result.intersect = true; + result.numIntersections = 1; + result.parameter = -vpResult.signedDistance / DdN; + } + else + { + // The line and plane are parallel. Determine whether the + // line is on the plane. + if (vpResult.distance == (Real)0) + { + // The line is coincident with the plane, so choose t = 0 + // for the parameter. + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + result.parameter = (Real)0; + } + else + { + // The line is not on the plane. + result.intersect = false; + result.numIntersections = 0; + } + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3Sphere3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3Sphere3.h new file mode 100644 index 0000000..3acdc57 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3Sphere3.h @@ -0,0 +1,105 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Sphere3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Line3 const& line, Sphere3 const& sphere) + { + // The sphere is (X-C)^T*(X-C)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the sphere equation to + // obtain a quadratic equation Q(t) = t^2 + 2*a1*t + a0 = 0, where + // a1 = D^T*(P-C) and a0 = (P-C)^T*(P-C)-1. + Result result; + + Vector3 diff = line.origin - sphere.center; + Real a0 = Dot(diff, diff) - sphere.radius * sphere.radius; + Real a1 = Dot(line.direction, diff); + + // Intersection occurs when Q(t) has real roots. + Real discr = a1 * a1 - a0; + result.intersect = (discr >= (Real)0); + return result; + } + }; + + template + class FIQuery, Sphere3> + { + public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Line3 const& line, Sphere3 const& sphere) + { + Result result; + DoQuery(line.origin, line.direction, sphere, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = line.origin + result.parameter[i] * line.direction; + } + return result; + } + + protected: + void DoQuery(Vector3 const& lineOrigin, + Vector3 const& lineDirection, Sphere3 const& sphere, + Result& result) + { + // The sphere is (X-C)^T*(X-C)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the sphere equation to + // obtain a quadratic equation Q(t) = t^2 + 2*a1*t + a0 = 0, where + // a1 = D^T*(P-C) and a0 = (P-C)^T*(P-C)-1. + Vector3 diff = lineOrigin - sphere.center; + Real a0 = Dot(diff, diff) - sphere.radius * sphere.radius; + Real a1 = Dot(lineDirection, diff); + + // Intersection occurs when Q(t) has real roots. + Real discr = a1 * a1 - a0; + if (discr > (Real)0) + { + result.intersect = true; + result.numIntersections = 2; + Real root = std::sqrt(discr); + result.parameter[0] = -a1 - root; + result.parameter[1] = -a1 + root; + } + else if (discr < (Real)0) + { + result.intersect = false; + result.numIntersections = 0; + } + else + { + result.intersect = true; + result.numIntersections = 1; + result.parameter[0] = -a1; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3Triangle3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3Triangle3.h new file mode 100644 index 0000000..fd0a1dd --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrLine3Triangle3.h @@ -0,0 +1,172 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Triangle3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Line3 const& line, Triangle3 const& triangle) + { + Result result; + + // Compute the offset origin, edges, and normal. + Vector3 diff = line.origin - triangle.v[0]; + Vector3 edge1 = triangle.v[1] - triangle.v[0]; + Vector3 edge2 = triangle.v[2] - triangle.v[0]; + Vector3 normal = Cross(edge1, edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = diff, D = line direction, + // E1 = edge1, E2 = edge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + Real DdN = Dot(line.direction, normal); + Real sign; + if (DdN > (Real)0) + { + sign = (Real)1; + } + else if (DdN < (Real)0) + { + sign = (Real)-1; + DdN = -DdN; + } + else + { + // Line and triangle are parallel, call it a "no intersection" + // even if the line and triangle are coplanar and + // intersecting. + result.intersect = false; + return result; + } + + Real DdQxE2 = sign * DotCross(line.direction, diff, edge2); + if (DdQxE2 >= (Real)0) + { + Real DdE1xQ = sign * DotCross(line.direction, edge1, diff); + if (DdE1xQ >= (Real)0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle. + result.intersect = true; + return result; + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + result.intersect = false; + return result; + } + }; + + template + class FIQuery, Triangle3> + { + public: + struct Result + { + Result() + : + intersect(false), + parameter((Real)0), + triangleBary{ (Real)0, (Real)0, (Real)0 }, + point{ (Real)0, (Real)0, (Real)0 } + { + } + + bool intersect; + Real parameter; + std::array triangleBary; + Vector3 point; + }; + + Result operator()(Line3 const& line, Triangle3 const& triangle) + { + Result result; + + // Compute the offset origin, edges, and normal. + Vector3 diff = line.origin - triangle.v[0]; + Vector3 edge1 = triangle.v[1] - triangle.v[0]; + Vector3 edge2 = triangle.v[2] - triangle.v[0]; + Vector3 normal = Cross(edge1, edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = diff, D = line direction, + // E1 = edge1, E2 = edge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + Real DdN = Dot(line.direction, normal); + Real sign; + if (DdN > (Real)0) + { + sign = (Real)1; + } + else if (DdN < (Real)0) + { + sign = (Real)-1; + DdN = -DdN; + } + else + { + // Line and triangle are parallel, call it a "no intersection" + // even if the line and triangle are coplanar and + // intersecting. + result.intersect = false; + return result; + } + + Real DdQxE2 = sign * DotCross(line.direction, diff, edge2); + if (DdQxE2 >= (Real)0) + { + Real DdE1xQ = sign * DotCross(line.direction, edge1, diff); + if (DdE1xQ >= (Real)0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle. + Real QdN = -sign * Dot(diff, normal); + Real inv = (Real)1 / DdN; + + result.intersect = true; + result.parameter = QdN * inv; + result.triangleBary[1] = DdQxE2 * inv; + result.triangleBary[2] = DdE1xQ * inv; + result.triangleBary[0] = + (Real)1 - result.triangleBary[1] - result.triangleBary[2]; + result.point = line.origin + result.parameter * line.direction; + return result; + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + result.intersect = false; + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox2Circle2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox2Circle2.h new file mode 100644 index 0000000..7341dbf --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox2Circle2.h @@ -0,0 +1,96 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The find-intersection query is based on the document +// https://www.geometrictools.com/Documentation/IntersectionMovingCircleRectangle.pdf + +namespace WwiseGTE +{ + template + class TIQuery, Circle2> + { + public: + // The intersection query considers the box and circle to be solids; + // that is, the circle object includes the region inside the circular + // boundary and the box object includes the region inside the + // rectangular boundary. If the circle object and rectangle object + // overlap, the objects intersect. + struct Result + { + bool intersect; + }; + + Result operator()(OrientedBox2 const& box, Circle2 const& circle) + { + DCPQuery, OrientedBox2> pbQuery; + auto pbResult = pbQuery(circle.center, box); + Result result; + result.intersect = (pbResult.sqrDistance <= circle.radius * circle.radius); + return result; + } + }; + + template + class FIQuery, Circle2> + : + public FIQuery, Circle2> + { + public: + // See the base class for the definition of 'struct Result'. + typename FIQuery, Circle2>::Result + operator()(OrientedBox2 const& box, Vector2 const& boxVelocity, + Circle2 const& circle, Vector2 const& circleVelocity) + { + // Transform the oriented box to an axis-aligned box centered at + // the origin and transform the circle accordingly. Compute the + // velocity of the circle relative to the box. + Real const zero(0), one(1), minusOne(-1); + Vector2 cdiff = circle.center - box.center; + Vector2 vdiff = circleVelocity - boxVelocity; + Vector2 C, V; + for (int i = 0; i < 2; ++i) + { + C[i] = Dot(cdiff, box.axis[i]); + V[i] = Dot(vdiff, box.axis[i]); + } + + // Change signs on components, if necessary, to transform C to the + // first quadrant. Adjust the velocity accordingly. + Real sign[2]; + for (int i = 0; i < 2; ++i) + { + if (C[i] >= zero) + { + sign[i] = one; + } + else + { + C[i] = -C[i]; + V[i] = -V[i]; + sign[i] = minusOne; + } + } + + typename FIQuery, Circle2>::Result result = { 0, zero, { zero, zero } }; + this->DoQuery(box.extent, C, circle.radius, V, result); + + if (result.intersectionType != 0) + { + // Transform back to the original coordinate system. + result.contactPoint = box.center + + (sign[0] * result.contactPoint[0]) * box.axis[0] + + (sign[1] * result.contactPoint[1]) * box.axis[1]; + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox2Cone2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox2Cone2.h new file mode 100644 index 0000000..7d98752 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox2Cone2.h @@ -0,0 +1,105 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The queries consider the box and cone to be solids. +// +// Define V = cone.ray.origin, D = cone.ray.direction, and cs = cone.cosAngle. +// Define C = box.center, U0 = box.axis[0], U1 = box.axis[1], +// e0 = box.extent[0], and e1 = box.extent[1]. A box point is +// P = C + x*U0 + y*U1 where |x| <= e0 and |y| <= e1. Define the function +// F(P) = Dot(D, (P-V)/Length(P-V)) = F(x,y) +// = Dot(D, (x*U0 + y*U1 + (C-V))/|x*U0 + y*U1 + (C-V)| +// = (a0*x + a1*y + a2)/(x^2 + y^2 + 2*b0*x + 2*b1*y + b2)^{1/2} +// The function has an essential singularity when P = V. The box intersects +// the cone (with positive-area overlap) when at least one of the four box +// corners is strictly inside the cone. It is necessary that the numerator +// of F(P) be positive at such a corner. The (interior of the) solid cone +// is defined by the quadratic inequality +// (Dot(D,P-V))^2 > |P-V|^2*(cone.cosAngle)^2 +// This inequality is inexpensive to compute. In summary, overlap occurs +// when there is a box corner P for which +// F(P) > 0 and (Dot(D,P-V))^2 > |P-V|^2*(cone.cosAngle)^2 + +namespace WwiseGTE +{ + template + class TIQuery, Cone<2, Real>> + { + public: + struct Result + { + // The value of 'intersect' is true when there is a box point that + // is strictly inside the cone. If the box just touches the cone + // from the outside, an intersection is not reported, which + // supports the common operation of culling objects outside a + // cone. + bool intersect; + }; + + Result operator()(OrientedBox<2, Real> const& box, Cone<2, Real>& cone) + { + Result result; + + TIQuery, OrientedBox<2, Real>> rbQuery; + auto rbResult = rbQuery(cone.ray, box); + if (rbResult.intersect) + { + // The cone intersects the box. + result.intersect = true; + return result; + } + + // Define V = cone.ray.origin, D = cone.ray.direction, and + // cs = cone.cosAngle. Define C = box.center, U0 = box.axis[0], + // U1 = box.axis[1], e0 = box.extent[0], and e1 = box.extent[1]. + // A box point is P = C + x*U0 + y*U1 where |x| <= e0 and + // |y| <= e1. Define the function + // F(x,y) = Dot(D, (P-V)/Length(P-V)) + // = Dot(D, (x*U0 + y*U1 + (C-V))/|x*U0 + y*U1 + (C-V)| + // = (a0*x + a1*y + a2)/(x^2 + y^2 + 2*b0*x + 2*b1*y + b2)^{1/2} + // The function has an essential singularity when P = V. + Vector<2, Real> diff = box.center - cone.ray.origin; + Real a0 = Dot(cone.ray.direction, box.axis[0]); + Real a1 = Dot(cone.ray.direction, box.axis[1]); + Real a2 = Dot(cone.ray.direction, diff); + Real b0 = Dot(box.axis[0], diff); + Real b1 = Dot(box.axis[1], diff); + Real b2 = Dot(diff, diff); + Real csSqr = cone.cosAngle * cone.cosAngle; + + for (int i1 = 0; i1 < 2; ++i1) + { + Real sign1 = i1 * (Real)2 - (Real)1; + Real y = sign1 * box.extent[1]; + for (int i0 = 0; i0 < 2; ++i0) + { + Real sign0 = i0 * (Real)2 - (Real)1; + Real x = sign0 * box.extent[0]; + Real fNumerator = a0 * x + a1 * y + a2; + if (fNumerator > (Real)0) + { + Real dSqr = x * x + y * y + (b0 * x + b1 * y) * (Real)2 + b2; + Real nSqr = fNumerator * fNumerator; + if (nSqr > dSqr * csSqr) + { + result.intersect = true; + return result; + } + } + } + } + + result.intersect = false; + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox2OrientedBox2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox2OrientedBox2.h new file mode 100644 index 0000000..fd693ec --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox2OrientedBox2.h @@ -0,0 +1,278 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection query uses the method of separating axes. +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The set of potential separating directions includes the 2 edge normals of +// box0 and the 2 edge normals of box1. The integer 'separating' identifies +// the axis that reported separation; there may be more than one but only one +// is reported. The value is 0 when box0.axis[0] separates, 1 when +// box0.axis[1] separates, 2 when box1.axis[0] separates or 3 when +// box1.axis[1] separates. + +namespace WwiseGTE +{ + template + class TIQuery, OrientedBox2> + { + public: + struct Result + { + bool intersect; + int separating; + }; + + Result operator()(OrientedBox2 const& box0, OrientedBox2 const& box1) + { + Result result; + + // Convenience variables. + Vector2 const* A0 = &box0.axis[0]; + Vector2 const* A1 = &box1.axis[0]; + Vector2 const& E0 = box0.extent; + Vector2 const& E1 = box1.extent; + + // Compute difference of box centers, D = C1-C0. + Vector2 D = box1.center - box0.center; + + Real absA0dA1[2][2], rSum; + + // Test box0.axis[0]. + absA0dA1[0][0] = std::fabs(Dot(A0[0], A1[0])); + absA0dA1[0][1] = std::fabs(Dot(A0[0], A1[1])); + rSum = E0[0] + E1[0] * absA0dA1[0][0] + E1[1] * absA0dA1[0][1]; + if (std::fabs(Dot(A0[0], D)) > rSum) + { + result.intersect = false; + result.separating = 0; + return result; + } + + // Test axis box0.axis[1]. + absA0dA1[1][0] = std::fabs(Dot(A0[1], A1[0])); + absA0dA1[1][1] = std::fabs(Dot(A0[1], A1[1])); + rSum = E0[1] + E1[0] * absA0dA1[1][0] + E1[1] * absA0dA1[1][1]; + if (std::fabs(Dot(A0[1], D)) > rSum) + { + result.intersect = false; + result.separating = 1; + return result; + } + + // Test axis box1.axis[0]. + rSum = E1[0] + E0[0] * absA0dA1[0][0] + E0[1] * absA0dA1[1][0]; + if (std::fabs(Dot(A1[0], D)) > rSum) + { + result.intersect = false; + result.separating = 2; + return result; + } + + // Test axis box1.axis[1]. + rSum = E1[1] + E0[0] * absA0dA1[0][1] + E0[1] * absA0dA1[1][1]; + if (std::fabs(Dot(A1[1], D)) > rSum) + { + result.intersect = false; + result.separating = 3; + return result; + } + + result.intersect = true; + return result; + } + }; + + template + class FIQuery, OrientedBox2> + { + public: + struct Result + { + bool intersect; + + // If 'intersect' is true, the boxes intersect in a convex + // 'polygon'. + std::vector> polygon; + }; + + Result operator()(OrientedBox2 const& box0, OrientedBox2 const& box1) + { + Result result; + result.intersect = true; + + // Initialize the intersection polygon to box0, listing the + // vertices in counterclockwise order. + std::array, 4> vertex; + box0.GetVertices(vertex); + result.polygon.push_back(vertex[0]); // C - e0 * U0 - e1 * U1 + result.polygon.push_back(vertex[1]); // C + e0 * U0 - e1 * U1 + result.polygon.push_back(vertex[3]); // C + e0 * U0 + e1 * U1 + result.polygon.push_back(vertex[2]); // C - e0 * U0 + e1 * U1 + + // Clip the polygon using the lines defining edges of box1. The + // line normal points inside box1. The line origin is the first + // vertex of the edge when traversing box1 counterclockwise. + box1.GetVertices(vertex); + std::array, 4> normal = + { + box1.axis[1], -box1.axis[0], box1.axis[0], -box1.axis[1] + }; + + for (int i = 0; i < 4; ++i) + { + if (Outside(vertex[i], normal[i], result.polygon)) + { + // The boxes are separated. + result.intersect = false; + result.polygon.clear(); + break; + } + } + + return result; + } + + private: + // The line normals are inner pointing. The function returns true + // when the incoming polygon is outside the line, in which case the + // boxes do not intersect. If the function returns false, the + // outgoing polygon is the incoming polygon intersected with the + // closed halfspacedefined by the line. + bool Outside(Vector2 const& origin, Vector2 const& normal, + std::vector>& polygon) + { + // Determine whether the polygon vertices are outside the polygon, + // inside the polygon, or on the polygon boundary. + int const numVertices = static_cast(polygon.size()); + std::vector distance(numVertices); + int positive = 0, negative = 0, positiveIndex = -1; + for (int i = 0; i < numVertices; ++i) + { + distance[i] = Dot(normal, polygon[i] - origin); + if (distance[i] > (Real)0) + { + ++positive; + if (positiveIndex == -1) + { + positiveIndex = i; + } + } + else if (distance[i] < (Real)0) + { + ++negative; + } + } + + if (positive == 0) + { + // The polygon is strictly outside the line. + return true; + } + + if (negative == 0) + { + // The polygon is contained in the closed halfspace whose + // boundary is the line. It is fully visible and no clipping + // is necessary. + return false; + } + + // The line transversely intersects the polygon. Clip the polygon. + std::vector> clipPolygon; + Vector2 vertex; + int curr, prev; + Real t; + + if (positiveIndex > 0) + { + // Compute the first clip vertex on the line. + curr = positiveIndex; + prev = curr - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + clipPolygon.push_back(vertex); + + // Include the vertices on the positive side of line. + while (curr < numVertices && distance[curr] >(Real)0) + { + clipPolygon.push_back(polygon[curr++]); + } + + // Compute the kast clip vertex on the line. + if (curr < numVertices) + { + prev = curr - 1; + } + else + { + curr = 0; + prev = numVertices - 1; + } + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + clipPolygon.push_back(vertex); + } + else // positiveIndex is 0 + { + // Include the vertices on the positive side of line. + curr = 0; + while (curr < numVertices && distance[curr] >(Real)0) + { + clipPolygon.push_back(polygon[curr++]); + } + + // Compute the last clip vertex on the line. + prev = curr - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + clipPolygon.push_back(vertex); + + // Skip the vertices on the negative side of the line. + while (curr < numVertices && distance[curr] <= (Real)0) + { + curr++; + } + + // Compute the first clip vertex on the line. + if (curr < numVertices) + { + prev = curr - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + clipPolygon.push_back(vertex); + + // Keep the vertices on the positive side of the line. + while (curr < numVertices && distance[curr] >(Real)0) + { + clipPolygon.push_back(polygon[curr++]); + } + } + else + { + curr = 0; + prev = numVertices - 1; + t = distance[curr] / (distance[curr] - distance[prev]); + vertex = polygon[curr] + t * (polygon[prev] - polygon[curr]); + clipPolygon.push_back(vertex); + } + } + + polygon = clipPolygon; + return false; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox2Sector2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox2Sector2.h new file mode 100644 index 0000000..da08114 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox2Sector2.h @@ -0,0 +1,130 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// The OrientedBox2 object is considered to be a solid. + +namespace WwiseGTE +{ + template + class TIQuery, Sector2> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(OrientedBox2 const& box, Sector2 const& sector) + { + Result result; + + // Determine whether the vertex is inside the box. + Vector2 CmV = box.center - sector.vertex; + Vector2 P{ Dot(box.axis[0], CmV), Dot(box.axis[1], CmV) }; + if (std::fabs(P[0]) <= box.extent[0] && std::fabs(P[1]) <= box.extent[1]) + { + // The vertex is inside the box. + result.intersect = true; + return result; + } + + // Test whether the box is outside the right ray boundary of the + // sector. + Vector2 U0 + { + +sector.cosAngle * sector.direction[0] + sector.sinAngle * sector.direction[1], + -sector.sinAngle * sector.direction[0] + sector.cosAngle * sector.direction[1] + }; + Vector2 N0 = Perp(U0); + Real prjcen0 = Dot(N0, CmV); + Real radius0 = box.extent[0] * std::fabs(Dot(N0, box.axis[0])) + + box.extent[1] * std::fabs(Dot(N0, box.axis[1])); + if (prjcen0 > radius0) + { + result.intersect = false; + return result; + } + + // Test whether the box is outside the ray of the left boundary + // of the sector. + Vector2 U1 + { + +sector.cosAngle * sector.direction[0] - sector.sinAngle * sector.direction[1], + +sector.sinAngle * sector.direction[0] + sector.cosAngle * sector.direction[1] + }; + Vector2 N1 = -Perp(U1); + Real prjcen1 = Dot(N1, CmV); + Real radius1 = box.extent[0] * std::fabs(Dot(N1, box.axis[0])) + + box.extent[1] * std::fabs(Dot(N1, box.axis[1])); + if (prjcen1 > radius1) + { + result.intersect = false; + return result; + } + + // Initialize the polygon of intersection to be the box. + Vector2 e0U0 = box.extent[0] * box.axis[0]; + Vector2 e1U1 = box.extent[1] * box.axis[1]; + std::vector> polygon; + polygon.reserve(8); + polygon.push_back(box.center - e0U0 - e1U1); + polygon.push_back(box.center + e0U0 - e1U1); + polygon.push_back(box.center + e0U0 + e1U1); + polygon.push_back(box.center - e0U0 + e1U1); + + FIQuery, std::vector>> hpQuery; + typename FIQuery, std::vector>>::Result hpResult; + Halfspace<2, Real> halfspace; + + // Clip the box against the right-ray sector boundary. + if (prjcen0 >= -radius0) + { + halfspace.normal = -N0; + halfspace.constant = Dot(halfspace.normal, sector.vertex); + hpResult = hpQuery(halfspace, polygon); + polygon = std::move(hpResult.polygon); + } + + // Clip the box against the left-ray sector boundary. + if (prjcen1 >= -radius1) + { + halfspace.normal = -N1; + halfspace.constant = Dot(halfspace.normal, sector.vertex); + hpResult = hpQuery(halfspace, polygon); + polygon = std::move(hpResult.polygon); + } + + DCPQuery, Segment2> psQuery; + typename DCPQuery, Segment2>::Result psResult; + int const numVertices = static_cast(polygon.size()); + if (numVertices >= 2) + { + for (int i0 = numVertices - 1, i1 = 0; i1 < numVertices; i0 = i1++) + { + Segment2 segment(polygon[i0], polygon[i1]); + psResult = psQuery(sector.vertex, segment); + if (psResult.distance <= sector.radius) + { + result.intersect = true; + return result; + } + } + } + + result.intersect = false; + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox3Cone3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox3Cone3.h new file mode 100644 index 0000000..aa79f78 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox3Cone3.h @@ -0,0 +1,86 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// Test for intersection of a box and a cone. The cone can be infinite +// 0 <= minHeight < maxHeight = std::numeric_limits::max() +// or finite (cone frustum) +// 0 <= minHeight < maxHeight < std::numeric_limits::max(). +// The algorithm is described in +// https://www.geometrictools.com/Documentation/IntersectionBoxCone.pdf +// and reports an intersection only when th intersection set has positive +// volume. For example, let the box be outside the cone. If the box is +// below the minHeight plane at the cone vertex and just touches the cone +// vertex, no intersection is reported. If the box is above the maxHeight +// plane and just touches the disk capping the cone, either at a single +// point, a line segment of points or a polygon of points, no intersection +// is reported. + +// TODO: These queries were designed when an infinite cone was defined +// by choosing maxHeight of std::numeric_limits::max(). The Cone +// class has been redesigned not to use std::numeric_limits to allow for +// arithmetic systems that do not have representations for infinities +// (such as BSNumber and BSRational). The intersection queries need to be +// rewritten for the new class design. FOR NOW, the queries will work with +// float/double when you create a cone using the cone-frustum constructor +// Cone(ray, angle, minHeight, std::numeric_limits::max()). + +namespace WwiseGTE +{ + template + class TIQuery, Cone<3, Real>> + : + public TIQuery, Cone<3, Real>> + { + public: + struct Result + : + public TIQuery, Cone<3, Real>>::Result + { + // No additional information to compute. + }; + + Result operator()(OrientedBox<3, Real> const& box, Cone<3, Real> const& cone) + { + // Transform the cone and box so that the cone vertex is at the + // origin and the box is axis aligned. This allows us to call the + // base class operator()(...). + Vector<3, Real> diff = box.center - cone.ray.origin; + Vector<3, Real> xfrmBoxCenter + { + Dot(box.axis[0], diff), + Dot(box.axis[1], diff), + Dot(box.axis[2], diff) + }; + AlignedBox<3, Real> xfrmBox; + xfrmBox.min = xfrmBoxCenter - box.extent; + xfrmBox.max = xfrmBoxCenter + box.extent; + + Cone<3, Real> xfrmCone = cone; + for (int i = 0; i < 3; ++i) + { + xfrmCone.ray.origin[i] = (Real)0; + xfrmCone.ray.direction[i] = Dot(box.axis[i], cone.ray.direction); + } + + // Test for intersection between the aligned box and the cone. + auto bcResult = TIQuery, Cone<3, Real>>::operator()(xfrmBox, xfrmCone); + Result result; + result.intersect = bcResult.intersect; + return result; + } + }; + + // Template alias for convenience. + template + using TIOrientedBox3Cone3 = TIQuery, Cone<3, Real>>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox3Cylinder3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox3Cylinder3.h new file mode 100644 index 0000000..d0f8d9f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox3Cylinder3.h @@ -0,0 +1,47 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The query considers the cylinder and box to be solids. + +namespace WwiseGTE +{ + template + class TIQuery, Cylinder3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(OrientedBox3 const& box, Cylinder3 const& cylinder) + { + // Transform the box and cylinder so that the box is axis-aligned. + AlignedBox3 aabb(-box.extent, box.extent); + Vector3 diff = cylinder.axis.origin - box.center; + Cylinder3 transformedCylinder; + transformedCylinder.radius = cylinder.radius; + transformedCylinder.height = cylinder.height; + for (int i = 0; i < 3; ++i) + { + transformedCylinder.axis.origin[i] = Dot(box.axis[i], diff); + transformedCylinder.axis.direction[i] = Dot(box.axis[i], cylinder.axis.direction); + } + + TIQuery, Cylinder3> aabbCylinderQuery; + auto aabbCylinderResult = aabbCylinderQuery(aabb, transformedCylinder); + Result result; + result.intersect = aabbCylinderResult.intersect; + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox3Frustum3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox3Frustum3.h new file mode 100644 index 0000000..49f1f80 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox3Frustum3.h @@ -0,0 +1,364 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The test-intersection query uses the method of separating axes. +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The potential separating axes include the 3 box face normals, the 5 +// distinct frustum normals (near and far plane have the same normal), and +// cross products of normals, one from the box and one from the frustum. + +namespace WwiseGTE +{ + template + class TIQuery, Frustum3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(OrientedBox3 const& box, Frustum3 const& frustum) + { + Result result; + + // Convenience variables. + Vector3 const* axis = &box.axis[0]; + Vector3 const& extent = box.extent; + + Vector3 diff = box.center - frustum.origin; // C-E + + Real A[3]; // Dot(R,A[i]) + Real B[3]; // Dot(U,A[i]) + Real C[3]; // Dot(D,A[i]) + Real D[3]; // (Dot(R,C-E),Dot(U,C-E),Dot(D,C-E)) + Real NA[3]; // dmin*Dot(R,A[i]) + Real NB[3]; // dmin*Dot(U,A[i]) + Real NC[3]; // dmin*Dot(D,A[i]) + Real ND[3]; // dmin*(Dot(R,C-E),Dot(U,C-E),?) + Real RC[3]; // rmax*Dot(D,A[i]) + Real RD[3]; // rmax*(?,?,Dot(D,C-E)) + Real UC[3]; // umax*Dot(D,A[i]) + Real UD[3]; // umax*(?,?,Dot(D,C-E)) + Real NApRC[3]; // dmin*Dot(R,A[i]) + rmax*Dot(D,A[i]) + Real NAmRC[3]; // dmin*Dot(R,A[i]) - rmax*Dot(D,A[i]) + Real NBpUC[3]; // dmin*Dot(U,A[i]) + umax*Dot(D,A[i]) + Real NBmUC[3]; // dmin*Dot(U,A[i]) - umax*Dot(D,A[i]) + Real RBpUA[3]; // rmax*Dot(U,A[i]) + umax*Dot(R,A[i]) + Real RBmUA[3]; // rmax*Dot(U,A[i]) - umax*Dot(R,A[i]) + Real DdD, radius, p, fmin, fmax, MTwoUF, MTwoRF, tmp; + int i, j; + + // M = D + D[2] = Dot(diff, frustum.dVector); + for (i = 0; i < 3; ++i) + { + C[i] = Dot(axis[i], frustum.dVector); + } + radius = + extent[0] * std::fabs(C[0]) + + extent[1] * std::fabs(C[1]) + + extent[2] * std::fabs(C[2]); + if (D[2] + radius < frustum.dMin + || D[2] - radius > frustum.dMax) + { + result.intersect = false; + return result; + } + + // M = n*R - r*D + for (i = 0; i < 3; ++i) + { + A[i] = Dot(axis[i], frustum.rVector); + RC[i] = frustum.rBound * C[i]; + NA[i] = frustum.dMin * A[i]; + NAmRC[i] = NA[i] - RC[i]; + } + D[0] = Dot(diff, frustum.rVector); + radius = + extent[0] * std::fabs(NAmRC[0]) + + extent[1] * std::fabs(NAmRC[1]) + + extent[2] * std::fabs(NAmRC[2]); + ND[0] = frustum.dMin * D[0]; + RD[2] = frustum.rBound * D[2]; + DdD = ND[0] - RD[2]; + MTwoRF = frustum.GetMTwoRF(); + if (DdD + radius < MTwoRF || DdD > radius) + { + result.intersect = false; + return result; + } + + // M = -n*R - r*D + for (i = 0; i < 3; ++i) + { + NApRC[i] = NA[i] + RC[i]; + } + radius = + extent[0] * std::fabs(NApRC[0]) + + extent[1] * std::fabs(NApRC[1]) + + extent[2] * std::fabs(NApRC[2]); + DdD = -(ND[0] + RD[2]); + if (DdD + radius < MTwoRF || DdD > radius) + { + result.intersect = false; + return result; + } + + // M = n*U - u*D + for (i = 0; i < 3; ++i) + { + B[i] = Dot(axis[i], frustum.uVector); + UC[i] = frustum.uBound * C[i]; + NB[i] = frustum.dMin * B[i]; + NBmUC[i] = NB[i] - UC[i]; + } + D[1] = Dot(diff, frustum.uVector); + radius = + extent[0] * std::fabs(NBmUC[0]) + + extent[1] * std::fabs(NBmUC[1]) + + extent[2] * std::fabs(NBmUC[2]); + ND[1] = frustum.dMin * D[1]; + UD[2] = frustum.uBound * D[2]; + DdD = ND[1] - UD[2]; + MTwoUF = frustum.GetMTwoUF(); + if (DdD + radius < MTwoUF || DdD > radius) + { + result.intersect = false; + return result; + } + + // M = -n*U - u*D + for (i = 0; i < 3; ++i) + { + NBpUC[i] = NB[i] + UC[i]; + } + radius = + extent[0] * std::fabs(NBpUC[0]) + + extent[1] * std::fabs(NBpUC[1]) + + extent[2] * std::fabs(NBpUC[2]); + DdD = -(ND[1] + UD[2]); + if (DdD + radius < MTwoUF || DdD > radius) + { + result.intersect = false; + return result; + } + + // M = A[i] + for (i = 0; i < 3; ++i) + { + p = frustum.rBound * std::fabs(A[i]) + + frustum.uBound * std::fabs(B[i]); + NC[i] = frustum.dMin * C[i]; + fmin = NC[i] - p; + if (fmin < (Real)0) + { + fmin *= frustum.GetDRatio(); + } + fmax = NC[i] + p; + if (fmax > (Real)0) + { + fmax *= frustum.GetDRatio(); + } + DdD = A[i] * D[0] + B[i] * D[1] + C[i] * D[2]; + if (DdD + extent[i] < fmin || DdD - extent[i] > fmax) + { + result.intersect = false; + return result; + } + } + + // M = Cross(R,A[i]) + for (i = 0; i < 3; ++i) + { + p = frustum.uBound * std::fabs(C[i]); + fmin = -NB[i] - p; + if (fmin < (Real)0) + { + fmin *= frustum.GetDRatio(); + } + fmax = -NB[i] + p; + if (fmax > (Real)0) + { + fmax *= frustum.GetDRatio(); + } + DdD = C[i] * D[1] - B[i] * D[2]; + radius = + extent[0] * std::fabs(B[i] * C[0] - B[0] * C[i]) + + extent[1] * std::fabs(B[i] * C[1] - B[1] * C[i]) + + extent[2] * std::fabs(B[i] * C[2] - B[2] * C[i]); + if (DdD + radius < fmin || DdD - radius > fmax) + { + result.intersect = false; + return result; + } + } + + // M = Cross(U,A[i]) + for (i = 0; i < 3; ++i) + { + p = frustum.rBound * std::fabs(C[i]); + fmin = NA[i] - p; + if (fmin < (Real)0) + { + fmin *= frustum.GetDRatio(); + } + fmax = NA[i] + p; + if (fmax > (Real)0) + { + fmax *= frustum.GetDRatio(); + } + DdD = -C[i] * D[0] + A[i] * D[2]; + radius = + extent[0] * std::fabs(A[i] * C[0] - A[0] * C[i]) + + extent[1] * std::fabs(A[i] * C[1] - A[1] * C[i]) + + extent[2] * std::fabs(A[i] * C[2] - A[2] * C[i]); + if (DdD + radius < fmin || DdD - radius > fmax) + { + result.intersect = false; + return result; + } + } + + // M = Cross(n*D+r*R+u*U,A[i]) + for (i = 0; i < 3; ++i) + { + Real fRB = frustum.rBound * B[i]; + Real fUA = frustum.uBound * A[i]; + RBpUA[i] = fRB + fUA; + RBmUA[i] = fRB - fUA; + } + for (i = 0; i < 3; ++i) + { + p = frustum.rBound * std::fabs(NBmUC[i]) + + frustum.uBound * std::fabs(NAmRC[i]); + tmp = -frustum.dMin * RBmUA[i]; + fmin = tmp - p; + if (fmin < (Real)0) + { + fmin *= frustum.GetDRatio(); + } + fmax = tmp + p; + if (fmax > (Real)0) + { + fmax *= frustum.GetDRatio(); + } + DdD = D[0] * NBmUC[i] - D[1] * NAmRC[i] - D[2] * RBmUA[i]; + radius = (Real)0; + for (j = 0; j < 3; j++) + { + radius += extent[j] * std::fabs(A[j] * NBmUC[i] - + B[j] * NAmRC[i] - C[j] * RBmUA[i]); + } + if (DdD + radius < fmin || DdD - radius > fmax) + { + result.intersect = false; + return result; + } + } + + // M = Cross(n*D+r*R-u*U,A[i]) + for (i = 0; i < 3; ++i) + { + p = frustum.rBound * std::fabs(NBpUC[i]) + + frustum.uBound * std::fabs(NAmRC[i]); + tmp = -frustum.dMin * RBpUA[i]; + fmin = tmp - p; + if (fmin < (Real)0) + { + fmin *= frustum.GetDRatio(); + } + fmax = tmp + p; + if (fmax > (Real)0) + { + fmax *= frustum.GetDRatio(); + } + DdD = D[0] * NBpUC[i] - D[1] * NAmRC[i] - D[2] * RBpUA[i]; + radius = (Real)0; + for (j = 0; j < 3; ++j) + { + radius += extent[j] * std::fabs(A[j] * NBpUC[i] - + B[j] * NAmRC[i] - C[j] * RBpUA[i]); + } + if (DdD + radius < fmin || DdD - radius > fmax) + { + result.intersect = false; + return result; + } + } + + // M = Cross(n*D-r*R+u*U,A[i]) + for (i = 0; i < 3; ++i) + { + p = frustum.rBound * std::fabs(NBmUC[i]) + + frustum.uBound * std::fabs(NApRC[i]); + tmp = frustum.dMin * RBpUA[i]; + fmin = tmp - p; + if (fmin < (Real)0) + { + fmin *= frustum.GetDRatio(); + } + fmax = tmp + p; + if (fmax > (Real)0) + { + fmax *= frustum.GetDRatio(); + } + DdD = D[0] * NBmUC[i] - D[1] * NApRC[i] + D[2] * RBpUA[i]; + radius = (Real)0; + for (j = 0; j < 3; ++j) + { + radius += extent[j] * std::fabs(A[j] * NBmUC[i] - + B[j] * NApRC[i] + C[j] * RBpUA[i]); + } + if (DdD + radius < fmin || DdD - radius > fmax) + { + result.intersect = false; + return result; + } + } + + // M = Cross(n*D-r*R-u*U,A[i]) + for (i = 0; i < 3; ++i) + { + p = frustum.rBound * std::fabs(NBpUC[i]) + + frustum.uBound * std::fabs(NApRC[i]); + tmp = frustum.dMin * RBmUA[i]; + fmin = tmp - p; + if (fmin < (Real)0) + { + fmin *= frustum.GetDRatio(); + } + fmax = tmp + p; + if (fmax > (Real)0) + { + fmax *= frustum.GetDRatio(); + } + DdD = D[0] * NBpUC[i] - D[1] * NApRC[i] + D[2] * RBmUA[i]; + radius = (Real)0; + for (j = 0; j < 3; ++j) + { + radius += extent[j] * std::fabs(A[j] * NBpUC[i] - + B[j] * NApRC[i] + C[j] * RBmUA[i]); + } + if (DdD + radius < fmin || DdD - radius > fmax) + { + result.intersect = false; + return result; + } + } + + result.intersect = true; + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox3OrientedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox3OrientedBox3.h new file mode 100644 index 0000000..573f228 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox3OrientedBox3.h @@ -0,0 +1,325 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection query uses the method of separating axes. +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The set of potential separating directions includes the 3 face normals of +// box0, the 3 face normals of box1, and 9 directions, each of which is the +// cross product of an edge of box0 and and an edge of box1. +// +// The separating axes involving cross products of edges has numerical +// robustness problems when the two edges are nearly parallel. The cross +// product of the edges is nearly the zero vector, so normalization of the +// cross product may produce unit-length directions that are not close to the +// true direction. Such a pair of edges occurs when a box0 face normal N0 and +// a box1 face normal N1 are nearly parallel. In this case, you may skip the +// edge-edge directions, which is equivalent to projecting the boxes onto the +// plane with normal N0 and applying a 2D separating axis test. The ability +// to do so involves choosing a small nonnegative epsilon. It is used to +// determine whether two face normals, one from each box, are nearly parallel: +// |Dot(N0,N1)| >= 1 - epsilon. If the input is negative, it is clamped to +// zero. +// +// The pair of integers 'separating', say, (i0,i1), identify the axis that +// reported separation; there may be more than one but only one is +// reported. If the separating axis is a face normal N[i0] of the aligned +// box0 in dimension i0, then (i0,-1) is returned. If the axis is a face +// normal box1.Axis[i1], then (-1,i1) is returned. If the axis is a cross +// product of edges, Cross(N[i0],box1.Axis[i1]), then (i0,i1) is returned. + +namespace WwiseGTE +{ + template + class TIQuery, OrientedBox3> + { + public: + struct Result + { + // The 'epsilon' value must be nonnegative. + Result(Real inEpsilon = (Real)0) + : + epsilon(inEpsilon >= (Real)0 ? inEpsilon : (Real)0) + { + } + + bool intersect; + Real epsilon; + int separating[2]; + }; + + Result operator()(OrientedBox3 const& box0, OrientedBox3 const& box1) + { + Result result; + + // Convenience variables. + Vector3 const& C0 = box0.center; + Vector3 const* A0 = &box0.axis[0]; + Vector3 const& E0 = box0.extent; + Vector3 const& C1 = box1.center; + Vector3 const* A1 = &box1.axis[0]; + Vector3 const& E1 = box1.extent; + + Real const cutoff = (Real)1 - result.epsilon; + bool existsParallelPair = false; + + // Compute difference of box centers. + Vector3 D = C1 - C0; + + // dot01[i][j] = Dot(A0[i],A1[j]) = A1[j][i] + Real dot01[3][3]; + + // |dot01[i][j]| + Real absDot01[3][3]; + + // Dot(D, A0[i]) + Real dotDA0[3]; + + // interval radii and distance between centers + Real r0, r1, r; + + // r0 + r1 + Real r01; + + // Test for separation on the axis C0 + t*A0[0]. + for (int i = 0; i < 3; ++i) + { + dot01[0][i] = Dot(A0[0], A1[i]); + absDot01[0][i] = std::fabs(dot01[0][i]); + if (absDot01[0][i] > cutoff) + { + existsParallelPair = true; + } + } + dotDA0[0] = Dot(D, A0[0]); + r = std::fabs(dotDA0[0]); + r1 = E1[0] * absDot01[0][0] + E1[1] * absDot01[0][1] + E1[2] * absDot01[0][2]; + r01 = E0[0] + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = -1; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]. + for (int i = 0; i < 3; ++i) + { + dot01[1][i] = Dot(A0[1], A1[i]); + absDot01[1][i] = std::fabs(dot01[1][i]); + if (absDot01[1][i] > cutoff) + { + existsParallelPair = true; + } + } + dotDA0[1] = Dot(D, A0[1]); + r = std::fabs(dotDA0[1]); + r1 = E1[0] * absDot01[1][0] + E1[1] * absDot01[1][1] + E1[2] * absDot01[1][2]; + r01 = E0[1] + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = -1; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]. + for (int i = 0; i < 3; ++i) + { + dot01[2][i] = Dot(A0[2], A1[i]); + absDot01[2][i] = std::fabs(dot01[2][i]); + if (absDot01[2][i] > cutoff) + { + existsParallelPair = true; + } + } + dotDA0[2] = Dot(D, A0[2]); + r = std::fabs(dotDA0[2]); + r1 = E1[0] * absDot01[2][0] + E1[1] * absDot01[2][1] + E1[2] * absDot01[2][2]; + r01 = E0[2] + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = -1; + return result; + } + + // Test for separation on the axis C0 + t*A1[0]. + r = std::fabs(Dot(D, A1[0])); + r0 = E0[0] * absDot01[0][0] + E0[1] * absDot01[1][0] + E0[2] * absDot01[2][0]; + r01 = r0 + E1[0]; + if (r > r01) + { + result.intersect = false; + result.separating[0] = -1; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A1[1]. + r = std::fabs(Dot(D, A1[1])); + r0 = E0[0] * absDot01[0][1] + E0[1] * absDot01[1][1] + E0[2] * absDot01[2][1]; + r01 = r0 + E1[1]; + if (r > r01) + { + result.intersect = false; + result.separating[0] = -1; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A1[2]. + r = std::fabs(Dot(D, A1[2])); + r0 = E0[0] * absDot01[0][2] + E0[1] * absDot01[1][2] + E0[2] * absDot01[2][2]; + r01 = r0 + E1[2]; + if (r > r01) + { + result.intersect = false; + result.separating[0] = -1; + result.separating[1] = 2; + return result; + } + + // At least one pair of box axes was parallel, so the separation is + // effectively in 2D. The edge-edge axes do not need to be tested. + if (existsParallelPair) + { + return true; + } + + // Test for separation on the axis C0 + t*A0[0]xA1[0]. + r = std::fabs(dotDA0[2] * dot01[1][0] - dotDA0[1] * dot01[2][0]); + r0 = E0[1] * absDot01[2][0] + E0[2] * absDot01[1][0]; + r1 = E1[1] * absDot01[0][2] + E1[2] * absDot01[0][1]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A0[0]xA1[1]. + r = std::fabs(dotDA0[2] * dot01[1][1] - dotDA0[1] * dot01[2][1]); + r0 = E0[1] * absDot01[2][1] + E0[2] * absDot01[1][1]; + r1 = E1[0] * absDot01[0][2] + E1[2] * absDot01[0][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A0[0]xA1[2]. + r = std::fabs(dotDA0[2] * dot01[1][2] - dotDA0[1] * dot01[2][2]); + r0 = E0[1] * absDot01[2][2] + E0[2] * absDot01[1][2]; + r1 = E1[0] * absDot01[0][1] + E1[1] * absDot01[0][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 0; + result.separating[1] = 2; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]xA1[0]. + r = std::fabs(dotDA0[0] * dot01[2][0] - dotDA0[2] * dot01[0][0]); + r0 = E0[0] * absDot01[2][0] + E0[2] * absDot01[0][0]; + r1 = E1[1] * absDot01[1][2] + E1[2] * absDot01[1][1]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]xA1[1]. + r = std::fabs(dotDA0[0] * dot01[2][1] - dotDA0[2] * dot01[0][1]); + r0 = E0[0] * absDot01[2][1] + E0[2] * absDot01[0][1]; + r1 = E1[0] * absDot01[1][2] + E1[2] * absDot01[1][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A0[1]xA1[2]. + r = std::fabs(dotDA0[0] * dot01[2][2] - dotDA0[2] * dot01[0][2]); + r0 = E0[0] * absDot01[2][2] + E0[2] * absDot01[0][2]; + r1 = E1[0] * absDot01[1][1] + E1[1] * absDot01[1][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 1; + result.separating[1] = 2; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]xA1[0]. + r = std::fabs(dotDA0[1] * dot01[0][0] - dotDA0[0] * dot01[1][0]); + r0 = E0[0] * absDot01[1][0] + E0[1] * absDot01[0][0]; + r1 = E1[1] * absDot01[2][2] + E1[2] * absDot01[2][1]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = 0; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]xA1[1]. + r = std::fabs(dotDA0[1] * dot01[0][1] - dotDA0[0] * dot01[1][1]); + r0 = E0[0] * absDot01[1][1] + E0[1] * absDot01[0][1]; + r1 = E1[0] * absDot01[2][2] + E1[2] * absDot01[2][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = 1; + return result; + } + + // Test for separation on the axis C0 + t*A0[2]xA1[2]. + r = std::fabs(dotDA0[1] * dot01[0][2] - dotDA0[0] * dot01[1][2]); + r0 = E0[0] * absDot01[1][2] + E0[1] * absDot01[0][2]; + r1 = E1[0] * absDot01[2][1] + E1[1] * absDot01[2][0]; + r01 = r0 + r1; + if (r > r01) + { + result.intersect = false; + result.separating[0] = 2; + result.separating[1] = 2; + return result; + } + + result.intersect = true; + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox3Sphere3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox3Sphere3.h new file mode 100644 index 0000000..25a4011 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrOrientedBox3Sphere3.h @@ -0,0 +1,94 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Sphere3> + : + public TIQuery, Sphere3> + { + public: + // The intersection query considers the box and sphere to be solids. + // For example, if the sphere is strictly inside the box (does not + // touch the box faces), the objects intersect. + struct Result + : + public TIQuery, Sphere3>::Result + { + // No additional information to compute. + }; + + Result operator()(OrientedBox3 const& box, Sphere3 const& sphere) + { + DCPQuery, OrientedBox3> pbQuery; + auto pbResult = pbQuery(sphere.center, box); + Result result; + result.intersect = (pbResult.sqrDistance <= sphere.radius * sphere.radius); + return result; + } + }; + + template + class FIQuery, Sphere3> + : + public FIQuery, Sphere3> + { + public: + // Currently, only a dynamic query is supported. The static query + // must compute the intersection set of (solid) box and sphere. + struct Result + : + public FIQuery, Sphere3>::Result + { + // No additional information to compute. + }; + + Result operator()(OrientedBox3 const& box, Vector3 const& boxVelocity, + Sphere3 const& sphere, Vector3 const& sphereVelocity) + { + Result result; + result.intersectionType = 0; + result.contactTime = (Real)0; + result.contactPoint = { (Real)0, (Real)0, (Real)0 }; + + // Transform the sphere and box so that the box center becomes + // the origin and the box is axis aligned. Compute the velocity + // of the sphere relative to the box. + Vector3 temp = sphere.center - box.center; + Vector3 C + { + Dot(temp, box.axis[0]), + Dot(temp, box.axis[1]), + Dot(temp, box.axis[2]) + }; + + temp = sphereVelocity - boxVelocity; + Vector3 V + { + Dot(temp, box.axis[0]), + Dot(temp, box.axis[1]), + Dot(temp, box.axis[2]) + }; + + this->DoQuery(box.extent, C, sphere.radius, V, result); + + // Transform back to the original coordinate system. + if (result.intersectionType != 0) + { + auto& P = result.contactPoint; + P = box.center + P[0] * box.axis[0] + P[1] * box.axis[1] + P[2] * box.axis[2]; + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3Capsule3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3Capsule3.h new file mode 100644 index 0000000..0be2ac9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3Capsule3.h @@ -0,0 +1,48 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Capsule3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Plane3 const& plane, Capsule3 const& capsule) + { + Result result; + + DCPQuery, Plane3> vpQuery; + Real sdistance0 = vpQuery(capsule.segment.p[0], plane).signedDistance; + Real sdistance1 = vpQuery(capsule.segment.p[1], plane).signedDistance; + if (sdistance0 * sdistance1 <= (Real)0) + { + // A capsule segment endpoint is on the plane or the two + // endpoints are on opposite sides of the plane. + result.intersect = true; + return result; + } + + // The endpoints on same side of plane, but the endpoint spheres + // might intersect the plane. + result.intersect = + std::fabs(sdistance0) <= capsule.radius || + std::fabs(sdistance1) <= capsule.radius; + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3Circle3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3Circle3.h new file mode 100644 index 0000000..c96311f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3Circle3.h @@ -0,0 +1,151 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Circle3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Plane3 const& plane, Circle3 const& circle) + { + Result result; + + // Construct the plane of the circle. + Plane3 cPlane(circle.normal, circle.center); + + // Compute the intersection of this plane with the input plane. + FIQuery, Plane3> ppQuery; + auto ppResult = ppQuery(plane, cPlane); + if (!ppResult.intersect) + { + // Planes are parallel and nonintersecting. + result.intersect = false; + return result; + } + + if (!ppResult.isLine) + { + // Planes are the same, the circle is the common intersection + // set. + result.intersect = true; + return result; + } + + // The planes intersect in a line. Locate one or two points that + // are on the circle and line. If the line is t*D+P, the circle + // center is C, and the circle radius is r, then + // r^2 = |t*D+P-C|^2 = |D|^2*t^2 + 2*Dot(D,P-C)*t + |P-C|^2 + // This is a quadratic equation of the form + // a2*t^2 + 2*a1*t + a0 = 0. + Vector3 diff = ppResult.line.origin - circle.center; + Real a2 = Dot(ppResult.line.direction, ppResult.line.direction); + Real a1 = Dot(diff, ppResult.line.direction); + Real a0 = Dot(diff, diff) - circle.radius * circle.radius; + + // Real-valued roots imply an intersection. + Real discr = a1 * a1 - a0 * a2; + result.intersect = (discr >= (Real)0); + return result; + } + }; + + template + class FIQuery, Circle3> + { + public: + struct Result + { + bool intersect; + + // If 'intersect' is true, the intersection is either 1 or 2 points + // or the entire circle. When points, 'numIntersections' and + // 'point' are valid. When a circle, 'circle' is set to the incoming + // circle. + bool isPoints; + int numIntersections; + Vector3 point[2]; + Circle3 circle; + }; + + Result operator()(Plane3 const& plane, Circle3 const& circle) + { + Result result; + + // Construct the plane of the circle. + Plane3 cPlane(circle.normal, circle.center); + + // Compute the intersection of this plane with the input plane. + FIQuery, Plane3> ppQuery; + auto ppResult = ppQuery(plane, cPlane); + if (!ppResult.intersect) + { + // Planes are parallel and nonintersecting. + result.intersect = false; + return result; + } + + if (!ppResult.isLine) + { + // Planes are the same, the circle is the common intersection + // set. + result.intersect = true; + result.isPoints = false; + result.circle = circle; + return result; + } + + // The planes intersect in a line. Locate one or two points that + // are on the circle and line. If the line is t*D+P, the circle + // center is C, and the circle radius is r, then + // r^2 = |t*D+P-C|^2 = |D|^2*t^2 + 2*Dot(D,P-C)*t + |P-C|^2 + // This is a quadratic equation of the form + // a2*t^2 + 2*a1*t + a0 = 0. + Vector3 diff = ppResult.line.origin - circle.center; + Real a2 = Dot(ppResult.line.direction, ppResult.line.direction); + Real a1 = Dot(diff, ppResult.line.direction); + Real a0 = Dot(diff, diff) - circle.radius * circle.radius; + + Real discr = a1 * a1 - a0 * a2; + if (discr < (Real)0) + { + // No real roots, the circle does not intersect the plane. + result.intersect = false; + return result; + } + + result.isPoints = true; + + Real inv = ((Real)1) / a2; + if (discr == (Real)0) + { + // One repeated root, the circle just touches the plane. + result.numIntersections = 1; + result.point[0] = ppResult.line.origin - (a1 * inv) * ppResult.line.direction; + return result; + } + + // Two distinct, real-valued roots, the circle intersects the + // plane in two points. + Real root = std::sqrt(discr); + result.numIntersections = 2; + result.point[0] = ppResult.line.origin - ((a1 + root) * inv) * ppResult.line.direction; + result.point[1] = ppResult.line.origin - ((a1 - root) * inv) * ppResult.line.direction; + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3Cylinder3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3Cylinder3.h new file mode 100644 index 0000000..bc088b8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3Cylinder3.h @@ -0,0 +1,149 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Cylinder3> + { + public: + struct Result + { + bool intersect; + }; + + // The cylinder must have finite height. + Result operator()(Plane3 const& plane, Cylinder3 const& cylinder) + { + LogAssert(cylinder.height != std::numeric_limits::max(), + "Cylinder height must be finite."); + + Result result; + + // Compute extremes of signed distance Dot(N,X)-d for points on + // the cylinder. These are + // min = (Dot(N,C)-d) - r*sqrt(1-Dot(N,W)^2) - (h/2)*|Dot(N,W)| + // max = (Dot(N,C)-d) + r*sqrt(1-Dot(N,W)^2) + (h/2)*|Dot(N,W)| + DCPQuery, Plane3> vpQuery; + Real distance = vpQuery(cylinder.axis.origin, plane).distance; + Real absNdW = std::fabs(Dot(plane.normal, cylinder.axis.direction)); + Real root = std::sqrt(std::max((Real)1 - absNdW * absNdW, (Real)0)); + Real term = cylinder.radius * root + (Real)0.5 * cylinder.height * absNdW; + + // Intersection occurs if and only if 0 is in the interval + // [min,max]. + result.intersect = (distance <= term); + return result; + } + }; + + template + class FIQuery, Cylinder3> + { + public: + struct Result + { + bool intersect; + + // The type of intersection. + // 0: none + // 1: single line (cylinder is tangent to plane), line[0] valid + // 2: two parallel lines (plane cuts cylinder in two lines) + // 3: circle (cylinder axis perpendicular to plane) + // 4: ellipse (cylinder axis neither parallel nor perpendicular) + int type; + Line3 line[2]; + Circle3 circle; + Ellipse3 ellipse; + }; + + // The cylinder must have infinite height. + Result operator()(Plane3 const& plane, Cylinder3 const& cylinder) + { + LogAssert(cylinder.height != std::numeric_limits::max(), + "Cylinder height must be finite."); + + Result result; + + DCPQuery, Plane3> vpQuery; + Real sdistance = vpQuery(cylinder.axis.origin, plane).signedDistance; + Vector3 center = cylinder.axis.origin - sdistance * plane.normal; + Real cosTheta = Dot(cylinder.axis.direction, plane.normal); + Real absCosTheta = std::fabs(cosTheta); + + if (absCosTheta > (Real)0) + { + // The cylinder axis intersects the plane in a unique point. + result.intersect = true; + if (absCosTheta < (Real)1) + { + result.type = 4; + result.ellipse.normal = plane.normal; + result.ellipse.center = cylinder.axis.origin - + (sdistance / cosTheta) * cylinder.axis.direction; + result.ellipse.axis[0] = cylinder.axis.direction - + cosTheta * plane.normal; + Normalize(result.ellipse.axis[0]); + result.ellipse.axis[1] = UnitCross(plane.normal, + result.ellipse.axis[0]); + result.ellipse.extent[0] = cylinder.radius / absCosTheta; + result.ellipse.extent[1] = cylinder.radius; + } + else + { + result.type = 3; + result.circle.normal = plane.normal; + result.circle.center = center; + result.circle.radius = cylinder.radius; + } + } + else + { + // The cylinder is parallel to the plane. + Real distance = std::fabs(sdistance); + if (distance < cylinder.radius) + { + result.intersect = true; + result.type = 2; + + Vector3 offset = Cross(cylinder.axis.direction, plane.normal); + Real extent = std::sqrt(cylinder.radius * cylinder.radius - sdistance * sdistance); + + result.line[0].origin = center - extent * offset; + result.line[0].direction = cylinder.axis.direction; + result.line[1].origin = center + extent * offset; + result.line[1].direction = cylinder.axis.direction; + } + else if (distance == cylinder.radius) + { + result.intersect = true; + result.type = 1; + result.line[0].origin = center; + result.line[0].direction = cylinder.axis.direction; + } + else + { + result.intersect = false; + result.type = 0; + } + } + + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3Ellipsoid3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3Ellipsoid3.h new file mode 100644 index 0000000..8edd343 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3Ellipsoid3.h @@ -0,0 +1,39 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Ellipsoid3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Plane3 const& plane, Ellipsoid3 const& ellipsoid) + { + Result result; + Matrix3x3 MInverse; + ellipsoid.GetMInverse(MInverse); + Real discr = Dot(plane.normal, MInverse * plane.normal); + Real root = std::sqrt(std::max(discr, (Real)0)); + DCPQuery, Plane3> vpQuery; + Real distance = vpQuery(ellipsoid.center, plane).distance; + result.intersect = (distance <= root); + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3OrientedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3OrientedBox3.h new file mode 100644 index 0000000..08d483f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3OrientedBox3.h @@ -0,0 +1,40 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, OrientedBox3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Plane3 const& plane, OrientedBox3 const& box) + { + Result result; + + Real radius = + std::fabs(box.extent[0] * Dot(plane.normal, box.axis[0])) + + std::fabs(box.extent[1] * Dot(plane.normal, box.axis[1])) + + std::fabs(box.extent[2] * Dot(plane.normal, box.axis[2])); + + DCPQuery, Plane3> ppQuery; + auto ppResult = ppQuery(box.center, plane); + result.intersect = (ppResult.distance <= radius); + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3Plane3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3Plane3.h new file mode 100644 index 0000000..a658d7f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3Plane3.h @@ -0,0 +1,141 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Plane3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Plane3 const& plane0, Plane3 const& plane1) + { + // If Cross(N0,N1) is zero, then either planes are parallel and + // separated or the same plane. In both cases, 'false' is + // returned. Otherwise, the planes intersect. To avoid subtle + // differences in reporting between Test() and Find(), the same + // parallel test is used. Mathematically, + // |Cross(N0,N1)|^2 = Dot(N0,N0)*Dot(N1,N1)-Dot(N0,N1)^2 + // = 1 - Dot(N0,N1)^2 + // The last equality is true since planes are required to have + // unit-length normal vectors. The test |Cross(N0,N1)| = 0 is the + // same as |Dot(N0,N1)| = 1. + + Result result; + Real dot = Dot(plane0.normal, plane1.normal); + if (std::fabs(dot) < (Real)1) + { + result.intersect = true; + return result; + } + + // The planes are parallel. Check whether they are coplanar. + Real cDiff; + if (dot >= (Real)0) + { + // Normals are in same direction, need to look at c0-c1. + cDiff = plane0.constant - plane1.constant; + } + else + { + // Normals are in opposite directions, need to look at c0+c1. + cDiff = plane0.constant + plane1.constant; + } + + result.intersect = (std::fabs(cDiff) == (Real)0); + return result; + } + }; + + template + class FIQuery, Plane3> + { + public: + struct Result + { + bool intersect; + + // If 'intersect' is true, the intersection is either a line or + // the planes are the same. When a line, 'line' is valid. When + // the same plane, 'plane' is set to one of the planes. + bool isLine; + Line3 line; + Plane3 plane; + }; + + Result operator()(Plane3 const& plane0, Plane3 const& plane1) + { + // If N0 and N1 are parallel, either the planes are parallel and + // separated or the same plane. In both cases, 'false' is + // returned. Otherwise, the intersection line is + // L(t) = t*Cross(N0,N1)/|Cross(N0,N1)| + c0*N0 + c1*N1 + // for some coefficients c0 and c1 and for t any real number (the + // line parameter). Taking dot products with the normals, + // d0 = Dot(N0,L) = c0*Dot(N0,N0) + c1*Dot(N0,N1) = c0 + c1*d + // d1 = Dot(N1,L) = c0*Dot(N0,N1) + c1*Dot(N1,N1) = c0*d + c1 + // where d = Dot(N0,N1). These are two equations in two unknowns. + // The solution is + // c0 = (d0 - d*d1)/det + // c1 = (d1 - d*d0)/det + // where det = 1 - d^2. + + Result result; + + Real dot = Dot(plane0.normal, plane1.normal); + if (std::fabs(dot) >= (Real)1) + { + // The planes are parallel. Check if they are coplanar. + Real cDiff; + if (dot >= (Real)0) + { + // Normals are in same direction, need to look at c0-c1. + cDiff = plane0.constant - plane1.constant; + } + else + { + // Normals are in opposite directions, need to look at + // c0+c1. + cDiff = plane0.constant + plane1.constant; + } + + if (std::fabs(cDiff) == (Real)0) + { + // The planes are coplanar. + result.intersect = true; + result.isLine = false; + result.plane = plane0; + return result; + } + + // The planes are parallel but distinct. + result.intersect = false; + return result; + } + + Real invDet = (Real)1 / ((Real)1 - dot * dot); + Real c0 = (plane0.constant - dot * plane1.constant) * invDet; + Real c1 = (plane1.constant - dot * plane0.constant) * invDet; + result.intersect = true; + result.isLine = true; + result.line.origin = c0 * plane0.normal + c1 * plane1.normal; + result.line.direction = UnitCross(plane0.normal, plane1.normal); + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3Sphere3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3Sphere3.h new file mode 100644 index 0000000..1d995fa --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3Sphere3.h @@ -0,0 +1,89 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Sphere3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Plane3 const& plane, Sphere3 const& sphere) + { + Result result; + DCPQuery, Plane3> ppQuery; + auto ppResult = ppQuery(sphere.center, plane); + result.intersect = (ppResult.distance <= sphere.radius); + return result; + } + }; + + template + class FIQuery, Sphere3> + { + public: + struct Result + { + bool intersect; + + // If 'intersect' is true, the intersection is either a point or a + // circle. When 'isCircle' is true, 'circle' is valid. When + // 'isCircle' is false, 'point' is valid. + bool isCircle; + Circle3 circle; + Vector3 point; + }; + + Result operator()(Plane3 const& plane, Sphere3 const& sphere) + { + Result result; + DCPQuery, Plane3> ppQuery; + auto ppResult = ppQuery(sphere.center, plane); + if (ppResult.distance < sphere.radius) + { + result.intersect = true; + result.isCircle = true; + result.circle.center = sphere.center - ppResult.signedDistance * plane.normal; + result.circle.normal = plane.normal; + + // The sum and diff are both positive numbers. + Real sum = sphere.radius + ppResult.distance; + Real dif = sphere.radius - ppResult.distance; + + // arg = sqr(sphere.radius) - sqr(ppResult.distance) + Real arg = sum * dif; + + result.circle.radius = std::sqrt(arg); + return result; + } + else if (ppResult.distance == sphere.radius) + { + result.intersect = true; + result.isCircle = false; + result.point = sphere.center - ppResult.signedDistance * plane.normal; + return result; + } + else + { + result.intersect = false; + return result; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3Triangle3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3Triangle3.h new file mode 100644 index 0000000..fa7aa31 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrPlane3Triangle3.h @@ -0,0 +1,273 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Triangle3> + { + public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (plane and triangle + // intersect at a single point [vertex]), 2 (plane and triangle + // intersect in a segment), or 3 (triangle is in the plane). + // When the number is 2, the segment is either interior to the + // triangle or is an edge of the triangle, the distinction stored + // in 'isInterior'. + int numIntersections; + bool isInterior; + }; + + Result operator()(Plane3 const& plane, Triangle3 const& triangle) + { + Result result; + + // Determine on which side of the plane the vertices lie. The + // table of possibilities is listed next with n = numNegative, + // p = numPositive, and z = numZero. + // + // n p z intersection + // ------------------------------------ + // 0 3 0 none + // 0 2 1 vertex + // 0 1 2 edge + // 0 0 3 triangle in the plane + // 1 2 0 segment (2 edges clipped) + // 1 1 1 segment (1 edge clipped) + // 1 0 2 edge + // 2 1 0 segment (2 edges clipped) + // 2 0 1 vertex + // 3 0 0 none + + Real s[3]; + int numPositive = 0, numNegative = 0, numZero = 0; + for (int i = 0; i < 3; ++i) + { + s[i] = Dot(plane.normal, triangle.v[i]) - plane.constant; + if (s[i] > (Real)0) + { + ++numPositive; + } + else if (s[i] < (Real)0) + { + ++numNegative; + } + else + { + ++numZero; + } + } + + if (numZero == 0 && numPositive > 0 && numNegative > 0) + { + result.intersect = true; + result.numIntersections = 2; + result.isInterior = true; + return result; + } + + if (numZero == 1) + { + result.intersect = true; + for (int i = 0; i < 3; ++i) + { + if (s[i] == (Real)0) + { + if (numPositive == 2 || numNegative == 2) + { + result.numIntersections = 1; + } + else + { + result.numIntersections = 2; + result.isInterior = true; + } + break; + } + } + return result; + } + + if (numZero == 2) + { + result.intersect = true; + result.numIntersections = 2; + result.isInterior = false; + return result; + } + + if (numZero == 3) + { + result.intersect = true; + result.numIntersections = 3; + } + else + { + result.intersect = false; + result.numIntersections = 0; + + } + return result; + } + }; + + template + class FIQuery, Triangle3> + { + public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (plane and triangle + // intersect at a single point [vertex]), 2 (plane and triangle + // intersect in a segment), or 3 (triangle is in the plane). + // When the number is 2, the segment is either interior to the + // triangle or is an edge of the triangle, the distinction stored + // in 'isInterior'. + int numIntersections; + bool isInterior; + Vector3 point[3]; + }; + + Result operator()(Plane3 const& plane, Triangle3 const& triangle) + { + Result result; + + // Determine on which side of the plane the vertices lie. The + // table of possibilities is listed next with n = numNegative, + // p = numPositive, and z = numZero. + // + // n p z intersection + // ------------------------------------ + // 0 3 0 none + // 0 2 1 vertex + // 0 1 2 edge + // 0 0 3 triangle in the plane + // 1 2 0 segment (2 edges clipped) + // 1 1 1 segment (1 edge clipped) + // 1 0 2 edge + // 2 1 0 segment (2 edges clipped) + // 2 0 1 vertex + // 3 0 0 none + + Real s[3]; + int numPositive = 0, numNegative = 0, numZero = 0; + for (int i = 0; i < 3; ++i) + { + s[i] = Dot(plane.normal, triangle.v[i]) - plane.constant; + if (s[i] > (Real)0) + { + ++numPositive; + } + else if (s[i] < (Real)0) + { + ++numNegative; + } + else + { + ++numZero; + } + } + + if (numZero == 0 && numPositive > 0 && numNegative > 0) + { + result.intersect = true; + result.numIntersections = 2; + result.isInterior = true; + Real sign = (Real)3 - numPositive * (Real)2; + for (int i0 = 0; i0 < 3; ++i0) + { + if (sign * s[i0] > (Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + Real t1 = s[i1] / (s[i1] - s[i0]); + Real t2 = s[i2] / (s[i2] - s[i0]); + result.point[0] = triangle.v[i1] + t1 * + (triangle.v[i0] - triangle.v[i1]); + result.point[1] = triangle.v[i2] + t2 * + (triangle.v[i0] - triangle.v[i2]); + break; + } + } + return result; + } + + if (numZero == 1) + { + result.intersect = true; + for (int i0 = 0; i0 < 3; ++i0) + { + if (s[i0] == (Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + result.point[0] = triangle.v[i0]; + if (numPositive == 2 || numNegative == 2) + { + result.numIntersections = 1; + } + else + { + result.numIntersections = 2; + result.isInterior = true; + Real t = s[i1] / (s[i1] - s[i2]); + result.point[1] = triangle.v[i1] + t * + (triangle.v[i2] - triangle.v[i1]); + } + break; + } + } + return result; + } + + if (numZero == 2) + { + result.intersect = true; + result.numIntersections = 2; + result.isInterior = false; + for (int i0 = 0; i0 < 3; ++i0) + { + if (s[i0] != (Real)0) + { + int i1 = (i0 + 1) % 3, i2 = (i0 + 2) % 3; + result.point[0] = triangle.v[i1]; + result.point[1] = triangle.v[i2]; + break; + } + } + return result; + } + + if (numZero == 3) + { + result.intersect = true; + result.numIntersections = 3; + result.point[0] = triangle.v[0]; + result.point[1] = triangle.v[1]; + result.point[2] = triangle.v[2]; + } + else + { + result.intersect = false; + result.numIntersections = 0; + + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay2AlignedBox2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay2AlignedBox2.h new file mode 100644 index 0000000..2869f0d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay2AlignedBox2.h @@ -0,0 +1,127 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection queries use the method of separating axes. +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The find-intersection queries use parametric clipping against the four +// edges of the box. + +namespace WwiseGTE +{ + template + class TIQuery, AlignedBox2> + : + public TIQuery, AlignedBox2> + { + public: + struct Result + : + public TIQuery, AlignedBox2>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray2 const& ray, AlignedBox2 const& box) + { + // Get the centered form of the aligned box. The axes are + // implicitly Axis[d] = Vector2::Unit(d). + Vector2 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the ray to the aligned-box coordinate system. + Vector2 rayOrigin = ray.origin - boxCenter; + + Result result; + DoQuery(rayOrigin, ray.direction, boxExtent, result); + return result; + } + + protected: + void DoQuery(Vector2 const& rayOrigin, + Vector2 const& rayDirection, Vector2 const& boxExtent, + Result& result) + { + for (int i = 0; i < 2; ++i) + { + if (std::fabs(rayOrigin[i]) > boxExtent[i] + && rayOrigin[i] * rayDirection[i] >= (Real)0) + { + result.intersect = false; + return; + } + } + + TIQuery, AlignedBox2>::DoQuery(rayOrigin, + rayDirection, boxExtent, result); + } + }; + + template + class FIQuery, AlignedBox2> + : + public FIQuery, AlignedBox2> + { + public: + struct Result + : + public FIQuery, AlignedBox2>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray2 const& ray, AlignedBox2 const& box) + { + // Get the centered form of the aligned box. The axes are + // implicitly Axis[d] = Vector2::Unit(d). + Vector2 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the ray to the aligned-box coordinate system. + Vector2 rayOrigin = ray.origin - boxCenter; + + Result result; + DoQuery(rayOrigin, ray.direction, boxExtent, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; + } + + protected: + void DoQuery(Vector2 const& rayOrigin, + Vector2 const& rayDirection, Vector2 const& boxExtent, + Result& result) + { + FIQuery, AlignedBox2>::DoQuery(rayOrigin, + rayDirection, boxExtent, result); + + if (result.intersect) + { + // The line containing the ray intersects the box; the + // t-interval is [t0,t1]. The ray intersects the box as long + // as [t0,t1] overlaps the ray t-interval [0,+infinity). + std::array rayInterval = + { (Real)0, std::numeric_limits::max() }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, rayInterval); + result.intersect = iiResult.intersect; + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay2Arc2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay2Arc2.h new file mode 100644 index 0000000..f6ec1b7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay2Arc2.h @@ -0,0 +1,80 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The queries consider the arc to be a 1-dimensional object. + +namespace WwiseGTE +{ + template + class TIQuery, Arc2> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray2 const& ray, Arc2 const& arc) + { + Result result; + FIQuery, Arc2> raQuery; + auto raResult = raQuery(ray, arc); + result.intersect = raResult.intersect; + return result; + } + }; + + template + class FIQuery, Arc2> + { + public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Ray2 const& ray, Arc2 const& arc) + { + Result result; + + FIQuery, Circle2> rcQuery; + Circle2 circle(arc.center, arc.radius); + auto rcResult = rcQuery(ray, circle); + if (rcResult.intersect) + { + // Test whether ray-circle intersections are on the arc. + result.numIntersections = 0; + for (int i = 0; i < rcResult.numIntersections; ++i) + { + if (arc.Contains(rcResult.point[i])) + { + result.intersect = true; + result.parameter[result.numIntersections] + = rcResult.parameter[i]; + result.point[result.numIntersections++] + = rcResult.point[i]; + } + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay2Circle2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay2Circle2.h new file mode 100644 index 0000000..4b155f1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay2Circle2.h @@ -0,0 +1,82 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The queries consider the circle to be a solid (disk). + +namespace WwiseGTE +{ + template + class TIQuery, Circle2> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray2 const& ray, Circle2 const& circle) + { + Result result; + FIQuery, Circle2> rcQuery; + result.intersect = rcQuery(ray, circle).intersect; + return result; + } + }; + + template + class FIQuery, Circle2> + : + public FIQuery, Circle2> + { + public: + struct Result + : + public FIQuery, Circle2>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray2 const& ray, Circle2 const& circle) + { + Result result; + DoQuery(ray.origin, ray.direction, circle, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; + } + + protected: + void DoQuery(Vector2 const& rayOrigin, + Vector2 const& rayDirection, Circle2 const& circle, + Result& result) + { + FIQuery, Circle2>::DoQuery(rayOrigin, + rayDirection, circle, result); + + if (result.intersect) + { + // The line containing the ray intersects the disk; the + // t-interval is [t0,t1]. The ray intersects the disk as long + // as [t0,t1] overlaps the ray t-interval [0,+infinity). + std::array rayInterval = { (Real)0, std::numeric_limits::max() }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, rayInterval); + result.intersect = iiResult.intersect; + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay2OrientedBox2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay2OrientedBox2.h new file mode 100644 index 0000000..d0772cb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay2OrientedBox2.h @@ -0,0 +1,93 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection queries use the method of separating axes. +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The find-intersection queries use parametric clipping against the four +// edges of the box. + +namespace WwiseGTE +{ + template + class TIQuery, OrientedBox2> + : + public TIQuery, AlignedBox2> + { + public: + struct Result + : + public TIQuery, AlignedBox2>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray2 const& ray, OrientedBox2 const& box) + { + // Transform the ray to the oriented-box coordinate system. + Vector2 diff = ray.origin - box.center; + Vector2 rayOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]) + }; + Vector2 rayDirection = Vector2 + { + Dot(ray.direction, box.axis[0]), + Dot(ray.direction, box.axis[1]) + }; + + Result result; + this->DoQuery(rayOrigin, rayDirection, box.extent, result); + return result; + } + }; + + template + class FIQuery, OrientedBox2> + : + public FIQuery, AlignedBox2> + { + public: + struct Result + : + public FIQuery, AlignedBox2>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray2 const& ray, OrientedBox2 const& box) + { + // Transform the ray to the oriented-box coordinate system. + Vector2 diff = ray.origin - box.center; + Vector2 rayOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]) + }; + Vector2 rayDirection = Vector2 + { + Dot(ray.direction, box.axis[0]), + Dot(ray.direction, box.axis[1]) + }; + + Result result; + this->DoQuery(rayOrigin, rayDirection, box.extent, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay2Ray2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay2Ray2.h new file mode 100644 index 0000000..481dcb3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay2Ray2.h @@ -0,0 +1,221 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Ray2> + { + public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (rays intersect in a + // single point), 2 (rays are collinear and intersect in a + // segment; ray directions are opposite of each other), or + // std::numeric_limits::max() (intersection is a ray; ray + // directions are the same). + int numIntersections; + }; + + Result operator()(Ray2 const& ray0, Ray2 const& ray1) + { + Result result; + FIQuery, Line2> llQuery; + Line2 line0(ray0.origin, ray0.direction); + Line2 line1(ray1.origin, ray1.direction); + auto llResult = llQuery(line0, line1); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the rays. + if (llResult.line0Parameter[0] >= (Real)0 + && llResult.line1Parameter[0] >= (Real)0) + { + result.intersect = true; + result.numIntersections = 1; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + if (Dot(ray0.direction, ray1.direction) > (Real)0) + { + // The rays are collinear and in the same direction, so + // they must overlap. + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + } + else + { + // The rays are collinear but in opposite directions. + // Test whether they overlap. Ray0 has interval + // [0,+infinity) and ray1 has interval (-infinity,t] + // relative to ray0.direction. + Vector2 diff = ray1.origin - ray0.origin; + Real t = Dot(ray0.direction, diff); + if (t > (Real)0) + { + result.intersect = true; + result.numIntersections = 2; + } + else if (t < (Real)0) + { + result.intersect = false; + result.numIntersections = 0; + } + else // t == 0 + { + result.intersect = true; + result.numIntersections = 1; + } + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + + return result; + } + }; + + template + class FIQuery, Ray2> + { + public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (rays intersect in a + // single point), 2 (rays are collinear and intersect in a + // segment; ray directions are opposite of each other), or + // std::numeric_limits::max() (intersection is a ray; ray + // directions are the same). + int numIntersections; + + // If numIntersections is 1, the intersection is + // point[0] = ray0.origin + ray0Parameter[0] * ray0.direction + // = ray1.origin + ray1Parameter[0] * ray1.direction + // If numIntersections is 2, the segment of intersection is formed + // by the ray origins, + // ray0Parameter[0] = ray1Parameter[0] = 0 + // point[0] = ray0.origin + // = ray1.origin + ray1Parameter[1] * ray1.direction + // point[1] = ray1.origin + // = ray0.origin + ray0Parameter[1] * ray0.direction + // where ray0Parameter[1] >= 0 and ray1Parameter[1] >= 0. + // If numIntersections is maxInt, let + // ray1.origin = ray0.origin + t * ray0.direction + // then + // ray0Parameter[] = { max(t,0), +maxReal } + // ray1Parameter[] = { -min(t,0), +maxReal } + // point[0] = ray0.origin + ray0Parameter[0] * ray0.direction + Real ray0Parameter[2], ray1Parameter[2]; + Vector2 point[2]; + }; + + Result operator()(Ray2 const& ray0, Ray2 const& ray1) + { + Result result; + FIQuery, Line2> llQuery; + Line2 line0(ray0.origin, ray0.direction); + Line2 line1(ray1.origin, ray1.direction); + auto llResult = llQuery(line0, line1); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the rays. + if (llResult.line0Parameter[0] >= (Real)0 + && llResult.line1Parameter[0] >= (Real)0) + { + result.intersect = true; + result.numIntersections = 1; + result.ray0Parameter[0] = llResult.line0Parameter[0]; + result.ray1Parameter[0] = llResult.line1Parameter[0]; + result.point[0] = llResult.point; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + // Compute t for which ray1.origin = + // ray0.origin + t*ray0.direction. + Real maxReal = std::numeric_limits::max(); + Vector2 diff = ray1.origin - ray0.origin; + Real t = Dot(ray0.direction, diff); + if (Dot(ray0.direction, ray1.direction) > (Real)0) + { + // The rays are collinear and in the same direction, so + // they must overlap. + result.intersect = true; + result.numIntersections = std::numeric_limits::max(); + if (t >= (Real)0) + { + result.ray0Parameter[0] = t; + result.ray0Parameter[1] = maxReal; + result.ray1Parameter[0] = (Real)0; + result.ray1Parameter[1] = maxReal; + result.point[0] = ray1.origin; + } + else + { + result.ray0Parameter[0] = (Real)0; + result.ray0Parameter[1] = maxReal; + result.ray1Parameter[0] = -t; + result.ray1Parameter[1] = maxReal; + result.point[0] = ray0.origin; + } + } + else + { + // The rays are collinear but in opposite directions. + // Test whether they overlap. Ray0 has interval + // [0,+infinity) and ray1 has interval (-infinity,t1] + // relative to ray0.direction. + if (t >= (Real)0) + { + result.intersect = true; + result.numIntersections = 2; + result.ray0Parameter[0] = (Real)0; + result.ray0Parameter[1] = t; + result.ray1Parameter[0] = (Real)0; + result.ray1Parameter[1] = t; + result.point[0] = ray0.origin; + result.point[1] = ray1.origin; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay2Segment2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay2Segment2.h new file mode 100644 index 0000000..63955c7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay2Segment2.h @@ -0,0 +1,191 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Segment2> + { + public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (ray and segment intersect + // in a single point), or 2 (ray and segment are collinear and + // intersect in a segment). + int numIntersections; + }; + + Result operator()(Ray2 const& ray, Segment2 const& segment) + { + Result result; + Vector2 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + FIQuery, Line2> llQuery; + Line2 line0(ray.origin, ray.direction); + Line2 line1(segOrigin, segDirection); + auto llResult = llQuery(line0, line1); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the ray and + // segment. + if (llResult.line0Parameter[0] >= (Real)0 + && std::fabs(llResult.line1Parameter[0]) <= segExtent) + { + result.intersect = true; + result.numIntersections = 1; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + // Compute the location of the right-most point of the segment + // relative to the ray direction. + Vector2 diff = segOrigin - ray.origin; + Real t = Dot(ray.direction, diff) + segExtent; + if (t > (Real)0) + { + result.intersect = true; + result.numIntersections = 2; + } + else if (t < (Real)0) + { + result.intersect = false; + result.numIntersections = 0; + } + else // t == 0 + { + result.intersect = true; + result.numIntersections = 1; + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + + return result; + } + }; + + template + class FIQuery, Segment2> + { + public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (ray and segment intersect + // in a single point), or 2 (ray and segment are collinear and + // intersect in a segment). + int numIntersections; + + // If numIntersections is 1, the intersection is + // point[0] = ray.origin + rayParameter[0] * ray.direction + // = segment.center + segmentParameter[0] * segment.direction + // If numIntersections is 2, the endpoints of the segment of + // intersection are + // point[i] = ray.origin + rayParameter[i] * ray.direction + // = segment.center + segmentParameter[i] * segment.direction + // with rayParameter[0] <= rayParameter[1] and + // segmentParameter[0] <= segmentParameter[1]. + Real rayParameter[2], segmentParameter[2]; + Vector2 point[2]; + }; + + Result operator()(Ray2 const& ray, Segment2 const& segment) + { + Result result; + Vector2 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + FIQuery, Line2> llQuery; + Line2 line0(ray.origin, ray.direction); + Line2 line1(segOrigin, segDirection); + auto llResult = llQuery(line0, line1); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the ray and + // segment. + if (llResult.line0Parameter[0] >= (Real)0 + && std::fabs(llResult.line1Parameter[0]) <= segExtent) + { + result.intersect = true; + result.numIntersections = 1; + result.rayParameter[0] = llResult.line0Parameter[0]; + result.segmentParameter[0] = llResult.line1Parameter[0]; + result.point[0] = llResult.point; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + // Compute t for which segment.origin = + // ray.origin + t*ray.direction. + Vector2 diff = segOrigin - ray.origin; + Real t = Dot(ray.direction, diff); + + // Get the ray interval. + std::array interval0 = + { + (Real)0, std::numeric_limits::max() + }; + + // Compute the location of the segment endpoints relative to + // the ray. + std::array interval1 = { t - segExtent, t + segExtent }; + + // Compute the intersection of [0,+infinity) and [tmin,tmax]. + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(interval0, interval1); + if (iiResult.intersect) + { + result.intersect = true; + result.numIntersections = iiResult.numIntersections; + for (int i = 0; i < iiResult.numIntersections; ++i) + { + result.rayParameter[i] = iiResult.overlap[i]; + result.segmentParameter[i] = iiResult.overlap[i] - t; + result.point[i] = ray.origin + result.rayParameter[i] * ray.direction; + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay2Triangle2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay2Triangle2.h new file mode 100644 index 0000000..5679d38 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay2Triangle2.h @@ -0,0 +1,80 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The queries consider the triangle to be a solid. + +namespace WwiseGTE +{ + template + class TIQuery, Triangle2> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray2 const& ray, Triangle2 const& triangle) + { + Result result; + FIQuery, Triangle2> rtQuery; + result.intersect = rtQuery(ray, triangle).intersect; + return result; + } + }; + + template + class FIQuery, Triangle2> + : + public FIQuery, Triangle2> + { + public: + struct Result + : + public FIQuery, Triangle2>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray2 const& ray, Triangle2 const& triangle) + { + Result result; + DoQuery(ray.origin, ray.direction, triangle, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; + } + + protected: + void DoQuery(Vector2 const& rayOrigin, + Vector2 const& rayDirection, Triangle2 const& triangle, + Result& result) + { + FIQuery, Triangle2>::DoQuery(rayOrigin, + rayDirection, triangle, result); + + if (result.intersect) + { + // The line containing the ray intersects the disk; the + // t-interval is [t0,t1]. The ray intersects the disk as long + // as [t0,t1] overlaps the ray t-interval [0,+infinity). + std::array rayInterval = + { (Real)0, std::numeric_limits::max() }; + FIQuery, std::array> iiQuery; + result.parameter = iiQuery(result.parameter, rayInterval).overlap; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3AlignedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3AlignedBox3.h new file mode 100644 index 0000000..95be349 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3AlignedBox3.h @@ -0,0 +1,125 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The test-intersection queries use the method of separating axes. +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The find-intersection queries use parametric clipping against the six +// faces of the box. The find-intersection queries use Liang-Barsky +// clipping. The queries consider the box to be a solid. The algorithms +// are described in +// https://www.geometrictools.com/Documentation/IntersectionLineBox.pdf + +namespace WwiseGTE +{ + template + class TIQuery, AlignedBox3> + : + public TIQuery, AlignedBox3> + { + public: + struct Result + : + public TIQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, AlignedBox3 const& box) + { + // Get the centered form of the aligned box. The axes are + // implicitly Axis[d] = Vector3::Unit(d). + Vector3 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the ray to the aligned-box coordinate system. + Vector3 rayOrigin = ray.origin - boxCenter; + + Result result; + DoQuery(rayOrigin, ray.direction, boxExtent, result); + return result; + } + + protected: + void DoQuery(Vector3 const& rayOrigin, Vector3 const& rayDirection, + Vector3 const& boxExtent, Result& result) + { + for (int i = 0; i < 3; ++i) + { + if (std::fabs(rayOrigin[i]) > boxExtent[i] && rayOrigin[i] * rayDirection[i] >= (Real)0) + { + result.intersect = false; + return; + } + } + + TIQuery, AlignedBox3>::DoQuery(rayOrigin, rayDirection, boxExtent, result); + } + }; + + template + class FIQuery, AlignedBox3> + : + public FIQuery, AlignedBox3> + { + public: + struct Result + : + public FIQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, AlignedBox3 const& box) + { + // Get the centered form of the aligned box. The axes are + // implicitly Axis[d] = Vector3::Unit(d). + Vector3 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the ray to the aligned-box coordinate system. + Vector3 rayOrigin = ray.origin - boxCenter; + + Result result; + DoQuery(rayOrigin, ray.direction, boxExtent, result); + for (int i = 0; i < result.numPoints; ++i) + { + result.point[i] = ray.origin + result.lineParameter[i] * ray.direction; + } + return result; + } + + protected: + void DoQuery(Vector3 const& rayOrigin, Vector3 const& rayDirection, + Vector3 const& boxExtent, Result& result) + { + FIQuery, AlignedBox3>::DoQuery(rayOrigin, rayDirection, boxExtent, result); + if (result.intersect) + { + // The line containing the ray intersects the box; the + // t-interval is [t0,t1]. The ray intersects the box as long + // as [t0,t1] overlaps the ray t-interval (0,+infinity). + if (result.lineParameter[1] >= (Real)0) + { + if (result.lineParameter[0] < (Real)0) + { + result.lineParameter[0] = (Real)0; + } + } + else + { + result.intersect = false; + result.numPoints = 0; + } + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3Capsule3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3Capsule3.h new file mode 100644 index 0000000..3f7187a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3Capsule3.h @@ -0,0 +1,92 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The queries consider the capsule to be a solid. +// +// The test-intersection queries are based on distance computations. + +namespace WwiseGTE +{ + template + class TIQuery, Capsule3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray3 const& ray, Capsule3 const& capsule) + { + Result result; + DCPQuery, Segment3> rsQuery; + auto rsResult = rsQuery(ray, capsule.segment); + result.intersect = (rsResult.distance <= capsule.radius); + return result; + } + }; + + template + class FIQuery, Capsule3> + : + public FIQuery, Capsule3> + { + public: + struct Result + : + public FIQuery, Capsule3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, Capsule3 const& capsule) + { + Result result; + DoQuery(ray.origin, ray.direction, capsule, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; + } + + protected: + void DoQuery(Vector3 const& rayOrigin, + Vector3 const& rayDirection, Capsule3 const& capsule, + Result& result) + { + FIQuery, Capsule3>::DoQuery(rayOrigin, + rayDirection, capsule, result); + + if (result.intersect) + { + // The line containing the ray intersects the capsule; the + // t-interval is [t0,t1]. The ray intersects the capsule as + // long as [t0,t1] overlaps the ray t-interval [0,+infinity). + std::array rayInterval = { (Real)0, std::numeric_limits::max() }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, rayInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3Cone3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3Cone3.h new file mode 100644 index 0000000..ee3c2b7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3Cone3.h @@ -0,0 +1,107 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The queries consider the cone to be single sided and solid. The +// cone height range is [hmin,hmax]. The cone can be infinite where +// hmin = 0 and hmax = +infinity, infinite truncated where hmin > 0 +// and hmax = +infinity, finite where hmin = 0 and hmax < +infinity, +// or a cone frustum where hmin > 0 and hmax < +infinity. The +// algorithm details are found in +// https://www.geometrictools.com/Documentation/IntersectionLineCone.pdf + +namespace WwiseGTE +{ + template + class FIQuery, Cone3> + : + public FIQuery, Cone3> + { + public: + struct Result + : + public FIQuery, Cone3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, Cone3 const& cone) + { + // Execute the line-cone query. + Result result; + this->DoQuery(ray.origin, ray.direction, cone, result); + + // Adjust the t-interval depending on whether the line-cone + // t-interval overlaps the ray interval [0,+infinity). The block + // numbers are a continuation of those in IntrLine3Cone3.h. + if (result.type != Result::isEmpty) + { + using QFN1 = typename FIQuery, Cone3>::QFN1; + QFN1 zero(0, 0, result.t[0].d); + + if (result.type == Result::isPoint) + { + if (result.t[0] < zero) + { + // Block 12. + this->SetEmpty(result); + } + // else: Block 13. + } + else if (result.type == Result::isSegment) + { + if (result.t[1] > zero) + { + // Block 14. + this->SetSegment(std::max(result.t[0], zero), result.t[1], result); + } + else if (result.t[1] < zero) + { + // Block 15. + this->SetEmpty(result); + } + else // result.t[1] == zero + { + // Block 16. + this->SetPoint(zero, result); + } + } + else if (result.type == Result::isRayPositive) + { + // Block 17. + this->SetRayPositive(std::max(result.t[0], zero), result); + } + else // result.type == Result::isRayNegative + { + if (result.t[1] > zero) + { + // Block 18. + this->SetSegment(zero, result.t[1], result); + } + else if (result.t[1] < zero) + { + // Block 19. + this->SetEmpty(result); + } + else // result.t[1] == zero + { + // Block 20. + this->SetPoint(zero, result); + } + } + } + + result.ComputePoints(ray.origin, ray.direction); + result.intersect = (result.type != Result::isEmpty); + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3Cylinder3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3Cylinder3.h new file mode 100644 index 0000000..0ef0138 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3Cylinder3.h @@ -0,0 +1,71 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The queries consider the cylinder to be a solid. + +namespace WwiseGTE +{ + template + class FIQuery, Cylinder3> + : + public FIQuery, Cylinder3> + { + public: + struct Result + : + public FIQuery, Cylinder3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, Cylinder3 const& cylinder) + { + Result result; + DoQuery(ray.origin, ray.direction, cylinder, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; + } + + protected: + void DoQuery(Vector3 const& rayOrigin, + Vector3 const& rayDirection, Cylinder3 const& cylinder, + Result& result) + { + FIQuery, Cylinder3>::DoQuery(rayOrigin, + rayDirection, cylinder, result); + + if (result.intersect) + { + // The line containing the ray intersects the cylinder; the + // t-interval is [t0,t1]. The ray intersects the cylinder as + // long as [t0,t1] overlaps the ray t-interval [0,+infinity). + std::array rayInterval = { (Real)0, std::numeric_limits::max() }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, rayInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3Ellipsoid3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3Ellipsoid3.h new file mode 100644 index 0000000..ae59f0a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3Ellipsoid3.h @@ -0,0 +1,129 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +// The queries consider the ellipsoid to be a solid. + +namespace WwiseGTE +{ + template + class TIQuery, Ellipsoid3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray3 const& ray, Ellipsoid3 const& ellipsoid) + { + // The ellipsoid is (X-K)^T*M*(X-K)-1 = 0 and the line is + // X = P+t*D. Substitute the line equation into the ellipsoid + // equation to obtain a quadratic equation + // Q(t) = a2*t^2 + 2*a1*t + a0 = 0 + // where a2 = D^T*M*D, a1 = D^T*M*(P-K) and + // a0 = (P-K)^T*M*(P-K)-1. + Result result; + + Matrix3x3 M; + ellipsoid.GetM(M); + + Vector3 diff = ray.origin - ellipsoid.center; + Vector3 matDir = M * ray.direction; + Vector3 matDiff = M * diff; + Real a2 = Dot(ray.direction, matDir); + Real a1 = Dot(ray.direction, matDiff); + Real a0 = Dot(diff, matDiff) - (Real)1; + + Real discr = a1 * a1 - a0 * a2; + if (discr >= (Real)0) + { + // Test whether ray origin is inside ellipsoid. + if (a0 <= (Real)0) + { + result.intersect = true; + } + else + { + // At this point, Q(0) = a0 > 0 and Q(t) has real roots. + // It is also the case that a2 > 0, since M is positive + // definite, implying that D^T*M*D > 0 for any nonzero + // vector D. Thus, an intersection occurs only when + // Q'(0) < 0. + result.intersect = (a1 < (Real)0); + } + } + else + { + // No intersection if Q(t) has no real roots. + result.intersect = false; + } + + return result; + } + }; + + template + class FIQuery, Ellipsoid3> + : + public FIQuery, Ellipsoid3> + { + public: + struct Result + : + public FIQuery, Ellipsoid3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, Ellipsoid3 const& ellipsoid) + { + Result result; + DoQuery(ray.origin, ray.direction, ellipsoid, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; + } + + protected: + void DoQuery(Vector3 const& rayOrigin, + Vector3 const& rayDirection, Ellipsoid3 const& ellipsoid, + Result& result) + { + FIQuery, Ellipsoid3>::DoQuery(rayOrigin, + rayDirection, ellipsoid, result); + + if (result.intersect) + { + // The line containing the ray intersects the ellipsoid; the + // t-interval is [t0,t1]. The ray intersects the capsule as + // long as [t0,t1] overlaps the ray t-interval [0,+infinity). + std::array rayInterval = { (Real)0, std::numeric_limits::max() }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, rayInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3OrientedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3OrientedBox3.h new file mode 100644 index 0000000..e68002a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3OrientedBox3.h @@ -0,0 +1,98 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The test-intersection queries use the method of separating axes. +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The find-intersection queries use parametric clipping against the six +// faces of the box. The find-intersection queries use Liang-Barsky +// clipping. The queries consider the box to be a solid. The algorithms +// are described in +// https://www.geometrictools.com/Documentation/IntersectionLineBox.pdf + +namespace WwiseGTE +{ + template + class TIQuery, OrientedBox3> + : + public TIQuery, AlignedBox3> + { + public: + struct Result + : + public TIQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, OrientedBox3 const& box) + { + // Transform the ray to the oriented-box coordinate system. + Vector3 diff = ray.origin - box.center; + Vector3 rayOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]), + Dot(diff, box.axis[2]) + }; + Vector3 rayDirection = Vector3 + { + Dot(ray.direction, box.axis[0]), + Dot(ray.direction, box.axis[1]), + Dot(ray.direction, box.axis[2]) + }; + + Result result; + this->DoQuery(rayOrigin, rayDirection, box.extent, result); + return result; + } + }; + + template + class FIQuery, OrientedBox3> + : + public FIQuery, AlignedBox3> + { + public: + struct Result + : + public FIQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, OrientedBox3 const& box) + { + // Transform the ray to the oriented-box coordinate system. + Vector3 diff = ray.origin - box.center; + Vector3 rayOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]), + Dot(diff, box.axis[2]) + }; + Vector3 rayDirection = Vector3 + { + Dot(ray.direction, box.axis[0]), + Dot(ray.direction, box.axis[1]), + Dot(ray.direction, box.axis[2]) + }; + + Result result; + this->DoQuery(rayOrigin, rayDirection, box.extent, result); + for (int i = 0; i < result.numPoints; ++i) + { + result.point[i] = ray.origin + result.lineParameter[i] * ray.direction; + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3Plane3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3Plane3.h new file mode 100644 index 0000000..732550b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3Plane3.h @@ -0,0 +1,98 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Plane3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray3 const& ray, Plane3 const& plane) + { + Result result; + + // Compute the (signed) distance from the ray origin to the plane. + DCPQuery, Plane3> vpQuery; + auto vpResult = vpQuery(ray.origin, plane); + + Real DdN = Dot(ray.direction, plane.normal); + if (DdN > (Real)0) + { + // The ray is not parallel to the plane and is directed toward + // the +normal side of the plane. + result.intersect = (vpResult.signedDistance <= (Real)0); + } + else if (DdN < (Real)0) + { + // The ray is not parallel to the plane and is directed toward + // the -normal side of the plane. + result.intersect = (vpResult.signedDistance >= (Real)0); + } + else + { + // The ray and plane are parallel. + result.intersect = (vpResult.distance == (Real)0); + } + + return result; + } + }; + + template + class FIQuery, Plane3> + : + public FIQuery, Plane3> + { + public: + struct Result + : + public FIQuery, Plane3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, Plane3 const& plane) + { + Result result; + DoQuery(ray.origin, ray.direction, plane, result); + if (result.intersect) + { + result.point = ray.origin + result.parameter * ray.direction; + } + return result; + } + + protected: + void DoQuery(Vector3 const& rayOrigin, + Vector3 const& rayDirection, Plane3 const& plane, + Result& result) + { + FIQuery, Plane3>::DoQuery(rayOrigin, + rayDirection, plane, result); + if (result.intersect) + { + // The line intersects the plane in a point that might not be + // on the ray. + if (result.parameter < (Real)0) + { + result.intersect = false; + result.numIntersections = 0; + } + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3Sphere3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3Sphere3.h new file mode 100644 index 0000000..12654b3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3Sphere3.h @@ -0,0 +1,110 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Sphere3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray3 const& ray, Sphere3 const& sphere) + { + // The sphere is (X-C)^T*(X-C)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the sphere equation to + // obtain a quadratic equation Q(t) = t^2 + 2*a1*t + a0 = 0, where + // a1 = D^T*(P-C) and a0 = (P-C)^T*(P-C)-1. + Result result; + + Vector3 diff = ray.origin - sphere.center; + Real a0 = Dot(diff, diff) - sphere.radius * sphere.radius; + if (a0 <= (Real)0) + { + // P is inside the sphere. + result.intersect = true; + return result; + } + // else: P is outside the sphere + + Real a1 = Dot(ray.direction, diff); + if (a1 >= (Real)0) + { + result.intersect = false; + return result; + } + + // Intersection occurs when Q(t) has real roots. + Real discr = a1 * a1 - a0; + result.intersect = (discr >= (Real)0); + return result; + } + }; + + template + class FIQuery, Sphere3> + : + public FIQuery, Sphere3> + { + public: + struct Result + : + public FIQuery, Sphere3>::Result + { + // No additional information to compute. + }; + + Result operator()(Ray3 const& ray, Sphere3 const& sphere) + { + Result result; + DoQuery(ray.origin, ray.direction, sphere, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = ray.origin + result.parameter[i] * ray.direction; + } + return result; + } + + protected: + void DoQuery(Vector3 const& rayOrigin, + Vector3 const& rayDirection, Sphere3 const& sphere, + Result& result) + { + FIQuery, Sphere3>::DoQuery(rayOrigin, + rayDirection, sphere, result); + + if (result.intersect) + { + // The line containing the ray intersects the sphere; the + // t-interval is [t0,t1]. The ray intersects the sphere as + // long as [t0,t1] overlaps the ray t-interval [0,+infinity). + std::array rayInterval = { (Real)0, std::numeric_limits::max() }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, rayInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3Triangle3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3Triangle3.h new file mode 100644 index 0000000..0b9cbde --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrRay3Triangle3.h @@ -0,0 +1,180 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Triangle3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Ray3 const& ray, Triangle3 const& triangle) + { + Result result; + + // Compute the offset origin, edges, and normal. + Vector3 diff = ray.origin - triangle.v[0]; + Vector3 edge1 = triangle.v[1] - triangle.v[0]; + Vector3 edge2 = triangle.v[2] - triangle.v[0]; + Vector3 normal = Cross(edge1, edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, + // E1 = edge1, E2 = edge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + Real DdN = Dot(ray.direction, normal); + Real sign; + if (DdN > (Real)0) + { + sign = (Real)1; + } + else if (DdN < (Real)0) + { + sign = (Real)-1; + DdN = -DdN; + } + else + { + // Ray and triangle are parallel, call it a "no intersection" + // even if the ray does intersect. + result.intersect = false; + return result; + } + + Real DdQxE2 = sign * DotCross(ray.direction, diff, edge2); + if (DdQxE2 >= (Real)0) + { + Real DdE1xQ = sign * DotCross(ray.direction, edge1, diff); + if (DdE1xQ >= (Real)0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle, check whether ray does. + Real QdN = -sign * Dot(diff, normal); + if (QdN >= (Real)0) + { + // Ray intersects triangle. + result.intersect = true; + return result; + } + // else: t < 0, no intersection + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + result.intersect = false; + return result; + } + }; + + template + class FIQuery, Triangle3> + { + public: + struct Result + { + Result() + : + intersect(false), + parameter((Real)0), + triangleBary{ (Real)0, (Real)0, (Real)0 }, + point{ (Real)0, (Real)0, (Real)0 } + { + } + + bool intersect; + Real parameter; + std::array triangleBary; + Vector3 point; + }; + + Result operator()(Ray3 const& ray, Triangle3 const& triangle) + { + Result result; + + // Compute the offset origin, edges, and normal. + Vector3 diff = ray.origin - triangle.v[0]; + Vector3 edge1 = triangle.v[1] - triangle.v[0]; + Vector3 edge2 = triangle.v[2] - triangle.v[0]; + Vector3 normal = Cross(edge1, edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, + // E1 = edge1, E2 = edge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + Real DdN = Dot(ray.direction, normal); + Real sign; + if (DdN > (Real)0) + { + sign = (Real)1; + } + else if (DdN < (Real)0) + { + sign = (Real)-1; + DdN = -DdN; + } + else + { + // Ray and triangle are parallel, call it a "no intersection" + // even if the ray does intersect. + result.intersect = false; + return result; + } + + Real DdQxE2 = sign * DotCross(ray.direction, diff, edge2); + if (DdQxE2 >= (Real)0) + { + Real DdE1xQ = sign * DotCross(ray.direction, edge1, diff); + if (DdE1xQ >= (Real)0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle, check whether ray does. + Real QdN = -sign * Dot(diff, normal); + if (QdN >= (Real)0) + { + // Ray intersects triangle. + result.intersect = true; + Real inv = (Real)1 / DdN; + result.parameter = QdN * inv; + result.triangleBary[1] = DdQxE2 * inv; + result.triangleBary[2] = DdE1xQ * inv; + result.triangleBary[0] = + (Real)1 - result.triangleBary[1] - result.triangleBary[2]; + result.point = ray.origin + result.parameter * ray.direction; + return result; + } + // else: t < 0, no intersection + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + result.intersect = false; + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment2AlignedBox2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment2AlignedBox2.h new file mode 100644 index 0000000..eabdc57 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment2AlignedBox2.h @@ -0,0 +1,153 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection queries use the method of separating axes. +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The find-intersection queries use parametric clipping against the four +// edges of the box. + +namespace WwiseGTE +{ + template + class TIQuery, AlignedBox2> + : + public TIQuery, AlignedBox2> + { + public: + struct Result + : + public TIQuery, AlignedBox2>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment2 const& segment, AlignedBox2 const& box) + { + // Get the centered form of the aligned box. The axes are + // implicitly Axis[d] = Vector2::Unit(d). + Vector2 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the segment to a centered form in the aligned-box + // coordinate system. + Vector2 transformedP0 = segment.p[0] - boxCenter; + Vector2 transformedP1 = segment.p[1] - boxCenter; + Segment2 transformedSegment(transformedP0, transformedP1); + Vector2 segOrigin, segDirection; + Real segExtent; + transformedSegment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, boxExtent, result); + return result; + } + + protected: + void DoQuery(Vector2 const& segOrigin, + Vector2 const& segDirection, Real segExtent, + Vector2 const& boxExtent, Result& result) + { + for (int i = 0; i < 2; ++i) + { + Real lhs = std::fabs(segOrigin[i]); + Real rhs = boxExtent[i] + segExtent * std::fabs(segDirection[i]); + if (lhs > rhs) + { + result.intersect = false; + return; + } + } + + TIQuery, AlignedBox2>::DoQuery(segOrigin, + segDirection, boxExtent, result); + } + }; + + template + class FIQuery, AlignedBox2> + : + public FIQuery, AlignedBox2> + { + public: + struct Result + : + public FIQuery, AlignedBox2>::Result + { + // The base class parameter[] values are t-values for the + // segment parameterization (1-t)*p[0] + t*p[1], where t in [0,1]. + // The values in this class are s-values for the centered form + // C + s * D, where s in [-e,e] and e is the extent of the + // segment. + std::array cdeParameter; + }; + + Result operator()(Segment2 const& segment, AlignedBox2 const& box) + { + // Get the centered form of the aligned box. The axes are + // implicitly Axis[d] = Vector2::Unit(d). + Vector2 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the segment to a centered form in the aligned-box + // coordinate system. + Vector2 transformedP0 = segment.p[0] - boxCenter; + Vector2 transformedP1 = segment.p[1] - boxCenter; + Segment2 transformedSegment(transformedP0, transformedP1); + Vector2 segOrigin, segDirection; + Real segExtent; + transformedSegment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, boxExtent, result); + for (int i = 0; i < result.numIntersections; ++i) + { + // Compute the segment in the aligned-box coordinate system + // and then translate it back to the original coordinates + // using the box cener. + result.point[i] = boxCenter + (segOrigin + result.parameter[i] * segDirection); + result.cdeParameter[i] = result.parameter[i]; + + // Convert the parameters from the centered form to the + // endpoint form. + result.parameter[i] = (result.parameter[i] / segExtent + (Real)1) * (Real)0.5; + } + return result; + } + + protected: + void DoQuery(Vector2 const& segOrigin, + Vector2 const& segDirection, Real segExtent, + Vector2 const& boxExtent, Result& result) + { + FIQuery, AlignedBox2>::DoQuery(segOrigin, + segDirection, boxExtent, result); + + if (result.intersect) + { + // The line containing the segment intersects the box; the + // t-interval is [t0,t1]. The segment intersects the box as + // long as [t0,t1] overlaps the segment t-interval + // [-segExtent,+segExtent]. + std::array segInterval = { -segExtent, segExtent }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, segInterval); + result.intersect = iiResult.intersect; + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment2Arc2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment2Arc2.h new file mode 100644 index 0000000..72c16a4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment2Arc2.h @@ -0,0 +1,78 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The queries consider the arc to be a 1-dimensional object. + +namespace WwiseGTE +{ + template + class TIQuery, Arc2> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment2 const& segment, Arc2 const& arc) + { + Result result; + FIQuery, Arc2> saQuery; + auto saResult = saQuery(segment, arc); + result.intersect = saResult.intersect; + return result; + } + }; + + template + class FIQuery, Arc2> + { + public: + struct Result + { + bool intersect; + int numIntersections; + std::array parameter; + std::array, 2> point; + }; + + Result operator()(Segment2 const& segment, Arc2 const& arc) + { + Result result; + result.intersect = false; + result.numIntersections = 0; + result.parameter[0] = (Real)0; + result.parameter[0] = (Real)0; + result.point[0] = { (Real)0, (Real)0 }; + result.point[1] = { (Real)0, (Real)0 }; + + FIQuery, Circle2> scQuery; + Circle2 circle(arc.center, arc.radius); + auto scResult = scQuery(segment, circle); + if (scResult.intersect) + { + // Test whether line-circle intersections are on the arc. + for (int i = 0; i < scResult.numIntersections; ++i) + { + if (arc.Contains(scResult.point[i])) + { + result.intersect = true; + result.parameter[result.numIntersections] = scResult.parameter[i]; + result.point[result.numIntersections++] = scResult.point[i]; + } + } + } + + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment2Circle2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment2Circle2.h new file mode 100644 index 0000000..242d7ff --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment2Circle2.h @@ -0,0 +1,87 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The queries consider the circle to be a solid (disk). + +namespace WwiseGTE +{ + template + class TIQuery, Circle2> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment2 const& segment, Circle2 const& circle) + { + Result result; + FIQuery, Circle2> scQuery; + result.intersect = scQuery(segment, circle).intersect; + return result; + } + }; + + template + class FIQuery, Circle2> + : + public FIQuery, Circle2> + { + public: + struct Result + : + public FIQuery, Circle2>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment2 const& segment, Circle2 const& circle) + { + Vector2 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, circle, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = segOrigin + result.parameter[i] * segDirection; + } + return result; + } + + protected: + void DoQuery(Vector2 const& segOrigin, + Vector2 const& segDirection, Real segExtent, + Circle2 const& circle, Result& result) + { + FIQuery, Circle2>::DoQuery(segOrigin, + segDirection, circle, result); + + if (result.intersect) + { + // The line containing the segment intersects the disk; the + // t-interval is [t0,t1]. The segment intersects the disk as + // long as [t0,t1] overlaps the segment t-interval + // [-segExtent,+segExtent]. + std::array segInterval = { -segExtent, segExtent }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, segInterval); + result.intersect = iiResult.intersect; + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment2OrientedBox2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment2OrientedBox2.h new file mode 100644 index 0000000..97a0359 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment2OrientedBox2.h @@ -0,0 +1,112 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The queries consider the box to be a solid. +// +// The test-intersection queries use the method of separating axes. +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The find-intersection queries use parametric clipping against the four +// edges of the box. + +namespace WwiseGTE +{ + template + class TIQuery, OrientedBox2> + : + public TIQuery, AlignedBox2> + { + public: + struct Result + : + public TIQuery, AlignedBox2>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment2 const& segment, OrientedBox2 const& box) + { + // Transform the segment to the oriented-box coordinate system. + Vector2 tmpOrigin, tmpDirection; + Real segExtent; + segment.GetCenteredForm(tmpOrigin, tmpDirection, segExtent); + Vector2 diff = tmpOrigin - box.center; + Vector2 segOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]) + }; + Vector2 segDirection + { + Dot(tmpDirection, box.axis[0]), + Dot(tmpDirection, box.axis[1]) + }; + + Result result; + this->DoQuery(segOrigin, segDirection, segExtent, box.extent, result); + return result; + } + }; + + template + class FIQuery, OrientedBox2> + : + public FIQuery, AlignedBox2> + { + public: + struct Result + : + public FIQuery, AlignedBox2>::Result + { + // The base class parameter[] values are t-values for the + // segment parameterization (1-t)*p[0] + t*p[1], where t in [0,1]. + // The values in this class are s-values for the centered form + // C + s * D, where s in [-e,e] and e is the extent of the + // segment. + std::array cdeParameter; + }; + + Result operator()(Segment2 const& segment, OrientedBox2 const& box) + { + // Transform the segment to the oriented-box coordinate system. + Vector2 tmpOrigin, tmpDirection; + Real segExtent; + segment.GetCenteredForm(tmpOrigin, tmpDirection, segExtent); + Vector2 diff = tmpOrigin - box.center; + Vector2 segOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]) + }; + Vector2 segDirection + { + Dot(tmpDirection, box.axis[0]), + Dot(tmpDirection, box.axis[1]) + }; + + Result result; + this->DoQuery(segOrigin, segDirection, segExtent, box.extent, result); + for (int i = 0; i < result.numIntersections; ++i) + { + // Compute the segment in the aligned-box coordinate system + // and then translate it back to the original coordinates + // using the box cener. + result.point[i] = box.center + (segOrigin + result.parameter[i] * segDirection); + result.cdeParameter[i] = result.parameter[i]; + + // Convert the parameters from the centered form to the + // endpoint form. + result.parameter[i] = (result.parameter[i] / segExtent + (Real)1) * (Real)0.5; + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment2Segment2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment2Segment2.h new file mode 100644 index 0000000..ade7b3c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment2Segment2.h @@ -0,0 +1,185 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Segment2> + { + public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (segments intersect in a + // single point), or 2 (segments are collinear and intersect in a + // segment). + int numIntersections; + }; + + Result operator()(Segment2 const& segment0, Segment2 const& segment1) + { + Result result; + Vector2 seg0Origin, seg0Direction, seg1Origin, seg1Direction; + Real seg0Extent, seg1Extent; + segment0.GetCenteredForm(seg0Origin, seg0Direction, seg0Extent); + segment1.GetCenteredForm(seg1Origin, seg1Direction, seg1Extent); + + FIQuery, Line2> llQuery; + Line2 line0(seg0Origin, seg0Direction); + Line2 line1(seg1Origin, seg1Direction); + auto llResult = llQuery(line0, line1); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the segments. + if (std::fabs(llResult.line0Parameter[0]) <= seg0Extent + && std::fabs(llResult.line1Parameter[0]) <= seg1Extent) + { + result.intersect = true; + result.numIntersections = 1; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + // Compute the location of segment1 endpoints relative to + // segment0. + Vector2 diff = seg1Origin - seg0Origin; + Real t = Dot(seg0Direction, diff); + + // Get the parameter intervals of the segments relative to + // segment0. + std::array interval0 = { -seg0Extent, seg0Extent }; + std::array interval1 = { t - seg1Extent, t + seg1Extent }; + + // Compute the intersection of the intervals. + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(interval0, interval1); + result.intersect = iiResult.intersect; + result.numIntersections = iiResult.numIntersections; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + + return result; + } + }; + + template + class FIQuery, Segment2> + { + public: + struct Result + { + bool intersect; + + // The number is 0 (no intersection), 1 (segments intersect in a + // a single point), or 2 (segments are collinear and intersect + // in a segment). + int numIntersections; + + // If numIntersections is 1, the intersection is + // point[0] + // = segment0.origin + segment0Parameter[0] * segment0.direction + // = segment1.origin + segment1Parameter[0] * segment1.direction + // If numIntersections is 2, the endpoints of the segment of + // intersection are + // point[i] + // = segment0.origin + segment0Parameter[i] * segment0.direction + // = segment1.origin + segment1Parameter[i] * segment1.direction + // with segment0Parameter[0] <= segment0Parameter[1] and + // segment1Parameter[0] <= segment1Parameter[1]. + Real segment0Parameter[2], segment1Parameter[2]; + Vector2 point[2]; + }; + + Result operator()(Segment2 const& segment0, Segment2 const& segment1) + { + Result result; + Vector2 seg0Origin, seg0Direction, seg1Origin, seg1Direction; + Real seg0Extent, seg1Extent; + segment0.GetCenteredForm(seg0Origin, seg0Direction, seg0Extent); + segment1.GetCenteredForm(seg1Origin, seg1Direction, seg1Extent); + + FIQuery, Line2> llQuery; + Line2 line0(seg0Origin, seg0Direction); + Line2 line1(seg1Origin, seg1Direction); + auto llResult = llQuery(line0, line1); + if (llResult.numIntersections == 1) + { + // Test whether the line-line intersection is on the segments. + if (std::fabs(llResult.line0Parameter[0]) <= seg0Extent + && std::fabs(llResult.line1Parameter[0]) <= seg1Extent) + { + result.intersect = true; + result.numIntersections = 1; + result.segment0Parameter[0] = llResult.line0Parameter[0]; + result.segment1Parameter[0] = llResult.line1Parameter[0]; + result.point[0] = llResult.point; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else if (llResult.numIntersections == std::numeric_limits::max()) + { + // Compute the location of segment1 endpoints relative to + // segment0. + Vector2 diff = seg1Origin - seg0Origin; + Real t = Dot(seg0Direction, diff); + + // Get the parameter intervals of the segments relative to + // segment0. + std::array interval0 = { -seg0Extent, seg0Extent }; + std::array interval1 = { t - seg1Extent, t + seg1Extent }; + + // Compute the intersection of the intervals. + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(interval0, interval1); + if (iiResult.intersect) + { + result.intersect = true; + result.numIntersections = iiResult.numIntersections; + for (int i = 0; i < iiResult.numIntersections; ++i) + { + result.segment0Parameter[i] = iiResult.overlap[i]; + result.segment1Parameter[i] = iiResult.overlap[i] - t; + result.point[i] = seg0Origin + + result.segment0Parameter[i] * seg0Direction; + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment2Triangle2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment2Triangle2.h new file mode 100644 index 0000000..116122d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment2Triangle2.h @@ -0,0 +1,84 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The queries consider the triangle to be a solid. + +namespace WwiseGTE +{ + template + class TIQuery, Triangle2> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment2 const& segment, Triangle2 const& triangle) + { + Result result; + FIQuery, Triangle2> stQuery; + result.intersect = stQuery(segment, triangle).intersect; + return result; + } + }; + + template + class FIQuery , Triangle2> + : + public FIQuery, Triangle2> + { + public: + struct Result + : + public FIQuery, Triangle2>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment2 const& segment, Triangle2 const& triangle) + { + Vector2 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, triangle, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = segOrigin + result.parameter[i] * segDirection; + } + return result; + } + + protected: + void DoQuery(Vector2 const& segOrigin, + Vector2 const& segDirection, Real segExtent, + Triangle2 const& triangle, Result& result) + { + FIQuery, Triangle2>::DoQuery(segOrigin, + segDirection, triangle, result); + + if (result.intersect) + { + // The line containing the segment intersects the disk; the + // t-interval is [t0,t1]. The segment intersects the disk as + // long as [t0,t1] overlaps the segment t-interval + // [-segExtent,+segExtent]. + std::array segInterval = { -segExtent, segExtent }; + FIQuery, std::array> iiQuery; + result.parameter = iiQuery(result.parameter, segInterval).overlap; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3AlignedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3AlignedBox3.h new file mode 100644 index 0000000..be9bd3e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3AlignedBox3.h @@ -0,0 +1,157 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The test-intersection queries use the method of separating axes. +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The find-intersection queries use parametric clipping against the six +// faces of the box. The find-intersection queries use Liang-Barsky +// clipping. The queries consider the box to be a solid. The algorithms +// are described in +// https://www.geometrictools.com/Documentation/IntersectionLineBox.pdf + +namespace WwiseGTE +{ + template + class TIQuery, AlignedBox3> + : + public TIQuery, AlignedBox3> + { + public: + struct Result + : + public TIQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, AlignedBox3 const& box) + { + // Get the centered form of the aligned box. The axes are + // implicitly Axis[d] = Vector3::Unit(d). + Vector3 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the segment to a centered form in the aligned-box + // coordinate system. + Vector3 transformedP0 = segment.p[0] - boxCenter; + Vector3 transformedP1 = segment.p[1] - boxCenter; + Segment3 transformedSegment(transformedP0, transformedP1); + Vector3 segOrigin, segDirection; + Real segExtent; + transformedSegment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, boxExtent, result); + return result; + } + + protected: + void DoQuery(Vector3 const& segOrigin, Vector3 const& segDirection, + Real segExtent, Vector3 const& boxExtent, Result& result) + { + for (int i = 0; i < 3; ++i) + { + if (std::fabs(segOrigin[i]) > boxExtent[i] + segExtent * std::fabs(segDirection[i])) + { + result.intersect = false; + return; + } + } + + TIQuery, AlignedBox3>::DoQuery(segOrigin, segDirection, boxExtent, result); + } + }; + + template + class FIQuery, AlignedBox3> + : + public FIQuery, AlignedBox3> + { + public: + struct Result + : + public FIQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, AlignedBox3 const& box) + { + // Get the centered form of the aligned box. The axes are + // implicitly Axis[d] = Vector3::Unit(d). + Vector3 boxCenter, boxExtent; + box.GetCenteredForm(boxCenter, boxExtent); + + // Transform the segment to a centered form in the aligned-box + // coordinate system. + Vector3 transformedP0 = segment.p[0] - boxCenter; + Vector3 transformedP1 = segment.p[1] - boxCenter; + Segment3 transformedSegment(transformedP0, transformedP1); + Vector3 segOrigin, segDirection; + Real segExtent; + transformedSegment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, boxExtent, result); + + // The segment origin is in aligned-box coordinates. Transform it + // back to the original space. + segOrigin += boxCenter; + for (int i = 0; i < result.numPoints; ++i) + { + result.point[i] = segOrigin + result.lineParameter[i] * segDirection; + } + return result; + } + + protected: + void DoQuery(Vector3 const& segOrigin, Vector3 const& segDirection, + Real segExtent, Vector3 const& boxExtent, Result& result) + { + FIQuery, AlignedBox3>::DoQuery(segOrigin, segDirection, boxExtent, result); + if (result.intersect) + { + // The line containing the segment intersects the box; the + // t-interval is [t0,t1]. The segment intersects the box as + // long as [t0,t1] overlaps the segment t-interval + // [-segExtent,+segExtent]. + FIQuery, std::array> iiQuery; + + std::array interval0 = + { + result.lineParameter[0], result.lineParameter[1] + }; + + std::array interval1 = + { + -segExtent, segExtent + }; + + auto iiResult = iiQuery(interval0, interval1); + if (iiResult.numIntersections > 0) + { + result.numPoints = iiResult.numIntersections; + for (int i = 0; i < result.numPoints; ++i) + { + result.lineParameter[i] = iiResult.overlap[i]; + } + } + else + { + result.intersect = false; + result.numPoints = 0; + } + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3Capsule3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3Capsule3.h new file mode 100644 index 0000000..32ffcd9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3Capsule3.h @@ -0,0 +1,97 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The queries consider the capsule to be a solid. +// +// The test-intersection queries are based on distance computations. + +namespace WwiseGTE +{ + template + class TIQuery, Capsule3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment3 const& segment, Capsule3 const& capsule) + { + Result result; + DCPQuery, Segment3> ssQuery; + auto ssResult = ssQuery(segment, capsule.segment); + result.intersect = (ssResult.distance <= capsule.radius); + return result; + } + }; + + template + class FIQuery, Capsule3> + : + public FIQuery, Capsule3> + { + public: + struct Result + : + public FIQuery, Capsule3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, Capsule3 const& capsule) + { + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, capsule, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = segOrigin + result.parameter[i] * segDirection; + } + return result; + } + + protected: + void DoQuery(Vector3 const& segOrigin, + Vector3 const& segDirection, Real segExtent, + Capsule3 const& capsule, Result& result) + { + FIQuery, Capsule3>::DoQuery(segOrigin, + segDirection, capsule, result); + + if (result.intersect) + { + // The line containing the segment intersects the capsule; the + // t-interval is [t0,t1]. The segment intersects the capsule + // as long as [t0,t1] overlaps the segment t-interval + // [-segExtent,+segExtent]. + std::array segInterval = { -segExtent, segExtent }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, segInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3Cone3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3Cone3.h new file mode 100644 index 0000000..5954b4a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3Cone3.h @@ -0,0 +1,128 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The queries consider the cone to be single sided and solid. The +// cone height range is [hmin,hmax]. The cone can be infinite where +// hmin = 0 and hmax = +infinity, infinite truncated where hmin > 0 +// and hmax = +infinity, finite where hmin = 0 and hmax < +infinity, +// or a cone frustum where hmin > 0 and hmax < +infinity. The +// algorithm details are found in +// https://www.geometrictools.com/Documentation/IntersectionLineCone.pdf + +namespace WwiseGTE +{ + template + class FIQuery, Cone3> + : + public FIQuery, Cone3> + { + public: + struct Result + : + public FIQuery, Cone3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, Cone3 const& cone) + { + // Execute the line-cone query. + Result result; + Vector3 segOrigin = segment.p[0]; + Vector3 segDirection = segment.p[1] - segment.p[0]; + this->DoQuery(segOrigin, segDirection, cone, result); + + // Adjust the t-interval depending on whether the line-cone + // t-interval overlaps the segment interval [0,1]. The block + // numbers are a continuation of those in IntrRay3Cone3.h, which + // themselves are a continuation of those in IntrLine3Cone3.h. + if (result.type != Result::isEmpty) + { + using QFN1 = typename FIQuery, Cone3>::QFN1; + QFN1 zero(0, 0, result.t[0].d), one(1, 0, result.t[0].d); + + if (result.type == Result::isPoint) + { + if (result.t[0] < zero || result.t[0] > one) + { + // Block 21. + this->SetEmpty(result); + } + // else: Block 22. + } + else if (result.type == Result::isSegment) + { + if (result.t[1] < zero || result.t[0] > one) + { + // Block 23. + this->SetEmpty(result); + } + else + { + auto t0 = std::max(zero, result.t[0]); + auto t1 = std::min(one, result.t[1]); + if (t0 < t1) + { + // Block 24. + this->SetSegment(t0, t1, result); + } + else + { + // Block 25. + this->SetPoint(t0, result); + } + } + } + else if (result.type == Result::isRayPositive) + { + if (one < result.t[0]) + { + // Block 26. + this->SetEmpty(result); + } + else if (one > result.t[0]) + { + // Block 27. + this->SetSegment(std::max(zero, result.t[0]), one, result); + } + else + { + // Block 28. + this->SetPoint(one, result); + } + } + else // result.type == Result::isRayNegative + { + if (zero > result.t[1]) + { + // Block 29. + this->SetEmpty(result); + } + else if (zero < result.t[1]) + { + // Block 30. + this->SetSegment(zero, std::min(one, result.t[1]), result); + } + else + { + // Block 31. + this->SetPoint(zero, result); + } + } + } + + result.ComputePoints(segment.p[0], segDirection); + result.intersect = (result.type != Result::isEmpty); + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3Cylinder3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3Cylinder3.h new file mode 100644 index 0000000..ef9c299 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3Cylinder3.h @@ -0,0 +1,76 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The queries consider the cylinder to be a solid. + +namespace WwiseGTE +{ + template + class FIQuery, Cylinder3> + : + public FIQuery, Cylinder3> + { + public: + struct Result + : + public FIQuery, Cylinder3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, Cylinder3 const& cylinder) + { + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, cylinder, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = segOrigin + result.parameter[i] * segDirection; + } + return result; + } + + protected: + void DoQuery(Vector3 const& segOrigin, + Vector3 const& segDirection, Real segExtent, + Cylinder3 const& cylinder, Result& result) + { + FIQuery, Cylinder3>::DoQuery(segOrigin, + segDirection, cylinder, result); + + if (result.intersect) + { + // The line containing the segment intersects the cylinder; + // the t-interval is [t0,t1]. The segment intersects the + // cylinder as long as [t0,t1] overlaps the segment t-interval + // [-segExtent,+segExtent]. + std::array segInterval = { -segExtent, segExtent }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, segInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3Ellipsoid3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3Ellipsoid3.h new file mode 100644 index 0000000..6c21e5e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3Ellipsoid3.h @@ -0,0 +1,169 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +// The queries consider the ellipsoid to be a solid. + +namespace WwiseGTE +{ + template + class TIQuery, Ellipsoid3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment3 const& segment, Ellipsoid3 const& ellipsoid) + { + // The ellipsoid is (X-K)^T*M*(X-K)-1 = 0 and the line is + // X = P+t*D. Substitute the line equation into the ellipsoid + // equation to obtain a quadratic equation + // Q(t) = a2*t^2 + 2*a1*t + a0 = 0 + // where a2 = D^T*M*D, a1 = D^T*M*(P-K) and + // a0 = (P-K)^T*M*(P-K)-1. + Result result; + + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Matrix3x3 M; + ellipsoid.GetM(M); + + Vector3 diff = segOrigin - ellipsoid.center; + Vector3 matDir = M * segDirection; + Vector3 matDiff = M * diff; + Real a2 = Dot(segDirection, matDir); + Real a1 = Dot(segDirection, matDiff); + Real a0 = Dot(diff, matDiff) - (Real)1; + + Real discr = a1 * a1 - a0 * a2; + if (discr >= (Real)0) + { + // Test whether ray origin is inside ellipsoid. + if (a0 <= (Real)0) + { + result.intersect = true; + } + else + { + // At this point, Q(0) = a0 > 0 and Q(t) has real roots. + // It is also the case that a2 > 0, since M is positive + // definite, implying that D^T*M*D > 0 for any nonzero + // vector D. + Real q, qder; + if (a1 >= (Real)0) + { + // Roots are possible only on [-e,0], e is the segment + // extent. At least one root occurs if Q(-e) <= 0 or + // if Q(-e) > 0 and Q'(-e) < 0. + q = a0 + segExtent * ((Real)-2 * a1 + a2 * segExtent); + if (q <= (Real)0) + { + result.intersect = true; + } + else + { + qder = a1 - a2 * segExtent; + result.intersect = (qder < (Real)0); + } + } + else + { + // Roots are only possible on [0,e], e is the segment + // extent. At least one root occurs if Q(e) <= 0 or + // if Q(e) > 0 and Q'(e) > 0. + q = a0 + segExtent * ((Real)2 * a1 + a2 * segExtent); + if (q <= (Real)0.0) + { + result.intersect = true; + } + else + { + qder = a1 + a2 * segExtent; + result.intersect = (qder < (Real)0); + } + } + } + } + else + { + // No intersection if Q(t) has no real roots. + result.intersect = false; + } + + return result; + } + }; + + template + class FIQuery, Ellipsoid3> + : + public FIQuery, Ellipsoid3> + { + public: + struct Result + : + public FIQuery, Ellipsoid3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, Ellipsoid3 const& ellipsoid) + { + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, ellipsoid, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = segOrigin + result.parameter[i] * segDirection; + } + return result; + } + + protected: + void DoQuery(Vector3 const& segOrigin, + Vector3 const& segDirection, Real segExtent, + Ellipsoid3 const& ellipsoid, Result& result) + { + FIQuery, Ellipsoid3>::DoQuery(segOrigin, + segDirection, ellipsoid, result); + + if (result.intersect) + { + // The line containing the segment intersects the ellipsoid; + // the t-interval is [t0,t1]. The segment intersects the + // ellipsoid as long as [t0,t1] overlaps the segment + // t-interval [-segExtent,+segExtent]. + std::array segInterval = { -segExtent, segExtent }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, segInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3OrientedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3OrientedBox3.h new file mode 100644 index 0000000..ad86cf7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3OrientedBox3.h @@ -0,0 +1,115 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The test-intersection queries use the method of separating axes. +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The find-intersection queries use parametric clipping against the six +// faces of the box. The find-intersection queries use Liang-Barsky +// clipping. The queries consider the box to be a solid. The algorithms +// are described in +// https://www.geometrictools.com/Documentation/IntersectionLineBox.pdf + +namespace WwiseGTE +{ + template + class TIQuery, OrientedBox3> + : + public TIQuery, AlignedBox3> + { + public: + struct Result + : + public TIQuery, AlignedBox3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, OrientedBox3 const& box) + { + // Transform the segment to the oriented-box coordinate system. + Vector3 tmpOrigin, tmpDirection; + Real segExtent; + segment.GetCenteredForm(tmpOrigin, tmpDirection, segExtent); + Vector3 diff = tmpOrigin - box.center; + Vector3 segOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]), + Dot(diff, box.axis[2]) + }; + Vector3 segDirection + { + Dot(tmpDirection, box.axis[0]), + Dot(tmpDirection, box.axis[1]), + Dot(tmpDirection, box.axis[2]) + }; + + Result result; + this->DoQuery(segOrigin, segDirection, segExtent, box.extent, result); + return result; + } + }; + + template + class FIQuery, OrientedBox3> + : + public FIQuery, AlignedBox3> + { + public: + struct Result + : + public FIQuery, AlignedBox3>::Result + { + // No additional relevant information to compute. + }; + + Result operator()(Segment3 const& segment, OrientedBox3 const& box) + { + // Transform the segment to the oriented-box coordinate system. + Vector3 tmpOrigin, tmpDirection; + Real segExtent; + segment.GetCenteredForm(tmpOrigin, tmpDirection, segExtent); + Vector3 diff = tmpOrigin - box.center; + Vector3 segOrigin + { + Dot(diff, box.axis[0]), + Dot(diff, box.axis[1]), + Dot(diff, box.axis[2]) + }; + Vector3 segDirection + { + Dot(tmpDirection, box.axis[0]), + Dot(tmpDirection, box.axis[1]), + Dot(tmpDirection, box.axis[2]) + }; + + Result result; + this->DoQuery(segOrigin, segDirection, segExtent, box.extent, result); + for (int i = 0; i < result.numPoints; ++i) + { + // Compute the intersection point in the oriented-box + // coordinate system. + Vector3 y = segOrigin + result.lineParameter[i] * segDirection; + + // Transform the intersection point to the original coordinate + // system. + result.point[i] = box.center; + for (int j = 0; j < 3; ++j) + { + result.point[i] += y[j] * box.axis[j]; + } + } + + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3Plane3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3Plane3.h new file mode 100644 index 0000000..086ecbc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3Plane3.h @@ -0,0 +1,100 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Plane3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment3 const& segment, Plane3 const& plane) + { + Result result; + + // Compute the (signed) distance from the segment endpoints to the + // plane. + DCPQuery, Plane3> vpQuery; + Real sdistance0 = vpQuery(segment.p[0], plane).signedDistance; + if (sdistance0 == (Real)0) + { + // Endpoint p[0] is on the plane. + result.intersect = true; + return result; + } + + Real sdistance1 = vpQuery(segment.p[1], plane).signedDistance; + if (sdistance1 == (Real)0) + { + // Endpoint p[1] is on the plane. + result.intersect = true; + return result; + } + + // Test whether the segment transversely intersects the plane. + result.intersect = (sdistance0 * sdistance1 < (Real)0); + return result; + } + }; + + template + class FIQuery, Plane3> + : + public FIQuery, Plane3> + { + public: + struct Result + : + public FIQuery, Plane3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, Plane3 const& plane) + { + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, plane, result); + if (result.intersect) + { + result.point = segOrigin + result.parameter * segDirection; + } + return result; + } + + protected: + void DoQuery(Vector3 const& segOrigin, + Vector3 const& segDirection, Real segExtent, + Plane3 const& plane, Result& result) + { + FIQuery, Plane3>::DoQuery(segOrigin, + segDirection, plane, result); + if (result.intersect) + { + // The line intersects the plane in a point that might not be + // on the segment. + if (std::fabs(result.parameter) > segExtent) + { + result.intersect = false; + result.numIntersections = 0; + } + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3Sphere3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3Sphere3.h new file mode 100644 index 0000000..bbab709 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3Sphere3.h @@ -0,0 +1,120 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Sphere3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment3 const& segment, Sphere3 const& sphere) + { + // The sphere is (X-C)^T*(X-C)-1 = 0 and the line is X = P+t*D. + // Substitute the line equation into the sphere equation to + // obtain a quadratic equation Q(t) = t^2 + 2*a1*t + a0 = 0, where + // a1 = D^T*(P-C) and a0 = (P-C)^T*(P-C)-1. + Result result; + + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Vector3 diff = segOrigin - sphere.center; + Real a0 = Dot(diff, diff) - sphere.radius * sphere.radius; + Real a1 = Dot(segDirection, diff); + Real discr = a1 * a1 - a0; + if (discr < (Real)0) + { + result.intersect = false; + return result; + } + + Real tmp0 = segExtent * segExtent + a0; + Real tmp1 = ((Real)2) * a1 * segExtent; + Real qm = tmp0 - tmp1; + Real qp = tmp0 + tmp1; + if (qm * qp <= (Real)0) + { + result.intersect = true; + return result; + } + + result.intersect = (qm > (Real)0 && std::fabs(a1) < segExtent); + return result; + } + }; + + template + class FIQuery, Sphere3> + : + public FIQuery, Sphere3> + { + public: + struct Result + : + public FIQuery, Sphere3>::Result + { + // No additional information to compute. + }; + + Result operator()(Segment3 const& segment, Sphere3 const& sphere) + { + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + Result result; + DoQuery(segOrigin, segDirection, segExtent, sphere, result); + for (int i = 0; i < result.numIntersections; ++i) + { + result.point[i] = segOrigin + result.parameter[i] * segDirection; + } + return result; + } + + protected: + void DoQuery(Vector3 const& segOrigin, + Vector3 const& segDirection, Real segExtent, + Sphere3 const& sphere, Result& result) + { + FIQuery, Sphere3>::DoQuery(segOrigin, + segDirection, sphere, result); + + if (result.intersect) + { + // The line containing the segment intersects the sphere; the + // t-interval is [t0,t1]. The segment intersects the sphere + // as long as [t0,t1] overlaps the segment t-interval + // [-segExtent,+segExtent]. + std::array segInterval = { -segExtent, segExtent }; + FIQuery, std::array> iiQuery; + auto iiResult = iiQuery(result.parameter, segInterval); + if (iiResult.intersect) + { + result.numIntersections = iiResult.numIntersections; + result.parameter = iiResult.overlap; + } + else + { + result.intersect = false; + result.numIntersections = 0; + } + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3Triangle3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3Triangle3.h new file mode 100644 index 0000000..8a621c8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSegment3Triangle3.h @@ -0,0 +1,192 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Triangle3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Segment3 const& segment, Triangle3 const& triangle) + { + Result result; + + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + // Compute the offset origin, edges, and normal. + Vector3 diff = segOrigin - triangle.v[0]; + Vector3 edge1 = triangle.v[1] - triangle.v[0]; + Vector3 edge2 = triangle.v[2] - triangle.v[0]; + Vector3 normal = Cross(edge1, edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = diff, D = segment direction, + // E1 = edge1, E2 = edge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + Real DdN = Dot(segDirection, normal); + Real sign; + if (DdN > (Real)0) + { + sign = (Real)1; + } + else if (DdN < (Real)0) + { + sign = (Real)-1; + DdN = -DdN; + } + else + { + // Segment and triangle are parallel, call it a "no + // intersection" even if the segment does intersect. + result.intersect = false; + return result; + } + + Real DdQxE2 = sign * DotCross(segDirection, diff, edge2); + if (DdQxE2 >= (Real)0) + { + Real DdE1xQ = sign * DotCross(segDirection, edge1, diff); + if (DdE1xQ >= (Real)0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle, check whether segment + // does. + Real QdN = -sign * Dot(diff, normal); + Real extDdN = segExtent * DdN; + if (-extDdN <= QdN && QdN <= extDdN) + { + // Segment intersects triangle. + result.intersect = true; + return result; + } + // else: |t| > extent, no intersection + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + result.intersect = false; + return result; + } + }; + + template + class FIQuery, Triangle3> + { + public: + struct Result + { + Result() + : + intersect(false), + parameter((Real)0), + triangleBary{ (Real)0, (Real)0, (Real)0 }, + point{ (Real)0, (Real)0, (Real)0 } + { + } + + bool intersect; + Real parameter; + std::array triangleBary; + Vector3 point; + }; + + Result operator()(Segment3 const& segment, Triangle3 const& triangle) + { + Result result; + + Vector3 segOrigin, segDirection; + Real segExtent; + segment.GetCenteredForm(segOrigin, segDirection, segExtent); + + // Compute the offset origin, edges, and normal. + Vector3 diff = segOrigin - triangle.v[0]; + Vector3 edge1 = triangle.v[1] - triangle.v[0]; + Vector3 edge2 = triangle.v[2] - triangle.v[0]; + Vector3 normal = Cross(edge1, edge2); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = diff, D = segment direction, + // E1 = edge1, E2 = edge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + Real DdN = Dot(segDirection, normal); + Real sign; + if (DdN > (Real)0) + { + sign = (Real)1; + } + else if (DdN < (Real)0) + { + sign = (Real)-1; + DdN = -DdN; + } + else + { + // Segment and triangle are parallel, call it a "no + // intersection" even if the segment does intersect. + result.intersect = false; + return result; + } + + Real DdQxE2 = sign * DotCross(segDirection, diff, edge2); + if (DdQxE2 >= (Real)0) + { + Real DdE1xQ = sign * DotCross(segDirection, edge1, diff); + if (DdE1xQ >= (Real)0) + { + if (DdQxE2 + DdE1xQ <= DdN) + { + // Line intersects triangle, check whether segment + // does. + Real QdN = -sign * Dot(diff, normal); + Real extDdN = segExtent * DdN; + if (-extDdN <= QdN && QdN <= extDdN) + { + // Segment intersects triangle. + result.intersect = true; + Real inv = ((Real)1) / DdN; + result.parameter = QdN * inv; + result.triangleBary[1] = DdQxE2 * inv; + result.triangleBary[2] = DdE1xQ * inv; + result.triangleBary[0] = + (Real)1 - result.triangleBary[1] - result.triangleBary[2]; + result.point = segOrigin + result.parameter * segDirection; + return result; + } + // else: |t| > extent, no intersection + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + result.intersect = false; + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSphere3Cone3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSphere3Cone3.h new file mode 100644 index 0000000..5195765 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSphere3Cone3.h @@ -0,0 +1,345 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// The test-intersection query is based on the document +// https://www.geometrictools.com/Documentation/IntersectionSphereCone.pdf +// +// The find-intersection returns a single point in the set of intersection +// when that intersection is not empty. + +namespace WwiseGTE +{ + template + class TIQuery, Cone3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Sphere3 const& sphere, Cone3 const& cone) + { + Result result; + if (cone.GetMinHeight() > (Real)0) + { + if (cone.IsFinite()) + { + result.intersect = DoQueryConeFrustum(sphere, cone); + } + else + { + result.intersect = DoQueryInfiniteTruncatedCone(sphere, cone); + } + } + else + { + if (cone.IsFinite()) + { + result.intersect = DoQueryFiniteCone(sphere, cone); + } + else + { + result.intersect = DoQueryInfiniteCone(sphere, cone); + } + } + return result; + } + + private: + bool DoQueryInfiniteCone(Sphere3 const& sphere, Cone3 const& cone) + { + Vector3 U = cone.ray.origin - (sphere.radius * cone.invSinAngle) * cone.ray.direction; + Vector3 CmU = sphere.center - U; + Real AdCmU = Dot(cone.ray.direction, CmU); + if (AdCmU > (Real)0) + { + Real sqrLengthCmU = Dot(CmU, CmU); + if (AdCmU * AdCmU >= sqrLengthCmU * cone.cosAngleSqr) + { + Vector3 CmV = sphere.center - cone.ray.origin; + Real AdCmV = Dot(cone.ray.direction, CmV); + if (AdCmV < -sphere.radius) + { + return false; + } + + Real rSinAngle = sphere.radius * cone.sinAngle; + if (AdCmV >= -rSinAngle) + { + return true; + } + + Real sqrLengthCmV = Dot(CmV, CmV); + return sqrLengthCmV <= sphere.radius * sphere.radius; + } + } + + return false; + } + + bool DoQueryInfiniteTruncatedCone(Sphere3 const& sphere, Cone3 const& cone) + { + Vector3 U = cone.ray.origin - (sphere.radius * cone.invSinAngle) * cone.ray.direction; + Vector3 CmU = sphere.center - U; + Real AdCmU = Dot(cone.ray.direction, CmU); + if (AdCmU > (Real)0) + { + Real sqrLengthCmU = Dot(CmU, CmU); + if (AdCmU * AdCmU >= sqrLengthCmU * cone.cosAngleSqr) + { + Vector3 CmV = sphere.center - cone.ray.origin; + Real AdCmV = Dot(cone.ray.direction, CmV); + Real minHeight = cone.GetMinHeight(); + if (AdCmV < minHeight - sphere.radius) + { + return false; + } + + Real rSinAngle = sphere.radius * cone.sinAngle; + if (AdCmV >= -rSinAngle) + { + return true; + } + + Vector3 D = CmV - minHeight * cone.ray.direction; + Real lengthAxD = Length(Cross(cone.ray.direction, D)); + Real hminTanAngle = minHeight * cone.tanAngle; + if (lengthAxD <= hminTanAngle) + { + return true; + } + + Real AdD = AdCmV - minHeight; + Real diff = lengthAxD - hminTanAngle; + Real sqrLengthCmK = AdD * AdD + diff * diff; + return sqrLengthCmK <= sphere.radius * sphere.radius; + } + } + + return false; + } + + bool DoQueryFiniteCone(Sphere3 const& sphere, Cone3 const& cone) + { + Vector3 U = cone.ray.origin - (sphere.radius * cone.invSinAngle) * cone.ray.direction; + Vector3 CmU = sphere.center - U; + Real AdCmU = Dot(cone.ray.direction, CmU); + if (AdCmU > (Real)0) + { + Real sqrLengthCmU = Dot(CmU, CmU); + if (AdCmU * AdCmU >= sqrLengthCmU * cone.cosAngleSqr) + { + Vector3 CmV = sphere.center - cone.ray.origin; + Real AdCmV = Dot(cone.ray.direction, CmV); + if (AdCmV < -sphere.radius) + { + return false; + } + + Real maxHeight = cone.GetMaxHeight(); + if (AdCmV > cone.GetMaxHeight() + sphere.radius) + { + return false; + } + + Real rSinAngle = sphere.radius * cone.sinAngle; + if (AdCmV >= -rSinAngle) + { + if (AdCmV <= maxHeight - rSinAngle) + { + return true; + } + else + { + Vector3 barD = CmV - maxHeight * cone.ray.direction; + Real lengthAxBarD = Length(Cross(cone.ray.direction, barD)); + Real hmaxTanAngle = maxHeight * cone.tanAngle; + if (lengthAxBarD <= hmaxTanAngle) + { + return true; + } + + Real AdBarD = AdCmV - maxHeight; + Real diff = lengthAxBarD - hmaxTanAngle; + Real sqrLengthCmBarK = AdBarD * AdBarD + diff * diff; + return sqrLengthCmBarK <= sphere.radius * sphere.radius; + } + } + else + { + Real sqrLengthCmV = Dot(CmV, CmV); + return sqrLengthCmV <= sphere.radius * sphere.radius; + } + } + } + + return false; + } + + bool DoQueryConeFrustum(Sphere3 const& sphere, Cone3 const& cone) + { + Vector3 U = cone.ray.origin - (sphere.radius * cone.invSinAngle) * cone.ray.direction; + Vector3 CmU = sphere.center - U; + Real AdCmU = Dot(cone.ray.direction, CmU); + if (AdCmU > (Real)0) + { + Real sqrLengthCmU = Dot(CmU, CmU); + if (AdCmU * AdCmU >= sqrLengthCmU * cone.cosAngleSqr) + { + Vector3 CmV = sphere.center - cone.ray.origin; + Real AdCmV = Dot(cone.ray.direction, CmV); + Real minHeight = cone.GetMinHeight(); + if (AdCmV < minHeight - sphere.radius) + { + return false; + } + + Real maxHeight = cone.GetMaxHeight(); + if (AdCmV > maxHeight + sphere.radius) + { + return false; + } + + Real rSinAngle = sphere.radius * cone.sinAngle; + if (AdCmV >= minHeight - rSinAngle) + { + if (AdCmV <= maxHeight - rSinAngle) + { + return true; + } + else + { + Vector3 barD = CmV - maxHeight * cone.ray.direction; + Real lengthAxBarD = Length(Cross(cone.ray.direction, barD)); + Real hmaxTanAngle = maxHeight * cone.tanAngle; + if (lengthAxBarD <= hmaxTanAngle) + { + return true; + } + + Real AdBarD = AdCmV - maxHeight; + Real diff = lengthAxBarD - hmaxTanAngle; + Real sqrLengthCmBarK = AdBarD * AdBarD + diff * diff; + return sqrLengthCmBarK <= sphere.radius * sphere.radius; + } + } + else + { + Vector3 D = CmV - minHeight * cone.ray.direction; + Real lengthAxD = Length(Cross(cone.ray.direction, D)); + Real hminTanAngle = minHeight * cone.tanAngle; + if (lengthAxD <= hminTanAngle) + { + return true; + } + + Real AdD = AdCmV - minHeight; + Real diff = lengthAxD - hminTanAngle; + Real sqrLengthCmK = AdD * AdD + diff * diff; + return sqrLengthCmK <= sphere.radius * sphere.radius; + } + } + } + + return false; + } + }; + + template + class FIQuery, Cone3> + { + public: + struct Result + { + // If an intersection occurs, it is potentially an infinite set. + // If the cone vertex is inside the sphere, 'point' is set to the + // cone vertex. If the sphere center is inside the cone, 'point' + // is set to the sphere center. Otherwise, 'point' is set to the + // cone point that is closest to the cone vertex and inside the + // sphere. + bool intersect; + Vector3 point; + }; + + Result operator()(Sphere3 const& sphere, Cone3 const& cone) + { + Result result; + + // Test whether the cone vertex is inside the sphere. + Vector3 diff = sphere.center - cone.ray.origin; + Real rSqr = sphere.radius * sphere.radius; + Real lenSqr = Dot(diff, diff); + if (lenSqr <= rSqr) + { + // The cone vertex is inside the sphere, so the sphere and + // cone intersect. + result.intersect = true; + result.point = cone.ray.origin; + return result; + } + + // Test whether the sphere center is inside the cone. + Real dot = Dot(diff, cone.ray.direction); + Real dotSqr = dot * dot; + if (dotSqr >= lenSqr * cone.cosAngleSqr && dot > (Real)0) + { + // The sphere center is inside cone, so the sphere and cone + // intersect. + result.intersect = true; + result.point = sphere.center; + return result; + } + + // The sphere center is outside the cone. The problem now reduces + // to computing an intersection between the circle and the ray in + // the plane containing the cone vertex and spanned by the cone + // axis and vector from the cone vertex to the sphere center. + + // The ray is parameterized by t * D + V with t >= 0, |D| = 1 and + // dot(A,D) = cos(angle). Also, D = e * A + f * (C - V). + // Substituting the ray equation into the sphere equation yields + // R^2 = |t * D + V - C|^2, so the quadratic for intersections is + // t^2 - 2 * dot(D, C - V) * t + |C - V|^2 - R^2 = 0. An + // intersection occurs if and only if the discriminant is + // nonnegative. This test becomes + // dot(D, C - V)^2 >= dot(C - V, C - V) - R^2 + // Note that if the right-hand side is nonpositive, then the + // inequality is true (the sphere contains V). This is already + // ruled out in the first block of code in this function. + + Real uLen = std::sqrt(std::max(lenSqr - dotSqr, (Real)0)); + Real test = cone.cosAngle * dot + cone.sinAngle * uLen; + Real discr = test * test - lenSqr + rSqr; + + if (discr >= (Real)0 && test >= (Real)0) + { + // Compute the point of intersection closest to the cone + // vertex. + result.intersect = true; + Real t = test - std::sqrt(std::max(discr, (Real)0)); + Vector3 B = diff - dot * cone.ray.direction; + Real tmp = cone.sinAngle / uLen; + result.point = t * (cone.cosAngle * cone.ray.direction + tmp * B); + } + else + { + result.intersect = false; + } + + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSphere3Frustum3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSphere3Frustum3.h new file mode 100644 index 0000000..39cffad --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSphere3Frustum3.h @@ -0,0 +1,34 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class TIQuery, Frustum3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Sphere3 const& sphere, Frustum3 const& frustum) + { + Result result; + DCPQuery, Frustum3> vfQuery; + Real distance = vfQuery(sphere.center, frustum).distance; + result.intersect = (distance <= sphere.radius); + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSphere3Sphere3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSphere3Sphere3.h new file mode 100644 index 0000000..70faf3c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSphere3Sphere3.h @@ -0,0 +1,138 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +// The queries consider the spheres to be solids. + +namespace WwiseGTE +{ + template + class TIQuery, Sphere3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Sphere3 const& sphere0, Sphere3 const& sphere1) + { + Result result; + Vector3 diff = sphere1.center - sphere0.center; + Real rSum = sphere0.radius + sphere1.radius; + result.intersect = (Dot(diff, diff) <= rSum * rSum); + return result; + } + }; + + template + class FIQuery, Sphere3> + { + public: + struct Result + { + bool intersect; + + // The type of intersection. + // 0: spheres are disjoint and separated + // 1: spheres touch at point, each sphere outside the other + // 2: spheres intersect in a circle + // 3: sphere0 strictly contained in sphere1 + // 4: sphere0 contained in sphere1, share common point + // 5: sphere1 strictly contained in sphere0 + // 6: sphere1 contained in sphere0, share common point + int type; + Vector3 point; // types 1, 4, 6 + Circle3 circle; // type 2 + }; + + Result operator()(Sphere3 const& sphere0, Sphere3 const& sphere1) + { + Result result; + + // The plane of intersection must have C1-C0 as its normal + // direction. + Vector3 C1mC0 = sphere1.center - sphere0.center; + Real sqrLen = Dot(C1mC0, C1mC0); + Real r0 = sphere0.radius, r1 = sphere1.radius; + Real rSum = r0 + r1; + Real rSumSqr = rSum * rSum; + + if (sqrLen > rSumSqr) + { + // The spheres are disjoint/separated. + result.intersect = false; + result.type = 0; + return result; + } + + if (sqrLen == rSumSqr) + { + // The spheres are just touching with each sphere outside the + // other. + Normalize(C1mC0); + result.intersect = true; + result.type = 1; + result.point = sphere0.center + r0 * C1mC0; + return result; + } + + Real rDif = r0 - r1; + Real rDifSqr = rDif * rDif; + if (sqrLen < rDifSqr) + { + // One sphere is strictly contained in the other. Compute a + // point in the intersection set. + result.intersect = true; + result.type = (rDif <= (Real)0 ? 3 : 5); + result.point = ((Real)0.5) * (sphere0.center + sphere1.center); + return result; + } + if (sqrLen == rDifSqr) + { + // One sphere is contained in the other sphere but with a + // single point of contact. + Normalize(C1mC0); + result.intersect = true; + if (rDif <= (Real)0) + { + result.type = 4; + result.point = sphere1.center + r1 * C1mC0; + } + else + { + result.type = 6; + result.point = sphere0.center + r0 * C1mC0; + } + return result; + } + + // Compute t for which the circle of intersection has center + // K = C0 + t*(C1 - C0). + Real t = ((Real)0.5) * ((Real)1 + rDif * rSum / sqrLen); + + // Compute the center and radius of the circle of intersection. + result.circle.center = sphere0.center + t * C1mC0; + result.circle.radius = std::sqrt(std::max(r0 * r0 - t * t * sqrLen, (Real)0)); + + // Compute the normal for the plane of the circle. + Normalize(C1mC0); + result.circle.normal = C1mC0; + + // The intersection is a circle. + result.intersect = true; + result.type = 2; + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSphere3Triangle3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSphere3Triangle3.h new file mode 100644 index 0000000..f11628a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrSphere3Triangle3.h @@ -0,0 +1,599 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + // Currently, only a dynamic query is supported. A static query will + // need to compute the intersection set of triangle and sphere. + + template + class FIQuery, Triangle3> + { + public: + // The implementation for floating-point types. + struct Result + { + // The cases are + // 1. Objects initially overlapping. The contactPoint is only one + // of infinitely many points in the overlap. + // intersectionType = -1 + // contactTime = 0 + // contactPoint = triangle point closest to sphere.center + // 2. Objects initially separated but do not intersect later. The + // contactTime and contactPoint are invalid. + // intersectionType = 0 + // contactTime = 0 + // contactPoint = (0,0,0) + // 3. Objects initially separated but intersect later. + // intersectionType = +1 + // contactTime = first time T > 0 + // contactPoint = corresponding first contact + int intersectionType; + Real contactTime; + Vector3 contactPoint; + }; + + template + typename std::enable_if::value, Result>::type + operator()(Sphere3 const& sphere, Vector3 const& sphereVelocity, + Triangle3 const& triangle, Vector3 const& triangleVelocity) + { + Result result = { 0, (Real)0, { (Real)0, (Real)0, (Real)0 } }; + + // Test for initial overlap or contact. + DCPQuery, Triangle3> ptQuery; + auto ptResult = ptQuery(sphere.center, triangle); + Real rsqr = sphere.radius * sphere.radius; + if (ptResult.sqrDistance <= rsqr) + { + result.intersectionType = (ptResult.sqrDistance < rsqr ? -1 : +1); + result.contactTime = (Real)0; + result.contactPoint = ptResult.closest; + return result; + } + + // To reach here, the sphere and triangle are initially separated. + // Compute the velocity of the sphere relative to the triangle. + Vector3 V = sphereVelocity - triangleVelocity; + Real sqrLenV = Dot(V, V); + if (sqrLenV == (Real)0) + { + // The sphere and triangle are separated and the sphere is not + // moving relative to the triangle, so there is no contact. + // The 'result' is already set to the correct state for this + // case. + return result; + } + + // Compute the triangle edge directions E[], the vector U normal + // to the plane of the triangle, and compute the normals to the + // edges in the plane of the triangle. TODO: For a nondeforming + // triangle (or mesh of triangles), these quantities can all be + // precomputed to reduce the computational cost of the query. Add + // another operator()-query that accepts the precomputed values. + // TODO: When the triangle is deformable, these quantities must be + // computed, either by the caller or here. Optimize the code to + // compute the quantities on-demand (i.e. only when they are + // needed, but cache them for later use). + Vector3 E[3] = + { + triangle.v[1] - triangle.v[0], + triangle.v[2] - triangle.v[1], + triangle.v[0] - triangle.v[2] + }; + Real sqrLenE[3] = { Dot(E[0], E[0]), Dot(E[1], E[1]), Dot(E[2], E[2]) }; + Vector3 U = UnitCross(E[0], E[1]); + Vector3 ExU[3] = + { + Cross(E[0], U), + Cross(E[1], U), + Cross(E[2], U) + }; + + // Compute the vectors from the triangle vertices to the sphere + // center. + Vector3 Delta[3] = + { + sphere.center - triangle.v[0], + sphere.center - triangle.v[1], + sphere.center - triangle.v[2] + }; + + // Determine where the sphere center is located relative to the + // planes of the triangle offset faces of the sphere-swept volume. + Real dotUDelta0 = Dot(U, Delta[0]); + if (dotUDelta0 >= sphere.radius) + { + // The sphere is on the positive side of Dot(U,X-C) = r. If + // the sphere will contact the sphere-swept volume at a + // triangular face, it can do so only on the face of the + // aforementioned plane. + Real dotUV = Dot(U, V); + if (dotUV >= (Real)0) + { + // The sphere is moving away from, or parallel to, the + // plane of the triangle. The 'result' is already set to + // the correct state for this case. + return result; + } + + Real tbar = (sphere.radius - dotUDelta0) / dotUV; + bool foundContact = true; + for (int i = 0; i < 3; ++i) + { + Real phi = Dot(ExU[i], Delta[i]); + Real psi = Dot(ExU[i], V); + if (phi + psi * tbar > (Real)0) + { + foundContact = false; + break; + } + } + if (foundContact) + { + result.intersectionType = 1; + result.contactTime = tbar; + result.contactPoint = sphere.center + tbar * sphereVelocity; + return result; + } + } + else if (dotUDelta0 <= -sphere.radius) + { + // The sphere is on the positive side of Dot(-U,X-C) = r. If + // the sphere will contact the sphere-swept volume at a + // triangular face, it can do so only on the face of the + // aforementioned plane. + Real dotUV = Dot(U, V); + if (dotUV <= (Real)0) + { + // The sphere is moving away from, or parallel to, the + // plane of the triangle. The 'result' is already set to + // the correct state for this case. + return result; + } + + Real tbar = (-sphere.radius - dotUDelta0) / dotUV; + bool foundContact = true; + for (int i = 0; i < 3; ++i) + { + Real phi = Dot(ExU[i], Delta[i]); + Real psi = Dot(ExU[i], V); + if (phi + psi * tbar > (Real)0) + { + foundContact = false; + break; + } + } + if (foundContact) + { + result.intersectionType = 1; + result.contactTime = tbar; + result.contactPoint = sphere.center + tbar * sphereVelocity; + return result; + } + } + // else: The ray-sphere-swept-volume contact point (if any) cannot + // be on a triangular face of the sphere-swept-volume. + + // The sphere is moving towards the slab between the two planes + // of the sphere-swept volume triangular faces. Determine whether + // the ray intersects the half cylinders or sphere wedges of the + // sphere-swept volume. + + // Test for contact with half cylinders of the sphere-swept + // volume. First, precompute some dot products required in the + // computations. TODO: Optimize the code to compute the quantities + // on-demand (i.e. only when they are needed, but cache them for + // later use). + Real del[3], delp[3], nu[3]; + for (int im1 = 2, i = 0; i < 3; im1 = i++) + { + del[i] = Dot(E[i], Delta[i]); + delp[im1] = Dot(E[im1], Delta[i]); + nu[i] = Dot(E[i], V); + } + + for (int i = 2, ip1 = 0; ip1 < 3; i = ip1++) + { + Vector3 hatV = V - E[i] * nu[i] / sqrLenE[i]; + Real sqrLenHatV = Dot(hatV, hatV); + if (sqrLenHatV > (Real)0) + { + Vector3 hatDelta = Delta[i] - E[i] * del[i] / sqrLenE[i]; + Real alpha = -Dot(hatV, hatDelta); + if (alpha >= (Real)0) + { + Real sqrLenHatDelta = Dot(hatDelta, hatDelta); + Real beta = alpha * alpha - sqrLenHatV * (sqrLenHatDelta - rsqr); + if (beta >= (Real)0) + { + Real tbar = (alpha - std::sqrt(beta)) / sqrLenHatV; + + Real mu = Dot(ExU[i], Delta[i]); + Real omega = Dot(ExU[i], hatV); + if (mu + omega * tbar >= (Real)0) + { + if (del[i] + nu[i] * tbar >= (Real)0) + { + if (delp[i] + nu[i] * tbar <= (Real)0) + { + // The constraints are satisfied, so + // tbar is the first time of contact. + result.intersectionType = 1; + result.contactTime = tbar; + result.contactPoint = sphere.center + tbar * sphereVelocity; + return result; + } + } + } + } + } + } + } + + // Test for contact with sphere wedges of the sphere-swept + // volume. We know that |V|^2 > 0 because of a previous + // early-exit test. + for (int im1 = 2, i = 0; i < 3; im1 = i++) + { + Real alpha = -Dot(V, Delta[i]); + if (alpha >= (Real)0) + { + Real sqrLenDelta = Dot(Delta[i], Delta[i]); + Real beta = alpha * alpha - sqrLenV * (sqrLenDelta - rsqr); + if (beta >= (Real)0) + { + Real tbar = (alpha - std::sqrt(beta)) / sqrLenV; + if (delp[im1] + nu[im1] * tbar >= (Real)0) + { + if (del[i] + nu[i] * tbar <= (Real)0) + { + // The constraints are satisfied, so tbar + // is the first time of contact. + result.intersectionType = 1; + result.contactTime = tbar; + result.contactPoint = sphere.center + tbar * sphereVelocity; + return result; + } + } + } + } + } + + // The ray and sphere-swept volume do not intersect, so the sphere + // and triangle do not come into contact. The 'result' is already + // set to the correct state for this case. + return result; + } + + + // The implementation for arbitrary-precision types. + using QFN1 = QFNumber; + + struct ExactResult + { + // The cases are + // 1. Objects initially overlapping. The contactPoint is only one + // of infinitely many points in the overlap. + // intersectionType = -1 + // contactTime = 0 + // contactPoint = triangle point closest to sphere.center + // 2. Objects initially separated but do not intersect later. The + // contactTime and contactPoint are invalid. + // intersectionType = 0 + // contactTime = 0 + // contactPoint = (0,0,0) + // 3. Objects initially separated but intersect later. + // intersectionType = +1 + // contactTime = first time T > 0 + // contactPoint = corresponding first contact + int intersectionType; + + // The exact representation of the contact time and point. To + // convert to a floating-point type, use + // FloatType contactTime; + // Vector3 contactPoint; + // Result::Convert(result.contactTime, contactTime); + // Result::Convert(result.contactPoint, contactPoint); + + template + static void Convert(QFN1 const& input, OutputType& output) + { + output = static_cast(input); + } + + template + static void Convert(Vector3 const& input, Vector3& output) + { + for (int i = 0; i < 3; ++i) + { + output[i] = static_cast(input[i]); + } + } + + QFN1 contactTime; + Vector3 contactPoint; + }; + + template + typename std::enable_if::value, ExactResult>::type + operator()(Sphere3 const& sphere, Vector3 const& sphereVelocity, + Triangle3 const& triangle, Vector3 const& triangleVelocity) + { + // The default constructors for the members of 'result' set their + // own members to zero. + ExactResult result; + + // Test for initial overlap or contact. + DCPQuery, Triangle3> ptQuery; + auto ptResult = ptQuery(sphere.center, triangle); + Real rsqr = sphere.radius * sphere.radius; + if (ptResult.sqrDistance <= rsqr) + { + // The values result.contactTime and result.contactPoint[] + // are both zero, so we need only set the + // result.contactPoint[].x values. + result.intersectionType = (ptResult.sqrDistance < rsqr ? -1 : +1); + for (int j = 0; j < 3; ++j) + { + result.contactPoint[j].x = ptResult.closest[j]; + } + return result; + } + + // To reach here, the sphere and triangle are initially separated. + // Compute the velocity of the sphere relative to the triangle. + Vector3 V = sphereVelocity - triangleVelocity; + Real sqrLenV = Dot(V, V); + if (sqrLenV == (Real)0) + { + // The sphere and triangle are separated and the sphere is not + // moving relative to the triangle, so there is no contact. + // The 'result' is already set to the correct state for this + // case. + return result; + } + + // Compute the triangle edge directions E[], the vector U normal + // to the plane of the triangle, and compute the normals to the + // edges in the plane of the triangle. TODO: For a nondeforming + // triangle (or mesh of triangles), these quantities can all be + // precomputed to reduce the computational cost of the query. Add + // another operator()-query that accepts the precomputed values. + // TODO: When the triangle is deformable, these quantities must be + // computed, either by the caller or here. Optimize the code to + // compute the quantities on-demand (i.e. only when they are + // needed, but cache them for later use). + Vector3 E[3] = + { + triangle.v[1] - triangle.v[0], + triangle.v[2] - triangle.v[1], + triangle.v[0] - triangle.v[2] + }; + Real sqrLenE[3] = { Dot(E[0], E[0]), Dot(E[1], E[1]), Dot(E[2], E[2]) }; + // Use an unnormalized U for the plane of the triangle. This + // allows us to use quadratic fields for the comparisons of the + // constraints. + Vector3 U = Cross(E[0], E[1]); + Real sqrLenU = Dot(U, U); + Vector3 ExU[3] = + { + Cross(E[0], U), + Cross(E[1], U), + Cross(E[2], U) + }; + + // Compute the vectors from the triangle vertices to the sphere + // center. + Vector3 Delta[3] = + { + sphere.center - triangle.v[0], + sphere.center - triangle.v[1], + sphere.center - triangle.v[2] + }; + + // Determine where the sphere center is located relative to the + // planes of the triangle offset faces of the sphere-swept volume. + QFN1 const qfzero((Real)0, (Real)0, sqrLenU); + QFN1 element(Dot(U, Delta[0]), -sphere.radius, sqrLenU); + if (element >= qfzero) + { + // The sphere is on the positive side of Dot(U,X-C) = r|U|. + // If the sphere will contact the sphere-swept volume at a + // triangular face, it can do so only on the face of the + // aforementioned plane. + Real dotUV = Dot(U, V); + if (dotUV >= (Real)0) + { + // The sphere is moving away from, or parallel to, the + // plane of the triangle. The 'result' is already set + // to the correct state for this case. + return result; + } + + bool foundContact = true; + for (int i = 0; i < 3; ++i) + { + Real phi = Dot(ExU[i], Delta[i]); + Real psi = Dot(ExU[i], V); + QFN1 arg(psi * element.x - phi * dotUV, psi * element.y, sqrLenU); + if (arg > qfzero) + { + foundContact = false; + break; + } + } + if (foundContact) + { + result.intersectionType = 1; + result.contactTime.x = -element.x / dotUV; + result.contactTime.y = -element.y / dotUV; + for (int j = 0; j < 3; ++j) + { + result.contactPoint[j].x = sphere.center[j] + result.contactTime.x * sphereVelocity[j]; + result.contactPoint[j].y = result.contactTime.y * sphereVelocity[j]; + } + return result; + } + } + else + { + element.y = -element.y; + if (element <= qfzero) + { + // The sphere is on the positive side of Dot(-U,X-C) = r|U|. + // If the sphere will contact the sphere-swept volume at a + // triangular face, it can do so only on the face of the + // aforementioned plane. + Real dotUV = Dot(U, V); + if (dotUV <= (Real)0) + { + // The sphere is moving away from, or parallel to, the + // plane of the triangle. The 'result' is already set + // to the correct state for this case. + return result; + } + + bool foundContact = true; + for (int i = 0; i < 3; ++i) + { + Real phi = Dot(ExU[i], Delta[i]); + Real psi = Dot(ExU[i], V); + QFN1 arg(phi * dotUV - psi * element.x, -psi * element.y, sqrLenU); + if (arg > qfzero) + { + foundContact = false; + break; + } + } + if (foundContact) + { + result.intersectionType = 1; + result.contactTime.x = -element.x / dotUV; + result.contactTime.y = -element.y / dotUV; + for (int j = 0; j < 3; ++j) + { + result.contactPoint[j].x = sphere.center[j] + result.contactTime.x * sphereVelocity[j]; + result.contactPoint[j].y = result.contactTime.y * sphereVelocity[j]; + } + return result; + } + } + // else: The ray-sphere-swept-volume contact point (if any) + // cannot be on a triangular face of the sphere-swept-volume. + } + + // The sphere is moving towards the slab between the two planes + // of the sphere-swept volume triangular faces. Determine whether + // the ray intersects the half cylinders or sphere wedges of the + // sphere-swept volume. + + // Test for contact with half cylinders of the sphere-swept + // volume. First, precompute some dot products required in the + // computations. TODO: Optimize the code to compute the quantities + // on-demand (i.e. only when they are needed, but cache them for + // later use). + Real del[3], delp[3], nu[3]; + for (int im1 = 2, i = 0; i < 3; im1 = i++) + { + del[i] = Dot(E[i], Delta[i]); + delp[im1] = Dot(E[im1], Delta[i]); + nu[i] = Dot(E[i], V); + } + + for (int i = 2, ip1 = 0; ip1 < 3; i = ip1++) + { + Vector3 hatV = V - E[i] * nu[i] / sqrLenE[i]; + Real sqrLenHatV = Dot(hatV, hatV); + if (sqrLenHatV > (Real)0) + { + Vector3 hatDelta = Delta[i] - E[i] * del[i] / sqrLenE[i]; + Real alpha = -Dot(hatV, hatDelta); + if (alpha >= (Real)0) + { + Real sqrLenHatDelta = Dot(hatDelta, hatDelta); + Real beta = alpha * alpha - sqrLenHatV * (sqrLenHatDelta - rsqr); + if (beta >= (Real)0) + { + QFN1 const qfzero((Real)0, (Real)0, beta); + Real mu = Dot(ExU[i], Delta[i]); + Real omega = Dot(ExU[i], hatV); + QFN1 arg0(mu * sqrLenHatV + omega * alpha, -omega, beta); + if (arg0 >= qfzero) + { + QFN1 arg1(del[i] * sqrLenHatV + nu[i] * alpha, -nu[i], beta); + if (arg1 >= qfzero) + { + QFN1 arg2(delp[i] * sqrLenHatV + nu[i] * alpha, -nu[i], beta); + if (arg2 <= qfzero) + { + result.intersectionType = 1; + result.contactTime.x = alpha / sqrLenHatV; + result.contactTime.y = (Real)-1 / sqrLenHatV; + for (int j = 0; j < 3; ++j) + { + result.contactPoint[j].x = sphere.center[j] + result.contactTime.x * sphereVelocity[j]; + result.contactPoint[j].y = result.contactTime.y * sphereVelocity[j]; + } + return result; + } + } + } + } + } + } + } + + // Test for contact with sphere wedges of the sphere-swept + // volume. We know that |V|^2 > 0 because of a previous + // early-exit test. + for (int im1 = 2, i = 0; i < 3; im1 = i++) + { + Real alpha = -Dot(V, Delta[i]); + if (alpha >= (Real)0) + { + Real sqrLenDelta = Dot(Delta[i], Delta[i]); + Real beta = alpha * alpha - sqrLenV * (sqrLenDelta - rsqr); + if (beta >= (Real)0) + { + QFN1 const qfzero((Real)0, (Real)0, beta); + QFN1 arg0(delp[im1] * sqrLenV + nu[im1] * alpha, -nu[im1], beta); + if (arg0 >= qfzero) + { + QFN1 arg1(del[i] * sqrLenV + nu[i] * alpha, -nu[i], beta); + if (arg1 <= qfzero) + { + result.intersectionType = 1; + result.contactTime.x = alpha / sqrLenV; + result.contactTime.y = (Real)-1 / sqrLenV; + for (int j = 0; j < 3; ++j) + { + result.contactPoint[j].x = sphere.center[j] + result.contactTime.x * sphereVelocity[j]; + result.contactPoint[j].y = result.contactTime.y * sphereVelocity[j]; + } + return result; + } + } + } + } + } + + // The ray and sphere-swept volume do not intersect, so the sphere + // and triangle do not come into contact. The 'result' is already + // set to the correct state for this case. + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrTriangle2Triangle2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrTriangle2Triangle2.h new file mode 100644 index 0000000..fb3000d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrTriangle2Triangle2.h @@ -0,0 +1,150 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The test-intersection queries are based on the document +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The find-intersection query for stationary triangles is based on clipping +// one triangle against the edges of the other to compute the intersection +// set (if it exists). The find-intersection query for moving triangles is +// based on the previously mentioned document about the method of separating +// axes. + +namespace WwiseGTE +{ + // Test whether two triangles intersect using the method of separating + // axes. The set of intersection, if it exists, is not computed. The + // input triangles' vertices must be counterclockwise ordered. + template + class TIQuery, Triangle2> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Triangle2 const& triangle0, Triangle2 const& triangle1) + { + Result result = + { + !Separated(triangle0, triangle1) && !Separated(triangle1, triangle0) + }; + return result; + } + + protected: + // The triangle vertices are projected to t-values for the line P+t*D. + // The D-vector is nonzero but does not have to be unit length. The + // return value is +1 if all t >= 0, -1 if all t <= 0, but 0 otherwise + // in which case the line splits the triangle into two subtriangles, + // each of positive area. + int WhichSide(Triangle2 const& triangle, Vector2 const& P, Vector2 const& D) const + { + int positive = 0, negative = 0; + for (int i = 0; i < 3; ++i) + { + Real t = Dot(D, triangle.v[i] - P); + if (t > (Real)0) + { + ++positive; + } + else if (t < (Real)0) + { + --negative; + } + + if (positive && negative) + { + // The triangle has vertices strictly on both sides of + // the line, so the line splits the triangle into two + // subtriangles each of positive area. + return 0; + } + } + + // Either positive > 0 or negative > 0 but not both are positive. + return (positive > 0 ? +1 : -1); + } + + bool Separated(Triangle2 const& triangle0, Triangle2 const& triangle1) const + { + // Test edges of triangle0 for separation. Because of the + // counterclockwise ordering, the projection interval for + // triangle0 is [T,0] for some T < 0. Determine whether + // triangle1 is on the positive side of the line; if it is, + // the triangles are separated. + for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++) + { + // The potential separating axis is P+t*D. + Vector2 P = triangle0.v[i0]; + Vector2 D = Perp(triangle0.v[i1] - triangle0.v[i0]); + if (WhichSide(triangle1, P, D) > 0) + { + // The triangle1 projection interval is [a,b] where a > 0, + // so the triangles are separated. + return true; + } + } + return false; + } + }; + + // Find the convex polygon, segment or point of intersection of two + // triangles. The input triangles' vertices must be counterclockwise + // ordered. + template + class FIQuery, Triangle2> + { + public: + struct Result + { + // An intersection exists iff intersection.size() > 0. + std::vector> intersection; + }; + + Result operator()(Triangle2 const& triangle0, Triangle2 const& triangle1) + { + Result result; + + // Start with triangle1 and clip against the edges of triangle0. + std::vector> polygon = + { + triangle1.v[0], triangle1.v[1], triangle1.v[2] + }; + + typedef FIQuery>, Hyperplane<2, Real>> PPQuery; + PPQuery ppQuery; + + for (int i1 = 2, i0 = 0; i0 < 3; i1 = i0++) + { + // Create the clipping line for the current edge. The edge + // normal N points inside the triangle. + Vector2 P = triangle0.v[i0]; + Vector2 N = Perp(triangle0.v[i1] - triangle0.v[i0]); + Hyperplane<2, Real> clippingLine(N, Dot(N, P)); + + // Do the clipping operation. + auto ppResult = ppQuery(polygon, clippingLine); + if (ppResult.positivePolygon.size() == 0) + { + // The current clipped polygon is outside triangle0. + return result; + } + polygon = std::move(ppResult.positivePolygon); + } + + result.intersection = polygon; + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrTriangle3OrientedBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrTriangle3OrientedBox3.h new file mode 100644 index 0000000..bf28950 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IntrTriangle3OrientedBox3.h @@ -0,0 +1,207 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The test-intersection query is based on the document +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The find-intersection query clips the triangle against the faces of +// the oriented box. + +namespace WwiseGTE +{ + template + class TIQuery, OrientedBox3> + { + public: + struct Result + { + bool intersect; + }; + + Result operator()(Triangle3 const& triangle, OrientedBox3 const& box) + { + Result result; + + Real min0, max0, min1, max1; + Vector3 D, edge[3]; + + // Test direction of triangle normal. + edge[0] = triangle.v[1] - triangle.v[0]; + edge[1] = triangle.v[2] - triangle.v[0]; + D = Cross(edge[0], edge[1]); + min0 = Dot(D, triangle.v[0]); + max0 = min0; + GetProjection(D, box, min1, max1); + if (max1 < min0 || max0 < min1) + { + result.intersect = false; + return result; + } + + // Test direction of box faces. + for (int i = 0; i < 3; ++i) + { + D = box.axis[i]; + GetProjection(D, triangle, min0, max0); + Real DdC = Dot(D, box.center); + min1 = DdC - box.extent[i]; + max1 = DdC + box.extent[i]; + if (max1 < min0 || max0 < min1) + { + result.intersect = false; + return result; + } + } + + // Test direction of triangle-box edge cross products. + edge[2] = edge[1] - edge[0]; + for (int i0 = 0; i0 < 3; ++i0) + { + for (int i1 = 0; i1 < 3; ++i1) + { + D = Cross(edge[i0], box.axis[i1]); + GetProjection(D, triangle, min0, max0); + GetProjection(D, box, min1, max1); + if (max1 < min0 || max0 < min1) + { + result.intersect = false; + return result; + } + } + } + + result.intersect = true; + return result; + } + + private: + void GetProjection(Vector3 const& axis, Triangle3 const& triangle, Real& imin, Real& imax) + { + Real dot[3] = + { + Dot(axis, triangle.v[0]), + Dot(axis, triangle.v[1]), + Dot(axis, triangle.v[2]) + }; + + imin = dot[0]; + imax = imin; + + if (dot[1] < imin) + { + imin = dot[1]; + } + else if (dot[1] > imax) + { + imax = dot[1]; + } + + if (dot[2] < imin) + { + imin = dot[2]; + } + else if (dot[2] > imax) + { + imax = dot[2]; + } + } + + void GetProjection(Vector3 const& axis, OrientedBox3 const& box, Real& imin, Real& imax) + { + Real origin = Dot(axis, box.center); + Real maximumExtent = + std::fabs(box.extent[0] * Dot(axis, box.axis[0])) + + std::fabs(box.extent[1] * Dot(axis, box.axis[1])) + + std::fabs(box.extent[2] * Dot(axis, box.axis[2])); + + imin = origin - maximumExtent; + imax = origin + maximumExtent; + } + }; + + template + class FIQuery, OrientedBox3> + { + public: + struct Result + { + std::vector> insidePolygon; + std::vector>> outsidePolygons; + }; + + Result operator()(Triangle3 const& triangle, OrientedBox3 const& box) + { + Result result; + + // Start with the triangle and clip it against each face of the + // box. The largest number of vertices for the polygon of + // intersection is 7. + result.insidePolygon.resize(3); + for (int i = 0; i < 3; ++i) + { + result.insidePolygon[i] = triangle.v[i]; + } + + typedef FIQuery>, Hyperplane<3, Real>> PPQuery; + + Plane3 plane; + PPQuery ppQuery; + typename PPQuery::Result ppResult; + for (int dir = -1; dir <= 1; dir += 2) + { + for (int side = 0; side < 3; ++side) + { + // Create a plane for the box face that points inside the box. + plane.normal = ((Real)dir) * box.axis[side]; + plane.constant = Dot(plane.normal, box.center) - box.extent[side]; + + ppResult = ppQuery(result.insidePolygon, plane); + switch (ppResult.configuration) + { + case PPQuery::Configuration::SPLIT: + result.insidePolygon = ppResult.positivePolygon; + result.outsidePolygons.push_back(ppResult.negativePolygon); + break; + case PPQuery::Configuration::POSITIVE_SIDE_VERTEX: + case PPQuery::Configuration::POSITIVE_SIDE_EDGE: + case PPQuery::Configuration::POSITIVE_SIDE_STRICT: + // The result.insidePolygon is already + // ppResult.positivePolygon, but to make it clear, + // assign it here. + result.insidePolygon = ppResult.positivePolygon; + break; + case PPQuery::Configuration::NEGATIVE_SIDE_VERTEX: + case PPQuery::Configuration::NEGATIVE_SIDE_EDGE: + case PPQuery::Configuration::NEGATIVE_SIDE_STRICT: + result.insidePolygon.clear(); + result.outsidePolygons.push_back(ppResult.negativePolygon); + return result; + case PPQuery::Configuration::CONTAINED: + // A triangle coplanar with a box face will be + // processed as if it is inside the box. + result.insidePolygon = ppResult.intersection; + break; + default: + result.insidePolygon.clear(); + result.outsidePolygons.clear(); + break; + } + } + } + + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/InvSqrtEstimate.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/InvSqrtEstimate.h new file mode 100644 index 0000000..fbb65fd --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/InvSqrtEstimate.h @@ -0,0 +1,160 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// Minimax polynomial approximations to 1/sqrt(x). The polynomial p(x) of +// degree D minimizes the quantity maximum{|1/sqrt(x) - p(x)| : x in [1,2]} +// over all polynomials of degree D. + +namespace WwiseGTE +{ + template + class InvSqrtEstimate + { + public: + // The input constraint is x in [1,2]. For example, + // float x; // in [1,2] + // float result = InvSqrtEstimate::Degree<3>(x); + template + inline static Real Degree(Real x) + { + Real t = x - (Real)1; // t in (0,1] + return Evaluate(degree(), t); + } + + // The input constraint is x > 0. Range reduction is used to generate + // a/ value y in [1,2], call Evaluate(y), and combine the output with + // the proper exponent to obtain the approximation. For example, + // float x; // x > 0 + // float result = InvSqrtEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x) + { + Real adj, y; + int p; + Reduce(x, adj, y, p); + Real poly = Degree(y); + Real result = Combine(adj, poly, p); + return result; + } + + private: + // Metaprogramming and private implementation to allow specialization + // of a template member function. + template struct degree {}; + + inline static Real Evaluate(degree<1>, Real t) + { + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG1_C1; + poly = (Real)GTE_C_INVSQRT_DEG1_C0 + poly * t; + return poly; + } + + inline static Real Evaluate(degree<2>, Real t) + { + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG2_C2; + poly = (Real)GTE_C_INVSQRT_DEG2_C1 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG2_C0 + poly * t; + return poly; + } + + inline static Real Evaluate(degree<3>, Real t) + { + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG3_C3; + poly = (Real)GTE_C_INVSQRT_DEG3_C2 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG3_C1 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG3_C0 + poly * t; + return poly; + } + + inline static Real Evaluate(degree<4>, Real t) + { + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG4_C4; + poly = (Real)GTE_C_INVSQRT_DEG4_C3 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG4_C2 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG4_C1 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG4_C0 + poly * t; + return poly; + } + + inline static Real Evaluate(degree<5>, Real t) + { + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG5_C5; + poly = (Real)GTE_C_INVSQRT_DEG5_C4 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG5_C3 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG5_C2 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG5_C1 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG5_C0 + poly * t; + return poly; + } + + inline static Real Evaluate(degree<6>, Real t) + { + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG6_C6; + poly = (Real)GTE_C_INVSQRT_DEG6_C5 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG6_C4 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG6_C3 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG6_C2 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG6_C1 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG6_C0 + poly * t; + return poly; + } + + inline static Real Evaluate(degree<7>, Real t) + { + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG7_C7; + poly = (Real)GTE_C_INVSQRT_DEG7_C6 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG7_C5 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG7_C4 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG7_C3 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG7_C2 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG7_C1 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG7_C0 + poly * t; + return poly; + } + + inline static Real Evaluate(degree<8>, Real t) + { + Real poly; + poly = (Real)GTE_C_INVSQRT_DEG8_C8; + poly = (Real)GTE_C_INVSQRT_DEG8_C7 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG8_C6 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG8_C5 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG8_C4 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG8_C3 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG8_C2 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG8_C1 + poly * t; + poly = (Real)GTE_C_INVSQRT_DEG8_C0 + poly * t; + return poly; + } + + // Support for range reduction. + inline static void Reduce(Real x, Real& adj, Real& y, int& p) + { + y = std::frexp(x, &p); // y in [1/2,1) + y = ((Real)2) * y; // y in [1,2) + --p; + adj = (1 & p) * (Real)GTE_C_INV_SQRT_2 + (1 & ~p) * (Real)1; + p = -(p >> 1); + } + + inline static Real Combine(Real adj, Real y, int p) + { + return adj * std::ldexp(y, p); + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IsPlanarGraph.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IsPlanarGraph.h new file mode 100644 index 0000000..8070f88 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/IsPlanarGraph.h @@ -0,0 +1,485 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// Test whether an undirected graph is planar. The input positions must be +// unique and the input edges must be unique, so the number of positions is +// at least 2 and the number of edges is at least one. The elements of the +// edges array must be indices in {0,..,positions.size()-1}. +// +// A sort-and-sweep algorithm is used to determine edge-edge intersections. +// If none of the intersections occur at edge-interior points, the graph is +// planar. See Game Physics (2nd edition), Section 6.2.2: Culling with +// Axis-Aligned Bounding Boxes for such an algorithm. The operator() +// returns 'true' when the graph is planar. If it returns 'false', the +// 'invalidIntersections' set contains pairs of edges that intersect at an +// edge-interior point (that by definition is not a graph vertex). Each set +// element is a pair of indices into the 'edges' array; the indices are +// ordered so that element[0] < element[1]. The Real type must be chosen +// to guarantee exact computation of edge-edge intersections. + +namespace WwiseGTE +{ + template + class IsPlanarGraph + { + public: + IsPlanarGraph() + : + mZero(0), + mOne(1) + { + } + + enum + { + IPG_IS_PLANAR_GRAPH = 0, + IPG_INVALID_INPUT_SIZES = 1, + IPG_DUPLICATED_POSITIONS = 2, + IPG_DUPLICATED_EDGES = 4, + IPG_DEGENERATE_EDGES = 8, + IPG_EDGES_WITH_INVALID_VERTICES = 16, + IPG_INVALID_INTERSECTIONS = 32 + }; + + // The function returns a combination of the IPG_* flags listed in the + // previous enumeration. A combined value of 0 indicates the input + // forms a planar graph. If the combined value is not zero, you may + // examine the flags for the failure conditions and use the Get* + // member accessors to obtain specific information about the failure. + // If the positions.size() < 2 or edges.size() == 0, the + // IPG_INVALID_INPUT_SIZES flag is set. + + int operator()(std::vector> const& positions, + std::vector> const& edges) + { + mDuplicatedPositions.clear(); + mDuplicatedEdges.clear(); + mDegenerateEdges.clear(); + mEdgesWithInvalidVertices.clear(); + mInvalidIntersections.clear(); + + int flags = IsValidTopology(positions, edges); + if (flags == IPG_INVALID_INPUT_SIZES) + { + return flags; + } + + std::set overlappingRectangles; + ComputeOverlappingRectangles(positions, edges, overlappingRectangles); + for (auto key : overlappingRectangles) + { + // Get the endpoints of the line segments for the edges whose + // bounding rectangles overlapped. Determine whether the line + // segments intersect. If they do, determine how they + // intersect. + std::array e0 = edges[key.V[0]]; + std::array e1 = edges[key.V[1]]; + std::array const& p0 = positions[e0[0]]; + std::array const& p1 = positions[e0[1]]; + std::array const& q0 = positions[e1[0]]; + std::array const& q1 = positions[e1[1]]; + if (InvalidSegmentIntersection(p0, p1, q0, q1)) + { + mInvalidIntersections.push_back(key); + } + } + + if (mInvalidIntersections.size() > 0) + { + flags |= IPG_INVALID_INTERSECTIONS; + } + + return flags; + } + + // A pair of indices (v0,v1) into the positions array is stored as + // (min(v0,v1), max(v0,v1)). This supports sorted containers of + // edges. + struct OrderedEdge + { + OrderedEdge(int v0 = -1, int v1 = -1) + { + if (v0 < v1) + { + // v0 is minimum + V[0] = v0; + V[1] = v1; + } + else + { + // v1 is minimum + V[0] = v1; + V[1] = v0; + } + } + + bool operator<(OrderedEdge const& edge) const + { + // Lexicographical ordering used by std::array. + return V < edge.V; + } + + std::array V; + }; + + inline std::vector> const& GetDuplicatedPositions() const + { + return mDuplicatedPositions; + } + + inline std::vector> const& GetDuplicatedEdges() const + { + return mDuplicatedEdges; + } + + inline std::vector const& GetDegenerateEdges() const + { + return mDegenerateEdges; + } + + inline std::vector const& GetEdgesWithInvalidVertices() const + { + return mEdgesWithInvalidVertices; + } + + inline std::vector::OrderedEdge> const& + GetInvalidIntersections() const + { + return mInvalidIntersections; + } + + private: + class Endpoint + { + public: + Real value; // endpoint value + int type; // '0' if interval min, '1' if interval max. + int index; // index of interval containing this endpoint + + // Comparison operator for sorting. + bool operator<(Endpoint const& endpoint) const + { + if (value < endpoint.value) + { + return true; + } + if (value > endpoint.value) + { + return false; + } + return type < endpoint.type; + } + }; + + int IsValidTopology(std::vector> const& positions, + std::vector> const& edges) + { + int const numPositions = static_cast(positions.size()); + int const numEdges = static_cast(edges.size()); + if (numPositions < 2 || numEdges == 0) + { + // The graph must have at least one edge. + return IPG_INVALID_INPUT_SIZES; + } + + // The positions must be unique. + int flags = IPG_IS_PLANAR_GRAPH; + std::map, std::vector> uniquePositions; + for (int i = 0; i < numPositions; ++i) + { + std::array p = positions[i]; + auto iter = uniquePositions.find(p); + if (iter == uniquePositions.end()) + { + std::vector indices; + indices.push_back(i); + uniquePositions.insert(std::make_pair(p, indices)); + } + else + { + iter->second.push_back(i); + } + } + if (uniquePositions.size() < positions.size()) + { + // At least two positions are duplicated. + for (auto const& element : uniquePositions) + { + if (element.second.size() > 1) + { + mDuplicatedPositions.push_back(element.second); + } + } + flags |= IPG_DUPLICATED_POSITIONS; + } + + // The edges must be unique. + std::map> uniqueEdges; + for (int i = 0; i < numEdges; ++i) + { + OrderedEdge key(edges[i][0], edges[i][1]); + auto iter = uniqueEdges.find(key); + if (iter == uniqueEdges.end()) + { + std::vector indices; + indices.push_back(i); + uniqueEdges.insert(std::make_pair(key, indices)); + } + else + { + iter->second.push_back(i); + } + } + if (uniqueEdges.size() < edges.size()) + { + // At least two edges are duplicated, possibly even a pair of + // edges (v0,v1) and (v1,v0) which is not allowed because the + // graph is undirected. + for (auto const& element : uniqueEdges) + { + if (element.second.size() > 1) + { + mDuplicatedEdges.push_back(element.second); + } + } + flags |= IPG_DUPLICATED_EDGES; + } + + // The edges are represented as pairs of indices into the + // 'positions' array. The indices for a single edge must be + // different (no edges allowed from a vertex to itself) and all + // indices must be valid. At the same time, keep track of unique + // edges. + for (int i = 0; i < numEdges; ++i) + { + std::array e = edges[i]; + if (e[0] == e[1]) + { + // The edge is degenerate, originating and terminating at + // the same vertex. + mDegenerateEdges.push_back(i); + flags |= IPG_DEGENERATE_EDGES; + } + + if (e[0] < 0 || e[0] >= numPositions || e[1] < 0 || e[1] >= numPositions) + { + // The edge has an index that references a nonexistent + // vertex. + mEdgesWithInvalidVertices.push_back(i); + flags |= IPG_EDGES_WITH_INVALID_VERTICES; + } + } + + return flags; + } + + void ComputeOverlappingRectangles(std::vector> const& positions, + std::vector> const& edges, + std::set& overlappingRectangles) const + { + // Compute axis-aligned bounding rectangles for the edges. + int const numEdges = static_cast(edges.size()); + std::vector> emin(numEdges); + std::vector> emax(numEdges); + for (int i = 0; i < numEdges; ++i) + { + std::array e = edges[i]; + std::array const& p0 = positions[e[0]]; + std::array const& p1 = positions[e[1]]; + + for (int j = 0; j < 2; ++j) + { + if (p0[j] < p1[j]) + { + emin[i][j] = p0[j]; + emax[i][j] = p1[j]; + } + else + { + emin[i][j] = p1[j]; + emax[i][j] = p0[j]; + } + } + } + + // Get the rectangle endpoints. + int const numEndpoints = 2 * numEdges; + std::vector xEndpoints(numEndpoints); + std::vector yEndpoints(numEndpoints); + for (int i = 0, j = 0; i < numEdges; ++i) + { + xEndpoints[j].type = 0; + xEndpoints[j].value = emin[i][0]; + xEndpoints[j].index = i; + yEndpoints[j].type = 0; + yEndpoints[j].value = emin[i][1]; + yEndpoints[j].index = i; + ++j; + + xEndpoints[j].type = 1; + xEndpoints[j].value = emax[i][0]; + xEndpoints[j].index = i; + yEndpoints[j].type = 1; + yEndpoints[j].value = emax[i][1]; + yEndpoints[j].index = i; + ++j; + } + + // Sort the rectangle endpoints. + std::sort(xEndpoints.begin(), xEndpoints.end()); + std::sort(yEndpoints.begin(), yEndpoints.end()); + + // Sweep through the endpoints to determine overlapping + // x-intervals. Use an active set of rectangles to reduce the + // complexity of the algorithm. + std::set active; + for (int i = 0; i < numEndpoints; ++i) + { + Endpoint const& endpoint = xEndpoints[i]; + int index = endpoint.index; + if (endpoint.type == 0) // an interval 'begin' value + { + // In the 1D problem, the current interval overlaps with + // all the active intervals. In 2D this we also need to + // check for y-overlap. + for (auto activeIndex : active) + { + // Rectangles activeIndex and index overlap in the + // x-dimension. Test for overlap in the y-dimension. + std::array const& r0min = emin[activeIndex]; + std::array const& r0max = emax[activeIndex]; + std::array const& r1min = emin[index]; + std::array const& r1max = emax[index]; + if (r0max[1] >= r1min[1] && r0min[1] <= r1max[1]) + { + if (activeIndex < index) + { + overlappingRectangles.insert(OrderedEdge(activeIndex, index)); + } + else + { + overlappingRectangles.insert(OrderedEdge(index, activeIndex)); + } + } + } + active.insert(index); + } + else // an interval 'end' value + { + active.erase(index); + } + } + } + + bool InvalidSegmentIntersection( + std::array const& p0, std::array const& p1, + std::array const& q0, std::array const& q1) const + { + // We must solve the two linear equations + // p0 + t0 * (p1 - p0) = q0 + t1 * (q1 - q0) + // for the unknown variables t0 and t1. These may be written as + // t0 * (p1 - p0) - t1 * (q1 - q0) = q0 - p0 + // If denom = Dot(p1 - p0, Perp(q1 - q0)) is not zero, then + // t0 = Dot(q0 - p0, Perp(q1 - q0)) / denom = numer0 / denom + // t1 = Dot(q0 - p0, Perp(p1 - p0)) / denom = numer1 / denom + // For an invalid intersection, we need (t0,t1) with: + // ((0 < t0 < 1) and (0 <= t1 <= 1)) or ((0 <= t0 <= 1) and + // (0 < t1 < 1). + + std::array p1mp0, q1mq0, q0mp0; + for (int j = 0; j < 2; ++j) + { + p1mp0[j] = p1[j] - p0[j]; + q1mq0[j] = q1[j] - q0[j]; + q0mp0[j] = q0[j] - p0[j]; + } + + Real denom = p1mp0[0] * q1mq0[1] - p1mp0[1] * q1mq0[0]; + Real numer0 = q0mp0[0] * q1mq0[1] - q0mp0[1] * q1mq0[0]; + Real numer1 = q0mp0[0] * p1mp0[1] - q0mp0[1] * p1mp0[0]; + + if (denom != mZero) + { + // The lines of the segments are not parallel. + if (denom > mZero) + { + if (mZero <= numer0 && numer0 <= denom && mZero <= numer1 && numer1 <= denom) + { + // The segments intersect. + return (numer0 != mZero && numer0 != denom) || (numer1 != mZero && numer1 != denom); + } + else + { + return false; + } + } + else // denom < mZero + { + if (mZero >= numer0 && numer0 >= denom && mZero >= numer1 && numer1 >= denom) + { + // The segments intersect. + return (numer0 != mZero && numer0 != denom) || (numer1 != mZero && numer1 != denom); + } + else + { + return false; + } + } + } + else + { + // The lines of the segments are parallel. + if (numer0 != mZero || numer1 != mZero) + { + // The lines of the segments are separated. + return false; + } + else + { + // The segments lie on the same line. Compute the + // parameter intervals for the segments in terms of the + // t0-parameter and determine their overlap (if any). + std::array q1mp0; + for (int j = 0; j < 2; ++j) + { + q1mp0[j] = q1[j] - p0[j]; + } + Real sqrLenP1mP0 = p1mp0[0] * p1mp0[0] + p1mp0[1] * p1mp0[1]; + Real value0 = q0mp0[0] * p1mp0[0] + q0mp0[1] * p1mp0[1]; + Real value1 = q1mp0[0] * p1mp0[0] + q1mp0[1] * p1mp0[1]; + if ((value0 >= sqrLenP1mP0 && value1 >= sqrLenP1mP0) + || (value0 <= mZero && value1 <= mZero)) + { + // If the segments intersect, they must do so at + // endpoints of the segments. + return false; + } + else + { + // The segments overlap in a positive-length interval. + return true; + } + } + } + } + + std::vector> mDuplicatedPositions; + std::vector> mDuplicatedEdges; + std::vector mDegenerateEdges; + std::vector mEdgesWithInvalidVertices; + std::vector mInvalidIntersections; + Real mZero, mOne; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LCPSolver.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LCPSolver.h new file mode 100644 index 0000000..58e8fdf --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LCPSolver.h @@ -0,0 +1,628 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.10.04 + +#pragma once + +#include +#include +#include +#include + +// A class for solving the Linear Complementarity Problem (LCP) +// w = q + M * z, w^T * z = 0, w >= 0, z >= 0. The vectors q, w, and z are +// n-tuples and the matrix M is n-by-n. The inputs to Solve(...) are q and M. +// The outputs are w and z, which are valid when the returned bool is true but +// are invalid when the returned bool is false. +// +// The comments at the end of this file explain what the preprocessor symbol +// means regarding the LCP solver implementation. If the algorithm fails to +// converge within the specified maximum number of iterations, consider +// increasing the number and calling Solve(...) again. + +// Expose the following preprocessor symbol if you want the code to throw an +// exception the algorithm fails to converge. You can choose to trap the +// exception and handle it as you please. If you do not expose the +// preprocessor symbol, you can pass a Result object and check whether the +// algorithm failed to converge. Again, you can handle this as you please. +// +//#define GTE_THROW_ON_LCPSOLVER_ERRORS + +namespace WwiseGTE +{ + // Support templates for number of dimensions known at compile time or + // known only at run time. + template + class LCPSolver {}; + + + template + class LCPSolverShared + { + protected: + // Abstract base class construction. A virtual destructor is not + // provided because there are no required side effects when destroying + // objects from the derived classes. The member mMaxIterations is set + // by this call to the default value n*n. + LCPSolverShared(int n) + : + mNumIterations(0), + mVarBasic(nullptr), + mVarNonbasic(nullptr), + mNumCols(0), + mAugmented(nullptr), + mQMin(nullptr), + mMinRatio(nullptr), + mRatio(nullptr), + mPoly(nullptr), + mZero((Real)0), + mOne((Real)1) + { + if (n > 0) + { + mDimension = n; + mMaxIterations = n * n; + } + else + { + mDimension = 0; + mMaxIterations = 0; + } + } + + // Use this constructor when you need a specific representation of + // zero and of one to be used when manipulating the polynomials of the + // base class. In particular, this is needed to select the correct + // zero and correct one for QFNumber objects. + LCPSolverShared(int n, Real const& zero, Real const& one) + : + mNumIterations(0), + mVarBasic(nullptr), + mVarNonbasic(nullptr), + mNumCols(0), + mAugmented(nullptr), + mQMin(nullptr), + mMinRatio(nullptr), + mRatio(nullptr), + mPoly(nullptr), + mZero(zero), + mOne(one) + { + if (n > 0) + { + mDimension = n; + mMaxIterations = n * n; + } + else + { + mDimension = 0; + mMaxIterations = 0; + } + } + + public: + // Theoretically, when there is a solution the algorithm must converge + // in a finite number of iterations. The number of iterations depends + // on the problem at hand, but we need to guard against an infinite + // loop by limiting the number. The implementation uses a maximum + // number of n*n (chosen arbitrarily). You can set the number + // yourself, perhaps when a call to Solve fails--increase the number + // of iterations and call and solve again. + inline void SetMaxIterations(int maxIterations) + { + mMaxIterations = (maxIterations > 0 ? maxIterations : mDimension * mDimension); + } + + inline int GetMaxIterations() const + { + return mMaxIterations; + } + + // Access the actual number of iterations used in a call to Solve. + inline int GetNumIterations() const + { + return mNumIterations; + } + + enum Result + { + HAS_TRIVIAL_SOLUTION, + HAS_NONTRIVIAL_SOLUTION, + NO_SOLUTION, + FAILED_TO_CONVERGE, + INVALID_INPUT + }; + + protected: + // Bookkeeping of variables during the iterations of the solver. The + // name is either 'w' or 'z' and is used for human-readable debugging + // help. The 'index' is that for the original variables w[index] or + // z[index]. The 'complementary' index is the location of the + // complementary variable in mVarBasic[] or in mVarNonbasic[]. The + // 'tuple' is a pointer to &w[0] or &z[0], the choice based on name of + // 'w' or 'z', and is used to fill in the solution values (the + // variables are permuted during the pivoting algorithm). + struct Variable + { + char name; + int index; + int complementary; + Real* tuple; + }; + + // The augmented problem is w = q + M*z + z[n]*U = 0, where U is an + // n-tuple of 1-values. We manipulate the augmented matrix + // [M | U | p(t)] where p(t) is a column vector of polynomials of at + // most degree n. If p[r](t) is the polynomial for row r, then + // p[r](0) = q[r]. These are perturbations of q[r] designed so that + // the algorithm avoids degeneracies (a q-term becomes zero during the + // iterations). The basic variables are w[0] through w[n-1] and the + // nonbasic variables are z[0] through z[n]. The returned z consists + // only of z[0] through z[n-1]. + + // The derived classes ensure that the pointers point to the correct + // of elements for each array. The matrix M must be stored in + // row-major order. + bool Solve(Real const* q, Real const* M, Real* w, Real* z, Result* result) + { + // Perturb the q[r] constants to be polynomials of degree r+1 + // represented as an array of n+1 coefficients. The coefficient + // with index r+1 is 1 and the coefficients with indices larger + // than r+1 are 0. + for (int r = 0; r < mDimension; ++r) + { + mPoly[r] = &Augmented(r, mDimension + 1); + MakeZero(mPoly[r]); + mPoly[r][0] = q[r]; + mPoly[r][r + 1] = mOne; + } + + // Determine whether there is the trivial solution w = z = 0. + Copy(mPoly[0], mQMin); + int basic = 0; + for (int r = 1; r < mDimension; ++r) + { + if (LessThan(mPoly[r], mQMin)) + { + Copy(mPoly[r], mQMin); + basic = r; + } + } + + if (!LessThanZero(mQMin)) + { + for (int r = 0; r < mDimension; ++r) + { + w[r] = q[r]; + z[r] = mZero; + } + + if (result) + { + *result = HAS_TRIVIAL_SOLUTION; + } + return true; + } + + // Initialize the remainder of the augmented matrix with M and U. + for (int r = 0; r < mDimension; ++r) + { + for (int c = 0; c < mDimension; ++c) + { + Augmented(r, c) = M[c + mDimension * r]; + } + Augmented(r, mDimension) = mOne; + } + + // Keep track of when the variables enter and exit the dictionary, + // including where complementary variables are relocated. + for (int i = 0; i <= mDimension; ++i) + { + mVarBasic[i].name = 'w'; + mVarBasic[i].index = i; + mVarBasic[i].complementary = i; + mVarBasic[i].tuple = w; + mVarNonbasic[i].name = 'z'; + mVarNonbasic[i].index = i; + mVarNonbasic[i].complementary = i; + mVarNonbasic[i].tuple = z; + } + + // The augmented variable z[n] is the initial driving variable for + // pivoting. The equation 'basic' is the one to solve for z[n] + // and pivoting with w[basic]. The last column of M remains all + // 1-values for this initial step, so no algebraic computations + // occur for M[r][n]. + int driving = mDimension; + for (int r = 0; r < mDimension; ++r) + { + if (r != basic) + { + for (int c = 0; c < mNumCols; ++c) + { + if (c != mDimension) + { + Augmented(r, c) -= Augmented(basic, c); + } + } + } + } + + for (int c = 0; c < mNumCols; ++c) + { + if (c != mDimension) + { + Augmented(basic, c) = -Augmented(basic, c); + } + } + + mNumIterations = 0; + for (int i = 0; i < mMaxIterations; ++i, ++mNumIterations) + { + // The basic variable of equation 'basic' exited the + // dictionary, so/ its complementary (nonbasic) variable must + // become the next driving variable in order for it to enter + // the dictionary. + int nextDriving = mVarBasic[basic].complementary; + mVarNonbasic[nextDriving].complementary = driving; + std::swap(mVarBasic[basic], mVarNonbasic[driving]); + if (mVarNonbasic[driving].index == mDimension) + { + // The algorithm has converged. + for (int r = 0; r < mDimension; ++r) + { + mVarBasic[r].tuple[mVarBasic[r].index] = mPoly[r][0]; + } + for (int c = 0; c <= mDimension; ++c) + { + int index = mVarNonbasic[c].index; + if (index < mDimension) + { + mVarNonbasic[c].tuple[index] = mZero; + } + } + if (result) + { + *result = HAS_NONTRIVIAL_SOLUTION; + } + return true; + } + + // Determine the 'basic' equation for which the ratio + // -q[r]/M(r,driving) is minimized among all equations r with + // M(r,driving) < 0. + driving = nextDriving; + basic = -1; + for (int r = 0; r < mDimension; ++r) + { + if (Augmented(r, driving) < mZero) + { + Real factor = -mOne / Augmented(r, driving); + Multiply(mPoly[r], factor, mRatio); + if (basic == -1 || LessThan(mRatio, mMinRatio)) + { + Copy(mRatio, mMinRatio); + basic = r; + } + } + } + + if (basic == -1) + { + // The coefficients of z[driving] in all the equations are + // nonnegative, so the z[driving] variable cannot leave + // the dictionary. There is no solution to the LCP. + for (int r = 0; r < mDimension; ++r) + { + w[r] = mZero; + z[r] = mZero; + } + + if (result) + { + *result = NO_SOLUTION; + } + return false; + } + + // Solve the basic equation so that z[driving] enters the + // dictionary and w[basic] exits the dictionary. + Real invDenom = mOne / Augmented(basic, driving); + for (int r = 0; r < mDimension; ++r) + { + if (r != basic && Augmented(r, driving) != mZero) + { + Real multiplier = Augmented(r, driving) * invDenom; + for (int c = 0; c < mNumCols; ++c) + { + if (c != driving) + { + Augmented(r, c) -= Augmented(basic, c) * multiplier; + } + else + { + Augmented(r, driving) = multiplier; + } + } + } + } + + for (int c = 0; c < mNumCols; ++c) + { + if (c != driving) + { + Augmented(basic, c) = -Augmented(basic, c) * invDenom; + } + else + { + Augmented(basic, driving) = invDenom; + } + } + } + + // Numerical round-off errors can cause the Lemke algorithm not to + // converge. In particular, the code above has a test + // if (mAugmented[r][driving] < (Real)0) { ... } + // to determine the 'basic' equation with which to pivot. It is + // possible that theoretically mAugmented[r][driving]is zero but + // rounding errors cause it to be slightly negative. If + // theoretically all mAugmented[r][driving] >= 0, there is no + // solution to the LCP. With the rounding errors, if the + // algorithm fails to converge within the specified number of + // iterations, NO_SOLUTION is returned, which is hopefully the + // correct result. It is also possible that the rounding errors + // lead to a NO_SOLUTION (returned from inside the loop) when in + // fact there is a solution. When the LCP solver is used by + // intersection testing algorithms, the hope is that + // misclassifications occur only when the two objects are nearly + // in tangential contact. + // + // To determine whether the rounding errors are the problem, you + // can execute the query using exact arithmetic with the following + // type used for 'Real' (replacing 'float' or 'double') of + // BSRational Rational. + // + // That said, if the algorithm fails to converge and you believe + // that the rounding errors are not causing this, please file a + // bug report and provide the input data to the solver. + +#if defined(GTE_THROW_ON_LCPSOLVER_ERRORS) + LogError("LCPSolverShared::Solve failed to converge."); +#endif + if (result) + { + *result = FAILED_TO_CONVERGE; + } + return false; + } + + // Access mAugmented as a 2-dimensional array. + inline Real const& Augmented(int row, int col) const + { + return mAugmented[col + mNumCols * row]; + } + + inline Real& Augmented(int row, int col) + { + return mAugmented[col + mNumCols * row]; + } + + // Support for polynomials with n+1 coefficients and degree no larger + // than n. + void MakeZero(Real* poly) + { + for (int i = 0; i <= mDimension; ++i) + { + poly[i] = mZero; + } + } + + void Copy(Real const* poly0, Real* poly1) + { + for (int i = 0; i <= mDimension; ++i) + { + poly1[i] = poly0[i]; + } + } + + bool LessThan(Real const* poly0, Real const* poly1) + { + for (int i = 0; i <= mDimension; ++i) + { + if (poly0[i] < poly1[i]) + { + return true; + } + + if (poly0[i] > poly1[i]) + { + return false; + } + } + + return false; + } + + bool LessThanZero(Real const* poly) + { + for (int i = 0; i <= mDimension; ++i) + { + if (poly[i] < mZero) + { + return true; + } + + if (poly[i] > mZero) + { + return false; + } + } + + return false; + } + + void Multiply(Real const* poly, Real scalar, Real* product) + { + for (int i = 0; i <= mDimension; ++i) + { + product[i] = poly[i] * scalar; + } + } + + int mDimension; + int mMaxIterations; + int mNumIterations; + + // These pointers are set by the derived-class constructors to arrays + // that have the correct number of elements. The arrays mVarBasic, + // mVarNonbasic, mQMin, mMinRatio, and mRatio each have n+1 elements. + // The mAugmented array has n rows and 2*(n+1) columns stored in + // row-major order in a 1-dimensional array. The array of pointers + // mPoly has n elements. + Variable* mVarBasic; + Variable* mVarNonbasic; + int mNumCols; + Real* mAugmented; + Real* mQMin; + Real* mMinRatio; + Real* mRatio; + Real** mPoly; + Real mZero, mOne; + }; + + + template + class LCPSolver : public LCPSolverShared + { + public: + // Construction. The member mMaxIterations is set by this call to the + // default value n*n. + LCPSolver() + : + LCPSolverShared(n) + { + this->mVarBasic = mArrayVarBasic.data(); + this->mVarNonbasic = mArrayVarNonbasic.data(); + this->mNumCols = 2 * (n + 1); + this->mAugmented = mArrayAugmented.data(); + this->mQMin = mArrayQMin.data(); + this->mMinRatio = mArrayMinRatio.data(); + this->mRatio = mArrayRatio.data(); + this->mPoly = mArrayPoly.data(); + } + + // Use this constructor when you need a specific representation of + // zero and of one to be used when manipulating the polynomials of the + // base class. In particular, this is needed to select the correct + // zero and correct one for QFNumber objects. + LCPSolver(Real const& zero, Real const& one) + : + LCPSolverShared(n, zero, one) + { + this->mVarBasic = mArrayVarBasic.data(); + this->mVarNonbasic = mArrayVarNonbasic.data(); + this->mNumCols = 2 * (n + 1); + this->mAugmented = mArrayAugmented.data(); + this->mQMin = mArrayQMin.data(); + this->mMinRatio = mArrayMinRatio.data(); + this->mRatio = mArrayRatio.data(); + this->mPoly = mArrayPoly.data(); + } + + // If you want to know specifically why 'true' or 'false' was + // returned, pass the address of a Result variable as the last + // parameter. + bool Solve(std::array const& q, std::array, n> const& M, + std::array& w, std::array& z, + typename LCPSolverShared::Result* result = nullptr) + { + return LCPSolverShared::Solve(q.data(), M.front().data(), w.data(), z.data(), result); + } + + private: + std::array::Variable, n + 1> mArrayVarBasic; + std::array::Variable, n + 1> mArrayVarNonbasic; + std::array mArrayAugmented; + std::array mArrayQMin; + std::array mArrayMinRatio; + std::array mArrayRatio; + std::array mArrayPoly; + }; + + + template + class LCPSolver : public LCPSolverShared + { + public: + // Construction. The member mMaxIterations is set by this call to the + // default value n*n. + LCPSolver(int n) + : + LCPSolverShared(n) + { + if (n > 0) + { + mVectorVarBasic.resize(n + 1); + mVectorVarNonbasic.resize(n + 1); + mVectorAugmented.resize(2 * (n + 1) * n); + mVectorQMin.resize(n + 1); + mVectorMinRatio.resize(n + 1); + mVectorRatio.resize(n + 1); + mVectorPoly.resize(n); + + this->mVarBasic = mVectorVarBasic.data(); + this->mVarNonbasic = mVectorVarNonbasic.data(); + this->mNumCols = 2 * (n + 1); + this->mAugmented = mVectorAugmented.data(); + this->mQMin = mVectorQMin.data(); + this->mMinRatio = mVectorMinRatio.data(); + this->mRatio = mVectorRatio.data(); + this->mPoly = mVectorPoly.data(); + } + } + + // The input q must have n elements and the input M must be an n-by-n + // matrix stored in row-major order. The outputs w and z have n + // elements. If you want to know specifically why 'true' or 'false' + // was returned, pass the address of a Result variable as the last + // parameter. + bool Solve(std::vector const& q, std::vector const& M, + std::vector& w, std::vector& z, + typename LCPSolverShared::Result* result = nullptr) + { + if (this->mDimension > static_cast(q.size()) + || this->mDimension * this->mDimension > static_cast(M.size())) + { + if (result) + { + *result = this->INVALID_INPUT; + } + return false; + } + + if (this->mDimension > static_cast(w.size())) + { + w.resize(this->mDimension); + } + + if (this->mDimension > static_cast(z.size())) + { + z.resize(this->mDimension); + } + + return LCPSolverShared::Solve(q.data(), M.data(), w.data(), z.data(), result); + } + + private: + std::vector::Variable> mVectorVarBasic; + std::vector::Variable> mVectorVarNonbasic; + std::vector mVectorAugmented; + std::vector mVectorQMin; + std::vector mVectorMinRatio; + std::vector mVectorRatio; + std::vector mVectorPoly; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LevenbergMarquardtMinimizer.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LevenbergMarquardtMinimizer.h new file mode 100644 index 0000000..d1ca466 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LevenbergMarquardtMinimizer.h @@ -0,0 +1,277 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// See GteGaussNewtonMinimizer.h for a formulation of the minimization +// problem and how Levenberg-Marquardt relates to Gauss-Newton. + +namespace WwiseGTE +{ + template + class LevenbergMarquardtMinimizer + { + public: + // Convenient types for the domain vectors, the range vectors, the + // function F and the Jacobian J. + typedef GVector DVector; // numPDimensions + typedef GVector RVector; // numFDImensions + typedef GMatrix JMatrix; // numFDimensions-by-numPDimensions + typedef GMatrix JTJMatrix; // numPDimensions-by-numPDimensions + typedef GVector JTFVector; // numPDimensions + typedef std::function FFunction; + typedef std::function JFunction; + typedef std::function JPlusFunction; + + // Create the minimizer that computes F(p) and J(p) directly. + LevenbergMarquardtMinimizer(int numPDimensions, int numFDimensions, + FFunction const& inFFunction, JFunction const& inJFunction) + : + mNumPDimensions(numPDimensions), + mNumFDimensions(numFDimensions), + mFFunction(inFFunction), + mJFunction(inJFunction), + mF(mNumFDimensions), + mJ(mNumFDimensions, mNumPDimensions), + mJTJ(mNumPDimensions, mNumPDimensions), + mNegJTF(mNumPDimensions), + mDecomposer(mNumPDimensions), + mUseJFunction(true) + { + LogAssert(mNumPDimensions > 0 && mNumFDimensions > 0, "Invalid dimensions."); + } + + // Create the minimizer that computes J^T(p)*J(p) and -J(p)*F(p). + LevenbergMarquardtMinimizer(int numPDimensions, int numFDimensions, + FFunction const& inFFunction, JPlusFunction const& inJPlusFunction) + : + mNumPDimensions(numPDimensions), + mNumFDimensions(numFDimensions), + mFFunction(inFFunction), + mJPlusFunction(inJPlusFunction), + mF(mNumFDimensions), + mJ(mNumFDimensions, mNumPDimensions), + mJTJ(mNumPDimensions, mNumPDimensions), + mNegJTF(mNumPDimensions), + mDecomposer(mNumPDimensions), + mUseJFunction(false) + { + LogAssert(mNumPDimensions > 0 && mNumFDimensions > 0, "Invalid dimensions."); + } + + // Disallow copy, assignment and move semantics. + LevenbergMarquardtMinimizer(LevenbergMarquardtMinimizer const&) = delete; + LevenbergMarquardtMinimizer& operator=(LevenbergMarquardtMinimizer const&) = delete; + LevenbergMarquardtMinimizer(LevenbergMarquardtMinimizer&&) = delete; + LevenbergMarquardtMinimizer& operator=(LevenbergMarquardtMinimizer&&) = delete; + + inline int GetNumPDimensions() const { return mNumPDimensions; } + inline int GetNumFDimensions() const { return mNumFDimensions; } + + // The lambda is positive, the multiplier is positive, and the initial + // guess for the p-parameter is p0. Typical choices are lambda = + // 0.001 and multiplier = 10. TODO: Explain lambda in more detail, + // Multiview Geometry mentions lambda = 0.001*average(diagonal(JTJ)), + // but let's just expose the factor in front of the average. + + struct Result + { + DVector minLocation; + Real minError; + Real minErrorDifference; + Real minUpdateLength; + size_t numIterations; + size_t numAdjustments; + bool converged; + }; + + Result operator()(DVector const& p0, size_t maxIterations, + Real updateLengthTolerance, Real errorDifferenceTolerance, + Real lambdaFactor, Real lambdaAdjust, size_t maxAdjustments) + { + Result result; + result.minLocation = p0; + result.minError = std::numeric_limits::max(); + result.minErrorDifference = std::numeric_limits::max(); + result.minUpdateLength = (Real)0; + result.numIterations = 0; + result.numAdjustments = 0; + result.converged = false; + + // As a simple precaution, ensure that the lambda inputs are + // valid. If invalid, fall back to Gauss-Newton iteration. + if (lambdaFactor <= (Real)0 || lambdaAdjust <= (Real)0) + { + maxAdjustments = 1; + lambdaFactor = (Real)0; + lambdaAdjust = (Real)1; + } + + // As a simple precaution, ensure the tolerances are nonnegative. + updateLengthTolerance = std::max(updateLengthTolerance, (Real)0); + errorDifferenceTolerance = std::max(errorDifferenceTolerance, (Real)0); + + // Compute the initial error. + mFFunction(p0, mF); + result.minError = Dot(mF, mF); + + // Do the Levenberg-Marquart iterations. + auto pCurrent = p0; + for (result.numIterations = 1; result.numIterations <= maxIterations; ++result.numIterations) + { + std::pair status; + DVector pNext; + for (result.numAdjustments = 0; result.numAdjustments < maxAdjustments; ++result.numAdjustments) + { + status = DoIteration(pCurrent, lambdaFactor, updateLengthTolerance, + errorDifferenceTolerance, pNext, result); + if (status.first) + { + // Either the Cholesky decomposition failed or the + // iterates converged within tolerance. TODO: See the + // note in DoIteration about not failing on Cholesky + // decomposition. + return result; + } + + if (status.second) + { + // The error has been reduced but we have not yet + // converged within tolerance. + break; + } + + lambdaFactor *= lambdaAdjust; + } + + if (result.numAdjustments < maxAdjustments) + { + // The current value of lambda led us to an update that + // reduced the error, but the error is not yet small + // enough to conclude we converged. Reduce lambda for the + // next outer-loop iteration. + lambdaFactor /= lambdaAdjust; + } + else + { + // All lambdas tried during the inner-loop iteration did + // not lead to a reduced error. If we do nothing here, + // the next inner-loop iteration will continue to multiply + // lambda, risking eventual floating-point overflow. To + // avoid this, fall back to a Gauss-Newton iterate. + status = DoIteration(pCurrent, lambdaFactor, updateLengthTolerance, + errorDifferenceTolerance, pNext, result); + if (status.first) + { + // Either the Cholesky decomposition failed or the + // iterates converged within tolerance. TODO: See the + // note in DoIteration about not failing on Cholesky + // decomposition. + return result; + } + } + + pCurrent = pNext; + } + + return result; + } + + private: + void ComputeLinearSystemInputs(DVector const& pCurrent, Real lambda) + { + if (mUseJFunction) + { + mJFunction(pCurrent, mJ); + mJTJ = MultiplyATB(mJ, mJ); + mNegJTF = -(mF * mJ); + } + else + { + mJPlusFunction(pCurrent, mJTJ, mNegJTF); + } + + Real diagonalSum(0); + for (int i = 0; i < mNumPDimensions; ++i) + { + diagonalSum += mJTJ(i, i); + } + + Real diagonalAdjust = lambda * diagonalSum / static_cast(mNumPDimensions); + for (int i = 0; i < mNumPDimensions; ++i) + { + mJTJ(i, i) += diagonalAdjust; + } + } + + // The returned 'first' is true when the linear system cannot be + // solved (result.converged is false in this case) or when the + // error is reduced to within the tolerances specified by the caller + // (result.converged is true in this case). When the 'first' value + // is true, the 'second' value is true when the error is reduced or + // false when it is not. + std::pair DoIteration(DVector const& pCurrent, Real lambdaFactor, + Real updateLengthTolerance, Real errorDifferenceTolerance, DVector& pNext, + Result& result) + { + ComputeLinearSystemInputs(pCurrent, lambdaFactor); + if (!mDecomposer.Factor(mJTJ)) + { + // TODO: The matrix mJTJ is positive semi-definite, so the + // failure can occur when mJTJ has a zero eigenvalue in + // which case mJTJ is not invertible. Generate an iterate + // anyway, perhaps using gradient descent? + return std::make_pair(true, false); + } + mDecomposer.SolveLower(mJTJ, mNegJTF); + mDecomposer.SolveUpper(mJTJ, mNegJTF); + + pNext = pCurrent + mNegJTF; + mFFunction(pNext, mF); + Real error = Dot(mF, mF); + if (error < result.minError) + { + result.minErrorDifference = result.minError - error; + result.minUpdateLength = Length(mNegJTF); + result.minLocation = pNext; + result.minError = error; + if (result.minErrorDifference <= errorDifferenceTolerance + || result.minUpdateLength <= updateLengthTolerance) + { + result.converged = true; + return std::make_pair(true, true); + } + else + { + return std::make_pair(false, true); + } + } + else + { + return std::make_pair(false, false); + } + } + + int mNumPDimensions, mNumFDimensions; + FFunction mFFunction; + JFunction mJFunction; + JPlusFunction mJPlusFunction; + + // Storage for J^T(p)*J(p) and -J^T(p)*F(p) during the iterations. + RVector mF; + JMatrix mJ; + JTJMatrix mJTJ; + JTFVector mNegJTF; + + CholeskyDecomposition mDecomposer; + + bool mUseJFunction; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LexicoArray2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LexicoArray2.h new file mode 100644 index 0000000..51171c1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LexicoArray2.h @@ -0,0 +1,160 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +namespace WwiseGTE +{ + // A template class to provide 2D array access that conforms to row-major + // order (RowMajor = true) or column-major order (RowMajor = false). The + template + class LexicoArray2 {}; + + // The array dimensions are known only at run time. + template + class LexicoArray2 + { + public: + LexicoArray2(int numRows, int numCols, Real* matrix) + : + mNumRows(numRows), + mNumCols(numCols), + mMatrix(matrix) + { + } + + inline int GetNumRows() const + { + return mNumRows; + } + + inline int GetNumCols() const + { + return mNumCols; + } + + inline Real& operator()(int r, int c) + { + return mMatrix[c + mNumCols * r]; + } + + inline Real const& operator()(int r, int c) const + { + return mMatrix[c + mNumCols * r]; + } + + private: + int mNumRows, mNumCols; + Real* mMatrix; + }; + + template + class LexicoArray2 + { + public: + LexicoArray2(int numRows, int numCols, Real* matrix) + : + mNumRows(numRows), + mNumCols(numCols), + mMatrix(matrix) + { + } + + inline int GetNumRows() const + { + return mNumRows; + } + + inline int GetNumCols() const + { + return mNumCols; + } + + inline Real& operator()(int r, int c) + { + return mMatrix[r + mNumRows * c]; + } + + inline Real const& operator()(int r, int c) const + { + return mMatrix[r + mNumRows * c]; + } + + private: + int mNumRows, mNumCols; + Real* mMatrix; + }; + + // The array dimensions are known at compile time. + template + class LexicoArray2 + { + public: + LexicoArray2(Real* matrix) + : + mMatrix(matrix) + { + } + + inline int GetNumRows() const + { + return NumRows; + } + + inline int GetNumCols() const + { + return NumCols; + } + + inline Real& operator()(int r, int c) + { + return mMatrix[c + NumCols * r]; + } + + inline Real const& operator()(int r, int c) const + { + return mMatrix[c + NumCols * r]; + } + + private: + Real* mMatrix; + }; + + template + class LexicoArray2 + { + public: + LexicoArray2(Real* matrix) + : + mMatrix(matrix) + { + } + + inline int GetNumRows() const + { + return NumRows; + } + + inline int GetNumCols() const + { + return NumCols; + } + + inline Real& operator()(int r, int c) + { + return mMatrix[r + NumRows * c]; + } + + inline Real const& operator()(int r, int c) const + { + return mMatrix[r + NumRows * c]; + } + + private: + Real* mMatrix; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Line.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Line.h new file mode 100644 index 0000000..82b3aa1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Line.h @@ -0,0 +1,89 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The line is represented by P+t*D, where P is an origin point, D is a +// unit-length direction vector, and t is any real number. The user must +// ensure that D is unit length. + +namespace WwiseGTE +{ + template + class Line + { + public: + // Construction and destruction. The default constructor sets the + // origin to (0,...,0) and the line direction to (1,0,...,0). + Line() + { + origin.MakeZero(); + direction.MakeUnit(0); + } + + Line(Vector const& inOrigin, Vector const& inDirection) + : + origin(inOrigin), + direction(inDirection) + { + } + + // Public member access. The direction must be unit length. + Vector origin, direction; + + public: + // Comparisons to support sorted containers. + bool operator==(Line const& line) const + { + return origin == line.origin && direction == line.direction; + } + + bool operator!=(Line const& line) const + { + return !operator==(line); + } + + bool operator< (Line const& line) const + { + if (origin < line.origin) + { + return true; + } + + if (origin > line.origin) + { + return false; + } + + return direction < line.direction; + } + + bool operator<=(Line const& line) const + { + return !line.operator<(*this); + } + + bool operator> (Line const& line) const + { + return line.operator<(*this); + } + + bool operator>=(Line const& line) const + { + return !operator<(line); + } + }; + + // Template aliases for convenience. + template + using Line2 = Line<2, Real>; + + template + using Line3 = Line<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LinearSystem.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LinearSystem.h new file mode 100644 index 0000000..741f881 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LinearSystem.h @@ -0,0 +1,342 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// Solve linear systems of equations where the matrix A is NxN. The return +// value of a function is 'true' when A is invertible. In this case the +// solution X and the solution is valid. If the return value is 'false', A +// is not invertible and X and Y are invalid, so do not use them. When a +// matrix is passed as Real*, the storage order is assumed to be the one +// consistent with your choice of GTE_USE_ROW_MAJOR or GTE_USE_COL_MAJOR. +// +// The linear solvers that use the conjugate gradient algorithm are based +// on the discussion in "Matrix Computations, 2nd edition" by G. H. Golub +// and Charles F. Van Loan, The Johns Hopkins Press, Baltimore MD, Fourth +// Printing 1993. + +namespace WwiseGTE +{ + template + class LinearSystem + { + public: + // Solve 2x2, 3x3, and 4x4 systems by inverting the matrix directly. + // This avoids the overhead of Gaussian elimination in small + // dimensions. + static bool Solve(Matrix2x2 const& A, Vector2 const& B, Vector2& X) + { + bool invertible; + Matrix2x2 invA = Inverse(A, &invertible); + if (invertible) + { + X = invA * B; + } + else + { + X = Vector2::Zero(); + } + return invertible; + } + + static bool Solve(Matrix3x3 const& A, Vector3 const& B, Vector3& X) + { + bool invertible; + Matrix3x3 invA = Inverse(A, &invertible); + if (invertible) + { + X = invA * B; + } + else + { + X = Vector3::Zero(); + } + return invertible; + } + + static bool Solve(Matrix4x4 const& A, Vector4 const& B, Vector4& X) + { + bool invertible; + Matrix4x4 invA = Inverse(A, &invertible); + if (invertible) + { + X = invA * B; + } + else + { + X = Vector4::Zero(); + } + return invertible; + } + + // Solve A*X = B, where B is Nx1 and the solution X is Nx1. + static bool Solve(int N, Real const* A, Real const* B, Real* X) + { + Real determinant; + return GaussianElimination()(N, A, nullptr, determinant, B, X, + nullptr, 0, nullptr); + } + + // Solve A*X = B, where B is NxM and the solution X is NxM. + static bool Solve(int N, int M, Real const* A, Real const* B, Real* X) + { + Real determinant; + return GaussianElimination()(N, A, nullptr, determinant, nullptr, + nullptr, B, M, X); + } + + // Solve A*X = B, where A is tridiagonal. The function expects the + // subdiagonal, diagonal, and superdiagonal of A. The diagonal input + // must have N elements. The subdiagonal and superdiagonal inputs + // must have N-1 elements. + static bool SolveTridiagonal(int N, Real const* subdiagonal, + Real const* diagonal, Real const* superdiagonal, Real const* B, Real* X) + { + if (diagonal[0] == (Real)0) + { + return false; + } + + std::vector tmp(N - 1); + Real expr = diagonal[0]; + Real invExpr = ((Real)1) / expr; + X[0] = B[0] * invExpr; + + int i0, i1; + for (i0 = 0, i1 = 1; i1 < N; ++i0, ++i1) + { + tmp[i0] = superdiagonal[i0] * invExpr; + expr = diagonal[i1] - subdiagonal[i0] * tmp[i0]; + if (expr == (Real)0) + { + return false; + } + invExpr = ((Real)1) / expr; + X[i1] = (B[i1] - subdiagonal[i0] * X[i0]) * invExpr; + } + + for (i0 = N - 1, i1 = N - 2; i1 >= 0; --i0, --i1) + { + X[i1] -= tmp[i1] * X[i0]; + } + return true; + } + + // Solve A*X = B, where A is tridiagonal. The function expects the + // subdiagonal, diagonal, and superdiagonal of A. Moreover, the + // subdiagonal elements are a constant, the diagonal elements are a + // constant, and the superdiagonal elements are a constant. + static bool SolveConstantTridiagonal(int N, Real subdiagonal, + Real diagonal, Real superdiagonal, Real const* B, Real* X) + { + if (diagonal == (Real)0) + { + return false; + } + + std::vector tmp(N - 1); + Real expr = diagonal; + Real invExpr = ((Real)1) / expr; + X[0] = B[0] * invExpr; + + int i0, i1; + for (i0 = 0, i1 = 1; i1 < N; ++i0, ++i1) + { + tmp[i0] = superdiagonal * invExpr; + expr = diagonal - subdiagonal * tmp[i0]; + if (expr == (Real)0) + { + return false; + } + invExpr = ((Real)1) / expr; + X[i1] = (B[i1] - subdiagonal * X[i0]) * invExpr; + } + + for (i0 = N - 1, i1 = N - 2; i1 >= 0; --i0, --i1) + { + X[i1] -= tmp[i1] * X[i0]; + } + return true; + } + + // Solve A*X = B using the conjugate gradient method, where A is + // symmetric. You must specify the maximum number of iterations and a + // tolerance for terminating the iterations. Reasonable choices for + // tolerance are 1e-06f for 'float' or 1e-08 for 'double'. + static unsigned int SolveSymmetricCG(int N, Real const* A, Real const* B, + Real* X, unsigned int maxIterations, Real tolerance) + { + // The first iteration. + std::vector tmpR(N), tmpP(N), tmpW(N); + Real* R = &tmpR[0]; + Real* P = &tmpP[0]; + Real* W = &tmpW[0]; + size_t numBytes = N * sizeof(Real); + std::memset(X, 0, numBytes); + std::memcpy(R, B, numBytes); + Real rho0 = Dot(N, R, R); + std::memcpy(P, R, numBytes); + Mul(N, A, P, W); + Real alpha = rho0 / Dot(N, P, W); + UpdateX(N, X, alpha, P); + UpdateR(N, R, alpha, W); + Real rho1 = Dot(N, R, R); + + // The remaining iterations. + unsigned int iteration; + for (iteration = 1; iteration <= maxIterations; ++iteration) + { + Real root0 = std::sqrt(rho1); + Real norm = Dot(N, B, B); + Real root1 = std::sqrt(norm); + if (root0 <= tolerance * root1) + { + break; + } + + Real beta = rho1 / rho0; + UpdateP(N, P, beta, R); + Mul(N, A, P, W); + alpha = rho1 / Dot(N, P, W); + UpdateX(N, X, alpha, P); + UpdateR(N, R, alpha, W); + rho0 = rho1; + rho1 = Dot(N, R, R); + } + return iteration; + } + + // Solve A*X = B using the conjugate gradient method, where A is + // sparse and symmetric. The nonzero entries of the symmetrix matrix + // A are stored in a map whose keys are pairs (i,j) and whose values + // are real numbers. The pair (i,j) is the location of the value in + // the array. Only one of (i,j) and (j,i) should be stored since A is + // symmetric. The column vector B is stored as an array of contiguous + // values. You must specify the maximum number of iterations and a + // tolerance for terminating the iterations. Reasonable choices for + // tolerance are 1e-06f for 'float' or 1e-08 for 'double'. + typedef std::map, Real> SparseMatrix; + static unsigned int SolveSymmetricCG(int N, SparseMatrix const& A, + Real const* B, Real* X, unsigned int maxIterations, Real tolerance) + { + // The first iteration. + std::vector tmpR(N), tmpP(N), tmpW(N); + Real* R = &tmpR[0]; + Real* P = &tmpP[0]; + Real* W = &tmpW[0]; + size_t numBytes = N * sizeof(Real); + std::memset(X, 0, numBytes); + std::memcpy(R, B, numBytes); + Real rho0 = Dot(N, R, R); + std::memcpy(P, R, numBytes); + Mul(N, A, P, W); + Real alpha = rho0 / Dot(N, P, W); + UpdateX(N, X, alpha, P); + UpdateR(N, R, alpha, W); + Real rho1 = Dot(N, R, R); + + // The remaining iterations. + unsigned int iteration; + for (iteration = 1; iteration <= maxIterations; ++iteration) + { + Real root0 = std::sqrt(rho1); + Real norm = Dot(N, B, B); + Real root1 = std::sqrt(norm); + if (root0 <= tolerance * root1) + { + break; + } + + Real beta = rho1 / rho0; + UpdateP(N, P, beta, R); + Mul(N, A, P, W); + alpha = rho1 / Dot(N, P, W); + UpdateX(N, X, alpha, P); + UpdateR(N, R, alpha, W); + rho0 = rho1; + rho1 = Dot(N, R, R); + } + return iteration; + } + + private: + // Support for the conjugate gradient method. + static Real Dot(int N, Real const* U, Real const* V) + { + Real dot = (Real)0; + for (int i = 0; i < N; ++i) + { + dot += U[i] * V[i]; + } + return dot; + } + + static void Mul(int N, Real const* A, Real const* X, Real* P) + { +#if defined(GTE_USE_ROW_MAJOR) + LexicoArray2 matA(N, N, const_cast(A)); +#else + LexicoArray2 matA(N, N, const_cast(A)); +#endif + + std::memset(P, 0, N * sizeof(Real)); + for (int row = 0; row < N; ++row) + { + for (int col = 0; col < N; ++col) + { + P[row] += matA(row, col) * X[col]; + } + } + } + + static void Mul(int N, SparseMatrix const& A, Real const* X, Real* P) + { + std::memset(P, 0, N * sizeof(Real)); + for (auto const& element : A) + { + int i = element.first[0]; + int j = element.first[1]; + Real value = element.second; + P[i] += value * X[j]; + if (i != j) + { + P[j] += value * X[i]; + } + } + } + + static void UpdateX(int N, Real* X, Real alpha, Real const* P) + { + for (int i = 0; i < N; ++i) + { + X[i] += alpha * P[i]; + } + } + + static void UpdateR(int N, Real* R, Real alpha, Real const* W) + { + for (int i = 0; i < N; ++i) + { + R[i] -= alpha * W[i]; + } + } + + static void UpdateP(int N, Real* P, Real beta, Real const* R) + { + for (int i = 0; i < N; ++i) + { + P[i] = R[i] + beta * P[i]; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Log2Estimate.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Log2Estimate.h new file mode 100644 index 0000000..b1f3186 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Log2Estimate.h @@ -0,0 +1,147 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// Minimax polynomial approximations to log2(x). The polynomial p(x) of +// degree D minimizes the quantity maximum{|log2(x) - p(x)| : x in [1,2]} +// over all polynomials of degree D. + +namespace WwiseGTE +{ + template + class Log2Estimate + { + public: + // The input constraint is x in [1,2]. For example, + // float x; // in [1,2] + // float result = Log2Estimate::Degree<3>(x); + template + inline static Real Degree(Real x) + { + Real t = x - (Real)1; // t in (0,1] + return Evaluate(degree(), t); + } + + // The input constraint is x > 0. Range reduction is used to generate + // a value y in (0,1], call Degree(y), and add the exponent for the + // power of two in the binary scientific representation of x. For + // example, + // float x; // x > 0 + // float result = Log2Estimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x) + { + int p; + Real y = std::frexp(x, &p); // y in [1/2,1) + y = ((Real)2) * y; // y in [1,2) + --p; + Real poly = Degree(y); + Real result = poly + (Real)p; + return result; + } + + private: + // Metaprogramming and private implementation to allow specialization + // of a template member function. + template struct degree {}; + + inline static Real Evaluate(degree<1>, Real t) + { + Real poly; + poly = (Real)GTE_C_LOG2_DEG1_C1; + poly = poly * t; + return poly; + } + + inline static Real Evaluate(degree<2>, Real t) + { + Real poly; + poly = (Real)GTE_C_LOG2_DEG2_C2; + poly = (Real)GTE_C_LOG2_DEG2_C1 + poly * t; + poly = poly * t; + return poly; + } + + inline static Real Evaluate(degree<3>, Real t) + { + Real poly; + poly = (Real)GTE_C_LOG2_DEG3_C3; + poly = (Real)GTE_C_LOG2_DEG3_C2 + poly * t; + poly = (Real)GTE_C_LOG2_DEG3_C1 + poly * t; + poly = poly * t; + return poly; + } + + inline static Real Evaluate(degree<4>, Real t) + { + Real poly; + poly = (Real)GTE_C_LOG2_DEG4_C4; + poly = (Real)GTE_C_LOG2_DEG4_C3 + poly * t; + poly = (Real)GTE_C_LOG2_DEG4_C2 + poly * t; + poly = (Real)GTE_C_LOG2_DEG4_C1 + poly * t; + poly = poly * t; + return poly; + } + + inline static Real Evaluate(degree<5>, Real t) + { + Real poly; + poly = (Real)GTE_C_LOG2_DEG5_C5; + poly = (Real)GTE_C_LOG2_DEG5_C4 + poly * t; + poly = (Real)GTE_C_LOG2_DEG5_C3 + poly * t; + poly = (Real)GTE_C_LOG2_DEG5_C2 + poly * t; + poly = (Real)GTE_C_LOG2_DEG5_C1 + poly * t; + poly = poly * t; + return poly; + } + + inline static Real Evaluate(degree<6>, Real t) + { + Real poly; + poly = (Real)GTE_C_LOG2_DEG6_C6; + poly = (Real)GTE_C_LOG2_DEG6_C5 + poly * t; + poly = (Real)GTE_C_LOG2_DEG6_C4 + poly * t; + poly = (Real)GTE_C_LOG2_DEG6_C3 + poly * t; + poly = (Real)GTE_C_LOG2_DEG6_C2 + poly * t; + poly = (Real)GTE_C_LOG2_DEG6_C1 + poly * t; + poly = poly * t; + return poly; + } + + inline static Real Evaluate(degree<7>, Real t) + { + Real poly; + poly = (Real)GTE_C_LOG2_DEG7_C7; + poly = (Real)GTE_C_LOG2_DEG7_C6 + poly * t; + poly = (Real)GTE_C_LOG2_DEG7_C5 + poly * t; + poly = (Real)GTE_C_LOG2_DEG7_C4 + poly * t; + poly = (Real)GTE_C_LOG2_DEG7_C3 + poly * t; + poly = (Real)GTE_C_LOG2_DEG7_C2 + poly * t; + poly = (Real)GTE_C_LOG2_DEG7_C1 + poly * t; + poly = poly * t; + return poly; + } + + inline static Real Evaluate(degree<8>, Real t) + { + Real poly; + poly = (Real)GTE_C_LOG2_DEG8_C8; + poly = (Real)GTE_C_LOG2_DEG8_C7 + poly * t; + poly = (Real)GTE_C_LOG2_DEG8_C6 + poly * t; + poly = (Real)GTE_C_LOG2_DEG8_C5 + poly * t; + poly = (Real)GTE_C_LOG2_DEG8_C4 + poly * t; + poly = (Real)GTE_C_LOG2_DEG8_C3 + poly * t; + poly = (Real)GTE_C_LOG2_DEG8_C2 + poly * t; + poly = (Real)GTE_C_LOG2_DEG8_C1 + poly * t; + poly = poly * t; + return poly; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LogEstimate.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LogEstimate.h new file mode 100644 index 0000000..ead567f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LogEstimate.h @@ -0,0 +1,44 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.12.23 + +#pragma once + +#include + +// Minimax polynomial approximations to log2(x). The polynomial p(x) of +// degree D minimizes the quantity maximum{|log2(x) - p(x)| : x in [1,2]} +// over all polynomials of degree D. The natural logarithm is computed +// using log(x) = log2(x)/log2(e) = log2(x)*log(2). + +namespace WwiseGTE +{ + template + class LogEstimate + { + public: + // The input constraint is x in [1,2]. For example, + // float x; // in [1,2] + // float result = LogEstimate::Degree<3>(x); + template + inline static Real Degree(Real x) + { + return Log2Estimate::Degree(x) * (Real)GTE_C_LN_2; + } + + // The input constraint is x > 0. Range reduction is used to generate + // a value y in (0,1], call Degree(y), and add the exponent for the + // power of two in the binary scientific representation of x. For + // example, + // float x; // x > 0 + // float result = LogEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x) + { + return Log2Estimate::DegreeRR(x) * (Real)GTE_C_LN_2; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LogToFile.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LogToFile.h new file mode 100644 index 0000000..86202f3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LogToFile.h @@ -0,0 +1,61 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + class LogToFile : public Logger::Listener + { + public: + LogToFile(std::string const& filename, int flags) + : + Logger::Listener(flags), + mFilename(filename) + { + std::ofstream logFile(filename); + if (logFile) + { + // This clears the file contents from any previous runs. + logFile.close(); + } + else + { + // The file cannot be opened. Use a null string for Report to + // know not to attempt opening the file for append. + mFilename = ""; + } + } + + private: + virtual void Report(std::string const& message) + { + if (mFilename != "") + { + // Open for append. + std::ofstream logFile(mFilename, std::ios_base::out | std::ios_base::app); + if (logFile) + { + logFile << message.c_str(); + logFile.close(); + } + else + { + // The file cannot be opened. Use a null string for + // Report not to attempt opening the file for append on + // the next call. + mFilename = ""; + } + } + } + + std::string mFilename; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LogToStdout.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LogToStdout.h new file mode 100644 index 0000000..d4d3ff4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LogToStdout.h @@ -0,0 +1,30 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + class LogToStdout : public Logger::Listener + { + public: + LogToStdout(int flags) + : + Logger::Listener(flags) + { + } + + private: + virtual void Report(std::string const& message) + { + std::cout << message.c_str() << std::flush; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LogToStringArray.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LogToStringArray.h new file mode 100644 index 0000000..f8a8151 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/LogToStringArray.h @@ -0,0 +1,49 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + class LogToStringArray : public Logger::Listener + { + public: + LogToStringArray(std::string const& name, int flags) + : + Logger::Listener(flags), + mName(name) + { + } + + inline std::string const& GetName() const + { + return mName; + } + + inline std::vector const& GetMessages() const + { + return mMessages; + } + + inline std::vector& GetMessages() + { + return mMessages; + } + + private: + virtual void Report(std::string const& message) + { + mMessages.push_back(message); + } + + std::string mName; + std::vector mMessages; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Logger.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Logger.h new file mode 100644 index 0000000..5ecf758 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Logger.h @@ -0,0 +1,193 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + class Logger + { + public: + // Listeners subscribe to Logger to receive message strings. + class Listener + { + public: + enum + { + LISTEN_FOR_NOTHING = 0x00000000, + LISTEN_FOR_ASSERTION = 0x00000001, + LISTEN_FOR_ERROR = 0x00000002, + LISTEN_FOR_WARNING = 0x00000004, + LISTEN_FOR_INFORMATION = 0x00000008, + LISTEN_FOR_ALL = 0xFFFFFFFF + }; + + // Construction and destruction. + virtual ~Listener() = default; + + Listener(int flags = LISTEN_FOR_NOTHING) + : + mFlags(flags) + { + } + + // What the listener wants to hear. + inline int GetFlags() const + { + return mFlags; + } + + // Handlers for the messages received from the logger. + void Assertion(std::string const& message) + { + Report("\nGTE ASSERTION:\n" + message); + } + + void Error(std::string const& message) + { + Report("\nGTE ERROR:\n" + message); + } + + void Warning(std::string const& message) + { + Report("\nGTE WARNING:\n" + message); + } + + void Information(std::string const& message) + { + Report("\nGTE INFORMATION:\n" + message); + } + + private: + virtual void Report(std::string const& message) + { + // Stub for derived classes. + (void)message; + } + + int mFlags; + }; + + // Construction. The Logger object is designed to exist only for a + // single-line call. A string is generated from the input parameters and + // is used for reporting. + Logger(char const* file, char const* function, int line, std::string const& message) + { + mMessage = + "File: " + std::string(file) + "\n" + + "Func: " + std::string(function) + "\n" + + "Line: " + std::to_string(line) + "\n" + + message + "\n\n"; + } + + // Notify current listeners about the logged information. + void Assertion() + { + Mutex().lock(); + for (auto listener : Listeners()) + { + if (listener->GetFlags() & Listener::LISTEN_FOR_ASSERTION) + { + listener->Assertion(mMessage); + } + } + Mutex().unlock(); + } + + void Error() + { + Mutex().lock(); + for (auto listener : Listeners()) + { + if (listener->GetFlags() & Listener::LISTEN_FOR_ERROR) + { + listener->Error(mMessage); + } + } + Mutex().unlock(); + } + + void Warning() + { + Mutex().lock(); + for (auto listener : Listeners()) + { + if (listener->GetFlags() & Listener::LISTEN_FOR_WARNING) + { + listener->Warning(mMessage); + } + } + Mutex().unlock(); + } + void Information() + { + Mutex().lock(); + for (auto listener : Listeners()) + { + if (listener->GetFlags() & Listener::LISTEN_FOR_INFORMATION) + { + listener->Information(mMessage); + } + } + Mutex().unlock(); + } + + static void Subscribe(Listener* listener) + { + Mutex().lock(); + Listeners().insert(listener); + Mutex().unlock(); + } + + static void Unsubscribe(Listener* listener) + { + Mutex().lock(); + Listeners().erase(listener); + Mutex().unlock(); + } + + + private: + std::string mMessage; + + static std::mutex& Mutex() + { + static std::mutex sMutex; + return sMutex; + } + + static std::set& Listeners() + { + static std::set sListeners; + return sListeners; + } + }; + +} + + +#define LogAssert(condition, message) \ + if (!(condition)) \ + { \ + WwiseGTE::Logger(__FILE__, __FUNCTION__, __LINE__, message).Assertion(); \ + throw std::runtime_error(message); \ + } + +#define LogError(message) \ + WwiseGTE::Logger(__FILE__, __FUNCTION__, __LINE__, message).Error(); \ + throw std::runtime_error(message) + +#define LogWarning(message) \ + WwiseGTE::Logger(__FILE__, __FUNCTION__, __LINE__, message).Warning() + +#define LogInformation(message) \ + WwiseGTE::Logger(__FILE__, __FUNCTION__, __LINE__, message).Information() diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Lozenge3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Lozenge3.h new file mode 100644 index 0000000..d7b5402 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Lozenge3.h @@ -0,0 +1,82 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + // A lozenge is the set of points that are equidistant from a rectangle, + // the common distance called the radius. + template + class Lozenge3 + { + public: + // Construction and destruction. The default constructor sets the + // rectangle to have origin (0,0,0), axes (1,0,0) and (0,1,0), and + // both extents 1. The default radius is 1. + Lozenge3() + : + radius((Real)1) + { + } + + Lozenge3(Rectangle<3, Real> const& inRectangle, Real inRadius) + : + rectangle(inRectangle), + radius(inRadius) + { + } + + // Public member access. + Rectangle<3, Real> rectangle; + Real radius; + + // Comparisons to support sorted containers. + bool operator==(Lozenge3 const& other) const + { + return rectangle == other.rectangle && radius == other.radius; + } + + bool operator!=(Lozenge3 const& other) const + { + return !operator==(other); + } + + bool operator< (Lozenge3 const& other) const + { + if (rectangle < other.rectangle) + { + return true; + } + + if (rectangle > other.rectangle) + { + return false; + } + + return radius < other.radius; + } + + bool operator<=(Lozenge3 const& other) const + { + return !other.operator<(*this); + } + + bool operator> (Lozenge3 const& other) const + { + return other.operator<(*this); + } + + bool operator>=(Lozenge3 const& other) const + { + return !operator<(other); + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MarchingCubes.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MarchingCubes.h new file mode 100644 index 0000000..11e843a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MarchingCubes.h @@ -0,0 +1,1254 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// Create the lookup table for the Marching Cubes algorithm that is used to +// extract a triangular mesh that represents a level surface of a 3D image +// sampled on a regular lattice. The assumption is that no data sample is +// zero, which allows us to have a table with 256 entries: 2 signs per +// sample, 8 samples per volume element (voxel). Each entry corresponds to +// the pattern of 8 signs at the corners of a voxel. The signs are stored as +// bits (b7,b6,b5,b4,b3,b2,b1,b0). The bit assignments to voxel corners is +// b0 = (x,y,z), b1 = (x+1,y,z), b2 = (x,y+1,z), b3 = (x+1,y+1,z) +// b4 = (x,y,z+1), b5 = (x+1,y,z+1), b6 = (x,y+1,z+1), b7 = (x+1,y+1,z+1) +// If a bit is zero, then the voxel value at the corresponding corner is +// positive; otherwise, the bit is one and the value is negative. The +// triangles are counterclockwise ordered according to an observer viewing +// the triangle from the negative side of the level surface. + +// INTERNAL USE ONLY. This is used for convenience in creating the +// Configuration table. TODO: Expand the macro manually for the table +// creation. +#define GTE_MC_ENTRY(name) CT_##name, &MarchingCubes::name + +namespace WwiseGTE +{ + class MarchingCubes + { + public: + // Construction and destruction. + virtual ~MarchingCubes() = default; + + MarchingCubes() + { + // Create the lookup table. + for (mEntry = 0; mEntry < 256; ++mEntry) + { + (this->*ConfigurationTable()[mEntry].F)(ConfigurationTable()[mEntry].index); + } + } + + enum + { + MAX_VERTICES = 12, + MAX_TRIANGLES = 5, + }; + + struct Topology + { + // All members are set to zeros. + Topology() + : + numVertices(0), + numTriangles(0) + { + std::fill(vpair.begin(), vpair.end(), std::array{ 0, 0 }); + std::fill(itriple.begin(), itriple.end(), std::array{ 0, 0, 0 }); + } + + int numVertices; + int numTriangles; + std::array, MAX_VERTICES> vpair; + std::array, MAX_TRIANGLES> itriple; + }; + + // The entry must be in {0..255}. + inline Topology const& GetTable(int entry) const + { + return TopologyTable()[entry]; + } + + // The table has 256 entries, each 41 integers, stored as + // table[256][41]. The return value is a pointer to the table via + // &table[0][0]. + inline int const* GetTable() const + { + return reinterpret_cast(TopologyTable().data()); + } + + // The pre-built topology table that is generated by the constructor. + // This is for reference in case you want to have a GPU-based + // implementation where you load the table as a GPU resource. + static std::array, 256> const& GetPrebuiltTable() + { + static std::array, 256> topologyTable = + { { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 3, 1, 0, 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 3, 1, 1, 3, 1, 5, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 4, 2, 0, 4, 0, 2, 1, 3, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 3, 1, 0, 2, 2, 6, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 4, 2, 2, 6, 2, 3, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 6, 2, 1, 3, 1, 5, 0, 1, 0, 2, 2, 6, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 0, 4, 2, 6, 2, 3, 1, 3, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 3, 1, 2, 3, 3, 7, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 6, 2, 0, 1, 0, 4, 0, 2, 2, 3, 3, 7, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 4, 2, 1, 5, 0, 1, 2, 3, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 1, 5, 0, 4, 0, 2, 2, 3, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 4, 2, 3, 7, 1, 3, 0, 2, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 2, 6, 3, 7, 1, 3, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 3, 7, 1, 5, 0, 1, 0, 2, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 4, 2, 0, 4, 2, 6, 3, 7, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 3, 1, 4, 5, 4, 6, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 4, 2, 4, 5, 4, 6, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 6, 2, 0, 1, 1, 3, 1, 5, 4, 5, 4, 6, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 0, 2, 1, 3, 1, 5, 4, 5, 4, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 6, 2, 0, 4, 4, 5, 4, 6, 2, 6, 2, 3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 0, 1, 4, 5, 4, 6, 2, 6, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 9, 3, 0, 2, 2, 6, 2, 3, 1, 3, 1, 5, 0, 1, 0, 4, 4, 5, 4, 6, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 4, 5, 4, 6, 2, 6, 2, 3, 1, 3, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 6, 2, 2, 3, 3, 7, 1, 3, 0, 4, 4, 5, 4, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 7, 3, 0, 1, 4, 5, 4, 6, 0, 2, 2, 3, 3, 7, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 7, 3, 0, 1, 2, 3, 3, 7, 1, 5, 4, 5, 4, 6, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 4, 5, 4, 6, 0, 2, 2, 3, 3, 7, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 7, 3, 2, 6, 3, 7, 1, 3, 0, 2, 0, 4, 4, 5, 4, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 4, 6, 2, 6, 3, 7, 1, 3, 0, 1, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 8, 4, 3, 7, 1, 5, 0, 1, 0, 2, 2, 6, 4, 5, 4, 6, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 5, 3, 3, 7, 2, 6, 4, 6, 4, 5, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 3, 1, 5, 7, 4, 5, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 6, 2, 0, 4, 0, 2, 0, 1, 1, 5, 5, 7, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 4, 2, 5, 7, 4, 5, 0, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 1, 3, 5, 7, 4, 5, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 6, 2, 0, 2, 2, 6, 2, 3, 1, 5, 5, 7, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 7, 3, 0, 4, 2, 6, 2, 3, 0, 1, 1, 5, 5, 7, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 7, 3, 1, 3, 5, 7, 4, 5, 0, 1, 0, 2, 2, 6, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 4, 5, 0, 4, 2, 6, 2, 3, 1, 3, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 6, 2, 5, 7, 4, 5, 1, 5, 1, 3, 2, 3, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 9, 3, 0, 1, 0, 4, 0, 2, 2, 3, 3, 7, 1, 3, 1, 5, 5, 7, 4, 5, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 0, 1, 2, 3, 3, 7, 5, 7, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 5, 7, 4, 5, 0, 4, 0, 2, 2, 3, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 7, 3, 1, 3, 0, 2, 2, 6, 3, 7, 5, 7, 4, 5, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 8, 4, 2, 6, 3, 7, 1, 3, 0, 1, 0, 4, 5, 7, 4, 5, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 6, 4, 5, 7, 4, 5, 0, 1, 0, 2, 2, 6, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 5, 3, 2, 6, 0, 4, 4, 5, 5, 7, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 4, 2, 4, 6, 0, 4, 1, 5, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 4, 6, 0, 2, 0, 1, 1, 5, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 5, 7, 4, 6, 0, 4, 0, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 4, 2, 0, 2, 1, 3, 5, 7, 4, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 7, 3, 0, 4, 1, 5, 5, 7, 4, 6, 2, 6, 2, 3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 1, 5, 5, 7, 4, 6, 2, 6, 2, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 8, 4, 5, 7, 4, 6, 0, 4, 0, 1, 1, 3, 2, 6, 2, 3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 5, 3, 5, 7, 1, 3, 2, 3, 2, 6, 4, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 7, 3, 5, 7, 4, 6, 0, 4, 1, 5, 1, 3, 2, 3, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 8, 4, 4, 6, 0, 2, 0, 1, 1, 5, 5, 7, 2, 3, 3, 7, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 6, 4, 3, 7, 5, 7, 4, 6, 0, 4, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 5, 3, 4, 6, 5, 7, 3, 7, 2, 3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 8, 4, 3, 7, 1, 3, 0, 2, 2, 6, 1, 5, 5, 7, 4, 6, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 0, 0, 0 }, + { 7, 5, 3, 7, 2, 6, 4, 6, 5, 7, 1, 5, 0, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 7, 5, 4, 6, 5, 7, 3, 7, 2, 6, 0, 2, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 4, 2, 2, 6, 4, 6, 5, 7, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 3, 1, 6, 7, 2, 6, 4, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 6, 2, 0, 2, 0, 1, 0, 4, 4, 6, 6, 7, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 6, 2, 1, 3, 1, 5, 0, 1, 2, 6, 4, 6, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 7, 3, 0, 2, 1, 3, 1, 5, 0, 4, 4, 6, 6, 7, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 4, 2, 4, 6, 6, 7, 2, 3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 2, 3, 0, 1, 0, 4, 4, 6, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 7, 3, 0, 2, 4, 6, 6, 7, 2, 3, 1, 3, 1, 5, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 4, 6, 6, 7, 2, 3, 1, 3, 1, 5, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 6, 2, 3, 7, 1, 3, 2, 3, 2, 6, 4, 6, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 9, 3, 2, 3, 3, 7, 1, 3, 0, 1, 0, 4, 0, 2, 2, 6, 4, 6, 6, 7, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0 }, + { 7, 3, 3, 7, 1, 5, 0, 1, 2, 3, 2, 6, 4, 6, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 8, 4, 1, 5, 0, 4, 0, 2, 2, 3, 3, 7, 4, 6, 6, 7, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 5, 3, 0, 2, 4, 6, 6, 7, 3, 7, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 4, 6, 6, 7, 3, 7, 1, 3, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 6, 4, 6, 7, 3, 7, 1, 5, 0, 1, 0, 2, 4, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 5, 3, 1, 5, 3, 7, 6, 7, 4, 6, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 4, 2, 6, 7, 2, 6, 0, 4, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 4, 5, 6, 7, 2, 6, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 7, 3, 4, 5, 6, 7, 2, 6, 0, 4, 0, 1, 1, 3, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 2, 6, 0, 2, 1, 3, 1, 5, 4, 5, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 5, 3, 6, 7, 2, 3, 0, 2, 0, 4, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 4, 2, 0, 1, 4, 5, 6, 7, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 8, 4, 6, 7, 2, 3, 0, 2, 0, 4, 4, 5, 1, 3, 1, 5, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 5, 3, 6, 7, 4, 5, 1, 5, 1, 3, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 7, 3, 2, 6, 0, 4, 4, 5, 6, 7, 3, 7, 1, 3, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 8, 4, 4, 5, 6, 7, 2, 6, 0, 2, 0, 1, 3, 7, 1, 3, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 8, 4, 1, 5, 0, 1, 2, 3, 3, 7, 0, 4, 4, 5, 6, 7, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 0, 0, 0 }, + { 7, 5, 6, 7, 4, 5, 1, 5, 3, 7, 2, 3, 0, 2, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 6, 4, 3, 7, 1, 3, 0, 2, 0, 4, 4, 5, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 5, 3, 4, 5, 0, 1, 1, 3, 3, 7, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 7, 5, 1, 5, 3, 7, 6, 7, 4, 5, 0, 4, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 4, 2, 4, 5, 1, 5, 3, 7, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 6, 2, 4, 5, 1, 5, 5, 7, 6, 7, 2, 6, 4, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 9, 3, 4, 5, 1, 5, 5, 7, 6, 7, 2, 6, 4, 6, 0, 4, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0 }, + { 7, 3, 4, 5, 0, 1, 1, 3, 5, 7, 6, 7, 2, 6, 4, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 8, 4, 1, 3, 5, 7, 4, 5, 0, 4, 0, 2, 6, 7, 2, 6, 4, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 7, 3, 6, 7, 2, 3, 0, 2, 4, 6, 4, 5, 1, 5, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 8, 4, 2, 3, 0, 1, 0, 4, 4, 6, 6, 7, 1, 5, 5, 7, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 8, 4, 4, 6, 6, 7, 2, 3, 0, 2, 5, 7, 4, 5, 0, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 0, 0, 0 }, + { 7, 5, 5, 7, 1, 3, 2, 3, 6, 7, 4, 6, 0, 4, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 9, 3, 6, 7, 2, 6, 4, 6, 4, 5, 1, 5, 5, 7, 3, 7, 1, 3, 2, 3, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0 }, + { 12, 4, 0, 1, 0, 4, 0, 2, 2, 6, 4, 6, 6, 7, 2, 3, 3, 7, 1, 3, 1, 5, 5, 7, 4, 5, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, 0, 0 }, + { 8, 4, 0, 1, 2, 3, 3, 7, 5, 7, 4, 5, 2, 6, 4, 6, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 9, 5, 4, 6, 0, 4, 4, 5, 5, 7, 3, 7, 6, 7, 2, 6, 2, 3, 0, 2, 0, 0, 0, 0, 0, 0, 1, 3, 2, 1, 4, 3, 1, 7, 4, 1, 8, 7, 0, 5, 6 }, + { 8, 4, 0, 2, 4, 6, 6, 7, 3, 7, 1, 3, 4, 5, 1, 5, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 9, 5, 5, 7, 3, 7, 6, 7, 4, 6, 0, 4, 4, 5, 1, 5, 0, 1, 1, 3, 0, 0, 0, 0, 0, 0, 1, 3, 2, 1, 4, 3, 1, 7, 4, 1, 8, 7, 0, 5, 6 }, + { 7, 5, 4, 6, 0, 2, 0, 1, 4, 5, 5, 7, 3, 7, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 6, 4, 5, 7, 3, 7, 6, 7, 4, 6, 0, 4, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 5, 1, 5, 4, 1, 4, 3, 1, 3, 2, 0, 0, 0 }, + { 5, 3, 0, 4, 1, 5, 5, 7, 6, 7, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 0, 2, 0, 1, 1, 5, 5, 7, 6, 7, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 6, 4, 6, 7, 2, 6, 0, 4, 0, 1, 1, 3, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 5, 3, 1, 3, 0, 2, 2, 6, 6, 7, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 0, 2, 0, 4, 1, 5, 5, 7, 6, 7, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 5, 3, 2, 3, 6, 7, 5, 7, 1, 5, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 7, 5, 2, 3, 6, 7, 5, 7, 1, 3, 0, 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 4, 2, 1, 3, 2, 3, 6, 7, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 8, 4, 0, 4, 1, 5, 5, 7, 6, 7, 2, 6, 1, 3, 2, 3, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 9, 5, 1, 3, 1, 5, 0, 1, 0, 2, 2, 6, 2, 3, 3, 7, 6, 7, 5, 7, 0, 0, 0, 0, 0, 0, 1, 3, 2, 1, 4, 3, 1, 7, 4, 1, 8, 7, 0, 5, 6 }, + { 7, 5, 2, 3, 0, 1, 0, 4, 2, 6, 6, 7, 5, 7, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 6, 4, 2, 3, 0, 2, 2, 6, 6, 7, 5, 7, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 5, 1, 5, 4, 1, 4, 3, 1, 3, 2, 0, 0, 0 }, + { 7, 5, 1, 5, 0, 4, 0, 2, 1, 3, 3, 7, 6, 7, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 6, 4, 1, 5, 0, 1, 1, 3, 3, 7, 6, 7, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 5, 1, 5, 4, 1, 4, 3, 1, 3, 2, 0, 0, 0 }, + { 6, 2, 0, 1, 0, 4, 0, 2, 3, 7, 6, 7, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 5, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 3, 1, 3, 7, 6, 7, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 3, 1, 3, 7, 6, 7, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 6, 2, 0, 1, 0, 4, 0, 2, 3, 7, 6, 7, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 6, 2, 1, 5, 0, 1, 1, 3, 3, 7, 6, 7, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 7, 3, 1, 5, 0, 4, 0, 2, 1, 3, 3, 7, 6, 7, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 6, 2, 2, 3, 0, 2, 2, 6, 6, 7, 5, 7, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 7, 3, 2, 3, 0, 1, 0, 4, 2, 6, 6, 7, 5, 7, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 9, 3, 1, 3, 1, 5, 0, 1, 0, 2, 2, 6, 2, 3, 3, 7, 6, 7, 5, 7, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0 }, + { 8, 4, 0, 4, 2, 6, 2, 3, 1, 3, 1, 5, 6, 7, 5, 7, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 4, 2, 1, 3, 2, 3, 6, 7, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 7, 3, 2, 3, 6, 7, 5, 7, 1, 3, 0, 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 2, 3, 6, 7, 5, 7, 1, 5, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 5, 7, 1, 5, 0, 4, 0, 2, 2, 3, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 5, 3, 1, 3, 0, 2, 2, 6, 6, 7, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 6, 7, 5, 7, 1, 3, 0, 1, 0, 4, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 6, 4, 6, 7, 5, 7, 1, 5, 0, 1, 0, 2, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 5, 3, 0, 4, 1, 5, 5, 7, 6, 7, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 6, 2, 5, 7, 3, 7, 6, 7, 4, 6, 0, 4, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 7, 3, 4, 6, 0, 2, 0, 1, 4, 5, 5, 7, 3, 7, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 9, 3, 5, 7, 3, 7, 6, 7, 4, 6, 0, 4, 4, 5, 1, 5, 0, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0 }, + { 8, 4, 0, 2, 1, 3, 1, 5, 4, 5, 4, 6, 3, 7, 6, 7, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 9, 3, 4, 6, 0, 4, 4, 5, 5, 7, 3, 7, 6, 7, 2, 6, 2, 3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0 }, + { 8, 4, 0, 1, 4, 5, 4, 6, 2, 6, 2, 3, 5, 7, 3, 7, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 12, 4, 1, 3, 1, 5, 0, 1, 0, 4, 4, 5, 4, 6, 0, 2, 2, 6, 2, 3, 3, 7, 6, 7, 5, 7, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, 0, 0 }, + { 9, 5, 6, 7, 2, 6, 4, 6, 4, 5, 1, 5, 5, 7, 3, 7, 1, 3, 2, 3, 0, 0, 0, 0, 0, 0, 1, 3, 2, 1, 4, 3, 1, 7, 4, 1, 8, 7, 0, 5, 6 }, + { 7, 3, 5, 7, 1, 3, 2, 3, 6, 7, 4, 6, 0, 4, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 8, 4, 4, 5, 4, 6, 0, 2, 0, 1, 6, 7, 5, 7, 1, 3, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 0, 0, 0 }, + { 8, 4, 2, 3, 6, 7, 5, 7, 1, 5, 0, 1, 4, 6, 0, 4, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 7, 5, 6, 7, 2, 3, 0, 2, 4, 6, 4, 5, 1, 5, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 8, 4, 1, 3, 0, 2, 2, 6, 6, 7, 5, 7, 0, 4, 4, 5, 4, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 7, 5, 4, 5, 0, 1, 1, 3, 5, 7, 6, 7, 2, 6, 4, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 9, 5, 4, 5, 1, 5, 5, 7, 6, 7, 2, 6, 4, 6, 0, 4, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 1, 3, 2, 1, 4, 3, 1, 7, 4, 1, 8, 7, 0, 5, 6 }, + { 6, 4, 4, 5, 1, 5, 5, 7, 6, 7, 2, 6, 4, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 5, 1, 5, 4, 1, 4, 3, 1, 3, 2, 0, 0, 0 }, + { 4, 2, 4, 5, 1, 5, 3, 7, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 7, 3, 1, 5, 3, 7, 6, 7, 4, 5, 0, 4, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 4, 5, 0, 1, 1, 3, 3, 7, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 3, 7, 6, 7, 4, 5, 0, 4, 0, 2, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 7, 3, 6, 7, 4, 5, 1, 5, 3, 7, 2, 3, 0, 2, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 8, 4, 2, 6, 2, 3, 0, 1, 0, 4, 3, 7, 6, 7, 4, 5, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 0, 0, 0 }, + { 8, 4, 4, 5, 0, 1, 1, 3, 3, 7, 6, 7, 0, 2, 2, 6, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 7, 5, 2, 6, 0, 4, 4, 5, 6, 7, 3, 7, 1, 3, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 5, 3, 6, 7, 4, 5, 1, 5, 1, 3, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 8, 4, 6, 7, 4, 5, 1, 5, 1, 3, 2, 3, 0, 4, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 4, 2, 0, 1, 2, 3, 6, 7, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 6, 7, 2, 3, 0, 2, 0, 4, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 1, 5, 1, 3, 0, 2, 2, 6, 6, 7, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 7, 5, 4, 5, 6, 7, 2, 6, 0, 4, 0, 1, 1, 3, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 5, 3, 4, 5, 6, 7, 2, 6, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 4, 2, 6, 7, 2, 6, 0, 4, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 1, 5, 3, 7, 6, 7, 4, 6, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 6, 7, 4, 6, 0, 2, 0, 1, 1, 5, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 6, 4, 3, 7, 6, 7, 4, 6, 0, 4, 0, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 5, 3, 0, 2, 4, 6, 6, 7, 3, 7, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 8, 4, 1, 5, 3, 7, 6, 7, 4, 6, 0, 4, 2, 3, 0, 2, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 7, 5, 3, 7, 1, 5, 0, 1, 2, 3, 2, 6, 4, 6, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 9, 5, 2, 3, 3, 7, 1, 3, 0, 1, 0, 4, 0, 2, 2, 6, 4, 6, 6, 7, 0, 0, 0, 0, 0, 0, 1, 3, 2, 1, 4, 3, 1, 7, 4, 1, 8, 7, 0, 5, 6 }, + { 6, 4, 3, 7, 1, 3, 2, 3, 2, 6, 4, 6, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 5, 1, 5, 4, 1, 4, 3, 1, 3, 2, 0, 0, 0 }, + { 6, 4, 4, 6, 0, 4, 1, 5, 1, 3, 2, 3, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 7, 5, 0, 2, 4, 6, 6, 7, 2, 3, 1, 3, 1, 5, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 5, 3, 2, 3, 0, 1, 0, 4, 4, 6, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 4, 2, 4, 6, 6, 7, 2, 3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 7, 5, 0, 2, 1, 3, 1, 5, 0, 4, 4, 6, 6, 7, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 6, 2, 1, 3, 1, 5, 0, 1, 2, 6, 4, 6, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 5, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 0, 2, 0, 1, 0, 4, 4, 6, 6, 7, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 5, 1, 5, 4, 1, 4, 3, 1, 3, 2, 0, 0, 0 }, + { 3, 1, 6, 7, 2, 6, 4, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 4, 2, 2, 6, 4, 6, 5, 7, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 7, 3, 4, 6, 5, 7, 3, 7, 2, 6, 0, 2, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 7, 3, 3, 7, 2, 6, 4, 6, 5, 7, 1, 5, 0, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }, + { 8, 4, 0, 4, 0, 2, 1, 3, 1, 5, 2, 6, 4, 6, 5, 7, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 0, 0, 0 }, + { 5, 3, 4, 6, 5, 7, 3, 7, 2, 3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 3, 7, 2, 3, 0, 1, 0, 4, 4, 6, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 8, 4, 4, 6, 5, 7, 3, 7, 2, 3, 0, 2, 1, 5, 0, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 7, 5, 5, 7, 4, 6, 0, 4, 1, 5, 1, 3, 2, 3, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 5, 3, 5, 7, 1, 3, 2, 3, 2, 6, 4, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 8, 4, 5, 7, 1, 3, 2, 3, 2, 6, 4, 6, 0, 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 6, 4, 1, 5, 0, 1, 2, 3, 2, 6, 4, 6, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 7, 5, 0, 4, 1, 5, 5, 7, 4, 6, 2, 6, 2, 3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 4, 2, 0, 2, 4, 6, 5, 7, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 5, 7, 4, 6, 0, 4, 0, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 4, 6, 0, 2, 0, 1, 1, 5, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 4, 2, 4, 6, 0, 4, 1, 5, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 2, 6, 0, 4, 4, 5, 5, 7, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 5, 7, 3, 7, 2, 6, 0, 2, 0, 1, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 8, 4, 2, 6, 0, 4, 4, 5, 5, 7, 3, 7, 0, 1, 1, 3, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 7, 5, 1, 3, 0, 2, 2, 6, 3, 7, 5, 7, 4, 5, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 6, 4, 5, 7, 3, 7, 2, 3, 0, 2, 0, 4, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 5, 3, 0, 1, 2, 3, 3, 7, 5, 7, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 9, 5, 0, 1, 0, 4, 0, 2, 2, 3, 3, 7, 1, 3, 1, 5, 5, 7, 4, 5, 0, 0, 0, 0, 0, 0, 1, 3, 2, 1, 4, 3, 1, 7, 4, 1, 8, 7, 0, 5, 6 }, + { 6, 4, 5, 7, 4, 5, 1, 5, 1, 3, 2, 3, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 5, 1, 5, 4, 1, 4, 3, 1, 3, 2, 0, 0, 0 }, + { 6, 4, 2, 3, 2, 6, 0, 4, 4, 5, 5, 7, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 7, 5, 1, 3, 5, 7, 4, 5, 0, 1, 0, 2, 2, 6, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 7, 5, 0, 4, 2, 6, 2, 3, 0, 1, 1, 5, 5, 7, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 6, 2, 0, 2, 2, 6, 2, 3, 1, 5, 5, 7, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 5, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 1, 3, 5, 7, 4, 5, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 4, 2, 5, 7, 4, 5, 0, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 0, 4, 0, 2, 0, 1, 1, 5, 5, 7, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 5, 1, 5, 4, 1, 4, 3, 1, 3, 2, 0, 0, 0 }, + { 3, 1, 5, 7, 4, 5, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 3, 7, 2, 6, 4, 6, 4, 5, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 0, 0, 0, 0, 0 }, + { 8, 4, 3, 7, 2, 6, 4, 6, 4, 5, 1, 5, 0, 2, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 5, 6, 7, 0, 0, 0 }, + { 6, 4, 1, 3, 3, 7, 2, 6, 4, 6, 4, 5, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 7, 5, 2, 6, 3, 7, 1, 3, 0, 2, 0, 4, 4, 5, 4, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 6, 4, 2, 3, 0, 2, 4, 6, 4, 5, 1, 5, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 7, 5, 0, 1, 2, 3, 3, 7, 1, 5, 4, 5, 4, 6, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 7, 5, 0, 1, 4, 5, 4, 6, 0, 2, 2, 3, 3, 7, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 5, 1, 0, 5, 2, 1, 5, 3, 2, 5, 4, 3 }, + { 6, 2, 2, 3, 3, 7, 1, 3, 0, 4, 4, 5, 4, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 5, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 1, 3, 2, 3, 2, 6, 4, 6, 4, 5, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 0, 0 }, + { 9, 5, 0, 2, 2, 6, 2, 3, 1, 3, 1, 5, 0, 1, 0, 4, 4, 5, 4, 6, 0, 0, 0, 0, 0, 0, 1, 3, 2, 1, 4, 3, 1, 7, 4, 1, 8, 7, 0, 5, 6 }, + { 5, 3, 0, 1, 4, 5, 4, 6, 2, 6, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 0, 4, 4, 5, 4, 6, 2, 6, 2, 3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 5, 1, 5, 4, 1, 4, 3, 1, 3, 2, 0, 0, 0 }, + { 5, 3, 0, 2, 1, 3, 1, 5, 4, 5, 4, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 0, 1, 1, 3, 1, 5, 4, 5, 4, 6, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 5, 1, 5, 4, 1, 4, 3, 1, 3, 2, 0, 0, 0 }, + { 4, 2, 4, 5, 4, 6, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 3, 1, 4, 5, 4, 6, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 4, 2, 0, 4, 1, 5, 3, 7, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 3, 7, 1, 5, 0, 1, 0, 2, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 2, 6, 3, 7, 1, 3, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 4, 2, 3, 7, 1, 3, 0, 2, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 1, 5, 0, 4, 0, 2, 2, 3, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 4, 2, 1, 5, 0, 1, 2, 3, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 0, 1, 0, 4, 0, 2, 2, 3, 3, 7, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 5, 1, 5, 4, 1, 4, 3, 1, 3, 2, 0, 0, 0 }, + { 3, 1, 2, 3, 3, 7, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 5, 3, 0, 4, 2, 6, 2, 3, 1, 3, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 0, 0, 0, 0, 0 }, + { 6, 4, 1, 3, 1, 5, 0, 1, 0, 2, 2, 6, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 5, 1, 5, 4, 1, 4, 3, 1, 3, 2, 0, 0, 0 }, + { 4, 2, 2, 6, 2, 3, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 3, 1, 0, 2, 2, 6, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 4, 2, 0, 4, 0, 2, 1, 3, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 3, 1, 1, 3, 1, 5, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 3, 1, 0, 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } + }}; + return topologyTable; + } + + // Get the configuration type for the voxel, which is one of the + // string names of the 'void Bits* (int[8])' functions. + static std::string GetConfigurationType(int entry) + { + if (0 <= entry && entry < 256) + { + return ConfigurationString()[ConfigurationTable()[entry].type]; + } + return ""; + } + + protected: + // Support for lookup construction and access. + // mTable[i][0] = numVertices + // mTable[i][1] = numTriangles + // mTable[i][2..25] = pairs of corner indices (maximum of 12 pairs) + // mTable[i][26..40] = triples of indices (maximum of 5 triples) + static std::array& TopologyTable() + { + static std::array topologyTable; + return topologyTable; + } + + // The constructor iterates mEntry from 0 to 255 and calls configuration + // functions, each calling SetTable(...). The mEntry value is the table + // index to be used. + int mEntry; + + void SetTable(int numV, int const* vpair, int numT, int const* itriple) + { + // The item is already zeroed in the constructor. + Topology& topology = TopologyTable()[mEntry]; + topology.numVertices = numV; + topology.numTriangles = numT; + + // Store vertex pairs with minimum index occurring first. + for (int i = 0; i < numV; ++i, vpair += 2) + { + topology.vpair[i][0] = std::min(vpair[0], vpair[1]); + topology.vpair[i][1] = std::max(vpair[0], vpair[1]); + } + + // Store triangle triples as is. + for (int i = 0; i < numT; ++i, itriple += 3) + { + topology.itriple[i] = { itriple[0], itriple[1], itriple[2] }; + } + } + + // The precomputed information about the 256 configurations for voxels. + void Bits0(int[8]) + { + SetTable(0, nullptr, 0, nullptr); + } + + void Bits1(int index[8]) + { + int const numV = 3; + int vpair[2 * numV] = + { + index[1], index[0], + index[4], index[0], + index[2], index[0] + }; + + int const numT = 1; + int itriple[3 * numT] = + { + 0, 1, 2 + }; + + SetTable(numV, vpair, numT, itriple); + } + + void Bits7(int index[8]) + { + int const numV = 3; + int vpair[2 * numV] = + { + index[1], index[0], + index[4], index[0], + index[2], index[0] + }; + + int const numT = 1; + int itriple[3 * numT] = + { + 0, 2, 1 + }; + + SetTable(numV, vpair, numT, itriple); + } + + void Bits2Edge(int index[8]) + { + int const numV = 4; + int vpair[2 * numV] = + { + index[4], index[0], + index[2], index[0], + index[3], index[1], + index[5], index[1] + }; + + int const numT = 2; + int itriple[3 * numT] = + { + 0, 1, 2, + 0, 2, 3 + }; + + SetTable(numV, vpair, numT, itriple); + } + + void Bits6Edge(int index[8]) + { + int const numV = 4; + int vpair[2 * numV] = + { + index[4], index[0], + index[2], index[0], + index[3], index[1], + index[5], index[1] + }; + + int const numT = 2; + int itriple[3 * numT] = + { + 0, 2, 1, + 0, 3, 2 + }; + + SetTable(numV, vpair, numT, itriple); + } + + void Bits2FaceDiag(int index[8]) + { + int const numV = 6; + int vpair[2 * numV] = + { + index[1], index[0], + index[4], index[0], + index[2], index[0], + index[2], index[3], + index[7], index[3], + index[1], index[3] + }; + + int const numT = 2; + int itriple[3 * numT] = + { + 0, 1, 2, + 3, 4, 5 + }; + + SetTable(numV, vpair, numT, itriple); + } + + void Bits6FaceDiag(int index[8]) + { + int const numV = 6; + int vpair[2 * numV] = + { + index[1], index[0], + index[4], index[0], + index[2], index[0], + index[2], index[3], + index[7], index[3], + index[1], index[3] + }; + + // Not the reverse ordering from Bit2FaceDiag due to ambiguous face + // handling. + int const numT = 4; + int itriple[3 * numT] = + { + 1, 0, 5, + 1, 5, 4, + 1, 4, 3, + 1, 3, 2 + }; + + SetTable(numV, vpair, numT, itriple); + } + + void Bits2BoxDiag(int index[8]) + { + int const numV = 6; + int vpair[2 * numV] = + { + index[1], index[0], + index[4], index[0], + index[2], index[0], + index[3], index[7], + index[6], index[7], + index[5], index[7] + }; + + int const numT = 2; + int itriple[3 * numT] = + { + 0, 1, 2, + 3, 4, 5 + }; + + SetTable(numV, vpair, numT, itriple); + } + + void Bits6BoxDiag(int index[8]) + { + int const numV = 6; + int vpair[2 * numV] = + { + index[1], index[0], + index[4], index[0], + index[2], index[0], + index[3], index[7], + index[6], index[7], + index[5], index[7] + }; + + int const numT = 2; + int itriple[3 * numT] = + { + 0, 2, 1, + 3, 5, 4 + }; + + SetTable(numV, vpair, numT, itriple); + } + + void Bits3SameFace(int index[8]) + { + int const numV = 5; + int vpair[2 * numV] = + { + index[4], index[0], + index[2], index[6], + index[2], index[3], + index[1], index[3], + index[1], index[5] + }; + + int const numT = 3; + int itriple[3 * numT] = + { + 0, 1, 2, + 0, 2, 3, + 0, 3, 4 + }; + + SetTable(numV, vpair, numT, itriple); + } + + void Bits5SameFace(int index[8]) + { + int const numV = 5; + int vpair[2 * numV] = + { + index[4], index[0], + index[2], index[6], + index[2], index[3], + index[1], index[3], + index[1], index[5] + }; + + int const numT = 3; + int itriple[3 * numT] = + { + 0, 2, 1, + 0, 3, 2, + 0, 4, 3 + }; + + SetTable(numV, vpair, numT, itriple); + } + + void Bits3EdgeFaceDiag(int index[8]) + { + int const numV = 7; + int vpair[2 * numV] = + { + index[0], index[1], + index[4], index[5], + index[4], index[6], + index[0], index[2], + index[2], index[3], + index[3], index[7], + index[1], index[3] + }; + + int const numT = 3; + int itriple[3 * numT] = + { + 0, 1, 2, + 0, 2, 3, + 4, 5, 6 + }; + + SetTable(numV, vpair, numT, itriple); + } + + void Bits5EdgeFaceDiag(int index[8]) + { + int const numV = 7; + int vpair[2 * numV] = + { + index[0], index[1], + index[4], index[5], + index[4], index[6], + index[0], index[2], + index[2], index[3], + index[3], index[7], + index[1], index[3] + }; + + // Not the reverse ordering from Bit3EdgeFaceDiag due to ambiguous face + // handling. + int const numT = 5; + int itriple[3 * numT] = + { + 5, 0, 6, + 5, 1, 0, + 5, 2, 1, + 5, 3, 2, + 5, 4, 3 + }; + + SetTable(numV, vpair, numT, itriple); + } + + void Bits3FaceDiagFaceDiag(int index[8]) + { + int const numV = 9; + int vpair[2 * numV] = + { + index[0], index[1], + index[0], index[4], + index[0], index[2], + index[2], index[3], + index[3], index[7], + index[1], index[3], + index[1], index[5], + index[5], index[7], + index[4], index[5] + }; + + int const numT = 3; + int itriple[3 * numT] = + { + 0, 1, 2, + 3, 4, 5, + 6, 7, 8 + }; + + SetTable(numV, vpair, numT, itriple); + } + + void Bits5FaceDiagFaceDiag(int index[8]) + { + int const numV = 9; + int vpair[2 * numV] = + { + index[0], index[1], + index[0], index[4], + index[0], index[2], + index[2], index[3], + index[3], index[7], + index[1], index[3], + index[1], index[5], + index[5], index[7], + index[4], index[5] + }; + + // Not the reverse ordering from Bit3FaceDiagFaceDiag due to ambiguous face + // handling. + int const numT = 5; + int itriple[3 * numT] = + { + 1, 3, 2, + 1, 4, 3, + 1, 7, 4, + 1, 8, 7, + 0, 5, 6 + }; + + SetTable(numV, vpair, numT, itriple); + } + + void Bits4SameFace(int index[8]) + { + int const numV = 4; + int vpair[2 * numV] = + { + index[0], index[4], + index[2], index[6], + index[3], index[7], + index[1], index[5] + }; + + int const numT = 2; + int itriple[3 * numT] = + { + 0, 1, 2, + 0, 2, 3 + }; + + SetTable(numV, vpair, numT, itriple); + } + + void Bits4FaceEdge(int index[8]) + { + int const numV = 6; + int vpair[2 * numV] = + { + index[4], index[5], + index[4], index[6], + index[2], index[6], + index[2], index[3], + index[1], index[3], + index[1], index[5] + }; + + int const numT = 4; + int itriple[3 * numT] = + { + 0, 1, 2, + 0, 2, 3, + 0, 3, 4, + 0, 4, 5 + }; + + SetTable(numV, vpair, numT, itriple); + } + + void Bits4FaceFaceDiagL(int index[8]) + { + int const numV = 6; + int vpair[2 * numV] = + { + index[4], index[5], + index[0], index[4], + index[2], index[6], + index[2], index[3], + index[1], index[3], + index[5], index[7] + }; + + int const numT = 4; + int itriple[3 * numT] = + { + 0, 1, 2, + 0, 2, 3, + 0, 3, 4, + 0, 4, 5 + }; + + SetTable(numV, vpair, numT, itriple); + } + + void Bits4FaceFaceDiagR(int index[8]) + { + int const numV = 6; + int vpair[2 * numV] = + { + index[4], index[6], + index[6], index[7], + index[2], index[3], + index[1], index[3], + index[1], index[5], + index[0], index[4] + }; + + int const numT = 4; + int itriple[3 * numT] = + { + 0, 1, 2, + 0, 2, 3, + 0, 3, 4, + 0, 4, 5 + }; + + SetTable(numV, vpair, numT, itriple); + } + + void Bits4FaceBoxDiag(int index[8]) + { + int const numV = 8; + int vpair[2 * numV] = + { + index[0], index[4], + index[2], index[6], + index[2], index[3], + index[1], index[3], + index[1], index[5], + index[6], index[7], + index[5], index[7], + index[3], index[7] + }; + + int const numT = 4; + int itriple[3 * numT] = + { + 0, 1, 2, + 0, 2, 3, + 0, 3, 4, + 5, 6, 7 + }; + + SetTable(numV, vpair, numT, itriple); + } + + void Bits4EdgeEdgePara(int index[8]) + { + int const numV = 8; + int vpair[2 * numV] = + { + index[0], index[4], + index[0], index[2], + index[1], index[3], + index[1], index[5], + index[2], index[6], + index[4], index[6], + index[5], index[7], + index[3], index[7] + }; + + int const numT = 4; + int itriple[3 * numT] = + { + 0, 1, 2, + 0, 2, 3, + 4, 5, 6, + 4, 6, 7 + }; + + SetTable(numV, vpair, numT, itriple); + } + + void Bits4EdgeEdgePerp(int index[8]) + { + int const numV = 12; + int vpair[2 * numV] = + { + index[0], index[1], + index[0], index[4], + index[0], index[2], + index[2], index[6], + index[4], index[6], + index[6], index[7], + index[2], index[3], + index[3], index[7], + index[1], index[3], + index[1], index[5], + index[5], index[7], + index[4], index[5] + }; + + int const numT = 4; + int itriple[3 * numT] = + { + 0, 1, 2, + 3, 4, 5, + 6, 7, 8, + 9, 10, 11 + }; + + SetTable(numV, vpair, numT, itriple); + } + + enum ConfigurationType + { + CT_Bits0, + CT_Bits1, + CT_Bits7, + CT_Bits2Edge, + CT_Bits6Edge, + CT_Bits2FaceDiag, + CT_Bits6FaceDiag, + CT_Bits2BoxDiag, + CT_Bits6BoxDiag, + CT_Bits3SameFace, + CT_Bits5SameFace, + CT_Bits3EdgeFaceDiag, + CT_Bits5EdgeFaceDiag, + CT_Bits3FaceDiagFaceDiag, + CT_Bits5FaceDiagFaceDiag, + CT_Bits4SameFace, + CT_Bits4FaceEdge, + CT_Bits4FaceFaceDiagL, + CT_Bits4FaceFaceDiagR, + CT_Bits4FaceBoxDiag, + CT_Bits4EdgeEdgePara, + CT_Bits4EdgeEdgePerp, + CT_NUM_TYPES + }; + + typedef void (MarchingCubes::*Function)(int[8]); + + struct Configuration + { + ConfigurationType type; + Function F; + int index[8]; + }; + + static std::array& ConfigurationTable() + { + static std::array configuration = + { { + /*00000000*/{ GTE_MC_ENTRY(Bits0), 0, 1, 2, 3, 4, 5, 6, 7 }, + /*00000001*/{ GTE_MC_ENTRY(Bits1), 0, 1, 2, 3, 4, 5, 6, 7 }, + /*00000010*/{ GTE_MC_ENTRY(Bits1), 1, 3, 0, 2, 5, 7, 4, 6 }, + /*00000011*/{ GTE_MC_ENTRY(Bits2Edge), 0, 1, 2, 3, 4, 5, 6, 7 }, + /*00000100*/{ GTE_MC_ENTRY(Bits1), 2, 0, 3, 1, 6, 4, 7, 5 }, + /*00000101*/{ GTE_MC_ENTRY(Bits2Edge), 2, 0, 3, 1, 6, 4, 7, 5 }, + /*00000110*/{ GTE_MC_ENTRY(Bits2FaceDiag), 1, 3, 0, 2, 5, 7, 4, 6 }, + /*00000111*/{ GTE_MC_ENTRY(Bits3SameFace), 0, 1, 2, 3, 4, 5, 6, 7 }, + /*00001000*/{ GTE_MC_ENTRY(Bits1), 3, 2, 1, 0, 7, 6, 5, 4 }, + /*00001001*/{ GTE_MC_ENTRY(Bits2FaceDiag), 0, 1, 2, 3, 4, 5, 6, 7 }, + /*00001010*/{ GTE_MC_ENTRY(Bits2Edge), 1, 3, 0, 2, 5, 7, 4, 6 }, + /*00001011*/{ GTE_MC_ENTRY(Bits3SameFace), 1, 3, 0, 2, 5, 7, 4, 6 }, + /*00001100*/{ GTE_MC_ENTRY(Bits2Edge), 3, 2, 1, 0, 7, 6, 5, 4 }, + /*00001101*/{ GTE_MC_ENTRY(Bits3SameFace), 2, 0, 3, 1, 6, 4, 7, 5 }, + /*00001110*/{ GTE_MC_ENTRY(Bits3SameFace), 3, 2, 1, 0, 7, 6, 5, 4 }, + /*00001111*/{ GTE_MC_ENTRY(Bits4SameFace), 0, 1, 2, 3, 4, 5, 6, 7 }, + /*00010000*/{ GTE_MC_ENTRY(Bits1), 4, 5, 0, 1, 6, 7, 2, 3 }, + /*00010001*/{ GTE_MC_ENTRY(Bits2Edge), 4, 0, 6, 2, 5, 1, 7, 3 }, + /*00010010*/{ GTE_MC_ENTRY(Bits2FaceDiag), 1, 0, 5, 4, 3, 2, 7, 6 }, + /*00010011*/{ GTE_MC_ENTRY(Bits3SameFace), 0, 4, 1, 5, 2, 6, 3, 7 }, + /*00010100*/{ GTE_MC_ENTRY(Bits2FaceDiag), 4, 0, 6, 2, 5, 1, 7, 3 }, + /*00010101*/{ GTE_MC_ENTRY(Bits3SameFace), 0, 2, 4, 6, 1, 3, 5, 7 }, + /*00010110*/{ GTE_MC_ENTRY(Bits3FaceDiagFaceDiag), 2, 0, 3, 1, 6, 4, 7, 5 }, + /*00010111*/{ GTE_MC_ENTRY(Bits4FaceEdge), 0, 1, 2, 3, 4, 5, 6, 7 }, + /*00011000*/{ GTE_MC_ENTRY(Bits2BoxDiag), 3, 2, 1, 0, 7, 6, 5, 4 }, + /*00011001*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 0, 1, 2, 3, 4, 5, 6, 7 }, + /*00011010*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 1, 0, 5, 4, 3, 2, 7, 6 }, + /*00011011*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagR), 1, 3, 0, 2, 5, 7, 4, 6 }, + /*00011100*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 2, 6, 0, 4, 3, 7, 1, 5 }, + /*00011101*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagL), 2, 0, 3, 1, 6, 4, 7, 5 }, + /*00011110*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 3, 2, 1, 0, 7, 6, 5, 4 }, + /*00011111*/{ GTE_MC_ENTRY(Bits5SameFace), 7, 5, 6, 4, 3, 1, 2, 0 }, + /*00100000*/{ GTE_MC_ENTRY(Bits1), 5, 7, 1, 3, 4, 6, 0, 2 }, + /*00100001*/{ GTE_MC_ENTRY(Bits2FaceDiag), 0, 4, 1, 5, 2, 6, 3, 7 }, + /*00100010*/{ GTE_MC_ENTRY(Bits2Edge), 5, 1, 4, 0, 7, 3, 6, 2 }, + /*00100011*/{ GTE_MC_ENTRY(Bits3SameFace), 1, 0, 5, 4, 3, 2, 7, 6 }, + /*00100100*/{ GTE_MC_ENTRY(Bits2BoxDiag), 2, 0, 3, 1, 6, 4, 7, 5 }, + /*00100101*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 0, 4, 1, 5, 2, 6, 3, 7 }, + /*00100110*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 1, 3, 0, 2, 5, 7, 4, 6 }, + /*00100111*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagL), 0, 1, 2, 3, 4, 5, 6, 7 }, + /*00101000*/{ GTE_MC_ENTRY(Bits2FaceDiag), 5, 7, 1, 3, 4, 6, 0, 2 }, + /*00101001*/{ GTE_MC_ENTRY(Bits3FaceDiagFaceDiag), 0, 1, 2, 3, 4, 5, 6, 7 }, + /*00101010*/{ GTE_MC_ENTRY(Bits3SameFace), 1, 5, 3, 7, 0, 4, 2, 6 }, + /*00101011*/{ GTE_MC_ENTRY(Bits4FaceEdge), 1, 3, 0, 2, 5, 7, 4, 6 }, + /*00101100*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 3, 1, 7, 5, 2, 0, 6, 4 }, + /*00101101*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 2, 0, 3, 1, 6, 4, 7, 5 }, + /*00101110*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagR), 3, 2, 1, 0, 7, 6, 5, 4 }, + /*00101111*/{ GTE_MC_ENTRY(Bits5SameFace), 6, 7, 4, 5, 2, 3, 0, 1 }, + /*00110000*/{ GTE_MC_ENTRY(Bits2Edge), 4, 5, 0, 1, 6, 7, 2, 3 }, + /*00110001*/{ GTE_MC_ENTRY(Bits3SameFace), 4, 5, 0, 1, 6, 7, 2, 3 }, + /*00110010*/{ GTE_MC_ENTRY(Bits3SameFace), 5, 1, 4, 0, 7, 3, 6, 2 }, + /*00110011*/{ GTE_MC_ENTRY(Bits4SameFace), 0, 4, 1, 5, 2, 6, 3, 7 }, + /*00110100*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 4, 0, 6, 2, 5, 1, 7, 3 }, + /*00110101*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagR), 0, 2, 4, 6, 1, 3, 5, 7 }, + /*00110110*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 5, 1, 4, 0, 7, 3, 6, 2 }, + /*00110111*/{ GTE_MC_ENTRY(Bits5SameFace), 7, 6, 3, 2, 5, 4, 1, 0 }, + /*00111000*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 5, 7, 1, 3, 4, 6, 0, 2 }, + /*00111001*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 4, 5, 0, 1, 6, 7, 2, 3 }, + /*00111010*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagL), 5, 1, 4, 0, 7, 3, 6, 2 }, + /*00111011*/{ GTE_MC_ENTRY(Bits5SameFace), 6, 2, 7, 3, 4, 0, 5, 1 }, + /*00111100*/{ GTE_MC_ENTRY(Bits4EdgeEdgePara), 3, 2, 1, 0, 7, 6, 5, 4 }, + /*00111101*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 7, 3, 5, 1, 6, 2, 4, 0 }, + /*00111110*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 6, 4, 2, 0, 7, 5, 3, 1 }, + /*00111111*/{ GTE_MC_ENTRY(Bits6Edge), 6, 7, 4, 5, 2, 3, 0, 1 }, + /*01000000*/{ GTE_MC_ENTRY(Bits1), 6, 7, 4, 5, 2, 3, 0, 1 }, + /*01000001*/{ GTE_MC_ENTRY(Bits2FaceDiag), 0, 2, 4, 6, 1, 3, 5, 7 }, + /*01000010*/{ GTE_MC_ENTRY(Bits2BoxDiag), 1, 3, 0, 2, 5, 7, 4, 6 }, + /*01000011*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 0, 2, 4, 6, 1, 3, 5, 7 }, + /*01000100*/{ GTE_MC_ENTRY(Bits2Edge), 6, 2, 7, 3, 4, 0, 5, 1 }, + /*01000101*/{ GTE_MC_ENTRY(Bits3SameFace), 2, 6, 0, 4, 3, 7, 1, 5 }, + /*01000110*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 2, 0, 3, 1, 6, 4, 7, 5 }, + /*01000111*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagR), 0, 1, 2, 3, 4, 5, 6, 7 }, + /*01001000*/{ GTE_MC_ENTRY(Bits2FaceDiag), 3, 7, 2, 6, 1, 5, 0, 4 }, + /*01001001*/{ GTE_MC_ENTRY(Bits3FaceDiagFaceDiag), 3, 2, 1, 0, 7, 6, 5, 4 }, + /*01001010*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 3, 7, 2, 6, 1, 5, 0, 4 }, + /*01001011*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 1, 3, 0, 2, 5, 7, 4, 6 }, + /*01001100*/{ GTE_MC_ENTRY(Bits3SameFace), 2, 3, 6, 7, 0, 1, 4, 5 }, + /*01001101*/{ GTE_MC_ENTRY(Bits4FaceEdge), 2, 0, 3, 1, 6, 4, 7, 5 }, + /*01001110*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagL), 3, 2, 1, 0, 7, 6, 5, 4 }, + /*01001111*/{ GTE_MC_ENTRY(Bits5SameFace), 5, 4, 7, 6, 1, 0, 3, 2 }, + /*01010000*/{ GTE_MC_ENTRY(Bits2Edge), 6, 4, 2, 0, 7, 5, 3, 1 }, + /*01010001*/{ GTE_MC_ENTRY(Bits3SameFace), 4, 0, 6, 2, 5, 1, 7, 3 }, + /*01010010*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 4, 5, 0, 1, 6, 7, 2, 3 }, + /*01010011*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagL), 0, 4, 1, 5, 2, 6, 3, 7 }, + /*01010100*/{ GTE_MC_ENTRY(Bits3SameFace), 6, 4, 2, 0, 7, 5, 3, 1 }, + /*01010101*/{ GTE_MC_ENTRY(Bits4SameFace), 0, 2, 4, 6, 1, 3, 5, 7 }, + /*01010110*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 6, 4, 2, 0, 7, 5, 3, 1 }, + /*01010111*/{ GTE_MC_ENTRY(Bits5SameFace), 7, 3, 5, 1, 6, 2, 4, 0 }, + /*01011000*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 6, 2, 7, 3, 4, 0, 5, 1 }, + /*01011001*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 4, 0, 6, 2, 5, 1, 7, 3 }, + /*01011010*/{ GTE_MC_ENTRY(Bits4EdgeEdgePara), 1, 3, 0, 2, 5, 7, 4, 6 }, + /*01011011*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 7, 6, 3, 2, 5, 4, 1, 0 }, + /*01011100*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagR), 6, 4, 2, 0, 7, 5, 3, 1 }, + /*01011101*/{ GTE_MC_ENTRY(Bits5SameFace), 5, 7, 1, 3, 4, 6, 0, 2 }, + /*01011110*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 5, 1, 4, 0, 7, 3, 6, 2 }, + /*01011111*/{ GTE_MC_ENTRY(Bits6Edge), 5, 7, 1, 3, 4, 6, 0, 2 }, + /*01100000*/{ GTE_MC_ENTRY(Bits2FaceDiag), 5, 4, 7, 6, 1, 0, 3, 2 }, + /*01100001*/{ GTE_MC_ENTRY(Bits3FaceDiagFaceDiag), 5, 4, 7, 6, 1, 0, 3, 2 }, + /*01100010*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 5, 4, 7, 6, 1, 0, 3, 2 }, + /*01100011*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 1, 0, 5, 4, 3, 2, 7, 6 }, + /*01100100*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 6, 7, 4, 5, 2, 3, 0, 1 }, + /*01100101*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 2, 6, 0, 4, 3, 7, 1, 5 }, + /*01100110*/{ GTE_MC_ENTRY(Bits4EdgeEdgePara), 6, 2, 7, 3, 4, 0, 5, 1 }, + /*01100111*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 7, 5, 6, 4, 3, 1, 2, 0 }, + /*01101000*/{ GTE_MC_ENTRY(Bits3FaceDiagFaceDiag), 6, 7, 4, 5, 2, 3, 0, 1 }, + /*01101001*/{ GTE_MC_ENTRY(Bits4EdgeEdgePerp), 0, 1, 2, 3, 4, 5, 6, 7 }, + /*01101010*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 1, 5, 3, 7, 0, 4, 2, 6 }, + /*01101011*/{ GTE_MC_ENTRY(Bits5FaceDiagFaceDiag), 4, 6, 5, 7, 0, 2, 1, 3 }, + /*01101100*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 2, 3, 6, 7, 0, 1, 4, 5 }, + /*01101101*/{ GTE_MC_ENTRY(Bits5FaceDiagFaceDiag), 7, 5, 6, 4, 3, 1, 2, 0 }, + /*01101110*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 4, 6, 5, 7, 0, 2, 1, 3 }, + /*01101111*/{ GTE_MC_ENTRY(Bits6FaceDiag), 7, 5, 6, 4, 3, 1, 2, 0 }, + /*01110000*/{ GTE_MC_ENTRY(Bits3SameFace), 4, 6, 5, 7, 0, 2, 1, 3 }, + /*01110001*/{ GTE_MC_ENTRY(Bits4FaceEdge), 4, 6, 5, 7, 0, 2, 1, 3 }, + /*01110010*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagR), 5, 1, 4, 0, 7, 3, 6, 2 }, + /*01110011*/{ GTE_MC_ENTRY(Bits5SameFace), 3, 7, 2, 6, 1, 5, 0, 4 }, + /*01110100*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagL), 4, 6, 5, 7, 0, 2, 1, 3 }, + /*01110101*/{ GTE_MC_ENTRY(Bits5SameFace), 3, 1, 7, 5, 2, 0, 6, 4 }, + /*01110110*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 3, 2, 1, 0, 7, 6, 5, 4 }, + /*01110111*/{ GTE_MC_ENTRY(Bits6Edge), 3, 7, 2, 6, 1, 5, 0, 4 }, + /*01111000*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 4, 6, 5, 7, 0, 2, 1, 3 }, + /*01111001*/{ GTE_MC_ENTRY(Bits5FaceDiagFaceDiag), 1, 3, 0, 2, 5, 7, 4, 6 }, + /*01111010*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 2, 3, 6, 7, 0, 1, 4, 5 }, + /*01111011*/{ GTE_MC_ENTRY(Bits6FaceDiag), 2, 3, 6, 7, 0, 1, 4, 5 }, + /*01111100*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 1, 5, 3, 7, 0, 4, 2, 6 }, + /*01111101*/{ GTE_MC_ENTRY(Bits6FaceDiag), 1, 5, 3, 7, 0, 4, 2, 6 }, + /*01111110*/{ GTE_MC_ENTRY(Bits6BoxDiag), 0, 1, 2, 3, 4, 5, 6, 7 }, + /*01111111*/{ GTE_MC_ENTRY(Bits7), 7, 3, 5, 1, 6, 2, 4, 0 }, + /*10000000*/{ GTE_MC_ENTRY(Bits1), 7, 3, 5, 1, 6, 2, 4, 0 }, + /*10000001*/{ GTE_MC_ENTRY(Bits2BoxDiag), 0, 1, 2, 3, 4, 5, 6, 7 }, + /*10000010*/{ GTE_MC_ENTRY(Bits2FaceDiag), 1, 5, 3, 7, 0, 4, 2, 6 }, + /*10000011*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 1, 5, 3, 7, 0, 4, 2, 6 }, + /*10000100*/{ GTE_MC_ENTRY(Bits2FaceDiag), 2, 3, 6, 7, 0, 1, 4, 5 }, + /*10000101*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 2, 3, 6, 7, 0, 1, 4, 5 }, + /*10000110*/{ GTE_MC_ENTRY(Bits3FaceDiagFaceDiag), 1, 3, 0, 2, 5, 7, 4, 6 }, + /*10000111*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 0, 1, 2, 3, 4, 5, 6, 7 }, + /*10001000*/{ GTE_MC_ENTRY(Bits2Edge), 3, 7, 2, 6, 1, 5, 0, 4 }, + /*10001001*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 3, 2, 1, 0, 7, 6, 5, 4 }, + /*10001010*/{ GTE_MC_ENTRY(Bits3SameFace), 3, 1, 7, 5, 2, 0, 6, 4 }, + /*10001011*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagL), 1, 3, 0, 2, 5, 7, 4, 6 }, + /*10001100*/{ GTE_MC_ENTRY(Bits3SameFace), 3, 7, 2, 6, 1, 5, 0, 4 }, + /*10001101*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagR), 2, 0, 3, 1, 6, 4, 7, 5 }, + /*10001110*/{ GTE_MC_ENTRY(Bits4FaceEdge), 3, 2, 1, 0, 7, 6, 5, 4 }, + /*10001111*/{ GTE_MC_ENTRY(Bits5SameFace), 4, 6, 5, 7, 0, 2, 1, 3 }, + /*10010000*/{ GTE_MC_ENTRY(Bits2FaceDiag), 7, 5, 6, 4, 3, 1, 2, 0 }, + /*10010001*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 4, 6, 5, 7, 0, 2, 1, 3 }, + /*10010010*/{ GTE_MC_ENTRY(Bits3FaceDiagFaceDiag), 7, 5, 6, 4, 3, 1, 2, 0 }, + /*10010011*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 0, 4, 1, 5, 2, 6, 3, 7 }, + /*10010100*/{ GTE_MC_ENTRY(Bits3FaceDiagFaceDiag), 4, 6, 5, 7, 0, 2, 1, 3 }, + /*10010101*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 0, 2, 4, 6, 1, 3, 5, 7 }, + /*10010110*/{ GTE_MC_ENTRY(Bits4EdgeEdgePerp), 1, 3, 0, 2, 5, 7, 4, 6 }, + /*10010111*/{ GTE_MC_ENTRY(Bits5FaceDiagFaceDiag), 6, 7, 4, 5, 2, 3, 0, 1 }, + /*10011000*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 7, 5, 6, 4, 3, 1, 2, 0 }, + /*10011001*/{ GTE_MC_ENTRY(Bits4EdgeEdgePara), 4, 0, 6, 2, 5, 1, 7, 3 }, + /*10011010*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 3, 1, 7, 5, 2, 0, 6, 4 }, + /*10011011*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 6, 7, 4, 5, 2, 3, 0, 1 }, + /*10011100*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 3, 7, 2, 6, 1, 5, 0, 4 }, + /*10011101*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 5, 4, 7, 6, 1, 0, 3, 2 }, + /*10011110*/{ GTE_MC_ENTRY(Bits5FaceDiagFaceDiag), 5, 4, 7, 6, 1, 0, 3, 2 }, + /*10011111*/{ GTE_MC_ENTRY(Bits6FaceDiag), 5, 4, 7, 6, 1, 0, 3, 2 }, + /*10100000*/{ GTE_MC_ENTRY(Bits2Edge), 5, 7, 1, 3, 4, 6, 0, 2 }, + /*10100001*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 5, 1, 4, 0, 7, 3, 6, 2 }, + /*10100010*/{ GTE_MC_ENTRY(Bits3SameFace), 5, 7, 1, 3, 4, 6, 0, 2 }, + /*10100011*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagR), 1, 0, 5, 4, 3, 2, 7, 6 }, + /*10100100*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 7, 6, 3, 2, 5, 4, 1, 0 }, + /*10100101*/{ GTE_MC_ENTRY(Bits4EdgeEdgePara), 2, 0, 3, 1, 6, 4, 7, 5 }, + /*10100110*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 5, 7, 1, 3, 4, 6, 0, 2 }, + /*10100111*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 6, 2, 7, 3, 4, 0, 5, 1 }, + /*10101000*/{ GTE_MC_ENTRY(Bits3SameFace), 7, 3, 5, 1, 6, 2, 4, 0 }, + /*10101001*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 7, 3, 5, 1, 6, 2, 4, 0 }, + /*10101010*/{ GTE_MC_ENTRY(Bits4SameFace), 1, 5, 3, 7, 0, 4, 2, 6 }, + /*10101011*/{ GTE_MC_ENTRY(Bits5SameFace), 6, 4, 2, 0, 7, 5, 3, 1 }, + /*10101100*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagL), 3, 7, 2, 6, 1, 5, 0, 4 }, + /*10101101*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 4, 5, 0, 1, 6, 7, 2, 3 }, + /*10101110*/{ GTE_MC_ENTRY(Bits5SameFace), 4, 0, 6, 2, 5, 1, 7, 3 }, + /*10101111*/{ GTE_MC_ENTRY(Bits6Edge), 6, 4, 2, 0, 7, 5, 3, 1 }, + /*10110000*/{ GTE_MC_ENTRY(Bits3SameFace), 5, 4, 7, 6, 1, 0, 3, 2 }, + /*10110001*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagL), 4, 5, 0, 1, 6, 7, 2, 3 }, + /*10110010*/{ GTE_MC_ENTRY(Bits4FaceEdge), 5, 1, 4, 0, 7, 3, 6, 2 }, + /*10110011*/{ GTE_MC_ENTRY(Bits5SameFace), 2, 3, 6, 7, 0, 1, 4, 5 }, + /*10110100*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 5, 4, 7, 6, 1, 0, 3, 2 }, + /*10110101*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 3, 7, 2, 6, 1, 5, 0, 4 }, + /*10110110*/{ GTE_MC_ENTRY(Bits5FaceDiagFaceDiag), 3, 2, 1, 0, 7, 6, 5, 4 }, + /*10110111*/{ GTE_MC_ENTRY(Bits6FaceDiag), 3, 7, 2, 6, 1, 5, 0, 4 }, + /*10111000*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagR), 7, 3, 5, 1, 6, 2, 4, 0 }, + /*10111001*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 2, 0, 3, 1, 6, 4, 7, 5 }, + /*10111010*/{ GTE_MC_ENTRY(Bits5SameFace), 2, 6, 0, 4, 3, 7, 1, 5 }, + /*10111011*/{ GTE_MC_ENTRY(Bits6Edge), 6, 2, 7, 3, 4, 0, 5, 1 }, + /*10111100*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 0, 2, 4, 6, 1, 3, 5, 7 }, + /*10111101*/{ GTE_MC_ENTRY(Bits6BoxDiag), 1, 3, 0, 2, 5, 7, 4, 6 }, + /*10111110*/{ GTE_MC_ENTRY(Bits6FaceDiag), 0, 2, 4, 6, 1, 3, 5, 7 }, + /*10111111*/{ GTE_MC_ENTRY(Bits7), 6, 7, 4, 5, 2, 3, 0, 1 }, + /*11000000*/{ GTE_MC_ENTRY(Bits2Edge), 6, 7, 4, 5, 2, 3, 0, 1 }, + /*11000001*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 6, 4, 2, 0, 7, 5, 3, 1 }, + /*11000010*/{ GTE_MC_ENTRY(Bits3EdgeFaceDiag), 7, 3, 5, 1, 6, 2, 4, 0 }, + /*11000011*/{ GTE_MC_ENTRY(Bits4EdgeEdgePara), 0, 1, 2, 3, 4, 5, 6, 7 }, + /*11000100*/{ GTE_MC_ENTRY(Bits3SameFace), 6, 2, 7, 3, 4, 0, 5, 1 }, + /*11000101*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagL), 2, 6, 0, 4, 3, 7, 1, 5 }, + /*11000110*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 6, 2, 7, 3, 4, 0, 5, 1 }, + /*11000111*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 5, 7, 1, 3, 4, 6, 0, 2 }, + /*11001000*/{ GTE_MC_ENTRY(Bits3SameFace), 7, 6, 3, 2, 5, 4, 1, 0 }, + /*11001001*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 7, 6, 3, 2, 5, 4, 1, 0 }, + /*11001010*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagR), 7, 6, 3, 2, 5, 4, 1, 0 }, + /*11001011*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 4, 0, 6, 2, 5, 1, 7, 3 }, + /*11001100*/{ GTE_MC_ENTRY(Bits4SameFace), 2, 3, 6, 7, 0, 1, 4, 5 }, + /*11001101*/{ GTE_MC_ENTRY(Bits5SameFace), 5, 1, 4, 0, 7, 3, 6, 2 }, + /*11001110*/{ GTE_MC_ENTRY(Bits5SameFace), 4, 5, 0, 1, 6, 7, 2, 3 }, + /*11001111*/{ GTE_MC_ENTRY(Bits6Edge), 4, 5, 0, 1, 6, 7, 2, 3 }, + /*11010000*/{ GTE_MC_ENTRY(Bits3SameFace), 6, 7, 4, 5, 2, 3, 0, 1 }, + /*11010001*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagR), 4, 0, 6, 2, 5, 1, 7, 3 }, + /*11010010*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 6, 7, 4, 5, 2, 3, 0, 1 }, + /*11010011*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 3, 1, 7, 5, 2, 0, 6, 4 }, + /*11010100*/{ GTE_MC_ENTRY(Bits4FaceEdge), 6, 4, 2, 0, 7, 5, 3, 1 }, + /*11010101*/{ GTE_MC_ENTRY(Bits5SameFace), 1, 5, 3, 7, 0, 4, 2, 6 }, + /*11010110*/{ GTE_MC_ENTRY(Bits5FaceDiagFaceDiag), 0, 1, 2, 3, 4, 5, 6, 7 }, + /*11010111*/{ GTE_MC_ENTRY(Bits6FaceDiag), 5, 7, 1, 3, 4, 6, 0, 2 }, + /*11011000*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagL), 6, 7, 4, 5, 2, 3, 0, 1 }, + /*11011001*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 1, 3, 0, 2, 5, 7, 4, 6 }, + /*11011010*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 0, 4, 1, 5, 2, 6, 3, 7 }, + /*11011011*/{ GTE_MC_ENTRY(Bits6BoxDiag), 2, 0, 3, 1, 6, 4, 7, 5 }, + /*11011100*/{ GTE_MC_ENTRY(Bits5SameFace), 1, 0, 5, 4, 3, 2, 7, 6 }, + /*11011101*/{ GTE_MC_ENTRY(Bits6Edge), 5, 1, 4, 0, 7, 3, 6, 2 }, + /*11011110*/{ GTE_MC_ENTRY(Bits6FaceDiag), 0, 4, 1, 5, 2, 6, 3, 7 }, + /*11011111*/{ GTE_MC_ENTRY(Bits7), 5, 7, 1, 3, 4, 6, 0, 2 }, + /*11100000*/{ GTE_MC_ENTRY(Bits3SameFace), 7, 5, 6, 4, 3, 1, 2, 0 }, + /*11100001*/{ GTE_MC_ENTRY(Bits4FaceBoxDiag), 7, 5, 6, 4, 3, 1, 2, 0 }, + /*11100010*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagL), 7, 5, 6, 4, 3, 1, 2, 0 }, + /*11100011*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 2, 6, 0, 4, 3, 7, 1, 5 }, + /*11100100*/{ GTE_MC_ENTRY(Bits4FaceFaceDiagR), 7, 5, 6, 4, 3, 1, 2, 0 }, + /*11100101*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 1, 0, 5, 4, 3, 2, 7, 6 }, + /*11100110*/{ GTE_MC_ENTRY(Bits5EdgeFaceDiag), 0, 1, 2, 3, 4, 5, 6, 7 }, + /*11100111*/{ GTE_MC_ENTRY(Bits6BoxDiag), 3, 2, 1, 0, 7, 6, 5, 4 }, + /*11101000*/{ GTE_MC_ENTRY(Bits4FaceEdge), 7, 5, 6, 4, 3, 1, 2, 0 }, + /*11101001*/{ GTE_MC_ENTRY(Bits5FaceDiagFaceDiag), 2, 0, 3, 1, 6, 4, 7, 5 }, + /*11101010*/{ GTE_MC_ENTRY(Bits5SameFace), 0, 2, 4, 6, 1, 3, 5, 7 }, + /*11101011*/{ GTE_MC_ENTRY(Bits6FaceDiag), 4, 0, 6, 2, 5, 1, 7, 3 }, + /*11101100*/{ GTE_MC_ENTRY(Bits5SameFace), 0, 4, 1, 5, 2, 6, 3, 7 }, + /*11101101*/{ GTE_MC_ENTRY(Bits6FaceDiag), 1, 0, 5, 4, 3, 2, 7, 6 }, + /*11101110*/{ GTE_MC_ENTRY(Bits6Edge), 4, 0, 6, 2, 5, 1, 7, 3 }, + /*11101111*/{ GTE_MC_ENTRY(Bits7), 4, 5, 0, 1, 6, 7, 2, 3 }, + /*11110000*/{ GTE_MC_ENTRY(Bits4SameFace), 4, 6, 5, 7, 0, 2, 1, 3 }, + /*11110001*/{ GTE_MC_ENTRY(Bits5SameFace), 3, 2, 1, 0, 7, 6, 5, 4 }, + /*11110010*/{ GTE_MC_ENTRY(Bits5SameFace), 2, 0, 3, 1, 6, 4, 7, 5 }, + /*11110011*/{ GTE_MC_ENTRY(Bits6Edge), 3, 2, 1, 0, 7, 6, 5, 4 }, + /*11110100*/{ GTE_MC_ENTRY(Bits5SameFace), 1, 3, 0, 2, 5, 7, 4, 6 }, + /*11110101*/{ GTE_MC_ENTRY(Bits6Edge), 1, 3, 0, 2, 5, 7, 4, 6 }, + /*11110110*/{ GTE_MC_ENTRY(Bits6FaceDiag), 0, 1, 2, 3, 4, 5, 6, 7 }, + /*11110111*/{ GTE_MC_ENTRY(Bits7), 3, 2, 1, 0, 7, 6, 5, 4 }, + /*11111000*/{ GTE_MC_ENTRY(Bits5SameFace), 0, 1, 2, 3, 4, 5, 6, 7 }, + /*11111001*/{ GTE_MC_ENTRY(Bits6FaceDiag), 1, 3, 0, 2, 5, 7, 4, 6 }, + /*11111010*/{ GTE_MC_ENTRY(Bits6Edge), 2, 0, 3, 1, 6, 4, 7, 5 }, + /*11111011*/{ GTE_MC_ENTRY(Bits7), 2, 0, 3, 1, 6, 4, 7, 5 }, + /*11111100*/{ GTE_MC_ENTRY(Bits6Edge), 0, 1, 2, 3, 4, 5, 6, 7 }, + /*11111101*/{ GTE_MC_ENTRY(Bits7), 1, 3, 0, 2, 5, 7, 4, 6 }, + /*11111110*/{ GTE_MC_ENTRY(Bits7), 0, 1, 2, 3, 4, 5, 6, 7 }, + /*11111111*/{ GTE_MC_ENTRY(Bits0), 0, 1, 2, 3, 4, 5, 6, 7 } + }}; + return configuration; + } + + static std::array& ConfigurationString() + { + static std::array configurationString = + { + "Bits0", + "Bits1", + "Bits7", + "Bits2Edge", + "Bits6Edge", + "Bits2FaceDiag", + "Bits6FaceDiag", + "Bits2BoxDiag", + "Bits6BoxDiag", + "Bits3SameFace", + "Bits5SameFace", + "Bits3EdgeFaceDiag", + "Bits5EdgeFaceDiag", + "Bits3FaceDiagFaceDiag", + "Bits5FaceDiagFaceDiag", + "Bits4SameFace", + "Bits4FaceEdge", + "Bits4FaceFaceDiagL", + "Bits4FaceFaceDiagR", + "Bits4FaceBoxDiag", + "Bits4EdgeEdgePara", + "Bits4EdgeEdgePerp" + }; + return configurationString; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MassSpringArbitrary.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MassSpringArbitrary.h new file mode 100644 index 0000000..0e44cc5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MassSpringArbitrary.h @@ -0,0 +1,121 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class MassSpringArbitrary : public ParticleSystem + { + public: + // Construction and destruction. This class represents a set of M + // masses that are connected by S springs with arbitrary topology. + // The function SetSpring(...) should be called for each spring that + // you want in the system. + virtual ~MassSpringArbitrary() = default; + + MassSpringArbitrary(int numParticles, int numSprings, Real step) + : + ParticleSystem(numParticles, step), + mSpring(numSprings, Spring()), + mAdjacent(numParticles) + { + } + + struct Spring + { + Spring() + : + particle0(0), + particle1(0), + constant((Real)0), + length((Real)0) + { + } + + int particle0, particle1; + Real constant, length; + }; + + // Member access. + inline int GetNumSprings() const + { + return static_cast(mSpring.size()); + } + + void SetSpring(int index, Spring const& spring) + { + mSpring[index] = spring; + mAdjacent[spring.particle0].insert(index); + mAdjacent[spring.particle1].insert(index); + } + + inline Spring const& GetSpring(int index) const + { + return mSpring[index]; + } + + // The default external force is zero. Derive a class from this one + // to provide nonzero external forces such as gravity, wind, friction, + // and so on. This function is called by Acceleration(...) to compute + // the impulse F/m generated by the external force F. + virtual Vector ExternalAcceleration(int, Real, + std::vector> const&, + std::vector> const&) + { + return Vector::Zero(); + } + + protected: + // Callback for acceleration (ODE solver uses x" = F/m) applied to + // particle i. The positions and velocities are not necessarily + // mPosition and mVelocity, because the ODE solver evaluates the + // impulse function at intermediate positions. + virtual Vector Acceleration(int i, Real time, + std::vector> const& position, + std::vector> const& velocity) + { + // Compute spring forces on position X[i]. The positions are not + // necessarily mPosition, because the RK4 solver in ParticleSystem + // evaluates the acceleration function at intermediate positions. + + Vector acceleration = ExternalAcceleration(i, time, position, velocity); + + for (auto adj : mAdjacent[i]) + { + // Process a spring connected to particle i. + Spring const& spring = mSpring[adj]; + Vector diff; + if (i != spring.particle0) + { + diff = position[spring.particle0] - position[i]; + } + else + { + diff = position[spring.particle1] - position[i]; + } + + Real ratio = spring.length / Length(diff); + Vector force = spring.constant * ((Real)1 - ratio) * diff; + acceleration += this->mInvMass[i] * force; + } + + return acceleration; + } + + std::vector mSpring; + + // Each particle has an associated array of spring indices for those + // springs adjacent to the particle. The set elements are spring + // indices, not indices of adjacent particles. + std::vector> mAdjacent; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MassSpringCurve.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MassSpringCurve.h new file mode 100644 index 0000000..3e3ade0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MassSpringCurve.h @@ -0,0 +1,112 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + template + class MassSpringCurve : public ParticleSystem + { + public: + // Construction and destruction. This class represents a set of N-1 + // springs connecting N masses that lie on a curve. + virtual ~MassSpringCurve() = default; + + MassSpringCurve(int numParticles, Real step) + : + ParticleSystem(numParticles, step), + mConstant(numParticles - 1), + mLength(numParticles - 1) + { + std::fill(mConstant.begin(), mConstant.end(), (Real)0); + std::fill(mLength.begin(), mLength.end(), (Real)0); + } + + // Member access. The parameters are spring constant and spring + // resting length. + inline int GetNumSprings() const + { + return this->mNumParticles - 1; + } + + inline void SetConstant(int i, Real constant) + { + mConstant[i] = constant; + } + + inline void SetLength(int i, Real length) + { + mLength[i] = length; + } + + inline Real const& GetConstant(int i) const + { + return mConstant[i]; + } + + inline Real const& GetLength(int i) const + { + return mLength[i]; + } + + // The default external force is zero. Derive a class from this one + // to provide nonzero external forces such as gravity, wind, friction, + // and so on. This function is called by Acceleration(...) to compute + // the impulse F/m generated by the external force F. + virtual Vector ExternalAcceleration(int, Real, + std::vector> const&, + std::vector> const&) + { + return Vector::Zero(); + } + + protected: + // Callback for acceleration (ODE solver uses x" = F/m) applied to + // particle i. The positions and velocities are not necessarily + // mPosition and mVelocity, because the ODE solver evaluates the + // impulse function at intermediate positions. + virtual Vector Acceleration(int i, Real time, + std::vector> const& position, + std::vector> const& velocity) + { + // Compute spring forces on position X[i]. The positions are not + // necessarily mPosition, because the RK4 solver in ParticleSystem + // evaluates the acceleration function at intermediate positions. + // The endpoints of the curve of masses must be handled + // separately, because each has only one spring attached to it. + + Vector acceleration = ExternalAcceleration(i, time, position, velocity); + Vector diff, force; + Real ratio; + + if (i > 0) + { + int iM1 = i - 1; + diff = position[iM1] - position[i]; + ratio = mLength[iM1] / Length(diff); + force = mConstant[iM1] * ((Real)1 - ratio) * diff; + acceleration += this->mInvMass[i] * force; + } + + int iP1 = i + 1; + if (iP1 < this->mNumParticles) + { + diff = position[iP1] - position[i]; + ratio = mLength[i] / Length(diff); + force = mConstant[i] * ((Real)1 - ratio) * diff; + acceleration += this->mInvMass[i] * force; + } + + return acceleration; + } + + std::vector mConstant, mLength; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MassSpringSurface.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MassSpringSurface.h new file mode 100644 index 0000000..326085e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MassSpringSurface.h @@ -0,0 +1,226 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + template + class MassSpringSurface : public ParticleSystem + { + public: + // Construction and destruction. This class represents an RxC array + // of masses lying on a surface and connected by an array of springs. + // The masses are indexed by mass[r][c] for 0 <= r < R and 0 <= c < C. + // The mass at interior position X[r][c] is connected by springs to + // the masses at positions X[r-1][c], X[r+1][c], X[r][c-1], and + // X[r][c+1]. Boundary masses have springs connecting them to the + // obvious neighbors ("edge" mass has 3 neighbors, "corner" mass has + // 2 neighbors). The masses are arranged in row-major order: + // position[c+C*r] = X[r][c] for 0 <= r < R and 0 <= c < C. The + // other arrays are stored similarly. + virtual ~MassSpringSurface() = default; + + MassSpringSurface(int numRows, int numCols, Real step) + : + ParticleSystem(numRows* numCols, step), + mNumRows(numRows), + mNumCols(numCols), + mConstantR(numRows* numCols), + mLengthR(numRows* numCols), + mConstantC(numRows* numCols), + mLengthC(numRows* numCols) + { + std::fill(mConstantR.begin(), mConstantR.end(), (Real)0); + std::fill(mLengthR.begin(), mLengthR.end(), (Real)0); + std::fill(mConstantC.begin(), mConstantC.end(), (Real)0); + std::fill(mLengthC.begin(), mLengthC.end(), (Real)0); + } + + // Member access. + inline int GetNumRows() const + { + return mNumRows; + } + + inline int GetNumCols() const + { + return mNumCols; + } + + inline void SetMass(int r, int c, Real mass) + { + ParticleSystem::SetMass(GetIndex(r, c), mass); + } + + inline void SetPosition(int r, int c, Vector const& position) + { + ParticleSystem::SetPosition(GetIndex(r, c), position); + } + + inline void SetVelocity(int r, int c, Vector const& velocity) + { + ParticleSystem::SetVelocity(GetIndex(r, c), velocity); + } + + inline Real const& GetMass(int r, int c) const + { + return ParticleSystem::GetMass(GetIndex(r, c)); + } + + inline Vector const& GetPosition(int r, int c) const + { + return ParticleSystem::GetPosition(GetIndex(r, c)); + } + + inline Vector const& GetVelocity(int r, int c) const + { + return ParticleSystem::GetVelocity(GetIndex(r, c)); + } + + // The interior mass at (r,c) has springs to the left, right, bottom, + // and top. Edge masses have only three neighbors and corner masses + // have only two neighbors. The mass at (r,c) provides access to the + // springs connecting to locations (r,c+1) and (r+1,c). Edge and + // corner masses provide access to only a subset of these. The caller + // is responsible for ensuring the validity of the (r,c) inputs. + + // to (r+1,c) + inline void SetConstantR(int r, int c, Real constant) + { + mConstantR[GetIndex(r, c)] = constant; + } + + // to (r+1,c) + inline void SetLengthR(int r, int c, Real length) + { + mLengthR[GetIndex(r, c)] = length; + } + + // to (r,c+1) + inline void SetConstantC(int r, int c, Real constant) + { + mConstantC[GetIndex(r, c)] = constant; + } + + // to (r,c+1) + inline void SetLengthC(int r, int c, Real length) + { + mLengthC[GetIndex(r, c)] = length; + } + + inline Real const& GetConstantR(int r, int c) const + { + return mConstantR[GetIndex(r, c)]; + } + + inline Real const& GetLengthR(int r, int c) const + { + return mLengthR[GetIndex(r, c)]; + } + + inline Real const& GetConstantC(int r, int c) const + { + return mConstantC[GetIndex(r, c)]; + } + + inline Real const& GetLengthC(int r, int c) const + { + return mLengthC[GetIndex(r, c)]; + } + + // The default external force is zero. Derive a class from this one + // to provide nonzero external forces such as gravity, wind, friction, + // and so on. This function is called by Acceleration(...) to compute + // the impulse F/m generated by the external force F. + virtual Vector ExternalAcceleration(int, Real, + std::vector> const&, + std::vector> const&) + { + return Vector::Zero(); + } + + protected: + // Callback for acceleration (ODE solver uses x" = F/m) applied to + // particle i. The positions and velocities are not necessarily + // mPosition and mVelocity, because the ODE solver evaluates the + // impulse function at intermediate positions. + virtual Vector Acceleration(int i, Real time, + std::vector> const& position, + std::vector> const& velocity) + { + // Compute spring forces on position X[i]. The positions are not + // necessarily mPosition, because the RK4 solver in ParticleSystem + // evaluates the acceleration function at intermediate positions. + // The edge and corner points of the surface of masses must be + // handled separately, because each has fewer than four springs + // attached to it. + + Vector acceleration = ExternalAcceleration(i, time, position, velocity); + Vector diff, force; + Real ratio; + + int r, c, prev, next; + GetCoordinates(i, r, c); + + if (r > 0) + { + prev = i - mNumCols; // index to previous row-neighbor + diff = position[prev] - position[i]; + ratio = GetLengthR(r - 1, c) / Length(diff); + force = GetConstantR(r - 1, c) * ((Real)1 - ratio) * diff; + acceleration += this->mInvMass[i] * force; + } + + if (r < mNumRows - 1) + { + next = i + mNumCols; // index to next row-neighbor + diff = position[next] - position[i]; + ratio = GetLengthR(r, c) / Length(diff); + force = GetConstantR(r, c) * ((Real)1 - ratio) * diff; + acceleration += this->mInvMass[i] * force; + } + + if (c > 0) + { + prev = i - 1; // index to previous col-neighbor + diff = position[prev] - position[i]; + ratio = GetLengthC(r, c - 1) / Length(diff); + force = GetConstantC(r, c - 1) * ((Real)1 - ratio) * diff; + acceleration += this->mInvMass[i] * force; + } + + if (c < mNumCols - 1) + { + next = i + 1; // index to next col-neighbor + diff = position[next] - position[i]; + ratio = GetLengthC(r, c) / Length(diff); + force = GetConstantC(r, c) * ((Real)1 - ratio) * diff; + acceleration += this->mInvMass[i] * force; + } + + return acceleration; + } + + inline int GetIndex(int r, int c) const + { + return c + mNumCols * r; + } + + void GetCoordinates(int i, int& r, int& c) const + { + c = i % mNumCols; + r = i / mNumCols; + } + + int mNumRows, mNumCols; + std::vector mConstantR, mLengthR; + std::vector mConstantC, mLengthC; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MassSpringVolume.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MassSpringVolume.h new file mode 100644 index 0000000..e4fe954 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MassSpringVolume.h @@ -0,0 +1,282 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + template + class MassSpringVolume : public ParticleSystem + { + public: + // Construction and destruction. This class represents an SxRxC array + // of masses lying on in a volume and connected by an array of + // springs. The masses are indexed by mass[s][r][c] for 0 <= s < S, + // 0 <= r < R and 0 <= c < C. The mass at interior position X[s][r][c] + // is connected by springs to the masses at positions X[s][r-1][c], + // X[s][r+1][c], X[s][r][c-1], X[s][r][c+1], X[s-1][r][c] and + // X[s+1][r][c]. Boundary masses have springs connecting them to the + // obvious neighbors ("face" mass has 5 neighbors, "edge" mass has 4 + // neighbors, "corner" mass has 3 neighbors). The masses are arranged + // in lexicographical order: position[c+C*(r+R*s)] = X[s][r][c] for + // 0 <= s < S, 0 <= r < R, and 0 <= c < C. The other arrays are + // stored similarly. + virtual ~MassSpringVolume() = default; + + MassSpringVolume(int numSlices, int numRows, int numCols, Real step) + : + ParticleSystem(numSlices* numRows* numCols, step), + mNumSlices(numSlices), + mNumRows(numRows), + mNumCols(numCols), + mConstantS(numSlices* numRows* numCols), + mLengthS(numSlices* numRows* numCols), + mConstantR(numSlices* numRows* numCols), + mLengthR(numSlices* numRows* numCols), + mConstantC(numSlices* numRows* numCols), + mLengthC(numSlices* numRows* numCols) + { + std::fill(mConstantS.begin(), mConstantS.end(), (Real)0); + std::fill(mLengthS.begin(), mLengthS.end(), (Real)0); + std::fill(mConstantR.begin(), mConstantR.end(), (Real)0); + std::fill(mLengthR.begin(), mLengthR.end(), (Real)0); + std::fill(mConstantC.begin(), mConstantC.end(), (Real)0); + std::fill(mLengthC.begin(), mLengthC.end(), (Real)0); + } + + // Member access. + inline int GetNumSlices() const + { + return mNumSlices; + } + + inline int GetNumRows() const + { + return mNumRows; + } + + inline int GetNumCols() const + { + return mNumCols; + } + + inline void SetMass(int s, int r, int c, Real mass) + { + ParticleSystem::SetMass(GetIndex(s, r, c), mass); + } + + inline void SetPosition(int s, int r, int c, Vector const& position) + { + ParticleSystem::SetPosition(GetIndex(s, r, c), position); + } + + inline void SetVelocity(int s, int r, int c, Vector const& velocity) + { + ParticleSystem::SetVelocity(GetIndex(s, r, c), velocity); + } + + Real const& GetMass(int s, int r, int c) const + { + return ParticleSystem::GetMass(GetIndex(s, r, c)); + } + + inline Vector const& GetPosition(int s, int r, int c) const + { + return ParticleSystem::GetPosition(GetIndex(s, r, c)); + } + + inline Vector const& GetVelocity(int s, int r, int c) const + { + return ParticleSystem::GetVelocity(GetIndex(s, r, c)); + } + + // Each interior mass at (s,r,c) has 6 adjacent springs. Face masses + // have only 5 neighbors, edge masses have only 4 neighbors, and corner + // masses have only 3 neighbors. Each mass provides access to 3 adjacent + // springs at (s,r,c+1), (s,r+1,c), and (s+1,r,c). The face, edge, and + // corner masses provide access to only an appropriate subset of these. + // The caller is responsible for ensuring the validity of the (s,r,c) + // inputs. + + // to (s+1,r,c) + inline void SetConstantS(int s, int r, int c, Real constant) + { + mConstantS[GetIndex(s, r, c)] = constant; + } + + // to (s+1,r,c) + inline void SetLengthS(int s, int r, int c, Real length) + { + mLengthS[GetIndex(s, r, c)] = length; + } + + // to (s,r+1,c) + inline void SetConstantR(int s, int r, int c, Real constant) + { + mConstantR[GetIndex(s, r, c)] = constant; + } + + // to (s,r+1,c) + inline void SetLengthR(int s, int r, int c, Real length) + { + mLengthR[GetIndex(s, r, c)] = length; + } + + // to (s,r,c+1) + inline void SetConstantC(int s, int r, int c, Real constant) + { + mConstantC[GetIndex(s, r, c)] = constant; + } + + // spring to (s,r,c+1) + inline void SetLengthC(int s, int r, int c, Real length) + { + mLengthC[GetIndex(s, r, c)] = length; + } + + inline Real const& GetConstantS(int s, int r, int c) const + { + return mConstantS[GetIndex(s, r, c)]; + } + + inline Real const& GetLengthS(int s, int r, int c) const + { + return mLengthS[GetIndex(s, r, c)]; + } + + inline Real const& GetConstantR(int s, int r, int c) const + { + return mConstantR[GetIndex(s, r, c)]; + } + + inline Real const& GetLengthR(int s, int r, int c) const + { + return mLengthR[GetIndex(s, r, c)]; + } + + inline Real const& GetConstantC(int s, int r, int c) const + { + return mConstantC[GetIndex(s, r, c)]; + } + + inline Real const& GetLengthC(int s, int r, int c) const + { + return mLengthC[GetIndex(s, r, c)]; + } + + // The default external force is zero. Derive a class from this one + // to provide nonzero external forces such as gravity, wind, + // friction and so on. This function is called by Acceleration(...) + // to compute the impulse F/m generated by the external force F. + virtual Vector ExternalAcceleration(int, Real, + std::vector> const&, + std::vector> const&) + { + return Vector::Zero(); + } + + protected: + // Callback for acceleration (ODE solver uses x" = F/m) applied to + // particle i. The positions and velocities are not necessarily + // mPosition and mVelocity, because the ODE solver evaluates the + // impulse function at intermediate positions. + virtual Vector Acceleration(int i, Real time, + std::vector> const& position, + std::vector> const& velocity) + { + // Compute spring forces on position X[i]. The positions are not + // necessarily mPosition, because the RK4 solver in ParticleSystem + // evaluates the acceleration function at intermediate positions. + // The face, edge, and corner points of the volume of masses must + // be handled separately, because each has fewer than eight + // springs attached to it. + + Vector acceleration = ExternalAcceleration(i, time, position, velocity); + Vector diff, force; + Real ratio; + + int s, r, c, prev, next; + GetCoordinates(i, s, r, c); + + if (s > 0) + { + prev = i - mNumRows * mNumCols; // index to previous s-neighbor + diff = position[prev] - position[i]; + ratio = GetLengthS(s - 1, r, c) / Length(diff); + force = GetConstantS(s - 1, r, c) * ((Real)1 - ratio) * diff; + acceleration += this->mInvMass[i] * force; + } + + if (s < mNumSlices - 1) + { + next = i + mNumRows * mNumCols; // index to next s-neighbor + diff = position[next] - position[i]; + ratio = GetLengthS(s, r, c) / Length(diff); + force = GetConstantS(s, r, c) * ((Real)1 - ratio) * diff; + acceleration += this->mInvMass[i] * force; + } + + if (r > 0) + { + prev = i - mNumCols; // index to previous r-neighbor + diff = position[prev] - position[i]; + ratio = GetLengthR(s, r - 1, c) / Length(diff); + force = GetConstantR(s, r - 1, c) * ((Real)1 - ratio) * diff; + acceleration += this->mInvMass[i] * force; + } + + if (r < mNumRows - 1) + { + next = i + mNumCols; // index to next r-neighbor + diff = position[next] - position[i]; + ratio = GetLengthR(s, r, c) / Length(diff); + force = GetConstantR(s, r, c) * ((Real)1 - ratio) * diff; + acceleration += this->mInvMass[i] * force; + } + + if (c > 0) + { + prev = i - 1; // index to previous c-neighbor + diff = position[prev] - position[i]; + ratio = GetLengthC(s, r, c - 1) / Length(diff); + force = GetConstantC(s, r, c - 1) * ((Real)1 - ratio) * diff; + acceleration += this->mInvMass[i] * force; + } + + if (c < mNumCols - 1) + { + next = i + 1; // index to next c-neighbor + diff = position[next] - position[i]; + ratio = GetLengthC(s, r, c) / Length(diff); + force = GetConstantC(s, r, c) * ((Real)1 - ratio) * diff; + acceleration += this->mInvMass[i] * force; + } + + return acceleration; + } + + inline int GetIndex(int s, int r, int c) const + { + return c + mNumCols * (r + mNumRows * s); + } + + void GetCoordinates(int i, int& s, int& r, int& c) const + { + c = i % mNumCols; + i = (i - c) / mNumCols; + r = i % mNumRows; + s = i / mNumRows; + } + + int mNumSlices, mNumRows, mNumCols; + std::vector mConstantS, mLengthS; + std::vector mConstantR, mLengthR; + std::vector mConstantC, mLengthC; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Math.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Math.h new file mode 100644 index 0000000..6bfbc2c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Math.h @@ -0,0 +1,654 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2020.01.08 + +#pragma once + +// This file extends the support to include convenient constants and +// functions. The shared constants for CPU, Intel SSE and GPU lead to +// correctly rounded approximations of the constants when using 'float' or +// 'double'. The file also includes a type trait, is_arbitrary_precision, +// to support selecting between floating-point arithmetic (float, double, +//long double) or arbitrary-precision arithmetic (BSNumber, BSRational) +// in an implementation using std::enable_if. There is also a type trait, +// has_division_operator, to support selecting between numeric types that +// have a division operator (BSRational) and those that do not have a +// division operator (BSNumber). + +#include +#include +#include +#include + +// Maximum number of iterations for bisection before a subinterval +// degenerates to a single point. TODO: Verify these. I used the formula: +// 3 + std::numeric_limits::digits - std::numeric_limits::min_exponent. +// IEEEBinary16: digits = 11, min_exponent = -13 +// float: digits = 27, min_exponent = -125 +// double: digits = 53, min_exponent = -1021 +// BSNumber and BSRational use std::numeric_limits::max(), +// but maybe these should be set to a large number or be user configurable. +// The MAX_BISECTIONS_GENERIC is an arbitrary choice for now and is used +// in template code where Real is the template parameter. +#define GTE_C_MAX_BISECTIONS_FLOAT16 27u +#define GTE_C_MAX_BISECTIONS_FLOAT32 155u +#define GTE_C_MAX_BISECTIONS_FLOAT64 1077u +#define GTE_C_MAX_BISECTIONS_BSNUMBER 0xFFFFFFFFu +#define GTE_C_MAX_BISECTIONS_BSRATIONAL 0xFFFFFFFFu +#define GTE_C_MAX_BISECTIONS_GENERIC 2048u + +// Constants involving pi. +#define GTE_C_PI 3.1415926535897931 +#define GTE_C_HALF_PI 1.5707963267948966 +#define GTE_C_QUARTER_PI 0.7853981633974483 +#define GTE_C_TWO_PI 6.2831853071795862 +#define GTE_C_INV_PI 0.3183098861837907 +#define GTE_C_INV_TWO_PI 0.1591549430918953 +#define GTE_C_INV_HALF_PI 0.6366197723675813 + +// Conversions between degrees and radians. +#define GTE_C_DEG_TO_RAD 0.0174532925199433 +#define GTE_C_RAD_TO_DEG 57.295779513082321 + +// Common constants. +#define GTE_C_SQRT_2 1.4142135623730951 +#define GTE_C_INV_SQRT_2 0.7071067811865475 +#define GTE_C_LN_2 0.6931471805599453 +#define GTE_C_INV_LN_2 1.4426950408889634 +#define GTE_C_LN_10 2.3025850929940459 +#define GTE_C_INV_LN_10 0.43429448190325176 + +// Constants for minimax polynomial approximations to sqrt(x). +// The algorithm minimizes the maximum absolute error on [1,2]. +#define GTE_C_SQRT_DEG1_C0 +1.0 +#define GTE_C_SQRT_DEG1_C1 +4.1421356237309505e-01 +#define GTE_C_SQRT_DEG1_MAX_ERROR 1.7766952966368793e-2 + +#define GTE_C_SQRT_DEG2_C0 +1.0 +#define GTE_C_SQRT_DEG2_C1 +4.8563183076125260e-01 +#define GTE_C_SQRT_DEG2_C2 -7.1418268388157458e-02 +#define GTE_C_SQRT_DEG2_MAX_ERROR 1.1795695163108744e-3 + +#define GTE_C_SQRT_DEG3_C0 +1.0 +#define GTE_C_SQRT_DEG3_C1 +4.9750045320242231e-01 +#define GTE_C_SQRT_DEG3_C2 -1.0787308044477850e-01 +#define GTE_C_SQRT_DEG3_C3 +2.4586189615451115e-02 +#define GTE_C_SQRT_DEG3_MAX_ERROR 1.1309620116468910e-4 + +#define GTE_C_SQRT_DEG4_C0 +1.0 +#define GTE_C_SQRT_DEG4_C1 +4.9955939832918816e-01 +#define GTE_C_SQRT_DEG4_C2 -1.2024066151943025e-01 +#define GTE_C_SQRT_DEG4_C3 +4.5461507257698486e-02 +#define GTE_C_SQRT_DEG4_C4 -1.0566681694362146e-02 +#define GTE_C_SQRT_DEG4_MAX_ERROR 1.2741170151556180e-5 + +#define GTE_C_SQRT_DEG5_C0 +1.0 +#define GTE_C_SQRT_DEG5_C1 +4.9992197660031912e-01 +#define GTE_C_SQRT_DEG5_C2 -1.2378506719245053e-01 +#define GTE_C_SQRT_DEG5_C3 +5.6122776972699739e-02 +#define GTE_C_SQRT_DEG5_C4 -2.3128836281145482e-02 +#define GTE_C_SQRT_DEG5_C5 +5.0827122737047148e-03 +#define GTE_C_SQRT_DEG5_MAX_ERROR 1.5725568940708201e-6 + +#define GTE_C_SQRT_DEG6_C0 +1.0 +#define GTE_C_SQRT_DEG6_C1 +4.9998616695784914e-01 +#define GTE_C_SQRT_DEG6_C2 -1.2470733323278438e-01 +#define GTE_C_SQRT_DEG6_C3 +6.0388587356982271e-02 +#define GTE_C_SQRT_DEG6_C4 -3.1692053551807930e-02 +#define GTE_C_SQRT_DEG6_C5 +1.2856590305148075e-02 +#define GTE_C_SQRT_DEG6_C6 -2.6183954624343642e-03 +#define GTE_C_SQRT_DEG6_MAX_ERROR 2.0584155535630089e-7 + +#define GTE_C_SQRT_DEG7_C0 +1.0 +#define GTE_C_SQRT_DEG7_C1 +4.9999754817809228e-01 +#define GTE_C_SQRT_DEG7_C2 -1.2493243476353655e-01 +#define GTE_C_SQRT_DEG7_C3 +6.1859954146370910e-02 +#define GTE_C_SQRT_DEG7_C4 -3.6091595023208356e-02 +#define GTE_C_SQRT_DEG7_C5 +1.9483946523450868e-02 +#define GTE_C_SQRT_DEG7_C6 -7.5166134568007692e-03 +#define GTE_C_SQRT_DEG7_C7 +1.4127567687864939e-03 +#define GTE_C_SQRT_DEG7_MAX_ERROR 2.8072302919734948e-8 + +#define GTE_C_SQRT_DEG8_C0 +1.0 +#define GTE_C_SQRT_DEG8_C1 +4.9999956583056759e-01 +#define GTE_C_SQRT_DEG8_C2 -1.2498490369914350e-01 +#define GTE_C_SQRT_DEG8_C3 +6.2318494667579216e-02 +#define GTE_C_SQRT_DEG8_C4 -3.7982961896432244e-02 +#define GTE_C_SQRT_DEG8_C5 +2.3642612312869460e-02 +#define GTE_C_SQRT_DEG8_C6 -1.2529377587270574e-02 +#define GTE_C_SQRT_DEG8_C7 +4.5382426960713929e-03 +#define GTE_C_SQRT_DEG8_C8 -7.8810995273670414e-04 +#define GTE_C_SQRT_DEG8_MAX_ERROR 3.9460605685825989e-9 + +// Constants for minimax polynomial approximations to 1/sqrt(x). +// The algorithm minimizes the maximum absolute error on [1,2]. +#define GTE_C_INVSQRT_DEG1_C0 +1.0 +#define GTE_C_INVSQRT_DEG1_C1 -2.9289321881345254e-01 +#define GTE_C_INVSQRT_DEG1_MAX_ERROR 3.7814314552701983e-2 + +#define GTE_C_INVSQRT_DEG2_C0 +1.0 +#define GTE_C_INVSQRT_DEG2_C1 -4.4539812104566801e-01 +#define GTE_C_INVSQRT_DEG2_C2 +1.5250490223221547e-01 +#define GTE_C_INVSQRT_DEG2_MAX_ERROR 4.1953446330581234e-3 + +#define GTE_C_INVSQRT_DEG3_C0 +1.0 +#define GTE_C_INVSQRT_DEG3_C1 -4.8703230993068791e-01 +#define GTE_C_INVSQRT_DEG3_C2 +2.8163710486669835e-01 +#define GTE_C_INVSQRT_DEG3_C3 -8.7498013749463421e-02 +#define GTE_C_INVSQRT_DEG3_MAX_ERROR 5.6307702007266786e-4 + +#define GTE_C_INVSQRT_DEG4_C0 +1.0 +#define GTE_C_INVSQRT_DEG4_C1 -4.9710061558048779e-01 +#define GTE_C_INVSQRT_DEG4_C2 +3.4266247597676802e-01 +#define GTE_C_INVSQRT_DEG4_C3 -1.9106356536293490e-01 +#define GTE_C_INVSQRT_DEG4_C4 +5.2608486153198797e-02 +#define GTE_C_INVSQRT_DEG4_MAX_ERROR 8.1513919987605266e-5 + +#define GTE_C_INVSQRT_DEG5_C0 +1.0 +#define GTE_C_INVSQRT_DEG5_C1 -4.9937760586004143e-01 +#define GTE_C_INVSQRT_DEG5_C2 +3.6508741295133973e-01 +#define GTE_C_INVSQRT_DEG5_C3 -2.5884890281853501e-01 +#define GTE_C_INVSQRT_DEG5_C4 +1.3275782221320753e-01 +#define GTE_C_INVSQRT_DEG5_C5 -3.2511945299404488e-02 +#define GTE_C_INVSQRT_DEG5_MAX_ERROR 1.2289367475583346e-5 + +#define GTE_C_INVSQRT_DEG6_C0 +1.0 +#define GTE_C_INVSQRT_DEG6_C1 -4.9987029229547453e-01 +#define GTE_C_INVSQRT_DEG6_C2 +3.7220923604495226e-01 +#define GTE_C_INVSQRT_DEG6_C3 -2.9193067713256937e-01 +#define GTE_C_INVSQRT_DEG6_C4 +1.9937605991094642e-01 +#define GTE_C_INVSQRT_DEG6_C5 -9.3135712130901993e-02 +#define GTE_C_INVSQRT_DEG6_C6 +2.0458166789566690e-02 +#define GTE_C_INVSQRT_DEG6_MAX_ERROR 1.9001451223750465e-6 + +#define GTE_C_INVSQRT_DEG7_C0 +1.0 +#define GTE_C_INVSQRT_DEG7_C1 -4.9997357250704977e-01 +#define GTE_C_INVSQRT_DEG7_C2 +3.7426216884998809e-01 +#define GTE_C_INVSQRT_DEG7_C3 -3.0539882498248971e-01 +#define GTE_C_INVSQRT_DEG7_C4 +2.3976005607005391e-01 +#define GTE_C_INVSQRT_DEG7_C5 -1.5410326351684489e-01 +#define GTE_C_INVSQRT_DEG7_C6 +6.5598809723041995e-02 +#define GTE_C_INVSQRT_DEG7_C7 -1.3038592450470787e-02 +#define GTE_C_INVSQRT_DEG7_MAX_ERROR 2.9887724993168940e-7 + +#define GTE_C_INVSQRT_DEG8_C0 +1.0 +#define GTE_C_INVSQRT_DEG8_C1 -4.9999471066120371e-01 +#define GTE_C_INVSQRT_DEG8_C2 +3.7481415745794067e-01 +#define GTE_C_INVSQRT_DEG8_C3 -3.1023804387422160e-01 +#define GTE_C_INVSQRT_DEG8_C4 +2.5977002682930106e-01 +#define GTE_C_INVSQRT_DEG8_C5 -1.9818790717727097e-01 +#define GTE_C_INVSQRT_DEG8_C6 +1.1882414252613671e-01 +#define GTE_C_INVSQRT_DEG8_C7 -4.6270038088550791e-02 +#define GTE_C_INVSQRT_DEG8_C8 +8.3891541755747312e-03 +#define GTE_C_INVSQRT_DEG8_MAX_ERROR 4.7596926146947771e-8 + +// Constants for minimax polynomial approximations to sin(x). +// The algorithm minimizes the maximum absolute error on [-pi/2,pi/2]. +#define GTE_C_SIN_DEG3_C0 +1.0 +#define GTE_C_SIN_DEG3_C1 -1.4727245910375519e-01 +#define GTE_C_SIN_DEG3_MAX_ERROR 1.3481903639145865e-2 + +#define GTE_C_SIN_DEG5_C0 +1.0 +#define GTE_C_SIN_DEG5_C1 -1.6600599923812209e-01 +#define GTE_C_SIN_DEG5_C2 +7.5924178409012000e-03 +#define GTE_C_SIN_DEG5_MAX_ERROR 1.4001209384639779e-4 + +#define GTE_C_SIN_DEG7_C0 +1.0 +#define GTE_C_SIN_DEG7_C1 -1.6665578084732124e-01 +#define GTE_C_SIN_DEG7_C2 +8.3109378830028557e-03 +#define GTE_C_SIN_DEG7_C3 -1.8447486103462252e-04 +#define GTE_C_SIN_DEG7_MAX_ERROR 1.0205878936686563e-6 + +#define GTE_C_SIN_DEG9_C0 +1.0 +#define GTE_C_SIN_DEG9_C1 -1.6666656235308897e-01 +#define GTE_C_SIN_DEG9_C2 +8.3329962509886002e-03 +#define GTE_C_SIN_DEG9_C3 -1.9805100675274190e-04 +#define GTE_C_SIN_DEG9_C4 +2.5967200279475300e-06 +#define GTE_C_SIN_DEG9_MAX_ERROR 5.2010746265374053e-9 + +#define GTE_C_SIN_DEG11_C0 +1.0 +#define GTE_C_SIN_DEG11_C1 -1.6666666601721269e-01 +#define GTE_C_SIN_DEG11_C2 +8.3333303183525942e-03 +#define GTE_C_SIN_DEG11_C3 -1.9840782426250314e-04 +#define GTE_C_SIN_DEG11_C4 +2.7521557770526783e-06 +#define GTE_C_SIN_DEG11_C5 -2.3828544692960918e-08 +#define GTE_C_SIN_DEG11_MAX_ERROR 1.9295870457014530e-11 + +// Constants for minimax polynomial approximations to cos(x). +// The algorithm minimizes the maximum absolute error on [-pi/2,pi/2]. +#define GTE_C_COS_DEG2_C0 +1.0 +#define GTE_C_COS_DEG2_C1 -4.0528473456935105e-01 +#define GTE_C_COS_DEG2_MAX_ERROR 5.4870946878404048e-2 + +#define GTE_C_COS_DEG4_C0 +1.0 +#define GTE_C_COS_DEG4_C1 -4.9607181958647262e-01 +#define GTE_C_COS_DEG4_C2 +3.6794619653489236e-02 +#define GTE_C_COS_DEG4_MAX_ERROR 9.1879932449712154e-4 + +#define GTE_C_COS_DEG6_C0 +1.0 +#define GTE_C_COS_DEG6_C1 -4.9992746217057404e-01 +#define GTE_C_COS_DEG6_C2 +4.1493920348353308e-02 +#define GTE_C_COS_DEG6_C3 -1.2712435011987822e-03 +#define GTE_C_COS_DEG6_MAX_ERROR 9.2028470133065365e-6 + +#define GTE_C_COS_DEG8_C0 +1.0 +#define GTE_C_COS_DEG8_C1 -4.9999925121358291e-01 +#define GTE_C_COS_DEG8_C2 +4.1663780117805693e-02 +#define GTE_C_COS_DEG8_C3 -1.3854239405310942e-03 +#define GTE_C_COS_DEG8_C4 +2.3154171575501259e-05 +#define GTE_C_COS_DEG8_MAX_ERROR 5.9804533020235695e-8 + +#define GTE_C_COS_DEG10_C0 +1.0 +#define GTE_C_COS_DEG10_C1 -4.9999999508695869e-01 +#define GTE_C_COS_DEG10_C2 +4.1666638865338612e-02 +#define GTE_C_COS_DEG10_C3 -1.3888377661039897e-03 +#define GTE_C_COS_DEG10_C4 +2.4760495088926859e-05 +#define GTE_C_COS_DEG10_C5 -2.6051615464872668e-07 +#define GTE_C_COS_DEG10_MAX_ERROR 2.7006769043325107e-10 + +// Constants for minimax polynomial approximations to tan(x). +// The algorithm minimizes the maximum absolute error on [-pi/4,pi/4]. +#define GTE_C_TAN_DEG3_C0 1.0 +#define GTE_C_TAN_DEG3_C1 4.4295926544736286e-01 +#define GTE_C_TAN_DEG3_MAX_ERROR 1.1661892256204731e-2 + +#define GTE_C_TAN_DEG5_C0 1.0 +#define GTE_C_TAN_DEG5_C1 3.1401320403542421e-01 +#define GTE_C_TAN_DEG5_C2 2.0903948109240345e-01 +#define GTE_C_TAN_DEG5_MAX_ERROR 5.8431854390143118e-4 + +#define GTE_C_TAN_DEG7_C0 1.0 +#define GTE_C_TAN_DEG7_C1 3.3607213284422555e-01 +#define GTE_C_TAN_DEG7_C2 1.1261037305184907e-01 +#define GTE_C_TAN_DEG7_C3 9.8352099470524479e-02 +#define GTE_C_TAN_DEG7_MAX_ERROR 3.5418688397723108e-5 + +#define GTE_C_TAN_DEG9_C0 1.0 +#define GTE_C_TAN_DEG9_C1 3.3299232843941784e-01 +#define GTE_C_TAN_DEG9_C2 1.3747843432474838e-01 +#define GTE_C_TAN_DEG9_C3 3.7696344813028304e-02 +#define GTE_C_TAN_DEG9_C4 4.6097377279281204e-02 +#define GTE_C_TAN_DEG9_MAX_ERROR 2.2988173242199927e-6 + +#define GTE_C_TAN_DEG11_C0 1.0 +#define GTE_C_TAN_DEG11_C1 3.3337224456224224e-01 +#define GTE_C_TAN_DEG11_C2 1.3264516053824593e-01 +#define GTE_C_TAN_DEG11_C3 5.8145237645931047e-02 +#define GTE_C_TAN_DEG11_C4 1.0732193237572574e-02 +#define GTE_C_TAN_DEG11_C5 2.1558456793513869e-02 +#define GTE_C_TAN_DEG11_MAX_ERROR 1.5426257940140409e-7 + +#define GTE_C_TAN_DEG13_C0 1.0 +#define GTE_C_TAN_DEG13_C1 3.3332916426394554e-01 +#define GTE_C_TAN_DEG13_C2 1.3343404625112498e-01 +#define GTE_C_TAN_DEG13_C3 5.3104565343119248e-02 +#define GTE_C_TAN_DEG13_C4 2.5355038312682154e-02 +#define GTE_C_TAN_DEG13_C5 1.8253255966556026e-03 +#define GTE_C_TAN_DEG13_C6 1.0069407176615641e-02 +#define GTE_C_TAN_DEG13_MAX_ERROR 1.0550264249037378e-8 + +// Constants for minimax polynomial approximations to acos(x), where the +// approximation is of the form acos(x) = sqrt(1 - x)*p(x) with p(x) a +// polynomial. The algorithm minimizes the maximum error +// |acos(x)/sqrt(1-x) - p(x)| on [0,1]. At the same time we get an +// approximation for asin(x) = pi/2 - acos(x). +#define GTE_C_ACOS_DEG1_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG1_C1 -1.5658276442180141e-01 +#define GTE_C_ACOS_DEG1_MAX_ERROR 1.1659002803738105e-2 + +#define GTE_C_ACOS_DEG2_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG2_C1 -2.0347053865798365e-01 +#define GTE_C_ACOS_DEG2_C2 +4.6887774236182234e-02 +#define GTE_C_ACOS_DEG2_MAX_ERROR 9.0311602490029258e-4 + +#define GTE_C_ACOS_DEG3_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG3_C1 -2.1253291899190285e-01 +#define GTE_C_ACOS_DEG3_C2 +7.4773789639484223e-02 +#define GTE_C_ACOS_DEG3_C3 -1.8823635069382449e-02 +#define GTE_C_ACOS_DEG3_MAX_ERROR 9.3066396954288172e-5 + +#define GTE_C_ACOS_DEG4_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG4_C1 -2.1422258835275865e-01 +#define GTE_C_ACOS_DEG4_C2 +8.4936675142844198e-02 +#define GTE_C_ACOS_DEG4_C3 -3.5991475120957794e-02 +#define GTE_C_ACOS_DEG4_C4 +8.6946239090712751e-03 +#define GTE_C_ACOS_DEG4_MAX_ERROR 1.0930595804481413e-5 + +#define GTE_C_ACOS_DEG5_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG5_C1 -2.1453292139805524e-01 +#define GTE_C_ACOS_DEG5_C2 +8.7973089282889383e-02 +#define GTE_C_ACOS_DEG5_C3 -4.5130266382166440e-02 +#define GTE_C_ACOS_DEG5_C4 +1.9467466687281387e-02 +#define GTE_C_ACOS_DEG5_C5 -4.3601326117634898e-03 +#define GTE_C_ACOS_DEG5_MAX_ERROR 1.3861070257241426-6 + +#define GTE_C_ACOS_DEG6_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG6_C1 -2.1458939285677325e-01 +#define GTE_C_ACOS_DEG6_C2 +8.8784960563641491e-02 +#define GTE_C_ACOS_DEG6_C3 -4.8887131453156485e-02 +#define GTE_C_ACOS_DEG6_C4 +2.7011519960012720e-02 +#define GTE_C_ACOS_DEG6_C5 -1.1210537323478320e-02 +#define GTE_C_ACOS_DEG6_C6 +2.3078166879102469e-03 +#define GTE_C_ACOS_DEG6_MAX_ERROR 1.8491291330427484e-7 + +#define GTE_C_ACOS_DEG7_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG7_C1 -2.1459960076929829e-01 +#define GTE_C_ACOS_DEG7_C2 +8.8986946573346160e-02 +#define GTE_C_ACOS_DEG7_C3 -5.0207843052845647e-02 +#define GTE_C_ACOS_DEG7_C4 +3.0961594977611639e-02 +#define GTE_C_ACOS_DEG7_C5 -1.7162031184398074e-02 +#define GTE_C_ACOS_DEG7_C6 +6.7072304676685235e-03 +#define GTE_C_ACOS_DEG7_C7 -1.2690614339589956e-03 +#define GTE_C_ACOS_DEG7_MAX_ERROR 2.5574620927948377e-8 + +#define GTE_C_ACOS_DEG8_C0 +1.5707963267948966 +#define GTE_C_ACOS_DEG8_C1 -2.1460143648688035e-01 +#define GTE_C_ACOS_DEG8_C2 +8.9034700107934128e-02 +#define GTE_C_ACOS_DEG8_C3 -5.0625279962389413e-02 +#define GTE_C_ACOS_DEG8_C4 +3.2683762943179318e-02 +#define GTE_C_ACOS_DEG8_C5 -2.0949278766238422e-02 +#define GTE_C_ACOS_DEG8_C6 +1.1272900916992512e-02 +#define GTE_C_ACOS_DEG8_C7 -4.1160981058965262e-03 +#define GTE_C_ACOS_DEG8_C8 +7.1796493341480527e-04 +#define GTE_C_ACOS_DEG8_MAX_ERROR 3.6340015129032732e-9 + +// Constants for minimax polynomial approximations to atan(x). +// The algorithm minimizes the maximum absolute error on [-1,1]. +#define GTE_C_ATAN_DEG3_C0 +1.0 +#define GTE_C_ATAN_DEG3_C1 -2.1460183660255172e-01 +#define GTE_C_ATAN_DEG3_MAX_ERROR 1.5970326392614240e-2 + +#define GTE_C_ATAN_DEG5_C0 +1.0 +#define GTE_C_ATAN_DEG5_C1 -3.0189478312144946e-01 +#define GTE_C_ATAN_DEG5_C2 +8.7292946518897740e-02 +#define GTE_C_ATAN_DEG5_MAX_ERROR 1.3509832247372636e-3 + +#define GTE_C_ATAN_DEG7_C0 +1.0 +#define GTE_C_ATAN_DEG7_C1 -3.2570157599356531e-01 +#define GTE_C_ATAN_DEG7_C2 +1.5342994884206673e-01 +#define GTE_C_ATAN_DEG7_C3 -4.2330209451053591e-02 +#define GTE_C_ATAN_DEG7_MAX_ERROR 1.5051227215514412e-4 + +#define GTE_C_ATAN_DEG9_C0 +1.0 +#define GTE_C_ATAN_DEG9_C1 -3.3157878236439586e-01 +#define GTE_C_ATAN_DEG9_C2 +1.8383034738018011e-01 +#define GTE_C_ATAN_DEG9_C3 -8.9253037587244677e-02 +#define GTE_C_ATAN_DEG9_C4 +2.2399635968909593e-02 +#define GTE_C_ATAN_DEG9_MAX_ERROR 1.8921598624582064e-5 + +#define GTE_C_ATAN_DEG11_C0 +1.0 +#define GTE_C_ATAN_DEG11_C1 -3.3294527685374087e-01 +#define GTE_C_ATAN_DEG11_C2 +1.9498657165383548e-01 +#define GTE_C_ATAN_DEG11_C3 -1.1921576270475498e-01 +#define GTE_C_ATAN_DEG11_C4 +5.5063351366968050e-02 +#define GTE_C_ATAN_DEG11_C5 -1.2490720064867844e-02 +#define GTE_C_ATAN_DEG11_MAX_ERROR 2.5477724974187765e-6 + +#define GTE_C_ATAN_DEG13_C0 +1.0 +#define GTE_C_ATAN_DEG13_C1 -3.3324998579202170e-01 +#define GTE_C_ATAN_DEG13_C2 +1.9856563505717162e-01 +#define GTE_C_ATAN_DEG13_C3 -1.3374657325451267e-01 +#define GTE_C_ATAN_DEG13_C4 +8.1675882859940430e-02 +#define GTE_C_ATAN_DEG13_C5 -3.5059680836411644e-02 +#define GTE_C_ATAN_DEG13_C6 +7.2128853633444123e-03 +#define GTE_C_ATAN_DEG13_MAX_ERROR 3.5859104691865484e-7 + +// Constants for minimax polynomial approximations to exp2(x) = 2^x. +// The algorithm minimizes the maximum absolute error on [0,1]. +#define GTE_C_EXP2_DEG1_C0 1.0 +#define GTE_C_EXP2_DEG1_C1 1.0 +#define GTE_C_EXP2_DEG1_MAX_ERROR 8.6071332055934313e-2 + +#define GTE_C_EXP2_DEG2_C0 1.0 +#define GTE_C_EXP2_DEG2_C1 6.5571332605741528e-01 +#define GTE_C_EXP2_DEG2_C2 3.4428667394258472e-01 +#define GTE_C_EXP2_DEG2_MAX_ERROR 3.8132476831060358e-3 + +#define GTE_C_EXP2_DEG3_C0 1.0 +#define GTE_C_EXP2_DEG3_C1 6.9589012084456225e-01 +#define GTE_C_EXP2_DEG3_C2 2.2486494900110188e-01 +#define GTE_C_EXP2_DEG3_C3 7.9244930154334980e-02 +#define GTE_C_EXP2_DEG3_MAX_ERROR 1.4694877755186408e-4 + +#define GTE_C_EXP2_DEG4_C0 1.0 +#define GTE_C_EXP2_DEG4_C1 6.9300392358459195e-01 +#define GTE_C_EXP2_DEG4_C2 2.4154981722455560e-01 +#define GTE_C_EXP2_DEG4_C3 5.1744260331489045e-02 +#define GTE_C_EXP2_DEG4_C4 1.3701998859367848e-02 +#define GTE_C_EXP2_DEG4_MAX_ERROR 4.7617792624521371e-6 + +#define GTE_C_EXP2_DEG5_C0 1.0 +#define GTE_C_EXP2_DEG5_C1 6.9315298010274962e-01 +#define GTE_C_EXP2_DEG5_C2 2.4014712313022102e-01 +#define GTE_C_EXP2_DEG5_C3 5.5855296413199085e-02 +#define GTE_C_EXP2_DEG5_C4 8.9477503096873079e-03 +#define GTE_C_EXP2_DEG5_C5 1.8968500441332026e-03 +#define GTE_C_EXP2_DEG5_MAX_ERROR 1.3162098333463490e-7 + +#define GTE_C_EXP2_DEG6_C0 1.0 +#define GTE_C_EXP2_DEG6_C1 6.9314698914837525e-01 +#define GTE_C_EXP2_DEG6_C2 2.4023013440952923e-01 +#define GTE_C_EXP2_DEG6_C3 5.5481276898206033e-02 +#define GTE_C_EXP2_DEG6_C4 9.6838443037086108e-03 +#define GTE_C_EXP2_DEG6_C5 1.2388324048515642e-03 +#define GTE_C_EXP2_DEG6_C6 2.1892283501756538e-04 +#define GTE_C_EXP2_DEG6_MAX_ERROR 3.1589168225654163e-9 + +#define GTE_C_EXP2_DEG7_C0 1.0 +#define GTE_C_EXP2_DEG7_C1 6.9314718588750690e-01 +#define GTE_C_EXP2_DEG7_C2 2.4022637363165700e-01 +#define GTE_C_EXP2_DEG7_C3 5.5505235570535660e-02 +#define GTE_C_EXP2_DEG7_C4 9.6136265387940512e-03 +#define GTE_C_EXP2_DEG7_C5 1.3429234504656051e-03 +#define GTE_C_EXP2_DEG7_C6 1.4299202757683815e-04 +#define GTE_C_EXP2_DEG7_C7 2.1662892777385423e-05 +#define GTE_C_EXP2_DEG7_MAX_ERROR 6.6864513925679603e-11 + +// Constants for minimax polynomial approximations to log2(x). +// The algorithm minimizes the maximum absolute error on [1,2]. +// The polynomials all have constant term zero. +#define GTE_C_LOG2_DEG1_C1 +1.0 +#define GTE_C_LOG2_DEG1_MAX_ERROR 8.6071332055934202e-2 + +#define GTE_C_LOG2_DEG2_C1 +1.3465553856377803 +#define GTE_C_LOG2_DEG2_C2 -3.4655538563778032e-01 +#define GTE_C_LOG2_DEG2_MAX_ERROR 7.6362868906658110e-3 + +#define GTE_C_LOG2_DEG3_C1 +1.4228653756681227 +#define GTE_C_LOG2_DEG3_C2 -5.8208556916449616e-01 +#define GTE_C_LOG2_DEG3_C3 +1.5922019349637218e-01 +#define GTE_C_LOG2_DEG3_MAX_ERROR 8.7902902652883808e-4 + +#define GTE_C_LOG2_DEG4_C1 +1.4387257478171547 +#define GTE_C_LOG2_DEG4_C2 -6.7778401359918661e-01 +#define GTE_C_LOG2_DEG4_C3 +3.2118898377713379e-01 +#define GTE_C_LOG2_DEG4_C4 -8.2130717995088531e-02 +#define GTE_C_LOG2_DEG4_MAX_ERROR 1.1318551355360418e-4 + +#define GTE_C_LOG2_DEG5_C1 +1.4419170408633741 +#define GTE_C_LOG2_DEG5_C2 -7.0909645927612530e-01 +#define GTE_C_LOG2_DEG5_C3 +4.1560609399164150e-01 +#define GTE_C_LOG2_DEG5_C4 -1.9357573729558908e-01 +#define GTE_C_LOG2_DEG5_C5 +4.5149061716699634e-02 +#define GTE_C_LOG2_DEG5_MAX_ERROR 1.5521274478735858e-5 + +#define GTE_C_LOG2_DEG6_C1 +1.4425449435950917 +#define GTE_C_LOG2_DEG6_C2 -7.1814525675038965e-01 +#define GTE_C_LOG2_DEG6_C3 +4.5754919692564044e-01 +#define GTE_C_LOG2_DEG6_C4 -2.7790534462849337e-01 +#define GTE_C_LOG2_DEG6_C5 +1.2179791068763279e-01 +#define GTE_C_LOG2_DEG6_C6 -2.5841449829670182e-02 +#define GTE_C_LOG2_DEG6_MAX_ERROR 2.2162051216689793e-6 + +#define GTE_C_LOG2_DEG7_C1 +1.4426664401536078 +#define GTE_C_LOG2_DEG7_C2 -7.2055423726162360e-01 +#define GTE_C_LOG2_DEG7_C3 +4.7332419162501083e-01 +#define GTE_C_LOG2_DEG7_C4 -3.2514018752954144e-01 +#define GTE_C_LOG2_DEG7_C5 +1.9302965529095673e-01 +#define GTE_C_LOG2_DEG7_C6 -7.8534970641157997e-02 +#define GTE_C_LOG2_DEG7_C7 +1.5209108363023915e-02 +#define GTE_C_LOG2_DEG7_MAX_ERROR 3.2546531700261561e-7 + +#define GTE_C_LOG2_DEG8_C1 +1.4426896453621882 +#define GTE_C_LOG2_DEG8_C2 -7.2115893912535967e-01 +#define GTE_C_LOG2_DEG8_C3 +4.7861716616785088e-01 +#define GTE_C_LOG2_DEG8_C4 -3.4699935395019565e-01 +#define GTE_C_LOG2_DEG8_C5 +2.4114048765477492e-01 +#define GTE_C_LOG2_DEG8_C6 -1.3657398692885181e-01 +#define GTE_C_LOG2_DEG8_C7 +5.1421382871922106e-02 +#define GTE_C_LOG2_DEG8_C8 -9.1364020499895560e-03 +#define GTE_C_LOG2_DEG8_MAX_ERROR 4.8796219218050219e-8 + +// These functions are convenient for some applications. The classes +// BSNumber, BSRational and IEEEBinary16 have implementations that +// (for now) use typecasting to call the 'float' or 'double' versions. +namespace WwiseGTE +{ + inline float atandivpi(float x) + { + return std::atan(x) * (float)GTE_C_INV_PI; + } + + inline float atan2divpi(float y, float x) + { + return std::atan2(y, x) * (float)GTE_C_INV_PI; + } + + inline float clamp(float x, float xmin, float xmax) + { + return (x <= xmin ? xmin : (x >= xmax ? xmax : x)); + } + + inline float cospi(float x) + { + return std::cos(x * (float)GTE_C_PI); + } + + inline float exp10(float x) + { + return std::exp(x * (float)GTE_C_LN_10); + } + + inline float invsqrt(float x) + { + return 1.0f / std::sqrt(x); + } + + inline int isign(float x) + { + return (x > 0.0f ? 1 : (x < 0.0f ? -1 : 0)); + } + + inline float saturate(float x) + { + return (x <= 0.0f ? 0.0f : (x >= 1.0f ? 1.0f : x)); + } + + inline float sign(float x) + { + return (x > 0.0f ? 1.0f : (x < 0.0f ? -1.0f : 0.0f)); + } + + inline float sinpi(float x) + { + return std::sin(x * (float)GTE_C_PI); + } + + inline float sqr(float x) + { + return x * x; + } + + + inline double atandivpi(double x) + { + return std::atan(x) * GTE_C_INV_PI; + } + + inline double atan2divpi(double y, double x) + { + return std::atan2(y, x) * GTE_C_INV_PI; + } + + inline double clamp(double x, double xmin, double xmax) + { + return (x <= xmin ? xmin : (x >= xmax ? xmax : x)); + } + + inline double cospi(double x) + { + return std::cos(x * GTE_C_PI); + } + + inline double exp10(double x) + { + return std::exp(x * GTE_C_LN_10); + } + + inline double invsqrt(double x) + { + return 1.0 / std::sqrt(x); + } + + inline double sign(double x) + { + return (x > 0.0 ? 1.0 : (x < 0.0 ? -1.0 : 0.0f)); + } + + inline int isign(double x) + { + return (x > 0.0 ? 1 : (x < 0.0 ? -1 : 0)); + } + + inline double saturate(double x) + { + return (x <= 0.0 ? 0.0 : (x >= 1.0 ? 1.0 : x)); + } + + inline double sinpi(double x) + { + return std::sin(x * GTE_C_PI); + } + + inline double sqr(double x) + { + return x * x; + } +} + +// Type traits to support std::enable_if conditional compilation for +// numerical computations. +namespace WwiseGTE +{ + // The trait is_arbitrary_precision for type T of float, double or + // long double generates is_arbitrary_precision::value of false. The + // implementations for arbitrary-precision arithmetic are found in + // GteArbitraryPrecision.h. + template + struct is_arbitrary_precision_internal : std::false_type {}; + + template + struct is_arbitrary_precision : is_arbitrary_precision_internal::type {}; + + // The trait has_division_operator for type T of float, double or + // long double generates has_division_operator::value of true. The + // implementations for arbitrary-precision arithmetic are found in + // ArbitraryPrecision.h. + template + struct has_division_operator_internal : std::false_type {}; + + template + struct has_division_operator : has_division_operator_internal::type {}; + + template <> + struct has_division_operator_internal : std::true_type {}; + + template <> + struct has_division_operator_internal : std::true_type {}; + + template <> + struct has_division_operator_internal : std::true_type {}; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Matrix.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Matrix.h new file mode 100644 index 0000000..27be82f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Matrix.h @@ -0,0 +1,724 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class Matrix + { + public: + // The table is initialized to zero. + Matrix() + { + MakeZero(); + } + + // The table is fully initialized by the inputs. The 'values' must be + // specified in row-major order, regardless of the active storage + // scheme (GTE_USE_ROW_MAJOR or GTE_USE_COL_MAJOR). + Matrix(std::array const& values) + { + for (int r = 0, i = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c, ++i) + { + mTable(r, c) = values[i]; + } + } + } + + // At most NumRows*NumCols are copied from the initializer list, + // setting any remaining elements to zero. The 'values' must be + // specified in row-major order, regardless of the active storage + // scheme (GTE_USE_ROW_MAJOR or GTE_USE_COL_MAJOR). Create the zero + // matrix using the syntax + // Matrix zero{(Real)0}; + // WARNING: The C++ 11 specification states that + // Matrix zero{}; + // will lead to a call of the default constructor, not the initializer + // constructor! + Matrix(std::initializer_list values) + { + int const numValues = static_cast(values.size()); + auto iter = values.begin(); + int r, c, i; + for (r = 0, i = 0; r < NumRows; ++r) + { + for (c = 0; c < NumCols; ++c, ++i) + { + if (i < numValues) + { + mTable(r, c) = *iter++; + } + else + { + break; + } + } + + if (c < NumCols) + { + // Fill in the remaining columns of the current row with zeros. + for (/**/; c < NumCols; ++c) + { + mTable(r, c) = (Real)0; + } + ++r; + break; + } + } + + if (r < NumRows) + { + // Fill in the remain rows with zeros. + for (/**/; r < NumRows; ++r) + { + for (c = 0; c < NumCols; ++c) + { + mTable(r, c) = (Real)0; + } + } + } + } + + // For 0 <= r < NumRows and 0 <= c < NumCols, element (r,c) is 1 and + // all others are 0. If either of r or c is invalid, the zero matrix + // is created. This is a convenience for creating the standard + // Euclidean basis matrices; see also MakeUnit(int,int) and + // Unit(int,int). + Matrix(int r, int c) + { + MakeUnit(r, c); + } + + // The copy constructor, destructor, and assignment operator are + // generated by the compiler. + + // Member access for which the storage representation is transparent. + // The matrix entry in row r and column c is A(r,c). The first + // operator() returns a const reference rather than a Real value. + // This supports writing via standard file operations that require a + // const pointer to data. + inline Real const& operator()(int r, int c) const + { + return mTable(r, c); + } + + inline Real& operator()(int r, int c) + { + return mTable(r, c); + } + + // Member access by rows or by columns. + void SetRow(int r, Vector const& vec) + { + for (int c = 0; c < NumCols; ++c) + { + mTable(r, c) = vec[c]; + } + } + + void SetCol(int c, Vector const& vec) + { + for (int r = 0; r < NumRows; ++r) + { + mTable(r, c) = vec[r]; + } + } + + Vector GetRow(int r) const + { + Vector vec; + for (int c = 0; c < NumCols; ++c) + { + vec[c] = mTable(r, c); + } + return vec; + } + + Vector GetCol(int c) const + { + Vector vec; + for (int r = 0; r < NumRows; ++r) + { + vec[r] = mTable(r, c); + } + return vec; + } + + // Member access by 1-dimensional index. NOTE: These accessors are + // useful for the manipulation of matrix entries when it does not + // matter whether storage is row-major or column-major. Do not use + // constructs such as M[c+NumCols*r] or M[r+NumRows*c] that expose the + // storage convention. + inline Real const& operator[](int i) const + { + return mTable[i]; + } + + inline Real& operator[](int i) + { + return mTable[i]; + } + + // Comparisons for sorted containers and geometric ordering. + inline bool operator==(Matrix const& mat) const + { + return mTable.mStorage == mat.mTable.mStorage; + } + + inline bool operator!=(Matrix const& mat) const + { + return mTable.mStorage != mat.mTable.mStorage; + } + + inline bool operator< (Matrix const& mat) const + { + return mTable.mStorage < mat.mTable.mStorage; + } + + inline bool operator<=(Matrix const& mat) const + { + return mTable.mStorage <= mat.mTable.mStorage; + } + + inline bool operator> (Matrix const& mat) const + { + return mTable.mStorage > mat.mTable.mStorage; + } + + inline bool operator>=(Matrix const& mat) const + { + return mTable.mStorage >= mat.mTable.mStorage; + } + + // Special matrices. + + // All components are 0. + void MakeZero() + { + Real const zero(0); + for (int i = 0; i < NumRows * NumCols; ++i) + { + mTable[i] = zero; + } + } + + // Component (r,c) is 1, all others zero. + void MakeUnit(int r, int c) + { + MakeZero(); + if (0 <= r && r < NumRows && 0 <= c && c < NumCols) + { + mTable(r, c) = (Real)1; + } + } + + // Diagonal entries 1, others 0, even when nonsquare + void MakeIdentity() + { + MakeZero(); + int const numDiagonal = (NumRows <= NumCols ? NumRows : NumCols); + for (int i = 0; i < numDiagonal; ++i) + { + mTable(i, i) = (Real)1; + } + } + + static Matrix Zero() + { + Matrix M; + M.MakeZero(); + return M; + } + + static Matrix Unit(int r, int c) + { + Matrix M; + M.MakeUnit(r, c); + return M; + } + + static Matrix Identity() + { + Matrix M; + M.MakeIdentity(); + return M; + } + + protected: + class Table + { + public: + // Storage-order-independent element access as 2D array. + inline Real const& operator()(int r, int c) const + { +#if defined(GTE_USE_ROW_MAJOR) + return mStorage[r][c]; +#else + return mStorage[c][r]; +#endif + } + + inline Real& operator()(int r, int c) + { +#if defined(GTE_USE_ROW_MAJOR) + return mStorage[r][c]; +#else + return mStorage[c][r]; +#endif + } + + // Element access as 1D array. Use this internally only when + // the 2D storage order is not relevant. + inline Real const& operator[](int i) const + { + Real const* elements = &mStorage[0][0]; + return elements[i]; + } + + inline Real& operator[](int i) + { + Real* elements = &mStorage[0][0]; + return elements[i]; + } + +#if defined(GTE_USE_ROW_MAJOR) + std::array, NumRows> mStorage; +#else + std::array, NumCols> mStorage; +#endif + }; + + Table mTable; + }; + + // Unary operations. + template + Matrix operator+(Matrix const& M) + { + return M; + } + + template + Matrix operator-(Matrix const& M) + { + Matrix result; + for (int i = 0; i < NumRows * NumCols; ++i) + { + result[i] = -M[i]; + } + return result; + } + + // Linear-algebraic operations. + template + Matrix operator+( + Matrix const& M0, + Matrix const& M1) + { + Matrix result = M0; + return result += M1; + } + + template + Matrix operator-( + Matrix const& M0, + Matrix const& M1) + { + Matrix result = M0; + return result -= M1; + } + + template + Matrix operator*( + Matrix const& M, Real scalar) + { + Matrix result = M; + return result *= scalar; + } + + template + Matrix operator*( + Real scalar, Matrix const& M) + { + Matrix result = M; + return result *= scalar; + } + + template + Matrix operator/( + Matrix const& M, Real scalar) + { + Matrix result = M; + return result /= scalar; + } + + template + Matrix& operator+=( + Matrix& M0, + Matrix const& M1) + { + for (int i = 0; i < NumRows * NumCols; ++i) + { + M0[i] += M1[i]; + } + return M0; + } + + template + Matrix& operator-=( + Matrix& M0, + Matrix const& M1) + { + for (int i = 0; i < NumRows * NumCols; ++i) + { + M0[i] -= M1[i]; + } + return M0; + } + + template + Matrix& operator*=( + Matrix& M, Real scalar) + { + for (int i = 0; i < NumRows * NumCols; ++i) + { + M[i] *= scalar; + } + return M; + } + + template + Matrix& operator/=( + Matrix& M, Real scalar) + { + if (scalar != (Real)0) + { + Real invScalar = ((Real)1) / scalar; + for (int i = 0; i < NumRows * NumCols; ++i) + { + M[i] *= invScalar; + } + } + else + { + for (int i = 0; i < NumRows * NumCols; ++i) + { + M[i] = (Real)0; + } + } + return M; + } + + // Geometric operations. + template + Real L1Norm(Matrix const& M) + { + Real sum = std::fabs(M[0]); + for (int i = 1; i < NumRows * NumCols; ++i) + { + sum += std::fabs(M[i]); + } + return sum; + } + + template + Real L2Norm(Matrix const& M) + { + Real sum = M[0] * M[0]; + for (int i = 1; i < NumRows * NumCols; ++i) + { + sum += M[i] * M[i]; + } + return std::sqrt(sum); + } + + template + Real LInfinityNorm(Matrix const& M) + { + Real maxAbsElement = M[0]; + for (int i = 1; i < NumRows * NumCols; ++i) + { + Real absElement = std::fabs(M[i]); + if (absElement > maxAbsElement) + { + maxAbsElement = absElement; + } + } + return maxAbsElement; + } + + template + Matrix Inverse(Matrix const& M, bool* reportInvertibility = nullptr) + { + Matrix invM; + Real determinant; + bool invertible = GaussianElimination()(N, &M[0], &invM[0], + determinant, nullptr, nullptr, nullptr, 0, nullptr); + if (reportInvertibility) + { + *reportInvertibility = invertible; + } + return invM; + } + + template + Real Determinant(Matrix const& M) + { + Real determinant; + GaussianElimination()(N, &M[0], nullptr, determinant, nullptr, + nullptr, nullptr, 0, nullptr); + return determinant; + } + + // M^T + template + Matrix Transpose(Matrix const& M) + { + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(c, r) = M(r, c); + } + } + return result; + } + + // M*V + template + Vector operator*(Matrix const& M, + Vector const& V) + { + Vector result; + for (int r = 0; r < NumRows; ++r) + { + result[r] = (Real)0; + for (int c = 0; c < NumCols; ++c) + { + result[r] += M(r, c) * V[c]; + } + } + return result; + } + + // V^T*M + template + Vector operator*(Vector const& V, + Matrix const& M) + { + Vector result; + for (int c = 0; c < NumCols; ++c) + { + result[c] = (Real)0; + for (int r = 0; r < NumRows; ++r) + { + result[c] += V[r] * M(r, c); + } + } + return result; + } + + // A*B + template + Matrix operator*( + Matrix const& A, + Matrix const& B) + { + return MultiplyAB(A, B); + } + + template + Matrix MultiplyAB( + Matrix const& A, + Matrix const& B) + { + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < NumCommon; ++i) + { + result(r, c) += A(r, i) * B(i, c); + } + } + } + return result; + } + + // A*B^T + template + Matrix MultiplyABT( + Matrix const& A, + Matrix const& B) + { + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < NumCommon; ++i) + { + result(r, c) += A(r, i) * B(c, i); + } + } + } + return result; + } + + // A^T*B + template + Matrix MultiplyATB( + Matrix const& A, + Matrix const& B) + { + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < NumCommon; ++i) + { + result(r, c) += A(i, r) * B(i, c); + } + } + } + return result; + } + + // A^T*B^T + template + Matrix MultiplyATBT( + Matrix const& A, + Matrix const& B) + { + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(r, c) = (Real)0; + for (int i = 0; i < NumCommon; ++i) + { + result(r, c) += A(i, r) * B(c, i); + } + } + } + return result; + } + + // M*D, D is diagonal NumCols-by-NumCols + template + Matrix MultiplyMD( + Matrix const& M, + Vector const& D) + { + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(r, c) = M(r, c) * D[c]; + } + } + return result; + } + + // D*M, D is diagonal NumRows-by-NumRows + template + Matrix MultiplyDM( + Vector const& D, + Matrix const& M) + { + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(r, c) = D[r] * M(r, c); + } + } + return result; + } + + // U*V^T, U is NumRows-by-1, V is Num-Cols-by-1, result is NumRows-by-NumCols. + template + Matrix OuterProduct( + Vector const& U, Vector const& V) + { + Matrix result; + for (int r = 0; r < NumRows; ++r) + { + for (int c = 0; c < NumCols; ++c) + { + result(r, c) = U[r] * V[c]; + } + } + return result; + } + + // Initialization to a diagonal matrix whose diagonal entries are the + // components of D. + template + void MakeDiagonal(Vector const& D, Matrix& M) + { + for (int i = 0; i < N * N; ++i) + { + M[i] = (Real)0; + } + + for (int i = 0; i < N; ++i) + { + M(i, i) = D[i]; + } + } + + // Create an (N+1)-by-(N+1) matrix H by setting the upper N-by-N block to + // the input N-by-N matrix and all other entries to 0 except for the last + // row and last column entry which is set to 1. + template + Matrix HLift(Matrix const& M) + { + Matrix result; + result.MakeIdentity(); + for (int r = 0; r < N; ++r) + { + for (int c = 0; c < N; ++c) + { + result(r, c) = M(r, c); + } + } + return result; + } + + // Extract the upper (N-1)-by-(N-1) block of the input N-by-N matrix. + template + Matrix HProject(Matrix const& M) + { + static_assert(N >= 2, "Invalid matrix dimension."); + Matrix result; + for (int r = 0; r < N - 1; ++r) + { + for (int c = 0; c < N - 1; ++c) + { + result(r, c) = M(r, c); + } + } + return result; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Matrix2x2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Matrix2x2.h new file mode 100644 index 0000000..3a2399f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Matrix2x2.h @@ -0,0 +1,159 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + // Template alias for convenience. + template + using Matrix2x2 = Matrix<2, 2, Real>; + + // Create a rotation matrix from an angle (in radians). The matrix is + // [GTE_USE_MAT_VEC] + // R(t) = {{c,-s},{s,c}} + // [GTE_USE_VEC_MAT] + // R(t) = {{c,s},{-s,c}} + // where c = cos(t), s = sin(t), and the inner-brace pairs are rows of the + // matrix. + template + void MakeRotation(Real angle, Matrix2x2& rotation) + { + Real cs = std::cos(angle); + Real sn = std::sin(angle); +#if defined(GTE_USE_MAT_VEC) + rotation(0, 0) = cs; + rotation(0, 1) = -sn; + rotation(1, 0) = sn; + rotation(1, 1) = cs; +#else + rotation(0, 0) = cs; + rotation(0, 1) = sn; + rotation(1, 0) = -sn; + rotation(1, 1) = cs; +#endif + } + + // Get the angle (radians) from a rotation matrix. The caller is + // responsible for ensuring the matrix is a rotation. + template + Real GetRotationAngle(Matrix2x2 const& rotation) + { +#if defined(GTE_USE_MAT_VEC) + return std::atan2(rotation(1, 0), rotation(0, 0)); +#else + return std::atan2(rotation(0, 1), rotation(0, 0)); +#endif + } + + // Geometric operations. + template + Matrix2x2 Inverse(Matrix2x2 const& M, bool* reportInvertibility = nullptr) + { + Matrix2x2 inverse; + bool invertible; + Real det = M(0, 0) * M(1, 1) - M(0, 1) * M(1, 0); + if (det != (Real)0) + { + Real invDet = ((Real)1) / det; + inverse = Matrix2x2 + { + M(1, 1) * invDet, -M(0, 1) * invDet, + -M(1, 0) * invDet, M(0, 0) * invDet + }; + invertible = true; + } + else + { + inverse.MakeZero(); + invertible = false; + } + + if (reportInvertibility) + { + *reportInvertibility = invertible; + } + return inverse; + } + + template + Matrix2x2 Adjoint(Matrix2x2 const& M) + { + return Matrix2x2 + { + M(1, 1), -M(0, 1), + -M(1, 0), M(0, 0) + }; + } + + template + Real Determinant(Matrix2x2 const& M) + { + Real det = M(0, 0) * M(1, 1) - M(0, 1) * M(1, 0); + return det; + } + + template + Real Trace(Matrix2x2 const& M) + { + Real trace = M(0, 0) + M(1, 1); + return trace; + } + + // Multiply M and V according to the user-selected convention. If it is + // GTE_USE_MAT_VEC, the function returns M*V. If it is GTE_USE_VEC_MAT, + // the function returns V*M. This function is provided to hide the + // preprocessor symbols in the GTEngine sample applications. + template + Vector2 DoTransform(Matrix2x2 const& M, Vector2 const& V) + { +#if defined(GTE_USE_MAT_VEC) + return M * V; +#else + return V * M; +#endif + } + + template + Matrix2x2 DoTransform(Matrix2x2 const& A, Matrix2x2 const& B) + { +#if defined(GTE_USE_MAT_VEC) + return A * B; +#else + return B * A; +#endif + } + + // For GTE_USE_MAT_VEC, the columns of an invertible matrix form a basis + // for the range of the matrix. For GTE_USE_VEC_MAT, the rows of an + // invertible matrix form a basis for the range of the matrix. These + // functions allow you to access the basis vectors. The caller is + // responsible for ensuring that the matrix is invertible (although the + // inverse is not calculated by these functions). + template + void SetBasis(Matrix2x2& M, int i, Vector2 const& V) + { +#if defined(GTE_USE_MAT_VEC) + return M.SetCol(i, V); +#else + return M.SetRow(i, V); +#endif + } + + template + Vector2 GetBasis(Matrix2x2 const& M, int i) + { +#if defined(GTE_USE_MAT_VEC) + return M.GetCol(i); +#else + return M.GetRow(i); +#endif + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Matrix3x3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Matrix3x3.h new file mode 100644 index 0000000..7df235e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Matrix3x3.h @@ -0,0 +1,142 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + // Template alias for convenience. + template + using Matrix3x3 = Matrix<3, 3, Real>; + + // Geometric operations. + template + Matrix3x3 Inverse(Matrix3x3 const& M, bool* reportInvertibility = nullptr) + { + Matrix3x3 inverse; + bool invertible; + Real c00 = M(1, 1) * M(2, 2) - M(1, 2) * M(2, 1); + Real c10 = M(1, 2) * M(2, 0) - M(1, 0) * M(2, 2); + Real c20 = M(1, 0) * M(2, 1) - M(1, 1) * M(2, 0); + Real det = M(0, 0) * c00 + M(0, 1) * c10 + M(0, 2) * c20; + if (det != (Real)0) + { + Real invDet = (Real)1 / det; + inverse = Matrix3x3 + { + c00 * invDet, + (M(0, 2) * M(2, 1) - M(0, 1) * M(2, 2)) * invDet, + (M(0, 1) * M(1, 2) - M(0, 2) * M(1, 1)) * invDet, + c10 * invDet, + (M(0, 0) * M(2, 2) - M(0, 2) * M(2, 0)) * invDet, + (M(0, 2) * M(1, 0) - M(0, 0) * M(1, 2)) * invDet, + c20 * invDet, + (M(0, 1) * M(2, 0) - M(0, 0) * M(2, 1)) * invDet, + (M(0, 0) * M(1, 1) - M(0, 1) * M(1, 0)) * invDet + }; + invertible = true; + } + else + { + inverse.MakeZero(); + invertible = false; + } + + if (reportInvertibility) + { + *reportInvertibility = invertible; + } + return inverse; + } + + template + Matrix3x3 Adjoint(Matrix3x3 const& M) + { + return Matrix3x3 + { + M(1, 1)* M(2, 2) - M(1, 2) * M(2, 1), + M(0, 2)* M(2, 1) - M(0, 1) * M(2, 2), + M(0, 1)* M(1, 2) - M(0, 2) * M(1, 1), + M(1, 2)* M(2, 0) - M(1, 0) * M(2, 2), + M(0, 0)* M(2, 2) - M(0, 2) * M(2, 0), + M(0, 2)* M(1, 0) - M(0, 0) * M(1, 2), + M(1, 0)* M(2, 1) - M(1, 1) * M(2, 0), + M(0, 1)* M(2, 0) - M(0, 0) * M(2, 1), + M(0, 0)* M(1, 1) - M(0, 1) * M(1, 0) + }; + } + + template + Real Determinant(Matrix3x3 const& M) + { + Real c00 = M(1, 1) * M(2, 2) - M(1, 2) * M(2, 1); + Real c10 = M(1, 2) * M(2, 0) - M(1, 0) * M(2, 2); + Real c20 = M(1, 0) * M(2, 1) - M(1, 1) * M(2, 0); + Real det = M(0, 0) * c00 + M(0, 1) * c10 + M(0, 2) * c20; + return det; + } + + template + Real Trace(Matrix3x3 const& M) + { + Real trace = M(0, 0) + M(1, 1) + M(2, 2); + return trace; + } + + // Multiply M and V according to the user-selected convention. If it is + // GTE_USE_MAT_VEC, the function returns M*V. If it is GTE_USE_VEC_MAT, + // the function returns V*M. This function is provided to hide the + // preprocessor symbols in the GTEngine sample applications. + template + Vector3 DoTransform(Matrix3x3 const& M, Vector3 const& V) + { +#if defined(GTE_USE_MAT_VEC) + return M * V; +#else + return V * M; +#endif + } + + template + Matrix3x3 DoTransform(Matrix3x3 const& A, Matrix3x3 const& B) + { +#if defined(GTE_USE_MAT_VEC) + return A * B; +#else + return B * A; +#endif + } + + // For GTE_USE_MAT_VEC, the columns of an invertible matrix form a basis + // for the range of the matrix. For GTE_USE_VEC_MAT, the rows of an + // invertible matrix form a basis for the range of the matrix. These + // functions allow you to access the basis vectors. The caller is + // responsible for ensuring that the matrix is invertible (although the + // inverse is not calculated by these functions). + template + void SetBasis(Matrix3x3& M, int i, Vector3 const& V) + { +#if defined(GTE_USE_MAT_VEC) + return M.SetCol(i, V); +#else + return M.SetRow(i, V); +#endif + } + + template + Vector3 GetBasis(Matrix3x3 const& M, int i) + { +#if defined(GTE_USE_MAT_VEC) + return M.GetCol(i); +#else + return M.GetRow(i); +#endif + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Matrix4x4.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Matrix4x4.h new file mode 100644 index 0000000..e318ba3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Matrix4x4.h @@ -0,0 +1,369 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + // Template alias for convenience. + template + using Matrix4x4 = Matrix<4, 4, Real>; + + // Geometric operations. + template + Matrix4x4 Inverse(Matrix4x4 const& M, bool* reportInvertibility = nullptr) + { + Matrix4x4 inverse; + bool invertible; + Real a0 = M(0, 0) * M(1, 1) - M(0, 1) * M(1, 0); + Real a1 = M(0, 0) * M(1, 2) - M(0, 2) * M(1, 0); + Real a2 = M(0, 0) * M(1, 3) - M(0, 3) * M(1, 0); + Real a3 = M(0, 1) * M(1, 2) - M(0, 2) * M(1, 1); + Real a4 = M(0, 1) * M(1, 3) - M(0, 3) * M(1, 1); + Real a5 = M(0, 2) * M(1, 3) - M(0, 3) * M(1, 2); + Real b0 = M(2, 0) * M(3, 1) - M(2, 1) * M(3, 0); + Real b1 = M(2, 0) * M(3, 2) - M(2, 2) * M(3, 0); + Real b2 = M(2, 0) * M(3, 3) - M(2, 3) * M(3, 0); + Real b3 = M(2, 1) * M(3, 2) - M(2, 2) * M(3, 1); + Real b4 = M(2, 1) * M(3, 3) - M(2, 3) * M(3, 1); + Real b5 = M(2, 2) * M(3, 3) - M(2, 3) * M(3, 2); + Real det = a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0; + if (det != (Real)0) + { + Real invDet = (Real)1 / det; + inverse = Matrix4x4 + { + (+M(1, 1) * b5 - M(1, 2) * b4 + M(1, 3) * b3) * invDet, + (-M(0, 1) * b5 + M(0, 2) * b4 - M(0, 3) * b3) * invDet, + (+M(3, 1) * a5 - M(3, 2) * a4 + M(3, 3) * a3) * invDet, + (-M(2, 1) * a5 + M(2, 2) * a4 - M(2, 3) * a3) * invDet, + (-M(1, 0) * b5 + M(1, 2) * b2 - M(1, 3) * b1) * invDet, + (+M(0, 0) * b5 - M(0, 2) * b2 + M(0, 3) * b1) * invDet, + (-M(3, 0) * a5 + M(3, 2) * a2 - M(3, 3) * a1) * invDet, + (+M(2, 0) * a5 - M(2, 2) * a2 + M(2, 3) * a1) * invDet, + (+M(1, 0) * b4 - M(1, 1) * b2 + M(1, 3) * b0) * invDet, + (-M(0, 0) * b4 + M(0, 1) * b2 - M(0, 3) * b0) * invDet, + (+M(3, 0) * a4 - M(3, 1) * a2 + M(3, 3) * a0) * invDet, + (-M(2, 0) * a4 + M(2, 1) * a2 - M(2, 3) * a0) * invDet, + (-M(1, 0) * b3 + M(1, 1) * b1 - M(1, 2) * b0) * invDet, + (+M(0, 0) * b3 - M(0, 1) * b1 + M(0, 2) * b0) * invDet, + (-M(3, 0) * a3 + M(3, 1) * a1 - M(3, 2) * a0) * invDet, + (+M(2, 0) * a3 - M(2, 1) * a1 + M(2, 2) * a0) * invDet + }; + invertible = true; + } + else + { + inverse.MakeZero(); + invertible = false; + } + + if (reportInvertibility) + { + *reportInvertibility = invertible; + } + return inverse; + } + + template + Matrix4x4 Adjoint(Matrix4x4 const& M) + { + Real a0 = M(0, 0) * M(1, 1) - M(0, 1) * M(1, 0); + Real a1 = M(0, 0) * M(1, 2) - M(0, 2) * M(1, 0); + Real a2 = M(0, 0) * M(1, 3) - M(0, 3) * M(1, 0); + Real a3 = M(0, 1) * M(1, 2) - M(0, 2) * M(1, 1); + Real a4 = M(0, 1) * M(1, 3) - M(0, 3) * M(1, 1); + Real a5 = M(0, 2) * M(1, 3) - M(0, 3) * M(1, 2); + Real b0 = M(2, 0) * M(3, 1) - M(2, 1) * M(3, 0); + Real b1 = M(2, 0) * M(3, 2) - M(2, 2) * M(3, 0); + Real b2 = M(2, 0) * M(3, 3) - M(2, 3) * M(3, 0); + Real b3 = M(2, 1) * M(3, 2) - M(2, 2) * M(3, 1); + Real b4 = M(2, 1) * M(3, 3) - M(2, 3) * M(3, 1); + Real b5 = M(2, 2) * M(3, 3) - M(2, 3) * M(3, 2); + + return Matrix4x4 + { + +M(1, 1) * b5 - M(1, 2) * b4 + M(1, 3) * b3, + -M(0, 1) * b5 + M(0, 2) * b4 - M(0, 3) * b3, + +M(3, 1) * a5 - M(3, 2) * a4 + M(3, 3) * a3, + -M(2, 1) * a5 + M(2, 2) * a4 - M(2, 3) * a3, + -M(1, 0) * b5 + M(1, 2) * b2 - M(1, 3) * b1, + +M(0, 0) * b5 - M(0, 2) * b2 + M(0, 3) * b1, + -M(3, 0) * a5 + M(3, 2) * a2 - M(3, 3) * a1, + +M(2, 0) * a5 - M(2, 2) * a2 + M(2, 3) * a1, + +M(1, 0) * b4 - M(1, 1) * b2 + M(1, 3) * b0, + -M(0, 0) * b4 + M(0, 1) * b2 - M(0, 3) * b0, + +M(3, 0) * a4 - M(3, 1) * a2 + M(3, 3) * a0, + -M(2, 0) * a4 + M(2, 1) * a2 - M(2, 3) * a0, + -M(1, 0) * b3 + M(1, 1) * b1 - M(1, 2) * b0, + +M(0, 0) * b3 - M(0, 1) * b1 + M(0, 2) * b0, + -M(3, 0) * a3 + M(3, 1) * a1 - M(3, 2) * a0, + +M(2, 0) * a3 - M(2, 1) * a1 + M(2, 2) * a0 + }; + } + + template + Real Determinant(Matrix4x4 const& M) + { + Real a0 = M(0, 0) * M(1, 1) - M(0, 1) * M(1, 0); + Real a1 = M(0, 0) * M(1, 2) - M(0, 2) * M(1, 0); + Real a2 = M(0, 0) * M(1, 3) - M(0, 3) * M(1, 0); + Real a3 = M(0, 1) * M(1, 2) - M(0, 2) * M(1, 1); + Real a4 = M(0, 1) * M(1, 3) - M(0, 3) * M(1, 1); + Real a5 = M(0, 2) * M(1, 3) - M(0, 3) * M(1, 2); + Real b0 = M(2, 0) * M(3, 1) - M(2, 1) * M(3, 0); + Real b1 = M(2, 0) * M(3, 2) - M(2, 2) * M(3, 0); + Real b2 = M(2, 0) * M(3, 3) - M(2, 3) * M(3, 0); + Real b3 = M(2, 1) * M(3, 2) - M(2, 2) * M(3, 1); + Real b4 = M(2, 1) * M(3, 3) - M(2, 3) * M(3, 1); + Real b5 = M(2, 2) * M(3, 3) - M(2, 3) * M(3, 2); + Real det = a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0; + return det; + } + + template + Real Trace(Matrix4x4 const& M) + { + Real trace = M(0, 0) + M(1, 1) + M(2, 2) + M(3, 3); + return trace; + } + + // Multiply M and V according to the user-selected convention. If it is + // GTE_USE_MAT_VEC, the function returns M*V. If it is GTE_USE_VEC_MAT, + // the function returns V*M. This function is provided to hide the + // preprocessor symbols in the GTEngine sample applications. + template + Vector4 DoTransform(Matrix4x4 const& M, Vector4 const& V) + { +#if defined(GTE_USE_MAT_VEC) + return M * V; +#else + return V * M; +#endif + } + + template + Matrix4x4 DoTransform(Matrix4x4 const& A, Matrix4x4 const& B) + { +#if defined(GTE_USE_MAT_VEC) + return A * B; +#else + return B * A; +#endif + } + + // For GTE_USE_MAT_VEC, the columns of an invertible matrix form a basis + // for the range of the matrix. For GTE_USE_VEC_MAT, the rows of an + // invertible matrix form a basis for the range of the matrix. These + // functions allow you to access the basis vectors. The caller is + // responsible for ensuring that the matrix is invertible (although the + // inverse is not calculated by these functions). + template + void SetBasis(Matrix4x4& M, int i, Vector4 const& V) + { +#if defined(GTE_USE_MAT_VEC) + return M.SetCol(i, V); +#else + return M.SetRow(i, V); +#endif + } + + template + Vector4 GetBasis(Matrix4x4 const& M, int i) + { +#if defined(GTE_USE_MAT_VEC) + return M.GetCol(i); +#else + return M.GetRow(i); +#endif + } + + // Special matrices. In the comments, the matrices are shown using the + // GTE_USE_MAT_VEC multiplication convention. + + // The projection plane is Dot(N,X-P) = 0 where N is a 3-by-1 unit-length + // normal vector and P is a 3-by-1 point on the plane. The projection is + // oblique to the plane, in the direction of the 3-by-1 vector D. + // Necessarily Dot(N,D) is not zero for this projection to make sense. + // Given a 3-by-1 point U, compute the intersection of the line U+t*D with + // the plane to obtain t = -Dot(N,U-P)/Dot(N,D); then + // + // projection(U) = P + [I - D*N^T/Dot(N,D)]*(U-P) + // + // A 4-by-4 homogeneous transformation representing the projection is + // + // +- -+ + // M = | D*N^T - Dot(N,D)*I -Dot(N,P)D | + // | 0^T -Dot(N,D) | + // +- -+ + // + // where M applies to [U^T 1]^T by M*[U^T 1]^T. The matrix is chosen so + // that M[3][3] > 0 whenever Dot(N,D) < 0; the projection is onto the + // "positive side" of the plane. + template + Matrix4x4 MakeObliqueProjection(Vector4 const& origin, + Vector4 const& normal, Vector4 const& direction) + { + Matrix4x4 M; + + Real const zero = (Real)0; + Real dotND = Dot(normal, direction); + Real dotNO = Dot(origin, normal); + +#if defined(GTE_USE_MAT_VEC) + M(0, 0) = direction[0] * normal[0] - dotND; + M(0, 1) = direction[0] * normal[1]; + M(0, 2) = direction[0] * normal[2]; + M(0, 3) = -dotNO * direction[0]; + M(1, 0) = direction[1] * normal[0]; + M(1, 1) = direction[1] * normal[1] - dotND; + M(1, 2) = direction[1] * normal[2]; + M(1, 3) = -dotNO * direction[1]; + M(2, 0) = direction[2] * normal[0]; + M(2, 1) = direction[2] * normal[1]; + M(2, 2) = direction[2] * normal[2] - dotND; + M(2, 3) = -dotNO * direction[2]; + M(3, 0) = zero; + M(3, 1) = zero; + M(3, 2) = zero; + M(3, 3) = -dotND; +#else + M(0, 0) = direction[0] * normal[0] - dotND; + M(1, 0) = direction[0] * normal[1]; + M(2, 0) = direction[0] * normal[2]; + M(3, 0) = -dotNO * direction[0]; + M(0, 1) = direction[1] * normal[0]; + M(1, 1) = direction[1] * normal[1] - dotND; + M(2, 1) = direction[1] * normal[2]; + M(3, 1) = -dotNO * direction[1]; + M(0, 2) = direction[2] * normal[0]; + M(1, 2) = direction[2] * normal[1]; + M(2, 2) = direction[2] * normal[2] - dotND; + M(3, 2) = -dotNO * direction[2]; + M(0, 2) = zero; + M(1, 3) = zero; + M(2, 3) = zero; + M(3, 3) = -dotND; +#endif + + return M; + } + + // The perspective projection of a point onto a plane is + // + // +- -+ + // M = | Dot(N,E-P)*I - E*N^T -(Dot(N,E-P)*I - E*N^T)*E | + // | -N^t Dot(N,E) | + // +- -+ + // + // where E is the eye point, P is a point on the plane, and N is a + // unit-length plane normal. + template + Matrix4x4 MakePerspectiveProjection(Vector4 const& origin, + Vector4 const& normal, Vector4 const& eye) + { + Matrix4x4 M; + + Real dotND = Dot(normal, eye - origin); + +#if defined(GTE_USE_MAT_VEC) + M(0, 0) = dotND - eye[0] * normal[0]; + M(0, 1) = -eye[0] * normal[1]; + M(0, 2) = -eye[0] * normal[2]; + M(0, 3) = -(M(0, 0) * eye[0] + M(0, 1) * eye[1] + M(0, 2) * eye[2]); + M(1, 0) = -eye[1] * normal[0]; + M(1, 1) = dotND - eye[1] * normal[1]; + M(1, 2) = -eye[1] * normal[2]; + M(1, 3) = -(M(1, 0) * eye[0] + M(1, 1) * eye[1] + M(1, 2) * eye[2]); + M(2, 0) = -eye[2] * normal[0]; + M(2, 1) = -eye[2] * normal[1]; + M(2, 2) = dotND - eye[2] * normal[2]; + M(2, 3) = -(M(2, 0) * eye[0] + M(2, 1) * eye[1] + M(2, 2) * eye[2]); + M(3, 0) = -normal[0]; + M(3, 1) = -normal[1]; + M(3, 2) = -normal[2]; + M(3, 3) = Dot(eye, normal); +#else + M(0, 0) = dotND - eye[0] * normal[0]; + M(1, 0) = -eye[0] * normal[1]; + M(2, 0) = -eye[0] * normal[2]; + M(3, 0) = -(M(0, 0) * eye[0] + M(0, 1) * eye[1] + M(0, 2) * eye[2]); + M(0, 1) = -eye[1] * normal[0]; + M(1, 1) = dotND - eye[1] * normal[1]; + M(2, 1) = -eye[1] * normal[2]; + M(3, 1) = -(M(1, 0) * eye[0] + M(1, 1) * eye[1] + M(1, 2) * eye[2]); + M(0, 2) = -eye[2] * normal[0]; + M(1, 2) = -eye[2] * normal[1]; + M(2, 2) = dotND - eye[2] * normal[2]; + M(3, 2) = -(M(2, 0) * eye[0] + M(2, 1) * eye[1] + M(2, 2) * eye[2]); + M(0, 3) = -normal[0]; + M(1, 3) = -normal[1]; + M(2, 3) = -normal[2]; + M(3, 3) = Dot(eye, normal); +#endif + + return M; + } + + // The reflection of a point through a plane is + // +- -+ + // M = | I-2*N*N^T 2*Dot(N,P)*N | + // | 0^T 1 | + // +- -+ + // + // where P is a point on the plane and N is a unit-length plane normal. + template + Matrix4x4 MakeReflection(Vector4 const& origin, + Vector4 const& normal) + { + Matrix4x4 M; + + Real const zero = (Real)0, one = (Real)1, two = (Real)2; + Real twoDotNO = two * Dot(origin, normal); + +#if defined(GTE_USE_MAT_VEC) + M(0, 0) = one - two * normal[0] * normal[0]; + M(0, 1) = -two * normal[0] * normal[1]; + M(0, 2) = -two * normal[0] * normal[2]; + M(0, 3) = twoDotNO * normal[0]; + M(1, 0) = M(0, 1); + M(1, 1) = one - two * normal[1] * normal[1]; + M(1, 2) = -two * normal[1] * normal[2]; + M(1, 3) = twoDotNO * normal[1]; + M(2, 0) = M(0, 2); + M(2, 1) = M(1, 2); + M(2, 2) = one - two * normal[2] * normal[2]; + M(2, 3) = twoDotNO * normal[2]; + M(3, 0) = zero; + M(3, 1) = zero; + M(3, 2) = zero; + M(3, 3) = one; +#else + M(0, 0) = one - two * normal[0] * normal[0]; + M(1, 0) = -two * normal[0] * normal[1]; + M(2, 0) = -two * normal[0] * normal[2]; + M(3, 0) = twoDotNO * normal[0]; + M(0, 1) = M(1, 0); + M(1, 1) = one - two * normal[1] * normal[1]; + M(2, 1) = -two * normal[1] * normal[2]; + M(3, 1) = twoDotNO * normal[1]; + M(0, 2) = M(2, 0); + M(1, 2) = M(2, 1); + M(2, 2) = one - two * normal[2] * normal[2]; + M(3, 2) = twoDotNO * normal[2]; + M(0, 3) = zero; + M(1, 3) = zero; + M(2, 3) = zero; + M(3, 3) = one; +#endif + + return M; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Mesh.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Mesh.h new file mode 100644 index 0000000..646d48e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Mesh.h @@ -0,0 +1,682 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include + +// The Mesh class is designed to support triangulations of surfaces of a small +// number of topologies. See the documents +// https://www.geometrictools.com/MeshDifferentialGeometry.pdf +// https://www.geometrictools.com/MeshFactory.pdf +// for details. +// +// You must set the vertex attribute sources before calling Update(). +// +// The semantic "position" is required and its source must be an array +// of Real with at least 3 channels so that positions are computed as +// Vector3. +// +// The positions are assumed to be parameterized by texture coordinates +// (u,v); the position is thought of as a function P(u,v). If texture +// coordinates are provided, the semantic must be "tcoord". If texture +// coordinates are not provided, default texture coordinates are computed +// internally as described in the mesh factory document. +// +// The frame for the tangent space is optional. All vectors in the frame +// must have sources that are arrays of Real with at least 3 channels per +// attribute. If normal vectors are provided, the semantic must be +// "normal". +// +// Two options are supported for tangent vectors. The first option is +// that the tangents are surface derivatives dP/du and dP/dv, which are +// not necessarily unit length or orthogonal. The semantics must be +// "dpdu" and "dpdv". The second option is that the tangents are unit +// length and orthogonal, with the infrequent possibility that a vertex +// is degenerate in that dP/du and dP/dv are linearly dependent. The +// semantics must be "tangent" and "bitangent". +// +// For each provided vertex attribute, a derived class can initialize +// that attribute by overriding one of the Initialize*() functions whose +// stubs are defined in this class. + +namespace WwiseGTE +{ + enum class MeshTopology + { + ARBITRARY, + RECTANGLE, + CYLINDER, + TORUS, + DISK, + SPHERE + }; + + class MeshDescription + { + public: + // Constructor for MeshTopology::ARBITRARY. The members topology, + // numVertices, and numTriangles are set in the obvious manner. The + // members numRows and numCols are set to zero. The remaining members + // must be set explicitly by the client. + MeshDescription(uint32_t inNumVertices, uint32_t inNumTriangles) + : + topology(MeshTopology::ARBITRARY), + numVertices(inNumVertices), + numTriangles(inNumTriangles), + wantDynamicTangentSpaceUpdate(false), + wantCCW(true), + hasTangentSpaceVectors(false), + allowUpdateFrame(false), + numRows(0), + numCols(0), + rMax(0), + cMax(0), + rIncrement(0), + constructed(false) + { + LogAssert(numVertices >= 3 && numTriangles >= 1, "Invalid input."); + } + + // Constructor for topologies other than MeshTopology::ARBITRARY. + // Compute the number of vertices and triangles for the mesh based on + // the requested number of rows and columns. If the number of rows or + // columns is invalid for the specified topology, they are modified to + // be valid, in which case inNumRows/numRows and inNumCols/numCols can + // differ. If the input topology is MeshTopology::ARBITRARY, then + // inNumRows and inNumCols are assigned to numVertices and + // numTriangles, respectively, and numRows and numCols are set to + // zero. The remaining members must be set explicitly by the client. + MeshDescription(MeshTopology inTopology, uint32_t inNumRows, uint32_t inNumCols) + : + topology(inTopology), + wantDynamicTangentSpaceUpdate(false), + wantCCW(true), + hasTangentSpaceVectors(false), + allowUpdateFrame(false), + constructed(false) + { + switch (topology) + { + case MeshTopology::ARBITRARY: + numVertices = inNumRows; + numTriangles = inNumCols; + numRows = 0; + numCols = 0; + rMax = 0; + cMax = 0; + rIncrement = 0; + break; + + case MeshTopology::RECTANGLE: + numRows = std::max(inNumRows, 2u); + numCols = std::max(inNumCols, 2u); + rMax = numRows - 1; + cMax = numCols - 1; + rIncrement = numCols; + numVertices = (rMax + 1) * (cMax + 1); + numTriangles = 2 * rMax * cMax; + break; + + case MeshTopology::CYLINDER: + numRows = std::max(inNumRows, 2u); + numCols = std::max(inNumCols, 3u); + rMax = numRows - 1; + cMax = numCols; + rIncrement = numCols + 1; + numVertices = (rMax + 1) * (cMax + 1); + numTriangles = 2 * rMax * cMax; + break; + + case MeshTopology::TORUS: + numRows = std::max(inNumRows, 2u); + numCols = std::max(inNumCols, 3u); + rMax = numRows; + cMax = numCols; + rIncrement = numCols + 1; + numVertices = (rMax + 1) * (cMax + 1); + numTriangles = 2 * rMax * cMax; + break; + + case MeshTopology::DISK: + numRows = std::max(inNumRows, 1u); + numCols = std::max(inNumCols, 3u); + rMax = numRows - 1; + cMax = numCols; + rIncrement = numCols + 1; + numVertices = (rMax + 1) * (cMax + 1) + 1; + numTriangles = 2 * rMax * cMax + numCols; + break; + + case MeshTopology::SPHERE: + numRows = std::max(inNumRows, 1u); + numCols = std::max(inNumCols, 3u); + rMax = numRows - 1; + cMax = numCols; + rIncrement = numCols + 1; + numVertices = (rMax + 1) * (cMax + 1) + 2; + numTriangles = 2 * rMax * cMax + 2 * numCols; + break; + } + } + + MeshTopology topology; + uint32_t numVertices; + uint32_t numTriangles; + std::vector vertexAttributes; + IndexAttribute indexAttribute; + bool wantDynamicTangentSpaceUpdate; // default: false + bool wantCCW; // default: true + + // For internal use only. + bool hasTangentSpaceVectors; + bool allowUpdateFrame; + uint32_t numRows, numCols; + uint32_t rMax, cMax, rIncrement; + + // After an attempt to construct a Mesh or Mesh-derived object, + // examine this value to determine whether the construction was + // successful. + bool constructed; + }; + + template + class Mesh + { + public: + // Construction and destruction. This constructor is for ARBITRARY + // topology. The vertices and indices must already be assigned by the + // client. Derived classes use the protected constructor, but + // assignment of vertices and indices occurs in the derived-class + // constructors. + Mesh(MeshDescription const& description, std::vector const& validTopologies) + : + mDescription(description), + mPositions(nullptr), + mNormals(nullptr), + mTangents(nullptr), + mBitangents(nullptr), + mDPDUs(nullptr), + mDPDVs(nullptr), + mTCoords(nullptr), + mPositionStride(0), + mNormalStride(0), + mTangentStride(0), + mBitangentStride(0), + mDPDUStride(0), + mDPDVStride(0), + mTCoordStride(0) + { + mDescription.constructed = false; + for (auto const& topology : validTopologies) + { + if (mDescription.topology == topology) + { + mDescription.constructed = true; + break; + } + } + + LogAssert(mDescription.indexAttribute.source != nullptr, + "The mesh needs triangles/indices in Mesh constructor."); + + // Set sources for the requested vertex attributes. + mDescription.hasTangentSpaceVectors = false; + mDescription.allowUpdateFrame = mDescription.wantDynamicTangentSpaceUpdate; + for (auto const& attribute : mDescription.vertexAttributes) + { + if (attribute.source != nullptr && attribute.stride > 0) + { + if (attribute.semantic == "position") + { + mPositions = reinterpret_cast*>(attribute.source); + mPositionStride = attribute.stride; + continue; + } + + if (attribute.semantic == "normal") + { + mNormals = reinterpret_cast*>(attribute.source); + mNormalStride = attribute.stride; + continue; + } + + if (attribute.semantic == "tangent") + { + mTangents = reinterpret_cast*>(attribute.source); + mTangentStride = attribute.stride; + mDescription.hasTangentSpaceVectors = true; + continue; + } + + if (attribute.semantic == "bitangent") + { + mBitangents = reinterpret_cast*>(attribute.source); + mBitangentStride = attribute.stride; + mDescription.hasTangentSpaceVectors = true; + continue; + } + + if (attribute.semantic == "dpdu") + { + mDPDUs = reinterpret_cast*>(attribute.source); + mDPDUStride = attribute.stride; + mDescription.hasTangentSpaceVectors = true; + continue; + } + + if (attribute.semantic == "dpdv") + { + mDPDVs = reinterpret_cast*>(attribute.source); + mDPDVStride = attribute.stride; + mDescription.hasTangentSpaceVectors = true; + continue; + } + + if (attribute.semantic == "tcoord") + { + mTCoords = reinterpret_cast*>(attribute.source); + mTCoordStride = attribute.stride; + continue; + } + } + } + + LogAssert(mPositions != nullptr, "The mesh needs positions in Mesh constructor."); + + // The initial value of allowUpdateFrame is the client request + // about wanting dynamic tangent-space updates. If the vertex + // attributes do not include tangent-space vectors, then dynamic + // updates are not necessary. If tangent-space vectors are + // present, the update algorithm requires texture coordinates + // (mTCoords must be nonnull) or must compute local coordinates + // (mNormals must be nonnull). + if (mDescription.allowUpdateFrame) + { + if (!mDescription.hasTangentSpaceVectors) + { + mDescription.allowUpdateFrame = false; + } + + if (!mTCoords && !mNormals) + { + mDescription.allowUpdateFrame = false; + } + } + + if (mDescription.allowUpdateFrame) + { + mUTU.resize(mDescription.numVertices); + mDTU.resize(mDescription.numVertices); + } + } + + virtual ~Mesh() + { + } + + // No copying or assignment is allowed. + Mesh(Mesh const&) = delete; + Mesh& operator=(Mesh const&) = delete; + + // Member accessors. + inline MeshDescription const& GetDescription() const + { + return mDescription; + } + + // If the underlying geometric data varies dynamically, call this + // function to update whatever vertex attributes are specified by + // the vertex pool. + void Update() + { + LogAssert(mDescription.constructed, "The Mesh object failed the construction."); + + UpdatePositions(); + + if (mDescription.allowUpdateFrame) + { + UpdateFrame(); + } + else if (mNormals) + { + UpdateNormals(); + } + // else: The mesh has no frame data, so there is nothing to do. + } + + protected: + // Access the vertex attributes. + inline Vector3& Position(uint32_t i) + { + char* positions = reinterpret_cast(mPositions); + return *reinterpret_cast*>(positions + i * mPositionStride); + } + + inline Vector3& Normal(uint32_t i) + { + char* normals = reinterpret_cast(mNormals); + return *reinterpret_cast*>(normals + i * mNormalStride); + } + + inline Vector3& Tangent(uint32_t i) + { + char* tangents = reinterpret_cast(mTangents); + return *reinterpret_cast*>(tangents + i * mTangentStride); + } + + inline Vector3& Bitangent(uint32_t i) + { + char* bitangents = reinterpret_cast(mBitangents); + return *reinterpret_cast*>(bitangents + i * mBitangentStride); + } + + inline Vector3& DPDU(uint32_t i) + { + char* dpdus = reinterpret_cast(mDPDUs); + return *reinterpret_cast*>(dpdus + i * mDPDUStride); + } + + inline Vector3& DPDV(uint32_t i) + { + char* dpdvs = reinterpret_cast(mDPDVs); + return *reinterpret_cast*>(dpdvs + i * mDPDVStride); + } + + inline Vector2& TCoord(uint32_t i) + { + char* tcoords = reinterpret_cast(mTCoords); + return *reinterpret_cast*>(tcoords + i * mTCoordStride); + } + + // Compute the indices for non-arbitrary topologies. This function is + // called by derived classes. + void ComputeIndices() + { + uint32_t t = 0; + for (uint32_t r = 0, i = 0; r < mDescription.rMax; ++r) + { + uint32_t v0 = i, v1 = v0 + 1; + i += mDescription.rIncrement; + uint32_t v2 = i, v3 = v2 + 1; + for (uint32_t c = 0; c < mDescription.cMax; ++c, ++v0, ++v1, ++v2, ++v3) + { + if (mDescription.wantCCW) + { + mDescription.indexAttribute.SetTriangle(t++, v0, v1, v2); + mDescription.indexAttribute.SetTriangle(t++, v1, v3, v2); + } + else + { + mDescription.indexAttribute.SetTriangle(t++, v0, v2, v1); + mDescription.indexAttribute.SetTriangle(t++, v1, v2, v3); + } + } + } + + if (mDescription.topology == MeshTopology::DISK) + { + uint32_t v0 = 0, v1 = 1, v2 = mDescription.numVertices - 1; + for (unsigned int c = 0; c < mDescription.numCols; ++c, ++v0, ++v1) + { + if (mDescription.wantCCW) + { + mDescription.indexAttribute.SetTriangle(t++, v0, v2, v1); + } + else + { + mDescription.indexAttribute.SetTriangle(t++, v0, v1, v2); + } + } + } + else if (mDescription.topology == MeshTopology::SPHERE) + { + uint32_t v0 = 0, v1 = 1, v2 = mDescription.numVertices - 2; + for (uint32_t c = 0; c < mDescription.numCols; ++c, ++v0, ++v1) + { + if (mDescription.wantCCW) + { + mDescription.indexAttribute.SetTriangle(t++, v0, v2, v1); + } + else + { + mDescription.indexAttribute.SetTriangle(t++, v0, v1, v2); + } + } + + v0 = (mDescription.numRows - 1) * mDescription.numCols; + v1 = v0 + 1; + v2 = mDescription.numVertices - 1; + for (uint32_t c = 0; c < mDescription.numCols; ++c, ++v0, ++v1) + { + if (mDescription.wantCCW) + { + mDescription.indexAttribute.SetTriangle(t++, v0, v2, v1); + } + else + { + mDescription.indexAttribute.SetTriangle(t++, v0, v1, v2); + } + } + } + } + + // The Update() function allows derived classes to use algorithms + // different from least-squares fitting to compute the normals (when + // no tangent-space information is requested) or to compute the frame + // (normals and tangent space). The UpdatePositions() is a stub; the + // base-class has no knowledge about how positions should be modified. + // A derived class, however, might choose to use dynamic updating + // and override UpdatePositions(). The base-class UpdateNormals() + // computes vertex normals as averages of area-weighted triangle + // normals (nonparametric approach). The base-class UpdateFrame() + // uses a least-squares algorithm for estimating the tangent space + // (parametric approach). + virtual void UpdatePositions() + { + } + + virtual void UpdateNormals() + { + // Compute normal vector as normalized weighted averages of triangle + // normal vectors. + + // Set the normals to zero to allow accumulation of triangle normals. + Vector3 zero{ (Real)0, (Real)0, (Real)0 }; + for (uint32_t i = 0; i < mDescription.numVertices; ++i) + { + Normal(i) = zero; + } + + // Accumulate the triangle normals. + for (uint32_t t = 0; t < mDescription.numTriangles; ++t) + { + // Get the positions for the triangle. + uint32_t v0, v1, v2; + mDescription.indexAttribute.GetTriangle(t, v0, v1, v2); + Vector3 P0 = Position(v0); + Vector3 P1 = Position(v1); + Vector3 P2 = Position(v2); + + // Get the edge vectors. + Vector3 E1 = P1 - P0; + Vector3 E2 = P2 - P0; + + // Compute a triangle normal show length is twice the area of the + // triangle. + Vector3 triangleNormal = Cross(E1, E2); + + // Accumulate the triangle normals. + Normal(v0) += triangleNormal; + Normal(v1) += triangleNormal; + Normal(v2) += triangleNormal; + } + + // Normalize the normals. + for (uint32_t i = 0; i < mDescription.numVertices; ++i) + { + Normalize(Normal(i), true); + } + } + + virtual void UpdateFrame() + { + if (!mTCoords) + { + // We need to compute vertex normals first in order to compute + // local texture coordinates. The vertex normals are recomputed + // later based on estimated tangent vectors. + UpdateNormals(); + } + + // Use the least-squares algorithm to estimate the tangent-space vectors + // and, if requested, normal vectors. + Matrix<2, 2, Real> zero2x2; // initialized to zero + Matrix<3, 2, Real> zero3x2; // initialized to zero + std::fill(mUTU.begin(), mUTU.end(), zero2x2); + std::fill(mDTU.begin(), mDTU.end(), zero3x2); + for (uint32_t t = 0; t < mDescription.numTriangles; ++t) + { + // Get the positions and differences for the triangle. + uint32_t v0, v1, v2; + mDescription.indexAttribute.GetTriangle(t, v0, v1, v2); + Vector3 P0 = Position(v0); + Vector3 P1 = Position(v1); + Vector3 P2 = Position(v2); + Vector3 D10 = P1 - P0; + Vector3 D20 = P2 - P0; + Vector3 D21 = P2 - P1; + + if (mTCoords) + { + // Get the texture coordinates and differences for the triangle. + Vector2 C0 = TCoord(v0); + Vector2 C1 = TCoord(v1); + Vector2 C2 = TCoord(v2); + Vector2 U10 = C1 - C0; + Vector2 U20 = C2 - C0; + Vector2 U21 = C2 - C1; + + // Compute the outer products. + Matrix<2, 2, Real> outerU10 = OuterProduct(U10, U10); + Matrix<2, 2, Real> outerU20 = OuterProduct(U20, U20); + Matrix<2, 2, Real> outerU21 = OuterProduct(U21, U21); + Matrix<3, 2, Real> outerD10 = OuterProduct(D10, U10); + Matrix<3, 2, Real> outerD20 = OuterProduct(D20, U20); + Matrix<3, 2, Real> outerD21 = OuterProduct(D21, U21); + + // Keep a running sum of U^T*U and D^T*U. + mUTU[v0] += outerU10 + outerU20; + mUTU[v1] += outerU10 + outerU21; + mUTU[v2] += outerU20 + outerU21; + mDTU[v0] += outerD10 + outerD20; + mDTU[v1] += outerD10 + outerD21; + mDTU[v2] += outerD20 + outerD21; + } + else + { + // Compute local coordinates and differences for the triangle. + Vector3 basis[3]; + + basis[0] = Normal(v0); + ComputeOrthogonalComplement(1, basis, true); + Vector2 U10{ Dot(basis[1], D10), Dot(basis[2], D10) }; + Vector2 U20{ Dot(basis[1], D20), Dot(basis[2], D20) }; + mUTU[v0] += OuterProduct(U10, U10) + OuterProduct(U20, U20); + mDTU[v0] += OuterProduct(D10, U10) + OuterProduct(D20, U20); + + basis[0] = Normal(v1); + ComputeOrthogonalComplement(1, basis, true); + Vector2 U01{ Dot(basis[1], D10), Dot(basis[2], D10) }; + Vector2 U21{ Dot(basis[1], D21), Dot(basis[2], D21) }; + mUTU[v1] += OuterProduct(U01, U01) + OuterProduct(U21, U21); + mDTU[v1] += OuterProduct(D10, U01) + OuterProduct(D21, U21); + + basis[0] = Normal(v2); + ComputeOrthogonalComplement(1, basis, true); + Vector2 U02{ Dot(basis[1], D20), Dot(basis[2], D20) }; + Vector2 U12{ Dot(basis[1], D21), Dot(basis[2], D21) }; + mUTU[v2] += OuterProduct(U02, U02) + OuterProduct(U12, U12); + mDTU[v2] += OuterProduct(D20, U02) + OuterProduct(D21, U12); + } + + } + + for (uint32_t i = 0; i < mDescription.numVertices; ++i) + { + Matrix<3, 2, Real> jacobian = mDTU[i] * Inverse(mUTU[i]); + + Vector3 basis[3]; + basis[0] = { jacobian(0, 0), jacobian(1, 0), jacobian(2, 0) }; + basis[1] = { jacobian(0, 1), jacobian(1, 1), jacobian(2, 1) }; + + if (mDPDUs) + { + DPDU(i) = basis[0]; + } + if (mDPDVs) + { + DPDV(i) = basis[1]; + } + + ComputeOrthogonalComplement(2, basis, true); + + if (mNormals) + { + Normal(i) = basis[2]; + } + if (mTangents) + { + Tangent(i) = basis[0]; + } + if (mBitangents) + { + Bitangent(i) = basis[1]; + } + } + } + + // Constructor inputs. + // The client requests this via the constructor; however, if it is + // requested and the vertex attributes do not contain entries for + // "tangent", "bitangent", "dpdu", or "dpdv", then this member is + // set to false. + MeshDescription mDescription; + + // Copied from mVertexAttributes when available. + Vector3* mPositions; + Vector3* mNormals; + Vector3* mTangents; + Vector3* mBitangents; + Vector3* mDPDUs; + Vector3* mDPDVs; + Vector2* mTCoords; + size_t mPositionStride; + size_t mNormalStride; + size_t mTangentStride; + size_t mBitangentStride; + size_t mDPDUStride; + size_t mDPDVStride; + size_t mTCoordStride; + + // When dynamic tangent-space updates are requested, the update algorithm + // requires texture coordinates (user-specified or non-local). It is + // possible to create a vertex-adjacent set (with indices into the + // vertex array) for each mesh vertex; however, instead we rely on a + // triangle iteration and incrementally store the information needed for + // the estimation of the tangent space. Each vertex has associated + // matrices D and U, but we need to store only U^T*U and D^T*U. See the + // PDF for details. + std::vector> mUTU; + std::vector> mDTU; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MeshCurvature.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MeshCurvature.h new file mode 100644 index 0000000..d1c38c3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MeshCurvature.h @@ -0,0 +1,285 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The MeshCurvature class estimates principal curvatures and principal +// directions at the vertices of a manifold triangle mesh. The algorithm +// is described in +// https://www.geometrictools.com/Documentation/MeshDifferentialGeometry.pdf + +namespace WwiseGTE +{ + template + class MeshCurvature + { + public: + MeshCurvature() = default; + + // The input to operator() is a triangle mesh with the specified + // vertex buffer and index buffer. The number of elements of + // 'indices' must be a multiple of 3, each triple of indices + // (3*t, 3*t+1, 3*t+2) representing the triangle with vertices + // (vertices[3*t], vertices[3*t+1], vertices[3*t+2]). The + // singularity threshold is a small nonnegative number. It is + // used to characterize whether the DWTrn matrix is singular. In + // theory, set the threshold to zero. In practice you might have + // to set this to a small positive number. + + void operator()( + size_t numVertices, Vector3 const* vertices, + size_t numTriangles, unsigned int const* indices, + Real singularityThreshold) + { + mNormals.resize(numVertices); + mMinCurvatures.resize(numVertices); + mMaxCurvatures.resize(numVertices); + mMinDirections.resize(numVertices); + mMaxDirections.resize(numVertices); + + // Compute the normal vectors for the vertices as an + // area-weighted sum of the triangles sharing a vertex. + Vector3 vzero{ (Real)0, (Real)0, (Real)0 }; + std::fill(mNormals.begin(), mNormals.end(), vzero); + unsigned int const* currentIndex = indices; + for (size_t i = 0; i < numTriangles; ++i) + { + // Get vertex indices. + unsigned int v0 = *currentIndex++; + unsigned int v1 = *currentIndex++; + unsigned int v2 = *currentIndex++; + + // Compute the normal (length provides a weighted sum). + Vector3 edge1 = vertices[v1] - vertices[v0]; + Vector3 edge2 = vertices[v2] - vertices[v0]; + Vector3 normal = Cross(edge1, edge2); + + mNormals[v0] += normal; + mNormals[v1] += normal; + mNormals[v2] += normal; + } + for (size_t i = 0; i < numVertices; ++i) + { + Normalize(mNormals[i]); + } + + // Compute the matrix of normal derivatives. + Matrix3x3 mzero; + std::vector> DNormal(numVertices, mzero); + std::vector> WWTrn(numVertices, mzero); + std::vector> DWTrn(numVertices, mzero); + std::vector DWTrnZero(numVertices, false); + + currentIndex = indices; + for (size_t i = 0; i < numTriangles; ++i) + { + // Get vertex indices. + unsigned int v[3]; + v[0] = *currentIndex++; + v[1] = *currentIndex++; + v[2] = *currentIndex++; + + for (size_t j = 0; j < 3; j++) + { + unsigned int v0 = v[j]; + unsigned int v1 = v[(j + 1) % 3]; + unsigned int v2 = v[(j + 2) % 3]; + + // Compute the edge direction from vertex v0 to vertex v1, + // project it to the tangent plane of vertex v0 and + // compute the difference of adjacent normals. + Vector3 E = vertices[v1] - vertices[v0]; + Vector3 W = E - Dot(E, mNormals[v0]) * mNormals[v0]; + Vector3 D = mNormals[v1] - mNormals[v0]; + for (int row = 0; row < 3; ++row) + { + for (int col = 0; col < 3; ++col) + { + WWTrn[v0](row, col) += W[row] * W[col]; + DWTrn[v0](row, col) += D[row] * W[col]; + } + } + + // Compute the edge direction from vertex v0 to vertex v2, + // project it to the tangent plane of vertex v0 and + // compute the difference of adjacent normals. + E = vertices[v2] - vertices[v0]; + W = E - Dot(E, mNormals[v0]) * mNormals[v0]; + D = mNormals[v2] - mNormals[v0]; + for (int row = 0; row < 3; ++row) + { + for (int col = 0; col < 3; ++col) + { + WWTrn[v0](row, col) += W[row] * W[col]; + DWTrn[v0](row, col) += D[row] * W[col]; + } + } + } + } + + // Add in N*N^T to W*W^T for numerical stability. In theory 0*0^T + // is added to D*W^T, but of course no update is needed in the + // implementation. Compute the matrix of normal derivatives. + for (size_t i = 0; i < numVertices; ++i) + { + for (int row = 0; row < 3; ++row) + { + for (int col = 0; col < 3; ++col) + { + WWTrn[i](row, col) = (Real)0.5 * WWTrn[i](row, col) + + mNormals[i][row] * mNormals[i][col]; + DWTrn[i](row, col) *= (Real)0.5; + } + } + + // Compute the max-abs entry of D*W^T. If this entry is + // (nearly) zero, flag the DNormal matrix as singular. + Real maxAbs = (Real)0; + for (int row = 0; row < 3; ++row) + { + for (int col = 0; col < 3; ++col) + { + Real absEntry = std::fabs(DWTrn[i](row, col)); + if (absEntry > maxAbs) + { + maxAbs = absEntry; + } + } + } + if (maxAbs < singularityThreshold) + { + DWTrnZero[i] = true; + } + + DNormal[i] = DWTrn[i] * Inverse(WWTrn[i]); + } + + // If N is a unit-length normal at a vertex, let U and V be + // unit-length tangents so that {U, V, N} is an orthonormal set. + // Define the matrix J = [U | V], a 3-by-2 matrix whose columns + // are U and V. Define J^T to be the transpose of J, a 2-by-3 + // matrix. Let dN/dX denote the matrix of first-order derivatives + // of the normal vector field. The shape matrix is + // S = (J^T * J)^{-1} * J^T * dN/dX * J = J^T * dN/dX * J + // where the superscript of -1 denotes the inverse; the formula + // allows for J to be created from non-perpendicular vectors. The + // matrix S is 2-by-2. The principal curvatures are the + // eigenvalues of S. If k is a principal curvature and W is the + // 2-by-1 eigenvector corresponding to it, then S*W = k*W (by + // definition). The corresponding 3-by-1 tangent vector at the + // vertex is a principal direction for k and is J*W. + for (size_t i = 0; i < numVertices; ++i) + { + // Compute U and V given N. + Vector3 basis[3]; + basis[0] = mNormals[i]; + ComputeOrthogonalComplement(1, basis); + Vector3 const& U = basis[1]; + Vector3 const& V = basis[2]; + + if (DWTrnZero[i]) + { + // At a locally planar point. + mMinCurvatures[i] = (Real)0; + mMaxCurvatures[i] = (Real)0; + mMinDirections[i] = U; + mMaxDirections[i] = V; + continue; + } + + // Compute S = J^T * dN/dX * J. In theory S is symmetric, but + // because dN/dX is estimated, we must ensure that the + // computed S is symmetric. + Real s00 = Dot(U, DNormal[i] * U); + Real s01 = Dot(U, DNormal[i] * V); + Real s10 = Dot(V, DNormal[i] * U); + Real s11 = Dot(V, DNormal[i] * V); + Real avr = (Real)0.5 * (s01 + s10); + Matrix2x2 S{ s00, avr, avr, s11 }; + + // Compute the eigenvalues of S (min and max curvatures). + Real trace = S(0, 0) + S(1, 1); + Real det = S(0, 0) * S(1, 1) - S(0, 1) * S(1, 0); + Real discr = trace * trace - (Real)4.0 * det; + Real rootDiscr = std::sqrt(std::max(discr, (Real)0)); + mMinCurvatures[i] = (Real)0.5* (trace - rootDiscr); + mMaxCurvatures[i] = (Real)0.5* (trace + rootDiscr); + + // Compute the eigenvectors of S. + Vector2 W0{ S(0, 1), mMinCurvatures[i] - S(0, 0) }; + Vector2 W1{ mMinCurvatures[i] - S(1, 1), S(1, 0) }; + if (Dot(W0, W0) >= Dot(W1, W1)) + { + Normalize(W0); + mMinDirections[i] = W0[0] * U + W0[1] * V; + } + else + { + Normalize(W1); + mMinDirections[i] = W1[0] * U + W1[1] * V; + } + + W0 = Vector2{ S(0, 1), mMaxCurvatures[i] - S(0, 0) }; + W1 = Vector2{ mMaxCurvatures[i] - S(1, 1), S(1, 0) }; + if (Dot(W0, W0) >= Dot(W1, W1)) + { + Normalize(W0); + mMaxDirections[i] = W0[0] * U + W0[1] * V; + } + else + { + Normalize(W1); + mMaxDirections[i] = W1[0] * U + W1[1] * V; + } + } + } + + void operator()( + std::vector> const& vertices, + std::vector const& indices, + Real singularityThreshold) + { + operator()(vertices.size(), vertices.data(), indices.size() / 3, + indices.data(), singularityThreshold); + } + + inline std::vector> const& GetNormals() const + { + return mNormals; + } + + inline std::vector const& GetMinCurvatures() const + { + return mMinCurvatures; + } + + inline std::vector const& GetMaxCurvatures() const + { + return mMaxCurvatures; + } + + inline std::vector> const& GetMinDirections() const + { + return mMinDirections; + } + + inline std::vector> const& GetMaxDirections() const + { + return mMaxDirections; + } + + private: + std::vector> mNormals; + std::vector mMinCurvatures; + std::vector mMaxCurvatures; + std::vector> mMinDirections; + std::vector> mMaxDirections; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MinHeap.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MinHeap.h new file mode 100644 index 0000000..b3f10da --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MinHeap.h @@ -0,0 +1,412 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// A min-heap is a binary tree whose nodes have weights and with the +// constraint that the weight of a parent node is less than or equal to the +// weights of its children. This data structure may be used as a priority +// queue. If the std::priority_queue interface suffices for your needs, use +// that instead. However, for some geometric algorithms, that interface is +// insufficient for optimal performance. For example, if you have a polyline +// vertices that you want to decimate, each vertex's weight depends on its +// neighbors' locations. If the minimum-weight vertex is removed from the +// min-heap, the neighboring vertex weights must be updated--something that +// is O(1) time when you store the vertices as a doubly linked list. The +// neighbors are already in the min-heap, so modifying their weights without +// removing then from--and then reinserting into--the min-heap requires they +// must be moved to their proper places to restore the invariant of the +// min-heap. With std::priority_queue, you have no direct access to the +// modified vertices, forcing you to search for those vertices, remove them, +// update their weights, and re-insert them. The min-heap implementation here +// does support the update without removal and reinsertion. +// +// The ValueType represents the weight and it must support comparisons +// "<" and "<=". Additional information can be stored in the min-heap for +// convenient access; this is stored as the KeyType. In the (open) polyline +// decimation example, the KeyType is a structure that stores indices to +// a vertex and its neighbors. The following code illustrates the creation +// and use of the min-heap. The Weight() function is whatever you choose to +// guide which vertices are removed first from the polyline. +// +// struct Vertex { int previous, current, next; }; +// int numVertices = ; +// std::vector> positions(numVertices); +// ; +// MinHeap minHeap(numVertices); +// std::vector::Record*> records(numVertices); +// for (int i = 0; i < numVertices; ++i) +// { +// Vertex vertex; +// vertex.previous = (i + numVertices - 1) % numVertices; +// vertex.current = i; +// vertex.next = (i + 1) % numVertices; +// records[i] = minHeap.Insert(vertex, Weight(positions, vertex)); +// } +// +// while (minHeap.GetNumElements() >= 2) +// { +// Vertex vertex; +// Real weight; +// minHeap.Remove(vertex, weight); +// ; +// +// // Remove 'vertex' from the doubly linked list. +// Vertex& vp = records[vertex.previous]->key; +// Vertex& vc = records[vertex.current]->key; +// Vertex& vn = records[vertex.next]->key; +// vp.next = vc.next; +// vn.previous = vc.previous; +// +// // Update the neighbors' weights in the min-heap. +// minHeap.Update(records[vertex.previous], Weight(positions, vp)); +// minHeap.Update(records[vertex.next], Weight(positions, vn)); +// } + +namespace WwiseGTE +{ + template + class MinHeap + { + public: + struct Record + { + KeyType key; + ValueType value; + int index; + }; + + // Construction. The record 'value' members are uninitialized for + // native types chosen for ValueType. If ValueType is of class type, + // then the default constructor is used to set the 'value' members. + MinHeap(int maxElements = 0) + { + Reset(maxElements); + } + + MinHeap(MinHeap const& minHeap) + { + *this = minHeap; + } + + // Assignment. + MinHeap& operator=(MinHeap const& minHeap) + { + mNumElements = minHeap.mNumElements; + mRecords = minHeap.mRecords; + mPointers.resize(minHeap.mPointers.size()); + for (auto& record : mRecords) + { + mPointers[record.index] = &record; + } + return *this; + } + + // Clear the min-heap so that it has the specified max elements, + // mNumElements is zero, and mPointers are set to the natural ordering + // of mRecords. + void Reset(int maxElements) + { + mNumElements = 0; + if (maxElements > 0) + { + mRecords.resize(maxElements); + mPointers.resize(maxElements); + for (int i = 0; i < maxElements; ++i) + { + mPointers[i] = &mRecords[i]; + mPointers[i]->index = i; + } + } + else + { + mRecords.clear(); + mPointers.clear(); + } + } + + // Get the remaining number of elements in the min-heap. This number + // is in the range {0..maxElements}. + inline int GetNumElements() const + { + return mNumElements; + } + + // Get the root of the min-heap. The return value is 'true' whenever + // the min-heap is not empty. This function reads the root but does + // not remove the element from the min-heap. + bool GetMinimum(KeyType& key, ValueType& value) const + { + if (mNumElements > 0) + { + key = mPointers[0]->key; + value = mPointers[0]->value; + return true; + } + else + { + return false; + } + } + + // Insert into the min-heap the 'value' that corresponds to the 'key'. + // The return value is a pointer to the heap record that stores a copy + // of 'value', and the pointer value is constant for the life of the + // min-heap. If you must update a member of the min-heap, say, as + // illustrated in the polyline decimation example, pass the pointer to + // Update: + // auto* valueRecord = minHeap.Insert(key, value); + // ; + // minHeap.Update(valueRecord, newValue). + Record* Insert(KeyType const& key, ValueType const& value) + { + // Return immediately when the heap is full. + if (mNumElements == static_cast(mRecords.size())) + { + return nullptr; + } + + // Store the input information in the last heap record, which is + // the last leaf in the tree. + int child = mNumElements++; + Record* record = mPointers[child]; + record->key = key; + record->value = value; + + // Propagate the information toward the root of the tree until it + // reaches its correct position, thus restoring the tree to a + // valid heap. + while (child > 0) + { + int parent = (child - 1) / 2; + if (mPointers[parent]->value <= value) + { + // The parent has a value smaller than or equal to the + // child's value, so we now have a valid heap. + break; + } + + // The parent has a larger value than the child's value. Swap + // the parent and child: + + // Move the parent into the child's slot. + mPointers[child] = mPointers[parent]; + mPointers[child]->index = child; + + // Move the child into the parent's slot. + mPointers[parent] = record; + mPointers[parent]->index = parent; + + child = parent; + } + + return mPointers[child]; + } + + // Remove the root of the heap and return its 'key' and 'value + // members. The root contains the minimum value of all heap elements. + // The return value is 'true' whenever the min-heap was not empty + // before the Remove call. + bool Remove(KeyType& key, ValueType& value) + { + // Return immediately when the heap is empty. + if (mNumElements == 0) + { + return false; + } + + // Get the information from the root of the heap. + Record* root = mPointers[0]; + key = root->key; + value = root->value; + + // Restore the tree to a heap. Abstractly, record is the new root + // of the heap. It is moved down the tree via parent-child swaps + // until it is in a location that restores the tree to a heap. + int last = --mNumElements; + Record* record = mPointers[last]; + int parent = 0, child = 1; + while (child <= last) + { + if (child < last) + { + // Select the child with smallest value to be the one that + // is swapped with the parent, if necessary. + int childP1 = child + 1; + if (mPointers[childP1]->value < mPointers[child]->value) + { + child = childP1; + } + } + + if (record->value <= mPointers[child]->value) + { + // The tree is now a heap. + break; + } + + // Move the child into the parent's slot. + mPointers[parent] = mPointers[child]; + mPointers[parent]->index = parent; + + parent = child; + child = 2 * child + 1; + } + + // The previous 'last' record was moved to the root and propagated + // down the tree to its final resting place, restoring the tree to + // a heap. The slot mPointers[parent] is that resting place. + mPointers[parent] = record; + mPointers[parent]->index = parent; + + // The old root record must not be lost. Attach it to the slot + // that contained the old last record. + mPointers[last] = root; + mPointers[last]->index = last; + return true; + } + + // The value of a heap record must be modified through this function + // call. The side effect is that the heap is updated accordingly to + // restore the data structure to a min-heap. The input 'record' + // should be a pointer returned by Insert(value); see the comments for + // the Insert() function. + void Update(Record* record, ValueType const& value) + { + // Return immediately on invalid record. + if (!record) + { + return; + } + + int parent, child, childP1, maxChild; + + if (record->value < value) + { + record->value = value; + + // The new value is larger than the old value. Propagate it + // toward the leaves. + parent = record->index; + child = 2 * parent + 1; + while (child < mNumElements) + { + // At least one child exists. Locate the one of maximum + // value. + childP1 = child + 1; + if (childP1 < mNumElements) + { + // Two children exist. + if (mPointers[child]->value <= mPointers[childP1]->value) + { + maxChild = child; + } + else + { + maxChild = childP1; + } + } + else + { + // One child exists. + maxChild = child; + } + + if (value <= mPointers[maxChild]->value) + { + // The new value is in the correct place to restore + // the tree to a heap. + break; + } + + // The child has a larger value than the parent's value. + // Swap the parent and child: + + // Move the child into the parent's slot. + mPointers[parent] = mPointers[maxChild]; + mPointers[parent]->index = parent; + + // Move the parent into the child's slot. + mPointers[maxChild] = record; + mPointers[maxChild]->index = maxChild; + + parent = maxChild; + child = 2 * parent + 1; + } + } + else if (value < record->value) + { + record->value = value; + + // The new weight is smaller than the old weight. Propagate + // it toward the root. + child = record->index; + while (child > 0) + { + // A parent exists. + parent = (child - 1) / 2; + + if (mPointers[parent]->value <= value) + { + // The new value is in the correct place to restore + // the tree to a heap. + break; + } + + // The parent has a smaller value than the child's value. + // Swap the child and parent. + + // Move the parent into the child's slot. + mPointers[child] = mPointers[parent]; + mPointers[child]->index = child; + + // Move the child into the parent's slot. + mPointers[parent] = record; + mPointers[parent]->index = parent; + + child = parent; + } + } + } + + // Support for debugging. The functions test whether the data + // structure is a valid min-heap. + bool IsValid() const + { + for (int child = 0; child < mNumElements; ++child) + { + int parent = (child - 1) / 2; + if (parent > 0) + { + if (mPointers[child]->value < mPointers[parent]->value) + { + return false; + } + + if (mPointers[parent]->index != parent) + { + return false; + } + } + } + + return true; + } + + private: + // A 2-level storage system is used. The pointers have two roles. + // Firstly, they are unique to each inserted value in order to support + // the Update() capability of the min-heap. Secondly, they avoid + // potentially expensive copying of Record objects as sorting occurs + // in the heap. + int mNumElements; + std::vector mRecords; + std::vector mPointers; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MinimalCycleBasis.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MinimalCycleBasis.h new file mode 100644 index 0000000..ec29540 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MinimalCycleBasis.h @@ -0,0 +1,681 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include + +// Extract the minimal cycle basis for a planar graph. The input vertices and +// edges must form a graph for which edges intersect only at vertices; that is, +// no two edges must intersect at an interior point of one of the edges. The +// algorithm is described in +// https://www.geometrictools.com/Documentation/MinimalCycleBasis.pdf +// The graph might have filaments, which are polylines in the graph that are +// not shared by a cycle. These are also extracted by the implementation. +// Because the inputs to the constructor are vertices and edges of the graph, +// isolated vertices are ignored. +// +// The computations that determine which adjacent vertex to visit next during +// a filament or cycle traversal do not require division, so the exact +// arithmetic type BSNumber suffices for ComputeType when you +// want to ensure a correct output. (Floating-point rounding errors +// potentially can lead to an incorrect output.) + +namespace WwiseGTE +{ + template + class MinimalCycleBasis + { + public: + struct Tree + { + std::vector cycle; + std::vector> children; + }; + + // The input positions and edges must form a planar graph for which + // edges intersect only at vertices; that is, no two edges must + // intersect at an interior point of one of the edges. + MinimalCycleBasis( + std::vector> const& positions, + std::vector> const& edges, + std::vector>& forest) + { + forest.clear(); + if (positions.size() == 0 || edges.size() == 0) + { + // The graph is empty, so there are no filaments or cycles. + return; + } + + // Determine the unique positions referenced by the edges. + std::map> unique; + for (auto const& edge : edges) + { + for (int i = 0; i < 2; ++i) + { + int name = edge[i]; + if (unique.find(name) == unique.end()) + { + auto vertex = std::make_shared(name, &positions[name]); + unique.insert(std::make_pair(name, vertex)); + + } + } + } + + // Assign responsibility for ownership of the Vertex objects. + std::vector vertices; + mVertexStorage.reserve(unique.size()); + vertices.reserve(unique.size()); + for (auto const& element : unique) + { + mVertexStorage.push_back(element.second); + vertices.push_back(element.second.get()); + } + + // Determine the adjacencies from the edge information. + for (auto const& edge : edges) + { + auto iter0 = unique.find(edge[0]); + auto iter1 = unique.find(edge[1]); + iter0->second->adjacent.insert(iter1->second.get()); + iter1->second->adjacent.insert(iter0->second.get()); + } + + // Get the connected components of the graph. The 'visited' flags + // are 0 (unvisited), 1 (discovered), 2 (finished). The Vertex + // constructor sets all 'visited' flags to 0. + std::vector> components; + for (auto vInitial : mVertexStorage) + { + if (vInitial->visited == 0) + { + components.push_back(std::vector()); + DepthFirstSearch(vInitial.get(), components.back()); + } + } + + // The depth-first search is used later for collecting vertices + // for subgraphs that are detached from the main graph, so the + // 'visited' flags must be reset to zero after component finding. + for (auto vertex : mVertexStorage) + { + vertex->visited = 0; + } + + // Get the primitives for the components. + for (auto& component : components) + { + forest.push_back(ExtractBasis(component)); + } + } + + // No copy or assignment allowed. + MinimalCycleBasis(MinimalCycleBasis const&) = delete; + MinimalCycleBasis& operator=(MinimalCycleBasis const&) = delete; + + private: + struct Vertex + { + Vertex(int inName, std::array const* inPosition) + : + name(inName), + position(inPosition), + visited(0) + { + } + + bool operator< (Vertex const& vertex) const + { + return name < vertex.name; + } + + // The index into the 'positions' input provided to the call to + // operator(). The index is used when reporting cycles to the + // caller of the constructor for MinimalCycleBasis. + int name; + + // Multiple vertices can share a position during processing of + // graph components. + std::array const* position; + + // The mVertexStorage member owns the Vertex objects and maintains + // the reference counts on those objects. The adjacent pointers + // are considered to be weak pointers; neither object ownership + // nor reference counting is required by 'adjacent'. + std::set adjacent; + + // Support for depth-first traversal of a graph. + int visited; + }; + + // The constructor uses GetComponents(...) and DepthFirstSearch(...) + // to get the connected components of the graph implied by the input + // 'edges'. Recursive processing uses only DepthFirstSearch(...) to + // collect vertices of the subgraphs of the original graph. + static void DepthFirstSearch(Vertex* vInitial, std::vector& component) + { + std::stack vStack; + vStack.push(vInitial); + while (vStack.size() > 0) + { + Vertex* vertex = vStack.top(); + vertex->visited = 1; + size_t i = 0; + for (auto adjacent : vertex->adjacent) + { + if (adjacent && adjacent->visited == 0) + { + vStack.push(adjacent); + break; + } + ++i; + } + + if (i == vertex->adjacent.size()) + { + vertex->visited = 2; + component.push_back(vertex); + vStack.pop(); + } + } + } + + // Support for traversing a simply connected component of the graph. + std::shared_ptr ExtractBasis(std::vector& component) + { + // The root will not have its 'cycle' member set. The children + // are the cycle trees extracted from the component. + auto tree = std::make_shared(); + while (component.size() > 0) + { + RemoveFilaments(component); + if (component.size() > 0) + { + tree->children.push_back(ExtractCycleFromComponent(component)); + } + } + + if (tree->cycle.size() == 0 && tree->children.size() == 1) + { + // Replace the parent by the child to avoid having two empty + // cycles in parent/child. + auto child = tree->children.back(); + tree->cycle = std::move(child->cycle); + tree->children = std::move(child->children); + } + return tree; + } + + void RemoveFilaments(std::vector& component) + { + // Locate all filament endpoints, which are vertices, each having + // exactly one adjacent vertex. + std::vector endpoints; + for (auto vertex : component) + { + if (vertex->adjacent.size() == 1) + { + endpoints.push_back(vertex); + } + } + + if (endpoints.size() > 0) + { + // Remove the filaments from the component. If a filament has + // two endpoints, each having one adjacent vertex, the + // adjacency set of the final visited vertex become empty. + // We must test for that condition before starting a new + // filament removal. + for (auto vertex : endpoints) + { + if (vertex->adjacent.size() == 1) + { + // Traverse the filament and remove the vertices. + while (vertex->adjacent.size() == 1) + { + // Break the connection between the two vertices. + Vertex* adjacent = *vertex->adjacent.begin(); + adjacent->adjacent.erase(vertex); + vertex->adjacent.erase(adjacent); + + // Traverse to the adjacent vertex. + vertex = adjacent; + } + } + } + + // At this time the component is either empty (it was a union + // of polylines) or it has no filaments and at least one + // cycle. Remove the isolated vertices generated by filament + // extraction. + std::vector remaining; + remaining.reserve(component.size()); + for (auto vertex : component) + { + if (vertex->adjacent.size() > 0) + { + remaining.push_back(vertex); + } + } + component = std::move(remaining); + } + } + + std::shared_ptr ExtractCycleFromComponent(std::vector& component) + { + // Search for the left-most vertex of the component. If two or + // more vertices attain minimum x-value, select the one that has + // minimum y-value. + Vertex* minVertex = component[0]; + for (auto vertex : component) + { + if (*vertex->position < *minVertex->position) + { + minVertex = vertex; + } + } + + // Traverse the closed walk, duplicating the starting vertex as + // the last vertex. + std::vector closedWalk; + Vertex* vCurr = minVertex; + Vertex* vStart = vCurr; + closedWalk.push_back(vStart); + Vertex* vAdj = GetClockwiseMost(nullptr, vStart); + while (vAdj != vStart) + { + closedWalk.push_back(vAdj); + Vertex* vNext = GetCounterclockwiseMost(vCurr, vAdj); + vCurr = vAdj; + vAdj = vNext; + } + closedWalk.push_back(vStart); + + // Recursively process the closed walk to extract cycles. + auto tree = ExtractCycleFromClosedWalk(closedWalk); + + // The isolated vertices generated by cycle removal are also + // removed from the component. + std::vector remaining; + remaining.reserve(component.size()); + for (auto vertex : component) + { + if (vertex->adjacent.size() > 0) + { + remaining.push_back(vertex); + } + } + component = std::move(remaining); + + return tree; + } + + std::shared_ptr ExtractCycleFromClosedWalk(std::vector& closedWalk) + { + auto tree = std::make_shared(); + + std::map duplicates; + std::set detachments; + int numClosedWalk = static_cast(closedWalk.size()); + for (int i = 1; i < numClosedWalk - 1; ++i) + { + auto diter = duplicates.find(closedWalk[i]); + if (diter == duplicates.end()) + { + // We have not yet visited this vertex. + duplicates.insert(std::make_pair(closedWalk[i], i)); + continue; + } + + // The vertex has been visited previously. Collapse the + // closed walk by removing the subwalk sharing this vertex. + // Note that the vertex is pointed to by + // closedWalk[diter->second] and closedWalk[i]. + int iMin = diter->second, iMax = i; + detachments.insert(iMin); + for (int j = iMin + 1; j < iMax; ++j) + { + Vertex* vertex = closedWalk[j]; + duplicates.erase(vertex); + detachments.erase(j); + } + closedWalk.erase(closedWalk.begin() + iMin + 1, closedWalk.begin() + iMax + 1); + numClosedWalk = static_cast(closedWalk.size()); + i = iMin; + } + + if (numClosedWalk > 3) + { + // We do not know whether closedWalk[0] is a detachment point. + // To determine this, we must test for any edges strictly + // contained by the wedge formed by the edges + // and + // . However, we must execute + // this test even for the known detachment points. The + // ensuing logic is designed to handle this and reduce the + // amount of code, so we insert closedWalk[0] into the + // detachment set and will ignore it later if it actually + // is not. + detachments.insert(0); + + // Detach subgraphs from the vertices of the cycle. + for (auto i : detachments) + { + Vertex* original = closedWalk[i]; + Vertex* maxVertex = closedWalk[i + 1]; + Vertex* minVertex = (i > 0 ? closedWalk[i - 1] : closedWalk[numClosedWalk - 2]); + + std::array dMin, dMax; + for (int j = 0; j < 2; ++j) + { + dMin[j] = (*minVertex->position)[j] - (*original->position)[j]; + dMax[j] = (*maxVertex->position)[j] - (*original->position)[j]; + } + + // For debugging. + bool isConvex = (dMax[0] * dMin[1] >= dMax[1] * dMin[0]); + (void)isConvex; + + std::set inWedge; + std::set adjacent = original->adjacent; + for (auto vertex : adjacent) + { + if (vertex->name == minVertex->name || vertex->name == maxVertex->name) + { + continue; + } + + std::array dVer; + for (int j = 0; j < 2; ++j) + { + dVer[j] = (*vertex->position)[j] - (*original->position)[j]; + } + + bool containsVertex; + if (isConvex) + { + containsVertex = + dVer[0] * dMin[1] > dVer[1] * dMin[0] && + dVer[0] * dMax[1] < dVer[1] * dMax[0]; + } + else + { + containsVertex = + (dVer[0] * dMin[1] > dVer[1] * dMin[0]) || + (dVer[0] * dMax[1] < dVer[1] * dMax[0]); + } + if (containsVertex) + { + inWedge.insert(vertex); + } + } + + if (inWedge.size() > 0) + { + // The clone will manage the adjacents for 'original' + // that lie inside the wedge defined by the first and + // last edges of the subgraph rooted at 'original'. + // The sorting is in the clockwise direction. + auto clone = std::make_shared(original->name, original->position); + mVertexStorage.push_back(clone); + + // Detach the edges inside the wedge. + for (auto vertex : inWedge) + { + original->adjacent.erase(vertex); + vertex->adjacent.erase(original); + clone->adjacent.insert(vertex); + vertex->adjacent.insert(clone.get()); + } + + // Get the subgraph (it is a single connected + // component). + std::vector component; + DepthFirstSearch(clone.get(), component); + + // Extract the cycles of the subgraph. + tree->children.push_back(ExtractBasis(component)); + } + // else the candidate was closedWalk[0] and it has no + // subgraph to detach. + } + + tree->cycle = std::move(ExtractCycle(closedWalk)); + } + else + { + // Detach the subgraph from vertex closedWalk[0]; the subgraph + // is attached via a filament. + Vertex* original = closedWalk[0]; + Vertex* adjacent = closedWalk[1]; + + auto clone = std::make_shared(original->name, original->position); + mVertexStorage.push_back(clone); + + original->adjacent.erase(adjacent); + adjacent->adjacent.erase(original); + clone->adjacent.insert(adjacent); + adjacent->adjacent.insert(clone.get()); + + // Get the subgraph (it is a single connected component). + std::vector component; + DepthFirstSearch(clone.get(), component); + + // Extract the cycles of the subgraph. + tree->children.push_back(ExtractBasis(component)); + if (tree->cycle.size() == 0 && tree->children.size() == 1) + { + // Replace the parent by the child to avoid having two + // empty cycles in parent/child. + auto child = tree->children.back(); + tree->cycle = std::move(child->cycle); + tree->children = std::move(child->children); + } + } + + return tree; + } + + std::vector ExtractCycle(std::vector& closedWalk) + { + // TODO: This logic was designed not to remove filaments after + // the cycle deletion is complete. Modify this to allow filament + // removal. + + // The closed walk is a cycle. + int const numVertices = static_cast(closedWalk.size()); + std::vector cycle(numVertices); + for (int i = 0; i < numVertices; ++i) + { + cycle[i] = closedWalk[i]->name; + } + + // The clockwise-most edge is always removable. + Vertex* v0 = closedWalk[0]; + Vertex* v1 = closedWalk[1]; + Vertex* vBranch = (v0->adjacent.size() > 2 ? v0 : nullptr); + v0->adjacent.erase(v1); + v1->adjacent.erase(v0); + + // Remove edges while traversing counterclockwise. + while (v1 != vBranch && v1->adjacent.size() == 1) + { + Vertex* adj = *v1->adjacent.begin(); + v1->adjacent.erase(adj); + adj->adjacent.erase(v1); + v1 = adj; + } + + if (v1 != v0) + { + // If v1 had exactly 3 adjacent vertices, removal of the CCW + // edge that shared v1 leads to v1 having 2 adjacent vertices. + // When the CW removal occurs and we reach v1, the edge + // deletion will lead to v1 having 1 adjacent vertex, making + // it a filament endpoint. We must ensure we do not delete v1 + // in this case, allowing the recursive algorithm to handle + // the filament later. + vBranch = v1; + + // Remove edges while traversing clockwise. + while (v0 != vBranch && v0->adjacent.size() == 1) + { + v1 = *v0->adjacent.begin(); + v0->adjacent.erase(v1); + v1->adjacent.erase(v0); + v0 = v1; + } + } + // else the cycle is its own connected component. + + return cycle; + } + + Vertex* GetClockwiseMost(Vertex* vPrev, Vertex* vCurr) const + { + Vertex* vNext = nullptr; + bool vCurrConvex = false; + std::array dCurr, dNext; + if (vPrev) + { + dCurr[0] = (*vCurr->position)[0] - (*vPrev->position)[0]; + dCurr[1] = (*vCurr->position)[1] - (*vPrev->position)[1]; + } + else + { + dCurr[0] = static_cast(0); + dCurr[1] = static_cast(-1); + } + + for (auto vAdj : vCurr->adjacent) + { + // vAdj is a vertex adjacent to vCurr. No backtracking is + // allowed. + if (vAdj == vPrev) + { + continue; + } + + // Compute the potential direction to move in. + std::array dAdj; + dAdj[0] = (*vAdj->position)[0] - (*vCurr->position)[0]; + dAdj[1] = (*vAdj->position)[1] - (*vCurr->position)[1]; + + // Select the first candidate. + if (!vNext) + { + vNext = vAdj; + dNext = dAdj; + vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]); + continue; + } + + // Update if the next candidate is clockwise of the current + // clockwise-most vertex. + if (vCurrConvex) + { + if (dCurr[0] * dAdj[1] < dCurr[1] * dAdj[0] + || dNext[0] * dAdj[1] < dNext[1] * dAdj[0]) + { + vNext = vAdj; + dNext = dAdj; + vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]); + } + } + else + { + if (dCurr[0] * dAdj[1] < dCurr[1] * dAdj[0] + && dNext[0] * dAdj[1] < dNext[1] * dAdj[0]) + { + vNext = vAdj; + dNext = dAdj; + vCurrConvex = (dNext[0] * dCurr[1] < dNext[1] * dCurr[0]); + } + } + } + + return vNext; + } + + Vertex* GetCounterclockwiseMost(Vertex* vPrev, Vertex* vCurr) const + { + Vertex* vNext = nullptr; + bool vCurrConvex = false; + std::array dCurr, dNext; + if (vPrev) + { + dCurr[0] = (*vCurr->position)[0] - (*vPrev->position)[0]; + dCurr[1] = (*vCurr->position)[1] - (*vPrev->position)[1]; + } + else + { + dCurr[0] = static_cast(0); + dCurr[1] = static_cast(-1); + } + + for (auto vAdj : vCurr->adjacent) + { + // vAdj is a vertex adjacent to vCurr. No backtracking is + // allowed. + if (vAdj == vPrev) + { + continue; + } + + // Compute the potential direction to move in. + std::array dAdj; + dAdj[0] = (*vAdj->position)[0] - (*vCurr->position)[0]; + dAdj[1] = (*vAdj->position)[1] - (*vCurr->position)[1]; + + // Select the first candidate. + if (!vNext) + { + vNext = vAdj; + dNext = dAdj; + vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]); + continue; + } + + // Select the next candidate if it is counterclockwise of the + // current counterclockwise-most vertex. + if (vCurrConvex) + { + if (dCurr[0] * dAdj[1] > dCurr[1] * dAdj[0] + && dNext[0] * dAdj[1] > dNext[1] * dAdj[0]) + { + vNext = vAdj; + dNext = dAdj; + vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]); + } + } + else + { + if (dCurr[0] * dAdj[1] > dCurr[1] * dAdj[0] + || dNext[0] * dAdj[1] > dNext[1] * dAdj[0]) + { + vNext = vAdj; + dNext = dAdj; + vCurrConvex = (dNext[0] * dCurr[1] <= dNext[1] * dCurr[0]); + } + } + } + + return vNext; + } + + // Storage for referenced vertices of the original graph and for new + // vertices added during graph traversal. + std::vector> mVertexStorage; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Minimize1.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Minimize1.h new file mode 100644 index 0000000..b87aacb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Minimize1.h @@ -0,0 +1,339 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +// The interval [t0,t1] provided to GetMinimum(Real,Real,Real,Real&,Real&) +// is processed by examining subintervals. On each subinteral [a,b], the +// values f0 = F(a), f1 = F((a+b)/2), and f2 = F(b) are examined. If +// {f0,f1,f2} is monotonic, then [a,b] is subdivided and processed. The +// maximum depth of the recursion is limited by 'maxLevel'. If {f0,f1,f2} +// is not monotonic, then two cases arise. First, if f1 = min{f0,f1,f2}, +// then {f0,f1,f2} is said to "bracket a minimum" and GetBracketedMinimum(*) +// is called to locate the function minimum. The process uses a form of +// bisection called "parabolic interpolation" and the maximum number of +// bisection steps is 'maxBracket'. Second, if f1 = max{f0,f1,f2}, then +// {f0,f1,f2} brackets a maximum. The minimum search continues recursively +// as before on [a,(a+b)/2] and [(a+b)/2,b]. + +namespace WwiseGTE +{ + template + class Minimize1 + { + public: + // Construction. + Minimize1(std::function const& F, int maxLevel, int maxBracket, + Real epsilon = (Real)1e-08, Real tolerance = (Real)1e-04) + : + mFunction(F), + mMaxLevel(maxLevel), + mMaxBracket(maxBracket), + mEpsilon(0), + mTolerance(0) + { + SetEpsilon(epsilon); + SetTolerance(tolerance); + } + + // Member access. + inline void SetEpsilon(Real epsilon) + { + mEpsilon = (epsilon > (Real)0 ? epsilon : (Real)0); + } + + inline void SetTolerance(Real tolerance) + { + mTolerance = (tolerance > (Real)0 ? tolerance : (Real)0); + } + + inline Real GetEpsilon() const + { + return mEpsilon; + } + + inline Real GetTolerance() const + { + return mTolerance; + } + + // Search for a minimum of 'function' on the interval [t0,t1] using an + // initial guess of 'tInitial'. The location of the minimum is 'tMin' + // and/ the value of the minimum is 'fMin'. + void GetMinimum(Real t0, Real t1, Real tInitial, Real& tMin, Real& fMin) + { + LogAssert(t0 <= tInitial && tInitial <= t1, "Invalid initial t value."); + + mTMin = std::numeric_limits::max(); + mFMin = std::numeric_limits::max(); + + Real f0 = mFunction(t0); + if (f0 < mFMin) + { + mTMin = t0; + mFMin = f0; + } + + Real fInitial = mFunction(tInitial); + if (fInitial < mFMin) + { + mTMin = tInitial; + mFMin = fInitial; + } + + Real f1 = mFunction(t1); + if (f1 < mFMin) + { + mTMin = t1; + mFMin = f1; + } + + GetMinimum(t0, f0, tInitial, fInitial, t1, f1, mMaxLevel); + + tMin = mTMin; + fMin = mFMin; + } + + private: + // This is called to start the search on [t0,tInitial] and + // [tInitial,t1]. + void GetMinimum(Real t0, Real f0, Real t1, Real f1, int level) + { + if (level-- == 0) + { + return; + } + + Real tm = (Real)0.5 * (t0 + t1); + Real fm = mFunction(tm); + if (fm < mFMin) + { + mTMin = tm; + mFMin = fm; + } + + if (f0 - (Real)2 * fm + f1 > (Real)0) + { + // The quadratic fit has positive second derivative at the + // midpoint. + if (f1 > f0) + { + if (fm >= f0) + { + // Increasing, repeat on [t0,tm]. + GetMinimum(t0, f0, tm, fm, level); + } + else + { + // Not monotonic, have a bracket. + GetBracketedMinimum(t0, f0, tm, fm, t1, f1, level); + } + } + else if (f1 < f0) + { + if (fm >= f1) + { + // Decreasing, repeat on [tm,t1]. + GetMinimum(tm, fm, t1, f1, level); + } + else + { + // Not monotonic, have a bracket. + GetBracketedMinimum(t0, f0, tm, fm, t1, f1, level); + } + } + else + { + // Constant, repeat on [t0,tm] and [tm,t1]. + GetMinimum(t0, f0, tm, fm, level); + GetMinimum(tm, fm, t1, f1, level); + } + } + else + { + // The quadratic fit has nonpositive second derivative at the + // midpoint. + if (f1 > f0) + { + // Repeat on [t0,tm]. + GetMinimum(t0, f0, tm, fm, level); + } + else if (f1 < f0) + { + // Repeat on [tm,t1]. + GetMinimum(tm, fm, t1, f1, level); + } + else + { + // Repeat on [t0,tm] and [tm,t1]. + GetMinimum(t0, f0, tm, fm, level); + GetMinimum(tm, fm, t1, f1, level); + } + } + } + + // This is called recursively to search [a,(a+b)/2] and [(a+b)/2,b]. + void GetMinimum(Real t0, Real f0, Real tm, Real fm, Real t1, Real f1, int level) + { + if (level-- == 0) + { + return; + } + + if ((t1 - tm) * (f0 - fm) > (tm - t0) * (fm - f1)) + { + // The quadratic fit has positive second derivative at the + // midpoint. + if (f1 > f0) + { + if (fm >= f0) + { + // Increasing, repeat on [t0,tm]. + GetMinimum(t0, f0, tm, fm, level); + } + else + { + // Not monotonic, have a bracket. + GetBracketedMinimum(t0, f0, tm, fm, t1, f1, level); + } + } + else if (f1 < f0) + { + if (fm >= f1) + { + // Decreasing, repeat on [tm,t1]. + GetMinimum(tm, fm, t1, f1, level); + } + else + { + // Not monotonic, have a bracket. + GetBracketedMinimum(t0, f0, tm, fm, t1, f1, level); + } + } + else + { + // Constant, repeat on [t0,tm] and [tm,t1]. + GetMinimum(t0, f0, tm, fm, level); + GetMinimum(tm, fm, t1, f1, level); + } + } + else + { + // The quadratic fit has a nonpositive second derivative at + // the midpoint. + if (f1 > f0) + { + // Repeat on [t0,tm]. + GetMinimum(t0, f0, tm, fm, level); + } + else if (f1 < f0) + { + // Repeat on [tm,t1]. + GetMinimum(tm, fm, t1, f1, level); + } + else + { + // Repeat on [t0,tm] and [tm,t1]. + GetMinimum(t0, f0, tm, fm, level); + GetMinimum(tm, fm, t1, f1, level); + } + } + } + + // This is called when {f0,f1,f2} brackets a minimum. + void GetBracketedMinimum(Real t0, Real f0, Real tm, Real fm, Real t1, Real f1, int level) + { + for (int i = 0; i < mMaxBracket; ++i) + { + // Update minimum value. + if (fm < mFMin) + { + mTMin = tm; + mFMin = fm; + } + + // Test for convergence. + if (std::fabs(t1 - t0) <= (Real)2 * mTolerance * std::fabs(tm) + mEpsilon) + { + break; + } + + // Compute vertex of interpolating parabola. + Real dt0 = t0 - tm; + Real dt1 = t1 - tm; + Real df0 = f0 - fm; + Real df1 = f1 - fm; + Real tmp0 = dt0 * df1; + Real tmp1 = dt1 * df0; + Real denom = tmp1 - tmp0; + if (std::fabs(denom) <= mEpsilon) + { + return; + } + + // Compute tv and clamp to [t0,t1] to offset floating-point + // rounding errors. + Real tv = tm + (Real)0.5 * (dt1 * tmp1 - dt0 * tmp0) / denom; + tv = std::max(t0, std::min(tv, t1)); + Real fv = mFunction(tv); + if (fv < mFMin) + { + mTMin = tv; + mFMin = fv; + } + + if (tv < tm) + { + if (fv < fm) + { + t1 = tm; + f1 = fm; + tm = tv; + fm = fv; + } + else + { + t0 = tv; + f0 = fv; + } + } + else if (tv > tm) + { + if (fv < fm) + { + t0 = tm; + f0 = fm; + tm = tv; + fm = fv; + } + else + { + t1 = tv; + f1 = fv; + } + } + else + { + // The vertex of parabola is already at middle sample point. + GetMinimum(t0, f0, tm, fm, level); + GetMinimum(tm, fm, t1, f1, level); + } + } + } + + std::function mFunction; + int mMaxLevel; + int mMaxBracket; + Real mTMin, mFMin; + Real mEpsilon, mTolerance; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MinimizeN.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MinimizeN.h new file mode 100644 index 0000000..f67785d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MinimizeN.h @@ -0,0 +1,200 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The Cartesian-product domain provided to GetMinimum(*) has minimum values +// stored in t0[0..d-1] and maximum values stored in t1[0..d-1], where d is +// 'dimensions'. The domain is searched along lines through the current +// estimate of the minimum location. Each such line is searched for a minimum +// using a Minimize1 object. This is called "Powell's Direction Set +// Method". The parameters 'maxLevel' and 'maxBracket' are used by +// Minimize1, so read the documentation for that class (in its header +// file) to understand what these mean. The input 'maxIterations' is the +// number of iterations for the direction-set method. + +namespace WwiseGTE +{ + template + class MinimizeN + { + public: + // Construction. + MinimizeN(int dimensions, std::function const& F, + int maxLevel, int maxBracket, int maxIterations, Real epsilon = (Real)1e-06) + : + mDimensions(dimensions), + mFunction(F), + mMaxIterations(maxIterations), + mEpsilon(0), + mDirections(dimensions + 1), + mDConjIndex(dimensions), + mDCurrIndex(0), + mTCurr(dimensions), + mTSave(dimensions), + mMinimizer([this](Real t){ return mFunction(&(mTCurr + t * mDirections[mDCurrIndex])[0]); }, maxLevel, maxBracket) + { + SetEpsilon(epsilon); + for (auto& direction : mDirections) + { + direction.SetSize(dimensions); + } + } + + // Member access. + inline void SetEpsilon(Real epsilon) + { + mEpsilon = (epsilon > (Real)0 ? epsilon : (Real)0); + } + + inline Real GetEpsilon() const + { + return mEpsilon; + } + + // Find the minimum on the Cartesian-product domain whose minimum + // values are stored in t0[0..d-1] and whose maximum values are stored + // in t1[0..d-1], where d is 'dimensions'. An initial guess is + // specified in tInitial[0..d-1]. The location of the minimum is + // tMin[0..d-1] and the value of the minimum is 'fMin'. + void GetMinimum(Real const* t0, Real const* t1, Real const* tInitial, Real* tMin, Real& fMin) + { + // The initial guess. + size_t numBytes = mDimensions * sizeof(Real); + mFCurr = mFunction(tInitial); + std::memcpy(&mTSave[0], tInitial, numBytes); + std::memcpy(&mTCurr[0], tInitial, numBytes); + + // Initialize the direction set to the standard Euclidean basis. + for (int i = 0; i < mDimensions; ++i) + { + mDirections[i].MakeUnit(i); + } + + Real ell0, ell1, ellMin; + for (int iter = 0; iter < mMaxIterations; ++iter) + { + // Find minimum in each direction and update current location. + for (int i = 0; i < mDimensions; ++i) + { + mDCurrIndex = i; + ComputeDomain(t0, t1, ell0, ell1); + mMinimizer.GetMinimum(ell0, ell1, (Real)0, ellMin, mFCurr); + mTCurr += ellMin * mDirections[i]; + } + + // Estimate a unit-length conjugate direction. + mDirections[mDConjIndex] = mTCurr - mTSave; + Real length = Length(mDirections[mDConjIndex]); + if (length <= mEpsilon) + { + // New position did not change significantly from old one. + // Should there be a better convergence criterion here? + break; + } + + mDirections[mDConjIndex] /= length; + + // Minimize in conjugate direction. + mDCurrIndex = mDConjIndex; + ComputeDomain(t0, t1, ell0, ell1); + mMinimizer.GetMinimum(ell0, ell1, (Real)0, ellMin, mFCurr); + mTCurr += ellMin * mDirections[mDCurrIndex]; + + // Cycle the directions and add conjugate direction to set. + mDConjIndex = 0; + for (int i = 0; i < mDimensions; ++i) + { + mDirections[i] = mDirections[i + 1]; + } + + // Set parameters for next pass. + mTSave = mTCurr; + } + + std::memcpy(tMin, &mTCurr[0], numBytes); + fMin = mFCurr; + } + + private: + // The current estimate of the minimum location is mTCurr[0..d-1]. The + // direction of the current line to search is mDCurr[0..d-1]. This + // line must be clipped against the Cartesian-product domain, a + // process implemented in this function. If the line is + // mTCurr+s*mDCurr, the clip result is the s-interval [ell0,ell1]. + void ComputeDomain(Real const* t0, Real const* t1, Real& ell0, Real& ell1) + { + ell0 = -std::numeric_limits::max(); + ell1 = +std::numeric_limits::max(); + + for (int i = 0; i < mDimensions; ++i) + { + Real value = mDirections[mDCurrIndex][i]; + if (value != (Real)0) + { + Real b0 = t0[i] - mTCurr[i]; + Real b1 = t1[i] - mTCurr[i]; + Real inv = ((Real)1) / value; + if (value > (Real)0) + { + // The valid t-interval is [b0,b1]. + b0 *= inv; + if (b0 > ell0) + { + ell0 = b0; + } + b1 *= inv; + if (b1 < ell1) + { + ell1 = b1; + } + } + else + { + // The valid t-interval is [b1,b0]. + b0 *= inv; + if (b0 < ell1) + { + ell1 = b0; + } + b1 *= inv; + if (b1 > ell0) + { + ell0 = b1; + } + } + } + } + + // Correction if numerical errors lead to values nearly zero. + if (ell0 > (Real)0) + { + ell0 = (Real)0; + } + if (ell1 < (Real)0) + { + ell1 = (Real)0; + } + } + + int mDimensions; + std::function mFunction; + int mMaxIterations; + Real mEpsilon; + std::vector> mDirections; + int mDConjIndex; + int mDCurrIndex; + GVector mTCurr; + GVector mTSave; + Real mFCurr; + Minimize1 mMinimizer; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MinimumAreaBox2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MinimumAreaBox2.h new file mode 100644 index 0000000..2277424 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MinimumAreaBox2.h @@ -0,0 +1,649 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// Compute a minimum-area oriented box containing the specified points. The +// algorithm uses the rotating calipers method. +// http://www-cgrl.cs.mcgill.ca/~godfried/research/calipers.html +// http://cgm.cs.mcgill.ca/~orm/rotcal.html +// The box is supported by the convex hull of the points, so the algorithm +// is really about computing the minimum-area box containing a convex polygon. +// The rotating calipers approach is O(n) in time for n polygon edges. +// +// A detailed description of the algorithm and implementation is found in +// https://www.geometrictools.com/Documentation/MinimumAreaRectangle.pdf +// +// NOTE: This algorithm guarantees a correct output only when ComputeType is +// an exact arithmetic type that supports division. In GTEngine, one such +// type is BSRational (arbitrary precision). Another such type +// is BSRational> (fixed precision), where N is chosen large +// enough for your input data sets. If you choose ComputeType to be 'float' +// or 'double', the output is not guaranteed to be correct. +// +// See GeometricTools/GTEngine/Samples/Geometrics/MinimumAreaBox2 for an +// example of how to use the code. + +namespace WwiseGTE +{ + template + class MinimumAreaBox2 + { + public: + // The class is a functor to support computing the minimum-area box of + // multiple data sets using the same class object. + MinimumAreaBox2() + : + mNumPoints(0), + mPoints(nullptr), + mSupportIndices{ 0, 0, 0, 0 }, + mArea((InputType)0), + mZero(0), + mOne(1), + mNegOne(-1), + mHalf((InputType)0.5) + { + } + + // The points are arbitrary, so we must compute the convex hull from + // them in order to compute the minimum-area box. The input + // parameters are necessary for using ConvexHull2. NOTE: ConvexHull2 + // guarantees that the hull does not have three consecutive collinear + // points. + OrientedBox2 operator()(int numPoints, + Vector2 const* points, + bool useRotatingCalipers = !std::is_floating_point::value) + { + mNumPoints = numPoints; + mPoints = points; + mHull.clear(); + + // Get the convex hull of the points. + ConvexHull2 ch2; + ch2(mNumPoints, mPoints, (InputType)0); + int dimension = ch2.GetDimension(); + + OrientedBox2 minBox; + + if (dimension == 0) + { + // The points are all effectively the same (using fuzzy + // epsilon). + minBox.center = mPoints[0]; + minBox.axis[0] = Vector2::Unit(0); + minBox.axis[1] = Vector2::Unit(1); + minBox.extent[0] = (InputType)0; + minBox.extent[1] = (InputType)0; + mHull.resize(1); + mHull[0] = 0; + return minBox; + } + + if (dimension == 1) + { + // The points effectively lie on a line (using fuzzy epsilon). + // Determine the extreme t-values for the points represented + // as P = origin + t*direction. We know that 'origin' is an + // input vertex, so we can start both t-extremes at zero. + Line2 const& line = ch2.GetLine(); + InputType tmin = (InputType)0, tmax = (InputType)0; + int imin = 0, imax = 0; + for (int i = 0; i < mNumPoints; ++i) + { + Vector2 diff = mPoints[i] - line.origin; + InputType t = Dot(diff, line.direction); + if (t > tmax) + { + tmax = t; + imax = i; + } + else if (t < tmin) + { + tmin = t; + imin = i; + } + } + + minBox.center = line.origin + (InputType)0.5 * (tmin + tmax) * line.direction; + minBox.extent[0] = (InputType)0.5 * (tmax - tmin); + minBox.extent[1] = (InputType)0; + minBox.axis[0] = line.direction; + minBox.axis[1] = -Perp(line.direction); + mHull.resize(2); + mHull[0] = imin; + mHull[1] = imax; + return minBox; + } + + mHull = ch2.GetHull(); + Vector2 const* queryPoints = ch2.GetQuery().GetVertices(); + std::vector> computePoints(mHull.size()); + for (size_t i = 0; i < mHull.size(); ++i) + { + computePoints[i] = queryPoints[mHull[i]]; + } + + RemoveCollinearPoints(computePoints); + + Box box; + if (useRotatingCalipers) + { + box = ComputeBoxForEdgeOrderN(computePoints); + } + else + { + box = ComputeBoxForEdgeOrderNSqr(computePoints); + } + + ConvertTo(box, computePoints, minBox); + return minBox; + } + + // The points already form a counterclockwise, nondegenerate convex + // polygon. If the points directly are the convex polygon, set + // numIndices to 0 and indices to nullptr. If the polygon vertices + // are a subset of the incoming points, that subset is identified by + // numIndices >= 3 and indices having numIndices elements. + OrientedBox2 operator()(int numPoints, + Vector2 const* points, int numIndices, int const* indices, + bool useRotatingCalipers = !std::is_floating_point::value) + { + mHull.clear(); + + OrientedBox2 minBox; + + if (numPoints < 3 || !points || (indices && numIndices < 3)) + { + minBox.center = Vector2::Zero(); + minBox.axis[0] = Vector2::Unit(0); + minBox.axis[1] = Vector2::Unit(1); + minBox.extent = Vector2::Zero(); + return minBox; + } + + if (indices) + { + mHull.resize(numIndices); + std::copy(indices, indices + numIndices, mHull.begin()); + } + else + { + numIndices = numPoints; + mHull.resize(numIndices); + for (int i = 0; i < numIndices; ++i) + { + mHull[i] = i; + } + } + + std::vector> computePoints(numIndices); + for (int i = 0; i < numIndices; ++i) + { + int h = mHull[i]; + computePoints[i][0] = (ComputeType)points[h][0]; + computePoints[i][1] = (ComputeType)points[h][1]; + } + + RemoveCollinearPoints(computePoints); + + Box box; + if (useRotatingCalipers) + { + box = ComputeBoxForEdgeOrderN(computePoints); + } + else + { + box = ComputeBoxForEdgeOrderNSqr(computePoints); + } + + ConvertTo(box, computePoints, minBox); + return minBox; + } + + // Member access. + inline int GetNumPoints() const + { + return mNumPoints; + } + + inline Vector2 const* GetPoints() const + { + return mPoints; + } + + inline std::vector const& GetHull() const + { + return mHull; + } + + inline std::array const& GetSupportIndices() const + { + return mSupportIndices; + } + + inline InputType GetArea() const + { + return mArea; + } + + private: + // The box axes are U[i] and are usually not unit-length in order to + // allow exact arithmetic. The box is supported by mPoints[index[i]], + // where i is one of the enumerations above. The box axes are not + // necessarily unit length, but they have the same length. They need + // to be normalized for conversion back to InputType. + struct Box + { + Vector2 U[2]; + std::array index; // order: bottom, right, top, left + ComputeType sqrLenU0, area; + }; + + // The rotating calipers algorithm has a loop invariant that requires + // the convex polygon not to have collinear points. Any such points + // must be removed first. The code is also executed for the O(n^2) + // algorithm to reduce the number of process edges. + void RemoveCollinearPoints(std::vector>& vertices) + { + std::vector> tmpVertices = vertices; + + int const numVertices = static_cast(vertices.size()); + int numNoncollinear = 0; + Vector2 ePrev = tmpVertices[0] - tmpVertices.back(); + for (int i0 = 0, i1 = 1; i0 < numVertices; ++i0) + { + Vector2 eNext = tmpVertices[i1] - tmpVertices[i0]; + + ComputeType dp = DotPerp(ePrev, eNext); + if (dp != mZero) + { + vertices[numNoncollinear++] = tmpVertices[i0]; + } + + ePrev = eNext; + if (++i1 == numVertices) + { + i1 = 0; + } + } + + vertices.resize(numNoncollinear); + } + + // This is the slow O(n^2) search. + Box ComputeBoxForEdgeOrderNSqr(std::vector> const& vertices) + { + Box minBox; + minBox.area = mNegOne; + int const numIndices = static_cast(vertices.size()); + for (int i0 = numIndices - 1, i1 = 0; i1 < numIndices; i0 = i1++) + { + Box box = SmallestBox(i0, i1, vertices); + if (minBox.area == mNegOne || box.area < minBox.area) + { + minBox = box; + } + } + return minBox; + } + + // The fast O(n) search. + Box ComputeBoxForEdgeOrderN(std::vector> const& vertices) + { + // The inputs are assumed to be the vertices of a convex polygon + // that is counterclockwise ordered. The input points must not + // contain three consecutive collinear points. + + // When the bounding box corresponding to a polygon edge is + // computed, we mark the edge as visited. If the edge is + // encountered later, the algorithm terminates. + std::vector visited(vertices.size()); + std::fill(visited.begin(), visited.end(), false); + + // Start the minimum-area rectangle search with the edge from the + // last polygon vertex to the first. When updating the extremes, + // we want the bottom-most point on the left edge, the top-most + // point on the right edge, the left-most point on the top edge, + // and the right-most point on the bottom edge. The polygon edges + // starting at these points are then guaranteed not to coincide + // with a box edge except when an extreme point is shared by two + // box edges (at a corner). + Box minBox = SmallestBox((int)vertices.size() - 1, 0, vertices); + visited[minBox.index[0]] = true; + + // Execute the rotating calipers algorithm. + Box box = minBox; + for (size_t i = 0; i < vertices.size(); ++i) + { + std::array, 4> A; + int numA; + if (!ComputeAngles(vertices, box, A, numA)) + { + // The polygon is a rectangle, so the search is over. + break; + } + + // Indirectly sort the A-array. + std::array sort = SortAngles(A, numA); + + // Update the supporting indices (box.index[]) and the box + // axis directions (box.U[]). + if (!UpdateSupport(A, numA, sort, vertices, visited, box)) + { + // We have already processed the box polygon edge, so the + // search is over. + break; + } + + if (box.area < minBox.area) + { + minBox = box; + } + } + + return minBox; + } + + // Compute the smallest box for the polygon edge . + Box SmallestBox(int i0, int i1, std::vector> const& vertices) + { + Box box; + box.U[0] = vertices[i1] - vertices[i0]; + box.U[1] = -Perp(box.U[0]); + box.index = { i1, i1, i1, i1 }; + box.sqrLenU0 = Dot(box.U[0], box.U[0]); + + Vector2 const& origin = vertices[i1]; + Vector2 support[4]; + for (int j = 0; j < 4; ++j) + { + support[j] = { mZero, mZero }; + } + + int i = 0; + for (auto const& vertex : vertices) + { + Vector2 diff = vertex - origin; + Vector2 v = { Dot(box.U[0], diff), Dot(box.U[1], diff) }; + + // The right-most vertex of the bottom edge is vertices[i1]. + // The assumption of no triple of collinear vertices + // guarantees that box.index[0] is i1, which is the initial + // value assigned at the beginning of this function. + // Therefore, there is no need to test for other vertices + // farther to the right than vertices[i1]. + + if (v[0] > support[1][0] || + (v[0] == support[1][0] && v[1] > support[1][1])) + { + // New right maximum OR same right maximum but closer + // to top. + box.index[1] = i; + support[1] = v; + } + + if (v[1] > support[2][1] || + (v[1] == support[2][1] && v[0] < support[2][0])) + { + // New top maximum OR same top maximum but closer + // to left. + box.index[2] = i; + support[2] = v; + } + + if (v[0] < support[3][0] || + (v[0] == support[3][0] && v[1] < support[3][1])) + { + // New left minimum OR same left minimum but closer + // to bottom. + box.index[3] = i; + support[3] = v; + } + + ++i; + } + + // The comment in the loop has the implication that + // support[0] = { 0, 0 }, so the scaled height + // (support[2][1] - support[0][1]) is simply support[2][1]. + ComputeType scaledWidth = support[1][0] - support[3][0]; + ComputeType scaledHeight = support[2][1]; + box.area = scaledWidth * scaledHeight / box.sqrLenU0; + return box; + } + + // Compute (sin(angle))^2 for the polygon edges emanating from the + // support vertices of the box. The return value is 'true' if at + // least one angle is in [0,pi/2); otherwise, the return value is + // 'false' and the original polygon must be a rectangle. + bool ComputeAngles(std::vector> const& vertices, + Box const& box, std::array, 4>& A, int& numA) const + { + int const numVertices = static_cast(vertices.size()); + numA = 0; + for (int k0 = 3, k1 = 0; k1 < 4; k0 = k1++) + { + if (box.index[k0] != box.index[k1]) + { + // The box edges are ordered in k1 as U[0], U[1], + // -U[0], -U[1]. + Vector2 D = ((k0 & 2) ? -box.U[k0 & 1] : box.U[k0 & 1]); + int j0 = box.index[k0], j1 = j0 + 1; + if (j1 == numVertices) + { + j1 = 0; + } + Vector2 E = vertices[j1] - vertices[j0]; + ComputeType dp = DotPerp(D, E); + ComputeType esqrlen = Dot(E, E); + ComputeType sinThetaSqr = (dp * dp) / esqrlen; + A[numA++] = std::make_pair(sinThetaSqr, k0); + } + } + return numA > 0; + } + + // Sort the angles indirectly. The sorted indices are returned. + // This avoids swapping elements of A[], which can be expensive when + // ComputeType is an exact rational type. + std::array SortAngles(std::array, 4> const& A, int numA) const + { + std::array sort = { 0, 1, 2, 3 }; + if (numA > 1) + { + if (numA == 2) + { + if (A[sort[0]].first > A[sort[1]].first) + { + std::swap(sort[0], sort[1]); + } + } + else if (numA == 3) + { + if (A[sort[0]].first > A[sort[1]].first) + { + std::swap(sort[0], sort[1]); + } + if (A[sort[0]].first > A[sort[2]].first) + { + std::swap(sort[0], sort[2]); + } + if (A[sort[1]].first > A[sort[2]].first) + { + std::swap(sort[1], sort[2]); + } + } + else // numA == 4 + { + if (A[sort[0]].first > A[sort[1]].first) + { + std::swap(sort[0], sort[1]); + } + if (A[sort[2]].first > A[sort[3]].first) + { + std::swap(sort[2], sort[3]); + } + if (A[sort[0]].first > A[sort[2]].first) + { + std::swap(sort[0], sort[2]); + } + if (A[sort[1]].first > A[sort[3]].first) + { + std::swap(sort[1], sort[3]); + } + if (A[sort[1]].first > A[sort[2]].first) + { + std::swap(sort[1], sort[2]); + } + } + } + return sort; + } + + bool UpdateSupport(std::array, 4> const& A, + int numA, std::array const& sort, + std::vector> const& vertices, + std::vector& visited, Box& box) + { + // Replace the support vertices of those edges attaining minimum + // angle with the other endpoints of the edges. + int const numVertices = static_cast(vertices.size()); + auto const& amin = A[sort[0]]; + for (int k = 0; k < numA; ++k) + { + auto const& a = A[sort[k]]; + if (a.first == amin.first) + { + if (++box.index[a.second] == numVertices) + { + box.index[a.second] = 0; + } + } + else + { + break; + } + } + + int bottom = box.index[amin.second]; + if (visited[bottom]) + { + // We have already processed this polygon edge. + return false; + } + visited[bottom] = true; + + // Cycle the vertices so that the bottom support occurs first. + std::array nextIndex; + for (int k = 0; k < 4; ++k) + { + nextIndex[k] = box.index[(amin.second + k) % 4]; + } + box.index = nextIndex; + + // Compute the box axis directions. + int j1 = box.index[0], j0 = j1 - 1; + if (j0 < 0) + { + j0 = numVertices - 1; + } + box.U[0] = vertices[j1] - vertices[j0]; + box.U[1] = -Perp(box.U[0]); + box.sqrLenU0 = Dot(box.U[0], box.U[0]); + + // Compute the box area. + Vector2 diff[2] = + { + vertices[box.index[1]] - vertices[box.index[3]], + vertices[box.index[2]] - vertices[box.index[0]] + }; + box.area = Dot(box.U[0], diff[0]) * Dot(box.U[1], diff[1]) / box.sqrLenU0; + return true; + } + + // Convert the ComputeType box to the InputType box. When the + // ComputeType is an exact rational type, the conversions are + // performed to avoid precision loss until necessary at the last step. + void ConvertTo(Box const& minBox, + std::vector> const& computePoints, + OrientedBox2& itMinBox) + { + // The sum, difference, and center are all computed exactly. + Vector2 sum[2] = + { + computePoints[minBox.index[1]] + computePoints[minBox.index[3]], + computePoints[minBox.index[2]] + computePoints[minBox.index[0]] + }; + + Vector2 difference[2] = + { + computePoints[minBox.index[1]] - computePoints[minBox.index[3]], + computePoints[minBox.index[2]] - computePoints[minBox.index[0]] + }; + + Vector2 center = mHalf * ( + Dot(minBox.U[0], sum[0]) * minBox.U[0] + + Dot(minBox.U[1], sum[1]) * minBox.U[1]) / minBox.sqrLenU0; + + // Calculate the squared extent using ComputeType to avoid loss of + // precision before computing a squared root. + Vector2 sqrExtent; + for (int i = 0; i < 2; ++i) + { + sqrExtent[i] = mHalf * Dot(minBox.U[i], difference[i]); + sqrExtent[i] *= sqrExtent[i]; + sqrExtent[i] /= minBox.sqrLenU0; + } + + for (int i = 0; i < 2; ++i) + { + itMinBox.center[i] = (InputType)center[i]; + itMinBox.extent[i] = std::sqrt((InputType)sqrExtent[i]); + + // Before converting to floating-point, factor out the maximum + // component using ComputeType to generate rational numbers in + // a range that avoids loss of precision during the conversion + // and normalization. + Vector2 const& axis = minBox.U[i]; + ComputeType cmax = std::max(std::fabs(axis[0]), std::fabs(axis[1])); + ComputeType invCMax = mOne / cmax; + for (int j = 0; j < 2; ++j) + { + itMinBox.axis[i][j] = (InputType)(axis[j] * invCMax); + } + Normalize(itMinBox.axis[i]); + } + + mSupportIndices = minBox.index; + mArea = (InputType)minBox.area; + } + + // The input points to be bound. + int mNumPoints; + Vector2 const* mPoints; + + // The indices into mPoints/mComputePoints for the convex hull + // vertices. + std::vector mHull; + + // The support indices for the minimum-area box. + std::array mSupportIndices; + + // The area of the minimum-area box. The ComputeType value is + // exact, so the only rounding errors occur in the conversion from + // ComputeType to InputType (default rounding mode is + // round-to-nearest-ties-to-even). + InputType mArea; + + // Convenient values that occur regularly in the code. When using + // rational ComputeType, we construct these numbers only once. + ComputeType mZero, mOne, mNegOne, mHalf; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MinimumAreaCircle2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MinimumAreaCircle2.h new file mode 100644 index 0000000..59eb05d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MinimumAreaCircle2.h @@ -0,0 +1,443 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include + +// Compute the minimum area circle containing the input set of points. The +// algorithm randomly permutes the input points so that the construction +// occurs in 'expected' O(N) time. All internal minimal circle calculations +// store the squared radius in the radius member of Circle2. Only at the +// end is a sqrt computed. +// +// The most robust choice for ComputeType is BSRational for exact rational +// arithmetic. As long as this code is a correct implementation of the theory +// (which I hope it is), you will obtain the minimum-area circle containing +// the points. +// +// Instead, if you choose ComputeType to be float or double, floating-point +// rounding errors can cause the UpdateSupport{2,3} functions to fail. +// The failure is trapped in those functions and a simple bounding circle is +// computed using GetContainer in file GteContCircle2.h. This circle is +// generally not the minimum-area circle containing the points. The +// minimum-area algorithm is terminated immediately. The circle is returned +// as well as a bool value of 'true' when the circle is minimum area or +// 'false' when the failure is trapped. When 'false' is returned, you can +// try another call to the operator()(...) function. The random shuffle +// that occurs is highly likely to be different from the previous shuffle, +// and there is a chance that the algorithm can succeed just because of the +// different ordering of points. + +namespace WwiseGTE +{ + template + class MinimumAreaCircle2 + { + public: + bool operator()(int numPoints, Vector2 const* points, Circle2& minimal) + { + if (numPoints >= 1 && points) + { + // Function array to avoid switch statement in the main loop. + std::function update[4]; + update[1] = [this](int i) { return UpdateSupport1(i); }; + update[2] = [this](int i) { return UpdateSupport2(i); }; + update[3] = [this](int i) { return UpdateSupport3(i); }; + + // Process only the unique points. + std::vector permuted(numPoints); + for (int i = 0; i < numPoints; ++i) + { + permuted[i] = i; + } + std::sort(permuted.begin(), permuted.end(), + [points](int i0, int i1) { return points[i0] < points[i1]; }); + auto end = std::unique(permuted.begin(), permuted.end(), + [points](int i0, int i1) { return points[i0] == points[i1]; }); + permuted.erase(end, permuted.end()); + numPoints = static_cast(permuted.size()); + + // Create a random permutation of the points. + std::shuffle(permuted.begin(), permuted.end(), mDRE); + + // Convert to the compute type, which is a simple copy when + // ComputeType is the same as InputType. + mComputePoints.resize(numPoints); + for (int i = 0; i < numPoints; ++i) + { + for (int j = 0; j < 2; ++j) + { + mComputePoints[i][j] = points[permuted[i]][j]; + } + } + + // Start with the first point. + Circle2 ctMinimal = ExactCircle1(0); + mNumSupport = 1; + mSupport[0] = 0; + + // The loop restarts from the beginning of the point list each + // time the circle needs updating. Linus Källberg (Computer + // Science at Mälardalen University in Sweden) discovered that + // performance is better when the remaining points in the + // array are processed before restarting. The points + // processed before the point that caused the update are + // likely to be enclosed by the new circle (or near the circle + // boundary) because they were enclosed by the previous + // circle. The chances are better that points after the + // current one will cause growth of the bounding circle. + for (int i = 1 % numPoints, n = 0; i != n; i = (i + 1) % numPoints) + { + if (!SupportContains(i)) + { + if (!Contains(i, ctMinimal)) + { + auto result = update[mNumSupport](i); + if (result.second == true) + { + if (result.first.radius > ctMinimal.radius) + { + ctMinimal = result.first; + n = i; + } + } + else + { + // This case can happen when ComputeType is + // float or double. See the comments at the + // beginning of this file. ComputeType is not + // exact and failure occurred. Returning + // non-minimal circle. TODO: Should we throw + // an exception? + GetContainer(numPoints, points, minimal); + mNumSupport = 0; + mSupport.fill(0); + return false; + } + } + } + } + + for (int j = 0; j < 2; ++j) + { + minimal.center[j] = static_cast(ctMinimal.center[j]); + } + minimal.radius = static_cast(ctMinimal.radius); + minimal.radius = std::sqrt(minimal.radius); + + for (int i = 0; i < mNumSupport; ++i) + { + mSupport[i] = permuted[mSupport[i]]; + } + return true; + } + else + { + LogError("Input must contain points."); + } + } + + // Member access. + inline int GetNumSupport() const + { + return mNumSupport; + } + + inline std::array const& GetSupport() const + { + return mSupport; + } + + private: + // Test whether point P is inside circle C using squared distance and + // squared radius. + bool Contains(int i, Circle2 const& circle) const + { + // NOTE: In this algorithm, circle.radius is the *squared radius* + // until the function returns at which time a square root is + // applied. + Vector2 diff = mComputePoints[i] - circle.center; + return Dot(diff, diff) <= circle.radius; + } + + Circle2 ExactCircle1(int i0) const + { + Circle2 minimal; + minimal.center = mComputePoints[i0]; + minimal.radius = (ComputeType)0; + return minimal; + } + + Circle2 ExactCircle2(int i0, int i1) const + { + Vector2 const& P0 = mComputePoints[i0]; + Vector2 const& P1 = mComputePoints[i1]; + Vector2 diff = P1 - P0; + Circle2 minimal; + minimal.center = ((ComputeType)0.5)*(P0 + P1); + minimal.radius = ((ComputeType)0.25)*Dot(diff, diff); + return minimal; + } + + Circle2 ExactCircle3(int i0, int i1, int i2) const + { + // Compute the 2D circle containing P0, P1, and P2. The center in + // barycentric coordinates is C = x0*P0 + x1*P1 + x2*P2, where + // x0 + x1 + x2 = 1. The center is equidistant from the three + // points, so |C - P0| = |C - P1| = |C - P2| = R, where R is the + // radius of the circle. From these conditions, + // C - P0 = x0*E0 + x1*E1 - E0 + // C - P1 = x0*E0 + x1*E1 - E1 + // C - P2 = x0*E0 + x1*E1 + // where E0 = P0 - P2 and E1 = P1 - P2, which leads to + // r^2 = |x0*E0 + x1*E1|^2 - 2*Dot(E0, x0*E0 + x1*E1) + |E0|^2 + // r^2 = |x0*E0 + x1*E1|^2 - 2*Dot(E1, x0*E0 + x1*E1) + |E1|^2 + // r^2 = |x0*E0 + x1*E1|^2 + // Subtracting the last equation from the first two and writing + // the equations as a linear system, + // + // +- -++ -+ +- -+ + // | Dot(E0,E0) Dot(E0,E1) || x0 | = 0.5 | Dot(E0,E0) | + // | Dot(E1,E0) Dot(E1,E1) || x1 | | Dot(E1,E1) | + // +- -++ -+ +- -+ + // + // The following code solves this system for x0 and x1 and then + // evaluates the third equation in r^2 to obtain r. + + Vector2 const& P0 = mComputePoints[i0]; + Vector2 const& P1 = mComputePoints[i1]; + Vector2 const& P2 = mComputePoints[i2]; + + Vector2 E0 = P0 - P2; + Vector2 E1 = P1 - P2; + + Matrix2x2 A; + A(0, 0) = Dot(E0, E0); + A(0, 1) = Dot(E0, E1); + A(1, 0) = A(0, 1); + A(1, 1) = Dot(E1, E1); + + ComputeType const half = (ComputeType)0.5; + Vector2 B{ half * A(0, 0), half* A(1, 1) }; + + Circle2 minimal; + Vector2 X; + if (LinearSystem::Solve(A, B, X)) + { + ComputeType x2 = (ComputeType)1 - X[0] - X[1]; + minimal.center = X[0] * P0 + X[1] * P1 + x2 * P2; + Vector2 tmp = X[0] * E0 + X[1] * E1; + minimal.radius = Dot(tmp, tmp); + } + else + { + minimal.center = Vector2::Zero(); + minimal.radius = (ComputeType)std::numeric_limits::max(); + } + + return minimal; + } + + typedef std::pair, bool> UpdateResult; + + UpdateResult UpdateSupport1(int i) + { + Circle2 minimal = ExactCircle2(mSupport[0], i); + mNumSupport = 2; + mSupport[1] = i; + return std::make_pair(minimal, true); + } + + UpdateResult UpdateSupport2(int i) + { + // Permutations of type 2, used for calling ExactCircle2(...). + int const numType2 = 2; + int const type2[numType2][2] = + { + { 0, /*2*/ 1 }, + { 1, /*2*/ 0 } + }; + + // Permutations of type 3, used for calling ExactCircle3(...). + int const numType3 = 1; // {0, 1, 2} + + Circle2 circle[numType2 + numType3]; + ComputeType minRSqr = (ComputeType)std::numeric_limits::max(); + int iCircle = 0, iMinRSqr = -1; + int k0, k1; + + // Permutations of type 2. + for (int j = 0; j < numType2; ++j, ++iCircle) + { + k0 = mSupport[type2[j][0]]; + circle[iCircle] = ExactCircle2(k0, i); + if (circle[iCircle].radius < minRSqr) + { + k1 = mSupport[type2[j][1]]; + if (Contains(k1, circle[iCircle])) + { + minRSqr = circle[iCircle].radius; + iMinRSqr = iCircle; + } + } + } + + // Permutations of type 3. + k0 = mSupport[0]; + k1 = mSupport[1]; + circle[iCircle] = ExactCircle3(k0, k1, i); + if (circle[iCircle].radius < minRSqr) + { + minRSqr = circle[iCircle].radius; + iMinRSqr = iCircle; + } + + switch (iMinRSqr) + { + case 0: + mSupport[1] = i; + break; + case 1: + mSupport[0] = i; + break; + case 2: + mNumSupport = 3; + mSupport[2] = i; + break; + case -1: + // For exact arithmetic, iMinRSqr >= 0, but for floating-point + // arithmetic, round-off errors can lead to iMinRSqr == -1. + // When this happens, use a simple bounding circle for the + // result and terminate the minimum-area algorithm. + return std::make_pair(Circle2(), false); + } + + return std::make_pair(circle[iMinRSqr], true); + } + + UpdateResult UpdateSupport3(int i) + { + // Permutations of type 2, used for calling ExactCircle2(...). + int const numType2 = 3; + int const type2[numType2][3] = + { + { 0, /*3*/ 1, 2 }, + { 1, /*3*/ 0, 2 }, + { 2, /*3*/ 0, 1 } + }; + + // Permutations of type 2, used for calling ExactCircle3(...). + int const numType3 = 3; + int const type3[numType3][3] = + { + { 0, 1, /*3*/ 2 }, + { 0, 2, /*3*/ 1 }, + { 1, 2, /*3*/ 0 } + }; + + Circle2 circle[numType2 + numType3]; + ComputeType minRSqr = (ComputeType)std::numeric_limits::max(); + int iCircle = 0, iMinRSqr = -1; + int k0, k1, k2; + + // Permutations of type 2. + for (int j = 0; j < numType2; ++j, ++iCircle) + { + k0 = mSupport[type2[j][0]]; + circle[iCircle] = ExactCircle2(k0, i); + if (circle[iCircle].radius < minRSqr) + { + k1 = mSupport[type2[j][1]]; + k2 = mSupport[type2[j][2]]; + if (Contains(k1, circle[iCircle]) && Contains(k2, circle[iCircle])) + { + minRSqr = circle[iCircle].radius; + iMinRSqr = iCircle; + } + } + } + + // Permutations of type 3. + for (int j = 0; j < numType3; ++j, ++iCircle) + { + k0 = mSupport[type3[j][0]]; + k1 = mSupport[type3[j][1]]; + circle[iCircle] = ExactCircle3(k0, k1, i); + if (circle[iCircle].radius < minRSqr) + { + k2 = mSupport[type3[j][2]]; + if (Contains(k2, circle[iCircle])) + { + minRSqr = circle[iCircle].radius; + iMinRSqr = iCircle; + } + } + } + + switch (iMinRSqr) + { + case 0: + mNumSupport = 2; + mSupport[1] = i; + break; + case 1: + mNumSupport = 2; + mSupport[0] = i; + break; + case 2: + mNumSupport = 2; + mSupport[0] = mSupport[2]; + mSupport[1] = i; + break; + case 3: + mSupport[2] = i; + break; + case 4: + mSupport[1] = i; + break; + case 5: + mSupport[0] = i; + break; + case -1: + // For exact arithmetic, iMinRSqr >= 0, but for floating-point + // arithmetic, round-off errors can lead to iMinRSqr == -1. + // When this happens, use a simple bounding circle for the + // result and terminate the minimum-area algorithm. + return std::make_pair(Circle2(), false); + } + + return std::make_pair(circle[iMinRSqr], true); + } + + // Indices of points that support the current minimum area circle. + bool SupportContains(int j) const + { + for (int i = 0; i < mNumSupport; ++i) + { + if (j == mSupport[i]) + { + return true; + } + } + return false; + } + + int mNumSupport; + std::array mSupport; + + // Random permutation of the unique input points to produce expected + // linear time for the algorithm. + std::default_random_engine mDRE; + std::vector> mComputePoints; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MinimumVolumeBox3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MinimumVolumeBox3.h new file mode 100644 index 0000000..ce07575 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MinimumVolumeBox3.h @@ -0,0 +1,1195 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// Compute a minimum-volume oriented box containing the specified points. The +// algorithm is really about computing the minimum-volume box containing the +// convex hull of the points, so we must compute the convex hull or you must +// pass an already built hull to the code. +// +// The minimum-volume oriented box has a face coincident with a hull face +// or has three mutually orthogonal edges coincident with three hull edges +// that (of course) are mutually orthogonal. +// J.O'Rourke, "Finding minimal enclosing boxes", +// Internat. J. Comput. Inform. Sci., 14:183-199, 1985. +// +// A detailed description of the algorithm and implementation is found in +// the documents +// https://www.geometrictools.com/Documentation/MinimumVolumeBox.pdf +// https://www.geometrictools.com/Documentation/MinimumAreaRectangle.pdf +// +// NOTE: This algorithm guarantees a correct output only when ComputeType is +// an exact arithmetic type that supports division. In GTEngine, one such +// type is BSRational (arbitrary precision). Another such type +// is BSRational> (fixed precision), where N is chosen large +// enough for your input data sets. If you choose ComputeType to be 'float' +// or 'double', the output is not guaranteed to be correct. +// +// See GeometricTools/GTEngine/Samples/Geometrics/MinimumVolumeBox3 for an +// example of how to use the code. + +namespace WwiseGTE +{ + template + class MinimumVolumeBox3 + { + public: + // The class is a functor to support computing the minimum-volume box + // of multiple data sets using the same class object. For + // multithreading in ProcessFaces, choose 'numThreads' subject to the + // constraints + // 1 <= numThreads <= std::thread::hardware_concurrency() + // To execute ProcessEdges in a thread separate from the main thread, + // choose 'threadProcessEdges' to 'true'. + MinimumVolumeBox3(unsigned int numThreads = 1, bool threadProcessEdges = false) + : + mNumThreads(numThreads), + mThreadProcessEdges(threadProcessEdges), + mNumPoints(0), + mPoints(nullptr), + mComputePoints(nullptr), + mUseRotatingCalipers(true), + mVolume((InputType)0), + mZero(0), + mOne(1), + mNegOne(-1), + mHalf((InputType)0.5) + { + } + + // The points are arbitrary, so we must compute the convex hull from + // them in order to compute the minimum-area box. The input + // parameters are necessary for using ConvexHull3. + OrientedBox3 operator()(int numPoints, Vector3 const* points, + InputType epsilon = (InputType)0, + bool useRotatingCalipers = !std::is_floating_point::value) + { + mNumPoints = numPoints; + mPoints = points; + mUseRotatingCalipers = useRotatingCalipers; + mHull.clear(); + mUniqueIndices.clear(); + + // Get the convex hull of the points. + ConvexHull3 ch3; + ch3(mNumPoints, mPoints, epsilon); + int dimension = ch3.GetDimension(); + + OrientedBox3 itMinBox; + + if (dimension == 0) + { + // The points are all effectively the same (using fuzzy + // epsilon). + itMinBox.center = mPoints[0]; + itMinBox.axis[0] = Vector3::Unit(0); + itMinBox.axis[1] = Vector3::Unit(1); + itMinBox.axis[2] = Vector3::Unit(2); + itMinBox.extent[0] = (InputType)0; + itMinBox.extent[1] = (InputType)0; + itMinBox.extent[2] = (InputType)0; + mHull.resize(1); + mHull[0] = 0; + return itMinBox; + } + + if (dimension == 1) + { + // The points effectively lie on a line (using fuzzy epsilon). + // Determine the extreme t-values for the points represented + // as P = origin + t*direction. We know that 'origin' is an + // input vertex, so we can start both t-extremes at zero. + Line3 const& line = ch3.GetLine(); + InputType tmin = (InputType)0, tmax = (InputType)0; + int imin = 0, imax = 0; + for (int i = 0; i < mNumPoints; ++i) + { + Vector3 diff = mPoints[i] - line.origin; + InputType t = Dot(diff, line.direction); + if (t > tmax) + { + tmax = t; + imax = i; + } + else if (t < tmin) + { + tmin = t; + imin = i; + } + } + + itMinBox.center = line.origin + ((InputType)0.5) * (tmin + tmax) * line.direction; + itMinBox.extent[0] = ((InputType)0.5) * (tmax - tmin); + itMinBox.extent[1] = (InputType)0; + itMinBox.extent[2] = (InputType)0; + itMinBox.axis[0] = line.direction; + ComputeOrthogonalComplement(1, &itMinBox.axis[0]); + mHull.resize(2); + mHull[0] = imin; + mHull[1] = imax; + return itMinBox; + } + + if (dimension == 2) + { + // The points effectively line on a plane (using fuzzy + // epsilon). Project the points onto the plane and compute + // the minimum-area bounding box of them. + Plane3 const& plane = ch3.GetPlane(); + + // Get a coordinate system relative to the plane of the + // points. Choose the origin to be any of the input points. + Vector3 origin = mPoints[0]; + Vector3 basis[3]; + basis[0] = plane.normal; + ComputeOrthogonalComplement(1, basis); + + // Project the input points onto the plane. + std::vector> projection(mNumPoints); + for (int i = 0; i < mNumPoints; ++i) + { + Vector3 diff = mPoints[i] - origin; + projection[i][0] = Dot(basis[1], diff); + projection[i][1] = Dot(basis[2], diff); + } + + // Compute the minimum area box in 2D. + MinimumAreaBox2 mab2; + OrientedBox2 rectangle = mab2(mNumPoints, &projection[0]); + + // Lift the values into 3D. + itMinBox.center = origin + rectangle.center[0] * basis[1] + rectangle.center[1] * basis[2]; + itMinBox.axis[0] = rectangle.axis[0][0] * basis[1] + rectangle.axis[0][1] * basis[2]; + itMinBox.axis[1] = rectangle.axis[1][0] * basis[1] + rectangle.axis[1][1] * basis[2]; + itMinBox.axis[2] = basis[0]; + itMinBox.extent[0] = rectangle.extent[0]; + itMinBox.extent[1] = rectangle.extent[1]; + itMinBox.extent[2] = (InputType)0; + mHull = mab2.GetHull(); + return itMinBox; + } + + // Get the set of unique indices of the hull. This is used to + // project hull vertices onto lines. + ETManifoldMesh const& mesh = ch3.GetHullMesh(); + mHull.resize(3 * mesh.GetTriangles().size()); + int h = 0; + for (auto const& element : mesh.GetTriangles()) + { + for (int i = 0; i < 3; ++i, ++h) + { + int index = element.first.V[i]; + mHull[h] = index; + mUniqueIndices.insert(index); + } + } + + mComputePoints = ch3.GetQuery().GetVertices(); + + Box minBox, minBoxEdges; + minBox.volume = mNegOne; + minBoxEdges.volume = mNegOne; + + if (mThreadProcessEdges) + { + std::thread doEdges( + [this, &mesh, &minBoxEdges]() + { + ProcessEdges(mesh, minBoxEdges); + }); + ProcessFaces(mesh, minBox); + doEdges.join(); + } + else + { + ProcessEdges(mesh, minBoxEdges); + ProcessFaces(mesh, minBox); + } + + if (minBoxEdges.volume != mNegOne + && minBoxEdges.volume < minBox.volume) + { + minBox = minBoxEdges; + } + + ConvertTo(minBox, itMinBox); + mComputePoints = nullptr; + return itMinBox; + } + + // The points form a nondegenerate convex polyhedron. The indices + // input must be nonnull and specify the triangle faces. + OrientedBox3 operator()(int numPoints, Vector3 const* points, + int numIndices, int const* indices, + bool useRotatingCalipers = !std::is_floating_point::value) + { + mNumPoints = numPoints; + mPoints = points; + mUseRotatingCalipers = useRotatingCalipers; + mUniqueIndices.clear(); + + // Build the mesh from the indices. The box construction uses the + // edge map of the mesh. + ETManifoldMesh mesh; + int numTriangles = numIndices / 3; + for (int t = 0; t < numTriangles; ++t) + { + int v0 = *indices++; + int v1 = *indices++; + int v2 = *indices++; + mesh.Insert(v0, v1, v2); + } + + // Get the set of unique indices of the hull. This is used to + // project hull vertices onto lines. + mHull.resize(3 * mesh.GetTriangles().size()); + int h = 0; + for (auto const& element : mesh.GetTriangles()) + { + for (int i = 0; i < 3; ++i, ++h) + { + int index = element.first.V[i]; + mHull[h] = index; + mUniqueIndices.insert(index); + } + } + + // Create the ComputeType points to be used downstream. + std::vector> computePoints(mNumPoints); + for (auto i : mUniqueIndices) + { + for (int j = 0; j < 3; ++j) + { + computePoints[i][j] = (ComputeType)mPoints[i][j]; + } + } + + OrientedBox3 itMinBox; + mComputePoints = &computePoints[0]; + + Box minBox, minBoxEdges; + minBox.volume = mNegOne; + minBoxEdges.volume = mNegOne; + + if (mThreadProcessEdges) + { + std::thread doEdges( + [this, &mesh, &minBoxEdges]() + { + ProcessEdges(mesh, minBoxEdges); + }); + ProcessFaces(mesh, minBox); + doEdges.join(); + } + else + { + ProcessEdges(mesh, minBoxEdges); + ProcessFaces(mesh, minBox); + } + + if (minBoxEdges.volume != mNegOne && minBoxEdges.volume < minBox.volume) + { + minBox = minBoxEdges; + } + + ConvertTo(minBox, itMinBox); + mComputePoints = nullptr; + return itMinBox; + } + + // Member access. + inline int GetNumPoints() const + { + return mNumPoints; + } + + inline Vector3 const* GetPoints() const + { + return mPoints; + } + + inline std::vector const& GetHull() const + { + return mHull; + } + + inline InputType GetVolume() const + { + return mVolume; + } + + private: + struct Box + { + Vector3 P, U[3]; + ComputeType sqrLenU[3], range[3][2], volume; + }; + + struct ExtrudeRectangle + { + Vector3 U[2]; + std::array index; + ComputeType sqrLenU[2], area; + }; + + // Compute the minimum-volume box relative to each hull face. + void ProcessFaces(ETManifoldMesh const& mesh, Box& minBox) + { + // Get the mesh data structures. + auto const& tmap = mesh.GetTriangles(); + auto const& emap = mesh.GetEdges(); + + // Compute inner-pointing face normals for searching boxes + // supported by a face and an extreme vertex. The indirection in + // triNormalMap, using an integer index instead of the + // normal/sqrlength pair itself, avoids expensive copies when + // using exact arithmetic. + std::vector> normal(tmap.size()); + std::map, int> triNormalMap; + int index = 0; + for (auto const& element : tmap) + { + auto tri = element.second; + Vector3 const& v0 = mComputePoints[tri->V[0]]; + Vector3 const& v1 = mComputePoints[tri->V[1]]; + Vector3 const& v2 = mComputePoints[tri->V[2]]; + Vector3 edge1 = v1 - v0; + Vector3 edge2 = v2 - v0; + normal[index] = Cross(edge2, edge1); // inner-pointing normal + if (normal[index] == Vector3(0)) + continue; + triNormalMap[tri] = index++; + } + + // Process the triangle faces. For each face, compute the + // polyline of edges that supports the bounding box with a face + // coincident to the triangle face. The projection of the + // polyline onto the plane of the triangle face is a convex + // polygon, so we can use the method of rotating calipers to + // compute its minimum-area box efficiently. + unsigned int numFaces = static_cast(tmap.size()); + if (mNumThreads > 1 && numFaces >= mNumThreads) + { + // Repackage the triangle pointers to support the partitioning + // of faces for multithreaded face processing. + std::vector> triangles; + triangles.reserve(numFaces); + for (auto const& element : tmap) + { + triangles.push_back(element.second); + } + + // Partition the data for multiple threads. + unsigned int numFacesPerThread = numFaces / mNumThreads; + std::vector imin(mNumThreads), imax(mNumThreads); + std::vector localMinBox(mNumThreads); + for (unsigned int t = 0; t < mNumThreads; ++t) + { + imin[t] = t * numFacesPerThread; + imax[t] = imin[t] + numFacesPerThread - 1; + localMinBox[t].volume = mNegOne; + } + imax[mNumThreads - 1] = numFaces - 1; + + // Execute the face processing in multiple threads. + std::vector process(mNumThreads); + for (unsigned int t = 0; t < mNumThreads; ++t) + { + process[t] = std::thread([this, t, &imin, &imax, &triangles, + &normal, &triNormalMap, &emap, &localMinBox]() + { + for (unsigned int i = imin[t]; i <= imax[t]; ++i) + { + auto const& supportTri = triangles[i]; + ProcessFace(supportTri, normal, triNormalMap, emap, localMinBox[t]); + } + }); + } + + // Wait for all threads to finish. + for (unsigned int t = 0; t < mNumThreads; ++t) + { + process[t].join(); + + // Update the minimum-volume box candidate. + if (minBox.volume == mNegOne || localMinBox[t].volume < minBox.volume) + { + minBox = localMinBox[t]; + } + } + } + else + { + for (auto const& element : tmap) + { + auto const& supportTri = element.second; + ProcessFace(supportTri, normal, triNormalMap, emap, minBox); + } + } + } + + // Compute the minimum-volume box for each triple of orthgonal hull + // edges. + void ProcessEdges(ETManifoldMesh const& mesh, Box& minBox) + { + // The minimum-volume box can also be supported by three mutually + // orthogonal edges of the convex hull. For each triple of + // orthogonal edges, compute the minimum-volume box for that + // coordinate frame by projecting the points onto the axes of the + // frame. Use a hull vertex as the origin. + int index = mesh.GetTriangles().begin()->first.V[0]; + Vector3 const& origin = mComputePoints[index]; + Vector3 U[3]; + std::array sqrLenU; + + auto const& emap = mesh.GetEdges(); + auto e2 = emap.begin(), end = emap.end(); + for (/**/; e2 != end; ++e2) + { + U[2] = mComputePoints[e2->first.V[1]] - mComputePoints[e2->first.V[0]]; + auto e1 = e2; + for (++e1; e1 != end; ++e1) + { + U[1] = mComputePoints[e1->first.V[1]] - mComputePoints[e1->first.V[0]]; + if (Dot(U[1], U[2]) != mZero) + { + continue; + } + sqrLenU[1] = Dot(U[1], U[1]); + + auto e0 = e1; + for (++e0; e0 != end; ++e0) + { + U[0] = mComputePoints[e0->first.V[1]] - mComputePoints[e0->first.V[0]]; + sqrLenU[0] = Dot(U[0], U[0]); + if (Dot(U[0], U[1]) != mZero || Dot(U[0], U[2]) != mZero) + { + continue; + } + + // The three edges are mutually orthogonal. To + // support exact rational arithmetic for volume + // computation, we replace U[2] by a parallel vector. + U[2] = Cross(U[0], U[1]); + sqrLenU[2] = sqrLenU[0] * sqrLenU[1]; + + // Project the vertices onto the lines containing the + // edges. Use vertex 0 as the origin. + std::array umin, umax; + for (int j = 0; j < 3; ++j) + { + umin[j] = mZero; + umax[j] = mZero; + } + + for (auto i : mUniqueIndices) + { + Vector3 diff = mComputePoints[i] - origin; + for (int j = 0; j < 3; ++j) + { + ComputeType dot = Dot(diff, U[j]); + if (dot < umin[j]) + { + umin[j] = dot; + } + else if (dot > umax[j]) + { + umax[j] = dot; + } + } + } + + ComputeType volume = (umax[0] - umin[0]) / sqrLenU[0]; + volume *= (umax[1] - umin[1]) / sqrLenU[1]; + volume *= (umax[2] - umin[2]); + + // Update current minimum-volume box (if necessary). + if (minBox.volume == mOne || volume < minBox.volume) + { + // The edge keys have unordered vertices, so it is + // possible that {U[0],U[1],U[2]} is a left-handed + // set. We need a right-handed set. + if (DotCross(U[0], U[1], U[2]) < mZero) + { + U[2] = -U[2]; + } + + minBox.P = origin; + for (int j = 0; j < 3; ++j) + { + minBox.U[j] = U[j]; + minBox.sqrLenU[j] = sqrLenU[j]; + for (int k = 0; k < 3; ++k) + { + minBox.range[k][0] = umin[k]; + minBox.range[k][1] = umax[k]; + } + } + minBox.volume = volume; + } + } + } + } + } + + // Compute the minimum-volume box relative to a single hull face. + typedef ETManifoldMesh::Triangle Triangle; + + void ProcessFace(std::shared_ptr const& supportTri, + std::vector> const& normal, + std::map, int> const& triNormalMap, + ETManifoldMesh::EMap const& emap, Box& localMinBox) + { + // Get the supporting triangle information. + Vector3 const& supportNormal = normal[triNormalMap.find(supportTri)->second]; + + static const Vector3 sZero = { 0.0, 0.0, 0.0 }; + if (supportNormal == sZero) + return; + + // Build the polyline of supporting edges. The pair + // (v,polyline[v]) represents an edge directed appropriately + // (see next set of comments). + std::vector polyline(mNumPoints); + int polylineStart = -1; + for (auto const& edgeElement : emap) + { + auto const& edge = *edgeElement.second; + auto const& tri0 = edge.T[0].lock(); + auto const& tri1 = edge.T[1].lock(); + auto const& normal0 = normal[triNormalMap.find(tri0)->second]; + auto const& normal1 = normal[triNormalMap.find(tri1)->second]; + ComputeType dot0 = Dot(supportNormal, normal0); + ComputeType dot1 = Dot(supportNormal, normal1); + + std::shared_ptr tri; + if (dot0 < mZero && dot1 >= mZero) + { + tri = tri0; + } + else if (dot1 < mZero && dot0 >= mZero) + { + tri = tri1; + } + + if (tri) + { + // The edge supports the bounding box. Insert the edge + // in the list using clockwise order relative to tri. + // This will lead to a polyline whose projection onto the + // plane of the hull face is a convex polygon that is + // counterclockwise oriented. + for (int j0 = 2, j1 = 0; j1 < 3; j0 = j1++) + { + if (tri->V[j1] == edge.V[0]) + { + if (tri->V[j0] == edge.V[1]) + { + polyline[edge.V[1]] = edge.V[0]; + } + else + { + polyline[edge.V[0]] = edge.V[1]; + } + polylineStart = edge.V[0]; + break; + } + } + } + } + + if (polylineStart == -1) + return; + + // Rearrange the edges to form a closed polyline. For M vertices, + // each ComputeBoxFor*() function starts with the edge from + // closedPolyline[M-1] to closedPolyline[0]. + std::vector closedPolyline(mNumPoints); + int numClosedPolyline = 0; + int v = polylineStart; + for (auto& cp : closedPolyline) + { + cp = v; + ++numClosedPolyline; + v = polyline[v]; + if (v == polylineStart) + { + break; + } + } + closedPolyline.resize(numClosedPolyline); + + // This avoids redundant face testing in the O(n^2) or O(n) + // algorithms, and it simplifies the O(n) implementation. + RemoveCollinearPoints(supportNormal, closedPolyline); + + // Compute the box coincident to the hull triangle that has + // minimum area on the face coincident with the triangle. + Box faceBox; + if (mUseRotatingCalipers) + { + ComputeBoxForFaceOrderN(supportNormal, closedPolyline, faceBox); + } + else + { + ComputeBoxForFaceOrderNSqr(supportNormal, closedPolyline, faceBox); + } + + // Update the minimum-volume box candidate. + if (localMinBox.volume == mNegOne || faceBox.volume < localMinBox.volume) + { + localMinBox = faceBox; + } + } + + // The rotating calipers algorithm has a loop invariant that requires + // the convex polygon not to have collinear points. Any such points + // must be removed first. The code is also executed for the O(n^2) + // algorithm to reduce the number of process edges. + void RemoveCollinearPoints(Vector3 const& N, std::vector& polyline) + { + std::vector tmpPolyline = polyline; + + int const numPolyline = static_cast(polyline.size()); + int numNoncollinear = 0; + Vector3 ePrev = + mComputePoints[tmpPolyline[0]] - mComputePoints[tmpPolyline.back()]; + + for (int i0 = 0, i1 = 1; i0 < numPolyline; ++i0) + { + Vector3 eNext = + mComputePoints[tmpPolyline[i1]] - mComputePoints[tmpPolyline[i0]]; + + ComputeType tsp = DotCross(ePrev, eNext, N); + if (tsp != mZero) + { + polyline[numNoncollinear++] = tmpPolyline[i0]; + } + + ePrev = eNext; + if (++i1 == numPolyline) + { + i1 = 0; + } + } + + polyline.resize(numNoncollinear); + } + + // This is the slow order O(n^2) search. + void ComputeBoxForFaceOrderNSqr(Vector3 const& N, std::vector const& polyline, Box& box) + { + // This code processes the polyline terminator associated with a + // convex hull face of inner-pointing normal N. The polyline is + // usually not contained by a plane, and projecting the polyline + // to a convex polygon in a plane perpendicular to N introduces + // floating-point rounding errors. The minimum-area box for the + // projected polyline is computed indirectly to support exact + // rational arithmetic. + + box.P = mComputePoints[polyline[0]]; + box.U[2] = N; + box.sqrLenU[2] = Dot(N, N); + box.range[1][0] = mZero; + box.volume = mNegOne; + int const numPolyline = static_cast(polyline.size()); + for (int i0 = numPolyline - 1, i1 = 0; i1 < numPolyline; i0 = i1++) + { + // Create a coordinate system for the plane perpendicular to + // the face normal and containing a polyline vertex. + Vector3 const& P = mComputePoints[polyline[i0]]; + Vector3 E = mComputePoints[polyline[i1]] - mComputePoints[polyline[i0]]; + Vector3 U1 = Cross(N, E); + Vector3 U0 = Cross(U1, N); + + // Compute the smallest rectangle containing the projected + // polyline. + ComputeType min0 = mZero, max0 = mZero, max1 = mZero; + for (int j = 0; j < numPolyline; ++j) + { + Vector3 diff = mComputePoints[polyline[j]] - P; + ComputeType dot = Dot(U0, diff); + if (dot < min0) + { + min0 = dot; + } + else if (dot > max0) + { + max0 = dot; + } + + dot = Dot(U1, diff); + if (dot > max1) + { + max1 = dot; + } + } + + // The true area is Area(rectangle)*Length(N). After the + // smallest scaled-area rectangle is computed and returned, + // the box.volume is updated to be the actual squared volume + // of the box. + ComputeType sqrLenU1 = Dot(U1, U1); + ComputeType volume = (max0 - min0) * max1 / sqrLenU1; + if (box.volume == mNegOne || volume < box.volume) + { + box.P = P; + box.U[0] = U0; + box.U[1] = U1; + box.sqrLenU[0] = sqrLenU1 * box.sqrLenU[2]; + box.sqrLenU[1] = sqrLenU1; + box.range[0][0] = min0; + box.range[0][1] = max0; + box.range[1][1] = max1; + box.volume = volume; + } + } + + // Compute the range of points in the support-normal direction. + box.range[2][0] = mZero; + box.range[2][1] = mZero; + for (auto i : mUniqueIndices) + { + Vector3 diff = mComputePoints[i] - box.P; + ComputeType height = Dot(box.U[2], diff); + if (height < box.range[2][0]) + { + box.range[2][0] = height; + } + else if (height > box.range[2][1]) + { + box.range[2][1] = height; + } + } + + // Compute the actual volume. + box.volume *= (box.range[2][1] - box.range[2][0]) / box.sqrLenU[2]; + } + + // This is the rotating calipers version, which is O(n). + void ComputeBoxForFaceOrderN(Vector3 const& N, std::vector const& polyline, Box& box) + { + // This code processes the polyline terminator associated with a + // convex hull face of inner-pointing normal N. The polyline is + // usually not contained by a plane, and projecting the polyline + // to a convex polygon in a plane perpendicular to N introduces + // floating-point rounding errors. The minimum-area box for the + // projected polyline is computed indirectly to support exact + // rational arithmetic. + + // When the bounding box corresponding to a polyline edge is + // computed, we mark the edge as visited. If the edge is + // encountered later, the algorithm terminates. + std::vector visited(polyline.size()); + std::fill(visited.begin(), visited.end(), false); + + // Start the minimum-area rectangle search with the edge from the + // last polyline vertex to the first. When updating the extremes, + // we want the bottom-most point on the left edge, the top-most + // point on the right edge, the left-most point on the top edge, + // and the right-most point on the bottom edge. The polygon edges + // starting at these points are then guaranteed not to coincide + // with a box edge except when an extreme point is shared by two + // box edges (at a corner). + ExtrudeRectangle minRct = + SmallestRectangle((int)polyline.size() - 1, 0, N, polyline); + visited[minRct.index[0]] = true; + + // Execute the rotating calipers algorithm. + ExtrudeRectangle rct = minRct; + for (size_t i = 0; i < polyline.size(); ++i) + { + std::array, 4> A; + int numA; + if (!ComputeAngles(N, polyline, rct, A, numA)) + { + // The polyline projects to a rectangle, so the search is + // over. + break; + } + + // Indirectly sort the A-array. + std::array sort = SortAngles(A, numA); + + // Update the supporting indices (rct.index[]) and the + // rectangle axis directions (rct.U[]). + if (!UpdateSupport(A, numA, sort, N, polyline, visited, rct)) + { + // We have already processed the rectangle polygon edge, + // so the search is over. + break; + } + + if (rct.area < minRct.area) + { + minRct = rct; + } + } + + // Store relevant box information for computing volume and + // converting to an InputType bounding box. + box.P = mComputePoints[polyline[minRct.index[0]]]; + box.U[0] = minRct.U[0]; + box.U[1] = minRct.U[1]; + box.U[2] = N; + box.sqrLenU[0] = minRct.sqrLenU[0]; + box.sqrLenU[1] = minRct.sqrLenU[1]; + box.sqrLenU[2] = Dot(box.U[2], box.U[2]); + + // Compute the range of points in the plane perpendicular to the + // support normal. + box.range[0][0] = Dot(box.U[0], mComputePoints[polyline[minRct.index[3]]] - box.P); + box.range[0][1] = Dot(box.U[0], mComputePoints[polyline[minRct.index[1]]] - box.P); + box.range[1][0] = mZero; + box.range[1][1] = Dot(box.U[1], mComputePoints[polyline[minRct.index[2]]] - box.P); + + // Compute the range of points in the support-normal direction. + box.range[2][0] = mZero; + box.range[2][1] = mZero; + for (auto i : mUniqueIndices) + { + Vector3 diff = mComputePoints[i] - box.P; + ComputeType height = Dot(box.U[2], diff); + if (height < box.range[2][0]) + { + box.range[2][0] = height; + } + else if (height > box.range[2][1]) + { + box.range[2][1] = height; + } + } + + // Compute the actual volume. + box.volume = + (box.range[0][1] - box.range[0][0]) * + ((box.range[1][1] - box.range[1][0]) / box.sqrLenU[1]) * + ((box.range[2][1] - box.range[2][0]) / box.sqrLenU[2]); + } + + // Compute the smallest rectangle for the polyline edge . + ExtrudeRectangle SmallestRectangle(int i0, int i1, Vector3 const& N, std::vector const& polyline) + { + ExtrudeRectangle rct; + Vector3 E = mComputePoints[polyline[i1]] - mComputePoints[polyline[i0]]; + rct.U[1] = Cross(N, E); + rct.U[0] = Cross(rct.U[1], N); + rct.index = { i1, i1, i1, i1 }; + rct.sqrLenU[0] = Dot(rct.U[0], rct.U[0]); + rct.sqrLenU[1] = Dot(rct.U[1], rct.U[1]); + + Vector3 const& origin = mComputePoints[polyline[i1]]; + Vector2 support[4]; + for (int j = 0; j < 4; ++j) + { + support[j] = { mZero, mZero }; + } + + int i = 0; + for (auto p : polyline) + { + Vector3 diff = mComputePoints[p] - origin; + Vector2 v = { Dot(rct.U[0], diff), Dot(rct.U[1], diff) }; + + // The right-most vertex of the bottom edge is vertices[i1]. + // The assumption of no triple of collinear vertices + // guarantees that box.index[0] is i1, which is the initial + // value assigned at the beginning of this function. + // Therefore, there is no need to test for other vertices + // farther to the right than vertices[i1]. + + if (v[0] > support[1][0] || + (v[0] == support[1][0] && v[1] > support[1][1])) + { + // New right maximum OR same right maximum but closer + // to top. + rct.index[1] = i; + support[1] = v; + } + + if (v[1] > support[2][1] || + (v[1] == support[2][1] && v[0] < support[2][0])) + { + // New top maximum OR same top maximum but closer + // to left. + rct.index[2] = i; + support[2] = v; + } + + if (v[0] < support[3][0] || + (v[0] == support[3][0] && v[1] < support[3][1])) + { + // New left minimum OR same left minimum but closer + // to bottom. + rct.index[3] = i; + support[3] = v; + } + + ++i; + } + + // The comment in the loop has the implication that + // support[0] = { 0, 0 }, so the scaled height + // (support[2][1] - support[0][1]) is simply support[2][1]. + ComputeType scaledWidth = support[1][0] - support[3][0]; + ComputeType scaledHeight = support[2][1]; + rct.area = scaledWidth * scaledHeight / rct.sqrLenU[1]; + return rct; + } + + // Compute (sin(angle))^2 for the polyline edges emanating from the + // support vertices of the rectangle. The return value is 'true' if + // at least one angle is in [0,pi/2); otherwise, the return value is + // 'false' and the original polyline must project to a rectangle. + bool ComputeAngles(Vector3 const& N, + std::vector const& polyline, ExtrudeRectangle const& rct, + std::array, 4>& A, int& numA) const + { + int const numPolyline = static_cast(polyline.size()); + numA = 0; + for (int k0 = 3, k1 = 0; k1 < 4; k0 = k1++) + { + if (rct.index[k0] != rct.index[k1]) + { + // The rct edges are ordered in k1 as U[0], U[1], + // -U[0], -U[1]. + int lookup = (k0 & 1); + Vector3 D = ((k0 & 2) ? -rct.U[lookup] : rct.U[lookup]); + int j0 = rct.index[k0], j1 = j0 + 1; + if (j1 == numPolyline) + { + j1 = 0; + } + Vector3 E = mComputePoints[polyline[j1]] - mComputePoints[polyline[j0]]; + Vector3 Eperp = Cross(N, E); + E = Cross(Eperp, N); + Vector3 DxE = Cross(D, E); + ComputeType csqrlen = Dot(DxE, DxE); + ComputeType dsqrlen = rct.sqrLenU[lookup]; + ComputeType esqrlen = Dot(E, E); + ComputeType sinThetaSqr = csqrlen / (dsqrlen * esqrlen); + A[numA++] = std::make_pair(sinThetaSqr, k0); + } + } + return numA > 0; + } + + // Sort the angles indirectly. The sorted indices are returned. This + // avoids swapping elements of A[], which can be expensive when + // ComputeType is an exact rational type. + std::array SortAngles(std::array, 4> const& A, int numA) const + { + std::array sort = { 0, 1, 2, 3 }; + if (numA > 1) + { + if (numA == 2) + { + if (A[sort[0]].first > A[sort[1]].first) + { + std::swap(sort[0], sort[1]); + } + } + else if (numA == 3) + { + if (A[sort[0]].first > A[sort[1]].first) + { + std::swap(sort[0], sort[1]); + } + if (A[sort[0]].first > A[sort[2]].first) + { + std::swap(sort[0], sort[2]); + } + if (A[sort[1]].first > A[sort[2]].first) + { + std::swap(sort[1], sort[2]); + } + } + else // numA == 4 + { + if (A[sort[0]].first > A[sort[1]].first) + { + std::swap(sort[0], sort[1]); + } + if (A[sort[2]].first > A[sort[3]].first) + { + std::swap(sort[2], sort[3]); + } + if (A[sort[0]].first > A[sort[2]].first) + { + std::swap(sort[0], sort[2]); + } + if (A[sort[1]].first > A[sort[3]].first) + { + std::swap(sort[1], sort[3]); + } + if (A[sort[1]].first > A[sort[2]].first) + { + std::swap(sort[1], sort[2]); + } + } + } + return sort; + } + + bool UpdateSupport(std::array, 4> const& A, int numA, + std::array const& sort, Vector3 const& N, std::vector const& polyline, + std::vector& visited, ExtrudeRectangle& rct) + { + // Replace the support vertices of those edges attaining minimum + // angle with the other endpoints of the edges. + int const numPolyline = static_cast(polyline.size()); + auto const& amin = A[sort[0]]; + for (int k = 0; k < numA; ++k) + { + auto const& a = A[sort[k]]; + if (a.first == amin.first) + { + if (++rct.index[a.second] == numPolyline) + { + rct.index[a.second] = 0; + } + } + else + { + break; + } + } + + int bottom = rct.index[amin.second]; + if (visited[bottom]) + { + // We have already processed this polyline edge. + return false; + } + visited[bottom] = true; + + // Cycle the vertices so that the bottom support occurs first. + std::array nextIndex; + for (int k = 0; k < 4; ++k) + { + nextIndex[k] = rct.index[(amin.second + k) % 4]; + } + rct.index = nextIndex; + + // Compute the rectangle axis directions. + int j1 = rct.index[0], j0 = j1 - 1; + if (j1 < 0) + { + j1 = numPolyline - 1; + } + Vector3 E = + mComputePoints[polyline[j1]] - mComputePoints[polyline[j0]]; + rct.U[1] = Cross(N, E); + rct.U[0] = Cross(rct.U[1], N); + rct.sqrLenU[0] = Dot(rct.U[0], rct.U[0]); + rct.sqrLenU[1] = Dot(rct.U[1], rct.U[1]); + + // Compute the rectangle area. + Vector3 diff[2] = + { + mComputePoints[polyline[rct.index[1]]] + - mComputePoints[polyline[rct.index[3]]], + mComputePoints[polyline[rct.index[2]]] + - mComputePoints[polyline[rct.index[0]]] + }; + ComputeType scaledWidth = Dot(rct.U[0], diff[0]); + ComputeType scaledHeight = Dot(rct.U[1], diff[1]); + rct.area = scaledWidth * scaledHeight / rct.sqrLenU[1]; + return true; + } + + // Convert the extruded box to the minimum-volume box of InputType. + // When the ComputeType is an exact rational type, the conversions are + // performed to avoid precision loss until necessary at the last step. + void ConvertTo(Box const& minBox, OrientedBox3& itMinBox) + { + Vector3 center = minBox.P; + for (int i = 0; i < 3; ++i) + { + ComputeType average = mHalf * (minBox.range[i][0] + minBox.range[i][1]); + center += (average / minBox.sqrLenU[i]) * minBox.U[i]; + } + + // Calculate the squared extent using ComputeType to avoid loss of + // precision before computing a squared root. + Vector3 sqrExtent; + for (int i = 0; i < 3; ++i) + { + sqrExtent[i] = mHalf * (minBox.range[i][1] - minBox.range[i][0]); + sqrExtent[i] *= sqrExtent[i]; + sqrExtent[i] /= minBox.sqrLenU[i]; + } + + for (int i = 0; i < 3; ++i) + { + itMinBox.center[i] = (InputType)center[i]; + itMinBox.extent[i] = std::sqrt((InputType)sqrExtent[i]); + + // Before converting to floating-point, factor out the maximum + // component using ComputeType to generate rational numbers in + // a range that avoids loss of precision during the conversion + // and normalization. + Vector3 const& axis = minBox.U[i]; + ComputeType cmax = std::max(std::abs((double)axis[0]), std::abs((double)axis[1])); + cmax = std::max(cmax, std::abs((double)axis[2])); + ComputeType invCMax = mOne / cmax; + for (int j = 0; j < 3; ++j) + { + itMinBox.axis[i][j] = (InputType)(axis[j] * invCMax); + } + Normalize(itMinBox.axis[i]); + } + + mVolume = (InputType)minBox.volume; + } + + // The code is multithreaded, both for convex hull computation and + // computing minimum-volume extruded boxes for the hull faces. The + // default value is 1, which implies a single-threaded computation (on + // the main thread). + unsigned int mNumThreads; + bool mThreadProcessEdges; + + // The input points to be bound. + int mNumPoints; + Vector3 const* mPoints; + + // The ComputeType conversions of the input points. Only points of + // the convex hull (vertices of a convex polyhedron) are converted + // for performance when ComputeType is rational. + Vector3 const* mComputePoints; + + // The indices into mPoints/mComputePoints for the convex hull + // vertices. + std::vector mHull; + + // The unique indices in mHull. This set allows us to compute only + // for the hull vertices and avoids redundant computations if the + // indices were to have repeated indices into mPoints/mComputePoints. + // This is a performance improvement for rational ComputeType. + std::set mUniqueIndices; + + // The caller can specify whether to use rotating calipers or the + // slower all-edge processing for computing an extruded bounding box. + bool mUseRotatingCalipers; + + // The volume of the minimum-volume box. The ComputeType value is + // exact, so the only rounding errors occur in the conversion from + // ComputeType to InputType (default rounding mode is + // round-to-nearest-ties-to-even). + InputType mVolume; + + // Convenient values that occur regularly in the code. When using + // rational ComputeType, we construct these numbers only once. + ComputeType mZero, mOne, mNegOne, mHalf; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MinimumVolumeSphere3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MinimumVolumeSphere3.h new file mode 100644 index 0000000..8675283 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/MinimumVolumeSphere3.h @@ -0,0 +1,694 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// Compute the minimum volume sphere containing the input set of points. The +// algorithm randomly permutes the input points so that the construction +// occurs in 'expected' O(N) time. All internal minimal sphere calculations +// store the squared radius in the radius member of Sphere3. Only at +// the end is a sqrt computed. +// +// The most robust choice for ComputeType is BSRational for exact rational +// arithmetic. As long as this code is a correct implementation of the theory +// (which I hope it is), you will obtain the minimum-volume sphere +// containing the points. +// +// Instead, if you choose ComputeType to be float or double, floating-point +// rounding errors can cause the UpdateSupport{2,3,4} functions to fail. +// The failure is trapped in those functions and a simple bounding sphere is +// computed using GetContainer in file GteContSphere3.h. This sphere is +// generally not the minimum-volume sphere containing the points. The +// minimum-volume algorithm is terminated immediately. The sphere is +// returned as well as a bool value of 'true' when the sphere is minimum +// volume or 'false' when the failure is trapped. When 'false' is returned, +// you can try another call to the operator()(...) function. The random +// shuffle that occurs is highly likely to be different from the previous +// shuffle, and there is a chance that the algorithm can succeed just because +// of the different ordering of points. + +namespace WwiseGTE +{ + template + class MinimumVolumeSphere3 + { + public: + bool operator()(int numPoints, Vector3 const* points, Sphere3& minimal) + { + if (numPoints >= 1 && points) + { + // Function array to avoid switch statement in the main loop. + std::function update[5]; + update[1] = [this](int i) { return UpdateSupport1(i); }; + update[2] = [this](int i) { return UpdateSupport2(i); }; + update[3] = [this](int i) { return UpdateSupport3(i); }; + update[4] = [this](int i) { return UpdateSupport4(i); }; + + // Process only the unique points. + std::vector permuted(numPoints); + for (int i = 0; i < numPoints; ++i) + { + permuted[i] = i; + } + std::sort(permuted.begin(), permuted.end(), + [points](int i0, int i1) { return points[i0] < points[i1]; }); + auto end = std::unique(permuted.begin(), permuted.end(), + [points](int i0, int i1) { return points[i0] == points[i1]; }); + permuted.erase(end, permuted.end()); + numPoints = static_cast(permuted.size()); + + // Create a random permutation of the points. + std::shuffle(permuted.begin(), permuted.end(), mDRE); + + // Convert to the compute type, which is a simple copy when + // ComputeType is the same as InputType. + mComputePoints.resize(numPoints); + for (int i = 0; i < numPoints; ++i) + { + for (int j = 0; j < 3; ++j) + { + mComputePoints[i][j] = points[permuted[i]][j]; + } + } + + // Start with the first point. + Sphere3 ctMinimal = ExactSphere1(0); + mNumSupport = 1; + mSupport[0] = 0; + + // The loop restarts from the beginning of the point list each + // time the sphere needs updating. Linus Källberg (Computer + // Science at Mälardalen University in Sweden) discovered that + // performance is/ better when the remaining points in the + // array are processed before restarting. The points + // processed before the point that caused the/ update are + // likely to be enclosed by the new sphere (or near the sphere + // boundary) because they were enclosed by the previous + // sphere. The chances are better that points after the + // current one will cause growth of the bounding sphere. + for (int i = 1 % numPoints, n = 0; i != n; i = (i + 1) % numPoints) + { + if (!SupportContains(i)) + { + if (!Contains(i, ctMinimal)) + { + auto result = update[mNumSupport](i); + if (result.second == true) + { + if (result.first.radius > ctMinimal.radius) + { + ctMinimal = result.first; + n = i; + } + } + else + { + // This case can happen when ComputeType is + // float or double. See the comments at the + // beginning of this file. ComputeType is not + // exact and failure occurred. Returning + // non-minimal circle. TODO: Should we throw + // an exception? + GetContainer(numPoints, points, minimal); + mNumSupport = 0; + mSupport.fill(0); + return false; + } + } + } + } + + for (int j = 0; j < 3; ++j) + { + minimal.center[j] = static_cast(ctMinimal.center[j]); + } + minimal.radius = static_cast(ctMinimal.radius); + minimal.radius = std::sqrt(minimal.radius); + + for (int i = 0; i < mNumSupport; ++i) + { + mSupport[i] = permuted[mSupport[i]]; + } + return true; + } + else + { + LogError("Input must contain points."); + } + } + + // Member access. + inline int GetNumSupport() const + { + return mNumSupport; + } + + inline std::array const& GetSupport() const + { + return mSupport; + } + + private: + // Test whether point P is inside sphere S using squared distance and + // squared radius. + bool Contains(int i, Sphere3 const& sphere) const + { + // NOTE: In this algorithm, sphere.radius is the *squared radius* + // until the function returns at which time a square root is + // applied. + Vector3 diff = mComputePoints[i] - sphere.center; + return Dot(diff, diff) <= sphere.radius; + } + + Sphere3 ExactSphere1(int i0) const + { + Sphere3 minimal; + minimal.center = mComputePoints[i0]; + minimal.radius = (ComputeType)0; + return minimal; + } + + Sphere3 ExactSphere2(int i0, int i1) const + { + Vector3 const& P0 = mComputePoints[i0]; + Vector3 const& P1 = mComputePoints[i1]; + Sphere3 minimal; + minimal.center = (ComputeType)0.5 * (P0 + P1); + Vector3 diff = P1 - P0; + minimal.radius = (ComputeType)0.25 * Dot(diff, diff); + return minimal; + } + + Sphere3 ExactSphere3(int i0, int i1, int i2) const + { + // Compute the 2D circle containing P0, P1, and P2. The center in + // barycentric coordinates is C = x0*P0 + x1*P1 + x2*P2, where + // x0 + x1 + x2 = 1. The center is equidistant from the three + // points, so |C - P0| = |C - P1| = |C - P2| = R, where R is the + // radius of the circle. From these conditions, + // C - P0 = x0*E0 + x1*E1 - E0 + // C - P1 = x0*E0 + x1*E1 - E1 + // C - P2 = x0*E0 + x1*E1 + // where E0 = P0 - P2 and E1 = P1 - P2, which leads to + // r^2 = |x0*E0 + x1*E1|^2 - 2*Dot(E0, x0*E0 + x1*E1) + |E0|^2 + // r^2 = |x0*E0 + x1*E1|^2 - 2*Dot(E1, x0*E0 + x1*E1) + |E1|^2 + // r^2 = |x0*E0 + x1*E1|^2 + // Subtracting the last equation from the first two and writing + // the equations as a linear system, + // + // +- -++ -+ +- -+ + // | Dot(E0,E0) Dot(E0,E1) || x0 | = 0.5 | Dot(E0,E0) | + // | Dot(E1,E0) Dot(E1,E1) || x1 | | Dot(E1,E1) | + // +- -++ -+ +- -+ + // + // The following code solves this system for x0 and x1 and then + // evaluates the third equation in r^2 to obtain r. + + Vector3 const& P0 = mComputePoints[i0]; + Vector3 const& P1 = mComputePoints[i1]; + Vector3 const& P2 = mComputePoints[i2]; + + Vector3 E0 = P0 - P2; + Vector3 E1 = P1 - P2; + + Matrix2x2 A; + A(0, 0) = Dot(E0, E0); + A(0, 1) = Dot(E0, E1); + A(1, 0) = A(0, 1); + A(1, 1) = Dot(E1, E1); + + ComputeType const half = (ComputeType)0.5; + Vector2 B{ half * A(0, 0), half * A(1, 1) }; + + Sphere3 minimal; + Vector2 X; + if (LinearSystem::Solve(A, B, X)) + { + ComputeType x2 = (ComputeType)1 - X[0] - X[1]; + minimal.center = X[0] * P0 + X[1] * P1 + x2 * P2; + Vector3 tmp = X[0] * E0 + X[1] * E1; + minimal.radius = Dot(tmp, tmp); + } + else + { + minimal.center = Vector3::Zero(); + minimal.radius = (ComputeType)std::numeric_limits::max(); + } + return minimal; + } + + Sphere3 ExactSphere4(int i0, int i1, int i2, int i3) const + { + // Compute the sphere containing P0, P1, P2, and P3. The center + // in barycentric coordinates is + // C = x0*P0 + x1*P1 + x2*P2 + x3*P3, + // where x0 + x1 + x2 + x3 = 1. The center is equidistant from + // the three points, so |C - P0| = |C - P1| = |C - P2| = |C - P3| + // = R, where R is the radius of the sphere. From these + // conditions, + // C - P0 = x0*E0 + x1*E1 + x2*E2 - E0 + // C - P1 = x0*E0 + x1*E1 + x2*E2 - E1 + // C - P2 = x0*E0 + x1*E1 + x2*E2 - E2 + // C - P3 = x0*E0 + x1*E1 + x2*E2 + // where E0 = P0 - P3, E1 = P1 - P3, and E2 = P2 - P3, which + // leads to + // r^2 = |x0*E0+x1*E1+x2*E2|^2-2*Dot(E0,x0*E0+x1*E1+x2*E2)+|E0|^2 + // r^2 = |x0*E0+x1*E1+x2*E2|^2-2*Dot(E1,x0*E0+x1*E1+x2*E2)+|E1|^2 + // r^2 = |x0*E0+x1*E1+x2*E2|^2-2*Dot(E2,x0*E0+x1*E1+x2*E2)+|E2|^2 + // r^2 = |x0*E0+x1*E1+x2*E2|^2 + // Subtracting the last equation from the first three and writing + // the equations as a linear system, + // + // +- -++ -+ +- -+ + // | Dot(E0,E0) Dot(E0,E1) Dot(E0,E2) || x0 | = 0.5 | Dot(E0,E0) | + // | Dot(E1,E0) Dot(E1,E1) Dot(E1,E2) || x1 | | Dot(E1,E1) | + // | Dot(E2,E0) Dot(E2,E1) Dot(E2,E2) || x2 | | Dot(E2,E2) | + // +- -++ -+ +- -+ + // + // The following code solves this system for x0, x1, and x2 and + // then evaluates the fourth equation in r^2 to obtain r. + + Vector3 const& P0 = mComputePoints[i0]; + Vector3 const& P1 = mComputePoints[i1]; + Vector3 const& P2 = mComputePoints[i2]; + Vector3 const& P3 = mComputePoints[i3]; + + Vector3 E0 = P0 - P3; + Vector3 E1 = P1 - P3; + Vector3 E2 = P2 - P3; + + Matrix3x3 A; + A(0, 0) = Dot(E0, E0); + A(0, 1) = Dot(E0, E1); + A(0, 2) = Dot(E0, E2); + A(1, 0) = A(0, 1); + A(1, 1) = Dot(E1, E1); + A(1, 2) = Dot(E1, E2); + A(2, 0) = A(0, 2); + A(2, 1) = A(1, 2); + A(2, 2) = Dot(E2, E2); + + ComputeType const half = (ComputeType)0.5; + Vector3 B{ half * A(0, 0), half * A(1, 1), half * A(2, 2) }; + + Sphere3 minimal; + Vector3 X; + if (LinearSystem::Solve(A, B, X)) + { + ComputeType x3 = (ComputeType)1 - X[0] - X[1] - X[2]; + minimal.center = X[0] * P0 + X[1] * P1 + X[2] * P2 + x3 * P3; + Vector3 tmp = X[0] * E0 + X[1] * E1 + X[2] * E2; + minimal.radius = Dot(tmp, tmp); + } + else + { + minimal.center = Vector3::Zero(); + minimal.radius = (ComputeType)std::numeric_limits::max(); + } + return minimal; + } + + typedef std::pair, bool> UpdateResult; + + UpdateResult UpdateSupport1(int i) + { + Sphere3 minimal = ExactSphere2(mSupport[0], i); + mNumSupport = 2; + mSupport[1] = i; + return std::make_pair(minimal, true); + } + + UpdateResult UpdateSupport2(int i) + { + // Permutations of type 2, used for calling ExactSphere2(...). + int const numType2 = 2; + int const type2[numType2][2] = + { + { 0, /*2*/ 1 }, + { 1, /*2*/ 0 } + }; + + // Permutations of type 3, used for calling ExactSphere3(...). + int const numType3 = 1; // {0, 1, 2} + + Sphere3 sphere[numType2 + numType3]; + ComputeType minRSqr = (ComputeType)std::numeric_limits::max(); + int iSphere = 0, iMinRSqr = -1; + int k0, k1; + + // Permutations of type 2. + for (int j = 0; j < numType2; ++j, ++iSphere) + { + k0 = mSupport[type2[j][0]]; + sphere[iSphere] = ExactSphere2(k0, i); + if (sphere[iSphere].radius < minRSqr) + { + k1 = mSupport[type2[j][1]]; + if (Contains(k1, sphere[iSphere])) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + } + } + + // Permutations of type 3. + k0 = mSupport[0]; + k1 = mSupport[1]; + sphere[iSphere] = ExactSphere3(k0, k1, i); + if (sphere[iSphere].radius < minRSqr) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + + switch (iMinRSqr) + { + case 0: + mSupport[1] = i; + break; + case 1: + mSupport[0] = i; + break; + case 2: + mNumSupport = 3; + mSupport[2] = i; + break; + case -1: + // For exact arithmetic, iMinRSqr >= 0, but for floating-point + // arithmetic, round-off errors can lead to iMinRSqr == -1. + // When this happens, use a simple bounding sphere for the + // result and terminate the minimum-volume algorithm. + return std::make_pair(Sphere3(), false); + } + + return std::make_pair(sphere[iMinRSqr], true); + } + + UpdateResult UpdateSupport3(int i) + { + // Permutations of type 2, used for calling ExactSphere2(...). + int const numType2 = 3; + int const type2[numType2][3] = + { + { 0, /*3*/ 1, 2 }, + { 1, /*3*/ 0, 2 }, + { 2, /*3*/ 0, 1 } + }; + + // Permutations of type 3, used for calling ExactSphere3(...). + int const numType3 = 3; + int const type3[numType3][3] = + { + { 0, 1, /*3*/ 2 }, + { 0, 2, /*3*/ 1 }, + { 1, 2, /*3*/ 0 } + }; + + // Permutations of type 4, used for calling ExactSphere4(...). + int const numType4 = 1; // {0, 1, 2, 3} + + Sphere3 sphere[numType2 + numType3 + numType4]; + ComputeType minRSqr = (ComputeType)std::numeric_limits::max(); + int iSphere = 0, iMinRSqr = -1; + int k0, k1, k2; + + // Permutations of type 2. + for (int j = 0; j < numType2; ++j, ++iSphere) + { + k0 = mSupport[type2[j][0]]; + sphere[iSphere] = ExactSphere2(k0, i); + if (sphere[iSphere].radius < minRSqr) + { + k1 = mSupport[type2[j][1]]; + k2 = mSupport[type2[j][2]]; + if (Contains(k1, sphere[iSphere]) && Contains(k2, sphere[iSphere])) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + } + } + + // Permutations of type 3. + for (int j = 0; j < numType3; ++j, ++iSphere) + { + k0 = mSupport[type3[j][0]]; + k1 = mSupport[type3[j][1]]; + sphere[iSphere] = ExactSphere3(k0, k1, i); + if (sphere[iSphere].radius < minRSqr) + { + k2 = mSupport[type3[j][2]]; + if (Contains(k2, sphere[iSphere])) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + } + } + + // Permutations of type 4. + k0 = mSupport[0]; + k1 = mSupport[1]; + k2 = mSupport[2]; + sphere[iSphere] = ExactSphere4(k0, k1, k2, i); + if (sphere[iSphere].radius < minRSqr) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + + switch (iMinRSqr) + { + case 0: + mNumSupport = 2; + mSupport[1] = i; + break; + case 1: + mNumSupport = 2; + mSupport[0] = i; + break; + case 2: + mNumSupport = 2; + mSupport[0] = mSupport[2]; + mSupport[1] = i; + break; + case 3: + mSupport[2] = i; + break; + case 4: + mSupport[1] = i; + break; + case 5: + mSupport[0] = i; + break; + case 6: + mNumSupport = 4; + mSupport[3] = i; + break; + case -1: + // For exact arithmetic, iMinRSqr >= 0, but for floating-point + // arithmetic, round-off errors can lead to iMinRSqr == -1. + // When this happens, use a simple bounding sphere for the + // result and terminate the minimum-area algorithm. + return std::make_pair(Sphere3(), false); + } + + return std::make_pair(sphere[iMinRSqr], true); + } + + UpdateResult UpdateSupport4(int i) + { + // Permutations of type 2, used for calling ExactSphere2(...). + int const numType2 = 4; + int const type2[numType2][4] = + { + { 0, /*4*/ 1, 2, 3 }, + { 1, /*4*/ 0, 2, 3 }, + { 2, /*4*/ 0, 1, 3 }, + { 3, /*4*/ 0, 1, 2 } + }; + + // Permutations of type 3, used for calling ExactSphere3(...). + int const numType3 = 6; + int const type3[numType3][4] = + { + { 0, 1, /*4*/ 2, 3 }, + { 0, 2, /*4*/ 1, 3 }, + { 0, 3, /*4*/ 1, 2 }, + { 1, 2, /*4*/ 0, 3 }, + { 1, 3, /*4*/ 0, 2 }, + { 2, 3, /*4*/ 0, 1 } + }; + + // Permutations of type 4, used for calling ExactSphere4(...). + int const numType4 = 4; + int const type4[numType4][4] = + { + { 0, 1, 2, /*4*/ 3 }, + { 0, 1, 3, /*4*/ 2 }, + { 0, 2, 3, /*4*/ 1 }, + { 1, 2, 3, /*4*/ 0 } + }; + + Sphere3 sphere[numType2 + numType3 + numType4]; + ComputeType minRSqr = (ComputeType)std::numeric_limits::max(); + int iSphere = 0, iMinRSqr = -1; + int k0, k1, k2, k3; + + // Permutations of type 2. + for (int j = 0; j < numType2; ++j, ++iSphere) + { + k0 = mSupport[type2[j][0]]; + sphere[iSphere] = ExactSphere2(k0, i); + if (sphere[iSphere].radius < minRSqr) + { + k1 = mSupport[type2[j][1]]; + k2 = mSupport[type2[j][2]]; + k3 = mSupport[type2[j][3]]; + if (Contains(k1, sphere[iSphere]) && Contains(k2, sphere[iSphere]) && Contains(k3, sphere[iSphere])) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + } + } + + // Permutations of type 3. + for (int j = 0; j < numType3; ++j, ++iSphere) + { + k0 = mSupport[type3[j][0]]; + k1 = mSupport[type3[j][1]]; + sphere[iSphere] = ExactSphere3(k0, k1, i); + if (sphere[iSphere].radius < minRSqr) + { + k2 = mSupport[type3[j][2]]; + k3 = mSupport[type3[j][3]]; + if (Contains(k2, sphere[iSphere]) && Contains(k3, sphere[iSphere])) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + } + } + + // Permutations of type 4. + for (int j = 0; j < numType4; ++j, ++iSphere) + { + k0 = mSupport[type4[j][0]]; + k1 = mSupport[type4[j][1]]; + k2 = mSupport[type4[j][2]]; + sphere[iSphere] = ExactSphere4(k0, k1, k2, i); + if (sphere[iSphere].radius < minRSqr) + { + k3 = mSupport[type4[j][3]]; + if (Contains(k3, sphere[iSphere])) + { + minRSqr = sphere[iSphere].radius; + iMinRSqr = iSphere; + } + } + } + + switch (iMinRSqr) + { + case 0: + mNumSupport = 2; + mSupport[1] = i; + break; + case 1: + mNumSupport = 2; + mSupport[0] = i; + break; + case 2: + mNumSupport = 2; + mSupport[0] = mSupport[2]; + mSupport[1] = i; + break; + case 3: + mNumSupport = 2; + mSupport[0] = mSupport[3]; + mSupport[1] = i; + break; + case 4: + mNumSupport = 3; + mSupport[2] = i; + break; + case 5: + mNumSupport = 3; + mSupport[1] = i; + break; + case 6: + mNumSupport = 3; + mSupport[1] = mSupport[3]; + mSupport[2] = i; + break; + case 7: + mNumSupport = 3; + mSupport[0] = i; + break; + case 8: + mNumSupport = 3; + mSupport[0] = mSupport[3]; + mSupport[2] = i; + break; + case 9: + mNumSupport = 3; + mSupport[0] = mSupport[3]; + mSupport[1] = i; + break; + case 10: + mSupport[3] = i; + break; + case 11: + mSupport[2] = i; + break; + case 12: + mSupport[1] = i; + break; + case 13: + mSupport[0] = i; + break; + case -1: + // For exact arithmetic, iMinRSqr >= 0, but for floating-point + // arithmetic, round-off errors can lead to iMinRSqr == -1. + // When this happens, use a simple bounding sphere for the + // result and terminate the minimum-area algorithm. + return std::make_pair(Sphere3(), false); + } + + return std::make_pair(sphere[iMinRSqr], true); + } + + // Indices of points that support current minimum volume sphere. + bool SupportContains(int j) const + { + for (int i = 0; i < mNumSupport; ++i) + { + if (j == mSupport[i]) + { + return true; + } + } + return false; + } + + int mNumSupport; + std::array mSupport; + + // Random permutation of the unique input points to produce expected + // linear time for the algorithm. + std::default_random_engine mDRE; + std::vector> mComputePoints; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/NURBSCircle.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/NURBSCircle.h new file mode 100644 index 0000000..f43e5be --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/NURBSCircle.h @@ -0,0 +1,143 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The algorithm for representing a circle as a NURBS curve or a sphere as a +// NURBS surface is described in +// https://www.geometrictools.com/Documentation/NURBSCircleSphere.pdf +// The implementations are related to the documents as shown next. +// NURBSQuarterCircleDegree2 implements equation (9) +// NURBSQuarterCircleDegree4 implements equation (10) +// NURBSHalfCircleDegree3 implements equation (12) +// NURBSFullCircleDegree3 implements Section 2.3 + +namespace WwiseGTE +{ + template + class NURBSQuarterCircleDegree2 : public NURBSCurve<2, Real> + { + public: + // Construction. The quarter circle is x^2 + y^2 = 1 for x >= 0 + // and y >= 0. The direction of traversal is counterclockwise as + // u increase from 0 to 1. + NURBSQuarterCircleDegree2() + : + NURBSCurve<2, Real>(BasisFunctionInput(3, 2), nullptr, nullptr) + { + Real const sqrt2 = std::sqrt((Real)2); + this->mWeights[0] = sqrt2; + this->mWeights[1] = (Real)1; + this->mWeights[2] = sqrt2; + + this->mControls[0] = { (Real)1, (Real)0 }; + this->mControls[1] = { (Real)1, (Real)1 }; + this->mControls[2] = { (Real)0, (Real)1 }; + } + }; + + template + class NURBSQuarterCircleDegree4 : public NURBSCurve<2, Real> + { + public: + // Construction. The quarter circle is x^2 + y^2 = 1 for x >= 0 + // and y >= 0. The direction of traversal is counterclockwise as + // u increases from 0 to 1. + NURBSQuarterCircleDegree4() + : + NURBSCurve<2, Real>(BasisFunctionInput(5, 4), nullptr, nullptr) + { + Real const sqrt2 = std::sqrt((Real)2); + this->mWeights[0] = (Real)1; + this->mWeights[1] = (Real)1; + this->mWeights[2] = (Real)2 * sqrt2 / (Real)3; + this->mWeights[3] = (Real)1; + this->mWeights[4] = (Real)1; + + Real const x1 = (Real)1; + Real const y1 = (Real)0.5 / sqrt2; + Real const x2 = (Real)1 - sqrt2 / (Real)8; + this->mControls[0] = { (Real)1, (Real)0 }; + this->mControls[1] = { x1, y1 }; + this->mControls[2] = { x2, x2 }; + this->mControls[3] = { y1, x1 }; + this->mControls[4] = { (Real)0, (Real)1 }; + } + }; + + template + class NURBSHalfCircleDegree3 : public NURBSCurve<2, Real> + { + public: + // Construction. The half circle is x^2 + y^2 = 1 for x >= 0. The + // direction of traversal is counterclockwise as u increases from + // 0 to 1. + NURBSHalfCircleDegree3() + : + NURBSCurve<2, Real>(BasisFunctionInput(4, 3), nullptr, nullptr) + { + Real const oneThird = (Real)1 / (Real)3; + this->mWeights[0] = (Real)1; + this->mWeights[1] = oneThird; + this->mWeights[2] = oneThird; + this->mWeights[3] = (Real)1; + + this->mControls[0] = { (Real)1, (Real)0 }; + this->mControls[1] = { (Real)1, (Real)2 }; + this->mControls[2] = { (Real)-1, (Real)2 }; + this->mControls[3] = { (Real)-1, (Real)0 }; + } + }; + + template + class NURBSFullCircleDegree3 : public NURBSCurve<2, Real> + { + public: + // Construction. The full circle is x^2 + y^2 = 1. The direction of + // traversal is counterclockwise as u increases from 0 to 1. + NURBSFullCircleDegree3() + : + NURBSCurve<2, Real>(CreateBasisFunctionInput(), nullptr, nullptr) + { + Real const oneThird = (Real)1 / (Real)3; + this->mWeights[0] = (Real)1; + this->mWeights[1] = oneThird; + this->mWeights[2] = oneThird; + this->mWeights[3] = (Real)1; + this->mWeights[4] = oneThird; + this->mWeights[5] = oneThird; + this->mWeights[6] = (Real)1; + + this->mControls[0] = { (Real)1, (Real)0 }; + this->mControls[1] = { (Real)1, (Real)2 }; + this->mControls[2] = { (Real)-1, (Real)2 }; + this->mControls[3] = { (Real)-1, (Real)0 }; + this->mControls[4] = { (Real)-1, (Real)-2 }; + this->mControls[5] = { (Real)1, (Real)-2 }; + this->mControls[6] = { (Real)1, (Real)0 }; + } + + private: + static BasisFunctionInput CreateBasisFunctionInput() + { + // We need knots (0,0,0,0,1/2,1/2,1/2,1,1,1,1). + BasisFunctionInput input; + input.numControls = 7; + input.degree = 3; + input.uniform = true; + input.periodic = false; + input.numUniqueKnots = 3; + input.uniqueKnots.resize(input.numUniqueKnots); + input.uniqueKnots[0] = { (Real)0, 4 }; + input.uniqueKnots[1] = { (Real)0.5, 3 }; + input.uniqueKnots[2] = { (Real)1, 4 }; + return input; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/NURBSCurve.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/NURBSCurve.h new file mode 100644 index 0000000..676c796 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/NURBSCurve.h @@ -0,0 +1,216 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class NURBSCurve : public ParametricCurve + { + public: + // Construction. If the input controls is non-null, a copy is made of + // the controls. To defer setting the control points or weights, pass + // null pointers and later access the control points or weights via + // GetControls(), GetWeights(), SetControl(), or SetWeight() member + // functions. The domain is t in [t[d],t[n]], where t[d] and t[n] are + // knots with d the degree and n the number of control points. + NURBSCurve(BasisFunctionInput const& input, + Vector const* controls, Real const* weights) + : + ParametricCurve((Real)0, (Real)1), + mBasisFunction(input) + { + // The mBasisFunction stores the domain but so does + // ParametricCurve. + this->mTime.front() = mBasisFunction.GetMinDomain(); + this->mTime.back() = mBasisFunction.GetMaxDomain(); + + // The replication of control points for periodic splines is + // avoided by wrapping the i-loop index in Evaluate. + mControls.resize(input.numControls); + mWeights.resize(input.numControls); + if (controls) + { + std::copy(controls, controls + input.numControls, mControls.begin()); + } + else + { + Vector zero{ (Real)0 }; + std::fill(mControls.begin(), mControls.end(), zero); + } + if (weights) + { + std::copy(weights, weights + input.numControls, mWeights.begin()); + } + else + { + std::fill(mWeights.begin(), mWeights.end(), (Real)0); + } + this->mConstructed = true; + } + + // Member access. + inline BasisFunction const& GetBasisFunction() const + { + return mBasisFunction; + } + + inline int GetNumControls() const + { + return static_cast(mControls.size()); + } + + inline Vector const* GetControls() const + { + return mControls.data(); + } + + inline Vector* GetControls() + { + return mControls.data(); + } + + inline Real const* GetWeights() const + { + return mWeights.data(); + } + + inline Real* GetWeights() + { + return mWeights.data(); + } + + void SetControl(int i, Vector const& control) + { + if (0 <= i && i < GetNumControls()) + { + mControls[i] = control; + } + } + + Vector const& GetControl(int i) const + { + if (0 <= i && i < GetNumControls()) + { + return mControls[i]; + } + else + { + // Invalid index, return something. + return mControls[0]; + } + } + + void SetWeight(int i, Real weight) + { + if (0 <= i && i < GetNumControls()) + { + mWeights[i] = weight; + } + } + + Real const& GetWeight(int i) const + { + if (0 <= i && i < GetNumControls()) + { + return mWeights[i]; + } + else + { + // Invalid index, return something. + return mWeights[0]; + } + } + + // Evaluation of the curve. The function supports derivative + // calculation through order 3; that is, order <= 3 is required. If + // you want/ only the position, pass in order of 0. If you want the + // position and first derivative, pass in order of 1, and so on. The + // output array 'jet' must have enough storage to support the maximum + // order. The values are ordered as: position, first derivative, + // second derivative, third derivative. + virtual void Evaluate(Real t, unsigned int order, Vector* jet) const override + { + unsigned int const supOrder = ParametricCurve::SUP_ORDER; + if (!this->mConstructed || order >= supOrder) + { + // Return a zero-valued jet for invalid state. + for (unsigned int i = 0; i < supOrder; ++i) + { + jet[i].MakeZero(); + } + return; + } + + int imin, imax; + mBasisFunction.Evaluate(t, order, imin, imax); + + // Compute position. + Vector X; + Real w; + Compute(0, imin, imax, X, w); + Real invW = (Real)1 / w; + jet[0] = invW * X; + + if (order >= 1) + { + // Compute first derivative. + Vector XDer1; + Real wDer1; + Compute(1, imin, imax, XDer1, wDer1); + jet[1] = invW * (XDer1 - wDer1 * jet[0]); + + if (order >= 2) + { + // Compute second derivative. + Vector XDer2; + Real wDer2; + Compute(2, imin, imax, XDer2, wDer2); + jet[2] = invW * (XDer2 - (Real)2 * wDer1 * jet[1] - wDer2 * jet[0]); + + if (order == 3) + { + // Compute third derivative. + Vector XDer3; + Real wDer3; + Compute(3, imin, imax, XDer3, wDer3); + jet[3] = invW * (XDer3 - (Real)3 * wDer1 * jet[2] - + (Real)3 * wDer2 * jet[1] - wDer3 * jet[0]); + } + } + } + } + + protected: + // Support for Evaluate(...). + void Compute(unsigned int order, int imin, int imax, Vector& X, Real& w) const + { + // The j-index introduces a tiny amount of overhead in order to + // handle both aperiodic and periodic splines. For aperiodic + // splines, j = i always. + + int numControls = GetNumControls(); + X.MakeZero(); + w = (Real)0; + for (int i = imin; i <= imax; ++i) + { + int j = (i >= numControls ? i - numControls : i); + Real tmp = mBasisFunction.GetValue(order, i) * mWeights[j]; + X += tmp * mControls[j]; + w += tmp; + } + } + + BasisFunction mBasisFunction; + std::vector> mControls; + std::vector mWeights; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/NURBSSphere.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/NURBSSphere.h new file mode 100644 index 0000000..7d4df3a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/NURBSSphere.h @@ -0,0 +1,459 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The algorithm for representing a circle as a NURBS curve or a sphere as a +// NURBS surface is described in +// https://www.geometrictools.com/Documentation/NURBSCircleSphere.pdf +// The implementations are related to the documents as shown next. +// NURBSEighthSphereDegree4 implements Section 3.1.2 (triangular domain) +// NURBSHalfSphereDegree3 implements Section 3.2 (rectangular domain) +// NURBSFullSphereDegree3 implements Section 2.3 (rectangular domain) +// TODO: The class NURBSSurface currently assumes a rectangular domain. +// Once support is added for triangular domains, make that new class a +// base class of the sphere-representing NURBS. This will allow sharing +// of the NURBS basis functions and evaluation framework. + +namespace WwiseGTE +{ + template + class NURBSEighthSphereDegree4 + { + public: + // Construction. The eigth sphere is x^2 + y^2 + z^2 = 1 for x >= 0, + // y >= 0 and z >= 0. + NURBSEighthSphereDegree4() + { + Real const sqrt2 = std::sqrt((Real)2); + Real const sqrt3 = std::sqrt((Real)3); + Real const a0 = (sqrt3 - (Real)1) / sqrt3; + Real const a1 = (sqrt3 + (Real)1) / ((Real)2 * sqrt3); + Real const a2 = (Real)1 - ((Real)5 - sqrt2) * ((Real)7 - sqrt3) / (Real)46; + Real const b0 = (Real)4 * sqrt3 * (sqrt3 - (Real)1); + Real const b1 = (Real)3 * sqrt2; + Real const b2 = (Real)4; + Real const b3 = sqrt2 * ((Real)3 + (Real)2 * sqrt2 - sqrt3) / sqrt3; + + mControls[0][0] = { (Real)0, (Real)0, (Real)1 }; // P004 + mControls[0][1] = { (Real)0, a0, (Real)1 }; // P013 + mControls[0][2] = { (Real)0, a1, a1 }; // P022 + mControls[0][3] = { (Real)0, (Real)1, a0 }; // P031 + mControls[0][4] = { (Real)0, (Real)1, (Real)0 }; // P040 + + mControls[1][0] = { a0, (Real)0, (Real)1 }; // P103 + mControls[1][1] = { a2, a2, (Real)1 }; // P112 + mControls[1][2] = { a2, (Real)1, a2 }; // P121 + mControls[1][3] = { a0, (Real)1, (Real)0 }; // P130 + mControls[1][4] = { (Real)0, (Real)0, (Real)0 }; // unused + + mControls[2][0] = { a1, (Real)0, a1 }; // P202 + mControls[2][1] = { (Real)1, a2, a2 }; // P211 + mControls[2][2] = { a1, a1, (Real)0 }; // P220 + mControls[2][3] = { (Real)0, (Real)0, (Real)0 }; // unused + mControls[2][4] = { (Real)0, (Real)0, (Real)0 }; // unused + + mControls[3][0] = { (Real)1, (Real)0, a0 }; // P301 + mControls[3][1] = { (Real)1, a0, (Real)0 }; // P310 + mControls[3][2] = { (Real)0, (Real)0, (Real)0 }; // unused + mControls[3][3] = { (Real)0, (Real)0, (Real)0 }; // unused + mControls[3][4] = { (Real)0, (Real)0, (Real)0 }; // unused + + mControls[4][0] = { (Real)1, (Real)0, (Real)0 }; // P400 + mControls[4][1] = { (Real)0, (Real)0, (Real)0 }; // unused + mControls[4][2] = { (Real)0, (Real)0, (Real)0 }; // unused + mControls[4][3] = { (Real)0, (Real)0, (Real)0 }; // unused + mControls[4][4] = { (Real)0, (Real)0, (Real)0 }; // unused + + mWeights[0][0] = b0; // w004 + mWeights[0][1] = b1; // w013 + mWeights[0][2] = b2; // w022 + mWeights[0][3] = b1; // w031 + mWeights[0][4] = b0; // w040 + + mWeights[1][0] = b1; // w103 + mWeights[1][1] = b3; // w112 + mWeights[1][2] = b3; // w121 + mWeights[1][3] = b1; // w130 + mWeights[1][4] = (Real)0; // unused + + mWeights[2][0] = b2; // w202 + mWeights[2][1] = b3; // w211 + mWeights[2][2] = b2; // w220 + mWeights[2][3] = (Real)0; // unused + mWeights[2][4] = (Real)0; // unused + + mWeights[3][0] = b1; // w301 + mWeights[3][1] = b1; // w310 + mWeights[3][2] = (Real)0; // unused + mWeights[3][3] = (Real)0; // unused + mWeights[3][4] = (Real)0; // unused + + mWeights[4][0] = b0; // w400 + mWeights[4][1] = (Real)0; // unused + mWeights[4][2] = (Real)0; // unused + mWeights[4][3] = (Real)0; // unused + mWeights[4][4] = (Real)0; // unused + } + + // Evaluation of the surface. The function supports derivative + // calculation through order 2; that is, maxOrder <= 2 is required. + // If you want only the position, pass in maxOrder of 0. If you want + // the position and first-order derivatives, pass in maxOrder of 1, + // and so on. The output 'values' are ordered as: position X; + // first-order derivatives dX/du, dX/dv; second-order derivatives + // d2X/du2, d2X/dudv, d2X/dv2. + void Evaluate(Real u, Real v, unsigned int maxOrder, Vector<3, Real> values[6]) const + { + // TODO: Some of the polynomials are used in other polynomials. + // Optimize the code by eliminating the redundant computations. + + Real w = (Real)1 - u - v; + Real uu = u * u, uv = u * v, uw = u * w, vv = v * v, vw = v * w, ww = w * w; + + // Compute the order-0 polynomials. Only the elements to be used + // are filled in. The other terms are uninitialized but never + // used. + Real B[5][5]; + B[0][0] = ww * ww; + B[0][1] = (Real)4 * vw * ww; + B[0][2] = (Real)6 * vv * ww; + B[0][3] = (Real)4 * vv * vw; + B[0][4] = vv * vv; + B[1][0] = (Real)4 * uw * ww; + B[1][1] = (Real)12 * uv * ww; + B[1][2] = (Real)12 * uv * vw; + B[1][3] = (Real)4 * uv * vv; + B[2][0] = (Real)6 * uu * ww; + B[2][1] = (Real)12 * uu * vw; + B[2][2] = (Real)6 * uu * vv; + B[3][0] = (Real)4 * uu * uw; + B[3][1] = (Real)4 * uu * uv; + B[4][0] = uu * uu; + + // Compute the NURBS position. + Vector<3, Real> N{ (Real)0, (Real)0, (Real)0 }; + Real D(0); + for (int j1 = 0; j1 <= 4; ++j1) + { + for (int j0 = 0; j0 <= 4 - j1; ++j0) + { + Real product = mWeights[j1][j0] * B[j1][j0]; + N += product * mControls[j1][j0]; + D += product; + } + } + values[0] = N / D; + + if (maxOrder >= 1) + { + // Compute the order-1 polynomials. Only the elements to be + // used are filled in. The other terms are uninitialized but + // never used. + Real WmU = w - u; + Real WmTwoU = WmU - u; + Real WmThreeU = WmTwoU - u; + Real TwoWmU = w + WmU; + Real ThreeWmU = w + TwoWmU; + Real WmV = w - v; + Real WmTwoV = WmV - v; + Real WmThreeV = WmTwoV - v; + Real TwoWmV = w + WmV; + Real ThreeWmV = w + TwoWmV; + Real Dsqr = D * D; + + Real Bu[5][5]; + Bu[0][0] = (Real)-4 * ww * w; + Bu[0][1] = (Real)-12 * v * ww; + Bu[0][2] = (Real)-12 * vv * w; + Bu[0][3] = (Real)-4 * v * vv; + Bu[0][4] = (Real)0; + Bu[1][0] = (Real)4 * ww * WmThreeU; + Bu[1][1] = (Real)12 * vw * WmTwoU; + Bu[1][2] = (Real)12 * vv * WmU; + Bu[1][3] = (Real)4 * vv; + Bu[2][0] = (Real)12 * uw * WmU; + Bu[2][1] = (Real)12 * uv * TwoWmU; + Bu[2][2] = (Real)12 * u * vv; + Bu[3][0] = (Real)4 * uu * ThreeWmU; + Bu[3][1] = (Real)12 * uu * v; + Bu[4][0] = (Real)4 * uu * u; + + Real Bv[5][5]; + Bv[0][0] = (Real)-4 * ww * w; + Bv[0][1] = (Real)4 * ww * WmThreeV; + Bv[0][2] = (Real)12 * vw * WmV; + Bv[0][3] = (Real)4 * vv * ThreeWmV; + Bv[0][4] = (Real)4 * vv * v; + Bv[1][0] = (Real)-12 * u * ww; + Bv[1][1] = (Real)12 * uw * WmTwoV; + Bv[1][2] = (Real)12 * uv * TwoWmV; + Bv[1][3] = (Real)12 * u * vv; + Bv[2][0] = (Real)-12 * uu * w; + Bv[2][1] = (Real)12 * uu * WmV; + Bv[2][2] = (Real)12 * uu * v; + Bv[3][0] = (Real)-4 * uu * u; + Bv[3][1] = (Real)4 * uu * u; + Bv[4][0] = (Real)0; + + Vector<3, Real> Nu{ (Real)0, (Real)0, (Real)0 }; + Vector<3, Real> Nv{ (Real)0, (Real)0, (Real)0 }; + Real Du(0), Dv(0); + for (int j1 = 0; j1 <= 4; ++j1) + { + for (int j0 = 0; j0 <= 4 - j1; ++j0) + { + Real product = mWeights[j1][j0] * Bu[j1][j0]; + Nu += product * mControls[j1][j0]; + Du += product; + product = mWeights[j1][j0] * Bv[j1][j0]; + Nv += product * mControls[j1][j0]; + Dv += product; + } + } + Vector<3, Real> numerDU = D * Nu - Du * N; + Vector<3, Real> numerDV = D * Nv - Dv * N; + values[1] = numerDU / Dsqr; + values[2] = numerDV / Dsqr; + + if (maxOrder >= 2) + { + // Compute the order-2 polynomials. Only the elements to + // be used are filled in. The other terms are + // uninitialized but never used. + Real Dcub = Dsqr * D; + + Real Buu[5][5]; + Buu[0][0] = (Real)12 * ww; + Buu[0][1] = (Real)24 * vw; + Buu[0][2] = (Real)12 * vv; + Buu[0][3] = (Real)0; + Buu[0][4] = (Real)0; + Buu[1][0] = (Real)-24 * w * WmU; + Buu[1][1] = (Real)-24 * v * TwoWmU; + Buu[1][2] = (Real)-24 * vv; + Buu[1][3] = (Real)0; + Buu[2][0] = (Real)12 * (ww - (Real)4 * uw + uu); + Buu[2][1] = (Real)24 * v * WmTwoU; + Buu[2][2] = (Real)12 * vv; + Buu[3][0] = (Real)24 * u * WmU; + Buu[3][1] = (Real)24 * uv; + Buu[4][0] = (Real)12 * uu; + + Real Buv[5][5]; + Buv[0][0] = (Real)12 * ww; + Buv[0][1] = (Real)-12 * w * WmTwoV; + Buv[0][2] = (Real)-12 * v * TwoWmV; + Buv[0][3] = (Real)-12 * vv; + Buv[0][4] = (Real)0; + Buv[1][0] = (Real)-12 * w * WmTwoU; + Buv[1][1] = (Real)12 * (ww + (Real)2 * (uv - uw - vw)); + Buv[1][2] = (Real)12 * v * ((Real)2 * WmU - v); + Buv[1][3] = (Real)12 * vv; + Buv[2][0] = (Real)-12 * u * TwoWmU; + Buv[2][1] = (Real)12 * u * ((Real)2 * WmV - u); + Buv[2][2] = (Real)24 * uv; + Buv[3][0] = (Real)-12 * uu; + Buv[3][1] = (Real)12 * uu; + Buv[4][0] = (Real)0; + + Real Bvv[5][5]; + Bvv[0][0] = (Real)12 * ww; + Bvv[0][1] = (Real)-24 * w * WmV; + Bvv[0][2] = (Real)12 * (ww - (Real)4 * vw + vv); + Bvv[0][3] = (Real)24 * v * WmV; + Bvv[0][4] = (Real)12 * vv; + Bvv[1][0] = (Real)24 * uw; + Bvv[1][1] = (Real)-24 * u * TwoWmV; + Bvv[1][2] = (Real)24 * u * WmTwoV; + Bvv[1][3] = (Real)24 * uv; + Bvv[2][0] = (Real)12 * uu; + Bvv[2][1] = (Real)-24 * uu; + Bvv[2][2] = (Real)12 * uu; + Bvv[3][0] = (Real)0; + Bvv[3][1] = (Real)0; + Bvv[4][0] = (Real)0; + + Vector<3, Real> Nuu{ (Real)0, (Real)0, (Real)0 }; + Vector<3, Real> Nuv{ (Real)0, (Real)0, (Real)0 }; + Vector<3, Real> Nvv{ (Real)0, (Real)0, (Real)0 }; + Real Duu(0), Duv(0), Dvv(0); + for (int j1 = 0; j1 <= 4; ++j1) + { + for (int j0 = 0; j0 <= 4 - j1; ++j0) + { + Real product = mWeights[j1][j0] * Buu[j1][j0]; + Nuu += product * mControls[j1][j0]; + Duu += product; + product = mWeights[j1][j0] * Buv[j1][j0]; + Nuv += product * mControls[j1][j0]; + Duv += product; + product = mWeights[j1][j0] * Bvv[j1][j0]; + Nvv += product * mControls[j1][j0]; + Dvv += product; + } + } + Vector<3, Real> termDuu = D * (D * Nuu - Duu * N); + Vector<3, Real> termDuv = D * (D * Nuv - Duv * N - Du * Nv - Dv * Nu); + Vector<3, Real> termDvv = D * (D * Nvv - Dvv * N); + values[3] = (D * termDuu - (Real)2 * Du * numerDU) / Dcub; + values[4] = (D * termDuv + (Real)2 * Du * Dv * N) / Dcub; + values[5] = (D * termDvv - (Real)2 * Dv * numerDV) / Dcub; + } + } + } + + private: + // For simplicity of the implementation, 2-dimensional arrays + // of size 5-by-5 are used. Only array[r][c] is used where + // 0 <= r <= 4 and 0 <= c < 4 - r. + Vector3 mControls[5][5]; + Real mWeights[5][5]; + }; + + template + class NURBSHalfSphereDegree3 : public NURBSSurface<3, Real> + { + public: + NURBSHalfSphereDegree3() + : + NURBSSurface<3, Real>(BasisFunctionInput(4, 3), + BasisFunctionInput(4, 3), nullptr, nullptr) + { + // weight[j][i] is mWeights[i + 4 * j], 0 <= i < 4, 0 <= j < 4 + Real const oneThird = (Real)1 / (Real)3; + Real const oneNinth = (Real)1 / (Real)9; + this->mWeights[0] = (Real)1; + this->mWeights[1] = oneThird; + this->mWeights[2] = oneThird; + this->mWeights[3] = (Real)1; + this->mWeights[4] = oneThird; + this->mWeights[5] = oneNinth; + this->mWeights[6] = oneNinth; + this->mWeights[7] = oneThird; + this->mWeights[8] = oneThird; + this->mWeights[9] = oneNinth; + this->mWeights[10] = oneNinth; + this->mWeights[11] = oneThird; + this->mWeights[12] = (Real)1; + this->mWeights[13] = oneThird; + this->mWeights[14] = oneThird; + this->mWeights[15] = (Real)1; + + // control[j][i] is mControls[i + 4 * j], 0 <= i < 4, 0 <= j < 4 + this->mControls[0] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[1] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[2] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[3] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[4] = { (Real)2, (Real)0, (Real)1 }; + this->mControls[5] = { (Real)2, (Real)4, (Real)1 }; + this->mControls[6] = { (Real)-2, (Real)4, (Real)1 }; + this->mControls[7] = { (Real)-2, (Real)0, (Real)1 }; + this->mControls[8] = { (Real)2, (Real)0, (Real)-1 }; + this->mControls[9] = { (Real)2, (Real)4, (Real)-1 }; + this->mControls[10] = { (Real)-2, (Real)4, (Real)-1 }; + this->mControls[11] = { (Real)-2, (Real)0, (Real)-1 }; + this->mControls[12] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[13] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[14] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[15] = { (Real)0, (Real)0, (Real)-1 }; + } + }; + + template + class NURBSFullSphereDegree3 : public NURBSSurface<3, Real> + { + public: + NURBSFullSphereDegree3() + : + NURBSSurface<3, Real>(BasisFunctionInput(4, 3), + CreateBasisFunctionInputV(), nullptr, nullptr) + { + // weight[j][i] is mWeights[i + 4 * j], 0 <= i < 4, 0 <= j < 7 + Real const oneThird = (Real)1 / (Real)3; + Real const oneNinth = (Real)1 / (Real)9; + this->mWeights[0] = (Real)1; + this->mWeights[4] = oneThird; + this->mWeights[8] = oneThird; + this->mWeights[12] = (Real)1; + this->mWeights[16] = oneThird; + this->mWeights[20] = oneThird; + this->mWeights[24] = (Real)1; + this->mWeights[1] = oneThird; + this->mWeights[5] = oneNinth; + this->mWeights[9] = oneNinth; + this->mWeights[13] = oneThird; + this->mWeights[17] = oneNinth; + this->mWeights[21] = oneNinth; + this->mWeights[25] = oneThird; + this->mWeights[2] = oneThird; + this->mWeights[6] = oneNinth; + this->mWeights[10] = oneNinth; + this->mWeights[14] = oneThird; + this->mWeights[18] = oneNinth; + this->mWeights[22] = oneNinth; + this->mWeights[26] = oneThird; + this->mWeights[3] = (Real)1; + this->mWeights[7] = oneThird; + this->mWeights[11] = oneThird; + this->mWeights[15] = (Real)1; + this->mWeights[19] = oneThird; + this->mWeights[23] = oneThird; + this->mWeights[27] = (Real)1; + + // control[j][i] is mControls[i + 4 * j], 0 <= i < 4, 0 <= j < 7 + this->mControls[0] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[4] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[8] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[12] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[16] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[20] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[24] = { (Real)0, (Real)0, (Real)1 }; + this->mControls[1] = { (Real)2, (Real)0, (Real)1 }; + this->mControls[5] = { (Real)2, (Real)4, (Real)1 }; + this->mControls[9] = { (Real)-2, (Real)4, (Real)1 }; + this->mControls[13] = { (Real)-2, (Real)0, (Real)1 }; + this->mControls[17] = { (Real)-2, (Real)-4, (Real)1 }; + this->mControls[21] = { (Real)2, (Real)-4, (Real)1 }; + this->mControls[25] = { (Real)2, (Real)0, (Real)1 }; + this->mControls[2] = { (Real)2, (Real)0, (Real)-1 }; + this->mControls[6] = { (Real)2, (Real)4, (Real)-1 }; + this->mControls[10] = { (Real)-2, (Real)4, (Real)-1 }; + this->mControls[14] = { (Real)-2, (Real)0, (Real)-1 }; + this->mControls[18] = { (Real)-2, (Real)-4, (Real)-1 }; + this->mControls[22] = { (Real)2, (Real)-4, (Real)-1 }; + this->mControls[26] = { (Real)2, (Real)0, (Real)-1 }; + this->mControls[3] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[7] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[11] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[15] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[19] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[23] = { (Real)0, (Real)0, (Real)-1 }; + this->mControls[27] = { (Real)0, (Real)0, (Real)-1 }; + } + + private: + static BasisFunctionInput CreateBasisFunctionInputV() + { + BasisFunctionInput input; + input.numControls = 7; + input.degree = 3; + input.uniform = true; + input.periodic = false; + input.numUniqueKnots = 3; + input.uniqueKnots.resize(input.numUniqueKnots); + input.uniqueKnots[0] = { (Real)0, 4 }; + input.uniqueKnots[1] = { (Real)0.5, 3 }; + input.uniqueKnots[2] = { (Real)1, 4 }; + return input; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/NURBSSurface.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/NURBSSurface.h new file mode 100644 index 0000000..ed055a8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/NURBSSurface.h @@ -0,0 +1,244 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.12.28 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class NURBSSurface : public ParametricSurface + { + public: + // Construction. If the input controls is non-null, a copy is made of + // the controls. To defer setting the control points or weights, pass + // null pointers and later access the control points or weights via + // GetControls(), GetWeights(), SetControl(), or SetWeight() member + // functions. The 'controls' and 'weights' must be stored in + // row-major order, attribute[i0 + numControls0*i1]. As a 2D array, + // this corresponds to attribute2D[i1][i0]. + NURBSSurface(BasisFunctionInput const& input0, + BasisFunctionInput const& input1, + Vector const* controls, Real const* weights) + : + ParametricSurface((Real)0, (Real)1, (Real)0, (Real)1, true) + { + BasisFunctionInput const* input[2] = { &input0, &input1 }; + for (int i = 0; i < 2; ++i) + { + mNumControls[i] = input[i]->numControls; + mBasisFunction[i].Create(*input[i]); + } + + // The mBasisFunction stores the domain but so does + // ParametricSurface. + this->mUMin = mBasisFunction[0].GetMinDomain(); + this->mUMax = mBasisFunction[0].GetMaxDomain(); + this->mVMin = mBasisFunction[1].GetMinDomain(); + this->mVMax = mBasisFunction[1].GetMaxDomain(); + + // The replication of control points for periodic splines is + // avoided by wrapping the i-loop index in Evaluate. + int numControls = mNumControls[0] * mNumControls[1]; + mControls.resize(numControls); + mWeights.resize(numControls); + if (controls) + { + std::copy(controls, controls + numControls, mControls.begin()); + } + else + { + Vector zero{ (Real)0 }; + std::fill(mControls.begin(), mControls.end(), zero); + } + if (weights) + { + std::copy(weights, weights + numControls, mWeights.begin()); + } + else + { + std::fill(mWeights.begin(), mWeights.end(), (Real)0); + } + this->mConstructed = true; + } + + // Member access. The index 'dim' must be in {0,1}. + inline BasisFunction const& GetBasisFunction(int dim) const + { + return mBasisFunction[dim]; + } + + inline int GetNumControls(int dim) const + { + return mNumControls[dim]; + } + + inline Vector const* GetControls() const + { + return mControls.data(); + } + + inline Vector* GetControls() + { + return mControls.data(); + } + + inline Real const* GetWeights() const + { + return mWeights.data(); + } + + inline Real* GetWeights() + { + return mWeights.data(); + } + + void SetControl(int i0, int i1, Vector const& control) + { + if (0 <= i0 && i0 < GetNumControls(0) && 0 <= i1 && i1 < GetNumControls(1)) + { + mControls[i0 + mNumControls[0] * i1] = control; + } + } + + Vector const& GetControl(int i0, int i1) const + { + if (0 <= i0 && i0 < GetNumControls(0) && 0 <= i1 && i1 < GetNumControls(1)) + { + return mControls[i0 + mNumControls[0] * i1]; + } + else + { + return mControls[0]; + } + } + + void SetWeight(int i0, int i1, Real weight) + { + if (0 <= i0 && i0 < GetNumControls(0) && 0 <= i1 && i1 < GetNumControls(1)) + { + mWeights[i0 + mNumControls[0] * i1] = weight; + } + } + + Real const& GetWeight(int i0, int i1) const + { + if (0 <= i0 && i0 < GetNumControls(0) && 0 <= i1 && i1 < GetNumControls(1)) + { + return mWeights[i0 + mNumControls[0] * i1]; + } + else + { + return mWeights[0]; + } + } + + // Evaluation of the surface. The function supports derivative + // calculation through order 2; that is, order <= 2 is required. If + // you want only the position, pass in order of 0. If you want the + // position and first-order derivatives, pass in order of 1, and so + // on. The output array 'jet' must have enough storage to support the + // maximum order. The values are ordered as: position X; first-order + // derivatives dX/du, dX/dv; second-order derivatives d2X/du2, + // d2X/dudv, d2X/dv2. + virtual void Evaluate(Real u, Real v, unsigned int order, Vector* jet) const override + { + unsigned int const supOrder = ParametricSurface::SUP_ORDER; + if (!this->mConstructed || order >= supOrder) + { + // Return a zero-valued jet for invalid state. + for (unsigned int i = 0; i < supOrder; ++i) + { + jet[i].MakeZero(); + } + return; + } + + int iumin, iumax, ivmin, ivmax; + mBasisFunction[0].Evaluate(u, order, iumin, iumax); + mBasisFunction[1].Evaluate(v, order, ivmin, ivmax); + + // Compute position. + Vector X; + Real w; + Compute(0, 0, iumin, iumax, ivmin, ivmax, X, w); + Real invW = (Real)1 / w; + jet[0] = invW * X; + + if (order >= 1) + { + // Compute first-order derivatives. + Vector XDerU; + Real wDerU; + Compute(1, 0, iumin, iumax, ivmin, ivmax, XDerU, wDerU); + jet[1] = invW * (XDerU - wDerU * jet[0]); + + Vector XDerV; + Real wDerV; + Compute(0, 1, iumin, iumax, ivmin, ivmax, XDerV, wDerV); + jet[2] = invW * (XDerV - wDerV * jet[0]); + + if (order >= 2) + { + // Compute second-order derivatives. + Vector XDerUU; + Real wDerUU; + Compute(2, 0, iumin, iumax, ivmin, ivmax, XDerUU, wDerUU); + jet[3] = invW * (XDerUU - (Real)2 * wDerU * jet[1] - wDerUU * jet[0]); + + Vector XDerUV; + Real wDerUV; + Compute(1, 1, iumin, iumax, ivmin, ivmax, XDerUV, wDerUV); + jet[4] = invW * (XDerUV - wDerU * jet[2] - wDerV * jet[1] + - wDerUV * jet[0]); + + Vector XDerVV; + Real wDerVV; + Compute(0, 2, iumin, iumax, ivmin, ivmax, XDerVV, wDerVV); + jet[5] = invW * (XDerVV - (Real)2 * wDerV * jet[2] - wDerVV * jet[0]); + } + } + } + + protected: + // Support for Evaluate(...). + void Compute(unsigned int uOrder, unsigned int vOrder, int iumin, + int iumax, int ivmin, int ivmax, Vector& X, Real& w) const + { + // The j*-indices introduce a tiny amount of overhead in order to handle + // both aperiodic and periodic splines. For aperiodic splines, j* = i* + // always. + + int const numControls0 = mNumControls[0]; + int const numControls1 = mNumControls[1]; + X.MakeZero(); + w = (Real)0; + for (int iv = ivmin; iv <= ivmax; ++iv) + { + Real tmpv = mBasisFunction[1].GetValue(vOrder, iv); + int jv = (iv >= numControls1 ? iv - numControls1 : iv); + for (int iu = iumin; iu <= iumax; ++iu) + { + Real tmpu = mBasisFunction[0].GetValue(uOrder, iu); + int ju = (iu >= numControls0 ? iu - numControls0 : iu); + int index = ju + numControls0 * jv; + Real tmp = tmpu * tmpv * mWeights[index]; + X += tmp * mControls[index]; + w += tmp; + } + } + } + + std::array, 2> mBasisFunction; + std::array mNumControls; + std::vector> mControls; + std::vector mWeights; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/NURBSVolume.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/NURBSVolume.h new file mode 100644 index 0000000..b2acdd6 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/NURBSVolume.h @@ -0,0 +1,246 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.12.28 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class NURBSVolume + { + public: + // Construction. If the input controls is non-null, a copy is made of + // the controls. To defer setting the control points or weights, pass + // null pointers and later access the control points or weights via + // GetControls(), GetWeights(), SetControl(), or SetWeight() member + // functions. The 'controls' and 'weights' must be stored in + // lexicographical order, + // attribute[i0 + numControls0 * (i1 + numControls1 * i2)] + // As a 3D array, this corresponds to attribute3D[i2][i1][i0]. + NURBSVolume(BasisFunctionInput const& input0, + BasisFunctionInput const& input1, + BasisFunctionInput const& input2, + Vector const* controls, Real const* weights) + : + mConstructed(false) + { + BasisFunctionInput const* input[3] = { &input0, &input1, &input2 }; + for (int i = 0; i < 3; ++i) + { + mNumControls[i] = input[i]->numControls; + mBasisFunction[i].Create(*input[i]); + } + + // The replication of control points for periodic splines is + // avoided by wrapping the i-loop index in Evaluate. + int numControls = mNumControls[0] * mNumControls[1] * mNumControls[2]; + mControls.resize(numControls); + mWeights.resize(numControls); + if (controls) + { + std::copy(controls, controls + numControls, mControls.begin()); + } + else + { + Vector zero{ (Real)0 }; + std::fill(mControls.begin(), mControls.end(), zero); + } + if (weights) + { + std::copy(weights, weights + numControls, mWeights.begin()); + } + else + { + std::fill(mWeights.begin(), mWeights.end(), (Real)0); + } + mConstructed = true; + } + + // To validate construction, create an object as shown: + // NURBSVolume volume(parameters); + // if (!volume) { ; } + inline operator bool() const + { + return mConstructed; + } + + // Member access. The index 'dim' must be in {0,1,2}. + inline BasisFunction const& GetBasisFunction(int dim) const + { + return mBasisFunction[dim]; + } + + inline Real GetMinDomain(int dim) const + { + return mBasisFunction[dim].GetMinDomain(); + } + + inline Real GetMaxDomain(int dim) const + { + return mBasisFunction[dim].GetMaxDomain(); + } + + inline int GetNumControls(int dim) const + { + return mNumControls[dim]; + } + + inline Vector const* GetControls() const + { + return mControls.data(); + } + + inline Vector* GetControls() + { + return mControls.data(); + } + + inline Real const* GetWeights() const + { + return mWeights.data(); + } + + inline Real* GetWeights() + { + return mWeights.data(); + } + + // Evaluation of the volume. The function supports derivative + // calculation through order 2; that is, order <= 2 is required. If + // you want only the position, pass in order of 0. If you want the + // position and first-order derivatives, pass in order of 1, and so + // on. The output array 'jet' muist have enough storage to support + // the maximum order. The values are ordered as: position X; + // first-order derivatives dX/du, dX/dv, dX/dw; second-order + // derivatives d2X/du2, d2X/dv2, d2X/dw2, d2X/dudv, d2X/dudw, + // d2X/dvdw. + enum { SUP_ORDER = 10 }; + void Evaluate(Real u, Real v, Real w, unsigned int order, Vector* jet) const + { + if (!mConstructed || order >= SUP_ORDER) + { + // Errors were already generated during construction. + for (unsigned int i = 0; i < SUP_ORDER; ++i) + { + jet[i].MakeZero(); + } + return; + } + + int iumin, iumax, ivmin, ivmax, iwmin, iwmax; + mBasisFunction[0].Evaluate(u, order, iumin, iumax); + mBasisFunction[1].Evaluate(v, order, ivmin, ivmax); + mBasisFunction[2].Evaluate(w, order, iwmin, iwmax); + + // Compute position. + Vector X; + Real h; + Compute(0, 0, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax, X, h); + Real invH = (Real)1 / h; + jet[0] = invH * X; + + if (order >= 1) + { + // Compute first-order derivatives. + Vector XDerU; + Real hDerU; + Compute(1, 0, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax, XDerU, hDerU); + jet[1] = invH * (XDerU - hDerU * jet[0]); + + Vector XDerV; + Real hDerV; + Compute(0, 1, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax, XDerV, hDerV); + jet[2] = invH * (XDerV - hDerV * jet[0]); + + Vector XDerW; + Real hDerW; + Compute(0, 0, 1, iumin, iumax, ivmin, ivmax, iwmin, iwmax, XDerW, hDerW); + jet[3] = invH * (XDerW - hDerW * jet[0]); + + if (order >= 2) + { + // Compute second-order derivatives. + Vector XDerUU; + Real hDerUU; + Compute(2, 0, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax, XDerUU, hDerUU); + jet[4] = invH * (XDerUU - (Real)2 * hDerU * jet[1] - hDerUU * jet[0]); + + Vector XDerVV; + Real hDerVV; + Compute(0, 2, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax, XDerVV, hDerVV); + jet[5] = invH * (XDerVV - (Real)2 * hDerV * jet[2] - hDerVV * jet[0]); + + Vector XDerWW; + Real hDerWW; + Compute(0, 0, 2, iumin, iumax, ivmin, ivmax, iwmin, iwmax, XDerWW, hDerWW); + jet[6] = invH * (XDerWW - (Real)2 * hDerW * jet[3] - hDerWW * jet[0]); + + Vector XDerUV; + Real hDerUV; + Compute(1, 1, 0, iumin, iumax, ivmin, ivmax, iwmin, iwmax, XDerUV, hDerUV); + jet[7] = invH * (XDerUV - hDerU * jet[2] - hDerV * jet[1] - hDerUV * jet[0]); + + Vector XDerUW; + Real hDerUW; + Compute(1, 0, 1, iumin, iumax, ivmin, ivmax, iwmin, iwmax, XDerUW, hDerUW); + jet[8] = invH * (XDerUW - hDerU * jet[3] - hDerW * jet[1] - hDerUW * jet[0]); + + Vector XDerVW; + Real hDerVW; + Compute(0, 1, 1, iumin, iumax, ivmin, ivmax, iwmin, iwmax, XDerVW, hDerVW); + jet[9] = invH * (XDerVW - hDerV * jet[3] - hDerW * jet[2] - hDerVW * jet[0]); + } + } + } + + private: + // Support for Evaluate(...). + void Compute(unsigned int uOrder, unsigned int vOrder, + unsigned int wOrder, int iumin, int iumax, int ivmin, int ivmax, + int iwmin, int iwmax, Vector& X, Real& h) const + { + // The j*-indices introduce a tiny amount of overhead in order to + // handle both aperiodic and periodic splines. For aperiodic + // splines, j* = i* always. + + int const numControls0 = mNumControls[0]; + int const numControls1 = mNumControls[1]; + int const numControls2 = mNumControls[2]; + X.MakeZero(); + h = (Real)0; + for (int iw = iwmin; iw <= iwmax; ++iw) + { + Real tmpw = mBasisFunction[2].GetValue(wOrder, iw); + int jw = (iw >= numControls2 ? iw - numControls2 : iw); + for (int iv = ivmin; iv <= ivmax; ++iv) + { + Real tmpv = mBasisFunction[1].GetValue(vOrder, iv); + Real tmpvw = tmpv * tmpw; + int jv = (iv >= numControls1 ? iv - numControls1 : iv); + for (int iu = iumin; iu <= iumax; ++iu) + { + Real tmpu = mBasisFunction[0].GetValue(uOrder, iu); + int ju = (iu >= numControls0 ? iu - numControls0 : iu); + int index = ju + numControls0 * (jv + numControls1 * jw); + Real tmp = (tmpu * tmpvw) * mWeights[index]; + X += tmp * mControls[index]; + h += tmp; + } + } + } + } + + std::array, 3> mBasisFunction; + std::array mNumControls; + std::vector> mControls; + std::vector mWeights; + bool mConstructed; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/NaturalSplineCurve.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/NaturalSplineCurve.h new file mode 100644 index 0000000..0148b23 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/NaturalSplineCurve.h @@ -0,0 +1,390 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class NaturalSplineCurve : public ParametricCurve + { + public: + // Construction and destruction. The object copies the input arrays. + // The number of points M must be at least 2. The first constructor + // is for a spline with second derivatives zero at the endpoints + // (isFree = true) or a spline that is closed (isFree = false). The + // second constructor is for clamped splines, where you specify the + // first derivatives at the endpoints. Usually, derivative0 = + // points[1] - points[0] at the first point and derivative1 = + // points[M-1] - points[M-2]. To validate construction, create an + // object as shown: + // NaturalSplineCurve curve(parameters); + // if (!curve) { ; } + NaturalSplineCurve(bool isFree, int numPoints, + Vector const* points, Real const* times) + : + ParametricCurve(numPoints - 1, times) + { + if (numPoints < 2 || !points) + { + LogError("Invalid input."); + return; + } + + auto const numSegments = numPoints - 1; + mCoefficients.resize(4 * numPoints + 1); + mA = &mCoefficients[0]; + mB = mA + numPoints; + mC = mB + numSegments; + mD = mC + numSegments + 1; + for (int i = 0; i < numPoints; ++i) + { + mA[i] = points[i]; + } + + if (isFree) + { + CreateFree(); + } + else + { + CreateClosed(); + } + + this->mConstructed = true; + } + + NaturalSplineCurve(int numPoints, Vector const* points, + Real const* times, Vector const& derivative0, + Vector const& derivative1) + : + ParametricCurve(numPoints - 1, times) + { + if (numPoints < 2 || !points) + { + LogError("Invalid input."); + return; + } + + auto const numSegments = numPoints - 1; + mCoefficients.resize(numPoints + 3 * numSegments + 1); + mA = &mCoefficients[0]; + for (int i = 0; i < numPoints; ++i) + { + mA[i] = points[i]; + } + + CreateClamped(derivative0, derivative1); + this->mConstructed = true; + } + + virtual ~NaturalSplineCurve() = default; + + // Member access. + inline int GetNumPoints() const + { + return static_cast((mCoefficients.size() - 1) / 4); + } + + inline Vector const* GetPoints() const + { + return &mA[0]; + } + + // Evaluation of the curve. The function supports derivative + // calculation through order 3; that is, order <= 3 is required. If + // you want/ only the position, pass in order of 0. If you want the + // position and first derivative, pass in order of 1, and so on. The + // output array 'jet' must have enough storage to support the maximum + // order. The values are ordered as: position, first derivative, + // second derivative, third derivative. + virtual void Evaluate(Real t, unsigned int order, Vector* jet) const override + { + unsigned int const supOrder = ParametricCurve::SUP_ORDER; + if (!this->mConstructed || order >= supOrder) + { + // Return a zero-valued jet for invalid state. + for (unsigned int i = 0; i < supOrder; ++i) + { + jet[i].MakeZero(); + } + return; + } + + int key = 0; + Real dt = (Real)0; + GetKeyInfo(t, key, dt); + + // Compute position. + jet[0] = mA[key] + dt * (mB[key] + dt * (mC[key] + dt * mD[key])); + if (order >= 1) + { + // Compute first derivative. + jet[1] = mB[key] + dt * ((Real)2 * mC[key] + (Real)3 * dt * mD[key]); + if (order >= 2) + { + // Compute second derivative. + jet[2] = (Real)2 * mC[key] + (Real)6 * dt * mD[key]; + if (order == 3) + { + jet[3] = (Real)6 * mD[key]; + } + } + } + } + + protected: + // Support for construction. + void CreateFree() + { + int numSegments = GetNumPoints() - 1; + WorkingData wd(numSegments); + for (int i = 0; i < numSegments; ++i) + { + wd.dt[i] = this->mTime[i + 1] - this->mTime[i]; + } + + std::vector d2t(numSegments); + for (int i = 1; i < numSegments; ++i) + { + wd.d2t[i] = this->mTime[i + 1] - this->mTime[i - 1]; + } + + std::vector> alpha(numSegments); + for (int i = 1; i < numSegments; ++i) + { + Vector numer = (Real)3 * (wd.dt[i - 1] * mA[i + 1] - wd.d2t[i] * mA[i] + wd.dt[i] * mA[i - 1]); + Real invDenom = (Real)1 / (wd.dt[i - 1] * wd.dt[i]); + wd.alpha[i] = invDenom * numer; + } + + std::vector ell(numSegments + 1); + std::vector mu(numSegments); + std::vector> z(numSegments + 1); + Real inv; + + wd.ell[0] = (Real)1; + wd.mu[0] = (Real)0; + wd.z[0].MakeZero(); + for (int i = 1; i < numSegments; ++i) + { + wd.ell[i] = (Real)2 * wd.d2t[i] - wd.dt[i - 1] * wd.mu[i - 1]; + inv = (Real)1 / wd.ell[i]; + wd.mu[i] = inv * wd.dt[i]; + wd.z[i] = inv * (wd.alpha[i] - wd.dt[i - 1] * wd.z[i - 1]); + } + wd.ell[numSegments] = (Real)1; + wd.z[numSegments].MakeZero(); + + Real const oneThird = (Real)1 / (Real)3; + mC[numSegments].MakeZero(); + for (int i = numSegments - 1; i >= 0; --i) + { + mC[i] = wd.z[i] - wd.mu[i] * mC[i + 1]; + inv = (Real)1 / wd.dt[i]; + mB[i] = inv * (mA[i + 1] - mA[i]) - oneThird * wd.dt[i] * (mC[i + 1] + (Real)2 * mC[i]); + mD[i] = oneThird * inv * (mC[i + 1] - mC[i]); + } + } + + void CreateClosed() + { + // TODO: A general linear system solver is used here. The matrix + // corresponding to this case is actually "cyclic banded", so a + // faster linear solver can be used. The current linear system + // code does not have such a solver. + + int numSegments = GetNumPoints() - 1; + std::vector dt(numSegments); + for (int i = 0; i < numSegments; ++i) + { + dt[i] = this->mTime[i + 1] - this->mTime[i]; + } + + // Construct matrix of system. + GMatrix mat(numSegments + 1, numSegments + 1); + mat(0, 0) = (Real)1; + mat(0, numSegments) = (Real)-1; + for (int i = 1; i <= numSegments - 1; ++i) + { + mat(i, i - 1) = dt[i - 1]; + mat(i, i) = (Real)2 * (dt[i - 1] + dt[i]); + mat(i, i + 1) = dt[i]; + } + mat(numSegments, numSegments - 1) = dt[numSegments - 1]; + mat(numSegments, 0) = (Real)2 * (dt[numSegments - 1] + dt[0]); + mat(numSegments, 1) = dt[0]; + + // Construct right-hand side of system. + mC[0].MakeZero(); + Real inv0, inv1; + for (int i = 1; i <= numSegments - 1; ++i) + { + inv0 = (Real)1 / dt[i]; + inv1 = (Real)1 / dt[i - 1]; + mC[i] = (Real)3 * (inv0 * (mA[i + 1] - mA[i]) - + inv1 * (mA[i] - mA[i - 1])); + } + inv0 = (Real)1 / dt[0]; + inv1 = (Real)1 / dt[numSegments - 1]; + mC[numSegments] = (Real)3 * (inv0 * (mA[1] - mA[0]) - + inv1 * (mA[0] - mA[numSegments - 1])); + + // Solve the linear systems. + GMatrix invMat = Inverse(mat); + GVector input(numSegments + 1); + GVector output(numSegments + 1); + for (int j = 0; j < N; ++j) + { + for (int i = 0; i <= numSegments; ++i) + { + input[i] = mC[i][j]; + } + output = invMat * input; + for (int i = 0; i <= numSegments; ++i) + { + mC[i][j] = output[i]; + } + } + + Real const oneThird = (Real)1 / (Real)3; + for (int i = 0; i < numSegments; ++i) + { + inv0 = (Real)1 / dt[i]; + mB[i] = inv0 * (mA[i + 1] - mA[i]) - oneThird * (mC[i + 1] + (Real)2 * mC[i]) * dt[i]; + mD[i] = oneThird * inv0 * (mC[i + 1] - mC[i]); + } + } + + void CreateClamped(Vector const& derivative0, Vector const& derivative1) + { + int numSegments = GetNumPoints() - 1; + std::vector dt(numSegments); + for (int i = 0; i < numSegments; ++i) + { + dt[i] = this->mTime[i + 1] - this->mTime[i]; + } + + std::vector d2t(numSegments); + for (int i = 1; i < numSegments; ++i) + { + d2t[i] = this->mTime[i + 1] - this->mTime[i - 1]; + } + + std::vector> alpha(numSegments + 1); + Real inv = (Real)1 / dt[0]; + alpha[0] = (Real)3 * (inv * (mA[1] - mA[0]) - derivative0); + inv = (Real)1 / dt[numSegments - 1]; + alpha[numSegments] = (Real)3 * (derivative1 - + inv * (mA[numSegments] - mA[numSegments - 1])); + for (int i = 1; i < numSegments; ++i) + { + Vector numer = (Real)3 * (dt[i - 1] * mA[i + 1] - + d2t[i] * mA[i] + dt[i] * mA[i - 1]); + Real invDenom = (Real)1 / (dt[i - 1] * dt[i]); + alpha[i] = invDenom * numer; + } + + std::vector ell(numSegments + 1); + std::vector mu(numSegments); + std::vector> z(numSegments + 1); + + ell[0] = (Real)2 * dt[0]; + mu[0] = (Real)0.5; + inv = (Real)1 / ell[0]; + z[0] = inv * alpha[0]; + + for (int i = 1; i < numSegments; ++i) + { + ell[i] = (Real)2 * d2t[i] - dt[i - 1] * mu[i - 1]; + inv = (Real)1 / ell[i]; + mu[i] = inv * dt[i]; + z[i] = inv * (alpha[i] - dt[i - 1] * z[i - 1]); + } + ell[numSegments] = dt[numSegments - 1] * ((Real)2 - mu[numSegments - 1]); + inv = (Real)1 / ell[numSegments]; + z[numSegments] = inv * (alpha[numSegments] - dt[numSegments - 1] * + z[numSegments - 1]); + + Real const oneThird = (Real)1 / (Real)3; + mC[numSegments] = z[numSegments]; + for (int i = numSegments - 1; i >= 0; --i) + { + mC[i] = z[i] - mu[i] * mC[i + 1]; + inv = (Real)1 / dt[i]; + mB[i] = inv * (mA[i + 1] - mA[i]) - oneThird * dt[i] * (mC[i + 1] + + (Real)2 * mC[i]); + mD[i] = oneThird * inv * (mC[i + 1] - mC[i]); + } + } + + // Determine the index i for which times[i] <= t < times[i+1]. + void GetKeyInfo(Real t, int& key, Real& dt) const + { + int numSegments = GetNumPoints() - 1; + if (t <= this->mTime[0]) + { + key = 0; + dt = (Real)0; + } + else if (t >= this->mTime[numSegments]) + { + key = numSegments - 1; + dt = this->mTime[numSegments] - this->mTime[numSegments - 1]; + } + else + { + for (int i = 0; i < numSegments; ++i) + { + if (t < this->mTime[i + 1]) + { + key = i; + dt = t - this->mTime[i]; + break; + } + } + } + } + + // Polynomial coefficients. mA are the points (constant coefficients of + // polynomials. mB are the degree 1 coefficients, mC are the degree 2 + // coefficients, and mD are the degree 3 coefficients. + Vector* mA; + Vector* mB; + Vector* mC; + Vector* mD; + + private: + std::vector> mCoefficients; + + struct WorkingData + { + WorkingData(int numSegments) + { + data.resize(4 * numSegments + 1 + N * (2 * numSegments + 1)); + dt = &data[0]; + d2t = dt + numSegments; + alpha = reinterpret_cast*>(d2t + numSegments); + ell = reinterpret_cast(alpha + numSegments); + mu = ell + numSegments + 1; + z = reinterpret_cast*>(mu + numSegments); + } + + Real* dt; + Real* d2t; + Vector* alpha; + Real* ell; + Real* mu; + Vector* z; + + std::vector data; + }; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/NearestNeighborQuery.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/NearestNeighborQuery.h new file mode 100644 index 0000000..6aee2ea --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/NearestNeighborQuery.h @@ -0,0 +1,336 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// TODO: This is not a KD-tree nearest neighbor query. Instead, it is an +// algorithm to get "approximate" nearest neighbors. Replace this by the +// actual KD-tree query. + +// Use a kd-tree for sorting used in a query for finding nearest neighbors of +// a point in a space of the specified dimension N. The split order is always +// 0,1,2,...,N-1. The number of sites at a leaf node is controlled by +// 'maxLeafSize' and the maximum level of the tree is controlled by +// 'maxLevels'. The points are of type Vector. The 'Site' is a +// structure of information that minimally implements the function +// 'Vector GetPosition () const'. The Site template parameter +// allows the query to be applied even when it has more local information +// than just point location. + +namespace WwiseGTE +{ + // Predefined site structs for convenience. + template + struct PositionSite + { + Vector position; + + PositionSite(Vector const& p) + : + position(p) + { + } + + Vector GetPosition() const + { + return position; + } + }; + + // Predefined site structs for convenience. + template + struct PositionDirectionSite + { + Vector position; + Vector direction; + + PositionDirectionSite(Vector const& p, Vector const& d) + : + position(p), + direction(d) + { + } + + Vector GetPosition() const + { + return position; + } + }; + + template + class NearestNeighborQuery + { + public: + // Supporting data structures. + typedef std::pair, int> SortedPoint; + + struct Node + { + Real split; + int axis; + int numSites; + int siteOffset; + int left; + int right; + }; + + // Construction. + NearestNeighborQuery(std::vector const& sites, int maxLeafSize, int maxLevel) + : + mMaxLeafSize(maxLeafSize), + mMaxLevel(maxLevel), + mSortedPoints(sites.size()), + mDepth(0), + mLargestNodeSize(0) + { + LogAssert(mMaxLevel > 0 && mMaxLevel <= 32, "Invalid max level."); + + int const numSites = static_cast(sites.size()); + for (int i = 0; i < numSites; ++i) + { + mSortedPoints[i] = std::make_pair(sites[i].GetPosition(), i); + } + + mNodes.push_back(Node()); + Build(numSites, 0, 0, 0); + } + + // Member access. + inline int GetMaxLeafSize() const + { + return mMaxLeafSize; + } + + inline int GetMaxLevel() const + { + return mMaxLevel; + } + + inline int GetDepth() const + { + return mDepth; + } + + inline int GetLargestNodeSize() const + { + return mLargestNodeSize; + } + + int GetNumNodes() const + { + return static_cast(mNodes.size()); + } + + inline std::vector const& GetNodes() const + { + return mNodes; + } + + // Compute up to MaxNeighbors nearest neighbors within the specified + // radius of the point. The returned integer is the number of + // neighbors found, possibly zero. The neighbors array stores indices + // into the array passed to the constructor. + template + int FindNeighbors(Vector const& point, Real radius, std::array& neighbors) const + { + Real sqrRadius = radius * radius; + int numNeighbors = 0; + std::array localNeighbors; + std::array neighborSqrLength; + for (int i = 0; i <= MaxNeighbors; ++i) + { + localNeighbors[i] = -1; + neighborSqrLength[i] = std::numeric_limits::max(); + } + + // The kd-tree construction is recursive, simulated here by using + // a stack. The maximum depth is limited to 32, because the number + // of sites is limited to 2^{32} (the number of 32-bit integer + // indices). + std::array stack; + int top = 0; + stack[0] = 0; + + int maxNeighbors = MaxNeighbors; + if (maxNeighbors == 1) + { + while (top >= 0) + { + Node node = mNodes[stack[top--]]; + + if (node.siteOffset != -1) + { + for (int i = 0, j = node.siteOffset; i < node.numSites; ++i, ++j) + { + auto diff = mSortedPoints[j].first - point; + auto sqrLength = Dot(diff, diff); + if (sqrLength <= sqrRadius) + { + // Maintain the nearest neighbors. + if (sqrLength <= neighborSqrLength[0]) + { + localNeighbors[0] = mSortedPoints[j].second; + neighborSqrLength[0] = sqrLength; + numNeighbors = 1; + } + } + } + } + + if (node.left != -1 && point[node.axis] - radius <= node.split) + { + stack[++top] = node.left; + } + + if (node.right != -1 && point[node.axis] + radius >= node.split) + { + stack[++top] = node.right; + } + } + } + else + { + while (top >= 0) + { + Node node = mNodes[stack[top--]]; + + if (node.siteOffset != -1) + { + for (int i = 0, j = node.siteOffset; i < node.numSites; ++i, ++j) + { + Vector diff = mSortedPoints[j].first - point; + Real sqrLength = Dot(diff, diff); + if (sqrLength <= sqrRadius) + { + // Maintain the nearest neighbors. + int k; + for (k = 0; k < numNeighbors; ++k) + { + if (sqrLength <= neighborSqrLength[k]) + { + for (int n = numNeighbors; n > k; --n) + { + localNeighbors[n] = localNeighbors[n - 1]; + neighborSqrLength[n] = neighborSqrLength[n - 1]; + } + break; + } + } + if (k < MaxNeighbors) + { + localNeighbors[k] = mSortedPoints[j].second; + neighborSqrLength[k] = sqrLength; + } + if (numNeighbors < MaxNeighbors) + { + ++numNeighbors; + } + } + } + } + + if (node.left != -1 && point[node.axis] - radius <= node.split) + { + stack[++top] = node.left; + } + + if (node.right != -1 && point[node.axis] + radius >= node.split) + { + stack[++top] = node.right; + } + } + } + + + for (int i = 0; i < numNeighbors; ++i) + { + neighbors[i] = localNeighbors[i]; + } + + return numNeighbors; + } + + inline std::vector const& GetSortedPoints() const + { + return mSortedPoints; + } + + private: + // Populate the node so that it contains the points split along the + // coordinate axes. + void Build(int numSites, int siteOffset, int nodeIndex, int level) + { + LogAssert(siteOffset != -1, "Invalid site offset."); + LogAssert(nodeIndex != -1, "Invalid node index."); + LogAssert(numSites > 0, "Empty point list."); + + mDepth = std::max(mDepth, level); + + Node& node = mNodes[nodeIndex]; + node.numSites = numSites; + + if (numSites > mMaxLeafSize && level <= mMaxLevel) + { + int halfNumSites = numSites / 2; + + // The point set is too large for a leaf node, so split it at + // the median. The O(m log m) sort is not needed; rather, we + // locate the median using an order statistic construction + // that is expected time O(m). + int const axis = level % N; + auto sorter = [axis](SortedPoint const& p0, SortedPoint const& p1) + { + return p0.first[axis] < p1.first[axis]; + }; + + auto begin = mSortedPoints.begin() + siteOffset; + auto mid = mSortedPoints.begin() + siteOffset + halfNumSites; + auto end = mSortedPoints.begin() + siteOffset + numSites; + std::nth_element(begin, mid, end, sorter); + + // Get the median position. + node.split = mSortedPoints[siteOffset + halfNumSites].first[axis]; + node.axis = axis; + node.siteOffset = -1; + + // Apply a divide-and-conquer step. + int left = (int)mNodes.size(), right = left + 1; + node.left = left; + node.right = right; + mNodes.push_back(Node()); + mNodes.push_back(Node()); + + int nextLevel = level + 1; + Build(halfNumSites, siteOffset, left, nextLevel); + Build(numSites - halfNumSites, siteOffset + halfNumSites, right, nextLevel); + } + else + { + // The number of points is small enough or we have run out of + // depth, so make this node a leaf. + node.split = std::numeric_limits::max(); + node.axis = -1; + node.siteOffset = siteOffset; + node.left = -1; + node.right = -1; + + mLargestNodeSize = std::max(mLargestNodeSize, node.numSites); + } + } + + int mMaxLeafSize; + int mMaxLevel; + std::vector mSortedPoints; + std::vector mNodes; + int mDepth; + int mLargestNodeSize; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/OBBTreeOfPoints.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/OBBTreeOfPoints.h new file mode 100644 index 0000000..6e79ebf --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/OBBTreeOfPoints.h @@ -0,0 +1,330 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The depth of a node in a (nonempty) tree is the distance from the node to +// the root of the tree. The height is the maximum depth. A tree with a +// single node has height 0. The set of nodes of a tree with the same depth +// is refered to as a level of a tree (corresponding to that depth). A +// complete binary tree of height H has 2^{H+1}-1 nodes. The level +// corresponding to depth D has 2^D nodes, in which case the number of +// leaf nodes (depth H) is 2^H. + +namespace WwiseGTE +{ + template + class OBBTreeForPoints + { + public: + struct Node + { + Node() + : + depth(0), + minIndex(std::numeric_limits::max()), + maxIndex(std::numeric_limits::max()), + leftChild(std::numeric_limits::max()), + rightChild(std::numeric_limits::max()) + { + } + + OrientedBox3 box; + uint32_t depth; + uint32_t minIndex, maxIndex; + uint32_t leftChild, rightChild; + }; + + + // The 'points' array is a collection of vertices, each occupying a + // chunk of memory with 'stride' bytes. A vertex must start at the + // first byte of this chunk but does not necessarily fill it. The + // 'height' specifies the height of the tree and must be no larger + // than 31. If it is set to std::numeric_limits::max(), + // then the entire tree is built and the actual height is computed + // from 'numPoints'. + OBBTreeForPoints(uint32_t numPoints, char const* points, size_t stride, + uint32_t height = std::numeric_limits::max()) + : + mNumPoints(numPoints), + mPoints(points), + mStride(stride), + mHeight(height), + mPartition(numPoints) + { + // The tree nodes are indexed by 32-bit unsigned integers, so + // the number of nodes can be at most 2^{32} - 1. This limits + // the height to 31. + + uint32_t numNodes; + if (mHeight == std::numeric_limits::max()) + { + uint32_t minPowerOfTwo = + static_cast(BitHacks::RoundUpToPowerOfTwo(mNumPoints)); + mHeight = BitHacks::Log2OfPowerOfTwo(minPowerOfTwo); + numNodes = 2 * mNumPoints - 1; + } + else + { + // The maximum level cannot exceed 30 because we are storing + // the indices into the node array as 32-bit unsigned + // integers. + if (mHeight < 32) + { + numNodes = (uint32_t)(1ULL << (mHeight + 1)) - 1; + } + else + { + // When the precondition is not met, return a tree of + // height 0 (a single node). + mHeight = 0; + numNodes = 1; + } + } + + // The tree is built recursively. A reference to a Node is + // passed to BuildTree and nodes are appended to a std::vector. + // Because the references are on the stack, we must guarantee no + // reallocations to avoid invalidating the references. TODO: + // This design can be modified to pass indices to the nodes so + // that reallocation is not a problem. + mTree.reserve(numNodes); + + // Build the tree recursively. The array mPartition stores the + // indices into the 'points' array so that at a node, the points + // represented by the node are those indexed by the range + // [node.minIndex, node.maxIndex]. + for (uint32_t i = 0; i < mNumPoints; ++i) + { + mPartition[i] = i; + } + mTree.push_back(Node()); + BuildTree(mTree.back(), 0, mNumPoints - 1); + } + + // Member access. + inline uint32_t GetNumPoints() const + { + return mNumPoints; + } + + inline char const* GetPoints() const + { + return mPoints; + } + + inline size_t GetStride() const + { + return mStride; + } + + inline std::vector const& GetTree() const + { + return mTree; + } + + inline uint32_t GetHeight() const + { + return mHeight; + } + + inline std::vector const& GetPartition() const + { + return mPartition; + } + + private: + inline Vector3 GetPosition(uint32_t index) const + { + return *reinterpret_cast const*>(mPoints + index * mStride); + } + + void BuildTree(Node& node, uint32_t i0, uint32_t i1) + { + node.minIndex = i0; + node.maxIndex = i1; + + if (i0 == i1) + { + // We are at a leaf node. The left and right child indices + // were set to std::numeric_limits::max() during + // construction. + + // Create a degenerate box whose center is the point. + node.box.center = GetPosition(mPartition[i0]); + node.box.axis[0] = Vector3{ (Real)1, (Real)0, (Real)0 }; + node.box.axis[1] = Vector3{ (Real)0, (Real)1, (Real)0 }; + node.box.axis[2] = Vector3{ (Real)0, (Real)0, (Real)1 }; + node.box.extent = Vector3{ (Real)0, (Real)0, (Real)0 }; + } + else // i0 < i1 + { + // We are at an interior node. Compute an oriented bounding + // box. + ComputeOBB(i0, i1, node.box); + + if (node.depth == mHeight) + { + return; + } + + // Use the box axis corresponding to largest extent for the + // splitting axis. Partition the points into two subsets, one + // for the left child and one for the right child. The subsets + // have numbers of elements that differ by at most 1, so the + // tree is balanced. + Vector3 axis2 = node.box.axis[2]; + uint32_t j0, j1; + SplitPoints(i0, i1, j0, j1, node.box.center, axis2); + + node.leftChild = static_cast(mTree.size()); + node.rightChild = node.leftChild + 1; + mTree.push_back(Node()); + Node& leftTree = mTree.back(); + mTree.push_back(Node()); + Node& rightTree = mTree.back(); + leftTree.depth = node.depth + 1; + rightTree.depth = node.depth + 1; + BuildTree(leftTree, i0, j0); + BuildTree(rightTree, j1, i1); + } + } + + void ComputeOBB(uint32_t i0, uint32_t i1, OrientedBox3& box) + { + // Compute the mean of the points. + Vector3 zero{ (Real)0, (Real)0, (Real)0 }; + box.center = zero; + for (uint32_t i = i0; i <= i1; ++i) + { + box.center += GetPosition(mPartition[i]); + } + Real invSize = ((Real)1) / (Real)(i1 - i0 + 1); + box.center *= invSize; + + // Compute the covariance matrix of the points. + Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0; + Real covar11 = (Real)0, covar12 = (Real)0, covar22 = (Real)0; + for (uint32_t i = i0; i <= i1; ++i) + { + Vector3 diff = GetPosition(mPartition[i]) - box.center; + covar00 += diff[0] * diff[0]; + covar01 += diff[0] * diff[1]; + covar02 += diff[0] * diff[2]; + covar11 += diff[1] * diff[1]; + covar12 += diff[1] * diff[2]; + covar22 += diff[2] * diff[2]; + } + covar00 *= invSize; + covar01 *= invSize; + covar02 *= invSize; + covar11 *= invSize; + covar12 *= invSize; + covar22 *= invSize; + + // Solve the eigensystem and use the eigenvectors for the box + // axes. + SymmetricEigensolver3x3 es; + std::array eval; + std::array, 3> evec; + es(covar00, covar01, covar02, covar11, covar12, covar22, false, +1, eval, evec); + for (int i = 0; i < 3; ++i) + { + box.axis[i] = evec[i]; + } + box.extent = eval; + + // Let C be the box center and let U0, U1, and U2 be the box axes. + // Each input point is of the form X = C + y0*U0 + y1*U1 + y2*U2. + // The following code computes min(y0), max(y0), min(y1), max(y1), + // min(y2), and max(y2). The box center is then adjusted to be + // C' = C + 0.5*(min(y0)+max(y0))*U0 + 0.5*(min(y1)+max(y1))*U1 + // + 0.5*(min(y2)+max(y2))*U2 + Vector3 pmin = zero, pmax = zero; + for (uint32_t i = i0; i <= i1; ++i) + { + Vector3 diff = GetPosition(mPartition[i]) - box.center; + for (int j = 0; j < 3; ++j) + { + Real dot = Dot(diff, box.axis[j]); + if (dot < pmin[j]) + { + pmin[j] = dot; + } + else if (dot > pmax[j]) + { + pmax[j] = dot; + } + } + } + + Real const half(0.5); + for (int j = 0; j < 3; ++j) + { + box.center += (half * (pmin[j] + pmax[j])) * box.axis[j]; + box.extent[j] = half * (pmax[j] - pmin[j]); + } + } + + void SplitPoints(uint32_t i0, uint32_t i1, uint32_t& j0, uint32_t& j1, + Vector3 const& origin, Vector3 const& direction) + { + // Project the points onto the splitting axis. + uint32_t numProjections = i1 - i0 + 1; + std::vector info(numProjections); + uint32_t i, k; + for (i = i0, k = 0; i <= i1; ++i, ++k) + { + Vector3 diff = GetPosition(mPartition[i]) - origin; + info[k].pointIndex = mPartition[i]; + info[k].projection = Dot(direction, diff); + } + + // Partition the projections by the median. + uint32_t medianIndex = (numProjections - 1) / 2; + std::nth_element(info.begin(), info.begin() + medianIndex, info.end()); + + // Partition the points by the median. + for (k = 0, j0 = i0 - 1; k <= medianIndex; ++k) + { + mPartition[++j0] = info[k].pointIndex; + } + for (j1 = i1 + 1; k < numProjections; ++k) + { + mPartition[--j1] = info[k].pointIndex; + } + } + + struct ProjectionInfo + { + ProjectionInfo() + : + pointIndex(0), + projection((Real)0) + { + } + + bool operator< (ProjectionInfo const& info) const + { + return projection < info.projection; + } + + uint32_t pointIndex; + Real projection; + }; + + uint32_t mNumPoints; + char const* mPoints; + size_t mStride; + uint32_t mHeight; + std::vector mTree; + std::vector mPartition; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/OdeEuler.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/OdeEuler.h new file mode 100644 index 0000000..6ea1254 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/OdeEuler.h @@ -0,0 +1,42 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The TVector template parameter allows you to create solvers with +// Vector when the dimension N is known at compile time or +// GVector when the dimension N is known at run time. Both classes +// have 'int GetSize() const' that allow OdeSolver-derived classes to query +// for the dimension. + +namespace WwiseGTE +{ + template + class OdeEuler : public OdeSolver + { + public: + // Construction and destruction. + virtual ~OdeEuler() = default; + + OdeEuler(Real tDelta, std::function const& F) + : + OdeSolver(tDelta, F) + { + } + + // Estimate x(t + tDelta) from x(t) using dx/dt = F(t,x). You may + // allow xIn and xOut to be the same object. + virtual void Update(Real tIn, TVector const& xIn, Real& tOut, TVector& xOut) override + { + TVector fVector = this->mFunction(tIn, xIn); + tOut = tIn + this->mTDelta; + xOut = xIn + this->mTDelta * fVector; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/OdeImplicitEuler.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/OdeImplicitEuler.h new file mode 100644 index 0000000..747907e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/OdeImplicitEuler.h @@ -0,0 +1,59 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The TVector template parameter allows you to create solvers with +// Vector when the dimension N is known at compile time or +// GVector when the dimension N is known at run time. Both classes +// have 'int GetSize() const' that allow OdeSolver-derived classes to query +// for the dimension. The TMatrix parameter must be either Matrix +// or GMatrix accordingly. +// +// The function F(t,x) has input t, a scalar, and input x, an N-vector. +// The first derivative matrix with respect to x is DF(t,x), an +// N-by-N matrix. Entry DF(r,c) is the derivative of F[r] with +// respect to x[c]. + +namespace WwiseGTE +{ + template + class OdeImplicitEuler : public OdeSolver + { + public: + // Construction and destruction. + virtual ~OdeImplicitEuler() = default; + + OdeImplicitEuler(Real tDelta, + std::function const& F, + std::function const& DF) + : + OdeSolver(tDelta, F), + mDerivativeFunction(DF) + { + } + + // Estimate x(t + tDelta) from x(t) using dx/dt = F(t,x). You may + // allow xIn and xOut to be the same object. + virtual void Update(Real tIn, TVector const& xIn, Real& tOut, TVector& xOut) override + { + TVector fVector = this->mFunction(tIn, xIn); + TMatrix dfMatrix = mDerivativeFunction(tIn, xIn); + TMatrix dgMatrix = TMatrix::Identity() - this->mTDelta * dfMatrix; + TMatrix dgInverse = Inverse(dgMatrix); + fVector = dgInverse * fVector; + tOut = tIn + this->mTDelta; + xOut = xIn + this->mTDelta * fVector; + } + + private: + std::function mDerivativeFunction; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/OdeMidpoint.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/OdeMidpoint.h new file mode 100644 index 0000000..f3ab6af --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/OdeMidpoint.h @@ -0,0 +1,49 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The TVector template parameter allows you to create solvers with +// Vector when the dimension N is known at compile time or +// GVector when the dimension N is known at run time. Both classes +// have 'int GetSize() const' that allow OdeSolver-derived classes to query +// for the dimension. + +namespace WwiseGTE +{ + template + class OdeMidpoint : public OdeSolver + { + public: + // Construction and destruction. + virtual ~OdeMidpoint() = default; + + OdeMidpoint(Real tDelta, std::function const& F) + : + OdeSolver(tDelta, F) + { + } + + // Estimate x(t + tDelta) from x(t) using dx/dt = F(t,x). You may + // allow xIn and xOut to be the same object. + virtual void Update(Real tIn, TVector const& xIn, Real& tOut, TVector& xOut) override + { + // Compute the first step. + Real halfTDelta = (Real)0.5 * this->mTDelta; + TVector fVector = this->mFunction(tIn, xIn); + TVector xTemp = xIn + halfTDelta * fVector; + + // Compute the second step. + Real halfT = tIn + halfTDelta; + fVector = this->mFunction(halfT, xTemp); + tOut = tIn + this->mTDelta; + xOut = xIn + this->mTDelta * fVector; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/OdeRungeKutta4.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/OdeRungeKutta4.h new file mode 100644 index 0000000..fe88a67 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/OdeRungeKutta4.h @@ -0,0 +1,58 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The TVector template parameter allows you to create solvers with +// Vector when the dimension N is known at compile time or +// GVector when the dimension N is known at run time. Both classes +// have 'int GetSize() const' that allow OdeSolver-derived classes to query +// for the dimension. + +namespace WwiseGTE +{ + template + class OdeRungeKutta4 : public OdeSolver + { + public: + // Construction and destruction. + virtual ~OdeRungeKutta4() = default; + + OdeRungeKutta4(Real tDelta, std::function const& F) + : + OdeSolver(tDelta, F) + { + } + + // Estimate x(t + tDelta) from x(t) using dx/dt = F(t,x). You may + // allow xIn and xOut to be the same object. + virtual void Update(Real tIn, TVector const& xIn, Real& tOut, TVector& xOut) override + { + // Compute the first step. + Real halfTDelta = (Real)0.5 * this->mTDelta; + TVector fTemp1 = this->mFunction(tIn, xIn); + TVector xTemp = xIn + halfTDelta * fTemp1; + + // Compute the second step. + Real halfT = tIn + halfTDelta; + TVector fTemp2 = this->mFunction(halfT, xTemp); + xTemp = xIn + halfTDelta * fTemp2; + + // Compute the third step. + TVector fTemp3 = this->mFunction(halfT, xTemp); + xTemp = xIn + this->mTDelta * fTemp3; + + // Compute the fourth step. + Real sixthTDelta = this->mTDelta / (Real)6; + tOut = tIn + this->mTDelta; + TVector fTemp4 = this->mFunction(tOut, xTemp); + xOut = xIn + sixthTDelta * (fTemp1 + (Real)2 * (fTemp2 + fTemp3) + fTemp4); + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/OdeSolver.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/OdeSolver.h new file mode 100644 index 0000000..94bcfeb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/OdeSolver.h @@ -0,0 +1,56 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The differential equation is dx/dt = F(t,x). The TVector template +// parameter allows you to create solvers with Vector when the +// dimension N is known at compile time or GVector when the dimension +// N is known at run time. Both classes have 'int GetSize() const' that +// allow OdeSolver-derived classes to query for the dimension. + +namespace WwiseGTE +{ + template + class OdeSolver + { + public: + // Abstract base class. + virtual ~OdeSolver() = default; + protected: + OdeSolver(Real tDelta, std::function const& F) + : + mTDelta(tDelta), + mFunction(F) + { + } + + public: + // Member access. + inline void SetTDelta(Real tDelta) + { + mTDelta = tDelta; + } + + inline Real GetTDelta() const + { + return mTDelta; + } + + // Estimate x(t + tDelta) from x(t) using dx/dt = F(t,x). The + // derived classes implement this so that it is possible for xIn and + // xOut to be the same object. + virtual void Update(Real tIn, TVector const& xIn, Real& tOut, TVector& xOut) = 0; + + protected: + Real mTDelta; + std::function mFunction; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/OrientedBox.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/OrientedBox.h new file mode 100644 index 0000000..e52f33e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/OrientedBox.h @@ -0,0 +1,138 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// A box has center C, axis directions U[i], and extents e[i]. The set +// {U[0],...,U[N-1]} is orthonormal, which means the vectors are +// unit-length and mutually perpendicular. The extents are nonnegative; +// zero is allowed, meaning the box is degenerate in the corresponding +// direction. A point X is represented in box coordinates by +// X = C + y[0]*U[0] + y[1]*U[1]. This point is inside or on the +// box whenever |y[i]| <= e[i] for all i. + +namespace WwiseGTE +{ + template + class OrientedBox + { + public: + // Construction and destruction. The default constructor sets the + // center to (0,...,0), axis d to Vector::Unit(d) and + // extent d to +1. + OrientedBox() + { + center.MakeZero(); + for (int i = 0; i < N; ++i) + { + axis[i].MakeUnit(i); + extent[i] = (Real)1; + } + } + + OrientedBox(Vector const& inCenter, + std::array, N> const& inAxis, + Vector const& inExtent) + : + center(inCenter), + axis(inAxis), + extent(inExtent) + { + } + + // Compute the vertices of the box. If index i has the bit pattern + // i = b[N-1]...b[0], then + // vertex[i] = center + sum_{d=0}^{N-1} sign[d] * extent[d] * axis[d] + // where sign[d] = 2*b[d] - 1. + void GetVertices(std::array, (1 << N)>& vertex) const + { + unsigned int const dsup = static_cast(N); + std::array, N> product; + for (unsigned int d = 0; d < dsup; ++d) + { + product[d] = extent[d] * axis[d]; + } + + int const imax = (1 << N); + for (int i = 0; i < imax; ++i) + { + vertex[i] = center; + for (unsigned int d = 0, mask = 1; d < dsup; ++d, mask <<= 1) + { + Real sign = (i & mask ? (Real)1 : (Real)-1); + vertex[i] += sign * product[d]; + } + } + } + + // Public member access. It is required that extent[i] >= 0. + Vector center; + std::array, N> axis; + Vector extent; + + public: + // Comparisons to support sorted containers. + bool operator==(OrientedBox const& box) const + { + return center == box.center && axis == box.axis && extent == box.extent; + } + + bool operator!=(OrientedBox const& box) const + { + return !operator==(box); + } + + bool operator< (OrientedBox const& box) const + { + if (center < box.center) + { + return true; + } + + if (center > box.center) + { + return false; + } + + if (axis < box.axis) + { + return true; + } + + if (axis > box.axis) + { + return false; + } + + return extent < box.extent; + } + + bool operator<=(OrientedBox const& box) const + { + return !box.operator<(*this); + } + + bool operator> (OrientedBox const& box) const + { + return box.operator<(*this); + } + + bool operator>=(OrientedBox const& box) const + { + return !operator<(box); + } + }; + + // Template aliases for convenience. + template + using OrientedBox2 = OrientedBox<2, Real>; + + template + using OrientedBox3 = OrientedBox<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ParametricCurve.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ParametricCurve.h new file mode 100644 index 0000000..b988461 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ParametricCurve.h @@ -0,0 +1,312 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class ParametricCurve + { + protected: + // Abstract base class for a parameterized curve X(t), where t is the + // parameter in [tmin,tmax] and X is an N-tuple position. The first + // constructor is for single-segment curves. The second constructor is + // for multiple-segment curves. The times must be strictly increasing. + ParametricCurve(Real tmin, Real tmax) + : + mTime(2), + mSegmentLength(1), + mAccumulatedLength(1), + mRombergOrder(DEFAULT_ROMBERG_ORDER), + mMaxBisections(DEFAULT_MAX_BISECTIONS), + mConstructed(false) + { + mTime[0] = tmin; + mTime[1] = tmax; + mSegmentLength[0] = (Real)0; + mAccumulatedLength[0] = (Real)0; + } + + ParametricCurve(int numSegments, Real const* times) + : + mTime(numSegments + 1), + mSegmentLength(numSegments), + mAccumulatedLength(numSegments), + mRombergOrder(DEFAULT_ROMBERG_ORDER), + mMaxBisections(DEFAULT_MAX_BISECTIONS), + mConstructed(false) + { + std::copy(times, times + numSegments + 1, mTime.begin()); + mSegmentLength[0] = (Real)0; + mAccumulatedLength[0] = (Real)0; + } + + public: + virtual ~ParametricCurve() + { + } + + // To validate construction, create an object as shown: + // DerivedClassCurve curve(parameters); + // if (!curve) { ; } + inline operator bool() const + { + return mConstructed; + } + + // Member access. + inline Real GetTMin() const + { + return mTime.front(); + } + + inline Real GetTMax() const + { + return mTime.back(); + } + + inline int GetNumSegments() const + { + return static_cast(mSegmentLength.size()); + } + + inline Real const* GetTimes() const + { + return &mTime[0]; + } + + // This function applies only when the first constructor is used (two + // times rather than a sequence of three or more times). + void SetTimeInterval(Real tmin, Real tmax) + { + if (mTime.size() == 2) + { + mTime[0] = tmin; + mTime[1] = tmax; + } + } + + // Parameters used in GetLength(...), GetTotalLength() and + // GetTime(...). + + // The default value is 8. + inline void SetRombergOrder(int order) + { + mRombergOrder = std::max(order, 1); + } + + // The default value is 1024. + inline void SetMaxBisections(unsigned int maxBisections) + { + mMaxBisections = std::max(maxBisections, 1u); + } + + // Evaluation of the curve. The function supports derivative + // calculation through order 3; that is, order <= 3 is required. If + // you want/ only the position, pass in order of 0. If you want the + // position and first derivative, pass in order of 1, and so on. The + // output array 'jet' must have enough storage to support the maximum + // order. The values are ordered as: position, first derivative, + // second derivative, third derivative. + enum { SUP_ORDER = 4 }; + virtual void Evaluate(Real t, unsigned int order, Vector* jet) const = 0; + + void Evaluate(Real t, unsigned int order, Real* values) const + { + Evaluate(t, order, reinterpret_cast*>(values)); + } + + // Differential geometric quantities. + Vector GetPosition(Real t) const + { + Vector position; + Evaluate(t, 0, &position); + return position; + } + + Vector GetTangent(Real t) const + { + std::array, 2> jet; // (position, tangent) + Evaluate(t, 1, jet.data()); + Normalize(jet[1]); + return jet[1]; + } + + Real GetSpeed(Real t) const + { + std::array, 2> jet; // (position, tangent) + Evaluate(t, 1, jet.data()); + return Length(jet[1]); + } + + Real GetLength(Real t0, Real t1) const + { + std::function speed = [this](Real t) + { + return GetSpeed(t); + }; + + if (mSegmentLength[0] == (Real)0) + { + // Lazy initialization of lengths of segments. + int const numSegments = static_cast(mSegmentLength.size()); + Real accumulated = (Real)0; + for (int i = 0; i < numSegments; ++i) + { + mSegmentLength[i] = Integration::Romberg(mRombergOrder, + mTime[i], mTime[i + 1], speed); + accumulated += mSegmentLength[i]; + mAccumulatedLength[i] = accumulated; + } + } + + t0 = std::max(t0, GetTMin()); + t1 = std::min(t1, GetTMax()); + auto iter0 = std::lower_bound(mTime.begin(), mTime.end(), t0); + int index0 = static_cast(iter0 - mTime.begin()); + auto iter1 = std::lower_bound(mTime.begin(), mTime.end(), t1); + int index1 = static_cast(iter1 - mTime.begin()); + + Real length; + if (index0 < index1) + { + length = (Real)0; + if (t0 < *iter0) + { + length += Integration::Romberg(mRombergOrder, t0, + mTime[index0], speed); + } + + int isup; + if (t1 < *iter1) + { + length += Integration::Romberg(mRombergOrder, + mTime[index1 - 1], t1, speed); + isup = index1 - 1; + } + else + { + isup = index1; + } + for (int i = index0; i < isup; ++i) + { + length += mSegmentLength[i]; + } + } + else + { + length = Integration::Romberg(mRombergOrder, t0, t1, speed); + } + return length; + } + + Real GetTotalLength() const + { + if (mAccumulatedLength.back() == (Real)0) + { + // Lazy evaluation of the accumulated length array. + return GetLength(mTime.front(), mTime.back()); + } + + return mAccumulatedLength.back(); + } + + // Inverse mapping of s = Length(t) given by t = Length^{-1}(s). The + // inverse length function generally cannot be written in closed form, + // in which case it is not directly computable. Instead, we can + // specify s and estimate the root t for F(t) = Length(t) - s. The + // derivative is F'(t) = Speed(t) >= 0, so F(t) is nondecreasing. To + // be robust, we use bisection to locate the root, although it is + // possible to use a hybrid of Newton's method and bisection. For + // details, see the document + // https://www.geometrictools.com/Documentation/MovingAlongCurveSpecifiedSpeed.pdf + Real GetTime(Real length) const + { + if (length > (Real)0) + { + if (length < GetTotalLength()) + { + std::function F = [this, &length](Real t) + { + return Integration::Romberg(mRombergOrder, + mTime.front(), t, [this](Real z) { return GetSpeed(z); }) + - length; + }; + + // We know that F(tmin) < 0 and F(tmax) > 0, which allows us to + // use bisection. Rather than bisect the entire interval, let's + // narrow it down with a reasonable initial guess. + Real ratio = length / GetTotalLength(); + Real omratio = (Real)1 - ratio; + Real tmid = omratio * mTime.front() + ratio * mTime.back(); + Real fmid = F(tmid); + if (fmid > (Real)0) + { + RootsBisection::Find(F, mTime.front(), tmid, (Real)-1, + (Real)1, mMaxBisections, tmid); + } + else if (fmid < (Real)0) + { + RootsBisection::Find(F, tmid, mTime.back(), (Real)-1, + (Real)1, mMaxBisections, tmid); + } + return tmid; + } + else + { + return mTime.back(); + } + } + else + { + return mTime.front(); + } + } + + // Compute a subset of curve points according to the specified attribute. + // The input 'numPoints' must be two or larger. + void SubdivideByTime(int numPoints, Vector* points) const + { + Real delta = (mTime.back() - mTime.front()) / (Real)(numPoints - 1); + for (int i = 0; i < numPoints; ++i) + { + Real t = mTime.front() + delta * i; + points[i] = GetPosition(t); + } + } + + void SubdivideByLength(int numPoints, Vector* points) const + { + Real delta = GetTotalLength() / (Real)(numPoints - 1); + for (int i = 0; i < numPoints; ++i) + { + Real length = delta * i; + Real t = GetTime(length); + points[i] = GetPosition(t); + } + } + + protected: + enum + { + DEFAULT_ROMBERG_ORDER = 8, + DEFAULT_MAX_BISECTIONS = 1024 + }; + + std::vector mTime; + mutable std::vector mSegmentLength; + mutable std::vector mAccumulatedLength; + int mRombergOrder; + unsigned int mMaxBisections; + bool mConstructed; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ParametricSurface.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ParametricSurface.h new file mode 100644 index 0000000..03a65e4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ParametricSurface.h @@ -0,0 +1,114 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + template + class ParametricSurface + { + protected: + // Abstract base class for a parameterized surface X(u,v). The + // parametric domain is either rectangular or triangular. Valid + // (u,v) values for a rectangular domain satisfy + // umin <= u <= umax, vmin <= v <= vmax + // and valid (u,v) values for a triangular domain satisfy + // umin <= u <= umax, vmin <= v <= vmax, + // (vmax-vmin)*(u-umin)+(umax-umin)*(v-vmax) <= 0 + ParametricSurface(Real umin, Real umax, Real vmin, Real vmax, bool rectangular) + : + mUMin(umin), + mUMax(umax), + mVMin(vmin), + mVMax(vmax), + mRectangular(rectangular) + { + } + + public: + virtual ~ParametricSurface() + { + } + + // To validate construction, create an object as shown: + // DerivedClassSurface surface(parameters); + // if (!surface) { ; } + inline operator bool() const + { + return mConstructed; + } + + // Member access. + inline Real GetUMin() const + { + return mUMin; + } + + inline Real GetUMax() const + { + return mUMax; + } + + inline Real GetVMin() const + { + return mVMin; + } + + inline Real GetVMax() const + { + return mVMax; + } + + inline bool IsRectangular() const + { + return mRectangular; + } + + // Evaluation of the surface. The function supports derivative + // calculation through order 2; that is, order <= 2 is required. If + // you want only the position, pass in order of 0. If you want the + // position and first-order derivatives, pass in order of 1, and so + // on. The output array 'jet' must have enough storage to support the + // maximum order. The values are ordered as: position X; first-order + // derivatives dX/du, dX/dv; second-order derivatives d2X/du2, + // d2X/dudv, d2X/dv2. + enum { SUP_ORDER = 6 }; + virtual void Evaluate(Real u, Real v, unsigned int order, Vector* jet) const = 0; + + // Differential geometric quantities. + Vector GetPosition(Real u, Real v) const + { + Vector position; + Evaluate(u, v, 0, &position); + return position; + } + + Vector GetUTangent(Real u, Real v) const + { + std::array, 3> jet; + Evaluate(u, v, 1, jet.data()); + Normalize(jet[1]); + return jet[1]; + } + + Vector GetVTangent(Real u, Real v) const + { + std::array, 3> jet; + Evaluate(u, v, 1, jet.data()); + Normalize(jet[2]); + return jet[2]; + } + + protected: + Real mUMin, mUMax, mVMin, mVMax; + bool mRectangular; + bool mConstructed; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ParticleSystem.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ParticleSystem.h new file mode 100644 index 0000000..bcfc89a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ParticleSystem.h @@ -0,0 +1,226 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class ParticleSystem + { + public: + // Construction and destruction. If a particle is to be immovable, + // set its mass to std::numeric_limits::max(). + virtual ~ParticleSystem() = default; + + ParticleSystem(int numParticles, Real step) + : + mNumParticles(numParticles), + mMass(numParticles), + mInvMass(numParticles), + mPosition(numParticles), + mVelocity(numParticles), + mStep(step), + mHalfStep(step / (Real)2), + mSixthStep(step / (Real)6), + mPTmp(numParticles), + mVTmp(numParticles), + mPAllTmp(numParticles), + mVAllTmp(numParticles) + { + std::fill(mMass.begin(), mMass.end(), (Real)0); + std::fill(mInvMass.begin(), mInvMass.end(), (Real)0); + std::fill(mPosition.begin(), mPosition.end(), Vector::Zero()); + std::fill(mVelocity.begin(), mVelocity.end(), Vector::Zero()); + } + + // Member access. + inline int GetNumParticles() const + { + return mNumParticles; + } + + void SetMass(int i, Real mass) + { + if ((Real)0 < mass && mass < std::numeric_limits::max()) + { + mMass[i] = mass; + mInvMass[i] = (Real)1 / mass; + } + else + { + mMass[i] = std::numeric_limits::max(); + mInvMass[i] = (Real)0; + } + } + + inline void SetPosition(int i, Vector const& position) + { + mPosition[i] = position; + } + + inline void SetVelocity(int i, Vector const& velocity) + { + mVelocity[i] = velocity; + } + + void SetStep(Real step) + { + mStep = step; + mHalfStep = mStep / (Real)2; + mSixthStep = mStep / (Real)6; + } + + inline Real const& GetMass(int i) const + { + return mMass[i]; + } + + inline Vector const& GetPosition(int i) const + { + return mPosition[i]; + } + + inline Vector const& GetVelocity(int i) const + { + return mVelocity[i]; + } + + inline Real GetStep() const + { + return mStep; + } + + // Update the particle positions based on current time and particle + // state. The Acceleration(...) function is called in this update + // for each particle. This function is virtual so that derived + // classes can perform pre-update and/or post-update semantics. + virtual void Update(Real time) + { + // Runge-Kutta fourth-order solver. + Real halfTime = time + mHalfStep; + Real fullTime = time + mStep; + + // Compute the first step. + int i; + for (i = 0; i < mNumParticles; ++i) + { + if (mInvMass[i] > (Real)0) + { + mPAllTmp[i].d1 = mVelocity[i]; + mVAllTmp[i].d1 = Acceleration(i, time, mPosition, mVelocity); + } + } + for (i = 0; i < mNumParticles; ++i) + { + if (mInvMass[i] > (Real)0) + { + mPTmp[i] = mPosition[i] + mHalfStep * mPAllTmp[i].d1; + mVTmp[i] = mVelocity[i] + mHalfStep * mVAllTmp[i].d1; + } + else + { + mPTmp[i] = mPosition[i]; + mVTmp[i].MakeZero(); + } + } + + // Compute the second step. + for (i = 0; i < mNumParticles; ++i) + { + if (mInvMass[i] > (Real)0) + { + mPAllTmp[i].d2 = mVTmp[i]; + mVAllTmp[i].d2 = Acceleration(i, halfTime, mPTmp, mVTmp); + } + } + for (i = 0; i < mNumParticles; ++i) + { + if (mInvMass[i] > (Real)0) + { + mPTmp[i] = mPosition[i] + mHalfStep * mPAllTmp[i].d2; + mVTmp[i] = mVelocity[i] + mHalfStep * mVAllTmp[i].d2; + } + else + { + mPTmp[i] = mPosition[i]; + mVTmp[i].MakeZero(); + } + } + + // Compute the third step. + for (i = 0; i < mNumParticles; ++i) + { + if (mInvMass[i] > (Real)0) + { + mPAllTmp[i].d3 = mVTmp[i]; + mVAllTmp[i].d3 = Acceleration(i, halfTime, mPTmp, mVTmp); + } + } + for (i = 0; i < mNumParticles; ++i) + { + if (mInvMass[i] > (Real)0) + { + mPTmp[i] = mPosition[i] + mStep * mPAllTmp[i].d3; + mVTmp[i] = mVelocity[i] + mStep * mVAllTmp[i].d3; + } + else + { + mPTmp[i] = mPosition[i]; + mVTmp[i].MakeZero(); + } + } + + // Compute the fourth step. + for (i = 0; i < mNumParticles; ++i) + { + if (mInvMass[i] > (Real)0) + { + mPAllTmp[i].d4 = mVTmp[i]; + mVAllTmp[i].d4 = Acceleration(i, fullTime, mPTmp, mVTmp); + } + } + for (i = 0; i < mNumParticles; ++i) + { + if (mInvMass[i] > (Real)0) + { + mPosition[i] += mSixthStep * (mPAllTmp[i].d1 + + (Real)2 * (mPAllTmp[i].d2 + mPAllTmp[i].d3) + mPAllTmp[i].d4); + + mVelocity[i] += mSixthStep * (mVAllTmp[i].d1 + + (Real)2 * (mVAllTmp[i].d2 + mVAllTmp[i].d3) + mVAllTmp[i].d4); + } + } + } + + protected: + // Callback for acceleration (ODE solver uses x" = F/m) applied to + // particle i. The positions and velocities are not necessarily + // mPosition and mVelocity, because the ODE solver evaluates the + // impulse function at intermediate positions. + virtual Vector Acceleration(int i, Real time, + std::vector> const& position, + std::vector> const& velocity) = 0; + + int mNumParticles; + std::vector mMass, mInvMass; + std::vector> mPosition, mVelocity; + Real mStep, mHalfStep, mSixthStep; + + // Temporary storage for the Runge-Kutta differential equation solver. + struct Temporary + { + Vector d1, d2, d3, d4; + }; + + std::vector> mPTmp, mVTmp; + std::vector mPAllTmp, mVAllTmp; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PdeFilter.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PdeFilter.h new file mode 100644 index 0000000..f3470c2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PdeFilter.h @@ -0,0 +1,164 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +namespace WwiseGTE +{ + template + class PdeFilter + { + public: + enum ScaleType + { + // The data is processed as is. + ST_NONE, + + // The data range is d in [min,max]. The scaled values are d'. + + // d' = (d-min)/(max-min) in [0,1] + ST_UNIT, + + // d' = -1 + 2*(d-min)/(max-min) in [-1,1] + ST_SYMMETRIC, + + // max > -min: d' = d/max in [min/max,1] + // max < -min: d' = -d/min in [-1,-max/min] + ST_PRESERVE_ZERO + }; + + // The abstract base class for all PDE-based filters. + virtual ~PdeFilter() + { + } + + // Member access. + inline int GetQuantity() const + { + return mQuantity; + } + + inline Real GetBorderValue() const + { + return mBorderValue; + } + + inline ScaleType GetScaleType() const + { + return mScaleType; + } + + // Access to the time step for the PDE solver. + inline void SetTimeStep(Real timeStep) + { + mTimeStep = timeStep; + } + + inline Real GetTimeStep() const + { + return mTimeStep; + } + + // This function executes one iteration of the filter. It calls + // OnPreUpdate, OnUpdate and OnPostUpdate, in that order. + void Update() + { + OnPreUpdate(); + OnUpdate(); + OnPostUpdate(); + } + + protected: + PdeFilter(int quantity, Real const* data, Real borderValue, ScaleType scaleType) + : + mQuantity(quantity), + mBorderValue(borderValue), + mScaleType(scaleType), + mTimeStep(0) + { + Real maxValue = data[0]; + mMin = maxValue; + for (int i = 1; i < mQuantity; i++) + { + Real value = data[i]; + if (value < mMin) + { + mMin = value; + } + else if (value > maxValue) + { + maxValue = value; + } + } + + if (mMin != maxValue) + { + switch (mScaleType) + { + case ST_NONE: + mOffset = (Real)0; + mScale = (Real)1; + break; + case ST_UNIT: + mOffset = (Real)0; + mScale = (Real)1 / (maxValue - mMin); + break; + case ST_SYMMETRIC: + mOffset = (Real)-1; + mScale = (Real)2 / (maxValue - mMin); + break; + case ST_PRESERVE_ZERO: + mOffset = (Real)0; + mScale = (maxValue >= -mMin ? (Real)1 / maxValue : (Real)-1 / mMin); + mMin = (Real)0; + break; + } + } + else + { + mOffset = (Real)0; + mScale = (Real)1; + } + } + + // The derived classes for 2D and 3D implement this to recompute the + // boundary values when Neumann conditions are used. If derived + // classes built on top of the 2D or 3D classes implement this also, + // they must call the base-class OnPreUpdate first. + virtual void OnPreUpdate() = 0; + + // The derived classes for 2D and 3D implement this to iterate over + // the image elements, updating an element only if it is not masked + // out. + virtual void OnUpdate() = 0; + + // The derived classes for 2D and 3D implement this to swap the + // buffers for the next pass. If derived classes built on top of the + // 2D or 3D classes implement this also, they must call the base-class + // OnPostUpdate last. + virtual void OnPostUpdate() = 0; + + // The number of image elements. + int mQuantity; + + // When set to std::numeric_limits::max(), Neumann conditions + // are in use (zero-valued derivatives on the image border). + // Dirichlet conditions are used, otherwise (image is constant on the + // border). + Real mBorderValue; + + // This member stores how the image data was transformed during the + // constructor call. + ScaleType mScaleType; + Real mMin, mOffset, mScale; + + // The time step for the PDE solver. The stability of an algorithm + // depends on the magnitude of the time step, but the magnitude itself + // depends on the algorithm. + Real mTimeStep; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PdeFilter1.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PdeFilter1.h new file mode 100644 index 0000000..a1240a6 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PdeFilter1.h @@ -0,0 +1,290 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class PdeFilter1 : public PdeFilter + { + public: + // Abstract base class. + PdeFilter1(int xBound, Real xSpacing, Real const* data, bool const* mask, + Real borderValue, typename PdeFilter::ScaleType scaleType) + : + PdeFilter(xBound, data, borderValue, scaleType), + mXBound(xBound), + mXSpacing(xSpacing), + mInvDx((Real)1 / xSpacing), + mHalfInvDx((Real)0.5 * mInvDx), + mInvDxDx(mInvDx * mInvDx), + mUm(0), mUz(0), mUp(0), + mSrc(0), + mDst(1), + mMask(xBound + 2), + mHasMask(mask != nullptr) + { + // The mBuffer[] are ping-pong buffers for filtering. + for (int i = 0; i < 2; ++i) + { + mBuffer[i].resize(xBound + 2); + } + + for (int x = 0, xp = 1, i = 0; x < mXBound; ++x, ++xp, ++i) + { + mBuffer[mSrc][xp] = this->mOffset + (data[i] - this->mMin) * this->mScale; + mBuffer[mDst][xp] = (Real)0; + mMask[xp] = (mHasMask ? mask[i] : 1); + } + + // Assign values to the 1-pixel image border. + if (this->mBorderValue != std::numeric_limits::max()) + { + AssignDirichletImageBorder(); + } + else + { + AssignNeumannImageBorder(); + } + + // To handle masks that do not cover the entire image, assign + // values to those pixels that are 8-neighbors of the mask pixels. + if (mHasMask) + { + if (this->mBorderValue != std::numeric_limits::max()) + { + AssignDirichletMaskBorder(); + } + else + { + AssignNeumannMaskBorder(); + } + } + } + + virtual ~PdeFilter1() + { + } + + // Member access. The internal 1D images for "data" and "mask" are + // copies of the inputs to the constructor but padded with a 1-pixel + // thick border to support filtering on the image boundary. These + // images are of size (xbound+2). The correct lookups into the padded + // arrays are handled internally. + inline int GetXBound() const + { + return mXBound; + } + + inline Real GetXSpacing() const + { + return mXSpacing; + } + + // Pixel access and derivative estimation. The lookups into the + // padded data are handled correctly. The estimation involves only + // the 3-tuple neighborhood of (x), where 0 <= x < xbound. TODO: If + // larger neighborhoods are desired at a later date, the padding and + // associated code must be adjusted accordingly. + Real GetU(int x) const + { + auto const& F = mBuffer[mSrc]; + return F[x + 1]; + } + + Real GetUx(int x) const + { + auto const& F = mBuffer[mSrc]; + return mHalfInvDx * (F[x + 2] - F[x]); + } + + Real GetUxx(int x) const + { + auto const& F = mBuffer[mSrc]; + return mInvDxDx * (F[x + 2] - (Real)2 * F[x + 1] + F[x]); + } + + int GetMask(int x) const + { + return mMask[x + 1]; + } + + protected: + // Assign values to the 1-pixel image border. + void AssignDirichletImageBorder() + { + int xBp1 = mXBound + 1; + + // vertex (0,0) + mBuffer[mSrc][0] = this->mBorderValue; + mBuffer[mDst][0] = this->mBorderValue; + if (mHasMask) + { + mMask[0] = 0; + } + + // vertex (xmax,0) + mBuffer[mSrc][xBp1] = this->mBorderValue; + mBuffer[mDst][xBp1] = this->mBorderValue; + if (mHasMask) + { + mMask[xBp1] = 0; + } + } + + void AssignNeumannImageBorder() + { + int xBp1 = mXBound + 1; + Real duplicate; + + // vertex (0,0) + duplicate = mBuffer[mSrc][1]; + mBuffer[mSrc][0] = duplicate; + mBuffer[mDst][0] = duplicate; + if (mHasMask) + { + mMask[0] = 0; + } + + // vertex (xmax,0) + duplicate = mBuffer[mSrc][mXBound]; + mBuffer[mSrc][xBp1] = duplicate; + mBuffer[mDst][xBp1] = duplicate; + if (mHasMask) + { + mMask[xBp1] = 0; + } + } + + // Assign values to the 1-pixel mask border. + void AssignDirichletMaskBorder() + { + for (int x = 1; x <= mXBound; ++x) + { + if (mMask[x]) + { + continue; + } + + for (int i0 = 0, j0 = x - 1; i0 < 3; ++i0, ++j0) + { + if (mMask[j0]) + { + mBuffer[mSrc][x] = this->mBorderValue; + mBuffer[mDst][x] = this->mBorderValue; + break; + } + } + } + } + + void AssignNeumannMaskBorder() + { + // Recompute the values just outside the masked region. This + // guarantees that derivative estimations use the current values + // around the boundary. + for (int x = 1; x <= mXBound; ++x) + { + if (mMask[x]) + { + continue; + } + + int count = 0; + Real average = (Real)0; + for (int i0 = 0, j0 = x - 1; i0 < 3; ++i0, ++j0) + { + if (mMask[j0]) + { + average += mBuffer[mSrc][j0]; + ++count; + } + } + + if (count > 0) + { + average /= (Real)count; + mBuffer[mSrc][x] = average; + mBuffer[mDst][x] = average; + } + } + } + + // This function recomputes the boundary values when Neumann + // conditions are used. If a derived class overrides this, it must + // call the base-class OnPreUpdate first. + virtual void OnPreUpdate() override + { + if (mHasMask && this->mBorderValue == std::numeric_limits::max()) + { + // Neumann boundary conditions are in use, so recompute the + // mask/ border. + AssignNeumannMaskBorder(); + } + // else: No mask has been specified or Dirichlet boundary + // conditions are in use. Nothing to do. + } + + // Iterate over all the pixels and call OnUpdate(x) for each pixel + // that is not masked out. + virtual void OnUpdate() override + { + for (int x = 1; x <= mXBound; ++x) + { + if (!mHasMask || mMask[x]) + { + OnUpdate(x); + } + } + } + + // If a derived class overrides this, it must call the base-class + // OnPostUpdate last. The base-class function swaps the buffers for + // the next pass. + virtual void OnPostUpdate() override + { + std::swap(mSrc, mDst); + } + + // The per-pixel processing depends on the PDE algorithm. The (x) + // must be in padded coordinates: 1 <= x <= xbound. + virtual void OnUpdate(int x) = 0; + + // Copy source data to temporary storage. + void LookUp3(int x) + { + auto const& F = mBuffer[mSrc]; + mUm = F[x - 1]; + mUz = F[x]; + mUp = F[x + 1]; + } + + // Image parameters. + int mXBound; + Real mXSpacing; // dx + Real mInvDx; // 1/dx + Real mHalfInvDx; // 1/(2*dx) + Real mInvDxDx; // 1/(dx*dx) + + // Temporary storage for 3-tuple neighborhood. In the notation mUx, + // the x index is in {m,z,p}, referring to subtract 1 (m), no change + // (z), or add 1 (p) to the appropriate index. + Real mUm, mUz, mUp; + + // Successive iterations toggle between two buffers. + std::array, 2> mBuffer; + int mSrc, mDst; + std::vector mMask; + bool mHasMask; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PdeFilter2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PdeFilter2.h new file mode 100644 index 0000000..31b48d5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PdeFilter2.h @@ -0,0 +1,495 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2020.01.11 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class PdeFilter2 : public PdeFilter + { + public: + // Abstract base class. + PdeFilter2(int xBound, int yBound, Real xSpacing, Real ySpacing, + Real const* data, bool const* mask, Real borderValue, + typename PdeFilter::ScaleType scaleType) + : + PdeFilter(xBound * yBound, data, borderValue, scaleType), + mXBound(xBound), + mYBound(yBound), + mXSpacing(xSpacing), + mYSpacing(ySpacing), + mInvDx((Real)1 / xSpacing), + mInvDy((Real)1 / ySpacing), + mHalfInvDx((Real)0.5 * mInvDx), + mHalfInvDy((Real)0.5 * mInvDy), + mInvDxDx(mInvDx * mInvDx), + mFourthInvDxDy(mHalfInvDx * mHalfInvDy), + mInvDyDy(mInvDy * mInvDy), + mUmm(0), mUzm(0), mUpm(0), + mUmz(0), mUzz(0), mUpz(0), + mUmp(0), mUzp(0), mUpp(0), + mSrc(0), + mDst(1), + mMask(xBound + 2, yBound + 2), + mHasMask(mask != nullptr) + { + for (int i = 0; i < 2; ++i) + { + mBuffer[i] = Array2(xBound + 2, yBound + 2); + } + + // The mBuffer[] are ping-pong buffers for filtering. + for (int y = 0, yp = 1, i = 0; y < mYBound; ++y, ++yp) + { + for (int x = 0, xp = 1; x < mXBound; ++x, ++xp, ++i) + { + mBuffer[mSrc][yp][xp] = this->mOffset + (data[i] - this->mMin) * this->mScale; + mBuffer[mDst][yp][xp] = (Real)0; + mMask[yp][xp] = (mHasMask ? mask[i] : 1); + } + } + + // Assign values to the 1-pixel image border. + if (this->mBorderValue != std::numeric_limits::max()) + { + AssignDirichletImageBorder(); + } + else + { + AssignNeumannImageBorder(); + } + + // To handle masks that do not cover the entire image, assign + // values to those pixels that are 8-neighbors of the mask pixels. + if (mHasMask) + { + if (this->mBorderValue != std::numeric_limits::max()) + { + AssignDirichletMaskBorder(); + } + else + { + AssignNeumannMaskBorder(); + } + } + } + + virtual ~PdeFilter2() + { + } + + // Member access. The internal 2D images for "data" and "mask" are + // copies of the inputs to the constructor but padded with a 1-pixel + // thick border to support filtering on the image boundary. These + // images are of size (xbound+2)-by-(ybound+2). The correct lookups + // into the padded arrays are handled internally. + inline int GetXBound() const + { + return mXBound; + } + + inline int GetYBound() const + { + return mYBound; + } + + inline Real GetXSpacing() const + { + return mXSpacing; + } + + inline Real GetYSpacing() const + { + return mYSpacing; + } + + // Pixel access and derivative estimation. The lookups into the + // padded data are handled correctly. The estimation involves only + // the 3-by-3 neighborhood of (x,y), where 0 <= x < xbound and + // 0 <= y < ybound. TODO: If larger neighborhoods are desired at a + // later date, the padding and associated code must be adjusted + // accordingly. + Real GetU(int x, int y) const + { + auto const& F = mBuffer[mSrc]; + int xp1 = x + 1, yp1 = y + 1; + return F[yp1][xp1]; + } + + Real GetUx(int x, int y) const + { + auto const& F = mBuffer[mSrc]; + int xp2 = x + 2, yp1 = y + 1; + return mHalfInvDx * (F[yp1][xp2] - F[yp1][x]); + } + + Real GetUy(int x, int y) const + { + auto const& F = mBuffer[mSrc]; + int xp1 = x + 1, yp2 = y + 2; + return mHalfInvDy * (F[yp2][xp1] - F[y][xp1]); + } + + Real GetUxx(int x, int y) const + { + auto const& F = mBuffer[mSrc]; + int xp1 = x + 1, xp2 = x + 2, yp1 = y + 1; + return mInvDxDx * (F[yp1][xp2] - (Real)2 * F[yp1][xp1] + F[yp1][x]); + } + + Real GetUxy(int x, int y) const + { + auto const& F = mBuffer[mSrc]; + int xp2 = x + 2, yp2 = y + 2; + return mFourthInvDxDy * (F[y][x] - F[y][xp2] + F[yp2][xp2] - F[yp2][x]); + } + + Real GetUyy(int x, int y) const + { + auto const& F = mBuffer[mSrc]; + int xp1 = x + 1, yp1 = y + 1, yp2 = y + 2; + return mInvDyDy * (F[yp2][xp1] - (Real)2 * F[yp1][xp1] + F[y][xp1]); + } + + int GetMask(int x, int y) const + { + int xp1 = x + 1, yp1 = y + 1; + return mMask[yp1][xp1]; + } + + protected: + // Assign values to the 1-pixel image border. + void AssignDirichletImageBorder() + { + int xBp1 = mXBound + 1, yBp1 = mYBound + 1; + int x, y; + + // vertex (0,0) + mBuffer[mSrc][0][0] = this->mBorderValue; + mBuffer[mDst][0][0] = this->mBorderValue; + if (mHasMask) + { + mMask[0][0] = 0; + } + + // vertex (xmax,0) + mBuffer[mSrc][0][xBp1] = this->mBorderValue; + mBuffer[mDst][0][xBp1] = this->mBorderValue; + if (mHasMask) + { + mMask[0][xBp1] = 0; + } + + // vertex (0,ymax) + mBuffer[mSrc][yBp1][0] = this->mBorderValue; + mBuffer[mDst][yBp1][0] = this->mBorderValue; + if (mHasMask) + { + mMask[yBp1][0] = 0; + } + + // vertex (xmax,ymax) + mBuffer[mSrc][yBp1][xBp1] = this->mBorderValue; + mBuffer[mDst][yBp1][xBp1] = this->mBorderValue; + if (mHasMask) + { + mMask[yBp1][xBp1] = 0; + } + + // edges (x,0) and (x,ymax) + for (x = 1; x <= mXBound; ++x) + { + mBuffer[mSrc][0][x] = this->mBorderValue; + mBuffer[mDst][0][x] = this->mBorderValue; + if (mHasMask) + { + mMask[0][x] = 0; + } + + mBuffer[mSrc][yBp1][x] = this->mBorderValue; + mBuffer[mDst][yBp1][x] = this->mBorderValue; + if (mHasMask) + { + mMask[yBp1][x] = 0; + } + } + + // edges (0,y) and (xmax,y) + for (y = 1; y <= mYBound; ++y) + { + mBuffer[mSrc][y][0] = this->mBorderValue; + mBuffer[mDst][y][0] = this->mBorderValue; + if (mHasMask) + { + mMask[y][0] = 0; + } + + mBuffer[mSrc][y][xBp1] = this->mBorderValue; + mBuffer[mDst][y][xBp1] = this->mBorderValue; + if (mHasMask) + { + mMask[y][xBp1] = 0; + } + } + } + + void AssignNeumannImageBorder() + { + int xBp1 = mXBound + 1, yBp1 = mYBound + 1; + int x, y; + Real duplicate; + + // vertex (0,0) + duplicate = mBuffer[mSrc][1][1]; + mBuffer[mSrc][0][0] = duplicate; + mBuffer[mDst][0][0] = duplicate; + if (mHasMask) + { + mMask[0][0] = 0; + } + + // vertex (xmax,0) + duplicate = mBuffer[mSrc][1][mXBound]; + mBuffer[mSrc][0][xBp1] = duplicate; + mBuffer[mDst][0][xBp1] = duplicate; + if (mHasMask) + { + mMask[0][xBp1] = 0; + } + + // vertex (0,ymax) + duplicate = mBuffer[mSrc][mYBound][1]; + mBuffer[mSrc][yBp1][0] = duplicate; + mBuffer[mDst][yBp1][0] = duplicate; + if (mHasMask) + { + mMask[yBp1][0] = 0; + } + + // vertex (xmax,ymax) + duplicate = mBuffer[mSrc][mYBound][mXBound]; + mBuffer[mSrc][yBp1][xBp1] = duplicate; + mBuffer[mDst][yBp1][xBp1] = duplicate; + if (mHasMask) + { + mMask[yBp1][xBp1] = 0; + } + + // edges (x,0) and (x,ymax) + for (x = 1; x <= mXBound; ++x) + { + duplicate = mBuffer[mSrc][1][x]; + mBuffer[mSrc][0][x] = duplicate; + mBuffer[mDst][0][x] = duplicate; + if (mHasMask) + { + mMask[0][x] = 0; + } + + duplicate = mBuffer[mSrc][mYBound][x]; + mBuffer[mSrc][yBp1][x] = duplicate; + mBuffer[mDst][yBp1][x] = duplicate; + if (mHasMask) + { + mMask[yBp1][x] = 0; + } + } + + // edges (0,y) and (xmax,y) + for (y = 1; y <= mYBound; ++y) + { + duplicate = mBuffer[mSrc][y][1]; + mBuffer[mSrc][y][0] = duplicate; + mBuffer[mDst][y][0] = duplicate; + if (mHasMask) + { + mMask[y][0] = 0; + } + + duplicate = mBuffer[mSrc][y][mXBound]; + mBuffer[mSrc][y][xBp1] = duplicate; + mBuffer[mDst][y][xBp1] = duplicate; + if (mHasMask) + { + mMask[y][xBp1] = 0; + } + } + } + + // Assign values to the 1-pixel mask border. + void AssignDirichletMaskBorder() + { + for (int y = 1; y <= mYBound; ++y) + { + for (int x = 1; x <= mXBound; ++x) + { + if (mMask[y][x]) + { + continue; + } + + bool found = false; + for (int i1 = 0, j1 = y - 1; i1 < 3 && !found; ++i1, ++j1) + { + for (int i0 = 0, j0 = x - 1; i0 < 3; ++i0, ++j0) + { + if (mMask[j1][j0]) + { + mBuffer[mSrc][y][x] = this->mBorderValue; + mBuffer[mDst][y][x] = this->mBorderValue; + found = true; + break; + } + } + } + } + } + } + + void AssignNeumannMaskBorder() + { + // Recompute the values just outside the masked region. This + // guarantees that derivative estimations use the current values + // around the boundary. + for (int y = 1; y <= mYBound; ++y) + { + for (int x = 1; x <= mXBound; ++x) + { + if (mMask[y][x]) + { + continue; + } + + int count = 0; + Real average = (Real)0; + for (int i1 = 0, j1 = y - 1; i1 < 3; ++i1, ++j1) + { + for (int i0 = 0, j0 = x - 1; i0 < 3; ++i0, ++j0) + { + if (mMask[j1][j0]) + { + average += mBuffer[mSrc][j1][j0]; + ++count; + } + } + } + + if (count > 0) + { + average /= (Real)count; + mBuffer[mSrc][y][x] = average; + mBuffer[mDst][y][x] = average; + } + } + } + } + + // This function recomputes the boundary values when Neumann + // conditions are used. If a derived class overrides this, it must + // call the base-class OnPreUpdate first. + virtual void OnPreUpdate() override + { + if (mHasMask && this->mBorderValue == std::numeric_limits::max()) + { + // Neumann boundary conditions are in use, so recompute the + // mask/ border. + AssignNeumannMaskBorder(); + } + // else: No mask has been specified or Dirichlet boundary + // conditions are in use. Nothing to do. + } + + // Iterate over all the pixels and call OnUpdate(x,y) for each pixel + // that is not masked out. + virtual void OnUpdate() override + { + for (int y = 1; y <= mYBound; ++y) + { + for (int x = 1; x <= mXBound; ++x) + { + if (!mHasMask || mMask[y][x]) + { + OnUpdateSingle(x, y); + } + } + } + } + + // If a derived class overrides this, it must call the base-class + // OnPostUpdate last. The base-class function swaps the buffers for + // the next pass. + virtual void OnPostUpdate() override + { + std::swap(mSrc, mDst); + } + + // The per-pixel processing depends on the PDE algorithm. The (x,y) + // must be in padded coordinates: 1 <= x <= xbound and + // 1 <= y <= ybound. + virtual void OnUpdateSingle(int x, int y) = 0; + + // Copy source data to temporary storage. + void LookUp5(int x, int y) + { + auto const& F = mBuffer[mSrc]; + int xm = x - 1, xp = x + 1; + int ym = y - 1, yp = y + 1; + mUzm = F[ym][x]; + mUmz = F[y][xm]; + mUzz = F[y][x]; + mUpz = F[y][xp]; + mUzp = F[yp][x]; + } + + void LookUp9(int x, int y) + { + auto const& F = mBuffer[mSrc]; + int xm = x - 1, xp = x + 1; + int ym = y - 1, yp = y + 1; + mUmm = F[ym][xm]; + mUzm = F[ym][x]; + mUpm = F[ym][xp]; + mUmz = F[y][xm]; + mUzz = F[y][x]; + mUpz = F[y][xp]; + mUmp = F[yp][xm]; + mUzp = F[yp][x]; + mUpp = F[yp][xp]; + } + + // Image parameters. + int mXBound, mYBound; + Real mXSpacing; // dx + Real mYSpacing; // dy + Real mInvDx; // 1/dx + Real mInvDy; // 1/dy + Real mHalfInvDx; // 1/(2*dx) + Real mHalfInvDy; // 1/(2*dy) + Real mInvDxDx; // 1/(dx*dx) + Real mFourthInvDxDy; // 1/(4*dx*dy) + Real mInvDyDy; // 1/(dy*dy) + + // Temporary storage for 3x3 neighborhood. In the notation mUxy, the + // x and y indices are in {m,z,p}, referring to subtract 1 (m), no + // change (z), or add 1 (p) to the appropriate index. + Real mUmm, mUzm, mUpm; + Real mUmz, mUzz, mUpz; + Real mUmp, mUzp, mUpp; + + // Successive iterations toggle between two buffers. + std::array, 2> mBuffer; + int mSrc, mDst; + Array2 mMask; + bool mHasMask; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PdeFilter3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PdeFilter3.h new file mode 100644 index 0000000..b08a5be --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PdeFilter3.h @@ -0,0 +1,951 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2020.01.11 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class PdeFilter3 : public PdeFilter + { + public: + // Abstract base class. + PdeFilter3(int xBound, int yBound, int zBound, Real xSpacing, Real ySpacing, + Real zSpacing, Real const* data, bool const* mask, Real borderValue, + typename PdeFilter::ScaleType scaleType) + : + PdeFilter(xBound * yBound * zBound, data, borderValue, scaleType), + mXBound(xBound), + mYBound(yBound), + mZBound(zBound), + mXSpacing(xSpacing), + mYSpacing(ySpacing), + mZSpacing(zSpacing), + mInvDx((Real)1 / xSpacing), + mInvDy((Real)1 / ySpacing), + mInvDz((Real)1 / zSpacing), + mHalfInvDx((Real)0.5 * mInvDx), + mHalfInvDy((Real)0.5 * mInvDy), + mHalfInvDz((Real)0.5 * mInvDz), + mInvDxDx(mInvDx * mInvDx), + mFourthInvDxDy(mHalfInvDx * mHalfInvDy), + mFourthInvDxDz(mHalfInvDx * mHalfInvDz), + mInvDyDy(mInvDy * mInvDy), + mFourthInvDyDz(mHalfInvDy * mHalfInvDz), + mInvDzDz(mInvDz * mInvDz), + mUmmm(0), mUzmm(0), mUpmm(0), + mUmzm(0), mUzzm(0), mUpzm(0), + mUmpm(0), mUzpm(0), mUppm(0), + mUmmz(0), mUzmz(0), mUpmz(0), + mUmzz(0), mUzzz(0), mUpzz(0), + mUmpz(0), mUzpz(0), mUppz(0), + mUmmp(0), mUzmp(0), mUpmp(0), + mUmzp(0), mUzzp(0), mUpzp(0), + mUmpp(0), mUzpp(0), mUppp(0), + mSrc(0), + mDst(1), + mMask(xBound + 2, yBound + 2, zBound + 2), + mHasMask(mask != nullptr) + { + for (int i = 0; i < 2; ++i) + { + mBuffer[i] = Array3(xBound + 2, yBound + 2, zBound + 2); + } + + // The mBuffer[] are ping-pong buffers for filtering. + for (int z = 0, zp = 1, i = 0; z < mZBound; ++z, ++zp) + { + for (int y = 0, yp = 1; y < mYBound; ++y, ++yp) + { + for (int x = 0, xp = 1; x < mXBound; ++x, ++xp, ++i) + { + mBuffer[mSrc][zp][yp][xp] = this->mOffset + (data[i] - this->mMin) * this->mScale; + mBuffer[mDst][zp][yp][xp] = (Real)0; + mMask[zp][yp][xp] = (mHasMask ? mask[i] : 1); + } + } + } + + // Assign values to the 1-voxel thick border. + if (this->mBorderValue != std::numeric_limits::max()) + { + AssignDirichletImageBorder(); + } + else + { + AssignNeumannImageBorder(); + } + + // To handle masks that do not cover the entire image, assign + // values to those voxels that are 26-neighbors of the mask + // voxels. + if (mHasMask) + { + if (this->mBorderValue != std::numeric_limits::max()) + { + AssignDirichletMaskBorder(); + } + else + { + AssignNeumannMaskBorder(); + } + } + } + + virtual ~PdeFilter3() + { + } + + // Member access. The internal 2D images for "data" and "mask" are + // copies of the inputs to the constructor but padded with a 1-voxel + // thick border to support filtering on the image boundary. These + // images are of size (xbound+2)-by-(ybound+2)-by-(zbound+2). The + // correct lookups into the padded arrays are handled internally. + inline int GetXBound() const + { + return mXBound; + } + + inline int GetYBound() const + { + return mYBound; + } + + inline int GetZBound() const + { + return mZBound; + } + + inline Real GetXSpacing() const + { + return mXSpacing; + } + + inline Real GetYSpacing() const + { + return mYSpacing; + } + + inline Real GetZSpacing() const + { + return mZSpacing; + } + + // Voxel access and derivative estimation. The lookups into the + // padded data are handled correctly. The estimation involves only + // the 3-by-3-by-3 neighborhood of (x,y,z), where 0 <= x < xbound, + // 0 <= y < ybound and 0 <= z < zbound. TODO: If larger neighborhoods + // are desired at a later date, the padding and associated code must + // be adjusted accordingly. + Real GetU(int x, int y, int z) const + { + auto const& F = mBuffer[mSrc]; + int xp1 = x + 1, yp1 = y + 1, zp1 = z + 1; + return F[zp1][yp1][xp1]; + } + + Real GetUx(int x, int y, int z) const + { + auto const& F = mBuffer[mSrc]; + int xp2 = x + 2, yp1 = y + 1, zp1 = z + 1; + return mHalfInvDx * (F[zp1][yp1][xp2] - F[zp1][yp1][x]); + } + + Real GetUy(int x, int y, int z) const + { + auto const& F = mBuffer[mSrc]; + int xp1 = x + 1, yp2 = y + 2, zp1 = z + 1; + return mHalfInvDy * (F[zp1][yp2][xp1] - F[zp1][y][xp1]); + } + + Real GetUz(int x, int y, int z) const + { + auto const& F = mBuffer[mSrc]; + int xp1 = x + 1, yp1 = y + 1, zp2 = z + 2; + return mHalfInvDz * (F[zp2][yp1][xp1] - F[z][yp1][xp1]); + } + + Real GetUxx(int x, int y, int z) const + { + auto const& F = mBuffer[mSrc]; + int xp1 = x + 1, xp2 = x + 2, yp1 = y + 1, zp1 = z + 1; + return mInvDxDx * (F[zp1][yp1][xp2] - (Real)2 * F[zp1][yp1][xp1] + F[zp1][yp1][x]); + } + + Real GetUxy(int x, int y, int z) const + { + auto const& F = mBuffer[mSrc]; + int xp2 = x + 2, yp2 = y + 2, zp1 = z + 1; + return mFourthInvDxDy * (F[zp1][y][x] - F[zp1][y][xp2] + F[zp1][yp2][xp2] - F[zp1][yp2][x]); + } + + Real GetUxz(int x, int y, int z) const + { + auto const& F = mBuffer[mSrc]; + int xp2 = x + 2, yp1 = y + 1, zp2 = z + 2; + return mFourthInvDxDz * (F[z][yp1][x] - F[z][yp1][xp2] + F[zp2][yp1][xp2] - F[zp2][yp1][x]); + } + + Real GetUyy(int x, int y, int z) const + { + auto const& F = mBuffer[mSrc]; + int xp1 = x + 1, yp1 = y + 1, yp2 = y + 2, zp1 = z + 1; + return mInvDyDy * (F[zp1][yp2][xp1] - (Real)2 * F[zp1][yp1][zp1] + F[zp1][y][xp1]); + } + + Real GetUyz(int x, int y, int z) const + { + auto const& F = mBuffer[mSrc]; + int xp1 = x + 1, yp2 = y + 2, zp2 = z + 2; + return mFourthInvDyDz * (F[z][y][xp1] - F[z][yp2][xp1] + F[zp2][yp2][xp1] - F[zp2][y][xp1]); + } + + Real GetUzz(int x, int y, int z) const + { + auto const& F = mBuffer[mSrc]; + int xp1 = x + 1, yp1 = y + 1, zp1 = z + 1, zp2 = z + 2; + return mInvDzDz * (F[zp2][yp1][xp1] - (Real)2 * F[zp1][yp1][xp1] + F[z][yp1][xp1]); + } + + int GetMask(int x, int y, int z) const + { + int xp1 = x + 1, yp1 = y + 1, zp1 = z + 1; + return mMask[zp1][yp1][xp1]; + } + + protected: + // Assign values to the 1-voxel image border. + void AssignDirichletImageBorder() + { + int xBp1 = mXBound + 1, yBp1 = mYBound + 1, zBp1 = mZBound + 1; + int x, y, z; + + // vertex (0,0,0) + mBuffer[mSrc][0][0][0] = this->mBorderValue; + mBuffer[mDst][0][0][0] = this->mBorderValue; + if (mHasMask) + { + mMask[0][0][0] = 0; + } + + // vertex (xmax,0,0) + mBuffer[mSrc][0][0][xBp1] = this->mBorderValue; + mBuffer[mDst][0][0][xBp1] = this->mBorderValue; + if (mHasMask) + { + mMask[0][0][xBp1] = 0; + } + + // vertex (0,ymax,0) + mBuffer[mSrc][0][yBp1][0] = this->mBorderValue; + mBuffer[mDst][0][yBp1][0] = this->mBorderValue; + if (mHasMask) + { + mMask[0][yBp1][0] = 0; + } + + // vertex (xmax,ymax,0) + mBuffer[mSrc][0][yBp1][xBp1] = this->mBorderValue; + mBuffer[mDst][0][yBp1][xBp1] = this->mBorderValue; + if (mHasMask) + { + mMask[0][yBp1][xBp1] = 0; + } + + // vertex (0,0,zmax) + mBuffer[mSrc][zBp1][0][0] = this->mBorderValue; + mBuffer[mDst][zBp1][0][0] = this->mBorderValue; + if (mHasMask) + { + mMask[zBp1][0][0] = 0; + } + + // vertex (xmax,0,zmax) + mBuffer[mSrc][zBp1][0][xBp1] = this->mBorderValue; + mBuffer[mDst][zBp1][0][xBp1] = this->mBorderValue; + if (mHasMask) + { + mMask[zBp1][0][xBp1] = 0; + } + + // vertex (0,ymax,zmax) + mBuffer[mSrc][zBp1][yBp1][0] = this->mBorderValue; + mBuffer[mDst][zBp1][yBp1][0] = this->mBorderValue; + if (mHasMask) + { + mMask[zBp1][yBp1][0] = 0; + } + + // vertex (xmax,ymax,zmax) + mBuffer[mSrc][zBp1][yBp1][xBp1] = this->mBorderValue; + mBuffer[mDst][zBp1][yBp1][xBp1] = this->mBorderValue; + if (mHasMask) + { + mMask[zBp1][yBp1][xBp1] = 0; + } + + // edges (x,0,0) and (x,ymax,0) + for (x = 1; x <= mXBound; ++x) + { + mBuffer[mSrc][0][0][x] = this->mBorderValue; + mBuffer[mDst][0][0][x] = this->mBorderValue; + if (mHasMask) + { + mMask[0][0][x] = 0; + } + + mBuffer[mSrc][0][yBp1][x] = this->mBorderValue; + mBuffer[mDst][0][yBp1][x] = this->mBorderValue; + if (mHasMask) + { + mMask[0][yBp1][x] = 0; + } + } + + // edges (0,y,0) and (xmax,y,0) + for (y = 1; y <= mYBound; ++y) + { + mBuffer[mSrc][0][y][0] = this->mBorderValue; + mBuffer[mDst][0][y][0] = this->mBorderValue; + if (mHasMask) + { + mMask[0][y][0] = 0; + } + + mBuffer[mSrc][0][y][xBp1] = this->mBorderValue; + mBuffer[mDst][0][y][xBp1] = this->mBorderValue; + if (mHasMask) + { + mMask[0][y][xBp1] = 0; + } + } + + // edges (x,0,zmax) and (x,ymax,zmax) + for (x = 1; x <= mXBound; ++x) + { + mBuffer[mSrc][zBp1][0][x] = this->mBorderValue; + mBuffer[mDst][zBp1][0][x] = this->mBorderValue; + if (mHasMask) + { + mMask[zBp1][0][x] = 0; + } + + mBuffer[mSrc][zBp1][yBp1][x] = this->mBorderValue; + mBuffer[mDst][zBp1][yBp1][x] = this->mBorderValue; + if (mHasMask) + { + mMask[zBp1][yBp1][x] = 0; + } + } + + // edges (0,y,zmax) and (xmax,y,zmax) + for (y = 1; y <= mYBound; ++y) + { + mBuffer[mSrc][zBp1][y][0] = this->mBorderValue; + mBuffer[mDst][zBp1][y][0] = this->mBorderValue; + if (mHasMask) + { + mMask[zBp1][y][0] = 0; + } + + mBuffer[mSrc][zBp1][y][xBp1] = this->mBorderValue; + mBuffer[mDst][zBp1][y][xBp1] = this->mBorderValue; + if (mHasMask) + { + mMask[zBp1][y][xBp1] = 0; + } + } + + // edges (0,0,z) and (xmax,0,z) + for (z = 1; z <= mZBound; ++z) + { + mBuffer[mSrc][z][0][0] = this->mBorderValue; + mBuffer[mDst][z][0][0] = this->mBorderValue; + if (mHasMask) + { + mMask[z][0][0] = 0; + } + + mBuffer[mSrc][z][0][xBp1] = this->mBorderValue; + mBuffer[mDst][z][0][xBp1] = this->mBorderValue; + if (mHasMask) + { + mMask[z][0][xBp1] = 0; + } + } + + // edges (0,ymax,z) and (xmax,ymax,z) + for (z = 1; z <= mZBound; ++z) + { + mBuffer[mSrc][z][yBp1][0] = this->mBorderValue; + mBuffer[mDst][z][yBp1][0] = this->mBorderValue; + if (mHasMask) + { + mMask[z][yBp1][0] = 0; + } + + mBuffer[mSrc][z][yBp1][xBp1] = this->mBorderValue; + mBuffer[mDst][z][yBp1][xBp1] = this->mBorderValue; + if (mHasMask) + { + mMask[z][yBp1][xBp1] = 0; + } + } + + // faces (x,y,0) and (x,y,zmax) + for (y = 1; y <= mYBound; ++y) + { + for (x = 1; x <= mXBound; ++x) + { + mBuffer[mSrc][0][y][x] = this->mBorderValue; + mBuffer[mDst][0][y][x] = this->mBorderValue; + if (mHasMask) + { + mMask[0][y][x] = 0; + } + + mBuffer[mSrc][zBp1][y][x] = this->mBorderValue; + mBuffer[mDst][zBp1][y][x] = this->mBorderValue; + if (mHasMask) + { + mMask[zBp1][y][x] = 0; + } + } + } + + // faces (x,0,z) and (x,ymax,z) + for (z = 1; z <= mZBound; ++z) + { + for (x = 1; x <= mXBound; ++x) + { + mBuffer[mSrc][z][0][x] = this->mBorderValue; + mBuffer[mDst][z][0][x] = this->mBorderValue; + if (mHasMask) + { + mMask[z][0][x] = 0; + } + + mBuffer[mSrc][z][yBp1][x] = this->mBorderValue; + mBuffer[mDst][z][yBp1][x] = this->mBorderValue; + if (mHasMask) + { + mMask[z][yBp1][x] = 0; + } + } + } + + // faces (0,y,z) and (xmax,y,z) + for (z = 1; z <= mZBound; ++z) + { + for (y = 1; y <= mYBound; ++y) + { + mBuffer[mSrc][z][y][0] = this->mBorderValue; + mBuffer[mDst][z][y][0] = this->mBorderValue; + if (mHasMask) + { + mMask[z][y][0] = 0; + } + + mBuffer[mSrc][z][y][xBp1] = this->mBorderValue; + mBuffer[mDst][z][y][xBp1] = this->mBorderValue; + if (mHasMask) + { + mMask[z][y][xBp1] = 0; + } + } + } + } + + void AssignNeumannImageBorder() + { + int xBp1 = mXBound + 1, yBp1 = mYBound + 1, zBp1 = mZBound + 1; + int x, y, z; + Real duplicate; + + // vertex (0,0,0) + duplicate = mBuffer[mSrc][1][1][1]; + mBuffer[mSrc][0][0][0] = duplicate; + mBuffer[mDst][0][0][0] = duplicate; + if (mHasMask) + { + mMask[0][0][0] = 0; + } + + // vertex (xmax,0,0) + duplicate = mBuffer[mSrc][1][1][mXBound]; + mBuffer[mSrc][0][0][xBp1] = duplicate; + mBuffer[mDst][0][0][xBp1] = duplicate; + if (mHasMask) + { + mMask[0][0][xBp1] = 0; + } + + // vertex (0,ymax,0) + duplicate = mBuffer[mSrc][1][mYBound][1]; + mBuffer[mSrc][0][yBp1][0] = duplicate; + mBuffer[mDst][0][yBp1][0] = duplicate; + if (mHasMask) + { + mMask[0][yBp1][0] = 0; + } + + // vertex (xmax,ymax,0) + duplicate = mBuffer[mSrc][1][mYBound][mXBound]; + mBuffer[mSrc][0][yBp1][xBp1] = duplicate; + mBuffer[mDst][0][yBp1][xBp1] = duplicate; + if (mHasMask) + { + mMask[0][yBp1][xBp1] = 0; + } + + // vertex (0,0,zmax) + duplicate = mBuffer[mSrc][mZBound][1][1]; + mBuffer[mSrc][zBp1][0][0] = duplicate; + mBuffer[mDst][zBp1][0][0] = duplicate; + if (mHasMask) + { + mMask[zBp1][0][0] = 0; + } + + // vertex (xmax,0,zmax) + duplicate = mBuffer[mSrc][mZBound][1][mXBound]; + mBuffer[mSrc][zBp1][0][xBp1] = duplicate; + mBuffer[mDst][zBp1][0][xBp1] = duplicate; + if (mHasMask) + { + mMask[zBp1][0][xBp1] = 0; + } + + // vertex (0,ymax,zmax) + duplicate = mBuffer[mSrc][mZBound][mYBound][1]; + mBuffer[mSrc][zBp1][yBp1][0] = duplicate; + mBuffer[mDst][zBp1][yBp1][0] = duplicate; + if (mHasMask) + { + mMask[zBp1][yBp1][0] = 0; + } + + // vertex (xmax,ymax,zmax) + duplicate = mBuffer[mSrc][mZBound][mYBound][mXBound]; + mBuffer[mSrc][zBp1][yBp1][xBp1] = duplicate; + mBuffer[mDst][zBp1][yBp1][xBp1] = duplicate; + if (mHasMask) + { + mMask[zBp1][yBp1][xBp1] = 0; + } + + // edges (x,0,0) and (x,ymax,0) + for (x = 1; x <= mXBound; ++x) + { + duplicate = mBuffer[mSrc][1][1][x]; + mBuffer[mSrc][0][0][x] = duplicate; + mBuffer[mDst][0][0][x] = duplicate; + if (mHasMask) + { + mMask[0][0][x] = 0; + } + + duplicate = mBuffer[mSrc][1][mYBound][x]; + mBuffer[mSrc][0][yBp1][x] = duplicate; + mBuffer[mDst][0][yBp1][x] = duplicate; + if (mHasMask) + { + mMask[0][yBp1][x] = 0; + } + } + + // edges (0,y,0) and (xmax,y,0) + for (y = 1; y <= mYBound; ++y) + { + duplicate = mBuffer[mSrc][1][y][1]; + mBuffer[mSrc][0][y][0] = duplicate; + mBuffer[mDst][0][y][0] = duplicate; + if (mHasMask) + { + mMask[0][y][0] = 0; + } + + duplicate = mBuffer[mSrc][1][y][mXBound]; + mBuffer[mSrc][0][y][xBp1] = duplicate; + mBuffer[mDst][0][y][xBp1] = duplicate; + if (mHasMask) + { + mMask[0][y][xBp1] = 0; + } + } + + // edges (x,0,zmax) and (x,ymax,zmax) + for (x = 1; x <= mXBound; ++x) + { + duplicate = mBuffer[mSrc][mZBound][1][x]; + mBuffer[mSrc][zBp1][0][x] = duplicate; + mBuffer[mDst][zBp1][0][x] = duplicate; + if (mHasMask) + { + mMask[zBp1][0][x] = 0; + } + + duplicate = mBuffer[mSrc][mZBound][mYBound][x]; + mBuffer[mSrc][zBp1][yBp1][x] = duplicate; + mBuffer[mDst][zBp1][yBp1][x] = duplicate; + if (mHasMask) + { + mMask[zBp1][yBp1][x] = 0; + } + } + + // edges (0,y,zmax) and (xmax,y,zmax) + for (y = 1; y <= mYBound; ++y) + { + duplicate = mBuffer[mSrc][mZBound][y][1]; + mBuffer[mSrc][zBp1][y][0] = duplicate; + mBuffer[mDst][zBp1][y][0] = duplicate; + if (mHasMask) + { + mMask[zBp1][y][0] = 0; + } + + duplicate = mBuffer[mSrc][mZBound][y][mXBound]; + mBuffer[mSrc][zBp1][y][xBp1] = duplicate; + mBuffer[mDst][zBp1][y][xBp1] = duplicate; + if (mHasMask) + { + mMask[zBp1][y][xBp1] = 0; + } + } + + // edges (0,0,z) and (xmax,0,z) + for (z = 1; z <= mZBound; ++z) + { + duplicate = mBuffer[mSrc][z][1][1]; + mBuffer[mSrc][z][0][0] = duplicate; + mBuffer[mDst][z][0][0] = duplicate; + if (mHasMask) + { + mMask[z][0][0] = 0; + } + + duplicate = mBuffer[mSrc][z][1][mXBound]; + mBuffer[mSrc][z][0][xBp1] = duplicate; + mBuffer[mDst][z][0][xBp1] = duplicate; + if (mHasMask) + { + mMask[z][0][xBp1] = 0; + } + } + + // edges (0,ymax,z) and (xmax,ymax,z) + for (z = 1; z <= mZBound; ++z) + { + duplicate = mBuffer[mSrc][z][mYBound][1]; + mBuffer[mSrc][z][yBp1][0] = duplicate; + mBuffer[mDst][z][yBp1][0] = duplicate; + if (mHasMask) + { + mMask[z][yBp1][0] = 0; + } + + duplicate = mBuffer[mSrc][z][mYBound][mXBound]; + mBuffer[mSrc][z][yBp1][xBp1] = duplicate; + mBuffer[mDst][z][yBp1][xBp1] = duplicate; + if (mHasMask) + { + mMask[z][yBp1][xBp1] = 0; + } + } + + // faces (x,y,0) and (x,y,zmax) + for (y = 1; y <= mYBound; ++y) + { + for (x = 1; x <= mXBound; ++x) + { + duplicate = mBuffer[mSrc][1][y][x]; + mBuffer[mSrc][0][y][x] = duplicate; + mBuffer[mDst][0][y][x] = duplicate; + if (mHasMask) + { + mMask[0][y][x] = 0; + } + + duplicate = mBuffer[mSrc][mZBound][y][x]; + mBuffer[mSrc][zBp1][y][x] = duplicate; + mBuffer[mDst][zBp1][y][x] = duplicate; + if (mHasMask) + { + mMask[zBp1][y][x] = 0; + } + } + } + + // faces (x,0,z) and (x,ymax,z) + for (z = 1; z <= mZBound; ++z) + { + for (x = 1; x <= mXBound; ++x) + { + duplicate = mBuffer[mSrc][z][1][x]; + mBuffer[mSrc][z][0][x] = duplicate; + mBuffer[mDst][z][0][x] = duplicate; + if (mHasMask) + { + mMask[z][0][x] = 0; + } + + duplicate = mBuffer[mSrc][z][mYBound][x]; + mBuffer[mSrc][z][yBp1][x] = duplicate; + mBuffer[mDst][z][yBp1][x] = duplicate; + if (mHasMask) + { + mMask[z][yBp1][x] = 0; + } + } + } + + // faces (0,y,z) and (xmax,y,z) + for (z = 1; z <= mZBound; ++z) + { + for (y = 1; y <= mYBound; ++y) + { + duplicate = mBuffer[mSrc][z][y][1]; + mBuffer[mSrc][z][y][0] = duplicate; + mBuffer[mDst][z][y][0] = duplicate; + if (mHasMask) + { + mMask[z][y][0] = 0; + } + + duplicate = mBuffer[mSrc][z][y][mXBound]; + mBuffer[mSrc][z][y][xBp1] = duplicate; + mBuffer[mDst][z][y][xBp1] = duplicate; + if (mHasMask) + { + mMask[z][y][xBp1] = 0; + } + } + } + } + + // Assign values to the 1-voxel mask border. + void AssignDirichletMaskBorder() + { + for (int z = 1; z <= mZBound; ++z) + { + for (int y = 1; y <= mYBound; ++y) + { + for (int x = 1; x <= mXBound; ++x) + { + if (mMask[z][y][x]) + { + continue; + } + + bool found = false; + for (int i2 = 0, j2 = z - 1; i2 < 3 && !found; ++i2, ++j2) + { + for (int i1 = 0, j1 = y - 1; i1 < 3 && !found; ++i1, ++j1) + { + for (int i0 = 0, j0 = x - 1; i0 < 3; ++i0, ++j0) + { + if (mMask[j2][j1][j0]) + { + mBuffer[mSrc][z][y][x] = this->mBorderValue; + mBuffer[mDst][z][y][x] = this->mBorderValue; + found = true; + break; + } + } + } + } + } + } + } + } + + void AssignNeumannMaskBorder() + { + // Recompute the values just outside the masked region. This + // guarantees that derivative estimations use the current values + // around the boundary. + for (int z = 1; z <= mZBound; ++z) + { + for (int y = 1; y <= mYBound; ++y) + { + for (int x = 1; x <= mXBound; ++x) + { + if (mMask[z][y][x]) + { + continue; + } + + int count = 0; + Real average = (Real)0; + for (int i2 = 0, j2 = z - 1; i2 < 3; ++i2, ++j2) + { + for (int i1 = 0, j1 = y - 1; i1 < 3; ++i1, ++j1) + { + for (int i0 = 0, j0 = x - 1; i0 < 3; ++i0, ++j0) + { + if (mMask[j2][j1][j0]) + { + average += mBuffer[mSrc][j2][j1][j0]; + count++; + } + } + } + } + + if (count > 0) + { + average /= (Real)count; + mBuffer[mSrc][z][y][x] = average; + mBuffer[mDst][z][y][x] = average; + } + } + } + } + } + + // This function recomputes the boundary values when Neumann + // conditions are used. If a derived class overrides this, it must + // call the base-class OnPreUpdate first. + virtual void OnPreUpdate() override + { + if (mHasMask && this->mBorderValue == std::numeric_limits::max()) + { + // Neumann boundary conditions are in use, so recompute the + // mask border. + AssignNeumannMaskBorder(); + } + // else: No mask has been specified or Dirichlet boundary + // conditions are in use. Nothing to do. + } + + // Iterate over all the pixels and call OnUpdate(x,y,z) for each voxel + // that is not masked out. + virtual void OnUpdate() override + { + for (int z = 1; z <= mZBound; ++z) + { + for (int y = 1; y <= mYBound; ++y) + { + for (int x = 1; x <= mXBound; ++x) + { + if (!mHasMask || mMask[z][y][x]) + { + OnUpdateSingle(x, y, z); + } + } + } + } + } + + // If a derived class overrides this, it must call the base-class + // OnPostUpdate last. The base-class function swaps the buffers for + // the next pass. + virtual void OnPostUpdate() override + { + std::swap(mSrc, mDst); + } + + // The per-pixel processing depends on the PDE algorithm. The (x,y,z) + // must be in padded coordinates: 1 <= x <= xbound, 1 <= y <= ybound + // and 1 <= z <= zbound. + virtual void OnUpdateSingle(int x, int y, int z) = 0; + + // Copy source data to temporary storage. + void LookUp7(int x, int y, int z) + { + auto const& F = mBuffer[mSrc]; + int xm = x - 1, xp = x + 1; + int ym = y - 1, yp = y + 1; + int zm = z - 1, zp = z + 1; + mUzzm = F[zm][y][x]; + mUzmz = F[z][ym][x]; + mUmzz = F[z][y][xm]; + mUzzz = F[z][y][x]; + mUpzz = F[z][y][xp]; + mUzpz = F[z][yp][x]; + mUzzp = F[zp][y][x]; + } + + void LookUp27(int x, int y, int z) + { + auto const& F = mBuffer[mSrc]; + int xm = x - 1, xp = x + 1; + int ym = y - 1, yp = y + 1; + int zm = z - 1, zp = z + 1; + mUmmm = F[zm][ym][xm]; + mUzmm = F[zm][ym][x]; + mUpmm = F[zm][ym][xp]; + mUmzm = F[zm][y][xm]; + mUzzm = F[zm][y][x]; + mUpzm = F[zm][y][xp]; + mUmpm = F[zm][yp][xm]; + mUzpm = F[zm][yp][x]; + mUppm = F[zm][yp][xp]; + mUmmz = F[z][ym][xm]; + mUzmz = F[z][ym][x]; + mUpmz = F[z][ym][xp]; + mUmzz = F[z][y][xm]; + mUzzz = F[z][y][x]; + mUpzz = F[z][y][xp]; + mUmpz = F[z][yp][xm]; + mUzpz = F[z][yp][x]; + mUppz = F[z][yp][xp]; + mUmmp = F[zp][ym][xm]; + mUzmp = F[zp][ym][x]; + mUpmp = F[zp][ym][xp]; + mUmzp = F[zp][y][xm]; + mUzzp = F[zp][y][x]; + mUpzp = F[zp][y][xp]; + mUmpp = F[zp][yp][xm]; + mUzpp = F[zp][yp][x]; + mUppp = F[zp][yp][xp]; + } + + // Image parameters. + int mXBound, mYBound, mZBound; + Real mXSpacing; // dx + Real mYSpacing; // dy + Real mZSpacing; // dz + Real mInvDx; // 1/dx + Real mInvDy; // 1/dy + Real mInvDz; // 1/dz + Real mHalfInvDx; // 1/(2*dx) + Real mHalfInvDy; // 1/(2*dy) + Real mHalfInvDz; // 1/(2*dz) + Real mInvDxDx; // 1/(dx*dx) + Real mFourthInvDxDy; // 1/(4*dx*dy) + Real mFourthInvDxDz; // 1/(4*dx*dz) + Real mInvDyDy; // 1/(dy*dy) + Real mFourthInvDyDz; // 1/(4*dy*dz) + Real mInvDzDz; // 1/(dz*dz) + + // Temporary storage for 3x3x3 neighborhood. In the notation mUxyz, + // the x, y and z indices are in {m,z,p}, referring to subtract 1 (m), + // no change (z), or add 1 (p) to the appropriate index. + Real mUmmm, mUzmm, mUpmm; + Real mUmzm, mUzzm, mUpzm; + Real mUmpm, mUzpm, mUppm; + Real mUmmz, mUzmz, mUpmz; + Real mUmzz, mUzzz, mUpzz; + Real mUmpz, mUzpz, mUppz; + Real mUmmp, mUzmp, mUpmp; + Real mUmzp, mUzzp, mUpzp; + Real mUmpp, mUzpp, mUppp; + + // Successive iterations toggle between two buffers. + std::array, 2> mBuffer; + int mSrc, mDst; + Array3 mMask; + bool mHasMask; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PlanarMesh.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PlanarMesh.h new file mode 100644 index 0000000..0504052 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PlanarMesh.h @@ -0,0 +1,448 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The planar mesh class is convenient for many applications involving +// searches for triangles containing a specified point. A couple of +// issues can show up in practice when the input data to the constructors +// is very large (number of triangles on the order of 10^5 or larger). +// +// The first constructor builds an ETManifoldMesh mesh that contains +// std::map objects. When such maps are large, the amount of time it +// takes to delete them is enormous. Although you can control the level +// of debug support in MSVS 2013 (see _ITERATOR_DEBUG_LEVEL), turning off +// checking might very well affect other classes for which you want +// iterator checking to be on. An alternative to reduce debugging time +// is to dynamically allocate the PlanarMesh object in the main thread but +// then launch another thread to delete the object and avoid stalling +// the main thread. For example, +// +// PlanarMesh* pmesh = +// new PlanarMesh(numV, vertices, numT, indices); +// ; +// std::thread deleter = [pmesh](){ delete pmesh; }; +// deleter.detach(); // Do not wait for the thread to finish. +// +// The second constructor has the mesh passed in, but mTriIndexMap is used +// in both constructors and can take time to delete. +// +// The input mesh should be consistently oriented, say, the triangles are +// counterclockwise ordered. The vertices should be consistent with this +// ordering. However, floating-point rounding errors in generating the +// vertices can cause apparent fold-over of the mesh; that is, theoretically +// the vertex geometry supports counterclockwise geometry but numerical +// errors cause an inconsistency. This can manifest in the mQuery.ToLine +// tests whereby cycles of triangles occur in the linear walk. When cycles +// occur, GetContainingTriangle(P,startTriangle) will iterate numTriangle +// times before reporting that the triangle cannot be found, which is a +// very slow process (in debug or release builds). The function +// GetContainingTriangle(P,startTriangle,visited) is provided to avoid the +// performance loss, trapping a cycle the first time and exiting, but +// again reporting that the triangle cannot be found. If you know that the +// query should be (theoretically) successful, use the second version of +// GetContainingTriangle. If it fails by returning -1, then perform an +// exhaustive search over the triangles. For example, +// +// int triangle = pmesh->GetContainingTriangle(P,startTriangle,visited); +// if (triangle >= 0) +// { +// ; +// } +// else +// { +// int numTriangles = pmesh->GetNumTriangles(); +// for (triangle = 0; triangle < numTriangles; ++triangle) +// { +// if (pmesh->Contains(triangle, P)) +// { +// ; +// break; +// } +// } +// if (triangle == numTriangles) +// { +// ; +// } +// } +// +// The PlanarMesh<*>::Contains function does not require the triangles to +// be ordered. + +namespace WwiseGTE +{ + template + class PlanarMesh + { + public: + // Construction. The inputs must represent a manifold mesh of + // triangles in the plane. The index array must have 3*numTriangles + // elements, each triple of indices representing a triangle in the + // mesh. Each index is into the 'vertices' array. + PlanarMesh(int numVertices, Vector2 const* vertices, int numTriangles, int const* indices) + : + mNumVertices(0), + mVertices(nullptr), + mNumTriangles(0) + { + LogAssert(numVertices >= 3 && vertices != nullptr && numTriangles >= 1 + && indices != nullptr, "Invalid input."); + + // Create a mesh in order to get adjacency information. + int const* current = indices; + for (int t = 0; t < numTriangles; ++t) + { + int v0 = *current++; + int v1 = *current++; + int v2 = *current++; + if (!mMesh.Insert(v0, v1, v2)) + { + // TODO: Fix this comment once the exception handling is + // tested. + // + // The 'mesh' object will throw on nonmanifold inputs. + return; + } + } + + // We have a valid mesh. + CreateVertices(numVertices, vertices); + + // Build the adjacency graph using the triangle ordering implied + // by the indices, not the mesh triangle map, to preserve the + // triangle ordering of the input indices. + mNumTriangles = numTriangles; + int const numIndices = 3 * numTriangles; + mIndices.resize(numIndices); + + std::copy(indices, indices + numIndices, mIndices.begin()); + for (int t = 0, vIndex = 0; t < numTriangles; ++t) + { + int v0 = indices[vIndex++]; + int v1 = indices[vIndex++]; + int v2 = indices[vIndex++]; + TriangleKey key(v0, v1, v2); + mTriIndexMap.insert(std::make_pair(key, t)); + } + + mAdjacencies.resize(numIndices); + auto const& tmap = mMesh.GetTriangles(); + for (int t = 0, base = 0; t < numTriangles; ++t, base += 3) + { + int v0 = indices[base]; + int v1 = indices[base + 1]; + int v2 = indices[base + 2]; + TriangleKey key(v0, v1, v2); + auto element = tmap.find(key); + for (int i = 0; i < 3; ++i) + { + auto adj = element->second->T[i].lock(); + if (adj) + { + key = TriangleKey(adj->V[0], adj->V[1], adj->V[2]); + mAdjacencies[base + i] = mTriIndexMap.find(key)->second; + } + else + { + mAdjacencies[base + i] = -1; + } + } + } + } + + PlanarMesh(int numVertices, Vector2 const* vertices, ETManifoldMesh const& mesh) + : + mNumVertices(0), + mVertices(nullptr), + mNumTriangles(0) + { + if (numVertices < 3 || !vertices || mesh.GetTriangles().size() < 1) + { + throw std::invalid_argument("Invalid input in PlanarMesh constructor."); + } + + // We have a valid mesh. + CreateVertices(numVertices, vertices); + + // Build the adjacency graph using the triangle ordering implied + // by the mesh triangle map. + auto const& tmap = mesh.GetTriangles(); + mNumTriangles = static_cast(tmap.size()); + mIndices.resize(3 * mNumTriangles); + + int tIndex = 0, vIndex = 0; + for (auto const& element : tmap) + { + mTriIndexMap.insert(std::make_pair(element.first, tIndex++)); + for (int i = 0; i < 3; ++i, ++vIndex) + { + mIndices[vIndex] = element.second->V[i]; + } + } + + mAdjacencies.resize(3 * mNumTriangles); + vIndex = 0; + for (auto const& element : tmap) + { + for (int i = 0; i < 3; ++i, ++vIndex) + { + auto adj = element.second->T[i].lock(); + if (adj) + { + TriangleKey key(adj->V[0], adj->V[1], adj->V[2]); + mAdjacencies[vIndex] = mTriIndexMap.find(key)->second; + } + else + { + mAdjacencies[vIndex] = -1; + } + } + } + } + + // Mesh information. + inline int GetNumVertices() const + { + return mNumVertices; + } + + inline int GetNumTriangles() const + { + return mNumTriangles; + } + + inline Vector2 const* GetVertices() const + { + return mVertices; + } + + inline int const* GetIndices() const + { + return mIndices.data(); + } + + inline int const* GetAdjacencies() const + { + return mAdjacencies.data(); + } + + // Containment queries. The function GetContainingTriangle works + // correctly when the planar mesh is a convex set. If the mesh is not + // convex, it is possible that the linear-walk search algorithm exits + // the mesh before finding a containing triangle. For example, a + // C-shaped mesh can contain a point in the top branch of the "C". + // A starting point in the bottom branch of the "C" will lead to the + // search exiting the bottom branch and having no path to walk to the + // top branch. If your mesh is not convex and you want a correct + // containment query, you will have to append "outside" triangles to + // your mesh to form a convex set. + int GetContainingTriangle(Vector2 const& P, int startTriangle = 0) const + { + Vector2 test{ P[0], P[1] }; + + // Use triangle edges as binary separating lines. + int triangle = startTriangle; + for (int i = 0; i < mNumTriangles; ++i) + { + int ibase = 3 * triangle; + int const* v = &mIndices[ibase]; + + if (mQuery.ToLine(test, v[0], v[1]) > 0) + { + triangle = mAdjacencies[ibase]; + if (triangle == -1) + { + return -1; + } + continue; + } + + if (mQuery.ToLine(test, v[1], v[2]) > 0) + { + triangle = mAdjacencies[ibase + 1]; + if (triangle == -1) + { + return -1; + } + continue; + } + + if (mQuery.ToLine(test, v[2], v[0]) > 0) + { + triangle = mAdjacencies[ibase + 2]; + if (triangle == -1) + { + return -1; + } + continue; + } + + return triangle; + } + + return -1; + } + + int GetContainingTriangle(Vector2 const& P, int startTriangle, std::set& visited) const + { + Vector2 test{ P[0], P[1] }; + visited.clear(); + + // Use triangle edges as binary separating lines. + int triangle = startTriangle; + for (int i = 0; i < mNumTriangles; ++i) + { + visited.insert(triangle); + int ibase = 3 * triangle; + int const* v = &mIndices[ibase]; + + if (mQuery.ToLine(test, v[0], v[1]) > 0) + { + triangle = mAdjacencies[ibase]; + if (triangle == -1 || visited.find(triangle) != visited.end()) + { + return -1; + } + continue; + } + + if (mQuery.ToLine(test, v[1], v[2]) > 0) + { + triangle = mAdjacencies[ibase + 1]; + if (triangle == -1 || visited.find(triangle) != visited.end()) + { + return -1; + } + continue; + } + + if (mQuery.ToLine(test, v[2], v[0]) > 0) + { + triangle = mAdjacencies[ibase + 2]; + if (triangle == -1 || visited.find(triangle) != visited.end()) + { + return -1; + } + continue; + } + + return triangle; + } + + return -1; + } + + bool GetVertices(int t, std::array, 3>& vertices) const + { + if (0 <= t && t < mNumTriangles) + { + for (int i = 0, vIndex = 3 * t; i < 3; ++i, ++vIndex) + { + vertices[i] = mVertices[mIndices[vIndex]]; + } + return true; + } + return false; + } + + bool GetIndices(int t, std::array& indices) const + { + if (0 <= t && t < mNumTriangles) + { + for (int i = 0, vIndex = 3 * t; i < 3; ++i, ++vIndex) + { + indices[i] = mIndices[vIndex]; + } + return true; + } + return false; + } + + bool GetAdjacencies(int t, std::array& adjacencies) const + { + if (0 <= t && t < mNumTriangles) + { + for (int i = 0, vIndex = 3 * t; i < 3; ++i, ++vIndex) + { + adjacencies[i] = mAdjacencies[vIndex]; + } + return true; + } + return false; + } + + bool GetBarycentrics(int t, Vector2 const& P, std::array& bary) const + { + std::array indices; + if (GetIndices(t, indices)) + { + Vector2 rtP{ P[0], P[1] }; + std::array, 3> rtV; + for (int i = 0; i < 3; ++i) + { + Vector2 const& V = mComputeVertices[indices[i]]; + for (int j = 0; j < 2; ++j) + { + rtV[i][j] = (RationalType)V[j]; + } + }; + + RationalType rtBary[3]; + if (ComputeBarycentrics(rtP, rtV[0], rtV[1], rtV[2], rtBary)) + { + for (int i = 0; i < 3; ++i) + { + bary[i] = (InputType)rtBary[i]; + } + return true; + } + } + return false; + } + + bool Contains(int triangle, Vector2 const& P) const + { + Vector2 test{ P[0], P[1] }; + Vector2 v[3]; + v[0] = mComputeVertices[mIndices[3 * triangle + 0]]; + v[1] = mComputeVertices[mIndices[3 * triangle + 1]]; + v[2] = mComputeVertices[mIndices[3 * triangle + 2]]; + PointInPolygon2 pip(3, v); + return pip.Contains(test); + } + + public: + void CreateVertices(int numVertices, Vector2 const* vertices) + { + mNumVertices = numVertices; + mVertices = vertices; + mComputeVertices.resize(mNumVertices); + for (int i = 0; i < mNumVertices; ++i) + { + for (int j = 0; j < 2; ++j) + { + mComputeVertices[i][j] = (ComputeType)mVertices[i][j]; + } + } + mQuery.Set(mNumVertices, &mComputeVertices[0]); + } + + int mNumVertices; + Vector2 const* mVertices; + int mNumTriangles; + std::vector mIndices; + ETManifoldMesh mMesh; + std::map, int> mTriIndexMap; + std::vector mAdjacencies; + std::vector> mComputeVertices; + PrimalQuery2 mQuery; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Polygon2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Polygon2.h new file mode 100644 index 0000000..50a7a4f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Polygon2.h @@ -0,0 +1,270 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The Polygon2 object represents a simple polygon: No duplicate vertices, +// closed (each vertex is shared by exactly two edges), and no +// self-intersections at interior edge points. The 'vertexPool' array can +// contain more points than needed to define the polygon, which allows the +// vertex pool to have multiple polygons associated with it. Thus, the +// programmer must ensure that the vertex pool persists as long as any +// Polygon2 objects exist that depend on the pool. The number of polygon +// vertices is 'numIndices' and must be 3 or larger. The 'indices' array +// refers to the points in 'vertexPool' that are part of the polygon and must +// have 'numIndices' unique elements. The edges of the polygon are pairs of +// indices into 'vertexPool', +// edge[0] = (indices[0], indices[1]) +// : +// edge[numIndices-2] = (indices[numIndices-2], indices[numIndices-1]) +// edge[numIndices-1] = (indices[numIndices-1], indices[0]) +// The programmer should ensure the polygon is simple. The geometric +// queries are valid regardless of whether the polygon is oriented clockwise +// or counterclockwise. +// +// NOTE: Comparison operators are not provided. The semantics of equal +// polygons is complicated and (at the moment) not useful. The vertex pools +// can be different and indices do not match, but the vertices they reference +// can match. Even with a shared vertex pool, the indices can be "rotated", +// leading to the same polygon abstractly but the data structures do not +// match. + +namespace WwiseGTE +{ + template + class Polygon2 + { + public: + // Construction. The constructor succeeds when 'numIndices' >= 3 and + // 'vertexPool' and 'indices' are not null; we cannot test whether you + // have a valid number of elements in the input arrays. A copy is + // made of 'indices', but the 'vertexPool' is not copied. If the + // constructor fails, the internal vertex pointer is set to null, the + // index array has no elements, and the orientation is set to + // clockwise. + Polygon2(Vector2 const* vertexPool, int numIndices, + int const* indices, bool counterClockwise) + : + mVertexPool(vertexPool), + mCounterClockwise(counterClockwise) + { + if (numIndices >= 3 && vertexPool && indices) + { + for (int i = 0; i < numIndices; ++i) + { + mVertices.insert(indices[i]); + } + + if (numIndices == static_cast(mVertices.size())) + { + mIndices.resize(numIndices); + std::copy(indices, indices + numIndices, mIndices.begin()); + return; + } + + // At least one duplicated vertex was encountered, so the + // polygon is not simple. Fail the constructor call. + mVertices.clear(); + } + + // Invalid input to the Polygon2 constructor. + mVertexPool = nullptr; + mCounterClockwise = false; + } + + // To validate construction, create an object as shown: + // Polygon2 polygon(parameters); + // if (!polygon) { ; } + inline operator bool() const + { + return mVertexPool != nullptr; + } + + // Member access. + inline Vector2 const* GetVertexPool() const + { + return mVertexPool; + } + + inline std::set const& GetVertices() const + { + return mVertices; + } + + inline std::vector const& GetIndices() const + { + return mIndices; + } + + inline bool CounterClockwise() const + { + return mCounterClockwise; + } + + // Geometric queries. + Vector2 ComputeVertexAverage() const + { + Vector2 average = Vector2::Zero(); + if (mVertexPool) + { + for (int index : mVertices) + { + average += mVertexPool[index]; + } + average /= static_cast(mVertices.size()); + } + return average; + } + + Real ComputePerimeterLength() const + { + Real length(0); + if (mVertexPool) + { + Vector2 v0 = mVertexPool[mIndices.back()]; + for (int index : mIndices) + { + Vector2 v1 = mVertexPool[index]; + length += Length(v1 - v0); + v0 = v1; + } + } + return length; + } + + Real ComputeArea() const + { + Real area(0); + if (mVertexPool) + { + int const numIndices = static_cast(mIndices.size()); + Vector2 v0 = mVertexPool[mIndices[numIndices - 2]]; + Vector2 v1 = mVertexPool[mIndices[numIndices - 1]]; + for (int index : mIndices) + { + Vector2 v2 = mVertexPool[index]; + area += v1[0] * (v2[1] - v0[1]); + v0 = v1; + v1 = v2; + } + area *= (Real)0.5; + } + return std::fabs(area); + } + + // Simple polygons have no self-intersections at interior points + // of edges. The test is an exhaustive all-pairs intersection + // test for edges, which is inefficient for polygons with a large + // number of vertices. TODO: Provide an efficient algorithm that + // uses the algorithm of class RectangleManager.h. + bool IsSimple() const + { + if (!mVertexPool) + { + return false; + } + + // For mVertexPool to be nonnull, the number of indices is + // guaranteed to be at least 3. + int const numIndices = static_cast(mIndices.size()); + if (numIndices == 3) + { + // The polygon is a triangle. + return true; + } + + return IsSimpleInternal(); + } + + // Convex polygons are simple polygons where the angles between + // consecutive edges are less than or equal to pi radians. + bool IsConvex() const + { + if (!mVertexPool) + { + return false; + } + + // For mVertexPool to be nonnull, the number of indices is + // guaranteed to be at least 3. + int const numIndices = static_cast(mIndices.size()); + if (numIndices == 3) + { + // The polygon is a triangle. + return true; + } + + return IsSimpleInternal() && IsConvexInternal(); + } + + private: + // These calls have preconditions that mVertexPool is not null and + // mIndices.size() > 3. The heart of the algorithms are implemented + // here. + bool IsSimpleInternal() const + { + Segment2 seg0, seg1; + TIQuery, Segment2> query; + typename TIQuery, Segment2>::Result result; + + int const numIndices = static_cast(mIndices.size()); + for (int i0 = 0; i0 < numIndices; ++i0) + { + int i0p1 = (i0 + 1) % numIndices; + seg0.p[0] = mVertexPool[mIndices[i0]]; + seg0.p[1] = mVertexPool[mIndices[i0p1]]; + + int i1min = (i0 + 2) % numIndices; + int i1max = (i0 - 2 + numIndices) % numIndices; + for (int i1 = i1min; i1 <= i1max; ++i1) + { + int i1p1 = (i1 + 1) % numIndices; + seg1.p[0] = mVertexPool[mIndices[i1]]; + seg1.p[1] = mVertexPool[mIndices[i1p1]]; + + result = query(seg0, seg1); + if (result.intersect) + { + return false; + } + } + } + return true; + } + + bool IsConvexInternal() const + { + Real sign = (mCounterClockwise ? (Real)1 : (Real)-1); + int const numIndices = static_cast(mIndices.size()); + for (int i = 0; i < numIndices; ++i) + { + int iPrev = (i + numIndices - 1) % numIndices; + int iNext = (i + 1) % numIndices; + Vector2 vPrev = mVertexPool[mIndices[iPrev]]; + Vector2 vCurr = mVertexPool[mIndices[i]]; + Vector2 vNext = mVertexPool[mIndices[iNext]]; + Vector2 edge0 = vCurr - vPrev; + Vector2 edge1 = vNext - vCurr; + Real test = sign * DotPerp(edge0, edge1); + if (test < (Real)0) + { + return false; + } + } + return true; + } + + Vector2 const* mVertexPool; + std::set mVertices; + std::vector mIndices; + bool mCounterClockwise; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PolyhedralMassProperties.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PolyhedralMassProperties.h new file mode 100644 index 0000000..c2d0448 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PolyhedralMassProperties.h @@ -0,0 +1,142 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + // The input triangle mesh must represent a polyhedron. The triangles are + // represented as triples of indices into the vertex array. + // The index array has numTriangles such triples. The Boolean value + // 'bodyCoords is' 'true' if you want the inertia tensor to be relative to + // body coordinates but 'false' if you want it to be relative to world + // coordinates. + // + // The code assumes the rigid body has a constant density of 1. If your + // application assigns a constant density of 'd', then you must multiply + // the output 'mass' by 'd' and the output 'inertia' by 'd'. + + template + void ComputeMassProperties(Vector3 const* vertices, int numTriangles, + int const* indices, bool bodyCoords, Real& mass, Vector3& center, + Matrix3x3& inertia) + { + Real const oneDiv6 = (Real)1 / (Real)6; + Real const oneDiv24 = (Real)1 / (Real)24; + Real const oneDiv60 = (Real)1 / (Real)60; + Real const oneDiv120 = (Real)1 / (Real)120; + + // order: 1, x, y, z, x^2, y^2, z^2, xy, yz, zx + std::array integral; + integral.fill((Real)0); + + int const* index = indices; + for (int i = 0; i < numTriangles; ++i) + { + // Get vertices of triangle i. + Vector3 v0 = vertices[*index++]; + Vector3 v1 = vertices[*index++]; + Vector3 v2 = vertices[*index++]; + + // Get cross product of edges and normal vector. + Vector3 V1mV0 = v1 - v0; + Vector3 V2mV0 = v2 - v0; + Vector3 N = Cross(V1mV0, V2mV0); + + // Compute integral terms. + Real tmp0, tmp1, tmp2; + Real f1x, f2x, f3x, g0x, g1x, g2x; + tmp0 = v0[0] + v1[0]; + f1x = tmp0 + v2[0]; + tmp1 = v0[0] * v0[0]; + tmp2 = tmp1 + v1[0] * tmp0; + f2x = tmp2 + v2[0] * f1x; + f3x = v0[0] * tmp1 + v1[0] * tmp2 + v2[0] * f2x; + g0x = f2x + v0[0] * (f1x + v0[0]); + g1x = f2x + v1[0] * (f1x + v1[0]); + g2x = f2x + v2[0] * (f1x + v2[0]); + + Real f1y, f2y, f3y, g0y, g1y, g2y; + tmp0 = v0[1] + v1[1]; + f1y = tmp0 + v2[1]; + tmp1 = v0[1] * v0[1]; + tmp2 = tmp1 + v1[1] * tmp0; + f2y = tmp2 + v2[1] * f1y; + f3y = v0[1] * tmp1 + v1[1] * tmp2 + v2[1] * f2y; + g0y = f2y + v0[1] * (f1y + v0[1]); + g1y = f2y + v1[1] * (f1y + v1[1]); + g2y = f2y + v2[1] * (f1y + v2[1]); + + Real f1z, f2z, f3z, g0z, g1z, g2z; + tmp0 = v0[2] + v1[2]; + f1z = tmp0 + v2[2]; + tmp1 = v0[2] * v0[2]; + tmp2 = tmp1 + v1[2] * tmp0; + f2z = tmp2 + v2[2] * f1z; + f3z = v0[2] * tmp1 + v1[2] * tmp2 + v2[2] * f2z; + g0z = f2z + v0[2] * (f1z + v0[2]); + g1z = f2z + v1[2] * (f1z + v1[2]); + g2z = f2z + v2[2] * (f1z + v2[2]); + + // Update integrals. + integral[0] += N[0] * f1x; + integral[1] += N[0] * f2x; + integral[2] += N[1] * f2y; + integral[3] += N[2] * f2z; + integral[4] += N[0] * f3x; + integral[5] += N[1] * f3y; + integral[6] += N[2] * f3z; + integral[7] += N[0] * (v0[1] * g0x + v1[1] * g1x + v2[1] * g2x); + integral[8] += N[1] * (v0[2] * g0y + v1[2] * g1y + v2[2] * g2y); + integral[9] += N[2] * (v0[0] * g0z + v1[0] * g1z + v2[0] * g2z); + } + + integral[0] *= oneDiv6; + integral[1] *= oneDiv24; + integral[2] *= oneDiv24; + integral[3] *= oneDiv24; + integral[4] *= oneDiv60; + integral[5] *= oneDiv60; + integral[6] *= oneDiv60; + integral[7] *= oneDiv120; + integral[8] *= oneDiv120; + integral[9] *= oneDiv120; + + // mass + mass = integral[0]; + + // center of mass + center = Vector3{ integral[1], integral[2], integral[3] } / mass; + + // inertia relative to world origin + inertia(0, 0) = integral[5] + integral[6]; + inertia(0, 1) = -integral[7]; + inertia(0, 2) = -integral[9]; + inertia(1, 0) = inertia(0, 1); + inertia(1, 1) = integral[4] + integral[6]; + inertia(1, 2) = -integral[8]; + inertia(2, 0) = inertia(0, 2); + inertia(2, 1) = inertia(1, 2); + inertia(2, 2) = integral[4] + integral[5]; + + // inertia relative to center of mass + if (bodyCoords) + { + inertia(0, 0) -= mass * (center[1] * center[1] + center[2] * center[2]); + inertia(0, 1) += mass * center[0] * center[1]; + inertia(0, 2) += mass * center[2] * center[0]; + inertia(1, 0) = inertia(0, 1); + inertia(1, 1) -= mass * (center[2] * center[2] + center[0] * center[0]); + inertia(1, 2) += mass * center[1] * center[2]; + inertia(2, 0) = inertia(0, 2); + inertia(2, 1) = inertia(1, 2); + inertia(2, 2) -= mass * (center[0] * center[0] + center[1] * center[1]); + } + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Polyhedron3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Polyhedron3.h new file mode 100644 index 0000000..2bd76af --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Polyhedron3.h @@ -0,0 +1,172 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +// The Polyhedron3 object represents a simple polyhedron. The 'vertexPool' +// array can contain more points than needed to define the polyhedron, which +// allows the vertex pool to have multiple polyhedra associated with it. +// Thus, the programmer must ensure that the vertex pool persists as long as +// any Polyhedron3 objects exist that depend on the pool. The number of +// polyhedron indices is 'numIndices' and must be 6 or larger The 'indices' +// array refers to the points in 'vertexPool' that form the triangle faces, +// so 'numIndices' must be a multiple of 3. The number of vertices is +// the number of unique elements in 'indices' and is determined during +// construction. The programmer should ensure the polyhedron is simple. The +// geometric queries are valid regardless of whether the polyhedron triangles +// are oriented clockwise or counterclockwise. +// +// NOTE: Comparison operators are not provided. The semantics of equal +// polyhedra is complicated and (at the moment) not useful. The vertex pools +// can be different and indices do not match, but the vertices they reference +// can match. Even with a shared vertex pool, the indices can be permuted, +// leading to the same polyhedron abstractly but the data structures do not +// match. + +namespace WwiseGTE +{ + template + class Polyhedron3 + { + public: + // Construction. The constructor succeeds when 'numIndices >= 12' (at + // least 4 triangles), and 'vertexPool' and 'indices' are not null; we + // cannot test whether you have a valid number of elements in the + // input arrays. A copy is made of 'indices', but the 'vertexPool' is + // not copied. If the constructor fails, the internal vertex pointer + // is set to null, the number of vertices is set to zero, the index + // array has no elements, and the triangle face orientation is set to + // clockwise. + Polyhedron3(std::shared_ptr>> const& vertexPool, + int numIndices, int const* indices, bool counterClockwise) + : + mVertexPool(vertexPool), + mCounterClockwise(counterClockwise) + { + if (vertexPool && indices && numIndices >= 12 && (numIndices % 3) == 0) + { + for (int i = 0; i < numIndices; ++i) + { + mUniqueIndices.insert(indices[i]); + } + + mIndices.resize(numIndices); + std::copy(indices, indices + numIndices, mIndices.begin()); + } + else + { + // Encountered an invalid input. + mVertexPool = nullptr; + mCounterClockwise = false; + } + } + + // To validate construction, create an object as shown: + // Polyhedron3 polyhedron(parameters); + // if (!polyhedron) { ; } + inline operator bool() const + { + return mVertexPool != nullptr; + } + + // Member access. + inline std::shared_ptr>> const& GetVertexPool() const + { + return mVertexPool; + } + + inline std::vector> const& GetVertices() const + { + return *mVertexPool.get(); + } + + inline std::set const& GetUniqueIndices() const + { + return mUniqueIndices; + } + + inline std::vector const& GetIndices() const + { + return mIndices; + } + + inline bool CounterClockwise() const + { + return mCounterClockwise; + } + + // Geometric queries. + Vector3 ComputeVertexAverage() const + { + Vector3 average = Vector3::Zero(); + if (mVertexPool) + { + auto vertexPool = GetVertices(); + for (int index : mUniqueIndices) + { + average += vertexPool[index]; + } + average /= static_cast(mUniqueIndices.size()); + } + return average; + } + + Real ComputeSurfaceArea() const + { + Real surfaceArea(0); + if (mVertexPool) + { + auto vertexPool = GetVertices(); + int const numTriangles = static_cast(mIndices.size()) / 3; + int const* indices = mIndices.data(); + for (int t = 0; t < numTriangles; ++t) + { + int v0 = *indices++; + int v1 = *indices++; + int v2 = *indices++; + Vector3 edge0 = vertexPool[v1] - vertexPool[v0]; + Vector3 edge1 = vertexPool[v2] - vertexPool[v0]; + Vector3 cross = Cross(edge0, edge1); + surfaceArea += Length(cross); + } + surfaceArea *= (Real)0.5; + } + return surfaceArea; + } + + Real ComputeVolume() const + { + Real volume(0); + if (mVertexPool) + { + auto vertexPool = GetVertices(); + int const numTriangles = static_cast(mIndices.size()) / 3; + int const* indices = mIndices.data(); + for (int t = 0; t < numTriangles; ++t) + { + int v0 = *indices++; + int v1 = *indices++; + int v2 = *indices++; + volume += DotCross(vertexPool[v0], vertexPool[v1], vertexPool[v2]); + } + volume /= (Real)6; + } + return std::fabs(volume); + } + + private: + std::shared_ptr>> mVertexPool; + std::set mUniqueIndices; + std::vector mIndices; + bool mCounterClockwise; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Polynomial1.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Polynomial1.h new file mode 100644 index 0000000..1a7fe54 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Polynomial1.h @@ -0,0 +1,598 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class Polynomial1 + { + public: + // Construction and destruction. The first constructor creates a + // polynomial of the specified degree but sets all coefficients to + // zero (to ensure initialization). You are responsible for setting + // the coefficients, presumably with the degree-term set to a nonzero + // number. In the second constructor, the degree is the number of + // initializers plus 1, but then adjusted so that coefficient[degree] + // is not zero (unless all initializer values are zero). + Polynomial1(unsigned int degree = 0) + : + mCoefficient(degree + 1, (Real)0) + { + } + + Polynomial1(std::initializer_list values) + { + // C++ 11 will call the default constructor for + // Polynomial1 p{}, so it is guaranteed that + // values.size() > 0. + mCoefficient.resize(values.size()); + std::copy(values.begin(), values.end(), mCoefficient.begin()); + EliminateLeadingZeros(); + } + + // Support for partial construction, where the default constructor is + // used when the degree is not yet known. The coefficients are + // uninitialized. + void SetDegree(unsigned int degree) + { + mCoefficient.resize(degree + 1); + } + + // Set all coefficients to the specified value. + void SetCoefficients(Real value) + { + std::fill(mCoefficient.begin(), mCoefficient.end(), value); + } + + // Member access. + inline unsigned int GetDegree() const + { + // By design, mCoefficient.size() > 0. + return static_cast(mCoefficient.size() - 1); + } + + inline Real const& operator[](unsigned int i) const + { + return mCoefficient[i]; + } + + inline Real& operator[](unsigned int i) + { + return mCoefficient[i]; + } + + + // Comparisons. + inline bool operator==(Polynomial1 const& p) const + { + return mCoefficient == p.mCoefficient; + } + + inline bool operator!=(Polynomial1 const& p) const + { + return mCoefficient != p.mCoefficient; + } + + inline bool operator< (Polynomial1 const& p) const + { + return mCoefficient < p.mCoefficient; + } + + inline bool operator<=(Polynomial1 const& p) const + { + return mCoefficient <= p.mCoefficient; + } + + inline bool operator> (Polynomial1 const& p) const + { + return mCoefficient > p.mCoefficient; + } + + inline bool operator>=(Polynomial1 const& p) const + { + return mCoefficient >= p.mCoefficient; + } + + // Evaluate the polynomial. If the polynomial is invalid, the + // function returns zero. + Real operator()(Real t) const + { + int i = static_cast(mCoefficient.size()); + Real result = mCoefficient[--i]; + for (--i; i >= 0; --i) + { + result *= t; + result += mCoefficient[i]; + } + return result; + } + + // Compute the derivative of the polynomial. + Polynomial1 GetDerivative() const + { + unsigned int const degree = GetDegree(); + if (degree > 0) + { + Polynomial1 result(degree - 1); + for (unsigned int i0 = 0, i1 = 1; i0 < degree; ++i0, ++i1) + { + result.mCoefficient[i0] = mCoefficient[i1] * (Real)i1; + } + return result; + } + else + { + Polynomial1 result(0); + result[0] = (Real)0; + return result; + } + } + + // Inversion (invpoly[i] = poly[degree-i] for 0 <= i <= degree). + Polynomial1 GetInversion() const + { + unsigned int const degree = GetDegree(); + Polynomial1 result(degree); + for (unsigned int i = 0; i <= degree; ++i) + { + result.mCoefficient[i] = mCoefficient[degree - i]; + } + return result; + } + + // Tranlation. If 'this' is p(t}, return p(t-t0). + Polynomial1 GetTranslation(Real t0) const + { + Polynomial1 factor{ -t0, (Real)1 }; // f(t) = t - t0 + unsigned int const degree = GetDegree(); + Polynomial1 result{ mCoefficient[degree] }; + for (unsigned int i = 1, j = degree - 1; i <= degree; ++i, --j) + { + result = mCoefficient[j] + factor * result; + } + return result; + } + + // Eliminate any leading zeros in the polynomial, except in the case + // the degree is 0 and the coefficient is 0. The elimination is + // necessary when arithmetic operations cause a decrease in the degree + // of the result. For example, (1 + x + x^2) + (1 + 2*x - x^2) = + // (2 + 3*x). The inputs both have degree 2, so the result is created + // with degree 2. After the addition we find that the degree is in + // fact 1 and resize the array of coefficients. This function is + // called internally by the arithmetic operators, but it is exposed in + // the public interface in case you need it for your own purposes. + void EliminateLeadingZeros() + { + size_t size = mCoefficient.size(); + if (size > 1) + { + Real const zero = (Real)0; + int leading; + for (leading = static_cast(size) - 1; leading > 0; --leading) + { + if (mCoefficient[leading] != zero) + { + break; + } + } + + mCoefficient.resize(++leading); + } + } + + // If 'this' is P(t) and the divisor is D(t) with + // degree(P) >= degree(D), then P(t) = Q(t)*D(t)+R(t) where Q(t) is + // the quotient with degree(Q) = degree(P) - degree(D) and R(t) is the + // remainder with degree(R) < degree(D). If this routine is called + // with degree(P) < degree(D), then Q = 0 and R = P are returned. + void Divide(Polynomial1 const& divisor, Polynomial1& quotient, Polynomial1& remainder) const + { + Real const zero = (Real)0; + int divisorDegree = static_cast(divisor.GetDegree()); + int quotientDegree = static_cast(GetDegree()) - divisorDegree; + if (quotientDegree >= 0) + { + quotient.SetDegree(quotientDegree); + + // Temporary storage for the remainder. + Polynomial1 tmp = *this; + + // Do the division using the Euclidean algorithm. + Real inv = ((Real)1) / divisor[divisorDegree]; + for (int i = quotientDegree; i >= 0; --i) + { + int j = divisorDegree + i; + quotient[i] = inv * tmp[j]; + for (j--; j >= i; j--) + { + tmp[j] -= quotient[i] * divisor[j - i]; + } + } + + // Calculate the correct degree for the remainder. + if (divisorDegree >= 1) + { + int remainderDegree = divisorDegree - 1; + while (remainderDegree > 0 && tmp[remainderDegree] == zero) + { + --remainderDegree; + } + + remainder.SetDegree(remainderDegree); + for (int i = 0; i <= remainderDegree; ++i) + { + remainder[i] = tmp[i]; + } + } + else + { + remainder.SetDegree(0); + remainder[0] = zero; + } + } + else + { + quotient.SetDegree(0); + quotient[0] = zero; + remainder = *this; + } + } + + // Scale the polynomial so the highest-degree term has coefficient 1. + void MakeMonic() + { + EliminateLeadingZeros(); + Real const one(1); + if (mCoefficient.back() != one) + { + unsigned int degree = GetDegree(); + Real invLeading = one / mCoefficient.back(); + mCoefficient.back() = one; + for (unsigned int i = 0; i < degree; ++i) + { + mCoefficient[i] *= invLeading; + } + } + } + + protected: + // The class is designed so that mCoefficient.size() >= 1. + std::vector mCoefficient; + }; + + // Compute the greatest common divisor of two polynomials. The returned + // polynomial has leading coefficient 1 (except when zero-valued + // polynomials are passed to the function. + template + Polynomial1 GreatestCommonDivisor(Polynomial1 const& p0, Polynomial1 const& p1) + { + // The numerator should be the polynomial of larger degree. + Polynomial1 a, b; + if (p0.GetDegree() >= p1.GetDegree()) + { + a = p0; + b = p1; + } + else + { + a = p1; + b = p0; + } + + Polynomial1 const zero{ (Real)0 }; + if (a == zero || b == zero) + { + return (a != zero ? a : zero); + } + + // Make the polynomials monic to keep the coefficients reasonable size + // when computing with floating-point Real. + a.MakeMonic(); + b.MakeMonic(); + + Polynomial1 q, r; + for (;;) + { + a.Divide(b, q, r); + if (r != zero) + { + // a = q * b + r, so gcd(a,b) = gcd(b, r) + a = b; + b = r; + b.MakeMonic(); + } + else + { + b.MakeMonic(); + break; + } + } + + return b; + } + + // Factor f = factor[0]*factor[1]^2*factor[2]^3*...*factor[n-1]^n + // according to the square-free factorization algorithm + // https://en.wikipedia.org/wiki/Square-free_polynomial + template + void SquareFreeFactorization(Polynomial1 const& f, std::vector>& factors) + { + // In the call to Divide(...), we know that the divisor exactly + // divides the numerator, so r = 0 after all such calls. + Polynomial1 fder = f.GetDerivative(); + Polynomial1 a, b, c, d, q, r; + + a = GreatestCommonDivisor(f, fder); + f.Divide(a, b, r); // b = f / a + fder.Divide(a, c, r); // c = fder / a + d = c - b.GetDerivative(); + + do + { + a = GreatestCommonDivisor(b, d); + factors.emplace_back(a); + b.Divide(a, q, r); // q = b / a + b = std::move(q); + d.Divide(a, c, r); // c = d / a + d = c - b.GetDerivative(); + } while (b.GetDegree() > 0); + } + + // Unary operations. + template + Polynomial1 operator+(Polynomial1 const& p) + { + return p; + } + + template + Polynomial1 operator-(Polynomial1 const& p) + { + unsigned int const degree = p.GetDegree(); + Polynomial1 result(degree); + for (unsigned int i = 0; i <= degree; ++i) + { + result[i] = -p[i]; + } + return result; + } + + // Linear-algebraic operations. + template + Polynomial1 operator+(Polynomial1 const& p0, Polynomial1 const& p1) + { + unsigned int const p0Degree = p0.GetDegree(), p1Degree = p1.GetDegree(); + unsigned int i; + if (p0Degree >= p1Degree) + { + Polynomial1 result(p0Degree); + for (i = 0; i <= p1Degree; ++i) + { + result[i] = p0[i] + p1[i]; + } + for (/**/; i <= p0Degree; ++i) + { + result[i] = p0[i]; + } + result.EliminateLeadingZeros(); + return result; + } + else + { + Polynomial1 result(p1Degree); + for (i = 0; i <= p0Degree; ++i) + { + result[i] = p0[i] + p1[i]; + } + for (/**/; i <= p1Degree; ++i) + { + result[i] = p1[i]; + } + result.EliminateLeadingZeros(); + return result; + } + } + + template + Polynomial1 operator-(Polynomial1 const& p0, Polynomial1 const& p1) + { + unsigned int const p0Degree = p0.GetDegree(), p1Degree = p1.GetDegree(); + unsigned int i; + if (p0Degree >= p1Degree) + { + Polynomial1 result(p0Degree); + for (i = 0; i <= p1Degree; ++i) + { + result[i] = p0[i] - p1[i]; + } + for (/**/; i <= p0Degree; ++i) + { + result[i] = p0[i]; + } + result.EliminateLeadingZeros(); + return result; + } + else + { + Polynomial1 result(p1Degree); + for (i = 0; i <= p0Degree; ++i) + { + result[i] = p0[i] - p1[i]; + } + for (/**/; i <= p1Degree; ++i) + { + result[i] = -p1[i]; + } + result.EliminateLeadingZeros(); + return result; + } + } + + template + Polynomial1 operator*(Polynomial1 const& p0, Polynomial1 const& p1) + { + unsigned int const p0Degree = p0.GetDegree(), p1Degree = p1.GetDegree(); + Polynomial1 result(p0Degree + p1Degree); + result.SetCoefficients((Real)0); + for (unsigned int i0 = 0; i0 <= p0Degree; ++i0) + { + for (unsigned int i1 = 0; i1 <= p1Degree; ++i1) + { + result[i0 + i1] += p0[i0] * p1[i1]; + } + } + return result; + } + + template + Polynomial1 operator+(Polynomial1 const& p, Real scalar) + { + unsigned int const degree = p.GetDegree(); + Polynomial1 result(degree); + result[0] = p[0] + scalar; + for (unsigned int i = 1; i <= degree; ++i) + { + result[i] = p[i]; + } + return result; + } + + template + Polynomial1 operator+(Real scalar, Polynomial1 const& p) + { + unsigned int const degree = p.GetDegree(); + Polynomial1 result(degree); + result[0] = p[0] + scalar; + for (unsigned int i = 1; i <= degree; ++i) + { + result[i] = p[i]; + } + return result; + } + + template + Polynomial1 operator-(Polynomial1 const& p, Real scalar) + { + unsigned int const degree = p.GetDegree(); + Polynomial1 result(degree); + result[0] = p[0] - scalar; + for (unsigned int i = 1; i <= degree; ++i) + { + result[i] = p[i]; + } + return result; + } + + template + Polynomial1 operator-(Real scalar, Polynomial1 const& p) + { + unsigned int const degree = p.GetDegree(); + Polynomial1 result(degree); + result[0] = scalar - p[0]; + for (unsigned int i = 1; i <= degree; ++i) + { + result[i] = -p[i]; + } + return result; + } + + template + Polynomial1 operator*(Polynomial1 const& p, Real scalar) + { + unsigned int const degree = p.GetDegree(); + Polynomial1 result(degree); + for (unsigned int i = 0; i <= degree; ++i) + { + result[i] = scalar * p[i]; + } + return result; + } + + template + Polynomial1 operator*(Real scalar, Polynomial1 const& p) + { + unsigned int const degree = p.GetDegree(); + Polynomial1 result(degree); + for (unsigned int i = 0; i <= degree; ++i) + { + result[i] = scalar * p[i]; + } + return result; + } + + template + Polynomial1 operator/(Polynomial1 const& p, Real scalar) + { + LogAssert(scalar != (Real)0, "Division by zero."); + + unsigned int const degree = p.GetDegree(); + Real invScalar = (Real)1 / scalar; + Polynomial1 result(degree); + for (unsigned int i = 0; i <= degree; ++i) + { + result[i] = invScalar * p[i]; + } + return result; + } + + template + Polynomial1& operator+=(Polynomial1& p0, Polynomial1 const& p1) + { + p0 = p0 + p1; + return p0; + } + + template + Polynomial1& operator-=(Polynomial1& p0, Polynomial1 const& p1) + { + p0 = p0 - p1; + return p0; + } + + template + Polynomial1& operator*=(Polynomial1& p0, Polynomial1 const& p1) + { + p0 = p0 * p1; + return p0; + } + + template + Polynomial1& operator+=(Polynomial1& p, Real scalar) + { + p[0] += scalar; + return p; + } + + template + Polynomial1& operator-=(Polynomial1& p, Real scalar) + { + p[0] -= scalar; + return p; + } + + template + Polynomial1& operator*=(Polynomial1& p, Real scalar) + { + p = p * scalar; + return p; + } + + template + Polynomial1 & operator/=(Polynomial1& p, Real scalar) + { + p = p / scalar; + return p; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PolynomialCurve.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PolynomialCurve.h new file mode 100644 index 0000000..3bc5e7c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PolynomialCurve.h @@ -0,0 +1,118 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class PolynomialCurve : public ParametricCurve + { + public: + // Construction and destruction. The default constructor creates a + // polynomial curve with all components set to the constant zero (all + // degree-0 polynomials). You can set these to other polynomials + // using member accessors. + PolynomialCurve(Real tmin, Real tmax) + : + ParametricCurve(tmin, tmax) + { + } + + PolynomialCurve(Real tmin, Real tmax, + std::array, N> const& components) + : + ParametricCurve(tmin, tmax) + { + for (int i = 0; i < N; ++i) + { + SetPolynomial(i, components[i]); + } + } + + virtual ~PolynomialCurve() + { + } + + // Member access. + void SetPolynomial(int i, Polynomial1 const& poly) + { + mPolynomial[i] = poly; + mDer1Polynomial[i] = mPolynomial[i].GetDerivative(); + mDer2Polynomial[i] = mDer1Polynomial[i].GetDerivative(); + mDer3Polynomial[i] = mDer2Polynomial[i].GetDerivative(); + } + + inline Polynomial1 const& GetPolynomial(int i) const + { + return mPolynomial[i]; + } + + inline Polynomial1 const& GetDer1Polynomial(int i) const + { + return mDer1Polynomial[i]; + } + + inline Polynomial1 const& GetDer2Polynomial(int i) const + { + return mDer2Polynomial[i]; + } + + inline Polynomial1 const& GetDer3Polynomial(int i) const + { + return mDer3Polynomial[i]; + } + + // Evaluation of the curve. The function supports derivative + // calculation through order 3; that is, order <= 3 is required. If + // you want/ only the position, pass in order of 0. If you want the + // position and first derivative, pass in order of 1, and so on. The + // output array 'jet' must have enough storage to support the maximum + // order. The values are ordered as: position, first derivative, + // second derivative, third derivative. + virtual void Evaluate(Real t, unsigned int order, Vector* jet) const override + { + for (int i = 0; i < N; ++i) + { + jet[0][i] = mPolynomial[i](t); + } + + if (order >= 1) + { + for (int i = 0; i < N; ++i) + { + jet[1][i] = mDer1Polynomial[i](t); + } + + if (order >= 2) + { + for (int i = 0; i < N; ++i) + { + jet[2][i] = mDer2Polynomial[i](t); + } + + if (order == 3) + { + for (int i = 0; i < N; ++i) + { + jet[3][i] = mDer3Polynomial[i](t); + } + } + } + } + } + + protected: + std::array, N> mPolynomial; + std::array, N> mDer1Polynomial; + std::array, N> mDer2Polynomial; + std::array, N> mDer3Polynomial; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PrimalQuery2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PrimalQuery2.h new file mode 100644 index 0000000..a9f3c86 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PrimalQuery2.h @@ -0,0 +1,400 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.10.17 + +#pragma once + +#include + +// Queries about the relation of a point to various geometric objects. The +// choices for N when using UIntegerFP32 for either BSNumber of BSRational +// are determined in GeometricTools/GTEngine/Tools/PrecisionCalculator. These +// N-values are worst case scenarios. Your specific input data might require +// much smaller N, in which case you can modify PrecisionCalculator to use the +// BSPrecision(int32_t,int32_t,int32_t,bool) constructors. + +namespace WwiseGTE +{ + template + class PrimalQuery2 + { + public: + // The caller is responsible for ensuring that the array is not empty + // before calling queries and that the indices passed to the queries + // are valid. The class does no range checking. + PrimalQuery2() + : + mNumVertices(0), + mVertices(nullptr) + { + } + + PrimalQuery2(int numVertices, Vector2 const* vertices) + : + mNumVertices(numVertices), + mVertices(vertices) + { + } + + // Member access. + inline void Set(int numVertices, Vector2 const* vertices) + { + mNumVertices = numVertices; + mVertices = vertices; + } + + inline int GetNumVertices() const + { + return mNumVertices; + } + + inline Vector2 const* GetVertices() const + { + return mVertices; + } + + // In the following, point P refers to vertices[i] or 'test' and Vi + // refers to vertices[vi]. + + // For a line with origin V0 and direction , ToLine returns + // +1, P on right of line + // -1, P on left of line + // 0, P on the line + // + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+---- + // float | BSNumber | 18 + // double | BSNumber | 132 + // float | BSRational | 35 + // double | BSRational | 263 + int ToLine(int i, int v0, int v1) const + { + return ToLine(mVertices[i], v0, v1); + } + + int ToLine(Vector2 const& test, int v0, int v1) const + { + Vector2 const& vec0 = mVertices[v0]; + Vector2 const& vec1 = mVertices[v1]; + + Real x0 = test[0] - vec0[0]; + Real y0 = test[1] - vec0[1]; + Real x1 = vec1[0] - vec0[0]; + Real y1 = vec1[1] - vec0[1]; + Real x0y1 = x0 * y1; + Real x1y0 = x1 * y0; + Real det = x0y1 - x1y0; + Real const zero(0); + + return (det > zero ? +1 : (det < zero ? -1 : 0)); + } + + // For a line with origin V0 and direction , ToLine returns + // +1, P on right of line + // -1, P on left of line + // 0, P on the line + // The 'order' parameter is + // -3, points not collinear, P on left of line + // -2, P strictly left of V0 on the line + // -1, P = V0 + // 0, P interior to line segment [V0,V1] + // +1, P = V1 + // +2, P strictly right of V0 on the line + // + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+---- + // float | BSNumber | 18 + // double | BSNumber | 132 + // float | BSRational | 35 + // double | BSRational | 263 + // This is the same as the first-listed ToLine calls because the + // worst-case path has the same computational complexity. + int ToLine(int i, int v0, int v1, int& order) const + { + return ToLine(mVertices[i], v0, v1, order); + } + + int ToLine(Vector2 const& test, int v0, int v1, int& order) const + { + Vector2 const& vec0 = mVertices[v0]; + Vector2 const& vec1 = mVertices[v1]; + + Real x0 = test[0] - vec0[0]; + Real y0 = test[1] - vec0[1]; + Real x1 = vec1[0] - vec0[0]; + Real y1 = vec1[1] - vec0[1]; + Real x0y1 = x0 * y1; + Real x1y0 = x1 * y0; + Real det = x0y1 - x1y0; + Real const zero(0); + + if (det > zero) + { + order = +3; + return +1; + } + + if (det < zero) + { + order = -3; + return -1; + } + + Real x0x1 = x0 * x1; + Real y0y1 = y0 * y1; + Real dot = x0x1 + y0y1; + if (dot == zero) + { + order = -1; + } + else if (dot < zero) + { + order = -2; + } + else + { + Real x0x0 = x0 * x0; + Real y0y0 = y0 * y0; + Real sqrLength = x0x0 + y0y0; + if (dot == sqrLength) + { + order = +1; + } + else if (dot > sqrLength) + { + order = +2; + } + else + { + order = 0; + } + } + + return 0; + } + + // For a triangle with counterclockwise vertices V0, V1, and V2, + // ToTriangle returns + // +1, P outside triangle + // -1, P inside triangle + // 0, P on triangle + // + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+----- + // float | BSNumber | 18 + // double | BSNumber | 132 + // float | BSRational | 35 + // double | BSRational | 263 + // The query involves three calls to ToLine, so the numbers match + // those of ToLine. + int ToTriangle(int i, int v0, int v1, int v2) const + { + return ToTriangle(mVertices[i], v0, v1, v2); + } + + int ToTriangle(Vector2 const& test, int v0, int v1, int v2) const + { + int sign0 = ToLine(test, v1, v2); + if (sign0 > 0) + { + return +1; + } + + int sign1 = ToLine(test, v0, v2); + if (sign1 < 0) + { + return +1; + } + + int sign2 = ToLine(test, v0, v1); + if (sign2 > 0) + { + return +1; + } + + return ((sign0 && sign1 && sign2) ? -1 : 0); + } + + // For a triangle with counterclockwise vertices V0, V1, and V2, + // ToCircumcircle returns + // +1, P outside circumcircle of triangle + // -1, P inside circumcircle of triangle + // 0, P on circumcircle of triangle + // + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+---- + // float | BSNumber | 35 + // double | BSNumber | 263 + // float | BSRational | 105 + // double | BSRational | 788 + // The query involves three calls of ToLine, so the numbers match + // those of ToLine. + int ToCircumcircle(int i, int v0, int v1, int v2) const + { + return ToCircumcircle(mVertices[i], v0, v1, v2); + } + + int ToCircumcircle(Vector2 const& test, int v0, int v1, int v2) const + { + Vector2 const& vec0 = mVertices[v0]; + Vector2 const& vec1 = mVertices[v1]; + Vector2 const& vec2 = mVertices[v2]; + + Real x0 = vec0[0] - test[0]; + Real y0 = vec0[1] - test[1]; + Real s00 = vec0[0] + test[0]; + Real s01 = vec0[1] + test[1]; + Real t00 = s00 * x0; + Real t01 = s01 * y0; + Real z0 = t00 + t01; + + Real x1 = vec1[0] - test[0]; + Real y1 = vec1[1] - test[1]; + Real s10 = vec1[0] + test[0]; + Real s11 = vec1[1] + test[1]; + Real t10 = s10 * x1; + Real t11 = s11 * y1; + Real z1 = t10 + t11; + + Real x2 = vec2[0] - test[0]; + Real y2 = vec2[1] - test[1]; + Real s20 = vec2[0] + test[0]; + Real s21 = vec2[1] + test[1]; + Real t20 = s20 * x2; + Real t21 = s21 * y2; + Real z2 = t20 + t21; + + Real y0z1 = y0 * z1; + Real y0z2 = y0 * z2; + Real y1z0 = y1 * z0; + Real y1z2 = y1 * z2; + Real y2z0 = y2 * z0; + Real y2z1 = y2 * z1; + Real c0 = y1z2 - y2z1; + Real c1 = y2z0 - y0z2; + Real c2 = y0z1 - y1z0; + Real x0c0 = x0 * c0; + Real x1c1 = x1 * c1; + Real x2c2 = x2 * c2; + Real term = x0c0 + x1c1; + Real det = term + x2c2; + Real const zero(0); + + return (det < zero ? 1 : (det > zero ? -1 : 0)); + } + + // An extended classification of the relationship of a point to a line + // segment. For noncollinear points, the return value is + // ORDER_POSITIVE when is a counterclockwise triangle + // ORDER_NEGATIVE when is a clockwise triangle + // For collinear points, the line direction is Q1-Q0. The return + // value is + // ORDER_COLLINEAR_LEFT when the line ordering is + // ORDER_COLLINEAR_RIGHT when the line ordering is + // ORDER_COLLINEAR_CONTAIN when the line ordering is + enum OrderType + { + ORDER_Q0_EQUALS_Q1, + ORDER_P_EQUALS_Q0, + ORDER_P_EQUALS_Q1, + ORDER_POSITIVE, + ORDER_NEGATIVE, + ORDER_COLLINEAR_LEFT, + ORDER_COLLINEAR_RIGHT, + ORDER_COLLINEAR_CONTAIN + }; + + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+---- + // float | BSNumber | 18 + // double | BSNumber | 132 + // float | BSRational | 35 + // double | BSRational | 263 + // This is the same as the first-listed ToLine calls because the + // worst-case path has the same computational complexity. + OrderType ToLineExtended(Vector2 const& P, Vector2 const& Q0, Vector2 const& Q1) const + { + Real const zero(0); + + Real x0 = Q1[0] - Q0[0]; + Real y0 = Q1[1] - Q0[1]; + if (x0 == zero && y0 == zero) + { + return ORDER_Q0_EQUALS_Q1; + } + + Real x1 = P[0] - Q0[0]; + Real y1 = P[1] - Q0[1]; + if (x1 == zero && y1 == zero) + { + return ORDER_P_EQUALS_Q0; + } + + Real x2 = P[0] - Q1[0]; + Real y2 = P[1] - Q1[1]; + if (x2 == zero && y2 == zero) + { + return ORDER_P_EQUALS_Q1; + } + + // The theoretical classification relies on computing exactly the + // sign of the determinant. Numerical roundoff errors can cause + // misclassification. + Real x0y1 = x0 * y1; + Real x1y0 = x1 * y0; + Real det = x0y1 - x1y0; + + if (det != zero) + { + if (det > zero) + { + // The points form a counterclockwise triangle . + return ORDER_POSITIVE; + } + else + { + // The points form a clockwise triangle . + return ORDER_NEGATIVE; + } + } + else + { + // The points are collinear; P is on the line through + // Q0 and Q1. + Real x0x1 = x0 * x1; + Real y0y1 = y0 * y1; + Real dot = x0x1 + y0y1; + if (dot < zero) + { + // The line ordering is . + return ORDER_COLLINEAR_LEFT; + } + + Real x0x0 = x0 * x0; + Real y0y0 = y0 * y0; + Real sqrLength = x0x0 + y0y0; + if (dot > sqrLength) + { + // The line ordering is . + return ORDER_COLLINEAR_RIGHT; + } + + // The line ordering is with P strictly between + // Q0 and Q1. + return ORDER_COLLINEAR_CONTAIN; + } + } + + private: + int mNumVertices; + Vector2 const* mVertices; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PrimalQuery3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PrimalQuery3.h new file mode 100644 index 0000000..d4de9fe --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/PrimalQuery3.h @@ -0,0 +1,293 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.10.17 + +#pragma once + +#include + +// Queries about the relation of a point to various geometric objects. The +// choices for N when using UIntegerFP32 for either BSNumber of BSRational +// are determined in GeometricTools/GTEngine/Tools/PrecisionCalculator. These +// N-values are worst case scenarios. Your specific input data might require +// much smaller N, in which case you can modify PrecisionCalculator to use the +// BSPrecision(int32_t,int32_t,int32_t,bool) constructors. + +namespace WwiseGTE +{ + template + class PrimalQuery3 + { + public: + // The caller is responsible for ensuring that the array is not empty + // before calling queries and that the indices passed to the queries + // are valid. The class does no range checking. + PrimalQuery3() + : + mNumVertices(0), + mVertices(nullptr) + { + } + + PrimalQuery3(int numVertices, Vector3 const* vertices) + : + mNumVertices(numVertices), + mVertices(vertices) + { + } + + // Member access. + inline void Set(int numVertices, Vector3 const* vertices) + { + mNumVertices = numVertices; + mVertices = vertices; + } + + inline int GetNumVertices() const + { + return mNumVertices; + } + + inline Vector3 const* GetVertices() const + { + return mVertices; + } + + // In the following, point P refers to vertices[i] or 'test' and Vi + // refers to vertices[vi]. + + // For a plane with origin V0 and normal N = Cross(V1-V0,V2-V0), + // ToPlane returns + // +1, P on positive side of plane (side to which N points) + // -1, P on negative side of plane (side to which -N points) + // 0, P on the plane + // + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+---- + // float | BSNumber | 27 + // double | BSNumber | 197 + // float | BSRational | 79 + // double | BSRational | 591 + int ToPlane(int i, int v0, int v1, int v2) const + { + return ToPlane(mVertices[i], v0, v1, v2); + } + + int ToPlane(Vector3 const& test, int v0, int v1, int v2) const + { + Vector3 const& vec0 = mVertices[v0]; + Vector3 const& vec1 = mVertices[v1]; + Vector3 const& vec2 = mVertices[v2]; + + Real x0 = test[0] - vec0[0]; + Real y0 = test[1] - vec0[1]; + Real z0 = test[2] - vec0[2]; + Real x1 = vec1[0] - vec0[0]; + Real y1 = vec1[1] - vec0[1]; + Real z1 = vec1[2] - vec0[2]; + Real x2 = vec2[0] - vec0[0]; + Real y2 = vec2[1] - vec0[1]; + Real z2 = vec2[2] - vec0[2]; + Real y1z2 = y1 * z2; + Real y2z1 = y2 * z1; + Real y2z0 = y2 * z0; + Real y0z2 = y0 * z2; + Real y0z1 = y0 * z1; + Real y1z0 = y1 * z0; + Real c0 = y1z2 - y2z1; + Real c1 = y2z0 - y0z2; + Real c2 = y0z1 - y1z0; + Real x0c0 = x0 * c0; + Real x1c1 = x1 * c1; + Real x2c2 = x2 * c2; + Real term = x0c0 + x1c1; + Real det = term + x2c2; + Real const zero(0); + + return (det > zero ? +1 : (det < zero ? -1 : 0)); + } + + // For a tetrahedron with vertices ordered as described in the file + // TetrahedronKey.h, the function returns + // +1, P outside tetrahedron + // -1, P inside tetrahedron + // 0, P on tetrahedron + // + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+---- + // float | BSNumber | 27 + // double | BSNumber | 197 + // float | BSRational | 79 + // double | BSRational | 591 + // The query involves four calls of ToPlane, so the numbers match + // those of ToPlane. + int ToTetrahedron(int i, int v0, int v1, int v2, int v3) const + { + return ToTetrahedron(mVertices[i], v0, v1, v2, v3); + } + + int ToTetrahedron(Vector3 const& test, int v0, int v1, int v2, int v3) const + { + int sign0 = ToPlane(test, v1, v2, v3); + if (sign0 > 0) + { + return +1; + } + + int sign1 = ToPlane(test, v0, v2, v3); + if (sign1 < 0) + { + return +1; + } + + int sign2 = ToPlane(test, v0, v1, v3); + if (sign2 > 0) + { + return +1; + } + + int sign3 = ToPlane(test, v0, v1, v2); + if (sign3 < 0) + { + return +1; + } + + return ((sign0 && sign1 && sign2 && sign3) ? -1 : 0); + } + + // For a tetrahedron with vertices ordered as described in the file + // TetrahedronKey.h, the function returns + // +1, P outside circumsphere of tetrahedron + // -1, P inside circumsphere of tetrahedron + // 0, P on circumsphere of tetrahedron + // + // Choice of N for UIntegerFP32. + // input type | compute type | N + // -----------+--------------+----- + // float | BSNumber | 44 + // double | BSNumber | 329 + // float | BSNumber | 262 + // double | BSRational | 1969 + int ToCircumsphere(int i, int v0, int v1, int v2, int v3) const + { + return ToCircumsphere(mVertices[i], v0, v1, v2, v3); + } + + int ToCircumsphere(Vector3 const& test, int v0, int v1, int v2, int v3) const + { + Vector3 const& vec0 = mVertices[v0]; + Vector3 const& vec1 = mVertices[v1]; + Vector3 const& vec2 = mVertices[v2]; + Vector3 const& vec3 = mVertices[v3]; + + Real x0 = vec0[0] - test[0]; + Real y0 = vec0[1] - test[1]; + Real z0 = vec0[2] - test[2]; + Real s00 = vec0[0] + test[0]; + Real s01 = vec0[1] + test[1]; + Real s02 = vec0[2] + test[2]; + Real t00 = s00 * x0; + Real t01 = s01 * y0; + Real t02 = s02 * z0; + Real t00pt01 = t00 + t01; + Real w0 = t00pt01 + t02; + + Real x1 = vec1[0] - test[0]; + Real y1 = vec1[1] - test[1]; + Real z1 = vec1[2] - test[2]; + Real s10 = vec1[0] + test[0]; + Real s11 = vec1[1] + test[1]; + Real s12 = vec1[2] + test[2]; + Real t10 = s10 * x1; + Real t11 = s11 * y1; + Real t12 = s12 * z1; + Real t10pt11 = t10 + t11; + Real w1 = t10pt11 + t12; + + Real x2 = vec2[0] - test[0]; + Real y2 = vec2[1] - test[1]; + Real z2 = vec2[2] - test[2]; + Real s20 = vec2[0] + test[0]; + Real s21 = vec2[1] + test[1]; + Real s22 = vec2[2] + test[2]; + Real t20 = s20 * x2; + Real t21 = s21 * y2; + Real t22 = s22 * z2; + Real t20pt21 = t20 + t21; + Real w2 = t20pt21 + t22; + + Real x3 = vec3[0] - test[0]; + Real y3 = vec3[1] - test[1]; + Real z3 = vec3[2] - test[2]; + Real s30 = vec3[0] + test[0]; + Real s31 = vec3[1] + test[1]; + Real s32 = vec3[2] + test[2]; + Real t30 = s30 * x3; + Real t31 = s31 * y3; + Real t32 = s32 * z3; + Real t30pt31 = t30 + t31; + Real w3 = t30pt31 + t32; + + Real x0y1 = x0 * y1; + Real x0y2 = x0 * y2; + Real x0y3 = x0 * y3; + Real x1y0 = x1 * y0; + Real x1y2 = x1 * y2; + Real x1y3 = x1 * y3; + Real x2y0 = x2 * y0; + Real x2y1 = x2 * y1; + Real x2y3 = x2 * y3; + Real x3y0 = x3 * y0; + Real x3y1 = x3 * y1; + Real x3y2 = x3 * y2; + Real a0 = x0y1 - x1y0; + Real a1 = x0y2 - x2y0; + Real a2 = x0y3 - x3y0; + Real a3 = x1y2 - x2y1; + Real a4 = x1y3 - x3y1; + Real a5 = x2y3 - x3y2; + + Real z0w1 = z0 * w1; + Real z0w2 = z0 * w2; + Real z0w3 = z0 * w3; + Real z1w0 = z1 * w0; + Real z1w2 = z1 * w2; + Real z1w3 = z1 * w3; + Real z2w0 = z2 * w0; + Real z2w1 = z2 * w1; + Real z2w3 = z2 * w3; + Real z3w0 = z3 * w0; + Real z3w1 = z3 * w1; + Real z3w2 = z3 * w2; + Real b0 = z0w1 - z1w0; + Real b1 = z0w2 - z2w0; + Real b2 = z0w3 - z3w0; + Real b3 = z1w2 - z2w1; + Real b4 = z1w3 - z3w1; + Real b5 = z2w3 - z3w2; + Real a0b5 = a0 * b5; + Real a1b4 = a1 * b4; + Real a2b3 = a2 * b3; + Real a3b2 = a3 * b2; + Real a4b1 = a4 * b1; + Real a5b0 = a5 * b0; + Real term0 = a0b5 - a1b4; + Real term1 = term0 + a2b3; + Real term2 = term1 + a3b2; + Real term3 = term2 - a4b1; + Real det = term3 + a5b0; + Real const zero(0); + + return (det > zero ? 1 : (det < zero ? -1 : 0)); + } + + private: + int mNumVertices; + Vector3 const* mVertices; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Projection.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Projection.h new file mode 100644 index 0000000..44a0a70 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Projection.h @@ -0,0 +1,61 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + // Project an ellipse onto a line. The projection interval is + // [smin,smax] and corresponds to the line segment P+s*D, where + // smin <= s <= smax. + template + void Project(Ellipse2 const& ellipse, Line2 const& line, + Real& smin, Real& smax) + { + // Center of projection interval. + Real center = Dot(line.direction, ellipse.center - line.origin); + + // Radius of projection interval. + Real tmp[2] = + { + ellipse.extent[0] * Dot(line.direction, ellipse.axis[0]), + ellipse.extent[1] * Dot(line.direction, ellipse.axis[1]) + }; + Real rSqr = tmp[0] * tmp[0] + tmp[1] * tmp[1]; + Real radius = std::sqrt(rSqr); + + smin = center - radius; + smax = center + radius; + } + + // Project an ellipsoid onto a line. The projection interval is + // [smin,smax] and corresponds to the line segment P+s*D, where + // smin <= s <= smax. + template + void Project(Ellipsoid3 const& ellipsoid, + Line3 const& line, Real& smin, Real& smax) + { + // Center of projection interval. + Real center = Dot(line.direction, ellipsoid.center - line.origin); + + // Radius of projection interval. + Real tmp[3] = + { + ellipsoid.extent[0] * Dot(line.direction, ellipsoid.axis[0]), + ellipsoid.extent[1] * Dot(line.direction, ellipsoid.axis[1]), + ellipsoid.extent[2] * Dot(line.direction, ellipsoid.axis[2]) + }; + Real rSqr = tmp[0] * tmp[0] + tmp[1] * tmp[1] + tmp[2] * tmp[2]; + Real radius = std::sqrt(rSqr); + + smin = center - radius; + smax = center + radius; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/QFNumber.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/QFNumber.h new file mode 100644 index 0000000..3a7a342 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/QFNumber.h @@ -0,0 +1,408 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2020.01.10 + +#pragma once + +#include + +// Class QFNumber is an implementation for quadratic fields with N >= 1 +// square root terms. The theory and details are provided in +// https://www.geometrictools.com/Documentation/QuadraticFields.pdf + +// Enable this macro if you want the logging system to trap when arithmetic +// operations are performed on two quadratic elements that do not share the +// same value d +#define GTE_ASSERT_ON_QFNUMBER_MISMATCHED_D + +namespace WwiseGTE +{ + // Arithmetic for quadratic fields with N >= 2 square root terms. The + // d-term is rational and the x-coefficients are elements in a quadratic + // field with N-1 >= 1 square root terms. + template + class QFNumber + { + public: + // The quadratic field numbers is x[0] + x[1] * sqrt(d). + std::array, 2> x; + T d; + + // Create z = 0 + 0 * sqrt(0), where the 0 coefficients are quadratic + // field elements with N-1 d-terms all set to 0 and x-coefficients all + // set to 0. + QFNumber() + : + d(0) + { + static_assert(N >= 2, "Invalid number of root arguments."); + } + + // Create z = 0 + 0 * sqrt(d), where the 0 coefficients are quadratic + // field elements with N-1 d-terms all set to 0 and x-coefficients all + // set to 0. + explicit QFNumber(T const& inD) + : + d(inD) + { + static_assert(N >= 2, "Invalid number of root arguments."); + } + + // Create z = x0 + x1 * sqrt(d), where the x-coefficients are + // quadratic field elements with N-1 d-terms. + QFNumber(QFNumber const& x0, QFNumber const& x1, T const& inD) + : + x{ x0, x1 }, + d(inD) + { + static_assert(N >= 2, "Invalid number of root arguments."); + } + + // Create z = inX[0] + inX[1] * sqrt(inD), where the x-coefficients are + // quadratic field elements with N-1 d-terms. + QFNumber(std::array, 2> const& inX, T const& inD) + : + x(inX), + d(inD) + { + static_assert(N >= 2, "Invalid number of root arguments."); + } + }; + + // Arithmetic for quadratic fields with 1 square root term. + template + class QFNumber + { + public: + // The quadratic field number is x[0] + x[1] * sqrt(d). + std::array x; + T d; + + // Create z = 0. You can defer the setting of d until later. + QFNumber() + : + x{ static_cast(0), static_cast(0) }, + d(static_cast(0)) + { + } + + // Create z = 0 + 0 * sqrt(d) = 0. + explicit QFNumber(T const& inD) + : + x{ static_cast(0), static_cast(0) }, + d(inD) + { + } + + // Create z = x0 + x1 * sqrt(d). + QFNumber(T const& x0, T const& x1, T const& inD) + : + x{ x0, x1 }, + d(inD) + { + } + + // Create z = inX[0] + inX[1] * sqrt(d). + QFNumber(std::array const& inX, T const& inD) + : + x(inX), + d(inD) + { + } + }; + + // Unary operations. + template + QFNumber operator+(QFNumber const& q) + { + static_assert(N >= 1, "Invalid number of d-terms."); + return q; + } + + template + QFNumber operator-(QFNumber const& q) + { + static_assert(N >= 1, "Invalid number of d-terms."); + return QFNumber(-q.x[0], -q.x[1], q.d); + } + + // Arithmetic operations between elements of a quadratic field must occur + // only when the d-values are the same. To trap mismatches, read the + // comments at the beginning of this file. + template + QFNumber operator+(QFNumber const& q0, QFNumber const& q1) + { +#if defined(GTE_ASSERT_ON_QFNUMBER_MISMATCHED_D) + LogAssert(q0.d == q1.d, "Mismatched d-value."); +#endif + return QFNumber(q0.x[0] + q1.x[0], q0.x[1] + q1.x[1], q0.d); + } + + template + QFNumber operator+(QFNumber const& q, T const& s) + { + return QFNumber(q.x[0] + s, q.x[1], q.d); + } + + template + QFNumber operator+(T const& s, QFNumber const& q) + { + return QFNumber(s + q.x[0], q.x[1], q.d); + } + + template + QFNumber operator-(QFNumber const& q0, QFNumber const& q1) + { +#if defined(GTE_ASSERT_ON_QFNUMBER_MISMATCHED_D) + LogAssert(q0.d == q1.d, "Mismatched d-value."); +#endif + return QFNumber(q0.x[0] - q1.x[0], q0.x[1] - q1.x[1], q0.d); + } + + template + QFNumber operator-(QFNumber const& q, T const& s) + { + return QFNumber(q.x[0] - s, q.x[1], q.d); + } + + template + QFNumber operator-(T const& s, QFNumber const& q) + { + return QFNumber(s - q.x[0], -q.x[1], q.d); + } + + template + QFNumber operator*(QFNumber const& q0, QFNumber const& q1) + { +#if defined(GTE_ASSERT_ON_QFNUMBER_MISMATCHED_D) + LogAssert(q0.d == q1.d, "Mismatched d-value."); +#endif + return QFNumber( + q0.x[0] * q1.x[0] + q0.x[1] * q1.x[1] * q0.d, + q0.x[0] * q1.x[1] + q0.x[1] * q1.x[0], + q0.d); + } + + template + QFNumber operator*(QFNumber const& q, T const& s) + { + return QFNumber(q.x[0] * s, q.x[1] * s, q.d); + } + + template + QFNumber operator*(T const& s, QFNumber const& q) + { + return QFNumber(s * q.x[0], s * q.x[1], q.d); + } + + template + QFNumber operator/(QFNumber const& q0, QFNumber const& q1) + { +#if defined(GTE_ASSERT_ON_QFNUMBER_MISMATCHED_D) + LogAssert(q0.d == q1.d, "Mismatched d-value."); +#endif + auto denom = q1.x[0] * q1.x[0] - q1.x[1] * q1.x[1] * q0.d; + auto numer0 = q0.x[0] * q1.x[0] - q0.x[1] * q1.x[1] * q0.d; + auto numer1 = q0.x[1] * q1.x[0] - q0.x[0] * q1.x[1]; + return QFNumber(numer0 / denom, numer1 / denom, q0.d); + } + + template + QFNumber operator/(QFNumber const& q, T const& s) + { + return QFNumber(q.x[0] / s, q.x[1] / s, q.d); + } + + template + QFNumber operator/(T const& s, QFNumber const& q) + { + auto denom = q.x[0] * q.x[0] - q.x[1] * q.x[1] * q.d; + auto x0 = (s * q.x[0]) / denom; + auto x1 = -(s * q.x[1]) / denom; + return QFNumber(x0, x1, q.d); + } + + // Arithmetic updates between elements of a quadratic field must occur + // only when the d-values are the same. To trap mismatches, read the + // comments at the beginning of this file. + template + QFNumber& operator+=(QFNumber& q0, QFNumber const& q1) + { +#if defined(GTE_ASSERT_ON_QFNUMBER_MISMATCHED_D) + LogAssert(q0.d == q1.d, "Mismatched d-value."); +#endif + q0.x[0] += q1.x[0]; + q0.x[1] += q1.x[1]; + return q0; + } + + template + QFNumber& operator+=(QFNumber& q, T const& s) + { + q.x[0] += s; + return q; + } + + template + QFNumber& operator-=(QFNumber& q0, QFNumber const& q1) + { +#if defined(GTE_ASSERT_ON_QFNUMBER_MISMATCHED_D) + LogAssert(q0.d == q1.d, "Mismatched d-value."); +#endif + q0.x[0] -= q1.x[0]; + q0.x[1] -= q1.x[1]; + return q0; + } + + template + QFNumber& operator-=(QFNumber& q, T const& s) + { + q.x[0] -= s; + return q; + } + + template + QFNumber& operator*=(QFNumber& q0, QFNumber const& q1) + { +#if defined(GTE_ASSERT_ON_QFNUMBER_MISMATCHED_D) + LogAssert(q0.d == q1.d, "Mismatched d-value."); +#endif + auto x0 = q0.x[0] * q1.x[0] + q0.x[1] * q1.x[1] * q0.d; + auto x1 = q0.x[0] * q1.x[1] + q0.x[1] * q1.x[0]; + q0.x[0] = x0; + q0.x[1] = x1; + return q0; + } + + template + QFNumber& operator*=(QFNumber& q, T const& s) + { + q.x[0] *= s; + q.x[1] *= s; + return q; + } + + template + QFNumber& operator/=(QFNumber& q0, QFNumber const& q1) + { +#if defined(GTE_ASSERT_ON_QFNUMBER_MISMATCHED_D) + LogAssert(q0.d == q1.d, "Mismatched d-value."); +#endif + auto denom = q1.x[0] * q1.x[0] - q1.x[1] * q1.x[1] * q0.d; + auto numer0 = q0.x[0] * q1.x[0] - q0.x[1] * q1.x[1] * q0.d; + auto numer1 = q0.x[1] * q1.x[0] - q0.x[0] * q1.x[1]; + q0.x[0] = numer0 / denom; + q0.x[1] = numer1 / denom; + return q0; + } + + template + QFNumber& operator/=(QFNumber& q, T const& s) + { + q.x[0] /= s; + q.x[1] /= s; + return q; + } + + // Comparisons between numbers of a quadratic field must occur only when + // the d-values are the same. To trap mismatches, read the comments at + // the beginning of this file. + template + bool operator==(QFNumber const& q0, QFNumber const& q1) + { +#if defined(GTE_ASSERT_ON_QFNUMBER_MISMATCHED_D) + LogAssert(q0.d == q1.d, "Mismatched d-value."); +#endif + if (q0.d == T(0) || q0.x[1] == q1.x[1]) + { + return q0.x[0] == q1.x[0]; + } + else if (q0.x[1] > q1.x[1]) + { + if (q0.x[0] >= q1.x[0]) + { + return false; + } + else // q0.x[0] < q1.x[0] + { + auto diff = q0 - q1; + return diff.x[0] * diff.x[0] == diff.x[1] * diff.x[1] * diff.d; + } + } + else // q0.x[1] < q1.x[1] + { + if (q0.x[0] <= q1.x[0]) + { + return false; + } + else // q0.x[0] > q1.x[0] + { + auto diff = q0 - q1; + return diff.x[0] * diff.x[0] == diff.x[1] * diff.x[1] * diff.d; + } + } + } + + template + bool operator!=(QFNumber const& q0, QFNumber const& q1) + { + return !operator==(q0, q1); + } + + template + bool operator<(QFNumber const& q0, QFNumber const& q1) + { +#if defined(GTE_ASSERT_ON_QFNUMBER_MISMATCHED_D) + LogAssert(q0.d == q1.d, "Mismatched d-value."); +#endif + if (q0.d == T(0) || q0.x[1] == q1.x[1]) + { + return q0.x[0] < q1.x[0]; + } + else if (q0.x[1] > q1.x[1]) + { + if (q0.x[0] >= q1.x[0]) + { + return false; + } + else // q0.x[0] < q1.x[0] + { + auto diff = q0 - q1; + return diff.x[0] * diff.x[0] > diff.x[1] * diff.x[1] * diff.d; + } + } + else // q0.x[1] < q1.x[1] + { + if (q0.x[0] <= q1.x[0]) + { + return true; + } + else // q0.x[0] > q1.x[0] + { + auto diff = q0 - q1; + return diff.x[0] * diff.x[0] < diff.x[1] * diff.x[1] * diff.d; + } + } + } + + template + bool operator>(QFNumber const& q0, QFNumber const& q1) + { + return operator<(q1, q0); + } + + template + bool operator<=(QFNumber const& q0, QFNumber const& q1) + { + return !operator<(q1, q0); + } + + template + bool operator>=(QFNumber const& q0, QFNumber const& q1) + { + return !operator<(q0, q1); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/QuadricSurface.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/QuadricSurface.h new file mode 100644 index 0000000..ba5feab --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/QuadricSurface.h @@ -0,0 +1,700 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// A quadric surface is defined implicitly by +// +// 0 = a0 + a1*x[0] + a2*x[1] + a3*x[2] + a4*x[0]^2 + a5*x[0]*x[1] + +// a6*x[0]*x[2] + a7*x[1]^2 + a8*x[1]*x[2] + a9*x[2]^2 +// +// = a0 + [a1 a2 a3]*X + X^T*[a4 a5/2 a6/2]*X +// [a5/2 a7 a8/2] +// [a6/2 a8/2 a9 ] +// = C + B^T*X + X^T*A*X +// +// The matrix A is symmetric. + +namespace WwiseGTE +{ + template + class QuadricSurface + { + public: + // Construction and destruction. The default constructor sets all + // coefficients to zero. + QuadricSurface() + { + mCoefficient.fill((Real)0); + mC = (Real)0; + mB.MakeZero(); + mA.MakeZero(); + } + + QuadricSurface(std::array const& coefficient) + : + mCoefficient(coefficient) + { + mC = mCoefficient[0]; + mB[0] = mCoefficient[1]; + mB[1] = mCoefficient[2]; + mB[2] = mCoefficient[3]; + mA(0, 0) = mCoefficient[4]; + mA(0, 1) = (Real)0.5 * mCoefficient[5]; + mA(0, 2) = (Real)0.5 * mCoefficient[6]; + mA(1, 0) = mA(0, 1); + mA(1, 1) = mCoefficient[7]; + mA(1, 2) = (Real)0.5 * mCoefficient[8]; + mA(2, 0) = mA(0, 2); + mA(2, 1) = mA(1, 2); + mA(2, 2) = mCoefficient[9]; + } + + // Member access. + inline std::array const& GetCoefficients() const + { + return mCoefficient; + } + + inline Real const& GetC() const + { + return mC; + } + + inline Vector3 const& GetB() const + { + return mB; + } + + inline Matrix3x3 const& GetA() const + { + return mA; + } + + // Evaluate the function. + Real F(Vector3 const& position) const + { + Real f = Dot(position, mA * position + mB) + mC; + return f; + } + + // Evaluate the first-order partial derivatives (gradient). + Real FX(Vector3 const& position) const + { + Real sum = mA(0, 0) * position[0] + mA(0, 1) * position[1] + mA(0, 2) * position[2]; + Real fx = (Real)2 * sum + mB[0]; + return fx; + } + + Real FY(Vector3 const& position) const + { + Real sum = mA(1, 0) * position[0] + mA(1, 1) * position[1] + mA(1, 2) * position[2]; + Real fy = (Real)2 * sum + mB[1]; + return fy; + } + + Real FZ(Vector3 const& position) const + { + Real sum = mA(2, 0) * position[0] + mA(2, 1) * position[1] + mA(2, 2) * position[2]; + Real fz = (Real)2 * sum + mB[2]; + return fz; + } + + // Evaluate the second-order partial derivatives (Hessian). + Real FXX(Vector3 const&) const + { + Real fxx = (Real)2 * mA(0, 0); + return fxx; + } + + Real FXY(Vector3 const&) const + { + Real fxy = (Real)2 * mA(0, 1); + return fxy; + } + + Real FXZ(Vector3 const&) const + { + Real fxz = (Real)2 * mA(0, 2); + return fxz; + } + + Real FYY(Vector3 const&) const + { + Real fyy = (Real)2 * mA(1, 1); + return fyy; + } + + Real FYZ(Vector3 const&) const + { + Real fyz = (Real)2 * mA(1, 2); + return fyz; + } + + Real FZZ(Vector3 const&) const + { + Real fzz = (Real)2 * mA(2, 2); + return fzz; + } + + // Classification of the quadric. The implementation uses exact + // rational arithmetic to avoid misclassification due to + // floating-point rounding errors. + enum Classification + { + QT_NONE, + QT_POINT, + QT_LINE, + QT_PLANE, + QT_TWO_PLANES, + QT_PARABOLIC_CYLINDER, + QT_ELLIPTIC_CYLINDER, + QT_HYPERBOLIC_CYLINDER, + QT_ELLIPTIC_PARABOLOID, + QT_HYPERBOLIC_PARABOLOID, + QT_ELLIPTIC_CONE, + QT_HYPERBOLOID_ONE_SHEET, + QT_HYPERBOLOID_TWO_SHEETS, + QT_ELLIPSOID + }; + + Classification GetClassification() const + { + // Convert the coefficients to their rational representations and + // compute various derived quantities. + RReps reps(mCoefficient); + + // Use Sturm sequences to determine the signs of the roots. + int positiveRoots, negativeRoots, zeroRoots; + GetRootSigns(reps, positiveRoots, negativeRoots, zeroRoots); + + // Classify the solution set to the equation. + Classification type = QT_NONE; + switch (zeroRoots) + { + case 0: + type = ClassifyZeroRoots0(reps, positiveRoots); + break; + case 1: + type = ClassifyZeroRoots1(reps, positiveRoots); + break; + case 2: + type = ClassifyZeroRoots2(reps, positiveRoots); + break; + case 3: + type = ClassifyZeroRoots3(reps); + break; + } + return type; + } + + private: + typedef BSRational Rational; + typedef Vector<3, Rational> RVector3; + + class RReps + { + public: + RReps(std::array const& coefficient) + { + Rational half = (Real)0.5; + + c = Rational(coefficient[0]); + b0 = Rational(coefficient[1]); + b1 = Rational(coefficient[2]); + b2 = Rational(coefficient[3]); + a00 = Rational(coefficient[4]); + a01 = half * Rational(coefficient[5]); + a02 = half * Rational(coefficient[6]); + a11 = Rational(coefficient[7]); + a12 = half * Rational(coefficient[8]); + a22 = Rational(coefficient[9]); + + sub00 = a11 * a22 - a12 * a12; + sub01 = a01 * a22 - a12 * a02; + sub02 = a01 * a12 - a02 * a11; + sub11 = a00 * a22 - a02 * a02; + sub12 = a00 * a12 - a02 * a01; + sub22 = a00 * a11 - a01 * a01; + + k0 = a00 * sub00 - a01 * sub01 + a02 * sub02; + k1 = sub00 + sub11 + sub22; + k2 = a00 + a11 + a22; + k3 = Rational(0); + k4 = Rational(0); + k5 = Rational(0); + } + + // Quadratic coefficients. + Rational a00, a01, a02, a11, a12, a22, b0, b1, b2, c; + + // 2-by-2 determinants + Rational sub00, sub01, sub02, sub11, sub12, sub22; + + // Characteristic polynomial L^3 - k2 * L^2 + k1 * L - k0. + Rational k0, k1, k2; + + // For Sturm sequences. + Rational k3, k4, k5; + }; + + static void GetRootSigns(RReps& reps, int& positiveRoots, int& negativeRoots, int& zeroRoots) + { + // Use Sturm sequences to determine the signs of the roots. + int signChangeMI, signChange0, signChangePI, distinctNonzeroRoots; + std::array value; + Rational const zero(0); + if (reps.k0 != zero) + { + reps.k3 = Rational(2, 9) * reps.k2 * reps.k2 - Rational(2, 3) * reps.k1; + reps.k4 = reps.k0 - Rational(1, 9) * reps.k1 * reps.k2; + + if (reps.k3 != zero) + { + reps.k5 = -(reps.k1 + ((Rational(2) * reps.k2 * reps.k3 + + Rational(3) * reps.k4) * reps.k4) / (reps.k3 * reps.k3)); + + value[0] = 1; + value[1] = -reps.k3; + value[2] = reps.k5; + signChangeMI = 1 + GetSignChanges(3, value); + + value[0] = -reps.k0; + value[1] = reps.k1; + value[2] = reps.k4; + value[3] = reps.k5; + signChange0 = GetSignChanges(4, value); + + value[0] = 1; + value[1] = reps.k3; + value[2] = reps.k5; + signChangePI = GetSignChanges(3, value); + } + else + { + value[0] = -reps.k0; + value[1] = reps.k1; + value[2] = reps.k4; + signChange0 = GetSignChanges(3, value); + + value[0] = 1; + value[1] = reps.k4; + signChangePI = GetSignChanges(2, value); + signChangeMI = 1 + signChangePI; + } + + positiveRoots = signChange0 - signChangePI; + LogAssert(positiveRoots >= 0, "Unexpected condition."); + negativeRoots = signChangeMI - signChange0; + LogAssert(negativeRoots >= 0, "Unexpected condition."); + zeroRoots = 0; + + distinctNonzeroRoots = positiveRoots + negativeRoots; + if (distinctNonzeroRoots == 2) + { + if (positiveRoots == 2) + { + positiveRoots = 3; + } + else if (negativeRoots == 2) + { + negativeRoots = 3; + } + else + { + // One root is positive and one is negative. One root + // has multiplicity 2, the other of multiplicity 1. + // Distinguish between the two cases by computing the + // sign of the polynomial at the inflection point + // L = k2/3. + Rational X = Rational(1, 3) * reps.k2; + Rational poly = X * (X * (X - reps.k2) + reps.k1) - reps.k0; + if (poly > zero) + { + positiveRoots = 2; + } + else + { + negativeRoots = 2; + } + } + } + else if (distinctNonzeroRoots == 1) + { + // Root of multiplicity 3. + if (positiveRoots == 1) + { + positiveRoots = 3; + } + else + { + negativeRoots = 3; + } + } + + return; + } + + if (reps.k1 != zero) + { + reps.k3 = Rational(1, 4) * reps.k2 * reps.k2 - reps.k1; + + value[0] = -1; + value[1] = reps.k3; + signChangeMI = 1 + GetSignChanges(2, value); + + value[0] = reps.k1; + value[1] = -reps.k2; + value[2] = reps.k3; + signChange0 = GetSignChanges(3, value); + + value[0] = 1; + value[1] = reps.k3; + signChangePI = GetSignChanges(2, value); + + positiveRoots = signChange0 - signChangePI; + LogAssert(positiveRoots >= 0, "Unexpected condition."); + negativeRoots = signChangeMI - signChange0; + LogAssert(negativeRoots >= 0, "Unexpected condition."); + zeroRoots = 1; + + distinctNonzeroRoots = positiveRoots + negativeRoots; + if (distinctNonzeroRoots == 1) + { + positiveRoots = 2; + } + + return; + } + + if (reps.k2 != zero) + { + zeroRoots = 2; + if (reps.k2 > zero) + { + positiveRoots = 1; + negativeRoots = 0; + } + else + { + positiveRoots = 0; + negativeRoots = 1; + } + return; + } + + positiveRoots = 0; + negativeRoots = 0; + zeroRoots = 3; + } + + static int GetSignChanges(int quantity, std::array const& value) + { + int signChanges = 0; + Rational const zero(0); + + Rational prev = value[0]; + for (int i = 1; i < quantity; ++i) + { + Rational next = value[i]; + if (next != zero) + { + if (prev * next < zero) + { + ++signChanges; + } + + prev = next; + } + } + + return signChanges; + } + + static Classification ClassifyZeroRoots0(RReps const& reps, int positiveRoots) + { + // The inverse matrix is + // +- -+ + // | sub00 -sub01 sub02 | + // | -sub01 sub11 -sub12 | * (1/det) + // | sub02 -sub12 sub22 | + // +- -+ + Rational fourDet = Rational(4) * reps.k0; + + Rational qForm = reps.b0 * (reps.sub00 * reps.b0 - + reps.sub01 * reps.b1 + reps.sub02 * reps.b2) - + reps.b1 * (reps.sub01 * reps.b0 - reps.sub11 * reps.b1 + + reps.sub12 * reps.b2) + reps.b2 * (reps.sub02 * reps.b0 - + reps.sub12 * reps.b1 + reps.sub22 * reps.b2); + + Rational r = Rational(1, 4) * qForm / fourDet - reps.c; + Rational const zero(0); + if (r > zero) + { + if (positiveRoots == 3) + { + return QT_ELLIPSOID; + } + else if (positiveRoots == 2) + { + return QT_HYPERBOLOID_ONE_SHEET; + } + else if (positiveRoots == 1) + { + return QT_HYPERBOLOID_TWO_SHEETS; + } + else + { + return QT_NONE; + } + } + else if (r < zero) + { + if (positiveRoots == 3) + { + return QT_NONE; + } + else if (positiveRoots == 2) + { + return QT_HYPERBOLOID_TWO_SHEETS; + } + else if (positiveRoots == 1) + { + return QT_HYPERBOLOID_ONE_SHEET; + } + else + { + return QT_ELLIPSOID; + } + } + + // else r == 0 + if (positiveRoots == 3 || positiveRoots == 0) + { + return QT_POINT; + } + + return QT_ELLIPTIC_CONE; + } + + static Classification ClassifyZeroRoots1(RReps const& reps, int positiveRoots) + { + // Generate an orthonormal set {p0,p1,p2}, where p0 is an + // eigenvector of A corresponding to eigenvalue zero. + RVector3 P0, P1, P2; + Rational const zero(0); + + if (reps.sub00 != zero || reps.sub01 != zero || reps.sub02 != zero) + { + // Rows 1 and 2 are linearly independent. + P0 = { reps.sub00, -reps.sub01, reps.sub02 }; + P1 = { reps.a01, reps.a11, reps.a12 }; + P2 = Cross(P0, P1); + return ClassifyZeroRoots1(reps, positiveRoots, P0, P1, P2); + } + + if (reps.sub01 != zero || reps.sub11 != zero || reps.sub12 != zero) + { + // Rows 2 and 0 are linearly independent. + P0 = { -reps.sub01, reps.sub11, -reps.sub12 }; + P1 = { reps.a02, reps.a12, reps.a22 }; + P2 = Cross(P0, P1); + return ClassifyZeroRoots1(reps, positiveRoots, P0, P1, P2); + } + + // Rows 0 and 1 are linearly independent. + P0 = { reps.sub02, -reps.sub12, reps.sub22 }; + P1 = { reps.a00, reps.a01, reps.a02 }; + P2 = Cross(P0, P1); + return ClassifyZeroRoots1(reps, positiveRoots, P0, P1, P2); + } + + static Classification ClassifyZeroRoots1(RReps const& reps, int positiveRoots, + RVector3 const& P0, RVector3 const& P1, RVector3 const& P2) + { + Rational const zero(0); + Rational e0 = P0[0] * reps.b0 + P0[1] * reps.b1 + P0[2] * reps.b2; + + if (e0 != zero) + { + if (positiveRoots == 1) + { + return QT_HYPERBOLIC_PARABOLOID; + } + else + { + return QT_ELLIPTIC_PARABOLOID; + } + } + + // Matrix F. + Rational f11 = P1[0] * (reps.a00 * P1[0] + reps.a01 * P1[1] + + reps.a02 * P1[2]) + P1[1] * (reps.a01 * P1[0] + + reps.a11 * P1[1] + reps.a12 * P1[2]) + P1[2] * ( + reps.a02 * P1[0] + reps.a12 * P1[1] + reps.a22 * P1[2]); + + Rational f12 = P2[0] * (reps.a00 * P1[0] + reps.a01 * P1[1] + + reps.a02 * P1[2]) + P2[1] * (reps.a01 * P1[0] + + reps.a11 * P1[1] + reps.a12 * P1[2]) + P2[2] * ( + reps.a02 * P1[0] + reps.a12 * P1[1] + reps.a22 * P1[2]); + + Rational f22 = P2[0] * (reps.a00 * P2[0] + reps.a01 * P2[1] + + reps.a02 * P2[2]) + P2[1] * (reps.a01 * P2[0] + + reps.a11 * P2[1] + reps.a12 * P2[2]) + P2[2] * ( + reps.a02 * P2[0] + reps.a12 * P2[1] + reps.a22 * P2[2]); + + // Vector g. + Rational g1 = P1[0] * reps.b0 + P1[1] * reps.b1 + P1[2] * reps.b2; + Rational g2 = P2[0] * reps.b0 + P2[1] * reps.b1 + P2[2] * reps.b2; + + // Compute g^T*F^{-1}*g/4 - c. + Rational fourDet = Rational(4) * (f11 * f22 - f12 * f12); + Rational r = (g1 * (f22 * g1 - f12 * g2) + g2 * (f11 * g2 - f12 * g1)) / fourDet - reps.c; + + if (r > zero) + { + if (positiveRoots == 2) + { + return QT_ELLIPTIC_CYLINDER; + } + else if (positiveRoots == 1) + { + return QT_HYPERBOLIC_CYLINDER; + } + else + { + return QT_NONE; + } + } + else if (r < zero) + { + if (positiveRoots == 2) + { + return QT_NONE; + } + else if (positiveRoots == 1) + { + return QT_HYPERBOLIC_CYLINDER; + } + else + { + return QT_ELLIPTIC_CYLINDER; + } + } + + // else r == 0 + return (positiveRoots == 1 ? QT_TWO_PLANES : QT_LINE); + } + + static Classification ClassifyZeroRoots2(const RReps& reps, int positiveRoots) + { + // Generate an orthonormal set {p0,p1,p2}, where p0 and p1 are + // eigenvectors of A corresponding to eigenvalue zero. The vector + // p2 is an eigenvector of A corresponding to eigenvalue c2. + Rational const zero(0); + RVector3 P0, P1, P2; + + if (reps.a00 != zero || reps.a01 != zero || reps.a02 != zero) + { + // row 0 is not zero + P2 = { reps.a00, reps.a01, reps.a02 }; + } + else if (reps.a01 != zero || reps.a11 != zero || reps.a12 != zero) + { + // row 1 is not zero + P2 = { reps.a01, reps.a11, reps.a12 }; + } + else + { + // row 2 is not zero + P2 = { reps.a02, reps.a12, reps.a22 }; + } + + if (P2[0] != zero) + { + P1[0] = P2[1]; + P1[1] = -P2[0]; + P1[2] = zero; + } + else + { + P1[0] = zero; + P1[1] = P2[2]; + P1[2] = -P2[1]; + } + P0 = Cross(P1, P2); + + return ClassifyZeroRoots2(reps, positiveRoots, P0, P1, P2); + } + + static Classification ClassifyZeroRoots2(RReps const& reps, int positiveRoots, + RVector3 const& P0, RVector3 const& P1, RVector3 const& P2) + { + Rational const zero(0); + Rational e0 = P0[0] * reps.b0 + P0[1] * reps.b1 + P0[2] * reps.b1; + if (e0 != zero) + { + return QT_PARABOLIC_CYLINDER; + } + + Rational e1 = P1[0] * reps.b0 + P1[1] * reps.b1 + P1[2] * reps.b1; + if (e1 != zero) + { + return QT_PARABOLIC_CYLINDER; + } + + Rational f1 = reps.k2 * Dot(P2, P2); + Rational e2 = P2[0] * reps.b0 + P2[1] * reps.b1 + P2[2] * reps.b1; + Rational r = e2 * e2 / (Rational(4) * f1) - reps.c; + if (r > zero) + { + if (positiveRoots == 1) + { + return QT_TWO_PLANES; + } + else + { + return QT_NONE; + } + } + else if (r < zero) + { + if (positiveRoots == 1) + { + return QT_NONE; + } + else + { + return QT_TWO_PLANES; + } + } + + // else r == 0 + return QT_PLANE; + } + + static Classification ClassifyZeroRoots3(RReps const& reps) + { + Rational const zero(0); + if (reps.b0 != zero || reps.b1 != zero || reps.b2 != zero) + { + return QT_PLANE; + } + + return QT_NONE; + } + + std::array mCoefficient; + Real mC; + Vector3 mB; + Matrix3x3 mA; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/QuarticRootsQR.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/QuarticRootsQR.h new file mode 100644 index 0000000..68bda42 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/QuarticRootsQR.h @@ -0,0 +1,283 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// An implementation of the QR algorithm described in "Matrix Computations, +// 2nd edition" by G. H. Golub and C. F. Van Loan, The Johns Hopkins +// University Press, Baltimore MD, Fourth Printing 1993. In particular, +// the implementation is based on Chapter 7 (The Unsymmetric Eigenvalue +// Problem), Section 7.5 (The Practical QR Algorithm). The algorithm is +// specialized for the companion matrix associated with a quartic polynomial. + +namespace WwiseGTE +{ + template + class QuarticRootsQR + { + public: + typedef std::array, 4> Matrix; + + // Solve p(x) = c0 + c1 * x + c2 * x^2 + c3 * x^3 + x^4 = 0. + uint32_t operator() (uint32_t maxIterations, Real c0, Real c1, Real c2, Real c3, + uint32_t& numRoots, std::array& roots) const + { + // Create the companion matrix for the polynomial. The matrix is + // in upper Hessenberg form. + Matrix A; + A[0][0] = (Real)0; + A[0][1] = (Real)0; + A[0][2] = (Real)0; + A[0][3] = -c0; + A[1][0] = (Real)1; + A[1][1] = (Real)0; + A[1][2] = (Real)0; + A[1][3] = -c1; + A[2][0] = (Real)0; + A[2][1] = (Real)1; + A[2][2] = (Real)0; + A[2][3] = -c2; + A[3][0] = (Real)0; + A[3][1] = (Real)0; + A[3][2] = (Real)1; + A[3][3] = -c3; + + // Avoid the QR-cycle when c1 = c2 = 0 and avoid the slow + // convergence when c1 and c2 are nearly zero. + std::array V{ + (Real)1, + (Real)0.36602540378443865, + (Real)0.36602540378443865 }; + DoIteration(V, A); + + return operator()(maxIterations, A, numRoots, roots); + } + + // Compute the real eigenvalues of the upper Hessenberg matrix A. The + // matrix is modified by in-place operations, so if you need to + // remember A, you must make your own copy before calling this + // function. + uint32_t operator() (uint32_t maxIterations, Matrix& A, + uint32_t& numRoots, std::array& roots) const + { + numRoots = 0; + std::fill(roots.begin(), roots.end(), (Real)0); + + for (uint32_t numIterations = 0; numIterations < maxIterations; ++numIterations) + { + // Apply a Francis QR iteration. + Real tr = A[2][2] + A[3][3]; + Real det = A[2][2] * A[3][3] - A[2][3] * A[3][2]; + std::array X{ + A[0][0] * A[0][0] + A[0][1] * A[1][0] - tr * A[0][0] + det, + A[1][0] * (A[0][0] + A[1][1] - tr), + A[1][0] * A[2][1] }; + std::array V = House<3>(X); + DoIteration(V, A); + + // Test for uncoupling of A. + Real tr12 = A[1][1] + A[2][2]; + if (tr12 + A[2][1] == tr12) + { + GetQuadraticRoots(0, 1, A, numRoots, roots); + GetQuadraticRoots(2, 3, A, numRoots, roots); + return numIterations; + } + + Real tr01 = A[0][0] + A[1][1]; + if (tr01 + A[1][0] == tr01) + { + numRoots = 1; + roots[0] = A[0][0]; + + // TODO: The cubic solver is not designed to process 3x3 + // submatrices of an NxN matrix, so the copy of a + // submatrix of A to B is a simple workaround for running + // the solver. Write general root-finding/ code that + // avoids such copying. + uint32_t subMaxIterations = maxIterations - numIterations; + typename CubicRootsQR::Matrix B; + for (int r = 0; r < 3; ++r) + { + for (int c = 0; c < 3; ++c) + { + B[r][c] = A[r + 1][c + 1]; + } + } + + uint32_t numSubroots = 0; + std::array subroots; + uint32_t numSubiterations = CubicRootsQR()(subMaxIterations, B, + numSubroots, subroots); + for (uint32_t i = 0; i < numSubroots; ++i) + { + roots[numRoots++] = subroots[i]; + } + return numIterations + numSubiterations; + } + + Real tr23 = A[2][2] + A[3][3]; + if (tr23 + A[3][2] == tr23) + { + numRoots = 1; + roots[0] = A[3][3]; + + // TODO: The cubic solver is not designed to process 3x3 + // submatrices of an NxN matrix, so the copy of a + // submatrix of A to B is a simple workaround for running + // the solver. Write general root-finding/ code that + // avoids such copying. + uint32_t subMaxIterations = maxIterations - numIterations; + typename CubicRootsQR::Matrix B; + for (int r = 0; r < 3; ++r) + { + for (int c = 0; c < 3; ++c) + { + B[r][c] = A[r][c]; + } + } + + uint32_t numSubroots = 0; + std::array subroots; + uint32_t numSubiterations = CubicRootsQR()(subMaxIterations, B, + numSubroots, subroots); + for (uint32_t i = 0; i < numSubroots; ++i) + { + roots[numRoots++] = subroots[i]; + } + return numIterations + numSubiterations; + } + } + return maxIterations; + } + + private: + void DoIteration(std::array const& V, Matrix& A) const + { + Real multV = (Real)-2 / (V[0] * V[0] + V[1] * V[1] + V[2] * V[2]); + std::array MV{ multV * V[0], multV * V[1], multV * V[2] }; + RowHouse<3>(0, 2, 0, 3, V, MV, A); + ColHouse<3>(0, 3, 0, 2, V, MV, A); + + std::array X{ A[1][0], A[2][0], A[3][0] }; + std::array locV = House<3>(X); + multV = (Real)-2 / (locV[0] * locV[0] + locV[1] * locV[1] + locV[2] * locV[2]); + MV = { multV * locV[0], multV * locV[1], multV * locV[2] }; + RowHouse<3>(1, 3, 0, 3, locV, MV, A); + ColHouse<3>(0, 3, 1, 3, locV, MV, A); + + std::array Y{ A[2][1], A[3][1] }; + std::array W = House<2>(Y); + Real multW = (Real)-2 / (W[0] * W[0] + W[1] * W[1]); + std::array MW = { multW * W[0], multW * W[1] }; + RowHouse<2>(2, 3, 0, 3, W, MW, A); + ColHouse<2>(0, 3, 2, 3, W, MW, A); + } + + template + std::array House(std::array const& X) const + { + std::array V; + Real length = (Real)0; + for (int i = 0; i < N; ++i) + { + length += X[i] * X[i]; + } + length = std::sqrt(length); + if (length != (Real)0) + { + Real sign = (X[0] >= (Real)0 ? (Real)1 : (Real)-1); + Real denom = X[0] + sign * length; + for (int i = 1; i < N; ++i) + { + V[i] = X[i] / denom; + } + } + else + { + V.fill((Real)0); + } + V[0] = (Real)1; + return V; + } + + template + void RowHouse(int rmin, int rmax, int cmin, int cmax, + std::array const& V, std::array const& MV, Matrix& A) const + { + // Only elements cmin through cmax are used. + std::array W; + + for (int c = cmin; c <= cmax; ++c) + { + W[c] = (Real)0; + for (int r = rmin, k = 0; r <= rmax; ++r, ++k) + { + W[c] += V[k] * A[r][c]; + } + } + + for (int r = rmin, k = 0; r <= rmax; ++r, ++k) + { + for (int c = cmin; c <= cmax; ++c) + { + A[r][c] += MV[k] * W[c]; + } + } + } + + template + void ColHouse(int rmin, int rmax, int cmin, int cmax, + std::array const& V, std::array const& MV, Matrix& A) const + { + // Only elements rmin through rmax are used. + std::array W; + + for (int r = rmin; r <= rmax; ++r) + { + W[r] = (Real)0; + for (int c = cmin, k = 0; c <= cmax; ++c, ++k) + { + W[r] += V[k] * A[r][c]; + } + } + + for (int r = rmin; r <= rmax; ++r) + { + for (int c = cmin, k = 0; c <= cmax; ++c, ++k) + { + A[r][c] += W[r] * MV[k]; + } + } + } + + void GetQuadraticRoots(int i0, int i1, Matrix const& A, + uint32_t& numRoots, std::array& roots) const + { + // Solve x^2 - t * x + d = 0, where t is the trace and d is the + // determinant of the 2x2 matrix defined by indices i0 and i1. + // The discriminant is D = (t/2)^2 - d. When D >= 0, the roots + // are real values t/2 - sqrt(D) and t/2 + sqrt(D). To avoid + // potential numerical issues with subtractive cancellation, the + // roots are computed as + // r0 = t/2 + sign(t/2)*sqrt(D), r1 = trace - r0 + Real trace = A[i0][i0] + A[i1][i1]; + Real halfTrace = trace * (Real)0.5; + Real determinant = A[i0][i0] * A[i1][i1] - A[i0][i1] * A[i1][i0]; + Real discriminant = halfTrace * halfTrace - determinant; + if (discriminant >= (Real)0) + { + Real sign = (trace >= (Real)0 ? (Real)1 : (Real)-1); + Real root = halfTrace + sign * std::sqrt(discriminant); + roots[numRoots++] = root; + roots[numRoots++] = trace - root; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Quaternion.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Quaternion.h new file mode 100644 index 0000000..fa9f061 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Quaternion.h @@ -0,0 +1,455 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// A quaternion is of the form +// q = x * i + y * j + z * k + w * 1 = x * i + y * j + z * k + w +// where w, x, y, and z are real numbers. The scalar and vector parts are +// Vector(q) = x * i + y * j + z * k +// Scalar(q) = w +// q = Vector(q) + Scalar(q) +// I assume that you are familiar with the arithmetic and algebraic properties +// of quaternions. See +// https://www.geometrictools.com/Documentation/Quaternions.pdf + +namespace WwiseGTE +{ + template + class Quaternion + { + public: + // The quaternions are of the form q = x*i + y*j + z*k + w. In tuple + // form, q = (x,y,z,w). + + // Construction. The default constructor does not initialize the + // members. + Quaternion() = default; + + Quaternion(Real x, Real y, Real z, Real w) + { + mTuple[0] = x; + mTuple[1] = y; + mTuple[2] = z; + mTuple[3] = w; + } + + // Member access. + inline Real const& operator[](int i) const + { + return mTuple[i]; + } + + inline Real& operator[](int i) + { + return mTuple[i]; + } + + // Comparisons. + inline bool operator==(Quaternion const& q) const + { + return mTuple == q.mTuple; + } + + inline bool operator!=(Quaternion const& q) const + { + return mTuple != q.mTuple; + } + + inline bool operator<(Quaternion const& q) const + { + return mTuple < q.mTuple; + } + + inline bool operator<=(Quaternion const& q) const + { + return mTuple <= q.mTuple; + } + + inline bool operator>(Quaternion const& q) const + { + return mTuple > q.mTuple; + } + + inline bool operator>=(Quaternion const& q) const + { + return mTuple >= q.mTuple; + } + + // Special quaternions. + + // z = 0*i + 0*j + 0*k + 0 + static Quaternion Zero() + { + return Quaternion((Real)0, (Real)0, (Real)0, (Real)0); + } + + // i = 1*i + 0*j + 0*k + 0 + static Quaternion I() + { + return Quaternion((Real)1, (Real)0, (Real)0, (Real)0); + } + + // j = 0*i + 1*j + 0*k + 0 + static Quaternion J() + { + return Quaternion((Real)0, (Real)1, (Real)0, (Real)0); + } + + // k = 0*i + 0*j + 1*k + 0 + static Quaternion K() + { + return Quaternion((Real)0, (Real)0, (Real)1, (Real)0); + } + + // 1 = 0*i + 0*j + 0*k + 1 + static Quaternion Identity() + { + return Quaternion((Real)0, (Real)0, (Real)0, (Real)1); + } + + protected: + std::array mTuple; + }; + + // Unary operations. + template + Quaternion operator+(Quaternion const& q) + { + return q; + } + + template + Quaternion operator-(Quaternion const& q) + { + Quaternion result; + for (int i = 0; i < 4; ++i) + { + result[i] = -q[i]; + } + return result; + } + + // Linear algebraic operations. + template + Quaternion operator+(Quaternion const& q0, Quaternion const& q1) + { + Quaternion result = q0; + return result += q1; + } + + template + Quaternion operator-(Quaternion const& q0, Quaternion const& q1) + { + Quaternion result = q0; + return result -= q1; + } + + template + Quaternion operator*(Quaternion const& q, Real scalar) + { + Quaternion result = q; + return result *= scalar; + } + + template + Quaternion operator*(Real scalar, Quaternion const& q) + { + Quaternion result = q; + return result *= scalar; + } + + template + Quaternion operator/(Quaternion const& q, Real scalar) + { + Quaternion result = q; + return result /= scalar; + } + + template + Quaternion& operator+=(Quaternion& q0, Quaternion const& q1) + { + for (int i = 0; i < 4; ++i) + { + q0[i] += q1[i]; + } + return q0; + } + + template + Quaternion& operator-=(Quaternion& q0, Quaternion const& q1) + { + for (int i = 0; i < 4; ++i) + { + q0[i] -= q1[i]; + } + return q0; + } + + template + Quaternion& operator*=(Quaternion& q, Real scalar) + { + for (int i = 0; i < 4; ++i) + { + q[i] *= scalar; + } + return q; + } + + template + Quaternion& operator/=(Quaternion& q, Real scalar) + { + if (scalar != (Real)0) + { + for (int i = 0; i < 4; ++i) + { + q[i] /= scalar; + } + } + else + { + for (int i = 0; i < 4; ++i) + { + q[i] = (Real)0; + } + } + return q; + } + + // Geometric operations. + template + Real Dot(Quaternion const& q0, Quaternion const& q1) + { + Real dot = q0[0] * q1[0]; + for (int i = 1; i < 4; ++i) + { + dot += q0[i] * q1[i]; + } + return dot; + } + + template + Real Length(Quaternion const& q) + { + return std::sqrt(Dot(q, q)); + } + + template + Real Normalize(Quaternion& q) + { + Real length = std::sqrt(Dot(q, q)); + if (length > (Real)0) + { + q /= length; + } + else + { + for (int i = 0; i < 4; ++i) + { + q[i] = (Real)0; + } + } + return length; + } + + // Multiplication of quaternions. This operation is not generally + // commutative; that is, q0*q1 and q1*q0 are not usually the same value. + // (x0*i + y0*j + z0*k + w0)*(x1*i + y1*j + z1*k + w1) + // = + // i*(+x0*w1 + y0*z1 - z0*y1 + w0*x1) + + // j*(-x0*z1 + y0*w1 + z0*x1 + w0*y1) + + // k*(+x0*y1 - y0*x1 + z0*w1 + w0*z1) + + // 1*(-x0*x1 - y0*y1 - z0*z1 + w0*w1) + template + Quaternion operator*(Quaternion const& q0, Quaternion const& q1) + { + // (x0*i + y0*j + z0*k + w0)*(x1*i + y1*j + z1*k + w1) + // = + // i*(+x0*w1 + y0*z1 - z0*y1 + w0*x1) + + // j*(-x0*z1 + y0*w1 + z0*x1 + w0*y1) + + // k*(+x0*y1 - y0*x1 + z0*w1 + w0*z1) + + // 1*(-x0*x1 - y0*y1 - z0*z1 + w0*w1) + + return Quaternion + ( + +q0[0] * q1[3] + q0[1] * q1[2] - q0[2] * q1[1] + q0[3] * q1[0], + -q0[0] * q1[2] + q0[1] * q1[3] + q0[2] * q1[0] + q0[3] * q1[1], + +q0[0] * q1[1] - q0[1] * q1[0] + q0[2] * q1[3] + q0[3] * q1[2], + -q0[0] * q1[0] - q0[1] * q1[1] - q0[2] * q1[2] + q0[3] * q1[3] + ); + } + + // For a nonzero quaternion q = (x,y,z,w), inv(q) = (-x,-y,-z,w)/|q|^2, + // where |q| is the length of the quaternion. When q is zero, the + // function returns zero, which is considered to be an improbable case. + template + Quaternion Inverse(Quaternion const& q) + { + Real sqrLen = Dot(q, q); + if (sqrLen > (Real)0) + { + Quaternion inverse = Conjugate(q) / sqrLen; + return inverse; + } + else + { + return Quaternion::Zero(); + } + } + + // The conjugate of q = (x,y,z,w) is conj(q) = (-x,-y,-z,w). + template + Quaternion Conjugate(Quaternion const& q) + { + return Quaternion(-q[0], -q[1], -q[2], +q[3]); + } + + // Rotate a vector using quaternion multiplication. The input quaternion + // must be unit length. + template + Vector<4, Real> Rotate(Quaternion const& q, Vector<4, Real> const& v) + { + Quaternion input(v[0], v[1], v[2], (Real)0); + Quaternion output = q * input * Conjugate(q); + Vector<4, Real> u{ output[0], output[1], output[2], (Real)0 }; + return u; + } + + // The spherical linear interpolation (slerp) of unit-length quaternions + // q0 and q1 for t in [0,1] is + // slerp(t,q0,q1) = [sin(t*theta)*q0 + sin((1-t)*theta)*q1]/sin(theta) + // where theta is the angle between q0 and q1 [cos(theta) = Dot(q0,q1)]. + // This function is a parameterization of the great spherical arc between + // q0 and q1 on the unit hypersphere. Moreover, the parameterization is + // one of normalized arclength--a particle traveling along the arc through + // time t does so with constant speed. + // + // When using slerp in animations involving sequences of quaternions, it + // is typical that the quaternions are preprocessed so that consecutive + // ones form an acute angle A in [0,pi/2]. Other preprocessing can help + // with performance. See the function comments below. + // + // See GteSlerpEstimate.{h,inl} for various approximations, including + // SLERP::EstimateRPH that gives good performance and accurate + // results for preprocessed quaternions. + + // The angle between q0 and q1 is in [0,pi). There are no angle + // restrictions and nothing is precomputed. + template + Quaternion Slerp(Real t, Quaternion const& q0, Quaternion const& q1) + { + Real cosA = Dot(q0, q1); + Real sign; + if (cosA >= (Real)0) + { + sign = (Real)1; + } + else + { + cosA = -cosA; + sign = (Real)-1; + } + + Real f0, f1; + ChebyshevRatio::Get(t, cosA, f0, f1); + return q0 * f0 + q1 * (sign * f1); + } + + // The angle between q0 and q1 must be in [0,pi/2]. The suffix R is for + // 'Restricted'. The preprocessing code is + // Quaternion q[n]; // assuming initialized + // for (i0 = 0, i1 = 1; i1 < n; i0 = i1++) + // { + // cosA = Dot(q[i0], q[i1]); + // if (cosA < 0) + // { + // q[i1] = -q[i1]; // now Dot(q[i0], q[i]1) >= 0 + // } + // } + template + Quaternion SlerpR(Real t, Quaternion const& q0, Quaternion const& q1) + { + Real f0, f1; + ChebyshevRatio::Get(t, Dot(q0, q1), f0, f1); + return q0 * f0 + q1 * f1; + } + + // The angle between q0 and q1 must be in [0,pi/2]. The suffix R is for + // 'Restricted' and the suffix P is for 'Preprocessed'. The preprocessing + // code is + // Quaternion q[n]; // assuming initialized + // Real cosA[n-1], omcosA[n-1]; // to be precomputed + // for (i0 = 0, i1 = 1; i1 < n; i0 = i1++) + // { + // cs = Dot(q[i0], q[i1]); + // if (cosA[i0] < 0) + // { + // q[i1] = -q[i1]; + // cs = -cs; + // } + // + // // for Quaterion::SlerpRP + // cosA[i0] = cs; + // + // // for SLERP::EstimateRP + // omcosA[i0] = 1 - cs; + // } + template + Quaternion SlerpRP(Real t, Quaternion const& q0, Quaternion const& q1, Real cosA) + { + Real f0, f1; + ChebyshevRatio::Get(t, cosA, f0, f1); + return q0 * f0 + q1 * f1; + } + + // The angle between q0 and q1 is A and must be in [0,pi/2]. The suffix R + // is for 'Restricted', the suffix P is for 'Preprocessed' and the suffix + // H is for 'Half' (the quaternion qh halfway between q0 and q1 is + // precomputed). Quaternion qh is slerp(1/2,q0,q1) = (q0+q1)/|q0+q1|, so + // the angle between q0 and qh is A/2 and the angle between qh and q1 is + // A/2. The preprocessing code is + // Quaternion q[n]; // assuming initialized + // Quaternion qh[n-1]; // to be precomputed + // Real omcosAH[n-1]; // to be precomputed + // for (i0 = 0, i1 = 1; i1 < n; i0 = i1++) + // { + // cosA = Dot(q[i0], q[i1]); + // if (cosA < 0) + // { + // q[i1] = -q[i1]; + // cosA = -cosA; + // } + // + // // for Quaternion::SlerpRPH and SLERP::EstimateRPH + // cosAH[i0] = sqrt((1+cosA)/2); + // qh[i0] = (q0 + q1) / (2 * cosAH[i0]); + // + // // for SLERP::EstimateRPH + // omcosAH[i0] = 1 - cosAH[i0]; + // } + template + Quaternion SlerpRPH(Real t, Quaternion const& q0, Quaternion const& q1, + Quaternion const& qh, Real cosAH) + { + Real f0, f1; + Real twoT = static_cast(2) * t; + if (twoT <= static_cast(1)) + { + ChebyshevRatio::Get(twoT, cosAH, f0, f1); + return q0 * f0 + qh * f1; + } + else + { + ChebyshevRatio::Get(twoT - static_cast(1), cosAH, f0, f1); + return qh * f0 + q1 * f1; + } + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RangeIteration.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RangeIteration.h new file mode 100644 index 0000000..7f68d85 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RangeIteration.h @@ -0,0 +1,66 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// For information on range-based for-loops, see +// http://en.cppreference.com/w/cpp/language/range-for + +namespace WwiseGTE +{ + // The function WwiseGTE::reverse supports reverse iteration in range-based + // for-loops using the auto keyword. For example, + // + // std::vector numbers(4); + // int i = 0; + // for (auto& number : numbers) + // { + // number = i++; + // std::cout << number << ' '; + // } + // // Output: 0 1 2 3 + // + // for (auto& number : WwiseGTE::reverse(numbers)) + // { + // std::cout << number << ' '; + // } + // // Output: 3 2 1 0 + + template + class ReversalObject + { + public: + ReversalObject(Iterator begin, Iterator end) + : + mBegin(begin), + mEnd(end) + { + } + + Iterator begin() const { return mBegin; } + Iterator end() const { return mEnd; } + + private: + Iterator mBegin, mEnd; + }; + + template + < + typename Iterable, + typename Iterator = decltype(std::begin(std::declval())), + typename ReverseIterator = std::reverse_iterator + > + ReversalObject reverse(Iterable && range) + { + return ReversalObject( + ReverseIterator(std::end(range)), + ReverseIterator(std::begin(range))); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Ray.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Ray.h new file mode 100644 index 0000000..93ed6a2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Ray.h @@ -0,0 +1,89 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The ray is represented as P+t*D, where P is the ray origin, D is a +// unit-length direction vector, and t >= 0. The user must ensure that D is +// unit length. + +namespace WwiseGTE +{ + template + class Ray + { + public: + // Construction and destruction. The default constructor sets the + // origin to (0,...,0) and the ray direction to (1,0,...,0). + Ray() + { + origin.MakeZero(); + direction.MakeUnit(0); + } + + Ray(Vector const& inOrigin, Vector const& inDirection) + : + origin(inOrigin), + direction(inDirection) + { + } + + // Public member access. The direction must be unit length. + Vector origin, direction; + + public: + // Comparisons to support sorted containers. + bool operator==(Ray const& ray) const + { + return origin == ray.origin && direction == ray.direction; + } + + bool operator!=(Ray const& ray) const + { + return !operator==(ray); + } + + bool operator< (Ray const& ray) const + { + if (origin < ray.origin) + { + return true; + } + + if (origin > ray.origin) + { + return false; + } + + return direction < ray.direction; + } + + bool operator<=(Ray const& ray) const + { + return !ray.operator<(*this); + } + + bool operator> (Ray const& ray) const + { + return ray.operator<(*this); + } + + bool operator>=(Ray const& ray) const + { + return !operator<(ray); + } + }; + + // Template aliases for convenience. + template + using Ray2 = Ray<2, Real>; + + template + using Ray3 = Ray<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Rectangle.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Rectangle.h new file mode 100644 index 0000000..642548d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Rectangle.h @@ -0,0 +1,144 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// Points are R(s0,s1) = C + s0*A0 + s1*A1, where C is the center of the +// rectangle and A0 and A1 are unit-length and perpendicular axes. The +// parameters s0 and s1 are constrained by |s0| <= e0 and |s1| <= e1, +// where e0 > 0 and e1 > 0 are the extents of the rectangle. + +namespace WwiseGTE +{ + template + class Rectangle + { + public: + // Construction and destruction. The default constructor sets the + // origin to (0,...,0), axis A0 to (1,0,...,0), axis A1 to + // (0,1,0,...0) and both extents to 1. + Rectangle() + { + center.MakeZero(); + for (int i = 0; i < 2; ++i) + { + axis[i].MakeUnit(i); + extent[i] = (Real)1; + } + } + + Rectangle(Vector const& inCenter, + std::array, 2> const& inAxis, + Vector<2, Real> const& inExtent) + : + center(inCenter), + axis(inAxis), + extent(inExtent) + { + } + + // Compute the vertices of the rectangle. If index i has the bit + // pattern i = b[1]b[0], then + // vertex[i] = center + sum_{d=0}^{1} sign[d] * extent[d] * axis[d] + // where sign[d] = 2*b[d] - 1. + void GetVertices(std::array, 4>& vertex) const + { + Vector product0 = extent[0] * axis[0]; + Vector product1 = extent[1] * axis[1]; + Vector sum = product0 + product1; + Vector dif = product0 - product1; + + vertex[0] = center - sum; + vertex[1] = center + dif; + vertex[2] = center - dif; + vertex[3] = center + sum; + } + + Vector center; + std::array, 2> axis; + Vector<2, Real> extent; + + public: + // Comparisons to support sorted containers. + bool operator==(Rectangle const& rectangle) const + { + if (center != rectangle.center) + { + return false; + } + + for (int i = 0; i < 2; ++i) + { + if (axis[i] != rectangle.axis[i]) + { + return false; + } + } + + for (int i = 0; i < 2; ++i) + { + if (extent[i] != rectangle.extent[i]) + { + return false; + } + } + + return true; + } + + bool operator!=(Rectangle const& rectangle) const + { + return !operator==(rectangle); + } + + bool operator< (Rectangle const& rectangle) const + { + if (center < rectangle.center) + { + return true; + } + + if (center > rectangle.center) + { + return false; + } + + if (axis < rectangle.axis) + { + return true; + } + + if (axis > rectangle.axis) + { + return false; + } + + return extent < rectangle.extent; + } + + bool operator<=(Rectangle const& rectangle) const + { + return !rectangle.operator<(*this); + } + + bool operator> (Rectangle const& rectangle) const + { + return rectangle.operator<(*this); + } + + bool operator>=(Rectangle const& rectangle) const + { + return !operator<(rectangle); + } + }; + + // Template alias for convenience. + template + using Rectangle3 = Rectangle<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RectangleManager.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RectangleManager.h new file mode 100644 index 0000000..3af96e4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RectangleManager.h @@ -0,0 +1,263 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class RectangleManager + { + public: + // Construction. + RectangleManager(std::vector>& rectangles) + : + mRectangles(rectangles) + { + Initialize(); + } + + // No default construction, copy construction, or assignment are + // allowed. + RectangleManager() = delete; + RectangleManager(RectangleManager const&) = delete; + RectangleManager& operator=(RectangleManager const&) = delete; + + // This function is called by the constructor and does the + // sort-and-sweep to initialize the update system. However, if you + // add or remove items from the array of rectangles after the + // constructor call, you will need to call this function once before + // you start the multiple calls of the update function. + void Initialize() + { + // Get the rectangle endpoints. + int intrSize = static_cast(mRectangles.size()), endpSize = 2 * intrSize; + mXEndpoints.resize(endpSize); + mYEndpoints.resize(endpSize); + for (int i = 0, j = 0; i < intrSize; ++i) + { + mXEndpoints[j].type = 0; + mXEndpoints[j].value = mRectangles[i].min[0]; + mXEndpoints[j].index = i; + mYEndpoints[j].type = 0; + mYEndpoints[j].value = mRectangles[i].min[1]; + mYEndpoints[j].index = i; + ++j; + + mXEndpoints[j].type = 1; + mXEndpoints[j].value = mRectangles[i].max[0]; + mXEndpoints[j].index = i; + mYEndpoints[j].type = 1; + mYEndpoints[j].value = mRectangles[i].max[1]; + mYEndpoints[j].index = i; + ++j; + } + + // Sort the rectangle endpoints. + std::sort(mXEndpoints.begin(), mXEndpoints.end()); + std::sort(mYEndpoints.begin(), mYEndpoints.end()); + + // Create the interval-to-endpoint lookup tables. + mXLookup.resize(endpSize); + mYLookup.resize(endpSize); + for (int j = 0; j < endpSize; ++j) + { + mXLookup[2 * mXEndpoints[j].index + mXEndpoints[j].type] = j; + mYLookup[2 * mYEndpoints[j].index + mYEndpoints[j].type] = j; + } + + // Active set of rectangles (stored by index in array). + std::set active; + + // Set of overlapping rectangles (stored by pairs of indices in + // array). + mOverlap.clear(); + + // Sweep through the endpoints to determine overlapping + // x-intervals. + for (int i = 0; i < endpSize; ++i) + { + Endpoint& endpoint = mXEndpoints[i]; + int index = endpoint.index; + if (endpoint.type == 0) // an interval 'begin' value + { + // In the 1D problem, the current interval overlaps with + // all the active intervals. In 2D we also need to check + // for y-overlap. + for (auto activeIndex : active) + { + // Rectangles activeIndex and index overlap in the + // x-dimension. Test for overlap in the y-dimension. + AlignedBox2 const& r0 = mRectangles[activeIndex]; + AlignedBox2 const& r1 = mRectangles[index]; + if (r0.max[1] >= r1.min[1] && r0.min[1] <= r1.max[1]) + { + if (activeIndex < index) + { + mOverlap.insert(EdgeKey(activeIndex, index)); + } + else + { + mOverlap.insert(EdgeKey(index, activeIndex)); + } + } + } + active.insert(index); + } + else // an interval 'end' value + { + active.erase(index); + } + } + } + + // After the system is initialized, you can move the rectangles using + // this function. It is not enough to modify the input array of + // rectangles because the endpoint values stored internally by this + // class must also change. You can also retrieve the current + // rectangles information. + void SetRectangle(int i, AlignedBox2 const& rectangle) + { + mRectangles[i] = rectangle; + mXEndpoints[mXLookup[2 * i]].value = rectangle.min[0]; + mXEndpoints[mXLookup[2 * i + 1]].value = rectangle.max[0]; + mYEndpoints[mYLookup[2 * i]].value = rectangle.min[1]; + mYEndpoints[mYLookup[2 * i + 1]].value = rectangle.max[1]; + } + + inline void GetRectangle(int i, AlignedBox2& rectangle) const + { + rectangle = mRectangles[i]; + } + + // When you are finished moving rectangles, call this function to + // determine the overlapping rectangles. An incremental update is + // applied to determine the new set of overlapping rectangles. + void Update() + { + InsertionSort(mXEndpoints, mXLookup); + InsertionSort(mYEndpoints, mYLookup); + } + + // If (i,j) is in the overlap set, then rectangle i and rectangle j + // are overlapping. The indices are those for the the input array. + // The set elements (i,j) are stored so that i < j. + inline std::set> const& GetOverlap() const + { + return mOverlap; + } + + private: + class Endpoint + { + public: + Real value; // endpoint value + int type; // '0' if interval min, '1' if interval max. + int index; // index of interval containing this endpoint + + // Support for sorting of endpoints. + bool operator<(Endpoint const& endpoint) const + { + if (value < endpoint.value) + { + return true; + } + if (value > endpoint.value) + { + return false; + } + return type < endpoint.type; + } + }; + + void InsertionSort(std::vector& endpoint, std::vector& lookup) + { + // Apply an insertion sort. Under the assumption that the + // rectangles have not changed much since the last call, the + // endpoints are nearly sorted. The insertion sort should be very + // fast in this case. + + TIQuery, AlignedBox2> query; + int endpSize = static_cast(endpoint.size()); + for (int j = 1; j < endpSize; ++j) + { + Endpoint key = endpoint[j]; + int i = j - 1; + while (i >= 0 && key < endpoint[i]) + { + Endpoint e0 = endpoint[i]; + Endpoint e1 = endpoint[i + 1]; + + // Update the overlap status. + if (e0.type == 0) + { + if (e1.type == 1) + { + // The 'b' of interval E0.index was smaller than + // the 'e' of interval E1.index, and the intervals + // *might have been* overlapping. Now 'b' and 'e' + // are swapped, and the intervals cannot overlap. + // Remove the pair from the overlap set. The + // removal operation needs to find the pair and + // erase it if it exists. Finding the pair is the + // expensive part of the operation, so there is no + // real time savings in testing for existence + // first, then deleting if it does. + mOverlap.erase(EdgeKey(e0.index, e1.index)); + } + } + else + { + if (e1.type == 0) + { + // The 'b' of interval E1.index was larger than + // the 'e' of interval E0.index, and the intervals + // were not overlapping. Now 'b' and 'e' are + // swapped, and the intervals *might be* + // overlapping. Determine if they are overlapping + // and then insert. + if (query(mRectangles[e0.index], mRectangles[e1.index]).intersect) + { + mOverlap.insert(EdgeKey(e0.index, e1.index)); + } + } + } + + // Reorder the items to maintain the sorted list. + endpoint[i] = e1; + endpoint[i + 1] = e0; + lookup[2 * e1.index + e1.type] = i; + lookup[2 * e0.index + e0.type] = i + 1; + --i; + } + endpoint[i + 1] = key; + lookup[2 * key.index + key.type] = i + 1; + } + } + + std::vector>& mRectangles; + std::vector mXEndpoints, mYEndpoints; + std::set> mOverlap; + + // The intervals are indexed 0 <= i < n. The endpoint array has 2*n + // entries. The original 2*n interval values are ordered as + // b[0], e[0], b[1], e[1], ..., b[n-1], e[n-1] + // When the endpoint array is sorted, the mapping between interval + // values and endpoints is lost. In order to modify interval values + // that are stored in the endpoint array, we need to maintain the + // mapping. This is done by the following lookup table of 2*n + // entries. The value mLookup[2*i] is the index of b[i] in the + // endpoint array. The value mLookup[2*i+1] is the index of e[i] + // in the endpoint array. + std::vector mXLookup, mYLookup; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RectangleMesh.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RectangleMesh.h new file mode 100644 index 0000000..559d1ec --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RectangleMesh.h @@ -0,0 +1,154 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class RectangleMesh : public Mesh + { + public: + // Create a mesh that tessellates a rectangle. + RectangleMesh(MeshDescription const& description, Rectangle<3, Real> const& rectangle) + : + Mesh(description, { MeshTopology::RECTANGLE }), + mRectangle(rectangle) + { + if (!this->mDescription.constructed) + { + // The logger system will report these errors in the Mesh + // constructor. + return; + } + + if (!this->mTCoords) + { + mDefaultTCoords.resize(this->mDescription.numVertices); + this->mTCoords = mDefaultTCoords.data(); + this->mTCoordStride = sizeof(Vector2); + + this->mDescription.allowUpdateFrame = this->mDescription.wantDynamicTangentSpaceUpdate; + if (this->mDescription.allowUpdateFrame) + { + if (!this->mDescription.hasTangentSpaceVectors) + { + this->mDescription.allowUpdateFrame = false; + } + + if (!this->mNormals) + { + this->mDescription.allowUpdateFrame = false; + } + } + } + + this->ComputeIndices(); + InitializeTCoords(); + InitializePositions(); + if (this->mDescription.allowUpdateFrame) + { + InitializeFrame(); + } + else if (this->mNormals) + { + InitializeNormals(); + } + } + + // Member access. + inline Rectangle<3, Real> const& GetRectangle() const + { + return mRectangle; + } + + protected: + void InitializeTCoords() + { + Vector2 tcoord; + for (uint32_t r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + tcoord[1] = (Real)r / (Real)(this->mDescription.numRows - 1); + for (uint32_t c = 0; c < this->mDescription.numCols; ++c, ++i) + { + tcoord[0] = (Real)c / (Real)(this->mDescription.numCols - 1); + this->TCoord(i) = tcoord; + } + } + } + + void InitializePositions() + { + for (uint32_t r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + for (uint32_t c = 0; c < this->mDescription.numCols; ++c, ++i) + { + Vector2 tcoord = this->TCoord(i); + Real w0 = ((Real)2 * tcoord[0] - (Real)1) * mRectangle.extent[0]; + Real w1 = ((Real)2 * tcoord[1] - (Real)1) * mRectangle.extent[1]; + this->Position(i) = mRectangle.center + w0 * mRectangle.axis[0] + w1 * mRectangle.axis[1]; + } + } + } + + void InitializeNormals() + { + Vector3 normal = UnitCross(mRectangle.axis[0], mRectangle.axis[1]); + for (uint32_t i = 0; i < this->mDescription.numVertices; ++i) + { + this->Normal(i) = normal; + } + } + + void InitializeFrame() + { + Vector3 normal = UnitCross(mRectangle.axis[0], mRectangle.axis[1]); + Vector3 tangent{ (Real)1, (Real)0, (Real)0 }; + Vector3 bitangent{ (Real)0, (Real)1, (Real)0 }; + // bitangent = Cross(normal,tangent) + // TODO: Are tangent and bitangent correct? + for (uint32_t i = 0; i < this->mDescription.numVertices; ++i) + { + if (this->mNormals) + { + this->Normal(i) = normal; + } + + if (this->mTangents) + { + this->Tangent(i) = tangent; + } + + if (this->mBitangents) + { + this->Bitangent(i) = bitangent; + } + + if (this->mDPDUs) + { + this->DPDU(i) = tangent; + } + + if (this->mDPDVs) + { + this->DPDV(i) = bitangent; + } + } + } + + Rectangle<3, Real> mRectangle; + + // If the client does not request texture coordinates, they will be + // computed internally for use in evaluation of the surface geometry. + std::vector> mDefaultTCoords; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RectanglePatchMesh.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RectanglePatchMesh.h new file mode 100644 index 0000000..36fdffe --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RectanglePatchMesh.h @@ -0,0 +1,196 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class RectanglePatchMesh : public Mesh + { + public: + // Create a mesh (x(u,v),y(u,v),z(u,v)) defined by the specified + // surface. It is required that surface->IsRectangular() return + // 'true'. + RectanglePatchMesh(MeshDescription const& description, + std::shared_ptr> const& surface) + : + Mesh(description, { MeshTopology::RECTANGLE }), + mSurface(surface) + { + if (!this->mDescription.constructed) + { + // The logger system will report these errors in the Mesh + // constructor. + mSurface = nullptr; + return; + } + + LogAssert(mSurface != nullptr && mSurface->IsRectangular(), + "A nonnull rectangular surface is required."); + + if (!this->mTCoords) + { + mDefaultTCoords.resize(this->mDescription.numVertices); + this->mTCoords = mDefaultTCoords.data(); + this->mTCoordStride = sizeof(Vector2); + + this->mDescription.allowUpdateFrame = this->mDescription.wantDynamicTangentSpaceUpdate; + if (this->mDescription.allowUpdateFrame) + { + if (!this->mDescription.hasTangentSpaceVectors) + { + this->mDescription.allowUpdateFrame = false; + } + + if (!this->mNormals) + { + this->mDescription.allowUpdateFrame = false; + } + } + } + + this->ComputeIndices(); + InitializeTCoords(); + InitializePositions(); + if (this->mDescription.allowUpdateFrame) + { + InitializeFrame(); + } + else if (this->mNormals) + { + InitializeNormals(); + } + } + + // Member access. + inline std::shared_ptr> const& GetSurface() const + { + return mSurface; + } + + protected: + void InitializeTCoords() + { + Real uMin = mSurface->GetUMin(); + Real uDelta = (mSurface->GetUMax() - uMin) / static_cast(this->mDescription.numCols - 1); + Real vMin = mSurface->GetVMin(); + Real vDelta = (mSurface->GetVMax() - vMin) / static_cast(this->mDescription.numRows - 1); + Vector2 tcoord; + for (uint32_t r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + tcoord[1] = vMin + vDelta * (Real)r; + for (uint32_t c = 0; c < this->mDescription.numCols; ++c, ++i) + { + tcoord[0] = uMin + uDelta * (Real)c; + this->TCoord(i) = tcoord; + } + } + } + + void InitializePositions() + { + for (uint32_t r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + for (uint32_t c = 0; c < this->mDescription.numCols; ++c, ++i) + { + Vector2 tcoord = this->TCoord(i); + this->Position(i) = mSurface->GetPosition(tcoord[0], tcoord[1]); + } + } + } + + void InitializeNormals() + { + for (uint32_t r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + for (uint32_t c = 0; c < this->mDescription.numCols; ++c, ++i) + { + Vector2 tcoord = this->TCoord(i); + Vector3 values[6]; + mSurface->Evaluate(tcoord[0], tcoord[1], 1, values); + Normalize(values[1], true); + Normalize(values[2], true); + this->Normal(i) = UnitCross(values[1], values[2], true); + } + } + } + + void InitializeFrame() + { + for (unsigned int r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + for (unsigned int c = 0; c < this->mDescription.numCols; ++c, ++i) + { + Vector2 tcoord = this->TCoord(i); + Vector3 values[6]; + mSurface->Evaluate(tcoord[0], tcoord[1], 1, values); + Normalize(values[1], true); + Normalize(values[2], true); + + if (this->mDPDUs) + { + this->DPDU(i) = values[1]; + } + if (this->mDPDVs) + { + this->DPDV(i) = values[2]; + } + + ComputeOrthogonalComplement(2, &values[1], true); + + if (this->mNormals) + { + this->Normal(i) = values[3]; + } + if (this->mTangents) + { + this->Tangent(i) = values[1]; + } + if (this->mBitangents) + { + this->Bitangent(i) = values[2]; + } + } + } + } + + virtual void UpdatePositions() override + { + if (mSurface) + { + InitializePositions(); + } + } + + virtual void UpdateNormals() override + { + if (mSurface) + { + InitializeNormals(); + } + } + + virtual void UpdateFrame() override + { + if (mSurface) + { + InitializeFrame(); + } + } + + std::shared_ptr> mSurface; + + // If the client does not request texture coordinates, they will be + // computed internally for use in evaluation of the surface geometry. + std::vector> mDefaultTCoords; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RevolutionMesh.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RevolutionMesh.h new file mode 100644 index 0000000..2fa3837 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RevolutionMesh.h @@ -0,0 +1,317 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class RevolutionMesh : public Mesh + { + public: + // The axis of revolution is the z-axis. The curve of revolution is + // p(t) = (x(t),z(t)), where t in [tmin,tmax], x(t) > 0 for t in + // (tmin,tmax), x(tmin) >= 0, and x(tmax) >= 0. The values tmin and + // tmax are those for the curve object passed to the constructor. The + // curve must be non-self-intersecting, except possibly at its + // endpoints. The curve is closed when p(tmin) = p(tmax), in which + // case the surface of revolution has torus topology. The curve is + // open when p(tmin) != p(tmax). For an open curve, define + // x0 = x(tmin) and x1 = x(tmax). The surface has cylinder topology + // when x0 > 0 and x1 > 0, disk topology when exactly one of x0 or x1 + // is zero, or sphere topology when x0 and x1 are both zero. However, + // to simplify the design, the mesh is always built using cylinder + // topology. The row samples correspond to curve points and the + // column samples correspond to the points on the circles of + // revolution. + RevolutionMesh(MeshDescription const& description, + std::shared_ptr> const& curve, + bool sampleByArcLength = false) + : + Mesh(description, + { MeshTopology::CYLINDER, MeshTopology::TORUS, MeshTopology::DISK, MeshTopology::SPHERE }), + mCurve(curve), + mSampleByArcLength(sampleByArcLength) + { + if (!this->mDescription.constructed) + { + // The logger system will report these errors in the Mesh + // constructor. + mCurve = nullptr; + return; + } + + LogAssert(mCurve != nullptr, "A nonnull revolution curve is required."); + + // The four supported topologies all wrap around in the column + // direction. + mCosAngle.resize(this->mDescription.numCols + 1); + mSinAngle.resize(this->mDescription.numCols + 1); + Real invRadialSamples = (Real)1 / (Real)this->mDescription.numCols; + for (unsigned int c = 0; c < this->mDescription.numCols; ++c) + { + Real angle = c * invRadialSamples * (Real)GTE_C_TWO_PI; + mCosAngle[c] = std::cos(angle); + mSinAngle[c] = std::sin(angle); + } + mCosAngle[this->mDescription.numCols] = mCosAngle[0]; + mSinAngle[this->mDescription.numCols] = mSinAngle[0]; + + CreateSampler(); + + if (!this->mTCoords) + { + mDefaultTCoords.resize(this->mDescription.numVertices); + this->mTCoords = mDefaultTCoords.data(); + this->mTCoordStride = sizeof(Vector2); + + this->mDescription.allowUpdateFrame = this->mDescription.wantDynamicTangentSpaceUpdate; + if (this->mDescription.allowUpdateFrame) + { + if (!this->mDescription.hasTangentSpaceVectors) + { + this->mDescription.allowUpdateFrame = false; + } + + if (!this->mNormals) + { + this->mDescription.allowUpdateFrame = false; + } + } + } + + this->ComputeIndices(); + InitializeTCoords(); + UpdatePositions(); + if (this->mDescription.allowUpdateFrame) + { + this->UpdateFrame(); + } + else if (this->mNormals) + { + this->UpdateNormals(); + } + } + + // Member access. + inline std::shared_ptr> const& GetCurve() const + { + return mCurve; + } + + inline bool IsSampleByArcLength() const + { + return mSampleByArcLength; + } + + private: + void CreateSampler() + { + if (this->mDescription.topology == MeshTopology::CYLINDER + || this->mDescription.topology == MeshTopology::TORUS) + { + mSamples.resize(this->mDescription.rMax + 1); + } + else if (this->mDescription.topology == MeshTopology::DISK) + { + mSamples.resize(this->mDescription.rMax + 2); + } + else if (this->mDescription.topology == MeshTopology::SPHERE) + { + mSamples.resize(this->mDescription.rMax + 3); + } + + Real invDenom = ((Real)1) / (Real)(mSamples.size() - 1); + if (mSampleByArcLength) + { + Real factor = mCurve->GetTotalLength() * invDenom; + mTSampler = [this, factor](unsigned int i) + { + return mCurve->GetTime(i * factor); + }; + } + else + { + Real factor = (mCurve->GetTMax() - mCurve->GetTMin()) * invDenom; + mTSampler = [this, factor](unsigned int i) + { + return mCurve->GetTMin() + i * factor; + }; + } + } + + void InitializeTCoords() + { + Vector2tcoord; + + switch (this->mDescription.topology) + { + case MeshTopology::CYLINDER: + { + for (unsigned int r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + tcoord[1] = (Real)r / (Real)(this->mDescription.numRows - 1); + for (unsigned int c = 0; c <= this->mDescription.numCols; ++c, ++i) + { + tcoord[0] = (Real)c / (Real)this->mDescription.numCols; + this->TCoord(i) = tcoord; + } + } + break; + } + case MeshTopology::TORUS: + { + for (unsigned int r = 0, i = 0; r <= this->mDescription.numRows; ++r) + { + tcoord[1] = (Real)r / (Real)this->mDescription.numRows; + for (unsigned int c = 0; c <= this->mDescription.numCols; ++c, ++i) + { + tcoord[0] = (Real)c / (Real)this->mDescription.numCols; + this->TCoord(i) = tcoord; + } + } + break; + } + case MeshTopology::DISK: + { + Vector2 origin{ (Real)0.5, (Real)0.5 }; + unsigned int i = 0; + for (unsigned int r = 0; r < this->mDescription.numRows; ++r) + { + Real radius = (Real)(r + 1) / (Real)(2 * this->mDescription.numRows); + radius = std::min(radius, (Real)0.5); + for (unsigned int c = 0; c <= this->mDescription.numCols; ++c, ++i) + { + Real angle = (Real)GTE_C_TWO_PI * (Real)c / (Real)this->mDescription.numCols; + this->TCoord(i) = { radius * std::cos(angle), radius * std::sin(angle) }; + } + } + this->TCoord(i) = origin; + break; + } + case MeshTopology::SPHERE: + { + unsigned int i = 0; + for (unsigned int r = 0; r < this->mDescription.numRows; ++r) + { + tcoord[1] = (Real)r / (Real)(this->mDescription.numRows - 1); + for (unsigned int c = 0; c <= this->mDescription.numCols; ++c, ++i) + { + tcoord[0] = (Real)c / (Real)this->mDescription.numCols; + this->TCoord(i) = tcoord; + } + } + this->TCoord(i++) = { (Real)0.5, (Real)0 }; + this->TCoord(i) = { (Real)0.5, (Real)1 }; + break; + } + default: + // Invalid topology is reported by the Mesh constructor, so there is + // no need to log a message here. + break; + } + } + + virtual void UpdatePositions() override + { + unsigned int const numSamples = static_cast(mSamples.size()); + for (unsigned int i = 0; i < numSamples; ++i) + { + Real t = mTSampler(i); + Vector2 position = mCurve->GetPosition(t); + mSamples[i][0] = position[0]; + mSamples[i][1] = (Real)0; + mSamples[i][2] = position[1]; + } + + switch (this->mDescription.topology) + { + case MeshTopology::CYLINDER: + UpdateCylinderPositions(); + break; + case MeshTopology::TORUS: + UpdateTorusPositions(); + break; + case MeshTopology::DISK: + UpdateDiskPositions(); + break; + case MeshTopology::SPHERE: + UpdateSpherePositions(); + break; + default: + break; + } + } + + void UpdateCylinderPositions() + { + for (unsigned int r = 0, i = 0; r <= this->mDescription.rMax; ++r) + { + Real radius = mSamples[r][0]; + for (unsigned int c = 0; c <= this->mDescription.cMax; ++c, ++i) + { + this->Position(i) = { radius * mCosAngle[c], radius * mSinAngle[c], mSamples[r][2] }; + } + } + } + + void UpdateTorusPositions() + { + for (unsigned int r = 0, i = 0; r <= this->mDescription.rMax; ++r) + { + Real radius = mSamples[r][0]; + for (unsigned int c = 0; c <= this->mDescription.cMax; ++c, ++i) + { + this->Position(i) = { radius * mCosAngle[c], radius * mSinAngle[c], mSamples[r][2] }; + } + } + } + + void UpdateDiskPositions() + { + for (unsigned int r = 0, rp1 = 1, i = 0; r <= this->mDescription.rMax; ++r, ++rp1) + { + Real radius = mSamples[rp1][0]; + for (unsigned int c = 0; c <= this->mDescription.cMax; ++c, ++i) + { + this->Position(i) = { radius * mCosAngle[c], radius * mSinAngle[c], mSamples[rp1][2] }; + } + } + + this->Position(this->mDescription.numVertices - 1) = { (Real)0, (Real)0, mSamples.front()[2] }; + } + + void UpdateSpherePositions() + { + for (unsigned int r = 0, rp1 = 1, i = 0; r <= this->mDescription.rMax; ++r, ++rp1) + { + Real radius = mSamples[rp1][0]; + for (unsigned int c = 0; c <= this->mDescription.cMax; ++c, ++i) + { + this->Position(i) = { radius * mCosAngle[c], radius * mSinAngle[c], mSamples[rp1][2] }; + } + } + + this->Position(this->mDescription.numVertices - 2) = { (Real)0, (Real)0, mSamples.front()[2] }; + this->Position(this->mDescription.numVertices - 1) = { (Real)0, (Real)0, mSamples.back()[2] }; + } + + std::shared_ptr> mCurve; + bool mSampleByArcLength; + std::vector mCosAngle, mSinAngle; + std::function mTSampler; + std::vector> mSamples; + + // If the client does not request texture coordinates, they will be + // computed internally for use in evaluation of the surface geometry. + std::vector> mDefaultTCoords; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RiemannianGeodesic.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RiemannianGeodesic.h new file mode 100644 index 0000000..2d907d7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RiemannianGeodesic.h @@ -0,0 +1,451 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// Computing geodesics on a surface is a differential geometric topic that +// involves Riemannian geometry. The algorithm for constructing geodesics +// that is implemented here uses a multiresolution approach. A description +// of the algorithm is in the document +// https://www.geometrictools.com/Documentation/RiemannianGeodesics.pdf + +namespace WwiseGTE +{ + template + class RiemannianGeodesic + { + public: + // Construction and destruction. The input dimension must be two or + // larger. + RiemannianGeodesic(int dimension) + : + integralSamples(16), + searchSamples(32), + derivativeStep((Real)1e-04), + subdivisions(7), + refinements(8), + searchRadius((Real)1), + refineCallback([]() {}), + mDimension(dimension >= 2 ? dimension : 2), + mMetric(mDimension, mDimension), + mMetricInverse(mDimension, mDimension), + mChristoffel1(mDimension), + mChristoffel2(mDimension), + mMetricDerivative(mDimension), + mMetricInverseExists(true), + mSubdivide(0), + mRefine(0), + mCurrentQuantity(0), + mIntegralStep((Real)1 / (Real)(integralSamples - 1)), + mSearchStep((Real)1 / (Real)searchSamples), + mDerivativeFactor((Real)0.5 / derivativeStep) + { + LogAssert(dimension >= 2, "Dimension must be at least 2."); + for (int i = 0; i < mDimension; ++i) + { + mChristoffel1[i].SetSize(mDimension, mDimension); + mChristoffel2[i].SetSize(mDimension, mDimension); + mMetricDerivative[i].SetSize(mDimension, mDimension); + } + } + + virtual ~RiemannianGeodesic() + { + } + + // Tweakable parameters. + // 1. The integral samples are the number of samples used in the + // Trapezoid Rule numerical integrator. + // 2. The search samples are the number of samples taken along a ray + // for the steepest descent algorithm used to refine the vertices + // of the polyline approximation to the geodesic curve. + // 3. The derivative step is the value of h used for centered + // difference approximations df/dx = (f(x+h)-f(x-h))/(2*h) in the + // steepest descent algorithm. + // 4. The number of subdivisions indicates how many times the polyline + // segments should be subdivided. The number of polyline vertices + // will be pow(2,subdivisions)+1. + // 5. The number of refinements per subdivision. Setting this to a + // positive value appears necessary when the geodesic curve has a + // large length. + // 6. The search radius is the distance over which the steepest + // descent algorithm searches for a minimum on the line whose + // direction is the estimated gradient. The default of 1 means the + // search interval is [-L,L], where L is the length of the gradient. + // If the search radius is r, then the interval is [-r*L,r*L]. + int integralSamples; // default = 16 + int searchSamples; // default = 32 + Real derivativeStep; // default = 0.0001 + int subdivisions; // default = 7 + int refinements; // default = 8 + Real searchRadius; // default = 1.0 + + // The dimension of the manifold. + inline int GetDimension() const + { + return mDimension; + } + + // Returns the length of the line segment connecting the points. + Real ComputeSegmentLength(GVector const& point0, GVector const& point1) + { + // The Trapezoid Rule is used for integration of the length + // integral. The ComputeMetric function internally modifies + // mMetric, which means the qForm values are actually varying + // even though diff does not. + GVector diff = point1 - point0; + GVector temp(mDimension); + + // Evaluate the integrand at point0. + ComputeMetric(point0); + Real qForm = Dot(diff, mMetric * diff); + LogAssert(qForm > (Real)0, "Unexpected condition."); + Real length = std::sqrt(qForm); + + // Evaluate the integrand at point1. + ComputeMetric(point1); + qForm = Dot(diff, mMetric * diff); + LogAssert(qForm > (Real)0, "Unexpected condition."); + length += std::sqrt(qForm); + length *= (Real)0.5; + + int imax = integralSamples - 2; + for (int i = 1; i <= imax; ++i) + { + // Evaluate the integrand at point0+t*(point1-point0). + Real t = mIntegralStep * static_cast(i); + temp = point0 + t * diff; + ComputeMetric(temp); + qForm = Dot(diff, mMetric * diff); + LogAssert(qForm > (Real)0, "Unexpected condition."); + length += std::sqrt(qForm); + } + length *= mIntegralStep; + return length; + } + + // Compute the total length of the polyline. The lengths of the + // segments are computed relative to the metric tensor. + Real ComputeTotalLength(int quantity, std::vector> const& path) + { + LogAssert(quantity >= 2, "Path must have at least two points."); + Real length = ComputeSegmentLength(path[0], path[1]); + for (int i = 1; i <= quantity - 2; ++i) + { + length += ComputeSegmentLength(path[i], path[i + 1]); + } + return length; + } + + // Returns a polyline approximation to a geodesic curve connecting the + // points. + void ComputeGeodesic(GVector const& end0, GVector const& end1, + int& quantity, std::vector>& path) + { + LogAssert(subdivisions < 32, "Exceeds maximum iterations."); + quantity = (1 << subdivisions) + 1; + path.resize(quantity); + for (int i = 0; i < quantity; ++i) + { + path[i].SetSize(mDimension); + } + + mCurrentQuantity = 2; + path[0] = end0; + path[1] = end1; + + for (mSubdivide = 1; mSubdivide <= subdivisions; ++mSubdivide) + { + // A subdivision essentially doubles the number of points. + int newQuantity = 2 * mCurrentQuantity - 1; + LogAssert(newQuantity <= quantity, "Unexpected condition."); + + // Copy the old points so that there are slots for the + // midpoints during the subdivision, the slots interleaved + // between the old points. + for (int i = mCurrentQuantity - 1; i > 0; --i) + { + path[2 * i] = path[i]; + } + + // Subdivide the polyline. + for (int i = 0; i <= mCurrentQuantity - 2; ++i) + { + Subdivide(path[2 * i], path[2 * i + 1], path[2 * i + 2]); + } + + mCurrentQuantity = newQuantity; + + // Refine the current polyline vertices. + for (mRefine = 1; mRefine <= refinements; ++mRefine) + { + for (int i = 1; i <= mCurrentQuantity - 2; ++i) + { + Refine(path[i - 1], path[i], path[i + 1]); + } + } + } + + LogAssert(mCurrentQuantity == quantity, "Unexpected condition."); + mSubdivide = 0; + mRefine = 0; + mCurrentQuantity = 0; + } + + // Start with the midpoint M of the line segment (E0,E1) and use a + // steepest descent algorithm to move M so that + // Length(E0,M) + Length(M,E1) < Length(E0,E1) + // This is essentially a relaxation scheme that inserts points into + // the current polyline approximation to the geodesic curve. + bool Subdivide(GVector const& end0, GVector& mid, GVector const& end1) + { + mid = (Real)0.5 * (end0 + end1); + auto save = refineCallback; + refineCallback = []() {}; + bool changed = Refine(end0, mid, end1); + refineCallback = save; + return changed; + } + + // Apply the steepest descent algorithm to move the midpoint M of the + // line segment (E0,E1) so that + // Length(E0,M) + Length(M,E1) < Length(E0,E1) + // This is essentially a relaxation scheme that inserts points into + // the current polyline approximation to the geodesic curve. + bool Refine(GVector const& end0, GVector& mid, GVector const& end1) + { + // Estimate the gradient vector for the function + // F(m) = Length(e0,m) + Length(m,e1). + GVector temp = mid; + GVector gradient(mDimension); + for (int i = 0; i < mDimension; ++i) + { + temp[i] = mid[i] + derivativeStep; + gradient[i] = ComputeSegmentLength(end0, temp); + gradient[i] += ComputeSegmentLength(temp, end1); + + temp[i] = mid[i] - derivativeStep; + gradient[i] -= ComputeSegmentLength(end0, temp); + gradient[i] -= ComputeSegmentLength(temp, end1); + + temp[i] = mid[i]; + gradient[i] *= mDerivativeFactor; + } + + // Compute the length sum for the current midpoint. + Real length0 = ComputeSegmentLength(end0, mid); + Real length1 = ComputeSegmentLength(mid, end1); + Real oldLength = length0 + length1; + + Real tRay, newLength; + GVector pRay(mDimension); + + Real multiplier = mSearchStep * searchRadius; + Real minLength = oldLength; + GVector minPoint = mid; + for (int i = -searchSamples; i <= searchSamples; ++i) + { + tRay = multiplier * static_cast(i); + pRay = mid - tRay * gradient; + length0 = ComputeSegmentLength(end0, pRay); + length1 = ComputeSegmentLength(end1, pRay); + newLength = length0 + length1; + if (newLength < minLength) + { + minLength = newLength; + minPoint = pRay; + } + } + + mid = minPoint; + refineCallback(); + return minLength < oldLength; + } + + // A callback that is executed during each call of Refine. + std::function refineCallback; + + // Information to be used during the callback. + inline int GetSubdivisionStep() const + { + return mSubdivide; + } + + inline int GetRefinementStep() const + { + return mRefine; + } + + inline int GetCurrentQuantity() const + { + return mCurrentQuantity; + } + + // Curvature computations to measure how close the approximating + // polyline is to a geodesic. + + // Returns the total curvature of the line segment connecting the + // points. + Real ComputeSegmentCurvature(GVector const& point0, GVector const& point1) + { + // The Trapezoid Rule is used for integration of the curvature + // integral. The ComputeIntegrand function internally modifies + // mMetric, which means the curvature values are actually varying + // even though diff does not. + GVector diff = point1 - point0; + GVector temp(mDimension); + + // Evaluate the integrand at point0. + Real curvature = ComputeIntegrand(point0, diff); + + // Evaluate the integrand at point1. + curvature += ComputeIntegrand(point1, diff); + curvature *= (Real)0.5; + + int imax = integralSamples - 2; + for (int i = 1; i <= imax; ++i) + { + // Evaluate the integrand at point0+t*(point1-point0). + Real t = mIntegralStep * static_cast(i); + temp = point0 + t * diff; + curvature += ComputeIntegrand(temp, diff); + } + curvature *= mIntegralStep; + return curvature; + } + + // Compute the total curvature of the polyline. The curvatures of the + // segments are computed relative to the metric tensor. + Real ComputeTotalCurvature(int quantity, std::vector> const& path) + { + LogAssert(quantity >= 2, "Path must have at least two points."); + Real curvature = ComputeSegmentCurvature(path[0], path[1]); + for (int i = 1; i <= quantity - 2; ++i) + { + curvature += ComputeSegmentCurvature(path[i], path[i + 1]); + } + return curvature; + } + + protected: + // Support for ComputeSegmentCurvature. + Real ComputeIntegrand(GVector const& pos, GVector const& der) + { + ComputeMetric(pos); + ComputeChristoffel1(pos); + ComputeMetricInverse(); + ComputeChristoffel2(); + + // g_{ij}*der_{i}*der_{j} + Real qForm0 = Dot(der, mMetric * der); + LogAssert(qForm0 > (Real)0, "Unexpected condition."); + + // gamma_{kij}*der_{k}*der_{i}*der_{j} + GMatrix mat(mDimension, mDimension); + for (int k = 0; k < mDimension; ++k) + { + mat += der[k] * mChristoffel1[k]; + } + // This product can be negative because mat is not guaranteed to + // be positive semidefinite. No assertion is added. + Real qForm1 = Dot(der, mat * der); + + Real ratio = -qForm1 / qForm0; + + // Compute the acceleration. + GVector acc = ratio * der; + for (int k = 0; k < mDimension; ++k) + { + acc[k] += Dot(der, mChristoffel2[k] * der); + } + + // Compute the curvature. + Real curvature = std::sqrt(Dot(acc, mMetric * acc)); + return curvature; + } + + // Compute the metric tensor for the specified point. Derived classes + // are responsible for implementing this function. + virtual void ComputeMetric(GVector const& point) = 0; + + // Compute the Christoffel symbols of the first kind for the current + // point. Derived classes are responsible for implementing this + // function. + virtual void ComputeChristoffel1(GVector const& point) = 0; + + // Compute the inverse of the current metric tensor. The function + // returns 'true' iff the inverse exists. + bool ComputeMetricInverse() + { + mMetricInverse = Inverse(mMetric, &mMetricInverseExists); + return mMetricInverseExists; + } + + // Compute the derivative of the metric tensor for the current state. + // This is a triply indexed quantity, the values computed using the + // Christoffel symbols of the first kind. + void ComputeMetricDerivative() + { + for (int derivative = 0; derivative < mDimension; ++derivative) + { + for (int i0 = 0; i0 < mDimension; ++i0) + { + for (int i1 = 0; i1 < mDimension; ++i1) + { + mMetricDerivative[derivative](i0, i1) = + mChristoffel1[derivative](i0, i1) + + mChristoffel1[derivative](i1, i0); + } + } + } + } + + // Compute the Christoffel symbols of the second kind for the current + // state. The values depend on the inverse of the metric tensor, so + // they may be computed only when the inverse exists. The function + // returns 'true' whenever the inverse metric tensor exists. + bool ComputeChristoffel2() + { + for (int i2 = 0; i2 < mDimension; ++i2) + { + for (int i0 = 0; i0 < mDimension; ++i0) + { + for (int i1 = 0; i1 < mDimension; ++i1) + { + Real fValue = (Real)0; + for (int j = 0; j < mDimension; ++j) + { + fValue += mMetricInverse(i2, j) * mChristoffel1[j](i0, i1); + } + mChristoffel2[i2](i0, i1) = fValue; + } + } + } + return mMetricInverseExists; + } + + int mDimension; + GMatrix mMetric; + GMatrix mMetricInverse; + std::vector> mChristoffel1; + std::vector> mChristoffel2; + std::vector> mMetricDerivative; + bool mMetricInverseExists; + + // Progress parameters that are useful to mRefineCallback. + int mSubdivide, mRefine, mCurrentQuantity; + + // Derived tweaking parameters. + Real mIntegralStep; // = 1/(mIntegralQuantity-1) + Real mSearchStep; // = 1/mSearchQuantity + Real mDerivativeFactor; // = 1/(2*mDerivativeStep) + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RigidBody.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RigidBody.h new file mode 100644 index 0000000..a32df1c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RigidBody.h @@ -0,0 +1,348 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class RigidBody + { + public: + // Construction and destruction. The rigid body state is + // uninitialized. Use the set functions to initialize the state + // before starting the simulation. + virtual ~RigidBody() = default; + + RigidBody() + : + mMass(std::numeric_limits::max()), + mInvMass((Real)0), + mInertia(Matrix3x3::Identity()), + mInvInertia(Matrix3x3::Zero()), + mPosition(Vector3::Zero()), + mQuatOrient(Quaternion::Identity()), + mLinearMomentum(Vector3::Zero()), + mAngularMomentum(Vector3::Zero()), + mRotOrient(Matrix3x3::Identity()), + mLinearVelocity(Vector3::Zero()), + mAngularVelocity(Vector3::Zero()) + { + // The default body is immovable. + } + + // Set rigid body state. + void SetMass(float mass) + { + if ((Real)0 < mass && mass < std::numeric_limits::max()) + { + mMass = mass; + mInvMass = (Real)1 / mass; + } + else + { + // Assume the body as immovable. + mMass = std::numeric_limits::max(); + mInvMass = (Real)0; + mInertia = Matrix3x3::Identity(); + mInvInertia = Matrix3x3::Zero(); + mQuatOrient = Quaternion::Identity(); + mLinearMomentum = Vector3::Zero(); + mAngularMomentum = Vector3::Zero(); + mRotOrient = Matrix3x3::Identity(); + mLinearVelocity = Vector3::Zero(); + mAngularVelocity = Vector3::Zero(); + } + } + + void SetBodyInertia(Matrix3x3 const& inertia) + { + mInertia = inertia; + mInvInertia = Inverse(mInertia); + } + + inline void SetPosition(Vector3 const& position) + { + mPosition = position; + } + + void SetQOrientation(Quaternion const& quatOrient) + { + mQuatOrient = quatOrient; + mRotOrient = Rotation<3, Real>(mQuatOrient); + } + + void SetLinearMomentum(Vector3 const& linearMomentum) + { + mLinearMomentum = linearMomentum; + mLinearVelocity = mInvMass * mLinearMomentum; + } + + void SetAngularMomentum(Vector3 const& angularMomentum) + { + mAngularMomentum = angularMomentum; + + // V = R^T*M + mAngularVelocity = mAngularMomentum * mRotOrient; + + // V = J^{-1}*R^T*M + mAngularVelocity = mInvInertia * mAngularVelocity; + + // V = R*J^{-1}*R^T*M + mAngularVelocity = mRotOrient * mAngularVelocity; + } + + void SetROrientation(Matrix3x3 const& rotOrient) + { + mRotOrient = rotOrient; + mQuatOrient = Rotation<3, Real>(mRotOrient); + } + + void SetLinearVelocity(Vector3 const& linearVelocity) + { + mLinearVelocity = linearVelocity; + mLinearMomentum = mMass * mLinearVelocity; + } + + void SetAngularVelocity(Vector3 const& angularVelocity) + { + mAngularVelocity = angularVelocity; + + // M = R^T*V + mAngularMomentum = mAngularVelocity * mRotOrient; + + // M = J*R^T*V + mAngularMomentum = mInertia * mAngularMomentum; + + // M = R*J*R^T*V + mAngularMomentum = mRotOrient * mAngularMomentum; + } + + // Get rigid body state. + inline Real GetMass() const + { + return mMass; + } + + inline Real GetInverseMass() const + { + return mInvMass; + } + + inline Matrix3x3 const& GetBodyInertia() const + { + return mInertia; + } + + inline Matrix3x3 const& GetBodyInverseInertia() const + { + return mInvInertia; + } + + Matrix3x3 GetWorldInertia() const + { + return MultiplyABT(mRotOrient * mInertia, mRotOrient); // R*J*R^T + } + + Matrix3x3 GetWorldInverseInertia() const + { + // R*J^{-1}*R^T + return MultiplyABT(mRotOrient * mInvInertia, mRotOrient); + } + + inline Vector3 const& GetPosition() const + { + return mPosition; + } + + Quaternion const& GetQOrientation() const + { + return mQuatOrient; + } + + inline Vector3 const& GetLinearMomentum() const + { + return mLinearMomentum; + } + + inline Vector3 const& GetAngularMomentum() const + { + return mAngularMomentum; + } + + inline Matrix3x3 const& GetROrientation() const + { + return mRotOrient; + } + + inline Vector3 const& GetLinearVelocity() const + { + return mLinearVelocity; + } + + inline Vector3 const& GetAngularVelocity() const + { + return mAngularVelocity; + } + + // Force/torque function format. + typedef std::function + < + Vector3 + ( + Real, // time of application + Real, // mass + Vector3 const&, // position + Quaternion const&, // orientation + Vector3 const&, // linear momentum + Vector3 const&, // angular momentum + Matrix3x3 const&, // orientation + Vector3 const&, // linear velocity + Vector3 const& // angular velocity + ) + > + Function; + + // Force and torque functions. + Function mForce; + Function mTorque; + + // Runge-Kutta fourth-order differential equation solver + void Update(Real t, Real dt) + { + // TODO: When GTE_MAT_VEC is not defined (i.e. use vec-mat), + // test to see whether dq/dt = 0.5 * w * q (mat-vec convention) + // needs to become a different equation. + Real halfDT = (Real)0.5 * dt; + Real sixthDT = dt / (Real)6; + Real TpHalfDT = t + halfDT; + Real TpDT = t + dt; + + Vector3 newPosition, newLinearMomentum, newAngularMomentum; + Vector3 newLinearVelocity, newAngularVelocity; + Quaternion newQuatOrient; + Matrix3x3 newRotOrient; + + // A1 = G(T,S0), B1 = S0 + (DT/2)*A1 + Vector3 A1DXDT = mLinearVelocity; + Quaternion W = Quaternion(mAngularVelocity[0], + mAngularVelocity[1], mAngularVelocity[2], (Real)0); + Quaternion A1DQDT = (Real)0.5 * W * mQuatOrient; + + Vector3 A1DPDT = mForce(t, mMass, mPosition, mQuatOrient, + mLinearMomentum, mAngularMomentum, mRotOrient, mLinearVelocity, + mAngularVelocity); + + Vector3 A1DLDT = mTorque(t, mMass, mPosition, mQuatOrient, + mLinearMomentum, mAngularMomentum, mRotOrient, mLinearVelocity, + mAngularVelocity); + + newPosition = mPosition + halfDT * A1DXDT; + newQuatOrient = mQuatOrient + halfDT * A1DQDT; + newLinearMomentum = mLinearMomentum + halfDT * A1DPDT; + newAngularMomentum = mAngularMomentum + halfDT * A1DLDT; + newRotOrient = Rotation<3, Real>(newQuatOrient); + newLinearVelocity = mInvMass * newLinearMomentum; + newAngularVelocity = newAngularMomentum * newRotOrient; + newAngularVelocity = mInvInertia * newAngularVelocity; + newAngularVelocity = newRotOrient * newAngularVelocity; + + // A2 = G(T+DT/2,B1), B2 = S0 + (DT/2)*A2 + Vector3 A2DXDT = newLinearVelocity; + W = Quaternion(newAngularVelocity[0], newAngularVelocity[1], + newAngularVelocity[2], (Real)0); + Quaternion A2DQDT = (Real)0.5 * W * newQuatOrient; + + Vector3 A2DPDT = mForce(TpHalfDT, mMass, newPosition, + newQuatOrient, newLinearMomentum, newAngularMomentum, newRotOrient, + newLinearVelocity, newAngularVelocity); + + Vector3 A2DLDT = mTorque(TpHalfDT, mMass, newPosition, + newQuatOrient, newLinearMomentum, newAngularMomentum, newRotOrient, + newLinearVelocity, newAngularVelocity); + + newPosition = mPosition + halfDT * A2DXDT; + newQuatOrient = mQuatOrient + halfDT * A2DQDT; + newLinearMomentum = mLinearMomentum + halfDT * A2DPDT; + newAngularMomentum = mAngularMomentum + halfDT * A2DLDT; + newRotOrient = Rotation<3, Real>(newQuatOrient); + newLinearVelocity = mInvMass * newLinearMomentum; + newAngularVelocity = newAngularMomentum * newRotOrient; + newAngularVelocity = mInvInertia * newAngularVelocity; + newAngularVelocity = newRotOrient * newAngularVelocity; + + // A3 = G(T+DT/2,B2), B3 = S0 + DT*A3 + Vector3 A3DXDT = newLinearVelocity; + W = Quaternion(newAngularVelocity[0], newAngularVelocity[1], + newAngularVelocity[2], (Real)0); + Quaternion A3DQDT = (Real)0.5 * W * newQuatOrient; + + Vector3 A3DPDT = mForce(TpHalfDT, mMass, newPosition, + newQuatOrient, newLinearMomentum, newAngularMomentum, newRotOrient, + newLinearVelocity, newAngularVelocity); + + Vector3 A3DLDT = mTorque(TpHalfDT, mMass, newPosition, + newQuatOrient, newLinearMomentum, newAngularMomentum, newRotOrient, + newLinearVelocity, newAngularVelocity); + + newPosition = mPosition + dt * A3DXDT; + newQuatOrient = mQuatOrient + dt * A3DQDT; + newLinearMomentum = mLinearMomentum + dt * A3DPDT; + newAngularMomentum = mAngularMomentum + dt * A3DLDT; + newRotOrient = Rotation<3, Real>(newQuatOrient); + newLinearVelocity = mInvMass * newLinearMomentum; + newAngularVelocity = newAngularMomentum * newRotOrient; + newAngularVelocity = mInvInertia * newAngularVelocity; + newAngularVelocity = newRotOrient * newAngularVelocity; + + // A4 = G(T+DT,B3), S1 = S0 + (DT/6)*(A1+2*(A2+A3)+A4) + Vector3 A4DXDT = newLinearVelocity; + W = Quaternion(newAngularVelocity[0], newAngularVelocity[1], + newAngularVelocity[2], (Real)0); + Quaternion A4DQDT = (Real)0.5 * W * newQuatOrient; + + Vector3 A4DPDT = mForce(TpDT, mMass, newPosition, + newQuatOrient, newLinearMomentum, newAngularMomentum, newRotOrient, + newLinearVelocity, newAngularVelocity); + + Vector3 A4DLDT = mTorque(TpDT, mMass, newPosition, newQuatOrient, + newLinearMomentum, newAngularMomentum, newRotOrient, + newLinearVelocity, newAngularVelocity); + + mPosition += sixthDT * (A1DXDT + (Real)2 * (A2DXDT + A3DXDT) + A4DXDT); + mQuatOrient += sixthDT * (A1DQDT + (Real)2 * (A2DQDT + A3DQDT) + A4DQDT); + mLinearMomentum += sixthDT * (A1DPDT + (Real)2 * (A2DPDT + A3DPDT) + A4DPDT); + mAngularMomentum += sixthDT * (A1DLDT + (Real)2 * (A2DLDT + A3DLDT) + A4DLDT); + + mRotOrient = Rotation<3, Real>(mQuatOrient); + mLinearVelocity = mInvMass * mLinearMomentum; + mAngularVelocity = mAngularMomentum * mRotOrient; + mAngularVelocity = mInvInertia * mAngularVelocity; + mAngularVelocity = mRotOrient * mAngularVelocity; + } + + protected: + // Constant quantities (matrices in body coordinates). + Real mMass, mInvMass; + Matrix3x3 mInertia, mInvInertia; + + // State variables. + Vector3 mPosition; + Quaternion mQuatOrient; + Vector3 mLinearMomentum; + Vector3 mAngularMomentum; + + // Derived state variables. + Matrix3x3 mRotOrient; + Vector3 mLinearVelocity; + Vector3 mAngularVelocity; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RootsBisection.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RootsBisection.h new file mode 100644 index 0000000..258e81e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RootsBisection.h @@ -0,0 +1,169 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// Compute a root of a function F(t) on an interval [t0, t1]. The caller +// specifies the maximum number of iterations, in case you want limited +// accuracy for the root. However, the function is designed for native types +// (Real = float/double). If you specify a sufficiently large number of +// iterations, the root finder bisects until either F(t) is identically zero +// [a condition dependent on how you structure F(t) for evaluation] or the +// midpoint (t0 + t1)/2 rounds numerically to tmin or tmax. Of course, it +// is required that t0 < t1. The return value of Find is: +// 0: F(t0)*F(t1) > 0, we cannot determine a root +// 1: F(t0) = 0 or F(t1) = 0 +// 2..maxIterations: the number of bisections plus one +// maxIterations+1: the loop executed without a break (no convergence) + +namespace WwiseGTE +{ + template + class RootsBisection + { + public: + // Use this function when F(t0) and F(t1) are not already known. + static unsigned int Find(std::function const& F, Real t0, + Real t1, unsigned int maxIterations, Real& root) + { + // Set 'root' initially to avoid "potentially uninitialized + // variable" warnings by a compiler. + root = t0; + + if (t0 < t1) + { + // Test the endpoints to see whether F(t) is zero. + Real f0 = F(t0); + if (f0 == (Real)0) + { + root = t0; + return 1; + } + + Real f1 = F(t1); + if (f1 == (Real)0) + { + root = t1; + return 1; + } + + if (f0 * f1 > (Real)0) + { + // It is not known whether the interval bounds a root. + return 0; + } + + unsigned int i; + for (i = 2; i <= maxIterations; ++i) + { + root = (Real)0.5 * (t0 + t1); + if (root == t0 || root == t1) + { + // The numbers t0 and t1 are consecutive + // floating-point numbers. + break; + } + + Real fm = F(root); + Real product = fm * f0; + if (product < (Real)0) + { + t1 = root; + f1 = fm; + } + else if (product > (Real)0) + { + t0 = root; + f0 = fm; + } + else + { + break; + } + } + return i; + } + else + { + // The interval endpoints are invalid. + return 0; + } + } + + // If f0 = F(t0) and f1 = F(t1) are already known, pass them to the + // bisector. This is useful when |f0| or |f1| is infinite, and you + // can pass sign(f0) or sign(f1) rather than then infinity because + // the bisector cares only about the signs of f. + static unsigned int Find(std::function const& F, Real t0, + Real t1, Real f0, Real f1, unsigned int maxIterations, Real& root) + { + // Set 'root' initially to avoid "potentially uninitialized + // variable" warnings by a compiler. + root = t0; + + if (t0 < t1) + { + // Test the endpoints to see whether F(t) is zero. + if (f0 == (Real)0) + { + root = t0; + return 1; + } + + if (f1 == (Real)0) + { + root = t1; + return 1; + } + + if (f0 * f1 > (Real)0) + { + // It is not known whether the interval bounds a root. + return 0; + } + + unsigned int i; + root = t0; + for (i = 2; i <= maxIterations; ++i) + { + root = (Real)0.5 * (t0 + t1); + if (root == t0 || root == t1) + { + // The numbers t0 and t1 are consecutive + // floating-point numbers. + break; + } + + Real fm = F(root); + Real product = fm * f0; + if (product < (Real)0) + { + t1 = root; + f1 = fm; + } + else if (product > (Real)0) + { + t0 = root; + f0 = fm; + } + else + { + break; + } + } + return i; + } + else + { + // The interval endpoints are invalid. + return 0; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RootsBrentsMethod.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RootsBrentsMethod.h new file mode 100644 index 0000000..04cfc11 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RootsBrentsMethod.h @@ -0,0 +1,225 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// This is an implementation of Brent's Method for computing a root of a +// function on an interval [t0,t1] for which F(t0)*F(t1) < 0. The method +// uses inverse quadratic interpolation to generate a root estimate but +// falls back to inverse linear interpolation (secant method) if +// necessary. Moreover, based on previous iterates, the method will fall +// back to bisection when it appears the interpolated estimate is not of +// sufficient quality. +// +// maxIterations: +// The maximum number of iterations used to locate a root. This +// should be positive. +// negFTolerance, posFTolerance: +// The root estimate t is accepted when the function value F(t) +// satisfies negFTolerance <= F(t) <= posFTolerance. The values +// must satisfy: negFTolerance <= 0, posFTolerance >= 0. +// stepTTolerance: +// Brent's Method requires additional tests before an interpolated +// t-value is accepted as the next root estimate. One of these +// tests compares the difference of consecutive iterates and +// requires it to be larger than a user-specified t-tolerance (to +// ensure progress is made). This parameter is that tolerance +// and should be nonnegative. +// convTTolerance: +// The root search is allowed to terminate when the current +// subinterval [tsub0,tsub1] is sufficiently small, say, +// |tsub1 - tsub0| <= tolerance. This parameter is that tolerance +// and should be nonnegative. + +namespace WwiseGTE +{ + template + class RootsBrentsMethod + { + public: + // It is necessary that F(t0)*F(t1) <= 0, in which case the function + // returns 'true' and the 'root' is valid; otherwise, the function + // returns 'false' and 'root' is invalid (do not use it). When + // F(t0)*F(t1) > 0, the interval may very well contain a root but we + // cannot know that. The function also returns 'false' if t0 >= t1. + + static bool Find(std::function const& F, Real t0, Real t1, + unsigned int maxIterations, Real negFTolerance, Real posFTolerance, + Real stepTTolerance, Real convTTolerance, Real& root) + { + // Parameter validation. + if (t1 <= t0 + || maxIterations == 0 + || negFTolerance > (Real)0 + || posFTolerance < (Real)0 + || stepTTolerance < (Real)0 + || convTTolerance < (Real)0) + { + // The input is invalid. + return false; + } + + Real f0 = F(t0); + if (negFTolerance <= f0 && f0 <= posFTolerance) + { + // This endpoint is an approximate root that satisfies the + // function tolerance. + root = t0; + return true; + } + + Real f1 = F(t1); + if (negFTolerance <= f1 && f1 <= posFTolerance) + { + // This endpoint is an approximate root that satisfies the + // function tolerance. + root = t1; + return true; + } + + if (f0 * f1 > (Real)0) + { + // The input interval must bound a root. + return false; + } + + if (std::fabs(f0) < std::fabs(f1)) + { + // Swap t0 and t1 so that |F(t1)| <= |F(t0)|. The number t1 + // is considered to be the best estimate of the root. + std::swap(t0, t1); + std::swap(f0, f1); + } + + // Initialize values for the root search. + Real t2 = t0, t3 = t0, f2 = f0; + bool prevBisected = true; + + // The root search. + for (unsigned int i = 0; i < maxIterations; ++i) + { + Real fDiff01 = f0 - f1, fDiff02 = f0 - f2, fDiff12 = f1 - f2; + Real invFDiff01 = ((Real)1) / fDiff01; + Real s; + if (fDiff02 != (Real)0 && fDiff12 != (Real)0) + { + // Use inverse quadratic interpolation. + Real infFDiff02 = ((Real)1) / fDiff02; + Real invFDiff12 = ((Real)1) / fDiff12; + s = + t0 * f1 * f2 * invFDiff01 * infFDiff02 - + t1 * f0 * f2 * invFDiff01 * invFDiff12 + + t2 * f0 * f1 * infFDiff02 * invFDiff12; + } + else + { + // Use inverse linear interpolation (secant method). + s = (t1 * f0 - t0 * f1) * invFDiff01; + } + + // Compute values need in the accept-or-reject tests. + Real tDiffSAvr = s - ((Real)0.75) * t0 - ((Real)0.25) * t1; + Real tDiffS1 = s - t1; + Real absTDiffS1 = std::fabs(tDiffS1); + Real absTDiff12 = std::fabs(t1 - t2); + Real absTDiff23 = std::fabs(t2 - t3); + + bool currBisected = false; + if (tDiffSAvr * tDiffS1 > (Real)0) + { + // The value s is not between 0.75*t0 + 0.25*t1 and t1. + // NOTE: The algorithm sometimes has t0 < t1 but sometimes + // t1 < t0, so the between-ness test does not use simple + // comparisons. + currBisected = true; + } + else if (prevBisected) + { + // The first of Brent's tests to determine whether to + // accept the interpolated s-value. + currBisected = + (absTDiffS1 >= ((Real)0.5) * absTDiff12) || + (absTDiff12 <= stepTTolerance); + } + else + { + // The second of Brent's tests to determine whether to + // accept the interpolated s-value. + currBisected = + (absTDiffS1 >= ((Real)0.5) * absTDiff23) || + (absTDiff23 <= stepTTolerance); + } + + if (currBisected) + { + // One of the additional tests failed, so reject the + // interpolated s-value and use bisection instead. + s = ((Real)0.5) * (t0 + t1); + if (s == t0 || s == t1) + { + // The numbers t0 and t1 are consecutive + // floating-point numbers. + root = s; + return true; + } + prevBisected = true; + } + else + { + prevBisected = false; + } + + // Evaluate the function at the new estimate and test for + // convergence. + Real fs = F(s); + if (negFTolerance <= fs && fs <= posFTolerance) + { + root = s; + return true; + } + + // Update the subinterval to include the new estimate as an + // endpoint. + t3 = t2; + t2 = t1; + f2 = f1; + if (f0 * fs < (Real)0) + { + t1 = s; + f1 = fs; + } + else + { + t0 = s; + f0 = fs; + } + + // Allow the algorithm to terminate when the subinterval is + // sufficiently small. + if (std::fabs(t1 - t0) <= convTTolerance) + { + root = t1; + return true; + } + + // A loop invariant is that t1 is the root estimate, + // F(t0)*F(t1) < 0 and |F(t1)| <= |F(t0)|. + if (std::fabs(f0) < std::fabs(f1)) + { + std::swap(t0, t1); + std::swap(f0, f1); + } + } + + // Failed to converge in the specified number of iterations. + return false; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RootsPolynomial.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RootsPolynomial.h new file mode 100644 index 0000000..38731ac --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/RootsPolynomial.h @@ -0,0 +1,1068 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.12.05 + +#pragma once + +#include +#include +#include +#include + +// The Find functions return the number of roots, if any, and this number +// of elements of the outputs are valid. If the polynomial is identically +// zero, Find returns 1. +// +// Some root-bounding algorithms for real-valued roots are mentioned next for +// the polynomial p(t) = c[0] + c[1]*t + ... + c[d-1]*t^{d-1} + c[d]*t^d. +// +// 1. The roots must be contained by the interval [-M,M] where +// M = 1 + max{|c[0]|, ..., |c[d-1]|}/|c[d]| >= 1 +// is called the Cauchy bound. +// +// 2. You may search for roots in the interval [-1,1]. Define +// q(t) = t^d*p(1/t) = c[0]*t^d + c[1]*t^{d-1} + ... + c[d-1]*t + c[d] +// The roots of p(t) not in [-1,1] are the roots of q(t) in [-1,1]. +// +// 3. Between two consecutive roots of the derivative p'(t), say, r0 < r1, +// the function p(t) is strictly monotonic on the open interval (r0,r1). +// If additionally, p(r0) * p(r1) <= 0, then p(x) has a unique root on +// the closed interval [r0,r1]. Thus, one can compute the derivatives +// through order d for p(t), find roots for the derivative of order k+1, +// then use these to bound roots for the derivative of order k. +// +// 4. Sturm sequences of polynomials may be used to determine bounds on the +// roots. This is a more sophisticated approach to root bounding than item 3. +// Moreover, a Sturm sequence allows you to compute the number of real-valued +// roots on a specified interval. +// +// 5. For the low-degree Solve* functions, see +// https://www.geometrictools.com/Documentation/LowDegreePolynomialRoots.pdf + +// FOR INTERNAL USE ONLY (unit testing). Do not define the symbol +// GTE_ROOTS_LOW_DEGREE_UNIT_TEST in your own code. +#if defined(GTE_ROOTS_LOW_DEGREE_UNIT_TEST) +extern void RootsLowDegreeBlock(int); +#define GTE_ROOTS_LOW_DEGREE_BLOCK(block) RootsLowDegreeBlock(block) +#else +#define GTE_ROOTS_LOW_DEGREE_BLOCK(block) +#endif + +namespace WwiseGTE +{ + template + class RootsPolynomial + { + public: + // Low-degree root finders. These use exact rational arithmetic for + // theoretically correct root classification. The roots themselves + // are computed with mixed types (rational and floating-point + // arithmetic). The Rational type must support rational arithmetic + // (+, -, *, /); for example, BSRational suffices. The + // Rational class must have single-input constructors where the input + // is type Real. This ensures you can call the Solve* functions with + // floating-point inputs; they will be converted to Rational + // implicitly. The highest-order coefficients must be nonzero + // (p2 != 0 for quadratic, p3 != 0 for cubic, and p4 != 0 for + // quartic). + + template + static void SolveQuadratic(Rational const& p0, Rational const& p1, + Rational const& p2, std::map& rmMap) + { + Rational const rat2 = 2; + Rational q0 = p0 / p2; + Rational q1 = p1 / p2; + Rational q1half = q1 / rat2; + Rational c0 = q0 - q1half * q1half; + + std::map rmLocalMap; + SolveDepressedQuadratic(c0, rmLocalMap); + + rmMap.clear(); + for (auto& rm : rmLocalMap) + { + Rational root = rm.first - q1half; + rmMap.insert(std::make_pair((Real)root, rm.second)); + } + } + + template + static void SolveCubic(Rational const& p0, Rational const& p1, + Rational const& p2, Rational const& p3, std::map& rmMap) + { + Rational const rat2 = 2, rat3 = 3; + Rational q0 = p0 / p3; + Rational q1 = p1 / p3; + Rational q2 = p2 / p3; + Rational q2third = q2 / rat3; + Rational c0 = q0 - q2third * (q1 - rat2 * q2third * q2third); + Rational c1 = q1 - q2 * q2third; + + std::map rmLocalMap; + SolveDepressedCubic(c0, c1, rmLocalMap); + + rmMap.clear(); + for (auto& rm : rmLocalMap) + { + Rational root = rm.first - q2third; + rmMap.insert(std::make_pair((Real)root, rm.second)); + } + } + + template + static void SolveQuartic(Rational const& p0, Rational const& p1, + Rational const& p2, Rational const& p3, Rational const& p4, + std::map& rmMap) + { + Rational const rat2 = 2, rat3 = 3, rat4 = 4, rat6 = 6; + Rational q0 = p0 / p4; + Rational q1 = p1 / p4; + Rational q2 = p2 / p4; + Rational q3 = p3 / p4; + Rational q3fourth = q3 / rat4; + Rational q3fourthSqr = q3fourth * q3fourth; + Rational c0 = q0 - q3fourth * (q1 - q3fourth * (q2 - q3fourthSqr * rat3)); + Rational c1 = q1 - rat2 * q3fourth * (q2 - rat4 * q3fourthSqr); + Rational c2 = q2 - rat6 * q3fourthSqr; + + std::map rmLocalMap; + SolveDepressedQuartic(c0, c1, c2, rmLocalMap); + + rmMap.clear(); + for (auto& rm : rmLocalMap) + { + Rational root = rm.first - q3fourth; + rmMap.insert(std::make_pair((Real)root, rm.second)); + } + } + + // Return only the number of real-valued roots and their + // multiplicities. info.size() is the number of real-valued roots + // and info[i] is the multiplicity of root corresponding to index i. + template + static void GetRootInfoQuadratic(Rational const& p0, Rational const& p1, + Rational const& p2, std::vector& info) + { + Rational const rat2 = 2; + Rational q0 = p0 / p2; + Rational q1 = p1 / p2; + Rational q1half = q1 / rat2; + Rational c0 = q0 - q1half * q1half; + + info.clear(); + info.reserve(2); + GetRootInfoDepressedQuadratic(c0, info); + } + + template + static void GetRootInfoCubic(Rational const& p0, Rational const& p1, + Rational const& p2, Rational const& p3, std::vector& info) + { + Rational const rat2 = 2, rat3 = 3; + Rational q0 = p0 / p3; + Rational q1 = p1 / p3; + Rational q2 = p2 / p3; + Rational q2third = q2 / rat3; + Rational c0 = q0 - q2third * (q1 - rat2 * q2third * q2third); + Rational c1 = q1 - q2 * q2third; + + info.clear(); + info.reserve(3); + GetRootInfoDepressedCubic(c0, c1, info); + } + + template + static void GetRootInfoQuartic(Rational const& p0, Rational const& p1, + Rational const& p2, Rational const& p3, Rational const& p4, + std::vector& info) + { + Rational const rat2 = 2, rat3 = 3, rat4 = 4, rat6 = 6; + Rational q0 = p0 / p4; + Rational q1 = p1 / p4; + Rational q2 = p2 / p4; + Rational q3 = p3 / p4; + Rational q3fourth = q3 / rat4; + Rational q3fourthSqr = q3fourth * q3fourth; + Rational c0 = q0 - q3fourth * (q1 - q3fourth * (q2 - q3fourthSqr * rat3)); + Rational c1 = q1 - rat2 * q3fourth * (q2 - rat4 * q3fourthSqr); + Rational c2 = q2 - rat6 * q3fourthSqr; + + info.clear(); + info.reserve(4); + GetRootInfoDepressedQuartic(c0, c1, c2, info); + } + + // General equations: sum_{i=0}^{d} c(i)*t^i = 0. The input array 'c' + // must have at least d+1 elements and the output array 'root' must + // have at least d elements. + + // Find the roots on (-infinity,+infinity). + static int Find(int degree, Real const* c, unsigned int maxIterations, Real* roots) + { + if (degree >= 0 && c) + { + Real const zero = (Real)0; + while (degree >= 0 && c[degree] == zero) + { + --degree; + } + + if (degree > 0) + { + // Compute the Cauchy bound. + Real const one = (Real)1; + Real invLeading = one / c[degree]; + Real maxValue = zero; + for (int i = 0; i < degree; ++i) + { + Real value = std::fabs(c[i] * invLeading); + if (value > maxValue) + { + maxValue = value; + } + } + Real bound = one + maxValue; + + return FindRecursive(degree, c, -bound, bound, maxIterations, + roots); + } + else if (degree == 0) + { + // The polynomial is a nonzero constant. + return 0; + } + else + { + // The polynomial is identically zero. + roots[0] = zero; + return 1; + } + } + else + { + // Invalid degree or c. + return 0; + } + } + + // If you know that p(tmin) * p(tmax) <= 0, then there must be at + // least one root in [tmin, tmax]. Compute it using bisection. + static bool Find(int degree, Real const* c, Real tmin, Real tmax, + unsigned int maxIterations, Real& root) + { + Real const zero = (Real)0; + Real pmin = Evaluate(degree, c, tmin); + if (pmin == zero) + { + root = tmin; + return true; + } + Real pmax = Evaluate(degree, c, tmax); + if (pmax == zero) + { + root = tmax; + return true; + } + + if (pmin * pmax > zero) + { + // It is not known whether the interval bounds a root. + return false; + } + + if (tmin >= tmax) + { + // Invalid ordering of interval endpoitns. + return false; + } + + for (unsigned int i = 1; i <= maxIterations; ++i) + { + root = ((Real)0.5) * (tmin + tmax); + + // This test is designed for 'float' or 'double' when tmin + // and tmax are consecutive floating-point numbers. + if (root == tmin || root == tmax) + { + break; + } + + Real p = Evaluate(degree, c, root); + Real product = p * pmin; + if (product < zero) + { + tmax = root; + pmax = p; + } + else if (product > zero) + { + tmin = root; + pmin = p; + } + else + { + break; + } + } + + return true; + } + + private: + // Support for the Solve* functions. + template + static void SolveDepressedQuadratic(Rational const& c0, + std::map& rmMap) + { + Rational const zero = 0; + if (c0 < zero) + { + // Two simple roots. + Rational root1 = (Rational)std::sqrt((double)-c0); + Rational root0 = -root1; + rmMap.insert(std::make_pair(root0, 1)); + rmMap.insert(std::make_pair(root1, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(0); + } + else if (c0 == zero) + { + // One double root. + rmMap.insert(std::make_pair(zero, 2)); + GTE_ROOTS_LOW_DEGREE_BLOCK(1); + } + else // c0 > 0 + { + // A complex-conjugate pair of roots. + // Complex z0 = -q1/2 - i*sqrt(c0); + // Complex z0conj = -q1/2 + i*sqrt(c0); + GTE_ROOTS_LOW_DEGREE_BLOCK(2); + } + } + + template + static void SolveDepressedCubic(Rational const& c0, Rational const& c1, + std::map& rmMap) + { + // Handle the special case of c0 = 0, in which case the polynomial + // reduces to a depressed quadratic. + Rational const zero = 0; + if (c0 == zero) + { + SolveDepressedQuadratic(c1, rmMap); + auto iter = rmMap.find(zero); + if (iter != rmMap.end()) + { + // The quadratic has a root of zero, so the multiplicity + // must be increased. + ++iter->second; + GTE_ROOTS_LOW_DEGREE_BLOCK(3); + } + else + { + // The quadratic does not have a root of zero. Insert the + // one for the cubic. + rmMap.insert(std::make_pair(zero, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(4); + } + return; + } + + // Handle the special case of c0 != 0 and c1 = 0. + double const oneThird = 1.0 / 3.0; + if (c1 == zero) + { + // One simple real root. + Rational root0; + if (c0 > zero) + { + root0 = (Rational)-std::pow((double)c0, oneThird); + GTE_ROOTS_LOW_DEGREE_BLOCK(5); + } + else + { + root0 = (Rational)std::pow(-(double)c0, oneThird); + GTE_ROOTS_LOW_DEGREE_BLOCK(6); + } + rmMap.insert(std::make_pair(root0, 1)); + + // One complex conjugate pair. + // Complex z0 = root0*(-1 - i*sqrt(3))/2; + // Complex z0conj = root0*(-1 + i*sqrt(3))/2; + return; + } + + // At this time, c0 != 0 and c1 != 0. + Rational const rat2 = 2, rat3 = 3, rat4 = 4, rat27 = 27, rat108 = 108; + Rational delta = -(rat4 * c1 * c1 * c1 + rat27 * c0 * c0); + if (delta > zero) + { + // Three simple roots. + Rational deltaDiv108 = delta / rat108; + Rational betaRe = -c0 / rat2; + Rational betaIm = std::sqrt(deltaDiv108); + Rational theta = std::atan2(betaIm, betaRe); + Rational thetaDiv3 = theta / rat3; + double angle = (double)thetaDiv3; + Rational cs = (Rational)std::cos(angle); + Rational sn = (Rational)std::sin(angle); + Rational rhoSqr = betaRe * betaRe + betaIm * betaIm; + Rational rhoPowThird = (Rational)std::pow((double)rhoSqr, 1.0 / 6.0); + Rational temp0 = rhoPowThird * cs; + Rational temp1 = rhoPowThird * sn * (Rational)std::sqrt(3.0); + Rational root0 = rat2 * temp0; + Rational root1 = -temp0 - temp1; + Rational root2 = -temp0 + temp1; + rmMap.insert(std::make_pair(root0, 1)); + rmMap.insert(std::make_pair(root1, 1)); + rmMap.insert(std::make_pair(root2, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(7); + } + else if (delta < zero) + { + // One simple root. + Rational deltaDiv108 = delta / rat108; + Rational temp0 = -c0 / rat2; + Rational temp1 = (Rational)std::sqrt(-(double)deltaDiv108); + Rational temp2 = temp0 - temp1; + Rational temp3 = temp0 + temp1; + if (temp2 >= zero) + { + temp2 = (Rational)std::pow((double)temp2, oneThird); + GTE_ROOTS_LOW_DEGREE_BLOCK(8); + } + else + { + temp2 = (Rational)-std::pow(-(double)temp2, oneThird); + GTE_ROOTS_LOW_DEGREE_BLOCK(9); + } + if (temp3 >= zero) + { + temp3 = (Rational)std::pow((double)temp3, oneThird); + GTE_ROOTS_LOW_DEGREE_BLOCK(10); + } + else + { + temp3 = (Rational)-std::pow(-(double)temp3, oneThird); + GTE_ROOTS_LOW_DEGREE_BLOCK(11); + } + Rational root0 = temp2 + temp3; + rmMap.insert(std::make_pair(root0, 1)); + + // One complex conjugate pair. + // Complex z0 = (-root0 - i*sqrt(3*root0*root0+4*c1))/2; + // Complex z0conj = (-root0 + i*sqrt(3*root0*root0+4*c1))/2; + } + else // delta = 0 + { + // One simple root and one double root. + Rational root0 = -rat3 * c0 / (rat2 * c1); + Rational root1 = -rat2 * root0; + rmMap.insert(std::make_pair(root0, 2)); + rmMap.insert(std::make_pair(root1, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(12); + } + } + + template + static void SolveDepressedQuartic(Rational const& c0, Rational const& c1, + Rational const& c2, std::map& rmMap) + { + // Handle the special case of c0 = 0, in which case the polynomial + // reduces to a depressed cubic. + Rational const zero = 0; + if (c0 == zero) + { + SolveDepressedCubic(c1, c2, rmMap); + auto iter = rmMap.find(zero); + if (iter != rmMap.end()) + { + // The cubic has a root of zero, so the multiplicity must + // be increased. + ++iter->second; + GTE_ROOTS_LOW_DEGREE_BLOCK(13); + } + else + { + // The cubic does not have a root of zero. Insert the one + // for the quartic. + rmMap.insert(std::make_pair(zero, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(14); + } + return; + } + + // Handle the special case of c1 = 0, in which case the quartic is + // a biquadratic + // x^4 + c1*x^2 + c0 = (x^2 + c2/2)^2 + (c0 - c2^2/4) + if (c1 == zero) + { + SolveBiquadratic(c0, c2, rmMap); + return; + } + + // At this time, c0 != 0 and c1 != 0, which is a requirement for + // the general solver that must use a root of a special cubic + // polynomial. + Rational const rat2 = 2, rat4 = 4, rat8 = 8, rat12 = 12, rat16 = 16; + Rational const rat27 = 27, rat36 = 36; + Rational c0sqr = c0 * c0, c1sqr = c1 * c1, c2sqr = c2 * c2; + Rational delta = c1sqr * (-rat27 * c1sqr + rat4 * c2 * + (rat36 * c0 - c2sqr)) + rat16 * c0 * (c2sqr * (c2sqr - rat8 * c0) + + rat16 * c0sqr); + Rational a0 = rat12 * c0 + c2sqr; + Rational a1 = rat4 * c0 - c2sqr; + + if (delta > zero) + { + if (c2 < zero && a1 < zero) + { + // Four simple real roots. + std::map rmCubicMap; + SolveCubic(c1sqr - rat4 * c0 * c2, rat8 * c0, rat4 * c2, -rat8, rmCubicMap); + Rational t = (Rational)rmCubicMap.rbegin()->first; + Rational alphaSqr = rat2 * t - c2; + Rational alpha = (Rational)std::sqrt((double)alphaSqr); + double sgnC1; + if (c1 > zero) + { + sgnC1 = 1.0; + GTE_ROOTS_LOW_DEGREE_BLOCK(15); + } + else + { + sgnC1 = -1.0; + GTE_ROOTS_LOW_DEGREE_BLOCK(16); + } + Rational arg = t * t - c0; + Rational beta = (Rational)(sgnC1 * std::sqrt(std::max((double)arg, 0.0))); + Rational D0 = alphaSqr - rat4 * (t + beta); + Rational sqrtD0 = (Rational)std::sqrt(std::max((double)D0, 0.0)); + Rational D1 = alphaSqr - rat4 * (t - beta); + Rational sqrtD1 = (Rational)std::sqrt(std::max((double)D1, 0.0)); + Rational root0 = (alpha - sqrtD0) / rat2; + Rational root1 = (alpha + sqrtD0) / rat2; + Rational root2 = (-alpha - sqrtD1) / rat2; + Rational root3 = (-alpha + sqrtD1) / rat2; + rmMap.insert(std::make_pair(root0, 1)); + rmMap.insert(std::make_pair(root1, 1)); + rmMap.insert(std::make_pair(root2, 1)); + rmMap.insert(std::make_pair(root3, 1)); + } + else // c2 >= 0 or a1 >= 0 + { + // Two complex-conjugate pairs. The values alpha, D0 + // and D1 are those of the if-block. + // Complex z0 = (alpha - i*sqrt(-D0))/2; + // Complex z0conj = (alpha + i*sqrt(-D0))/2; + // Complex z1 = (-alpha - i*sqrt(-D1))/2; + // Complex z1conj = (-alpha + i*sqrt(-D1))/2; + GTE_ROOTS_LOW_DEGREE_BLOCK(17); + } + } + else if (delta < zero) + { + // Two simple real roots, one complex-conjugate pair. + std::map rmCubicMap; + SolveCubic(c1sqr - rat4 * c0 * c2, rat8 * c0, rat4 * c2, -rat8, + rmCubicMap); + Rational t = (Rational)rmCubicMap.rbegin()->first; + Rational alphaSqr = rat2 * t - c2; + Rational alpha = (Rational)std::sqrt(std::max((double)alphaSqr, 0.0)); + double sgnC1; + if (c1 > zero) + { + sgnC1 = 1.0; // Leads to BLOCK(18) + } + else + { + sgnC1 = -1.0; // Leads to BLOCK(19) + } + Rational arg = t * t - c0; + Rational beta = (Rational)(sgnC1 * std::sqrt(std::max((double)arg, 0.0))); + Rational root0, root1; + if (sgnC1 > 0.0) + { + Rational D1 = alphaSqr - rat4 * (t - beta); + Rational sqrtD1 = (Rational)std::sqrt(std::max((double)D1, 0.0)); + root0 = (-alpha - sqrtD1) / rat2; + root1 = (-alpha + sqrtD1) / rat2; + + // One complex conjugate pair. + // Complex z0 = (alpha - i*sqrt(-D0))/2; + // Complex z0conj = (alpha + i*sqrt(-D0))/2; + GTE_ROOTS_LOW_DEGREE_BLOCK(18); + } + else + { + Rational D0 = alphaSqr - rat4 * (t + beta); + Rational sqrtD0 = (Rational)std::sqrt(std::max((double)D0, 0.0)); + root0 = (alpha - sqrtD0) / rat2; + root1 = (alpha + sqrtD0) / rat2; + + // One complex conjugate pair. + // Complex z0 = (-alpha - i*sqrt(-D1))/2; + // Complex z0conj = (-alpha + i*sqrt(-D1))/2; + GTE_ROOTS_LOW_DEGREE_BLOCK(19); + } + rmMap.insert(std::make_pair(root0, 1)); + rmMap.insert(std::make_pair(root1, 1)); + } + else // delta = 0 + { + if (a1 > zero || (c2 > zero && (a1 != zero || c1 != zero))) + { + // One double real root, one complex-conjugate pair. + Rational const rat9 = 9; + Rational root0 = -c1 * a0 / (rat9 * c1sqr - rat2 * c2 * a1); + rmMap.insert(std::make_pair(root0, 2)); + + // One complex conjugate pair. + // Complex z0 = -root0 - i*sqrt(c2 + root0^2); + // Complex z0conj = -root0 + i*sqrt(c2 + root0^2); + GTE_ROOTS_LOW_DEGREE_BLOCK(20); + } + else + { + Rational const rat3 = 3; + if (a0 != zero) + { + // One double real root, two simple real roots. + Rational const rat9 = 9; + Rational root0 = -c1 * a0 / (rat9 * c1sqr - rat2 * c2 * a1); + Rational alpha = rat2 * root0; + Rational beta = c2 + rat3 * root0 * root0; + Rational discr = alpha * alpha - rat4 * beta; + Rational temp1 = (Rational)std::sqrt((double)discr); + Rational root1 = (-alpha - temp1) / rat2; + Rational root2 = (-alpha + temp1) / rat2; + rmMap.insert(std::make_pair(root0, 2)); + rmMap.insert(std::make_pair(root1, 1)); + rmMap.insert(std::make_pair(root2, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(21); + } + else + { + // One triple real root, one simple real root. + Rational root0 = -rat3 * c1 / (rat4 * c2); + Rational root1 = -rat3 * root0; + rmMap.insert(std::make_pair(root0, 3)); + rmMap.insert(std::make_pair(root1, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(22); + } + } + } + } + + template + static void SolveBiquadratic(Rational const& c0, Rational const& c2, + std::map& rmMap) + { + // Solve 0 = x^4 + c2*x^2 + c0 = (x^2 + c2/2)^2 + a1, where + // a1 = c0 - c2^2/2. We know that c0 != 0 at the time of the + // function call, so x = 0 is not a root. The condition c1 = 0 + // implies the quartic Delta = 256*c0*a1^2. + + Rational const zero = 0, rat2 = 2, rat256 = 256; + Rational c2Half = c2 / rat2; + Rational a1 = c0 - c2Half * c2Half; + Rational delta = rat256 * c0 * a1 * a1; + if (delta > zero) + { + if (c2 < zero) + { + if (a1 < zero) + { + // Four simple roots. + Rational temp0 = (Rational)std::sqrt(-(double)a1); + Rational temp1 = -c2Half - temp0; + Rational temp2 = -c2Half + temp0; + Rational root1 = (Rational)std::sqrt((double)temp1); + Rational root0 = -root1; + Rational root2 = (Rational)std::sqrt((double)temp2); + Rational root3 = -root2; + rmMap.insert(std::make_pair(root0, 1)); + rmMap.insert(std::make_pair(root1, 1)); + rmMap.insert(std::make_pair(root2, 1)); + rmMap.insert(std::make_pair(root3, 1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(23); + } + else // a1 > 0 + { + // Two simple complex conjugate pairs. + // double thetaDiv2 = atan2(sqrt(a1), -c2/2) / 2.0; + // double cs = cos(thetaDiv2), sn = sin(thetaDiv2); + // double length = pow(c0, 0.25); + // Complex z0 = length*(cs + i*sn); + // Complex z0conj = length*(cs - i*sn); + // Complex z1 = length*(-cs + i*sn); + // Complex z1conj = length*(-cs - i*sn); + GTE_ROOTS_LOW_DEGREE_BLOCK(24); + } + } + else // c2 >= 0 + { + // Two simple complex conjugate pairs. + // Complex z0 = -i*sqrt(c2/2 - sqrt(-a1)); + // Complex z0conj = +i*sqrt(c2/2 - sqrt(-a1)); + // Complex z1 = -i*sqrt(c2/2 + sqrt(-a1)); + // Complex z1conj = +i*sqrt(c2/2 + sqrt(-a1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(25); + } + } + else if (delta < zero) + { + // Two simple real roots. + Rational temp0 = (Rational)std::sqrt(-(double)a1); + Rational temp1 = -c2Half + temp0; + Rational root1 = (Rational)std::sqrt((double)temp1); + Rational root0 = -root1; + rmMap.insert(std::make_pair(root0, 1)); + rmMap.insert(std::make_pair(root1, 1)); + + // One complex conjugate pair. + // Complex z0 = -i*sqrt(c2/2 + sqrt(-a1)); + // Complex z0conj = +i*sqrt(c2/2 + sqrt(-a1)); + GTE_ROOTS_LOW_DEGREE_BLOCK(26); + } + else // delta = 0 + { + if (c2 < zero) + { + // Two double real roots. + Rational root1 = (Rational)std::sqrt(-(double)c2Half); + Rational root0 = -root1; + rmMap.insert(std::make_pair(root0, 2)); + rmMap.insert(std::make_pair(root1, 2)); + GTE_ROOTS_LOW_DEGREE_BLOCK(27); + } + else // c2 > 0 + { + // Two double complex conjugate pairs. + // Complex z0 = -i*sqrt(c2/2); // multiplicity 2 + // Complex z0conj = +i*sqrt(c2/2); // multiplicity 2 + GTE_ROOTS_LOW_DEGREE_BLOCK(28); + } + } + } + + // Support for the GetNumRoots* functions. + template + static void GetRootInfoDepressedQuadratic(Rational const& c0, + std::vector& info) + { + Rational const zero = 0; + if (c0 < zero) + { + // Two simple roots. + info.push_back(1); + info.push_back(1); + } + else if (c0 == zero) + { + // One double root. + info.push_back(2); // root is zero + } + else // c0 > 0 + { + // A complex-conjugate pair of roots. + } + } + + template + static void GetRootInfoDepressedCubic(Rational const& c0, + Rational const& c1, std::vector& info) + { + // Handle the special case of c0 = 0, in which case the polynomial + // reduces to a depressed quadratic. + Rational const zero = 0; + if (c0 == zero) + { + if (c1 == zero) + { + info.push_back(3); // triple root of zero + } + else + { + info.push_back(1); // simple root of zero + GetRootInfoDepressedQuadratic(c1, info); + } + return; + } + + Rational const rat4 = 4, rat27 = 27; + Rational delta = -(rat4 * c1 * c1 * c1 + rat27 * c0 * c0); + if (delta > zero) + { + // Three simple real roots. + info.push_back(1); + info.push_back(1); + info.push_back(1); + } + else if (delta < zero) + { + // One simple real root. + info.push_back(1); + } + else // delta = 0 + { + // One simple real root and one double real root. + info.push_back(1); + info.push_back(2); + } + } + + template + static void GetRootInfoDepressedQuartic(Rational const& c0, + Rational const& c1, Rational const& c2, std::vector& info) + { + // Handle the special case of c0 = 0, in which case the polynomial + // reduces to a depressed cubic. + Rational const zero = 0; + if (c0 == zero) + { + if (c1 == zero) + { + if (c2 == zero) + { + info.push_back(4); // quadruple root of zero + } + else + { + info.push_back(2); // double root of zero + GetRootInfoDepressedQuadratic(c2, info); + } + } + else + { + info.push_back(1); // simple root of zero + GetRootInfoDepressedCubic(c1, c2, info); + } + return; + } + + // Handle the special case of c1 = 0, in which case the quartic is + // a biquadratic + // x^4 + c1*x^2 + c0 = (x^2 + c2/2)^2 + (c0 - c2^2/4) + if (c1 == zero) + { + GetRootInfoBiquadratic(c0, c2, info); + return; + } + + // At this time, c0 != 0 and c1 != 0, which is a requirement for + // the general solver that must use a root of a special cubic + // polynomial. + Rational const rat4 = 4, rat8 = 8, rat12 = 12, rat16 = 16; + Rational const rat27 = 27, rat36 = 36; + Rational c0sqr = c0 * c0, c1sqr = c1 * c1, c2sqr = c2 * c2; + Rational delta = c1sqr * (-rat27 * c1sqr + rat4 * c2 * + (rat36 * c0 - c2sqr)) + rat16 * c0 * (c2sqr * (c2sqr - rat8 * c0) + + rat16 * c0sqr); + Rational a0 = rat12 * c0 + c2sqr; + Rational a1 = rat4 * c0 - c2sqr; + + if (delta > zero) + { + if (c2 < zero && a1 < zero) + { + // Four simple real roots. + info.push_back(1); + info.push_back(1); + info.push_back(1); + info.push_back(1); + } + else // c2 >= 0 or a1 >= 0 + { + // Two complex-conjugate pairs. + } + } + else if (delta < zero) + { + // Two simple real roots, one complex-conjugate pair. + info.push_back(1); + info.push_back(1); + } + else // delta = 0 + { + if (a1 > zero || (c2 > zero && (a1 != zero || c1 != zero))) + { + // One double real root, one complex-conjugate pair. + info.push_back(2); + } + else + { + if (a0 != zero) + { + // One double real root, two simple real roots. + info.push_back(2); + info.push_back(1); + info.push_back(1); + } + else + { + // One triple real root, one simple real root. + info.push_back(3); + info.push_back(1); + } + } + } + } + + template + static void GetRootInfoBiquadratic(Rational const& c0, + Rational const& c2, std::vector& info) + { + // Solve 0 = x^4 + c2*x^2 + c0 = (x^2 + c2/2)^2 + a1, where + // a1 = c0 - c2^2/2. We know that c0 != 0 at the time of the + // function call, so x = 0 is not a root. The condition c1 = 0 + // implies the quartic Delta = 256*c0*a1^2. + + Rational const zero = 0, rat2 = 2, rat256 = 256; + Rational c2Half = c2 / rat2; + Rational a1 = c0 - c2Half * c2Half; + Rational delta = rat256 * c0 * a1 * a1; + if (delta > zero) + { + if (c2 < zero) + { + if (a1 < zero) + { + // Four simple roots. + info.push_back(1); + info.push_back(1); + info.push_back(1); + info.push_back(1); + } + else // a1 > 0 + { + // Two simple complex conjugate pairs. + } + } + else // c2 >= 0 + { + // Two simple complex conjugate pairs. + } + } + else if (delta < zero) + { + // Two simple real roots, one complex conjugate pair. + info.push_back(1); + info.push_back(1); + } + else // delta = 0 + { + if (c2 < zero) + { + // Two double real roots. + info.push_back(2); + info.push_back(2); + } + else // c2 > 0 + { + // Two double complex conjugate pairs. + } + } + } + + // Support for the Find functions. + static int FindRecursive(int degree, Real const* c, Real tmin, Real tmax, + unsigned int maxIterations, Real* roots) + { + // The base of the recursion. + Real const zero = (Real)0; + Real root = zero; + if (degree == 1) + { + int numRoots; + if (c[1] != zero) + { + root = -c[0] / c[1]; + numRoots = 1; + } + else if (c[0] == zero) + { + root = zero; + numRoots = 1; + } + else + { + numRoots = 0; + } + + if (numRoots > 0 && tmin <= root && root <= tmax) + { + roots[0] = root; + return 1; + } + return 0; + } + + // Find the roots of the derivative polynomial scaled by 1/degree. + // The scaling avoids the factorial growth in the coefficients; + // for example, without the scaling, the high-order term x^d + // becomes (d!)*x through multiple differentiations. With the + // scaling we instead get x. This leads to better numerical + // behavior of the root finder. + int derivDegree = degree - 1; + std::vector derivCoeff(derivDegree + 1); + std::vector derivRoots(derivDegree); + for (int i = 0; i <= derivDegree; ++i) + { + derivCoeff[i] = c[i + 1] * (Real)(i + 1) / (Real)degree; + } + int numDerivRoots = FindRecursive(degree - 1, &derivCoeff[0], tmin, tmax, + maxIterations, &derivRoots[0]); + + int numRoots = 0; + if (numDerivRoots > 0) + { + // Find root on [tmin,derivRoots[0]]. + if (Find(degree, c, tmin, derivRoots[0], maxIterations, root)) + { + roots[numRoots++] = root; + } + + // Find root on [derivRoots[i],derivRoots[i+1]]. + for (int i = 0; i <= numDerivRoots - 2; ++i) + { + if (Find(degree, c, derivRoots[i], derivRoots[i + 1], + maxIterations, root)) + { + roots[numRoots++] = root; + } + } + + // Find root on [derivRoots[numDerivRoots-1],tmax]. + if (Find(degree, c, derivRoots[numDerivRoots - 1], tmax, + maxIterations, root)) + { + roots[numRoots++] = root; + } + } + else + { + // The polynomial is monotone on [tmin,tmax], so has at most one root. + if (Find(degree, c, tmin, tmax, maxIterations, root)) + { + roots[numRoots++] = root; + } + } + return numRoots; + } + + static Real Evaluate(int degree, Real const* c, Real t) + { + int i = degree; + Real result = c[i]; + while (--i >= 0) + { + result = t * result + c[i]; + } + return result; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Rotation.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Rotation.h new file mode 100644 index 0000000..a06a4d8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Rotation.h @@ -0,0 +1,829 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + // Conversions among various representations of rotations. The value of + // N must be 3 or 4. The latter case supports affine algebra when you use + // 4-tuple vectors (w-component is 1 for points and 0 for vector) and 4x4 + // matrices for affine transformations. Rotation axes must be unit + // length. The angles are in radians. The Euler angles are in world + // coordinates; we have not yet added support for body coordinates. + + template + class Rotation + { + public: + // Create rotations from various representations. + Rotation(Matrix const& matrix) + : + mType(IS_MATRIX), + mMatrix(matrix) + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + } + + Rotation(Quaternion const& quaternion) + : + mType(IS_QUATERNION), + mQuaternion(quaternion) + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + } + + Rotation(AxisAngle const& axisAngle) + : + mType(IS_AXIS_ANGLE), + mAxisAngle(axisAngle) + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + } + + Rotation(EulerAngles const& eulerAngles) + : + mType(IS_EULER_ANGLES), + mEulerAngles(eulerAngles) + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + } + + // Convert one representation to another. + operator Matrix() const + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + switch (mType) + { + case IS_MATRIX: + break; + case IS_QUATERNION: + Convert(mQuaternion, mMatrix); + break; + case IS_AXIS_ANGLE: + Convert(mAxisAngle, mMatrix); + break; + case IS_EULER_ANGLES: + Convert(mEulerAngles, mMatrix); + break; + } + + return mMatrix; + } + + operator Quaternion() const + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + switch (mType) + { + case IS_MATRIX: + Convert(mMatrix, mQuaternion); + break; + case IS_QUATERNION: + break; + case IS_AXIS_ANGLE: + Convert(mAxisAngle, mQuaternion); + break; + case IS_EULER_ANGLES: + Convert(mEulerAngles, mQuaternion); + break; + } + + return mQuaternion; + } + + operator AxisAngle() const + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + switch (mType) + { + case IS_MATRIX: + Convert(mMatrix, mAxisAngle); + break; + case IS_QUATERNION: + Convert(mQuaternion, mAxisAngle); + break; + case IS_AXIS_ANGLE: + break; + case IS_EULER_ANGLES: + Convert(mEulerAngles, mAxisAngle); + break; + } + + return mAxisAngle; + } + + EulerAngles const& operator()(int i0, int i1, int i2) const + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + mEulerAngles.axis[0] = i0; + mEulerAngles.axis[1] = i1; + mEulerAngles.axis[2] = i2; + + switch (mType) + { + case IS_MATRIX: + Convert(mMatrix, mEulerAngles); + break; + case IS_QUATERNION: + Convert(mQuaternion, mEulerAngles); + break; + case IS_AXIS_ANGLE: + Convert(mAxisAngle, mEulerAngles); + break; + case IS_EULER_ANGLES: + break; + } + + return mEulerAngles; + } + + private: + enum RepresentationType + { + IS_MATRIX, + IS_QUATERNION, + IS_AXIS_ANGLE, + IS_EULER_ANGLES + }; + + RepresentationType mType; + mutable Matrix mMatrix; + mutable Quaternion mQuaternion; + mutable AxisAngle mAxisAngle; + mutable EulerAngles mEulerAngles; + + // Convert a rotation matrix to a quaternion. + // + // x^2 = (+r00 - r11 - r22 + 1)/4 + // y^2 = (-r00 + r11 - r22 + 1)/4 + // z^2 = (-r00 - r11 + r22 + 1)/4 + // w^2 = (+r00 + r11 + r22 + 1)/4 + // x^2 + y^2 = (1 - r22)/2 + // z^2 + w^2 = (1 + r22)/2 + // y^2 - x^2 = (r11 - r00)/2 + // w^2 - z^2 = (r11 + r00)/2 + // x*y = (r01 + r10)/4 + // x*z = (r02 + r20)/4 + // y*z = (r12 + r21)/4 + // [GTE_USE_MAT_VEC] + // x*w = (r21 - r12)/4 + // y*w = (r02 - r20)/4 + // z*w = (r10 - r01)/4 + // [GTE_USE_VEC_MAT] + // x*w = (r12 - r21)/4 + // y*w = (r20 - r02)/4 + // z*w = (r01 - r10)/4 + // + // If Q is the 4x1 column vector (x,y,z,w), the previous equations + // give us + // +- -+ + // | x*x x*y x*z x*w | + // Q*Q^T = | y*x y*y y*z y*w | + // | z*x z*y z*z z*w | + // | w*x w*y w*z w*w | + // +- -+ + // The code extracts the row of maximum length, normalizing it to + // obtain the result q. + static void Convert(Matrix const& r, Quaternion& q) + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Real r22 = r(2, 2); + if (r22 <= (Real)0) // x^2 + y^2 >= z^2 + w^2 + { + Real dif10 = r(1, 1) - r(0, 0); + Real omr22 = (Real)1 - r22; + if (dif10 <= (Real)0) // x^2 >= y^2 + { + Real fourXSqr = omr22 - dif10; + Real inv4x = ((Real)0.5) / std::sqrt(fourXSqr); + q[0] = fourXSqr * inv4x; + q[1] = (r(0, 1) + r(1, 0)) * inv4x; + q[2] = (r(0, 2) + r(2, 0)) * inv4x; +#if defined(GTE_USE_MAT_VEC) + q[3] = (r(2, 1) - r(1, 2)) * inv4x; +#else + q[3] = (r(1, 2) - r(2, 1)) * inv4x; +#endif + } + else // y^2 >= x^2 + { + Real fourYSqr = omr22 + dif10; + Real inv4y = ((Real)0.5) / std::sqrt(fourYSqr); + q[0] = (r(0, 1) + r(1, 0)) * inv4y; + q[1] = fourYSqr * inv4y; + q[2] = (r(1, 2) + r(2, 1)) * inv4y; +#if defined(GTE_USE_MAT_VEC) + q[3] = (r(0, 2) - r(2, 0)) * inv4y; +#else + q[3] = (r(2, 0) - r(0, 2)) * inv4y; +#endif + } + } + else // z^2 + w^2 >= x^2 + y^2 + { + Real sum10 = r(1, 1) + r(0, 0); + Real opr22 = (Real)1 + r22; + if (sum10 <= (Real)0) // z^2 >= w^2 + { + Real fourZSqr = opr22 - sum10; + Real inv4z = ((Real)0.5) / std::sqrt(fourZSqr); + q[0] = (r(0, 2) + r(2, 0)) * inv4z; + q[1] = (r(1, 2) + r(2, 1)) * inv4z; + q[2] = fourZSqr * inv4z; +#if defined(GTE_USE_MAT_VEC) + q[3] = (r(1, 0) - r(0, 1)) * inv4z; +#else + q[3] = (r(0, 1) - r(1, 0)) * inv4z; +#endif + } + else // w^2 >= z^2 + { + Real fourWSqr = opr22 + sum10; + Real inv4w = ((Real)0.5) / std::sqrt(fourWSqr); +#if defined(GTE_USE_MAT_VEC) + q[0] = (r(2, 1) - r(1, 2)) * inv4w; + q[1] = (r(0, 2) - r(2, 0)) * inv4w; + q[2] = (r(1, 0) - r(0, 1)) * inv4w; +#else + q[0] = (r(1, 2) - r(2, 1)) * inv4w; + q[1] = (r(2, 0) - r(0, 2)) * inv4w; + q[2] = (r(0, 1) - r(1, 0)) * inv4w; +#endif + q[3] = fourWSqr * inv4w; + } + } + } + + // Convert a quaterion q = x*i + y*j + z*k + w to a rotation matrix. + // [GTE_USE_MAT_VEC] + // +- -+ +- -+ + // R = | r00 r01 r02 | = | 1-2y^2-2z^2 2(xy-zw) 2(xz+yw) | + // | r10 r11 r12 | | 2(xy+zw) 1-2x^2-2z^2 2(yz-xw) | + // | r20 r21 r22 | | 2(xz-yw) 2(yz+xw) 1-2x^2-2y^2 | + // +- -+ +- -+ + // [GTE_USE_VEC_MAT] + // +- -+ +- -+ + // R = | r00 r01 r02 | = | 1-2y^2-2z^2 2(xy+zw) 2(xz-yw) | + // | r10 r11 r12 | | 2(xy-zw) 1-2x^2-2z^2 2(yz+xw) | + // | r20 r21 r22 | | 2(xz+yw) 2(yz-xw) 1-2x^2-2y^2 | + // +- -+ +- -+ + static void Convert(Quaternion const& q, Matrix& r) + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + r.MakeIdentity(); + + Real twoX = ((Real)2) * q[0]; + Real twoY = ((Real)2) * q[1]; + Real twoZ = ((Real)2) * q[2]; + Real twoXX = twoX * q[0]; + Real twoXY = twoX * q[1]; + Real twoXZ = twoX * q[2]; + Real twoXW = twoX * q[3]; + Real twoYY = twoY * q[1]; + Real twoYZ = twoY * q[2]; + Real twoYW = twoY * q[3]; + Real twoZZ = twoZ * q[2]; + Real twoZW = twoZ * q[3]; + +#if defined(GTE_USE_MAT_VEC) + r(0, 0) = (Real)1 - twoYY - twoZZ; + r(0, 1) = twoXY - twoZW; + r(0, 2) = twoXZ + twoYW; + r(1, 0) = twoXY + twoZW; + r(1, 1) = (Real)1 - twoXX - twoZZ; + r(1, 2) = twoYZ - twoXW; + r(2, 0) = twoXZ - twoYW; + r(2, 1) = twoYZ + twoXW; + r(2, 2) = (Real)1 - twoXX - twoYY; +#else + r(0, 0) = (Real)1 - twoYY - twoZZ; + r(1, 0) = twoXY - twoZW; + r(2, 0) = twoXZ + twoYW; + r(0, 1) = twoXY + twoZW; + r(1, 1) = (Real)1 - twoXX - twoZZ; + r(2, 1) = twoYZ - twoXW; + r(0, 2) = twoXZ - twoYW; + r(1, 2) = twoYZ + twoXW; + r(2, 2) = (Real)1 - twoXX - twoYY; +#endif + } + + // Convert a rotation matrix to an axis-angle pair. Let (x0,x1,x2) be + // the axis let t be an angle of rotation. The rotation matrix is + // [GTE_USE_MAT_VEC] + // R = I + sin(t)*S + (1-cos(t))*S^2 + // or + // [GTE_USE_VEC_MAT] + // R = I - sin(t)*S + (1-cos(t))*S^2 + // where I is the identity and S = {{0,-x2,x1},{x2,0,-x0},{-x1,x0,0}} + // where the inner-brace triples are the rows of the matrix. If + // t > 0, R represents a counterclockwise rotation; see the comments + // for the constructor Matrix3x3(axis,angle). It may be shown that + // cos(t) = (trace(R)-1)/2 and R - Transpose(R) = 2*sin(t)*S. As long + // as sin(t) is not zero, we may solve for S in the second equation, + // which produces the axis direction U = (S21,S02,S10). When t = 0, + // the rotation is the identity, in which case any axis direction is + // valid; we choose (1,0,0). When t = pi, it must be that + // R - Transpose(R) = 0, which prevents us from extracting the axis. + // Instead, note that (R+I)/2 = I+S^2 = U*U^T, where U is a + // unit-length axis direction. + static void Convert(Matrix const& r, AxisAngle& a) + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Real trace = r(0, 0) + r(1, 1) + r(2, 2); + Real half = (Real)0.5; + Real cs = half * (trace - (Real)1); + cs = std::max(std::min(cs, (Real)1), (Real)-1); + a.angle = std::acos(cs); // The angle is in [0,pi]. + a.axis.MakeZero(); + + if (a.angle > (Real)0) + { + if (a.angle < (Real)GTE_C_PI) + { + // The angle is in (0,pi). +#if defined(GTE_USE_MAT_VEC) + a.axis[0] = r(2, 1) - r(1, 2); + a.axis[1] = r(0, 2) - r(2, 0); + a.axis[2] = r(1, 0) - r(0, 1); + Normalize(a.axis); +#else + a.axis[0] = r(1, 2) - r(2, 1); + a.axis[1] = r(2, 0) - r(0, 2); + a.axis[2] = r(0, 1) - r(1, 0); + Normalize(a.axis); +#endif + } + else + { + // The angle is pi, in which case R is symmetric and + // R+I = 2*(I+S^2) = 2*U*U^T, where U = (u0,u1,u2) is the + // unit-length direction of the rotation axis. Determine + // the largest diagonal entry of R+I and normalize the + // corresponding row to produce U. It does not matter the + // sign on u[d] for chosen diagonal d, because + // R(U,pi) = R(-U,pi). + Real one = (Real)1; + if (r(0, 0) >= r(1, 1)) + { + if (r(0, 0) >= r(2, 2)) + { + // r00 is maximum diagonal term + a.axis[0] = r(0, 0) + one; + a.axis[1] = half * (r(0, 1) + r(1, 0)); + a.axis[2] = half * (r(0, 2) + r(2, 0)); + } + else + { + // r22 is maximum diagonal term + a.axis[0] = half * (r(2, 0) + r(0, 2)); + a.axis[1] = half * (r(2, 1) + r(1, 2)); + a.axis[2] = r(2, 2) + one; + } + } + else + { + if (r(1, 1) >= r(2, 2)) + { + // r11 is maximum diagonal term + a.axis[0] = half * (r(1, 0) + r(0, 1)); + a.axis[1] = r(1, 1) + one; + a.axis[2] = half * (r(1, 2) + r(2, 1)); + } + else + { + // r22 is maximum diagonal term + a.axis[0] = half * (r(2, 0) + r(0, 2)); + a.axis[1] = half * (r(2, 1) + r(1, 2)); + a.axis[2] = r(2, 2) + one; + } + } + Normalize(a.axis); + } + } + else + { + // The angle is 0 and the matrix is the identity. Any axis + // will work, so choose the Unit(0) axis. + a.axis[0] = (Real)1; + } + } + + // Convert an axis-angle pair to a rotation matrix. Assuming + // (x0,x1,x2) is for a right-handed world (x0 to right, x1 up, x2 out + // of plane of page), a positive angle corresponds to a + // counterclockwise rotation from the perspective of an observer + // looking at the origin of the plane of rotation and having view + // direction the negative of the axis direction. The coordinate-axis + // rotations are the following, where unit(0) = (1,0,0), + // unit(1) = (0,1,0), unit(2) = (0,0,1), + // [GTE_USE_MAT_VEC] + // R(unit(0),t) = {{ 1, 0, 0}, { 0, c,-s}, { 0, s, c}} + // R(unit(1),t) = {{ c, 0, s}, { 0, 1, 0}, {-s, 0, c}} + // R(unit(2),t) = {{ c,-s, 0}, { s, c, 0}, { 0, 0, 1}} + // or + // [GTE_USE_VEC_MAT] + // R(unit(0),t) = {{ 1, 0, 0}, { 0, c, s}, { 0,-s, c}} + // R(unit(1),t) = {{ c, 0,-s}, { 0, 1, 0}, { s, 0, c}} + // R(unit(2),t) = {{ c, s, 0}, {-s, c, 0}, { 0, 0, 1}} + // where c = cos(t), s = sin(t), and the inner-brace triples are rows + // of the matrix. The general matrix is + // [GTE_USE_MAT_VEC] + // +- -+ + // R = | (1-c)*x0^2 + c (1-c)*x0*x1 - s*x2 (1-c)*x0*x2 + s*x1 | + // | (1-c)*x0*x1 + s*x2 (1-c)*x1^2 + c (1-c)*x1*x2 - s*x0 | + // | (1-c)*x0*x2 - s*x1 (1-c)*x1*x2 + s*x0 (1-c)*x2^2 + c | + // +- -+ + // [GTE_USE_VEC_MAT] + // +- -+ + // R = | (1-c)*x0^2 + c (1-c)*x0*x1 + s*x2 (1-c)*x0*x2 - s*x1 | + // | (1-c)*x0*x1 - s*x2 (1-c)*x1^2 + c (1-c)*x1*x2 + s*x0 | + // | (1-c)*x0*x2 + s*x1 (1-c)*x1*x2 - s*x0 (1-c)*x2^2 + c | + // +- -+ + static void Convert(AxisAngle const& a, Matrix& r) + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + r.MakeIdentity(); + + Real cs = std::cos(a.angle); + Real sn = std::sin(a.angle); + Real oneMinusCos = ((Real)1) - cs; + Real x0sqr = a.axis[0] * a.axis[0]; + Real x1sqr = a.axis[1] * a.axis[1]; + Real x2sqr = a.axis[2] * a.axis[2]; + Real x0x1m = a.axis[0] * a.axis[1] * oneMinusCos; + Real x0x2m = a.axis[0] * a.axis[2] * oneMinusCos; + Real x1x2m = a.axis[1] * a.axis[2] * oneMinusCos; + Real x0Sin = a.axis[0] * sn; + Real x1Sin = a.axis[1] * sn; + Real x2Sin = a.axis[2] * sn; + +#if defined(GTE_USE_MAT_VEC) + r(0, 0) = x0sqr * oneMinusCos + cs; + r(0, 1) = x0x1m - x2Sin; + r(0, 2) = x0x2m + x1Sin; + r(1, 0) = x0x1m + x2Sin; + r(1, 1) = x1sqr * oneMinusCos + cs; + r(1, 2) = x1x2m - x0Sin; + r(2, 0) = x0x2m - x1Sin; + r(2, 1) = x1x2m + x0Sin; + r(2, 2) = x2sqr * oneMinusCos + cs; +#else + r(0, 0) = x0sqr * oneMinusCos + cs; + r(1, 0) = x0x1m - x2Sin; + r(2, 0) = x0x2m + x1Sin; + r(0, 1) = x0x1m + x2Sin; + r(1, 1) = x1sqr * oneMinusCos + cs; + r(2, 1) = x1x2m - x0Sin; + r(0, 2) = x0x2m - x1Sin; + r(1, 2) = x1x2m + x0Sin; + r(2, 2) = x2sqr * oneMinusCos + cs; +#endif + } + + // Convert a rotation matrix to Euler angles. Factorization into + // Euler angles is not necessarily unique. If the result is + // ER_NOT_UNIQUE_SUM, then the multiple solutions occur because + // angleN2+angleN0 is constant. If the result is ER_NOT_UNIQUE_DIF, + // then the multiple solutions occur because angleN2-angleN0 is + // constant. In either type of nonuniqueness, the function returns + // angleN0=0. + static void Convert(Matrix const& r, EulerAngles& e) + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + if (0 <= e.axis[0] && e.axis[0] < 3 + && 0 <= e.axis[1] && e.axis[1] < 3 + && 0 <= e.axis[2] && e.axis[2] < 3 + && e.axis[1] != e.axis[0] + && e.axis[1] != e.axis[2]) + { + if (e.axis[0] != e.axis[2]) + { +#if defined(GTE_USE_MAT_VEC) + // Map (0,1,2), (1,2,0), and (2,0,1) to +1. + // Map (0,2,1), (2,1,0), and (1,0,2) to -1. + int parity = (((e.axis[2] | (e.axis[1] << 2)) >> e.axis[0]) & 1); + Real const sgn = (parity & 1 ? (Real)-1 : (Real)+1); + + if (r(e.axis[2], e.axis[0]) < (Real)1) + { + if (r(e.axis[2], e.axis[0]) > (Real)-1) + { + e.angle[2] = std::atan2(sgn * r(e.axis[1], e.axis[0]), + r(e.axis[0], e.axis[0])); + e.angle[1] = std::asin(-sgn * r(e.axis[2], e.axis[0])); + e.angle[0] = std::atan2(sgn * r(e.axis[2], e.axis[1]), + r(e.axis[2], e.axis[2])); + e.result = ER_UNIQUE; + } + else + { + e.angle[2] = (Real)0; + e.angle[1] = sgn * (Real)GTE_C_HALF_PI; + e.angle[0] = std::atan2(-sgn * r(e.axis[1], e.axis[2]), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_DIF; + } + } + else + { + e.angle[2] = (Real)0; + e.angle[1] = -sgn * (Real)GTE_C_HALF_PI; + e.angle[0] = std::atan2(-sgn * r(e.axis[1], e.axis[2]), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_SUM; + } +#else + // Map (0,1,2), (1,2,0), and (2,0,1) to +1. + // Map (0,2,1), (2,1,0), and (1,0,2) to -1. + int parity = (((e.axis[0] | (e.axis[1] << 2)) >> e.axis[2]) & 1); + Real const sgn = (parity & 1 ? (Real)+1 : (Real)-1); + + if (r(e.axis[0], e.axis[2]) < (Real)1) + { + if (r(e.axis[0], e.axis[2]) > (Real)-1) + { + e.angle[0] = std::atan2(sgn * r(e.axis[1], e.axis[2]), + r(e.axis[2], e.axis[2])); + e.angle[1] = std::asin(-sgn * r(e.axis[0], e.axis[2])); + e.angle[2] = std::atan2(sgn * r(e.axis[0], e.axis[1]), + r(e.axis[0], e.axis[0])); + e.result = ER_UNIQUE; + } + else + { + e.angle[0] = (Real)0; + e.angle[1] = sgn * (Real)GTE_C_HALF_PI; + e.angle[2] = std::atan2(-sgn * r(e.axis[1], e.axis[0]), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_DIF; + } + } + else + { + e.angle[0] = (Real)0; + e.angle[1] = -sgn * (Real)GTE_C_HALF_PI; + e.angle[2] = std::atan2(-sgn * r(e.axis[1], e.axis[0]), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_SUM; + } +#endif + } + else + { +#if defined(GTE_USE_MAT_VEC) + // Map (0,2,0), (1,0,1), and (2,1,2) to +1. + // Map (0,1,0), (1,2,1), and (2,0,2) to -1. + int b0 = 3 - e.axis[1] - e.axis[2]; + int parity = (((b0 | (e.axis[1] << 2)) >> e.axis[2]) & 1); + Real const sgn = (parity & 1 ? (Real)+1 : (Real)-1); + + if (r(e.axis[2], e.axis[2]) < (Real)1) + { + if (r(e.axis[2], e.axis[2]) > (Real)-1) + { + e.angle[2] = std::atan2(r(e.axis[1], e.axis[2]), + sgn * r(b0, e.axis[2])); + e.angle[1] = std::acos(r(e.axis[2], e.axis[2])); + e.angle[0] = std::atan2(r(e.axis[2], e.axis[1]), + -sgn * r(e.axis[2], b0)); + e.result = ER_UNIQUE; + } + else + { + e.angle[2] = (Real)0; + e.angle[1] = (Real)GTE_C_PI; + e.angle[0] = std::atan2(sgn * r(e.axis[1], b0), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_DIF; + } + } + else + { + e.angle[2] = (Real)0; + e.angle[1] = (Real)0; + e.angle[0] = std::atan2(sgn * r(e.axis[1], b0), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_SUM; + } +#else + // Map (0,2,0), (1,0,1), and (2,1,2) to -1. + // Map (0,1,0), (1,2,1), and (2,0,2) to +1. + int b2 = 3 - e.axis[0] - e.axis[1]; + int parity = (((b2 | (e.axis[1] << 2)) >> e.axis[0]) & 1); + Real const sgn = (parity & 1 ? (Real)-1 : (Real)+1); + + if (r(e.axis[0], e.axis[0]) < (Real)1) + { + if (r(e.axis[0], e.axis[0]) > (Real)-1) + { + e.angle[0] = std::atan2(r(e.axis[1], e.axis[0]), + sgn * r(b2, e.axis[0])); + e.angle[1] = std::acos(r(e.axis[0], e.axis[0])); + e.angle[2] = std::atan2(r(e.axis[0], e.axis[1]), + -sgn * r(e.axis[0], b2)); + e.result = ER_UNIQUE; + } + else + { + e.angle[0] = (Real)0; + e.angle[1] = (Real)GTE_C_PI; + e.angle[2] = std::atan2(sgn * r(e.axis[1], b2), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_DIF; + } + } + else + { + e.angle[0] = (Real)0; + e.angle[1] = (Real)0; + e.angle[2] = std::atan2(sgn * r(e.axis[1], b2), + r(e.axis[1], e.axis[1])); + e.result = ER_NOT_UNIQUE_SUM; + } +#endif + } + } + else + { + // Invalid angles. + e.angle[0] = (Real)0; + e.angle[1] = (Real)0; + e.angle[2] = (Real)0; + e.result = ER_INVALID; + } + } + + // Convert Euler angles to a rotation matrix. The three integer + // inputs are in {0,1,2} and correspond to world directions + // unit(0) = (1,0,0), unit(1) = (0,1,0), or unit(2) = (0,0,1). The + // triples (N0,N1,N2) must be in the following set, + // {(0,1,2),(0,2,1),(1,0,2),(1,2,0),(2,0,1),(2,1,0), + // (0,1,0),(0,2,0),(1,0,1),(1,2,1),(2,0,2),(2,1,2)} + // The rotation matrix is + // [GTE_USE_MAT_VEC] + // R(unit(N2),angleN2)*R(unit(N1),angleN1)*R(unit(N0),angleN0) + // or + // [GTE_USE_VEC_MAT] + // R(unit(N0),angleN0)*R(unit(N1),angleN1)*R(unit(N2),angleN2) + // The conventions of constructor Matrix3(axis,angle) apply here as + // well. + // + // NOTE: The reversal of order is chosen so that a rotation matrix + // built with one multiplication convention is the transpose of the + // rotation matrix built with the other multiplication convention. + // Thus, + // [GTE_USE_MAT_VEC] + // Matrix3x3 R_mvconvention(N0,N1,N2,angleN0,angleN1,angleN2); + // Vector3 V(...); + // Vector3 U = R_mvconvention*V; // (u0,u1,u2) = R2*R1*R0*V + // [GTE_USE_VEC_MAT] + // Matrix3x3 R_vmconvention(N0,N1,N2,angleN0,angleN1,angleN2); + // Vector3 V(...); + // Vector3 U = R_mvconvention*V; // (u0,u1,u2) = V*R0*R1*R2 + // In either convention, you get the same 3-tuple U. + static void Convert(EulerAngles const& e, Matrix& r) + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + if (0 <= e.axis[0] && e.axis[0] < 3 + && 0 <= e.axis[1] && e.axis[1] < 3 + && 0 <= e.axis[2] && e.axis[2] < 3 + && e.axis[1] != e.axis[0] + && e.axis[1] != e.axis[2]) + { + Matrix r0, r1, r2; + Convert(AxisAngle(Vector::Unit(e.axis[0]), + e.angle[0]), r0); + Convert(AxisAngle(Vector::Unit(e.axis[1]), + e.angle[1]), r1); + Convert(AxisAngle(Vector::Unit(e.axis[2]), + e.angle[2]), r2); +#if defined(GTE_USE_MAT_VEC) + r = r2 * r1 * r0; +#else + r = r0 * r1 * r2; +#endif + } + else + { + // Invalid angles. + r.MakeIdentity(); + } + } + + // Convert a quaternion to an axis-angle pair, where + // q = sin(angle/2)*(axis[0]*i+axis[1]*j+axis[2]*k)+cos(angle/2) + static void Convert(Quaternion const& q, AxisAngle& a) + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + a.axis.MakeZero(); + + Real axisSqrLen = q[0] * q[0] + q[1] * q[1] + q[2] * q[2]; + if (axisSqrLen > (Real)0) + { +#if defined(GTE_USE_MAT_VEC) + Real adjust = ((Real)1) / std::sqrt(axisSqrLen); +#else + Real adjust = ((Real)-1) / std::sqrt(axisSqrLen); +#endif + a.axis[0] = q[0] * adjust; + a.axis[1] = q[1] * adjust; + a.axis[2] = q[2] * adjust; + Real cs = std::max(std::min(q[3], (Real)1), (Real)-1); + a.angle = (Real)2 * std::acos(cs); + } + else + { + // The angle is 0 (modulo 2*pi). Any axis will work, so choose + // the Unit(0) axis. + a.axis[0] = (Real)1; + a.angle = (Real)0; + } + } + + // Convert an axis-angle pair to a quaternion, where + // q = sin(angle/2)*(axis[0]*i+axis[1]*j+axis[2]*k)+cos(angle/2) + static void Convert(AxisAngle const& a, Quaternion& q) + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + +#if defined(GTE_USE_MAT_VEC) + Real halfAngle = (Real)0.5 * a.angle; +#else + Real halfAngle = (Real)-0.5 * a.angle; +#endif + Real sn = std::sin(halfAngle); + q[0] = sn * a.axis[0]; + q[1] = sn * a.axis[1]; + q[2] = sn * a.axis[2]; + q[3] = std::cos(halfAngle); + } + + // Convert a quaternion to Euler angles. The quaternion is converted + // to a matrix which is then converted to Euler angles. + static void Convert(Quaternion const& q, EulerAngles& e) + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Matrix r; + Convert(q, r); + Convert(r, e); + } + + // Convert Euler angles to a quaternion. The Euler angles are + // converted to a matrix which is then converted to a quaternion. + static void Convert(EulerAngles const& e, Quaternion& q) + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Matrix r; + Convert(e, r); + Convert(r, q); + } + + // Convert an axis-angle pair to Euler angles. The axis-angle pair + // is converted to a quaternion which is then converted to Euler + // angles. + static void Convert(AxisAngle const& a, EulerAngles& e) + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Quaternion q; + Convert(a, q); + Convert(q, e); + } + + // Convert Euler angles to an axis-angle pair. The Euler angles are + // converted to a quaternion which is then converted to an axis-angle + // pair. + static void Convert(EulerAngles const& e, AxisAngle& a) + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Quaternion q; + Convert(e, q); + Convert(q, a); + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Sector2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Sector2.h new file mode 100644 index 0000000..1fed9cc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Sector2.h @@ -0,0 +1,137 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// A solid sector is the intersection of a disk and a 2D cone. The disk +// has center C, radius R, and contains points X for which |X-C| <= R. The +// 2D cone has vertex C, unit-length axis direction D, angle A in (0,pi) +// measured from D, and contains points X for which +// Dot(D,(X-C)/|X-C|) >= cos(A). Sector points X satisfy both inequality +// constraints. + +namespace WwiseGTE +{ + template + class Sector2 + { + public: + // Construction and destruction. The default constructor sets the + // vertex to (0,0), radius to 1, axis direction to (1,0), and angle + // to pi, all of which define a disk. + Sector2() + : + vertex(Vector2::Zero()), + radius((Real)1), + direction(Vector2::Unit(0)), + angle((Real)GTE_C_PI), + cosAngle((Real)-1), + sinAngle((Real)0) + { + } + + Sector2(Vector2 const& inVertex, Real inRadius, + Vector2 const& inDirection, Real inAngle) + : + vertex(inVertex), + radius(inRadius), + direction(inDirection) + { + SetAngle(inAngle); + } + + // Set the angle and cos(angle) simultaneously. + void SetAngle(Real inAngle) + { + angle = inAngle; + cosAngle = std::cos(angle); + sinAngle = std::sin(angle); + } + + // Test whether P is in the sector. + bool Contains(Vector2 const& p) const + { + Vector2 diff = p - vertex; + Real length = Length(diff); + return length <= radius && Dot(direction, diff) >= length * cosAngle; + } + + // The cosine and sine of the angle are used in queries, so all o + // angle, cos(angle), and sin(angle) are stored. If you set 'angle' + // via the public members, you must set all to be consistent. You + // can also call SetAngle(...) to ensure consistency. + Vector2 vertex; + Real radius; + Vector2 direction; + Real angle, cosAngle, sinAngle; + + public: + // Comparisons to support sorted containers. + bool operator==(Sector2 const& sector) const + { + return vertex == sector.vertex && radius == sector.radius + && direction == sector.direction && angle == sector.angle; + } + + bool operator!=(Sector2 const& sector) const + { + return !operator==(sector); + } + + bool operator< (Sector2 const& sector) const + { + if (vertex < sector.vertex) + { + return true; + } + + if (vertex > sector.vertex) + { + return false; + } + + if (radius < sector.radius) + { + return true; + } + + if (radius > sector.radius) + { + return false; + } + + if (direction < sector.direction) + { + return true; + } + + if (direction > sector.direction) + { + return false; + } + + return angle < sector.angle; + } + + bool operator<=(Sector2 const& sector) const + { + return !sector.operator<(*this); + } + + bool operator> (Sector2 const& sector) const + { + return sector.operator<(*this); + } + + bool operator>=(Sector2 const& sector) const + { + return !operator<(sector); + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Segment.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Segment.h new file mode 100644 index 0000000..74e0119 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Segment.h @@ -0,0 +1,112 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The segment is represented by (1-t)*P0 + t*P1, where P0 and P1 are the +// endpoints of the segment and 0 <= t <= 1. Some algorithms prefer a +// centered representation that is similar to how oriented bounding boxes are +// defined. This representation is C + s*D, where C = (P0 + P1)/2 is the +// center of the segment, D = (P1 - P0)/|P1 - P0| is a unit-length direction +// vector for the segment, and |t| <= e. The value e = |P1 - P0|/2 is the +// extent (or radius or half-length) of the segment. + +namespace WwiseGTE +{ + template + class Segment + { + public: + // Construction and destruction. The default constructor sets p0 to + // (-1,0,...,0) and p1 to (1,0,...,0). NOTE: If you set p0 and p1; + // compute C, D, and e; and then recompute q0 = C-e*D and q1 = C+e*D, + // numerical round-off errors can lead to q0 not exactly equal to p0 + // and q1 not exactly equal to p1. + Segment() + { + p[1].MakeUnit(0); + p[0] = -p[1]; + } + + Segment(Vector const& p0, Vector const& p1) + : + p{ p0, p1 } + { + } + + Segment(std::array, 2> const& inP) + : + p(inP) + { + } + + Segment(Vector const& center, Vector const& direction, Real extent) + { + SetCenteredForm(center, direction, extent); + } + + // Manipulation via the centered form. + void SetCenteredForm(Vector const& center, + Vector const& direction, Real extent) + { + p[0] = center - extent * direction; + p[1] = center + extent * direction; + } + + void GetCenteredForm(Vector& center, + Vector& direction, Real& extent) const + { + center = (Real)0.5 * (p[0] + p[1]); + direction = p[1] - p[0]; + extent = (Real)0.5 * Normalize(direction); + } + + // Public member access. + std::array, 2> p; + + public: + // Comparisons to support sorted containers. + bool operator==(Segment const& segment) const + { + return p == segment.p; + } + + bool operator!=(Segment const& segment) const + { + return p != segment.p; + } + + bool operator< (Segment const& segment) const + { + return p < segment.p; + } + + bool operator<=(Segment const& segment) const + { + return p <= segment.p; + } + + bool operator> (Segment const& segment) const + { + return p > segment.p; + } + + bool operator>=(Segment const& segment) const + { + return p >= segment.p; + } + }; + + // Template aliases for convenience. + template + using Segment2 = Segment<2, Real>; + + template + using Segment3 = Segment<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SeparatePoints2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SeparatePoints2.h new file mode 100644 index 0000000..f9b68f9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SeparatePoints2.h @@ -0,0 +1,205 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// Separate two point sets, if possible, by computing a line for which the +// point sets lie on opposite sides. The algorithm computes the convex hull +// of the point sets, then uses the method of separating axes to determine +// whether the two convex polygons are disjoint. +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The ComputeType is for the ConvexHull2 class. + +namespace WwiseGTE +{ + template + class SeparatePoints2 + { + public: + // The return value is 'true' if and only if there is a separation. + // If 'true', the returned line is a separating line. The code + // assumes that each point set has at least 3 noncollinear points. + bool operator()(int numPoints0, Vector2 const* points0, + int numPoints1, Vector2 const* points1, + Line2& separatingLine) const + { + // Construct convex hull of point set 0. + ConvexHull2 ch0; + ch0(numPoints0, points0, (Real)0); + if (ch0.GetDimension() != 2) + { + return false; + } + + // Construct convex hull of point set 1. + ConvexHull2 ch1; + ch1(numPoints1, points1, (Real)0); + if (ch1.GetDimension() != 2) + { + return false; + } + + int numEdges0 = static_cast(ch0.GetHull().size()); + int const* edges0 = &ch0.GetHull()[0]; + int numEdges1 = static_cast(ch1.GetHull().size()); + int const* edges1 = &ch1.GetHull()[0]; + + // Test edges of hull 0 for possible separation of points. + int j0, j1, i0, i1, side0, side1; + Vector2 lineNormal; + Real lineConstant; + for (j1 = 0, j0 = numEdges0 - 1; j1 < numEdges0; j0 = j1++) + { + // Look up edge (assert: i0 != i1 ). + i0 = edges0[j0]; + i1 = edges0[j1]; + + // Compute potential separating line + // (assert: (xNor,yNor) != (0,0)). + separatingLine.origin = points0[i0]; + separatingLine.direction = points0[i1] - points0[i0]; + Normalize(separatingLine.direction); + lineNormal = Perp(separatingLine.direction); + lineConstant = Dot(lineNormal, separatingLine.origin); + + // Determine whether hull 1 is on same side of line. + side1 = OnSameSide(lineNormal, lineConstant, numEdges1, edges1, + points1); + + if (side1) + { + // Determine on which side of line hull 0 lies. + side0 = WhichSide(lineNormal, lineConstant, numEdges0, + edges0, points0); + + if (side0 * side1 <= 0) // Line separates hulls. + { + return true; + } + } + } + + // Test edges of hull 1 for possible separation of points. + for (j1 = 0, j0 = numEdges1 - 1; j1 < numEdges1; j0 = j1++) + { + // Look up edge (assert: i0 != i1 ). + i0 = edges1[j0]; + i1 = edges1[j1]; + + // Compute perpendicular to edge + // (assert: (xNor,yNor) != (0,0)). + separatingLine.origin = points1[i0]; + separatingLine.direction = points1[i1] - points1[i0]; + Normalize(separatingLine.direction); + lineNormal = Perp(separatingLine.direction); + lineConstant = Dot(lineNormal, separatingLine.origin); + + // Determine whether hull 0 is on same side of line. + side0 = OnSameSide(lineNormal, lineConstant, numEdges0, edges0, + points0); + + if (side0) + { + // Determine on which side of line hull 1 lies. + side1 = WhichSide(lineNormal, lineConstant, numEdges1, + edges1, points1); + + if (side0 * side1 <= 0) // Line separates hulls. + { + return true; + } + } + } + + return false; + } + + private: + int OnSameSide(Vector2 const& lineNormal, Real lineConstant, + int numEdges, int const* edges, Vector2 const* points) const + { + // Test whether all points on same side of line Dot(N,X) = c. + Real c0; + int posSide = 0, negSide = 0; + + for (int i1 = 0, i0 = numEdges - 1; i1 < numEdges; i0 = i1++) + { + c0 = Dot(lineNormal, points[edges[i0]]); + if (c0 > lineConstant) + { + ++posSide; + } + else if (c0 < lineConstant) + { + ++negSide; + } + + if (posSide && negSide) + { + // Line splits point set. + return 0; + } + + c0 = Dot(lineNormal, points[edges[i1]]); + if (c0 > lineConstant) + { + ++posSide; + } + else if (c0 < lineConstant) + { + ++negSide; + } + + if (posSide && negSide) + { + // Line splits point set. + return 0; + } + } + + return (posSide ? +1 : -1); + } + + int WhichSide(Vector2 const& lineNormal, Real lineConstant, + int numEdges, int const* edges, Vector2 const* points) const + { + // Establish which side of line hull is on. + Real c0; + for (int i1 = 0, i0 = numEdges - 1; i1 < numEdges; i0 = i1++) + { + c0 = Dot(lineNormal, points[edges[i0]]); + if (c0 > lineConstant) + { + // Hull on positive side. + return +1; + } + if (c0 < lineConstant) + { + // Hull on negative side. + return -1; + } + + c0 = Dot(lineNormal, points[edges[i1]]); + if (c0 > lineConstant) + { + // Hull on positive side. + return +1; + } + if (c0 < lineConstant) + { + // Hull on negative side. + return -1; + } + } + + // Hull is effectively collinear. + return 0; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SeparatePoints3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SeparatePoints3.h new file mode 100644 index 0000000..68a1ea5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SeparatePoints3.h @@ -0,0 +1,227 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2020.01.11 + +#pragma once + +#include + +// Separate two point sets, if possible, by computing a plane for which the +// point sets lie on opposite sides. The algorithm computes the convex hull +// of the point sets, then uses the method of separating axes to determine +// whether the two convex polyhedra are disjoint. +// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf +// The ComputeType is for the ConvexHull3 class. + +namespace WwiseGTE +{ + template + class SeparatePoints3 + { + public: + // The return value is 'true' if and only if there is a separation. + // If 'true', the returned plane is a separating plane. The code + // assumes that each point set has at least 4 noncoplanar points. + bool operator()(int numPoints0, Vector3 const* points0, + int numPoints1, Vector3 const* points1, + Plane3& separatingPlane) const + { + // Construct convex hull of point set 0. + ConvexHull3 ch0; + ch0(numPoints0, points0, (Real)0); + if (ch0.GetDimension() != 3) + { + return false; + } + + // Construct convex hull of point set 1. + ConvexHull3 ch1; + ch1(numPoints1, points1, (Real)0); + if (ch1.GetDimension() != 3) + { + return false; + } + + auto const& hull0 = ch0.GetHullUnordered(); + auto const& hull1 = ch1.GetHullUnordered(); + int numTriangles0 = static_cast(hull0.size()); + int const* indices0 = reinterpret_cast(&hull0[0]); + int numTriangles1 = static_cast(hull1.size()); + int const* indices1 = reinterpret_cast(&hull1[0]); + + // Test faces of hull 0 for possible separation of points. + int i, i0, i1, i2, side0, side1; + Vector3 diff0, diff1; + for (i = 0; i < numTriangles0; ++i) + { + // Look up face (assert: i0 != i1 && i0 != i2 && i1 != i2). + i0 = indices0[3 * i]; + i1 = indices0[3 * i + 1]; + i2 = indices0[3 * i + 2]; + + // Compute potential separating plane + // (assert: normal != (0,0,0)). + separatingPlane = Plane3({ points0[i0], points0[i1], points0[i2] }); + + // Determine whether hull 1 is on same side of plane. + side1 = OnSameSide(separatingPlane, numTriangles1, indices1, points1); + + if (side1) + { + // Determine on which side of plane hull 0 lies. + side0 = WhichSide(separatingPlane, numTriangles0, indices0, points0); + if (side0 * side1 <= 0) // Plane separates hulls. + { + return true; + } + } + } + + // Test faces of hull 1 for possible separation of points. + for (i = 0; i < numTriangles1; ++i) + { + // Look up edge (assert: i0 != i1 && i0 != i2 && i1 != i2). + i0 = indices1[3 * i]; + i1 = indices1[3 * i + 1]; + i2 = indices1[3 * i + 2]; + + // Compute perpendicular to face + // (assert: normal != (0,0,0)). + separatingPlane = Plane3({ points1[i0], points1[i1], points1[i2] }); + + // Determine whether hull 0 is on same side of plane. + side0 = OnSameSide(separatingPlane, numTriangles0, indices0, points0); + if (side0) + { + // Determine on which side of plane hull 1 lies. + side1 = WhichSide(separatingPlane, numTriangles1, indices1, + points1); + if (side0 * side1 <= 0) // Plane separates hulls. + { + return true; + } + } + } + + // Build edge set for hull 0. + std::set> edgeSet0; + for (i = 0; i < numTriangles0; ++i) + { + // Look up face (assert: i0 != i1 && i0 != i2 && i1 != i2). + i0 = indices0[3 * i]; + i1 = indices0[3 * i + 1]; + i2 = indices0[3 * i + 2]; + edgeSet0.insert(std::make_pair(i0, i1)); + edgeSet0.insert(std::make_pair(i0, i2)); + edgeSet0.insert(std::make_pair(i1, i2)); + } + + // Build edge list for hull 1. + std::set> edgeSet1; + for (i = 0; i < numTriangles1; ++i) + { + // Look up face (assert: i0 != i1 && i0 != i2 && i1 != i2). + i0 = indices1[3 * i]; + i1 = indices1[3 * i + 1]; + i2 = indices1[3 * i + 2]; + edgeSet1.insert(std::make_pair(i0, i1)); + edgeSet1.insert(std::make_pair(i0, i2)); + edgeSet1.insert(std::make_pair(i1, i2)); + } + + // Test planes whose normals are cross products of two edges, one + // from each hull. + for (auto const& e0 : edgeSet0) + { + // Get edge. + diff0 = points0[e0.second] - points0[e0.first]; + + for (auto const& e1 : edgeSet1) + { + diff1 = points1[e1.second] - points1[e1.first]; + + // Compute potential separating plane. + separatingPlane.normal = UnitCross(diff0, diff1); + separatingPlane.constant = Dot(separatingPlane.normal, + points0[e0.first]); + + // Determine if hull 0 is on same side of plane. + side0 = OnSameSide(separatingPlane, numTriangles0, indices0, + points0); + side1 = OnSameSide(separatingPlane, numTriangles1, indices1, + points1); + + if (side0 * side1 < 0) // Plane separates hulls. + { + return true; + } + } + } + + return false; + } + + private: + int OnSameSide(Plane3 const& plane, int numTriangles, + int const* indices, Vector3 const* points) const + { + // Test whether all points on same side of plane Dot(N,X) = c. + int posSide = 0, negSide = 0; + + for (int t = 0; t < numTriangles; ++t) + { + for (int i = 0; i < 3; ++i) + { + int v = indices[3 * t + i]; + Real c0 = Dot(plane.normal, points[v]); + if (c0 > plane.constant) + { + ++posSide; + } + else if (c0 < plane.constant) + { + ++negSide; + } + + if (posSide && negSide) + { + // Plane splits point set. + return 0; + } + } + } + + return (posSide ? +1 : -1); + } + + int WhichSide(Plane3 const& plane, int numTriangles, + int const* indices, Vector3 const* points) const + { + // Establish which side of plane hull is on. + for (int t = 0; t < numTriangles; ++t) + { + for (int i = 0; i < 3; ++i) + { + int v = indices[3 * t + i]; + Real c0 = Dot(plane.normal, points[v]); + if (c0 > plane.constant) + { + // Positive side. + return +1; + } + if (c0 < plane.constant) + { + // Negative side. + return -1; + } + } + } + + // Hull is effectively collinear. + return 0; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SharedPtrCompare.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SharedPtrCompare.h new file mode 100644 index 0000000..ba69173 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SharedPtrCompare.h @@ -0,0 +1,86 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// Comparison operators for std::shared_ptr objects. The type T must implement +// comparison operators. You must be careful when managing containers whose +// ordering is based on std::shared_ptr comparisons. The underlying objects +// can change, which invalidates the container ordering. If objects do not +// change while the container persists, these are safe to use. +// +// NOTE: std::shared_ptr already has comparison operators, but these +// compare pointer values instead of comparing the objects referenced by the +// pointers. If a container sorted using std::shared_ptr is created for +// two different executions of a program, the object ordering implied by the +// pointer ordering can differ. This might be undesirable for reproducibility +// of results between executions. + +namespace WwiseGTE +{ + // sp0 == sp1 + template + struct SharedPtrEQ + { + bool operator()(std::shared_ptr const& sp0, std::shared_ptr const& sp1) const + { + return (sp0 ? (sp1 ? *sp0 == *sp1 : false) : !sp1); + } + }; + + // sp0 != sp1 + template + struct SharedPtrNEQ + { + bool operator()(std::shared_ptr const& sp0, std::shared_ptr const& sp1) const + { + return !SharedPtrEQ()(sp0, sp1); + } + }; + + // sp0 < sp1 + template + struct SharedPtrLT + { + bool operator()(std::shared_ptr const& sp0, std::shared_ptr const& sp1) const + { + return (sp1 ? (!sp0 || *sp0 < *sp1) : false); + } + }; + + // sp0 <= sp1 + template + struct SharedPtrLTE + { + bool operator()(std::shared_ptr const& sp0, std::shared_ptr const& sp1) const + { + return !SharedPtrLT()(sp1, sp0); + } + }; + + // sp0 > sp1 + template + struct SharedPtrGT + { + bool operator()(std::shared_ptr const& sp0, std::shared_ptr const& sp1) const + { + return SharedPtrLT()(sp1, sp0); + } + }; + + // sp0 >= sp1 + template + struct SharedPtrGTE + { + bool operator()(std::shared_ptr const& sp0, std::shared_ptr const& sp1) const + { + return !SharedPtrLT()(sp0, sp1); + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SinEstimate.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SinEstimate.h new file mode 100644 index 0000000..ce80943 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SinEstimate.h @@ -0,0 +1,136 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// Minimax polynomial approximations to sin(x). The polynomial p(x) of +// degree D has only odd-power terms, is required to have linear term x, +// and p(pi/2) = sin(pi/2) = 1. It minimizes the quantity +// maximum{|sin(x) - p(x)| : x in [-pi/2,pi/2]} over all polynomials of +// degree D subject to the constraints mentioned. + +namespace WwiseGTE +{ + template + class SinEstimate + { + public: + // The input constraint is x in [-pi/2,pi/2]. For example, + // float x; // in [-pi/2,pi/2] + // float result = SinEstimate::Degree<3>(x); + template + inline static Real Degree(Real x) + { + return Evaluate(degree(), x); + } + + // The input x can be any real number. Range reduction is used to + // generate a value y in [-pi/2,pi/2] for which sin(y) = sin(x). + // For example, + // float x; // x any real number + // float result = SinEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x) + { + return Degree(Reduce(x)); + } + + private: + // Metaprogramming and private implementation to allow specialization + // of a template member function. + template struct degree {}; + + inline static Real Evaluate(degree<3>, Real x) + { + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_SIN_DEG3_C1; + poly = (Real)GTE_C_SIN_DEG3_C0 + poly * xsqr; + poly = poly * x; + return poly; + } + + inline static Real Evaluate(degree<5>, Real x) + { + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_SIN_DEG5_C2; + poly = (Real)GTE_C_SIN_DEG5_C1 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG5_C0 + poly * xsqr; + poly = poly * x; + return poly; + } + + inline static Real Evaluate(degree<7>, Real x) + { + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_SIN_DEG7_C3; + poly = (Real)GTE_C_SIN_DEG7_C2 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG7_C1 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG7_C0 + poly * xsqr; + poly = poly * x; + return poly; + } + + inline static Real Evaluate(degree<9>, Real x) + { + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_SIN_DEG9_C4; + poly = (Real)GTE_C_SIN_DEG9_C3 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG9_C2 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG9_C1 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG9_C0 + poly * xsqr; + poly = poly * x; + return poly; + } + + inline static Real Evaluate(degree<11>, Real x) + { + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_SIN_DEG11_C5; + poly = (Real)GTE_C_SIN_DEG11_C4 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG11_C3 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG11_C2 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG11_C1 + poly * xsqr; + poly = (Real)GTE_C_SIN_DEG11_C0 + poly * xsqr; + poly = poly * x; + return poly; + } + + // Support for range reduction. + inline static Real Reduce(Real x) + { + // Map x to y in [-pi,pi], x = 2*pi*quotient + remainder. + Real quotient = (Real)GTE_C_INV_TWO_PI * x; + if (x >= (Real)0) + { + quotient = (Real)((int)(quotient + (Real)0.5)); + } + else + { + quotient = (Real)((int)(quotient - (Real)0.5)); + } + Real y = x - (Real)GTE_C_TWO_PI * quotient; + + // Map y to [-pi/2,pi/2] with sin(y) = sin(x). + if (y > (Real)GTE_C_HALF_PI) + { + y = (Real)GTE_C_PI - y; + } + else if (y < (Real)-GTE_C_HALF_PI) + { + y = (Real)-GTE_C_PI - y; + } + return y; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SingularValueDecomposition.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SingularValueDecomposition.h new file mode 100644 index 0000000..ee6aea3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SingularValueDecomposition.h @@ -0,0 +1,1101 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// The SingularValueDecomposition class is an implementation of Algorithm +// 8.3.2 (The SVD Algorithm) described in "Matrix Computations, 2nd +// edition" by G. H. Golub and Charles F. Van Loan, The Johns Hopkins +// Press, Baltimore MD, Fourth Printing 1993. Algorithm 5.4.2 (Householder +// Bidiagonalization) is used to reduce A to bidiagonal B. Algorithm 8.3.1 +// (Golub-Kahan SVD Step) is used for the iterative reduction from bidiagonal +// to diagonal. If A is the original matrix, S is the matrix whose diagonal +// entries are the singular values, and U and V are corresponding matrices, +// then theoretically U^T*A*V = S. Numerically, we have errors +// E = U^T*A*V - S. Algorithm 8.3.2 mentions that one expects |E| is +// approximately u*|A|, where |M| denotes the Frobenius norm of M and where +// u is the unit roundoff for the floating-point arithmetic: 2^{-23} for +// 'float', which is FLT_EPSILON = 1.192092896e-7f, and 2^{-52} for'double', +// which is DBL_EPSILON = 2.2204460492503131e-16. +// +// The condition |a(i,i+1)| <= epsilon*(|a(i,i) + a(i+1,i+1)|) used to +// determine when the reduction decouples to smaller problems is implemented +// as: sum = |a(i,i)| + |a(i+1,i+1)|; sum + |a(i,i+1)| == sum. The idea is +// that the superdiagonal term is small relative to its diagonal neighbors, +// and so it is effectively zero. The unit tests have shown that this +// interpretation of decoupling is effective. +// +// The condition |a(i,i)| <= epsilon*|B| used to determine when the +// reduction decouples (with a zero singular value) is implemented using +// the Frobenius norm of B and epsilon = multiplier*u, where for now the +// multiplier is hard-coded in Solve(...) as 8. +// +// The authors suggest that once you have the bidiagonal matrix, a practical +// implementation will store the diagonal and superdiagonal entries in linear +// arrays, ignoring the theoretically zero values not in the 2-band. This is +// good for cache coherence, and we have used the suggestion. The essential +// parts of the Householder u-vectors are stored in the lower-triangular +// portion of the matrix and the essential parts of the Householder v-vectors +// are stored in the upper-triangular portion of the matrix. To avoid having +// to recompute 2/Dot(u,u) and 2/Dot(v,v) when constructing orthogonal U and +// V, we store these quantities in additional memory during bidiagonalization. +// +// For matrices with randomly generated values in [0,1], the unit tests +// produce the following information for N-by-N matrices. +// +// N |A| |E| |E|/|A| iterations +// ------------------------------------------- +// 2 1.4831 4.1540e-16 2.8007e-16 1 +// 3 2.1065 3.5024e-16 1.6626e-16 4 +// 4 2.4979 7.4605e-16 2.9867e-16 6 +// 5 3.6591 1.8305e-15 5.0025e-16 9 +// 6 4.0572 2.0571e-15 5.0702e-16 10 +// 7 4.7745 2.9057e-15 6.0859e-16 12 +// 8 5.1964 2.7958e-15 5.3803e-16 13 +// 9 5.7599 3.3128e-15 5.7514e-16 16 +// 10 6.2700 3.7209e-15 5.9344e-16 16 +// 11 6.8220 5.0580e-15 7.4142e-16 18 +// 12 7.4540 5.2493e-15 7.0422e-16 21 +// 13 8.1225 5.6043e-15 6.8997e-16 24 +// 14 8.5883 5.8553e-15 6.8177e-16 26 +// 15 9.1337 6.9663e-15 7.6270e-16 27 +// 16 9.7884 9.1358e-15 9.3333e-16 29 +// 17 10.2407 8.2715e-15 8.0771e-16 34 +// 18 10.7147 8.9748e-15 8.3761e-16 33 +// 19 11.1887 1.0094e-14 9.0220e-16 32 +// 20 11.7739 9.7000e-15 8.2386e-16 35 +// 21 12.2822 1.1217e-14 9.1329e-16 36 +// 22 12.7649 1.1071e-14 8.6732e-16 37 +// 23 13.3366 1.1271e-14 8.4513e-16 41 +// 24 13.8505 1.2806e-14 9.2460e-16 43 +// 25 14.4332 1.3081e-14 9.0637e-16 43 +// 26 14.9301 1.4882e-14 9.9680e-16 46 +// 27 15.5214 1.5571e-14 1.0032e-15 48 +// 28 16.1029 1.7553e-14 1.0900e-15 49 +// 29 16.6407 1.6219e-14 9.7467e-16 53 +// 30 17.1891 1.8560e-14 1.0797e-15 55 +// 31 17.7773 1.8522e-14 1.0419e-15 56 +// +// The singularvalues and |E|/|A| values were compared to those generated by +// Mathematica Version 9.0; Wolfram Research, Inc., Champaign IL, 2012. +// The results were all comparable with singular values agreeing to a large +// number of decimal places. +// +// Timing on an Intel (R) Core (TM) i7-3930K CPU @ 3.20 GHZ (in seconds) +// for NxN matrices: +// +// N |E|/|A| iters bidiag QR U-and-V comperr +// ------------------------------------------------------- +// 512 3.8632e-15 848 0.341 0.016 1.844 2.203 +// 1024 5.6456e-15 1654 4.279 0.032 18.765 20.844 +// 2048 7.5499e-15 3250 40.421 0.141 186.607 213.216 +// +// where iters is the number of QR steps taken, bidiag is the computation +// of the Householder reflection vectors, U-and-V is the composition of +// Householder reflections and Givens rotations to obtain the orthogonal +// matrices of the decomposigion, and comperr is the computation E = +// U^T*A*V - S. + +namespace WwiseGTE +{ + template + class SingularValueDecomposition + { + public: + // The solver processes MxN symmetric matrices, where M >= N > 1 + // ('numRows' is M and 'numCols' is N) and the matrix is stored in + // row-major order. The maximum number of iterations + // ('maxIterations') must be specified for the reduction of a + // bidiagonal matrix to a diagonal matrix. The goal is to compute + // MxM orthogonal U, NxN orthogonal V and MxN matrix S for which + // U^T*A*V = S. The only nonzero entries of S are on the diagonal; + // the diagonal entries are the singular values of the original + // matrix. + SingularValueDecomposition(int numRows, int numCols, unsigned int maxIterations) + : + mNumRows(0), + mNumCols(0), + mMaxIterations(0) + { + if (numCols > 1 && numRows >= numCols && maxIterations > 0) + { + mNumRows = numRows; + mNumCols = numCols; + mMaxIterations = maxIterations; + mMatrix.resize(numRows * numCols); + mDiagonal.resize(numCols); + mSuperdiagonal.resize(numCols - 1); + mRGivens.reserve(maxIterations * (numCols - 1)); + mLGivens.reserve(maxIterations * (numCols - 1)); + mFixupDiagonal.resize(numCols); + mPermutation.resize(numCols); + mVisited.resize(numCols); + mTwoInvUTU.resize(numCols); + mTwoInvVTV.resize(numCols - 2); + mUVector.resize(numRows); + mVVector.resize(numCols); + mWVector.resize(numRows); + } + } + + // A copy of the MxN input is made internally. The order of the + // singular values is specified by sortType: -1 (decreasing), + // 0 (no sorting), or +1 (increasing). When sorted, the columns of + // the orthogonal matrices are ordered accordingly. The return value + // is the number of iterations consumed when convergence occurred, + // 0xFFFFFFFF when convergence did not occur or 0 when N <= 1 or + // M < N was passed to the constructor. + unsigned int Solve(Real const* input, int sortType) + { + if (mNumRows > 0) + { + int numElements = mNumRows * mNumCols; + std::copy(input, input + numElements, mMatrix.begin()); + Bidiagonalize(); + + // Compute 'threshold = multiplier*epsilon*|B|' as the + // threshold for diagonal entries effectively zero; that is, + // |d| <= |threshold| implies that d is (effectively) zero. + // TODO: Allow the caller to pass 'multiplier' to the + // constructor. + // + // We will use the L2-norm |B|, which is the length of the + // elements of B treated as an NM-tuple. The following code + // avoids overflow when accumulating the squares of the + // elements when those elements are large. + Real maxAbsComp = std::fabs(input[0]); + for (int i = 1; i < numElements; ++i) + { + Real absComp = std::fabs(input[i]); + if (absComp > maxAbsComp) + { + maxAbsComp = absComp; + } + } + + Real norm = (Real)0; + if (maxAbsComp > (Real)0) + { + Real invMaxAbsComp = ((Real)1) / maxAbsComp; + for (int i = 0; i < numElements; ++i) + { + Real ratio = input[i] * invMaxAbsComp; + norm += ratio * ratio; + } + norm = maxAbsComp * std::sqrt(norm); + } + + Real const multiplier = (Real)8; // TODO: Expose to caller. + Real const epsilon = std::numeric_limits::epsilon(); + Real const threshold = multiplier * epsilon * norm; + + mRGivens.clear(); + mLGivens.clear(); + for (unsigned int j = 0; j < mMaxIterations; ++j) + { + int imin = -1, imax = -1; + for (int i = mNumCols - 2; i >= 0; --i) + { + // When a01 is much smaller than its diagonal + // neighbors, it is effectively zero. + Real a00 = mDiagonal[i]; + Real a01 = mSuperdiagonal[i]; + Real a11 = mDiagonal[i + 1]; + Real sum = std::fabs(a00) + std::fabs(a11); + if (sum + std::fabs(a01) != sum) + { + if (imax == -1) + { + imax = i; + } + imin = i; + } + else + { + // The superdiagonal term is effectively zero + // compared to the neighboring diagonal terms. + if (imin >= 0) + { + break; + } + } + } + + if (imax == -1) + { + // The algorithm has converged. + EnsureNonnegativeDiagonal(); + ComputePermutation(sortType); + return j; + } + + // We need to test diagonal entries of B for zero. For + // each zero diagonal entry, zero the superdiagonal. + if (DiagonalEntriesNonzero(imin, imax, threshold)) + { + // Process the lower-right-most unreduced bidiagonal + // block. + DoGolubKahanStep(imin, imax); + } + } + return 0xFFFFFFFF; + } + else + { + return 0; + } + } + + // Get the singular values of the matrix passed to Solve(...). The + // input 'singularValues' must have N elements. + void GetSingularValues(Real* singularValues) const + { + if (singularValues && mNumCols > 0) + { + if (mPermutation[0] >= 0) + { + // Sorting was requested. + for (int i = 0; i < mNumCols; ++i) + { + int p = mPermutation[i]; + singularValues[i] = mDiagonal[p]; + } + } + else + { + // Sorting was not requested. + size_t numBytes = mNumCols * sizeof(Real); + std::memcpy(singularValues, &mDiagonal[0], numBytes); + } + } + } + + // Accumulate the Householder reflections, the Givens rotations, and + // the diagonal fix-up matrix to compute the orthogonal matrices U and + // V for which U^T*A*V = S. The input uMatrix must be MxM and the + // input vMatrix must be NxN, both stored in row-major order. + void GetU(Real* uMatrix) const + { + if (!uMatrix || mNumCols == 0) + { + // Invalid input or the constructor failed. + return; + } + + // Start with the identity matrix. + std::fill(uMatrix, uMatrix + mNumRows * mNumRows, (Real)0); + for (int d = 0; d < mNumRows; ++d) + { + uMatrix[d + mNumRows * d] = (Real)1; + } + + // Multiply the Householder reflections using backward + // accumulation. + int r, c; + for (int i0 = mNumCols - 1, i1 = i0 + 1; i0 >= 0; --i0, --i1) + { + // Copy the u vector and 2/Dot(u,u) from the matrix. + Real twoinvudu = mTwoInvUTU[i0]; + Real const* column = &mMatrix[i0]; + mUVector[i0] = (Real)1; + for (r = i1; r < mNumRows; ++r) + { + mUVector[r] = column[mNumCols * r]; + } + + // Compute the w vector. + mWVector[i0] = twoinvudu; + for (r = i1; r < mNumRows; ++r) + { + mWVector[r] = (Real)0; + for (c = i1; c < mNumRows; ++c) + { + mWVector[r] += mUVector[c] * uMatrix[r + mNumRows * c]; + } + mWVector[r] *= twoinvudu; + } + + // Update the matrix, U <- U - u*w^T. + for (r = i0; r < mNumRows; ++r) + { + for (c = i0; c < mNumRows; ++c) + { + uMatrix[c + mNumRows * r] -= mUVector[r] * mWVector[c]; + } + } + } + + // Multiply the Givens rotations. + for (auto const& givens : mLGivens) + { + int j0 = givens.index0; + int j1 = givens.index1; + for (r = 0; r < mNumRows; ++r, j0 += mNumRows, j1 += mNumRows) + { + Real& q0 = uMatrix[j0]; + Real& q1 = uMatrix[j1]; + Real prd0 = givens.cs * q0 - givens.sn * q1; + Real prd1 = givens.sn * q0 + givens.cs * q1; + q0 = prd0; + q1 = prd1; + } + } + + if (mPermutation[0] >= 0) + { + // Sorting was requested. + std::fill(mVisited.begin(), mVisited.end(), 0); + for (c = 0; c < mNumCols; ++c) + { + if (mVisited[c] == 0 && mPermutation[c] != c) + { + // The item starts a cycle with 2 or more elements. + int start = c, current = c, next; + for (r = 0; r < mNumRows; ++r) + { + mWVector[r] = uMatrix[c + mNumRows * r]; + } + while ((next = mPermutation[current]) != start) + { + mVisited[current] = 1; + for (r = 0; r < mNumRows; ++r) + { + uMatrix[current + mNumRows * r] = + uMatrix[next + mNumRows * r]; + } + current = next; + } + mVisited[current] = 1; + for (r = 0; r < mNumRows; ++r) + { + uMatrix[current + mNumRows * r] = mWVector[r]; + } + } + } + } + } + + void GetV(Real* vMatrix) const + { + if (!vMatrix || mNumCols == 0) + { + // Invalid input or the constructor failed. + return; + } + + // Start with the identity matrix. + std::fill(vMatrix, vMatrix + mNumCols * mNumCols, (Real)0); + for (int d = 0; d < mNumCols; ++d) + { + vMatrix[d + mNumCols * d] = (Real)1; + } + + // Multiply the Householder reflections using backward accumulation. + int i0 = mNumCols - 3; + int i1 = i0 + 1; + int i2 = i0 + 2; + int r, c; + for (/**/; i0 >= 0; --i0, --i1, --i2) + { + // Copy the v vector and 2/Dot(v,v) from the matrix. + Real twoinvvdv = mTwoInvVTV[i0]; + Real const* row = &mMatrix[mNumCols * i0]; + mVVector[i1] = (Real)1; + for (r = i2; r < mNumCols; ++r) + { + mVVector[r] = row[r]; + } + + // Compute the w vector. + mWVector[i1] = twoinvvdv; + for (r = i2; r < mNumCols; ++r) + { + mWVector[r] = (Real)0; + for (c = i2; c < mNumCols; ++c) + { + mWVector[r] += mVVector[c] * vMatrix[r + mNumCols * c]; + } + mWVector[r] *= twoinvvdv; + } + + // Update the matrix, V <- V - v*w^T. + for (r = i1; r < mNumCols; ++r) + { + for (c = i1; c < mNumCols; ++c) + { + vMatrix[c + mNumCols * r] -= mVVector[r] * mWVector[c]; + } + } + } + + // Multiply the Givens rotations. + for (auto const& givens : mRGivens) + { + int j0 = givens.index0; + int j1 = givens.index1; + for (c = 0; c < mNumCols; ++c, j0 += mNumCols, j1 += mNumCols) + { + Real& q0 = vMatrix[j0]; + Real& q1 = vMatrix[j1]; + Real prd0 = givens.cs * q0 - givens.sn * q1; + Real prd1 = givens.sn * q0 + givens.cs * q1; + q0 = prd0; + q1 = prd1; + } + } + + // Fix-up the diagonal. + for (r = 0; r < mNumCols; ++r) + { + for (c = 0; c < mNumCols; ++c) + { + vMatrix[c + mNumCols * r] *= mFixupDiagonal[c]; + } + } + + if (mPermutation[0] >= 0) + { + // Sorting was requested. + std::fill(mVisited.begin(), mVisited.end(), 0); + for (c = 0; c < mNumCols; ++c) + { + if (mVisited[c] == 0 && mPermutation[c] != c) + { + // The item starts a cycle with 2 or more elements. + int start = c, current = c, next; + for (r = 0; r < mNumCols; ++r) + { + mWVector[r] = vMatrix[c + mNumCols * r]; + } + while ((next = mPermutation[current]) != start) + { + mVisited[current] = 1; + for (r = 0; r < mNumCols; ++r) + { + vMatrix[current + mNumCols * r] = + vMatrix[next + mNumCols * r]; + } + current = next; + } + mVisited[current] = 1; + for (r = 0; r < mNumCols; ++r) + { + vMatrix[current + mNumCols * r] = mWVector[r]; + } + } + } + } + } + + // Compute a single column of U or V. The reflections and rotations + // are applied incrementally. This is useful when you want only a + // small number of the singular values or vectors. + void GetUColumn(int index, Real* uColumn) const + { + if (0 <= index && index < mNumRows) + { + // y = H*x, then x and y are swapped for the next H + Real* x = uColumn; + Real* y = &mWVector[0]; + + // Start with the Euclidean basis vector. + std::memset(x, 0, mNumRows * sizeof(Real)); + if (index < mNumCols && mPermutation[0] >= 0) + { + // Sorting was requested. + x[mPermutation[index]] = (Real)1; + } + else + { + x[index] = (Real)1; + } + + // Apply the Givens rotations. + for (auto const& givens : WwiseGTE::reverse(mLGivens)) + { + Real& xr0 = x[givens.index0]; + Real& xr1 = x[givens.index1]; + Real tmp0 = givens.cs * xr0 + givens.sn * xr1; + Real tmp1 = -givens.sn * xr0 + givens.cs * xr1; + xr0 = tmp0; + xr1 = tmp1; + } + + // Apply the Householder reflections. + for (int c = mNumCols - 1; c >= 0; --c) + { + // Get the Householder vector u. + int r; + for (r = 0; r < c; ++r) + { + y[r] = x[r]; + } + + // Compute s = Dot(x,u) * 2/u^T*u. + Real s = x[r]; // r = c, u[r] = 1 + for (int j = r + 1; j < mNumRows; ++j) + { + s += x[j] * mMatrix[c + mNumCols * j]; + } + s *= mTwoInvUTU[c]; + + // r = c, y[r] = x[r]*u[r] - s = x[r] - s because u[r] = 1 + y[r] = x[r] - s; + + // Compute the remaining components of y. + for (++r; r < mNumRows; ++r) + { + y[r] = x[r] - s * mMatrix[c + mNumCols * r]; + } + + std::swap(x, y); + } + + // The final product is stored in x. + if (x != uColumn) + { + size_t numBytes = mNumRows * sizeof(Real); + std::memcpy(uColumn, x, numBytes); + } + } + } + + void GetVColumn(int index, Real* vColumn) const + { + if (0 <= index && index < mNumCols) + { + // y = H*x, then x and y are swapped for the next H + Real* x = vColumn; + Real* y = &mWVector[0]; + + // Start with the Euclidean basis vector. + std::memset(x, 0, mNumCols * sizeof(Real)); + if (mPermutation[0] >= 0) + { + // Sorting was requested. + int p = mPermutation[index]; + x[p] = mFixupDiagonal[p]; + } + else + { + x[index] = mFixupDiagonal[index]; + } + + // Apply the Givens rotations. + for (auto const& givens : WwiseGTE::reverse(mRGivens)) + { + Real& xr0 = x[givens.index0]; + Real& xr1 = x[givens.index1]; + Real tmp0 = givens.cs * xr0 + givens.sn * xr1; + Real tmp1 = -givens.sn * xr0 + givens.cs * xr1; + xr0 = tmp0; + xr1 = tmp1; + } + + // Apply the Householder reflections. + for (int r = mNumCols - 3; r >= 0; --r) + { + // Get the Householder vector v. + int c; + for (c = 0; c < r + 1; ++c) + { + y[c] = x[c]; + } + + // Compute s = Dot(x,v) * 2/v^T*v. + Real s = x[c]; // c = r+1, v[c] = 1 + for (int j = c + 1; j < mNumCols; ++j) + { + s += x[j] * mMatrix[j + mNumCols * r]; + } + s *= mTwoInvVTV[r]; + + // c = r+1, y[c] = x[c]*v[c] - s = x[c] - s + // because v[c] = 1 + y[c] = x[c] - s; + + // Compute the remaining components of y. + for (++c; c < mNumCols; ++c) + { + y[c] = x[c] - s * mMatrix[c + mNumCols * r]; + } + + std::swap(x, y); + } + + // The final product is stored in x. + if (x != vColumn) + { + size_t numBytes = mNumCols * sizeof(Real); + std::memcpy(vColumn, x, numBytes); + } + } + } + + Real GetSingularValue(int index) const + { + if (0 <= index && index < mNumCols) + { + if (mPermutation[0] >= 0) + { + // Sorting was requested. + return mDiagonal[mPermutation[index]]; + } + else + { + // Sorting was not requested. + return mDiagonal[index]; + } + } + else + { + return (Real)0; + } + } + + private: + // Bidiagonalize using Householder reflections. On input, mMatrix is + // a copy of the input matrix and has one extra row. On output, the + // diagonal and superdiagonal contain the bidiagonalized results. The + // lower-triangular portion stores the essential parts of the + // Householder u vectors (the elements of u after the leading 1-valued + // component) and the upper-triangular portion stores the essential + // parts of the Householder v vectors. To avoid recomputing + // 2/Dot(u,u) and 2/Dot(v,v), these quantities are stored in + // mTwoInvUTU and mTwoInvVTV. + void Bidiagonalize() + { + int r, c; + for (int i = 0, ip1 = 1; i < mNumCols; ++i, ++ip1) + { + // Compute the U-Householder vector. + Real length = (Real)0; + for (r = i; r < mNumRows; ++r) + { + Real ur = mMatrix[i + mNumCols * r]; + mUVector[r] = ur; + length += ur * ur; + } + Real udu = (Real)1; + length = std::sqrt(length); + if (length > (Real)0) + { + Real& u1 = mUVector[i]; + Real sgn = (u1 >= (Real)0 ? (Real)1 : (Real)-1); + Real invDenom = (Real)1 / (u1 + sgn * length); + u1 = (Real)1; + for (r = ip1; r < mNumRows; ++r) + { + Real& ur = mUVector[r]; + ur *= invDenom; + udu += ur * ur; + } + } + + // Compute the rank-1 offset u*w^T. + Real invudu = (Real)1 / udu; + Real twoinvudu = invudu * (Real)2; + for (c = i; c < mNumCols; ++c) + { + mWVector[c] = (Real)0; + for (r = i; r < mNumRows; ++r) + { + mWVector[c] += mMatrix[c + mNumCols * r] * mUVector[r]; + } + mWVector[c] *= twoinvudu; + } + + // Update the input matrix. + for (r = i; r < mNumRows; ++r) + { + for (c = i; c < mNumCols; ++c) + { + mMatrix[c + mNumCols * r] -= mUVector[r] * mWVector[c]; + } + } + + if (i < mNumCols - 2) + { + // Compute the V-Householder vectors. + length = (Real)0; + for (c = ip1; c < mNumCols; ++c) + { + Real vc = mMatrix[c + mNumCols * i]; + mVVector[c] = vc; + length += vc * vc; + } + Real vdv = (Real)1; + length = std::sqrt(length); + if (length > (Real)0) + { + Real& v1 = mVVector[ip1]; + Real sgn = (v1 >= (Real)0 ? (Real)1 : (Real)-1); + Real invDenom = (Real)1 / (v1 + sgn * length); + v1 = (Real)1; + for (c = ip1 + 1; c < mNumCols; ++c) + { + Real& vc = mVVector[c]; + vc *= invDenom; + vdv += vc * vc; + } + } + + // Compute the rank-1 offset w*v^T. + Real invvdv = (Real)1 / vdv; + Real twoinvvdv = invvdv * (Real)2; + for (r = i; r < mNumRows; ++r) + { + mWVector[r] = (Real)0; + for (c = ip1; c < mNumCols; ++c) + { + mWVector[r] += mMatrix[c + mNumCols * r] * mVVector[c]; + } + mWVector[r] *= twoinvvdv; + } + + // Update the input matrix. + for (r = i; r < mNumRows; ++r) + { + for (c = ip1; c < mNumCols; ++c) + { + mMatrix[c + mNumCols * r] -= mWVector[r] * mVVector[c]; + } + } + + mTwoInvVTV[i] = twoinvvdv; + for (c = i + 2; c < mNumCols; ++c) + { + mMatrix[c + mNumCols * i] = mVVector[c]; + } + } + + mTwoInvUTU[i] = twoinvudu; + for (r = ip1; r < mNumRows; ++r) + { + mMatrix[i + mNumCols * r] = mUVector[r]; + } + } + + // Copy the diagonal and subdiagonal for cache coherence in the + // Golub-Kahan iterations. + int k, ksup = mNumCols - 1, index = 0, delta = mNumCols + 1; + for (k = 0; k < ksup; ++k, index += delta) + { + mDiagonal[k] = mMatrix[index]; + mSuperdiagonal[k] = mMatrix[index + 1]; + } + mDiagonal[k] = mMatrix[index]; + } + + // A helper for generating Givens rotation sine and cosine robustly. + void GetSinCos(Real x, Real y, Real& cs, Real& sn) + { + // Solves sn*x + cs*y = 0 robustly. + Real tau; + if (y != (Real)0) + { + if (std::fabs(y) > std::fabs(x)) + { + tau = -x / y; + sn = (Real)1 / std::sqrt((Real)1 + tau * tau); + cs = sn * tau; + } + else + { + tau = -y / x; + cs = (Real)1 / std::sqrt((Real)1 + tau * tau); + sn = cs * tau; + } + } + else + { + cs = (Real)1; + sn = (Real)0; + } + } + + // Test for (effectively) zero-valued diagonal entries through all + // but the last. For each such entry, the B matrix decouples. Perform + // that decoupling. If there are no zero-valued entries, then the + // Golub-Kahan step must be performed. + bool DiagonalEntriesNonzero(int imin, int imax, Real threshold) + { + for (int i = imin; i <= imax; ++i) + { + if (std::fabs(mDiagonal[i]) <= threshold) + { + // Use planar rotations to case the superdiagonal entry + // out of the matrix, thus producing a row of zeros. + Real x, z, cs, sn; + Real y = mSuperdiagonal[i]; + mSuperdiagonal[i] = (Real)0; + for (int j = i + 1; j <= imax + 1; ++j) + { + x = mDiagonal[j]; + GetSinCos(x, y, cs, sn); + mLGivens.push_back(GivensRotation(i, j, cs, sn)); + mDiagonal[j] = cs * x - sn * y; + if (j <= imax) + { + z = mSuperdiagonal[j]; + mSuperdiagonal[j] = cs * z; + y = sn * z; + } + } + return false; + } + } + return true; + } + + // This is Algorithm 8.3.1 in "Matrix Computations, 2nd edition" by + // G. H. Golub and C. F. Van Loan. + void DoGolubKahanStep(int imin, int imax) + { + // The implicit shift. Compute the eigenvalue u of the + // lower-right 2x2 block of A = B^T*B that is closer to b11. + Real f0 = (imax >= (Real)1 ? mSuperdiagonal[imax - 1] : (Real)0); + Real d1 = mDiagonal[imax]; + Real f1 = mSuperdiagonal[imax]; + Real d2 = mDiagonal[imax + 1]; + Real a00 = d1 * d1 + f0 * f0; + Real a01 = d1 * f1; + Real a11 = d2 * d2 + f1 * f1; + Real dif = (a00 - a11) * (Real)0.5; + Real sgn = (dif >= (Real)0 ? (Real)1 : (Real)-1); + Real a01sqr = a01 * a01; + Real u = a11 - a01sqr / (dif + sgn * std::sqrt(dif * dif + a01sqr)); + Real x = mDiagonal[imin] * mDiagonal[imin] - u; + Real y = mDiagonal[imin] * mSuperdiagonal[imin]; + + Real a12, a21, a22, a23, cs, sn; + Real a02 = (Real)0; + int i0 = imin - 1, i1 = imin, i2 = imin + 1; + for (/**/; i1 <= imax; ++i0, ++i1, ++i2) + { + // Compute the Givens rotation G and save it for use in + // computing V in U^T*A*V = S. + GetSinCos(x, y, cs, sn); + mRGivens.push_back(GivensRotation(i1, i2, cs, sn)); + + // Update B0 = B*G. + if (i1 > imin) + { + mSuperdiagonal[i0] = cs * mSuperdiagonal[i0] - sn * a02; + } + + a11 = mDiagonal[i1]; + a12 = mSuperdiagonal[i1]; + a22 = mDiagonal[i2]; + mDiagonal[i1] = cs * a11 - sn * a12; + mSuperdiagonal[i1] = sn * a11 + cs * a12; + mDiagonal[i2] = cs * a22; + a21 = -sn * a22; + + // Update the parameters for the next Givens rotations. + x = mDiagonal[i1]; + y = a21; + + // Compute the Givens rotation G and save it for use in + // computing U in U^T*A*V = S. + GetSinCos(x, y, cs, sn); + mLGivens.push_back(GivensRotation(i1, i2, cs, sn)); + + // Update B1 = G^T*B0. + a11 = mDiagonal[i1]; + a12 = mSuperdiagonal[i1]; + a22 = mDiagonal[i2]; + mDiagonal[i1] = cs * a11 - sn * a21; + mSuperdiagonal[i1] = cs * a12 - sn * a22; + mDiagonal[i2] = sn * a12 + cs * a22; + + if (i1 < imax) + { + a23 = mSuperdiagonal[i2]; + a02 = -sn * a23; + mSuperdiagonal[i2] = cs * a23; + + // Update the parameters for the next Givens rotations. + x = mSuperdiagonal[i1]; + y = a02; + } + } + } + + // The diagonal entries are not guaranteed to be nonnegative during + // the construction. After convergence to a diagonal matrix S, test + // for negative entries and build a diagonal matrix that reverses the + // sign on the S-entry. + void EnsureNonnegativeDiagonal() + { + for (int i = 0; i < mNumCols; ++i) + { + if (mDiagonal[i] >= (Real)0) + { + mFixupDiagonal[i] = (Real)1; + } + else + { + mDiagonal[i] = -mDiagonal[i]; + mFixupDiagonal[i] = (Real)-1; + } + } + } + + // Sort the singular values and compute the corresponding permutation + // of the indices of the array storing the singular values. The + // permutation is used for reordering the singular values and the + // corresponding columns of the orthogonal matrix in the calls to + // GetSingularValues(...) and GetOrthogonalMatrices(...). + void ComputePermutation(int sortType) + { + if (sortType == 0) + { + // Set a flag for GetSingularValues() and + // GetOrthogonalMatrices() to know that sorted output was not + // requested. + mPermutation[0] = -1; + return; + } + + // Compute the permutation induced by sorting. Initially, we + // start with the identity permutation I = (0,1,...,N-1). + struct SortItem + { + Real singularValue; + int index; + }; + + std::vector items(mNumCols); + int i; + for (i = 0; i < mNumCols; ++i) + { + items[i].singularValue = mDiagonal[i]; + items[i].index = i; + } + + if (sortType > 0) + { + std::sort(items.begin(), items.end(), + [](SortItem const& item0, SortItem const& item1) + { + return item0.singularValue < item1.singularValue; + } + ); + } + else + { + std::sort(items.begin(), items.end(), + [](SortItem const& item0, SortItem const& item1) + { + return item0.singularValue > item1.singularValue; + } + ); + } + + i = 0; + for (auto const& item : items) + { + mPermutation[i++] = item.index; + } + + // GetOrthogonalMatrices() has nontrivial code for computing the + // orthogonal U and V from the reflections and rotations. To + // avoid complicating the code further when sorting is requested, + // U and V are computed as in the unsorted case. We then need to + // swap columns of U and V to be consistent with the sorting of + // the singular values. To minimize copying due to column swaps, + // we use permutation P. The minimum number of transpositions to + // obtain P from I is N minus the number of cycles of P. Each + // cycle is reordered with a minimum number of transpositions; + // that is, the singular items are cyclically swapped, leading to + // a minimum amount of copying. For example, if there is a + // cycle i0 -> i1 -> i2 -> i3, then the copying is + // save = singularitem[i0]; + // singularitem[i1] = singularitem[i2]; + // singularitem[i2] = singularitem[i3]; + // singularitem[i3] = save; + } + + // The number rows and columns of the matrices to be processed. + int mNumRows, mNumCols; + + // The maximum number of iterations for reducing the bidiagonal matrix + // to a diagonal matrix. + unsigned int mMaxIterations; + + // The internal copy of a matrix passed to the solver. See the + // comments about function Bidiagonalize() about what is stored in the + // matrix. + std::vector mMatrix; // MxN elements + + // After the initial bidiagonalization by Householder reflections, we + // no longer need the full mMatrix. Copy the diagonal and + // superdiagonal entries to linear arrays in order to be cache + // friendly. + std::vector mDiagonal; // N elements + std::vector mSuperdiagonal; // N-1 elements + + // The Givens rotations used to reduce the initial bidiagonal matrix + // to a diagonal matrix. A rotation is the identity with the following + // replacement entries: R(index0,index0) = cs, R(index0,index1) = sn, + // R(index1,index0) = -sn, and R(index1,index1) = cs. If N is the + // number of matrix columns and K is the maximum number of iterations, + // the maximum number of right or left Givens rotations is K*(N-1). + // The maximum amount of memory is allocated to store these. However, + // we also potentially need left rotations to decouple the matrix when + // diagonal terms are zero. Worst case is a number of matrices + // quadratic in N, so for now we just use std::vector whose + // initial capacity is K*(N-1). + struct GivensRotation + { + // No default initialization for fast creation of std::vector of + // objects of this type. + GivensRotation() = default; + + GivensRotation(int inIndex0, int inIndex1, Real inCs, Real inSn) + : + index0(inIndex0), + index1(inIndex1), + cs(inCs), + sn(inSn) + { + } + + int index0, index1; + Real cs, sn; + }; + + std::vector mRGivens; + std::vector mLGivens; + + // The diagonal matrix that is used to convert S-entries to + // nonnegative. + std::vector mFixupDiagonal; // N elements + + // When sorting is requested, the permutation associated with the sort + // is stored in mPermutation. When sorting is not requested, + // mPermutation[0] is set to -1. mVisited is used for finding cycles + // in the permutation. + std::vector mPermutation; // N elements + mutable std::vector mVisited; // N elements + + // Temporary storage to compute Householder reflections and to support + // sorting of columns of the orthogonal matrices. + std::vector mTwoInvUTU; // N elements + std::vector mTwoInvVTV; // N-2 elements + mutable std::vector mUVector; // M elements + mutable std::vector mVVector; // N elements + mutable std::vector mWVector; // max(M,N) elements + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SlerpEstimate.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SlerpEstimate.h new file mode 100644 index 0000000..ebb7d1c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SlerpEstimate.h @@ -0,0 +1,151 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The spherical linear interpolation (slerp) of unit-length quaternions +// q0 and q1 for t in [0,1] and theta in (0,pi) is +// slerp(t,q0,q1) = [sin((1-t)*theta)*q0 + sin(theta)*q1]/sin(theta) +// where theta is the angle between q0 and q1 [cos(theta) = Dot(q0,q1)]. +// This function is a parameterization of the great spherical arc between +// q0 and q1 on the unit hypersphere. Moreover, the parameterization is +// one of normalized arclength--a particle traveling along the arc through +// time t does so with constant speed. +// +// Read the comments in GteChebyshevRatio.h regarding estimates for the +// ratio sin(t*theta)/sin(theta). +// +// When using slerp in animations involving sequences of quaternions, it is +// typical that the quaternions are preprocessed so that consecutive ones +// form an acute angle A in [0,pi/2]. Other preprocessing can help with +// performance. See the function comments in the SLERP class. + +namespace WwiseGTE +{ + template + class SLERP + { + public: + // The angle between q0 and q1 is in [0,pi). There are no angle + // restrictions and nothing is precomputed. + template + inline static Quaternion Estimate(Real t, Quaternion const& q0, Quaternion const& q1) + { + static_assert(1 <= N && N <= 16, "Invalid degree."); + + Real cs = Dot(q0, q1); + Real sign; + if (cs >= (Real)0) + { + sign = (Real)1; + } + else + { + cs = -cs; + sign = (Real)-1; + } + + Real f0, f1; + ChebyshevRatio::template GetEstimate(t, (Real)1 - cs, f0, f1); + return q0 * f0 + q1 * (sign * f1); + } + + + // The angle between q0 and q1 must be in [0,pi/2]. The suffix R is + // for 'Restricted'. The preprocessing code is + // Quaternion q[n]; // assuming initialized + // for (i0 = 0, i1 = 1; i1 < n; i0 = i1++) + // { + // cosA = Dot(q[i0], q[i1]); + // if (cosA < 0) + // { + // q[i1] = -q[i1]; // now Dot(q[i0], q[i]1) >= 0 + // } + // } + template + inline static Quaternion EstimateR(Real t, Quaternion const& q0, Quaternion const& q1) + { + static_assert(1 <= N && N <= 16, "Invalid degree."); + + Real f0, f1; + ChebyshevRatio::template GetEstimate(t, (Real)1 - Dot(q0, q1), f0, f1); + return q0 * f0 + q1 * f1; + } + + // The angle between q0 and q1 must be in [0,pi/2]. The suffix R is + // for 'Restricted' and the suffix P is for 'Preprocessed'. The + // preprocessing code is + // Quaternion q[n]; // assuming initialized + // Real cosA[n-1], omcosA[n-1]; // to be precomputed + // for (i0 = 0, i1 = 1; i1 < n; i0 = i1++) + // { + // cs = Dot(q[i0], q[i1]); + // if (cosA[i0] < 0) + // { + // q[i1] = -q[i1]; + // cs = -cs; + // } + // + // // for Quaterion::SlerpRP + // cosA[i0] = cs; + // + // // for SLERP::EstimateRP + // omcosA[i0] = 1 - cs; + // } + template + inline static Quaternion EstimateRP(Real t, Quaternion const& q0, Quaternion const& q1, + Real omcosA) + { + static_assert(1 <= N && N <= 16, "Invalid degree."); + + Real f0, f1; + ChebyshevRatio::template GetEstimate(t, omcosA, f0, f1); + return q0 * f0 + q1 * f1; + } + + // The angle between q0 and q1 is A and must be in [0,pi/2]. + // Quaternion qh is slerp(1/2,q0,q1) = (q0+q1)/|q0+q1|, so the angle + // between q0 and qh is A/2 and the angle between qh and q1 is A/2. + // The preprocessing code is + // Quaternion q[n]; // assuming initialized + // Quaternion qh[n-1]; // to be precomputed + // Real omcosAH[n-1]; // to be precomputed + // for (i0 = 0, i1 = 1; i1 < n; i0 = i1++) + // { + // cosA = Dot(q[i0], q[i1]); + // if (cosA < 0) + // { + // q[i1] = -q[i1]; + // cosA = -cosA; + // } + // cosAH = sqrt((1 + cosA)/2); + // qh[i0] = (q0 + q1) / (2 * cosAH[i0]); + // omcosAH[i0] = 1 - cosAH; + // } + template + inline static Quaternion EstimateRPH(Real t, Quaternion const& q0, Quaternion const& q1, + Quaternion const& qh, Real omcosAH) + { + static_assert(1 <= N && N <= 16, "Invalid degree."); + + Real f0, f1; + Real twoT = t * (Real)2; + if (twoT <= (Real)1) + { + ChebyshevRatio::template GetEstimate(twoT, omcosAH, f0, f1); + return q0 * f0 + qh * f1; + } + else + { + ChebyshevRatio::template GetEstimate(twoT - (Real)1, omcosAH, f0, f1); + return qh * f0 + q1 * f1; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SplitMeshByPlane.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SplitMeshByPlane.h new file mode 100644 index 0000000..ef569c1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SplitMeshByPlane.h @@ -0,0 +1,397 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The algorithm for splitting a mesh by a plane is described in +// https://www.geometrictools.com/Documentation/ClipMesh.pdf +// Currently, the code here does not include generating a closed +// mesh (from the "positive" and "zero" vertices) by attaching +// triangulated faces to the mesh, where the those faces live in +// the splitting plane. (TODO: Add this code.) + +namespace WwiseGTE +{ + template + class SplitMeshByPlane + { + public: + // The 'indices' are lookups into the 'vertices' array. The indices + // represent a triangle mesh. The number of indices must be a + // multiple of 3, each triple representing a triangle. If t is a + // triangle index, then the triangle is formed by + // vertices[indices[3 * t + i]] for 0 <= i <= 2. The outputs + // 'negIndices' and 'posIndices' are formatted similarly. + void operator()( + std::vector> const& vertices, + std::vector const& indices, + Plane3 const& plane, + std::vector>& clipVertices, + std::vector& negIndices, + std::vector& posIndices) + { + mSignedDistances.resize(vertices.size()); + mEMap.clear(); + + // Make a copy of the incoming vertices. If the mesh intersects + // the plane, new vertices must be generated. These are appended + // to the clipVertices array. + clipVertices = vertices; + + ClassifyVertices(clipVertices, plane); + ClassifyEdges(clipVertices, indices); + ClassifyTriangles(indices, negIndices, posIndices); + } + + private: + void ClassifyVertices(std::vector> const& clipVertices, + Plane3 const& plane) + { + DCPQuery, Plane3> query; + for (size_t i = 0; i < clipVertices.size(); ++i) + { + mSignedDistances[i] = query(clipVertices[i], plane).signedDistance; + } + } + + void ClassifyEdges(std::vector>& clipVertices, + std::vector const& indices) + { + int const numTriangles = static_cast(indices.size() / 3); + int nextIndex = static_cast(clipVertices.size()); + for (int i = 0; i < numTriangles; ++i) + { + int v0 = indices[3 * i + 0]; + int v1 = indices[3 * i + 1]; + int v2 = indices[3 * i + 2]; + Real sDist0 = mSignedDistances[v0]; + Real sDist1 = mSignedDistances[v1]; + Real sDist2 = mSignedDistances[v2]; + + EdgeKey key; + Real t; + Vector3 intr, diff; + + // The change-in-sign tests are structured this way to avoid + // numerical round-off problems. For example, sDist0 > 0 and + // sDist1 < 0, but both are very small and sDist0 * sDist1 = 0 + // because of round-off errors. The tests also guarantee + // consistency between this function and ClassifyTriangles, + // the latter function using sign tests only on the individual + // sDist values. + + if ((sDist0 > (Real)0 && sDist1 < (Real)0) + || (sDist0 < (Real)0 && sDist1 >(Real)0)) + { + key = EdgeKey(v0, v1); + if (mEMap.find(key) == mEMap.end()) + { + t = sDist0 / (sDist0 - sDist1); + diff = clipVertices[v1] - clipVertices[v0]; + intr = clipVertices[v0] + t * diff; + clipVertices.push_back(intr); + mEMap[key] = std::make_pair(intr, nextIndex); + ++nextIndex; + } + } + + if ((sDist1 > (Real)0 && sDist2 < (Real)0) + || (sDist1 < (Real)0 && sDist2 >(Real)0)) + { + key = EdgeKey(v1, v2); + if (mEMap.find(key) == mEMap.end()) + { + t = sDist1 / (sDist1 - sDist2); + diff = clipVertices[v2] - clipVertices[v1]; + intr = clipVertices[v1] + t * diff; + clipVertices.push_back(intr); + mEMap[key] = std::make_pair(intr, nextIndex); + ++nextIndex; + } + } + + if ((sDist2 > (Real)0 && sDist0 < (Real)0) + || (sDist2 < (Real)0 && sDist0 >(Real)0)) + { + key = EdgeKey(v2, v0); + if (mEMap.find(key) == mEMap.end()) + { + t = sDist2 / (sDist2 - sDist0); + diff = clipVertices[v0] - clipVertices[v2]; + intr = clipVertices[v2] + t * diff; + clipVertices.push_back(intr); + mEMap[key] = std::make_pair(intr, nextIndex); + ++nextIndex; + } + } + } + } + + void ClassifyTriangles(std::vector const& indices, + std::vector& negIndices, std::vector& posIndices) + { + int const numTriangles = static_cast(indices.size() / 3); + for (int i = 0; i < numTriangles; ++i) + { + int v0 = indices[3 * i + 0]; + int v1 = indices[3 * i + 1]; + int v2 = indices[3 * i + 2]; + Real sDist0 = mSignedDistances[v0]; + Real sDist1 = mSignedDistances[v1]; + Real sDist2 = mSignedDistances[v2]; + + if (sDist0 > (Real)0) + { + if (sDist1 > (Real)0) + { + if (sDist2 > (Real)0) + { + // +++ + AppendTriangle(posIndices, v0, v1, v2); + } + else if (sDist2 < (Real)0) + { + // ++- + SplitTrianglePPM(negIndices, posIndices, v0, v1, v2); + } + else + { + // ++0 + AppendTriangle(posIndices, v0, v1, v2); + } + } + else if (sDist1 < (Real)0) + { + if (sDist2 > (Real)0) + { + // +-+ + SplitTrianglePPM(negIndices, posIndices, v2, v0, v1); + } + else if (sDist2 < (Real)0) + { + // +-- + SplitTriangleMMP(negIndices, posIndices, v1, v2, v0); + } + else + { + // +-0 + SplitTrianglePMZ(negIndices, posIndices, v0, v1, v2); + } + } + else + { + if (sDist2 > (Real)0) + { + // +0+ + AppendTriangle(posIndices, v0, v1, v2); + } + else if (sDist2 < (Real)0) + { + // +0- + SplitTriangleMPZ(negIndices, posIndices, v2, v0, v1); + } + else + { + // +00 + AppendTriangle(posIndices, v0, v1, v2); + } + } + } + else if (sDist0 < (Real)0) + { + if (sDist1 > (Real)0) + { + if (sDist2 > (Real)0) + { + // -++ + SplitTrianglePPM(negIndices, posIndices, v1, v2, v0); + } + else if (sDist2 < (Real)0) + { + // -+- + SplitTriangleMMP(negIndices, posIndices, v2, v0, v1); + } + else + { + // -+0 + SplitTriangleMPZ(negIndices, posIndices, v0, v1, v2); + } + } + else if (sDist1 < (Real)0) + { + if (sDist2 > (Real)0) + { + // --+ + SplitTriangleMMP(negIndices, posIndices, v0, v1, v2); + } + else if (sDist2 < (Real)0) + { + // --- + AppendTriangle(negIndices, v0, v1, v2); + } + else + { + // --0 + AppendTriangle(negIndices, v0, v1, v2); + } + } + else + { + if (sDist2 > (Real)0) + { + // -0+ + SplitTrianglePMZ(negIndices, posIndices, v2, v0, v1); + } + else if (sDist2 < (Real)0) + { + // -0- + AppendTriangle(negIndices, v0, v1, v2); + } + else + { + // -00 + AppendTriangle(negIndices, v0, v1, v2); + } + } + } + else + { + if (sDist1 > (Real)0) + { + if (sDist2 > (Real)0) + { + // 0++ + AppendTriangle(posIndices, v0, v1, v2); + } + else if (sDist2 < (Real)0) + { + // 0+- + SplitTrianglePMZ(negIndices, posIndices, v1, v2, v0); + } + else + { + // 0+0 + AppendTriangle(posIndices, v0, v1, v2); + } + } + else if (sDist1 < (Real)0) + { + if (sDist2 > (Real)0) + { + // 0-+ + SplitTriangleMPZ(negIndices, posIndices, v1, v2, v0); + } + else if (sDist2 < (Real)0) + { + // 0-- + AppendTriangle(negIndices, v0, v1, v2); + } + else + { + // 0-0 + AppendTriangle(negIndices, v0, v1, v2); + } + } + else + { + if (sDist2 > (Real)0) + { + // 00+ + AppendTriangle(posIndices, v0, v1, v2); + } + else if (sDist2 < (Real)0) + { + // 00- + AppendTriangle(negIndices, v0, v1, v2); + } + else + { + // 000, reject triangles lying in the plane + } + } + } + } + } + + void AppendTriangle(std::vector& indices, int v0, int v1, int v2) + { + indices.push_back(v0); + indices.push_back(v1); + indices.push_back(v2); + } + + void SplitTrianglePPM(std::vector& negIndices, + std::vector& posIndices, int v0, int v1, int v2) + { + int v12 = mEMap[EdgeKey(v1, v2)].second; + int v20 = mEMap[EdgeKey(v2, v0)].second; + posIndices.push_back(v0); + posIndices.push_back(v1); + posIndices.push_back(v12); + posIndices.push_back(v0); + posIndices.push_back(v12); + posIndices.push_back(v20); + negIndices.push_back(v2); + negIndices.push_back(v20); + negIndices.push_back(v12); + } + + void SplitTriangleMMP(std::vector& negIndices, + std::vector& posIndices, int v0, int v1, int v2) + { + int v12 = mEMap[EdgeKey(v1, v2)].second; + int v20 = mEMap[EdgeKey(v2, v0)].second; + negIndices.push_back(v0); + negIndices.push_back(v1); + negIndices.push_back(v12); + negIndices.push_back(v0); + negIndices.push_back(v12); + negIndices.push_back(v20); + posIndices.push_back(v2); + posIndices.push_back(v20); + posIndices.push_back(v12); + } + + void SplitTrianglePMZ(std::vector& negIndices, + std::vector& posIndices, int v0, int v1, int v2) + { + int v01 = mEMap[EdgeKey(v0, v1)].second; + posIndices.push_back(v2); + posIndices.push_back(v0); + posIndices.push_back(v01); + negIndices.push_back(v2); + negIndices.push_back(v01); + negIndices.push_back(v1); + } + + void SplitTriangleMPZ(std::vector& negIndices, + std::vector& posIndices, int v0, int v1, int v2) + { + int v01 = mEMap[EdgeKey(v0, v1)].second; + negIndices.push_back(v2); + negIndices.push_back(v0); + negIndices.push_back(v01); + posIndices.push_back(v2); + posIndices.push_back(v01); + posIndices.push_back(v1); + } + + // Stores the signed distances from the vertices to the plane. + std::vector mSignedDistances; + + // Stores the edges whose vertices are on opposite sides of the + // plane. The key is a pair of indices into the vertex array. + // The value is the point of intersection of the edge with the + // plane and an index into m_kVertices (the index is larger or + // equal to the number of vertices of incoming rkVertices). + std::map, std::pair, int>> mEMap; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SqrtEstimate.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SqrtEstimate.h new file mode 100644 index 0000000..9b7d182 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SqrtEstimate.h @@ -0,0 +1,161 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// Minimax polynomial approximations to sqrt(x). The polynomial p(x) of +// degree D minimizes the quantity maximum{|sqrt(x) - p(x)| : x in [1,2]} +// over all polynomials of degree D. + +namespace WwiseGTE +{ + template + class SqrtEstimate + { + public: + // The input constraint is x in [1,2]. For example, + // float x; // in [1,2] + // float result = SqrtEstimate::Degree<3>(x); + template + inline static Real Degree(Real x) + { + Real t = x - (Real)1; // t in [0,1] + return Evaluate(degree(), t); + } + + // The input constraint is x >= 0. Range reduction is used to + // generate a value y in [0,1], call Degree(y), and combine the + // output with the proper exponent to obtain the approximation. + // For example, + // float x; // x >= 0 + // float result = SqrtEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x) + { + Real adj, y; + int p; + Reduce(x, adj, y, p); + Real poly = Degree(y); + Real result = Combine(adj, poly, p); + return result; + } + + private: + // Metaprogramming and private implementation to allow specialization + // of a template member function. + template struct degree {}; + + inline static Real Evaluate(degree<1>, Real t) + { + Real poly; + poly = (Real)GTE_C_SQRT_DEG1_C1; + poly = (Real)GTE_C_SQRT_DEG1_C0 + poly * t; + return poly; + } + + inline static Real Evaluate(degree<2>, Real t) + { + Real poly; + poly = (Real)GTE_C_SQRT_DEG2_C2; + poly = (Real)GTE_C_SQRT_DEG2_C1 + poly * t; + poly = (Real)GTE_C_SQRT_DEG2_C0 + poly * t; + return poly; + } + + inline static Real Evaluate(degree<3>, Real t) + { + Real poly; + poly = (Real)GTE_C_SQRT_DEG3_C3; + poly = (Real)GTE_C_SQRT_DEG3_C2 + poly * t; + poly = (Real)GTE_C_SQRT_DEG3_C1 + poly * t; + poly = (Real)GTE_C_SQRT_DEG3_C0 + poly * t; + return poly; + } + + inline static Real Evaluate(degree<4>, Real t) + { + Real poly; + poly = (Real)GTE_C_SQRT_DEG4_C4; + poly = (Real)GTE_C_SQRT_DEG4_C3 + poly * t; + poly = (Real)GTE_C_SQRT_DEG4_C2 + poly * t; + poly = (Real)GTE_C_SQRT_DEG4_C1 + poly * t; + poly = (Real)GTE_C_SQRT_DEG4_C0 + poly * t; + return poly; + } + + inline static Real Evaluate(degree<5>, Real t) + { + Real poly; + poly = (Real)GTE_C_SQRT_DEG5_C5; + poly = (Real)GTE_C_SQRT_DEG5_C4 + poly * t; + poly = (Real)GTE_C_SQRT_DEG5_C3 + poly * t; + poly = (Real)GTE_C_SQRT_DEG5_C2 + poly * t; + poly = (Real)GTE_C_SQRT_DEG5_C1 + poly * t; + poly = (Real)GTE_C_SQRT_DEG5_C0 + poly * t; + return poly; + } + + inline static Real Evaluate(degree<6>, Real t) + { + Real poly; + poly = (Real)GTE_C_SQRT_DEG6_C6; + poly = (Real)GTE_C_SQRT_DEG6_C5 + poly * t; + poly = (Real)GTE_C_SQRT_DEG6_C4 + poly * t; + poly = (Real)GTE_C_SQRT_DEG6_C3 + poly * t; + poly = (Real)GTE_C_SQRT_DEG6_C2 + poly * t; + poly = (Real)GTE_C_SQRT_DEG6_C1 + poly * t; + poly = (Real)GTE_C_SQRT_DEG6_C0 + poly * t; + return poly; + } + + inline static Real Evaluate(degree<7>, Real t) + { + Real poly; + poly = (Real)GTE_C_SQRT_DEG7_C7; + poly = (Real)GTE_C_SQRT_DEG7_C6 + poly * t; + poly = (Real)GTE_C_SQRT_DEG7_C5 + poly * t; + poly = (Real)GTE_C_SQRT_DEG7_C4 + poly * t; + poly = (Real)GTE_C_SQRT_DEG7_C3 + poly * t; + poly = (Real)GTE_C_SQRT_DEG7_C2 + poly * t; + poly = (Real)GTE_C_SQRT_DEG7_C1 + poly * t; + poly = (Real)GTE_C_SQRT_DEG7_C0 + poly * t; + return poly; + } + + inline static Real Evaluate(degree<8>, Real t) + { + Real poly; + poly = (Real)GTE_C_SQRT_DEG8_C8; + poly = (Real)GTE_C_SQRT_DEG8_C7 + poly * t; + poly = (Real)GTE_C_SQRT_DEG8_C6 + poly * t; + poly = (Real)GTE_C_SQRT_DEG8_C5 + poly * t; + poly = (Real)GTE_C_SQRT_DEG8_C4 + poly * t; + poly = (Real)GTE_C_SQRT_DEG8_C3 + poly * t; + poly = (Real)GTE_C_SQRT_DEG8_C2 + poly * t; + poly = (Real)GTE_C_SQRT_DEG8_C1 + poly * t; + poly = (Real)GTE_C_SQRT_DEG8_C0 + poly * t; + return poly; + } + + // Support for range reduction. + inline static void Reduce(Real x, Real& adj, Real& y, int& p) + { + y = std::frexp(x, &p); // y in [1/2,1) + y = (Real)2 * y; // y in [1,2) + --p; + adj = (1 & p) * (Real)GTE_C_SQRT_2 + (1 & ~p) * (Real)1; + p >>= 1; + } + + inline static Real Combine(Real adj, Real y, int p) + { + return adj * std::ldexp(y, p); + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/StringUtility.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/StringUtility.h new file mode 100644 index 0000000..5a2898d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/StringUtility.h @@ -0,0 +1,138 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.11.22 + +#pragma once + +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + inline std::wstring ConvertNarrowToWide(std::string const& input) + { + std::wstring output; + std::transform(input.begin(), input.end(), std::back_inserter(output), + [](char c) { return static_cast(c); }); + return output; + } + + inline std::string ConvertWideToNarrow(std::wstring const& input) + { + std::string output; + std::transform(input.begin(), input.end(), std::back_inserter(output), + [](wchar_t c) { return static_cast(c); }); + return output; + } + + inline std::string ToLower(std::string const& input) + { + std::string output; + std::transform(input.begin(), input.end(), std::back_inserter(output), + [](int c) { return static_cast(::tolower(c)); }); + return output; + } + + inline std::string ToUpper(std::string const& input) + { + std::string output; + std::transform(input.begin(), input.end(), std::back_inserter(output), + [](int c) { return static_cast(::toupper(c)); }); + return output; + } + + // In the default locale for C++ strings, the whitespace characters are + // space (0x20, ' '), form feed (0x0C, '\f'), line feed (0x0A, '\n'), + // carriage return (0x0D, 'r'), horizontal tab (0x09, 't') and + // vertical tab (0x0B, '\v'). See + // https://en.cppreference.com/w/cpp/string/byte/isspace + // for a table of ASCII values and related is* and isw* functions (with + // 'int ch' input) that return 0 or !0. + inline void GetTokens(std::string const& input, std::string const& whiteSpace, + std::vector& tokens) + { + std::string tokenString(input); + tokens.clear(); + while (tokenString.length() > 0) + { + // Find the beginning of a token. + auto begin = tokenString.find_first_not_of(whiteSpace); + if (begin == std::string::npos) + { + // All tokens have been found. + break; + } + + // Strip off the white space. + if (begin > 0) + { + tokenString = tokenString.substr(begin); + } + + // Find the end of the token. + auto end = tokenString.find_first_of(whiteSpace); + if (end != std::string::npos) + { + std::string token = tokenString.substr(0, end); + tokens.push_back(token); + tokenString = tokenString.substr(end); + } + else + { + // This is the last token. + tokens.push_back(tokenString); + break; + } + } + } + + // For basic text extraction, choose 'whiteSpace' to be ASCII values + // 0x00-0x20,0x7F-0xFF in GetTokens(...). + inline void GetTextTokens(std::string const& input, + std::vector& tokens) + { + static std::string const whiteSpace = [] + { + std::string temp; + for (int i = 0; i <= 32; ++i) + { + temp += char(i); + } + for (int i = 127; i < 255; ++i) + { + temp += char(i); + } + return temp; + } + (); + + GetTokens(input, whiteSpace, tokens); + } + + // For advanced text extraction, choose 'whiteSpace' to be ASCII values + // 0x00-0x20,0x7F in GetTokens(...). Any special characters for ASCII + // values 0x80 or larger are retained as text. + inline void GetAdvancedTextTokens(std::string const& input, + std::vector& tokens) + { + static std::string const whiteSpace = [] + { + std::string temp; + for (int i = 0; i <= 32; ++i) + { + temp += char(i); + } + temp += char(127); + return temp; + } + (); + + GetTokens(input, whiteSpace, tokens); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SurfaceExtractor.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SurfaceExtractor.h new file mode 100644 index 0000000..c7dc47f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SurfaceExtractor.h @@ -0,0 +1,462 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + // The image type T must be one of the integer types: int8_t, int16_t, + // int32_t, uint8_t, uint16_t or uint32_t. Internal integer computations + // are performed using int64_t. The type Real is for extraction to + // floating-point vertices. + template + class SurfaceExtractor + { + public: + // Abstract base class. + virtual ~SurfaceExtractor() = default; + + // The level surfaces form a graph of vertices, edges and triangles. + // The vertices are computed as triples of nonnegative rational + // numbers. Vertex represents the rational triple + // (xNumer/xDenom, yNumer/yDenom, zNumer/zDenom) + // as + // (xNumer, xDenom, yNumer, yDenom, zNumer, zDenom) + // where all components are nonnegative. The edges connect pairs of + // vertices and the triangles connect triples of vertices, forming + // a graph that represents the level set. + struct Vertex + { + Vertex() = default; + + Vertex(int64_t inXNumer, int64_t inXDenom, int64_t inYNumer, int64_t inYDenom, + int64_t inZNumer, int64_t inZDenom) + { + // The vertex generation leads to the numerator and + // denominator having the same sign. This constructor changes + // sign to ensure the numerator and denominator are both + // positive. + if (inXDenom > 0) + { + xNumer = inXNumer; + xDenom = inXDenom; + } + else + { + xNumer = -inXNumer; + xDenom = -inXDenom; + } + + if (inYDenom > 0) + { + yNumer = inYNumer; + yDenom = inYDenom; + } + else + { + yNumer = -inYNumer; + yDenom = -inYDenom; + } + + if (inZDenom > 0) + { + zNumer = inZNumer; + zDenom = inZDenom; + } + else + { + zNumer = -inZNumer; + zDenom = -inZDenom; + } + } + + // The non-default constructor guarantees that xDenom > 0, + // yDenom > 0 and zDenom > 0. The following comparison operators + // assume that the denominators are positive. + bool operator==(Vertex const& other) const + { + return + // xn0/xd0 == xn1/xd1 + xNumer * other.xDenom == other.xNumer * xDenom + && + // yn0/yd0 == yn1/yd1 + yNumer * other.yDenom == other.yNumer * yDenom + && + // zn0/zd0 == zn1/zd1 + zNumer * other.zDenom == other.zNumer * zDenom; + } + + bool operator<(Vertex const& other) const + { + int64_t xn0txd1 = xNumer * other.xDenom; + int64_t xn1txd0 = other.xNumer * xDenom; + if (xn0txd1 < xn1txd0) + { + // xn0/xd0 < xn1/xd1 + return true; + } + if (xn0txd1 > xn1txd0) + { + // xn0/xd0 > xn1/xd1 + return false; + } + + int64_t yn0tyd1 = yNumer * other.yDenom; + int64_t yn1tyd0 = other.yNumer * yDenom; + if (yn0tyd1 < yn1tyd0) + { + // yn0/yd0 < yn1/yd1 + return true; + } + if (yn0tyd1 > yn1tyd0) + { + // yn0/yd0 > yn1/yd1 + return false; + } + + int64_t zn0tzd1 = zNumer * other.zDenom; + int64_t zn1tzd0 = other.zNumer * zDenom; + // zn0/zd0 < zn1/zd1 + return zn0tzd1 < zn1tzd0; + } + + int64_t xNumer, xDenom, yNumer, yDenom, zNumer, zDenom; + }; + + struct Triangle + { + Triangle() = default; + + Triangle(int v0, int v1, int v2) + { + // After the code is executed, (v[0],v[1],v[2]) is a cyclic + // permutation of (v0,v1,v2) with v[0] = min{v0,v1,v2}. + if (v0 < v1) + { + if (v0 < v2) + { + v[0] = v0; + v[1] = v1; + v[2] = v2; + } + else + { + v[0] = v2; + v[1] = v0; + v[2] = v1; + } + } + else + { + if (v1 < v2) + { + v[0] = v1; + v[1] = v2; + v[2] = v0; + } + else + { + v[0] = v2; + v[1] = v0; + v[2] = v1; + } + } + } + + bool operator==(Triangle const& other) const + { + return v[0] == other.v[0] && v[1] == other.v[1] && v[2] == other.v[2]; + } + + bool operator<(Triangle const& other) const + { + for (int i = 0; i < 3; ++i) + { + if (v[i] < other.v[i]) + { + return true; + } + if (v[i] > other.v[i]) + { + return false; + } + } + return false; + } + + std::array v; + }; + + // Extract level surfaces and return rational vertices. + virtual void Extract(T level, std::vector& vertices, + std::vector& triangles) = 0; + + void Extract(T level, bool removeDuplicateVertices, + std::vector>& vertices, std::vector& triangles) + { + std::vector rationalVertices; + Extract(level, rationalVertices, triangles); + if (removeDuplicateVertices) + { + MakeUnique(rationalVertices, triangles); + } + Convert(rationalVertices, vertices); + } + + // The extraction has duplicate vertices on edges shared by voxels. + // This function will eliminate the duplicates. + void MakeUnique(std::vector& vertices, std::vector& triangles) + { + size_t numVertices = vertices.size(); + size_t numTriangles = triangles.size(); + if (numVertices == 0 || numTriangles == 0) + { + return; + } + + // Compute the map of unique vertices and assign to them new and + // unique indices. + std::map vmap; + int nextVertex = 0; + for (size_t v = 0; v < numVertices; ++v) + { + // Keep only unique vertices. + auto result = vmap.insert(std::make_pair(vertices[v], nextVertex)); + if (result.second) + { + ++nextVertex; + } + } + + // Compute the map of unique triangles and assign to them new and + // unique indices. + std::map tmap; + int nextTriangle = 0; + for (size_t t = 0; t < numTriangles; ++t) + { + Triangle& triangle = triangles[t]; + for (int i = 0; i < 3; ++i) + { + auto iter = vmap.find(vertices[triangle.v[i]]); + LogAssert(iter != vmap.end(), "Expecting the vertex to be in the vmap."); + triangle.v[i] = iter->second; + } + + // Keep only unique triangles. + auto result = tmap.insert(std::make_pair(triangle, nextTriangle)); + if (result.second) + { + ++nextTriangle; + } + } + + // Pack the vertices into an array. + vertices.resize(vmap.size()); + for (auto const& element : vmap) + { + vertices[element.second] = element.first; + } + + // Pack the triangles into an array. + triangles.resize(tmap.size()); + for (auto const& element : tmap) + { + triangles[element.second] = element.first; + } + } + + // Convert from Vertex to std::array rationals. + void Convert(std::vector const& input, std::vector>& output) + { + output.resize(input.size()); + for (size_t i = 0; i < input.size(); ++i) + { + Real rxNumer = static_cast(input[i].xNumer); + Real rxDenom = static_cast(input[i].xDenom); + Real ryNumer = static_cast(input[i].yNumer); + Real ryDenom = static_cast(input[i].yDenom); + Real rzNumer = static_cast(input[i].zNumer); + Real rzDenom = static_cast(input[i].zDenom); + output[i][0] = rxNumer / rxDenom; + output[i][1] = ryNumer / ryDenom; + output[i][2] = rzNumer / rzDenom; + } + } + + // The extraction does not use any topological information about the + // level surfaces. The triangles can be a mixture of clockwise-ordered + // and counterclockwise-ordered. This function is an attempt to give + // the triangles a consistent ordering by selecting a normal in + // approximately the same direction as the average gradient at the + // vertices (when sameDir is true), or in the opposite direction (when + // sameDir is false). This might not always produce a consistent + // order, but is fast. A consistent order can be computed by + // choosing a winding order for each triangle so that any corner of + // the voxel containing the triangle and that has positive sign sees + // a counterclockwise order. Of course, you can also choose that the + // positive sign corners of the voxel always see the voxel-contained + // triangles in clockwise order. + void OrientTriangles(std::vector>& vertices, + std::vector& triangles, bool sameDir) + { + for (auto& triangle : triangles) + { + // Get the triangle vertices. + std::array v0 = vertices[triangle.v[0]]; + std::array v1 = vertices[triangle.v[1]]; + std::array v2 = vertices[triangle.v[2]]; + + // Construct the triangle normal based on the current + // orientation. + std::array edge1, edge2, normal; + for (int i = 0; i < 3; ++i) + { + edge1[i] = v1[i] - v0[i]; + edge2[i] = v2[i] - v0[i]; + } + normal[0] = edge1[1] * edge2[2] - edge1[2] * edge2[1]; + normal[1] = edge1[2] * edge2[0] - edge1[0] * edge2[2]; + normal[2] = edge1[0] * edge2[1] - edge1[1] * edge2[0]; + + // Get the image gradient at the vertices. + std::array grad0 = GetGradient(v0); + std::array grad1 = GetGradient(v1); + std::array grad2 = GetGradient(v2); + + // Compute the average gradient. + std::array gradAvr; + for (int i = 0; i < 3; ++i) + { + gradAvr[i] = (grad0[i] + grad1[i] + grad2[i]) / (Real)3; + } + + // Compute the dot product of normal and average gradient. + Real dot = gradAvr[0] * normal[0] + gradAvr[1] * normal[1] + gradAvr[2] * normal[2]; + + // Choose triangle orientation based on gradient direction. + if (sameDir) + { + if (dot < (Real)0) + { + // Wrong orientation, reorder it. + std::swap(triangle.v[1], triangle.v[2]); + } + } + else + { + if (dot > (Real)0) + { + // Wrong orientation, reorder it. + std::swap(triangle.v[1], triangle.v[2]); + } + } + } + } + + // Use this function if you want vertex normals for dynamic lighting + // of the mesh. + void ComputeNormals(std::vector> const& vertices, + std::vector const& triangles, + std::vector>& normals) + { + // Compute a vertex normal to be area-weighted sums of the normals + // to the triangles that share that vertex. + std::array const zero{ (Real)0, (Real)0, (Real)0 }; + normals.resize(vertices.size()); + std::fill(normals.begin(), normals.end(), zero); + + for (auto const& triangle : triangles) + { + // Get the triangle vertices. + std::array v0 = vertices[triangle.v[0]]; + std::array v1 = vertices[triangle.v[1]]; + std::array v2 = vertices[triangle.v[2]]; + + // Construct the triangle normal. + std::array edge1, edge2, normal; + for (int i = 0; i < 3; ++i) + { + edge1[i] = v1[i] - v0[i]; + edge2[i] = v2[i] - v0[i]; + } + normal[0] = edge1[1] * edge2[2] - edge1[2] * edge2[1]; + normal[1] = edge1[2] * edge2[0] - edge1[0] * edge2[2]; + normal[2] = edge1[0] * edge2[1] - edge1[1] * edge2[0]; + + // Maintain the sum of normals at each vertex. + for (int i = 0; i < 3; ++i) + { + for (int j = 0; j < 3; ++j) + { + normals[triangle.v[i]][j] += normal[j]; + } + } + } + + // The normal vector storage was used to accumulate the sum of + // triangle normals. Now these vectors must be rescaled to be + // unit length. + for (auto& normal : normals) + { + Real sqrLength = normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]; + Real length = std::sqrt(sqrLength); + if (length > (Real)0) + { + for (int i = 0; i < 3; ++i) + { + normal[i] /= length; + } + } + else + { + for (int i = 0; i < 3; ++i) + { + normal[i] = (Real)0; + } + } + } + } + + protected: + // The input is a 3D image with lexicographically ordered voxels + // (x,y,z) stored in a linear array. Voxel (x,y,z) is stored in the + // array at location index = x + xBound * (y + yBound * z). The + // inputs xBound, yBound and zBound must each be 2 or larger so that + // there is at least one image cube to process. The inputVoxels must + // be nonnull and point to contiguous storage that contains at least + // xBound * yBound * zBound elements. + SurfaceExtractor(int xBound, int yBound, int zBound, T const* inputVoxels) + : + mXBound(xBound), + mYBound(yBound), + mZBound(zBound), + mXYBound(xBound * yBound), + mInputVoxels(inputVoxels) + { + static_assert(std::is_integral::value && sizeof(T) <= 4, + "Type T must be int{8,16,32}_t or uint{8,16,32}_t."); + LogAssert(xBound > 1 && yBound > 1 && zBound > 1 && mInputVoxels != nullptr, + "Invalid input."); + + mVoxels.resize(static_cast(mXBound * mYBound * mZBound)); + } + + virtual std::array GetGradient(std::array const& pos) = 0; + + int mXBound, mYBound, mZBound, mXYBound; + T const* mInputVoxels; + std::vector mVoxels; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SurfaceExtractorCubes.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SurfaceExtractorCubes.h new file mode 100644 index 0000000..dfd9abc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SurfaceExtractorCubes.h @@ -0,0 +1,1031 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The level set extraction algorithm implemented here is described +// in Section 3.2 of the document +// https://www.geometrictools.com/Documentation/LevelSetExtraction.pdf + +namespace WwiseGTE +{ + // The image type T must be one of the integer types: int8_t, int16_t, + // int32_t, uint8_t, uint16_t or uint32_t. Internal integer computations + // are performed using int64_t. The type Real is for extraction to + // floating-point vertices. + template + class SurfaceExtractorCubes : public SurfaceExtractor + { + public: + // Convenience type definitions. + typedef typename SurfaceExtractor::Vertex Vertex; + typedef typename SurfaceExtractor::Triangle Triangle; + + // The input is a 3D image with lexicographically ordered voxels + // (x,y,z) stored in a linear array. Voxel (x,y,z) is stored in the + // array at location index = x + xBound * (y + yBound * z). The + // inputs xBound, yBound and zBound must each be 2 or larger so that + // there is at least one image cube to process. The inputVoxels must + // be nonnull and point to contiguous storage that contains at least + // xBound * yBound * zBound elements. + SurfaceExtractorCubes(int xBound, int yBound, int zBound, T const* inputVoxels) + : + SurfaceExtractor(xBound, yBound, zBound, inputVoxels) + { + } + + // Extract level surfaces and return rational vertices. Use the + // base-class Extract if you want real-valued vertices. + virtual void Extract(T level, std::vector& vertices, + std::vector& triangles) override + { + // Adjust the image so that the level set is F(x,y,z) = 0. The + // precondition for 'level' is that it is not exactly a voxel + // value. However, T is an integer type, so we cannot pass in + // a 'level' that has a fractional value. To circumvent this, + // the voxel values are doubled so that they are even integers. + // The level value is doubled and 1 added to obtain an odd + // integer, guaranteeing 'level' is not a voxel value. + int64_t levelI64 = 2 * static_cast(level) + 1; + for (size_t i = 0; i < this->mVoxels.size(); ++i) + { + int64_t inputI64 = 2 * static_cast(this->mInputVoxels[i]); + this->mVoxels[i] = inputI64 - levelI64; + } + + vertices.clear(); + triangles.clear(); + for (int z = 0; z < this->mZBound - 1; ++z) + { + for (int y = 0; y < this->mYBound - 1; ++y) + { + for (int x = 0; x < this->mXBound - 1; ++x) + { + // Get vertices on edges of box (if any). + VETable table; + int type = GetVertices(x, y, z, table); + if (type != 0) + { + // Get edges on faces of box. + GetXMinEdges(x, y, z, type, table); + GetXMaxEdges(x, y, z, type, table); + GetYMinEdges(x, y, z, type, table); + GetYMaxEdges(x, y, z, type, table); + GetZMinEdges(x, y, z, type, table); + GetZMaxEdges(x, y, z, type, table); + + // Ear-clip the wireframe mesh. + table.RemoveTriangles(vertices, triangles); + } + } + } + } + } + + protected: + enum + { + EI_XMIN_YMIN = 0, + EI_XMIN_YMAX = 1, + EI_XMAX_YMIN = 2, + EI_XMAX_YMAX = 3, + EI_XMIN_ZMIN = 4, + EI_XMIN_ZMAX = 5, + EI_XMAX_ZMIN = 6, + EI_XMAX_ZMAX = 7, + EI_YMIN_ZMIN = 8, + EI_YMIN_ZMAX = 9, + EI_YMAX_ZMIN = 10, + EI_YMAX_ZMAX = 11, + FI_XMIN = 12, + FI_XMAX = 13, + FI_YMIN = 14, + FI_YMAX = 15, + FI_ZMIN = 16, + FI_ZMAX = 17, + + EB_XMIN_YMIN = 1 << EI_XMIN_YMIN, + EB_XMIN_YMAX = 1 << EI_XMIN_YMAX, + EB_XMAX_YMIN = 1 << EI_XMAX_YMIN, + EB_XMAX_YMAX = 1 << EI_XMAX_YMAX, + EB_XMIN_ZMIN = 1 << EI_XMIN_ZMIN, + EB_XMIN_ZMAX = 1 << EI_XMIN_ZMAX, + EB_XMAX_ZMIN = 1 << EI_XMAX_ZMIN, + EB_XMAX_ZMAX = 1 << EI_XMAX_ZMAX, + EB_YMIN_ZMIN = 1 << EI_YMIN_ZMIN, + EB_YMIN_ZMAX = 1 << EI_YMIN_ZMAX, + EB_YMAX_ZMIN = 1 << EI_YMAX_ZMIN, + EB_YMAX_ZMAX = 1 << EI_YMAX_ZMAX, + FB_XMIN = 1 << FI_XMIN, + FB_XMAX = 1 << FI_XMAX, + FB_YMIN = 1 << FI_YMIN, + FB_YMAX = 1 << FI_YMAX, + FB_ZMIN = 1 << FI_ZMIN, + FB_ZMAX = 1 << FI_ZMAX + }; + + // Vertex-edge-triangle table to support mesh topology. + class VETable + { + public: + VETable() = default; + + inline bool IsValidVertex(int i) const + { + return mVertex[i].valid; + } + + inline int64_t GetXN(int i) const + { + return mVertex[i].pos.xNumer; + } + + inline int64_t GetXD(int i) const + { + return mVertex[i].pos.xDenom; + } + + inline int64_t GetYN(int i) const + { + return mVertex[i].pos.yNumer; + } + + inline int64_t GetYD(int i) const + { + return mVertex[i].pos.yDenom; + } + + inline int64_t GetZN(int i) const + { + return mVertex[i].pos.zNumer; + } + + inline int64_t GetZD(int i) const + { + return mVertex[i].pos.zDenom; + } + + void Insert(int i, Vertex const& pos) + { + TVertex& vertex = mVertex[i]; + vertex.pos = pos; + vertex.valid = true; + } + + void Insert(int i0, int i1) + { + TVertex& vertex0 = mVertex[i0]; + TVertex& vertex1 = mVertex[i1]; + vertex0.adj[vertex0.numAdjacents++] = i1; + vertex1.adj[vertex1.numAdjacents++] = i0; + } + + void RemoveTriangles(std::vector& vertices, std::vector& triangles) + { + // Ear-clip the wireframe to get the triangles. + Triangle triangle; + while (Remove(triangle)) + { + int v0 = static_cast(vertices.size()); + int v1 = v0 + 1; + int v2 = v1 + 1; + triangles.push_back(Triangle(v0, v1, v2)); + vertices.push_back(mVertex[triangle.v[0]].pos); + vertices.push_back(mVertex[triangle.v[1]].pos); + vertices.push_back(mVertex[triangle.v[2]].pos); + } + } + + protected: + void RemoveVertex(int i) + { + TVertex& vertex0 = mVertex[i]; + // assert: vertex0.numAdjacents == 2 + int a0 = vertex0.adj[0]; + int a1 = vertex0.adj[1]; + TVertex& adjVertex0 = mVertex[a0]; + TVertex& adjVertex1 = mVertex[a1]; + + int j; + for (j = 0; j < adjVertex0.numAdjacents; ++j) + { + if (adjVertex0.adj[j] == i) + { + adjVertex0.adj[j] = a1; + break; + } + } + // assert: j != adjVertex0.numAdjacents + + for (j = 0; j < adjVertex1.numAdjacents; j++) + { + if (adjVertex1.adj[j] == i) + { + adjVertex1.adj[j] = a0; + break; + } + } + // assert: j != adjVertex1.numAdjacents + + vertex0.valid = false; + + if (adjVertex0.numAdjacents == 2) + { + if (adjVertex0.adj[0] == adjVertex0.adj[1]) + { + adjVertex0.valid = false; + } + } + + if (adjVertex1.numAdjacents == 2) + { + if (adjVertex1.adj[0] == adjVertex1.adj[1]) + { + adjVertex1.valid = false; + } + } + } + + bool Remove(Triangle& triangle) + { + for (int i = 0; i < 18; ++i) + { + TVertex& vertex = mVertex[i]; + if (vertex.valid && vertex.numAdjacents == 2) + { + triangle.v[0] = i; + triangle.v[1] = vertex.adj[0]; + triangle.v[2] = vertex.adj[1]; + RemoveVertex(i); + return true; + } + } + return false; + } + + class TVertex + { + public: + TVertex() + : + numAdjacents(0), + valid(false) + { + } + + Vertex pos; + int numAdjacents; + std::array adj; + bool valid; + }; + + std::array mVertex; + }; + + virtual std::array GetGradient(std::array const& pos) override + { + std::array const zero{ (Real)0, (Real)0, (Real)0 }; + + int x = static_cast(pos[0]); + if (x < 0 || x + 1 >= this->mXBound) + { + return zero; + } + + int y = static_cast(pos[1]); + if (y < 0 || y + 1 >= this->mYBound) + { + return zero; + } + + int z = static_cast(pos[2]); + if (z < 0 || z + 1 >= this->mZBound) + { + return zero; + } + + // Get image values at corners of voxel. + int i000 = x + this->mXBound * (y + this->mYBound * z); + int i100 = i000 + 1; + int i010 = i000 + this->mXBound; + int i110 = i010 + 1; + int i001 = i000 + this->mXYBound; + int i101 = i001 + 1; + int i011 = i001 + this->mXBound; + int i111 = i011 + 1; + Real f000 = static_cast(this->mVoxels[i000]); + Real f100 = static_cast(this->mVoxels[i100]); + Real f010 = static_cast(this->mVoxels[i010]); + Real f110 = static_cast(this->mVoxels[i110]); + Real f001 = static_cast(this->mVoxels[i001]); + Real f101 = static_cast(this->mVoxels[i101]); + Real f011 = static_cast(this->mVoxels[i011]); + Real f111 = static_cast(this->mVoxels[i111]); + + Real dx = pos[0] - static_cast(x); + Real dy = pos[1] - static_cast(y); + Real dz = pos[2] - static_cast(z); + Real oneMX = 1.0f - dx; + Real oneMY = 1.0f - dy; + Real oneMZ = 1.0f - dz; + + std::array grad; + + Real tmp0 = oneMY * (f100 - f000) + dy * (f110 - f010); + Real tmp1 = oneMY * (f101 - f001) + dy * (f111 - f011); + grad[0] = oneMZ * tmp0 + dz * tmp1; + + tmp0 = oneMX * (f010 - f000) + dx * (f110 - f100); + tmp1 = oneMX * (f011 - f001) + dx * (f111 - f101); + grad[1] = oneMZ * tmp0 + dz * tmp1; + + tmp0 = oneMX * (f001 - f000) + dx * (f101 - f100); + tmp1 = oneMX * (f011 - f010) + dx * (f111 - f110); + grad[2] = oneMY * tmp0 + dy * tmp1; + + return grad; + } + + int GetVertices(int x, int y, int z, VETable& table) + { + int type = 0; + + // Get the image values at the corners of the voxel. + int i000 = x + this->mXBound * (y + this->mYBound * z); + int i100 = i000 + 1; + int i010 = i000 + this->mXBound; + int i110 = i010 + 1; + int i001 = i000 + this->mXYBound; + int i101 = i001 + 1; + int i011 = i001 + this->mXBound; + int i111 = i011 + 1; + int64_t f000 = this->mVoxels[i000]; + int64_t f100 = this->mVoxels[i100]; + int64_t f010 = this->mVoxels[i010]; + int64_t f110 = this->mVoxels[i110]; + int64_t f001 = this->mVoxels[i001]; + int64_t f101 = this->mVoxels[i101]; + int64_t f011 = this->mVoxels[i011]; + int64_t f111 = this->mVoxels[i111]; + + int64_t x0 = x, x1 = x + 1, y0 = y, y1 = y + 1, z0 = z, z1 = z + 1; + int64_t d; + + // xmin-ymin edge + if (f000 * f001 < 0) + { + type |= EB_XMIN_YMIN; + d = f001 - f000; + table.Insert(EI_XMIN_YMIN, Vertex(x0, 1, y0, 1, z0 * d - f000, d)); + } + + // xmin-ymax edge + if (f010 * f011 < 0) + { + type |= EB_XMIN_YMAX; + d = f011 - f010; + table.Insert(EI_XMIN_YMAX, Vertex(x0, 1, y1, 1, z0 * d - f010, d)); + } + + // xmax-ymin edge + if (f100 * f101 < 0) + { + type |= EB_XMAX_YMIN; + d = f101 - f100; + table.Insert(EI_XMAX_YMIN, Vertex(x1, 1, y0, 1, z0 * d - f100, d)); + } + + // xmax-ymax edge + if (f110 * f111 < 0) + { + type |= EB_XMAX_YMAX; + d = f111 - f110; + table.Insert(EI_XMAX_YMAX, Vertex(x1, 1, y1, 1, z0 * d - f110, d)); + } + + // xmin-zmin edge + if (f000 * f010 < 0) + { + type |= EB_XMIN_ZMIN; + d = f010 - f000; + table.Insert(EI_XMIN_ZMIN, Vertex(x0, 1, y0 * d - f000, d, z0, 1)); + } + + // xmin-zmax edge + if (f001 * f011 < 0) + { + type |= EB_XMIN_ZMAX; + d = f011 - f001; + table.Insert(EI_XMIN_ZMAX, Vertex(x0, 1, y0 * d - f001, d, z1, 1)); + } + + // xmax-zmin edge + if (f100 * f110 < 0) + { + type |= EB_XMAX_ZMIN; + d = f110 - f100; + table.Insert(EI_XMAX_ZMIN, Vertex(x1, 1, y0 * d - f100, d, z0, 1)); + } + + // xmax-zmax edge + if (f101 * f111 < 0) + { + type |= EB_XMAX_ZMAX; + d = f111 - f101; + table.Insert(EI_XMAX_ZMAX, Vertex(x1, 1, y0 * d - f101, d, z1, 1)); + } + + // ymin-zmin edge + if (f000 * f100 < 0) + { + type |= EB_YMIN_ZMIN; + d = f100 - f000; + table.Insert(EI_YMIN_ZMIN, Vertex(x0 * d - f000, d, y0, 1, z0, 1)); + } + + // ymin-zmax edge + if (f001 * f101 < 0) + { + type |= EB_YMIN_ZMAX; + d = f101 - f001; + table.Insert(EI_YMIN_ZMAX, Vertex(x0 * d - f001, d, y0, 1, z1, 1)); + } + + // ymax-zmin edge + if (f010 * f110 < 0) + { + type |= EB_YMAX_ZMIN; + d = f110 - f010; + table.Insert(EI_YMAX_ZMIN, Vertex(x0 * d - f010, d, y1, 1, z0, 1)); + } + + // ymax-zmax edge + if (f011 * f111 < 0) + { + type |= EB_YMAX_ZMAX; + d = f111 - f011; + table.Insert(EI_YMAX_ZMAX, Vertex(x0 * d - f011, d, y1, 1, z1, 1)); + } + + return type; + } + + void GetXMinEdges(int x, int y, int z, int type, VETable& table) + { + int faceType = 0; + if (type & EB_XMIN_YMIN) + { + faceType |= 0x01; + } + if (type & EB_XMIN_YMAX) + { + faceType |= 0x02; + } + if (type & EB_XMIN_ZMIN) + { + faceType |= 0x04; + } + if (type & EB_XMIN_ZMAX) + { + faceType |= 0x08; + } + + switch (faceType) + { + case 0: + break; + case 3: + table.Insert(EI_XMIN_YMIN, EI_XMIN_YMAX); + break; + case 5: + table.Insert(EI_XMIN_YMIN, EI_XMIN_ZMIN); + break; + case 6: + table.Insert(EI_XMIN_YMAX, EI_XMIN_ZMIN); + break; + case 9: + table.Insert(EI_XMIN_YMIN, EI_XMIN_ZMAX); + break; + case 10: + table.Insert(EI_XMIN_YMAX, EI_XMIN_ZMAX); + break; + case 12: + table.Insert(EI_XMIN_ZMIN, EI_XMIN_ZMAX); + break; + case 15: + { + // Four vertices, one per edge, need to disambiguate. + int i = x + this->mXBound * (y + this->mYBound * z); + // F(x,y,z) + int64_t f00 = this->mVoxels[i]; + i += this->mXBound; + // F(x,y+1,z) + int64_t f10 = this->mVoxels[i]; + i += this->mXYBound; + // F(x,y+1,z+1) + int64_t f11 = this->mVoxels[i]; + i -= this->mXBound; + // F(x,y,z+1) + int64_t f01 = this->mVoxels[i]; + int64_t det = f00 * f11 - f01 * f10; + + if (det > 0) + { + // Disjoint hyperbolic segments, pair , . + table.Insert(EI_XMIN_YMIN, EI_XMIN_ZMIN); + table.Insert(EI_XMIN_YMAX, EI_XMIN_ZMAX); + } + else if (det < 0) + { + // Disjoint hyperbolic segments, pair , . + table.Insert(EI_XMIN_YMIN, EI_XMIN_ZMAX); + table.Insert(EI_XMIN_YMAX, EI_XMIN_ZMIN); + } + else + { + // Plus-sign configuration, add branch point to tessellation. + table.Insert(FI_XMIN, Vertex( + table.GetXN(EI_XMIN_ZMIN), table.GetXD(EI_XMIN_ZMIN), + table.GetYN(EI_XMIN_ZMIN), table.GetYD(EI_XMIN_ZMIN), + table.GetZN(EI_XMIN_YMIN), table.GetZD(EI_XMIN_YMIN))); + + // Add edges sharing the branch point. + table.Insert(EI_XMIN_YMIN, FI_XMIN); + table.Insert(EI_XMIN_YMAX, FI_XMIN); + table.Insert(EI_XMIN_ZMIN, FI_XMIN); + table.Insert(EI_XMIN_ZMAX, FI_XMIN); + } + break; + } + default: + LogError("Unexpected condition."); + } + } + + void GetXMaxEdges(int x, int y, int z, int type, VETable& table) + { + int faceType = 0; + if (type & EB_XMAX_YMIN) + { + faceType |= 0x01; + } + if (type & EB_XMAX_YMAX) + { + faceType |= 0x02; + } + if (type & EB_XMAX_ZMIN) + { + faceType |= 0x04; + } + if (type & EB_XMAX_ZMAX) + { + faceType |= 0x08; + } + + switch (faceType) + { + case 0: + break; + case 3: + table.Insert(EI_XMAX_YMIN, EI_XMAX_YMAX); + break; + case 5: + table.Insert(EI_XMAX_YMIN, EI_XMAX_ZMIN); + break; + case 6: + table.Insert(EI_XMAX_YMAX, EI_XMAX_ZMIN); + break; + case 9: + table.Insert(EI_XMAX_YMIN, EI_XMAX_ZMAX); + break; + case 10: + table.Insert(EI_XMAX_YMAX, EI_XMAX_ZMAX); + break; + case 12: + table.Insert(EI_XMAX_ZMIN, EI_XMAX_ZMAX); + break; + case 15: + { + // Four vertices, one per edge, need to disambiguate. + int i = (x + 1) + this->mXBound * (y + this->mYBound * z); + // F(x,y,z) + int64_t f00 = this->mVoxels[i]; + i += this->mXBound; + // F(x,y+1,z) + int64_t f10 = this->mVoxels[i]; + i += this->mXYBound; + // F(x,y+1,z+1) + int64_t f11 = this->mVoxels[i]; + i -= this->mXBound; + // F(x,y,z+1) + int64_t f01 = this->mVoxels[i]; + int64_t det = f00 * f11 - f01 * f10; + + if (det > 0) + { + // Disjoint hyperbolic segments, pair , . + table.Insert(EI_XMAX_YMIN, EI_XMAX_ZMIN); + table.Insert(EI_XMAX_YMAX, EI_XMAX_ZMAX); + } + else if (det < 0) + { + // Disjoint hyperbolic segments, pair , . + table.Insert(EI_XMAX_YMIN, EI_XMAX_ZMAX); + table.Insert(EI_XMAX_YMAX, EI_XMAX_ZMIN); + } + else + { + // Plus-sign configuration, add branch point to tessellation. + table.Insert(FI_XMAX, Vertex( + table.GetXN(EI_XMAX_ZMIN), table.GetXD(EI_XMAX_ZMIN), + table.GetYN(EI_XMAX_ZMIN), table.GetYD(EI_XMAX_ZMIN), + table.GetZN(EI_XMAX_YMIN), table.GetZD(EI_XMAX_YMIN))); + + // Add edges sharing the branch point. + table.Insert(EI_XMAX_YMIN, FI_XMAX); + table.Insert(EI_XMAX_YMAX, FI_XMAX); + table.Insert(EI_XMAX_ZMIN, FI_XMAX); + table.Insert(EI_XMAX_ZMAX, FI_XMAX); + } + break; + } + default: + LogError("Unexpected condition."); + } + } + + void GetYMinEdges(int x, int y, int z, int type, VETable& table) + { + int faceType = 0; + if (type & EB_XMIN_YMIN) + { + faceType |= 0x01; + } + if (type & EB_XMAX_YMIN) + { + faceType |= 0x02; + } + if (type & EB_YMIN_ZMIN) + { + faceType |= 0x04; + } + if (type & EB_YMIN_ZMAX) + { + faceType |= 0x08; + } + + switch (faceType) + { + case 0: + break; + case 3: + table.Insert(EI_XMIN_YMIN, EI_XMAX_YMIN); + break; + case 5: + table.Insert(EI_XMIN_YMIN, EI_YMIN_ZMIN); + break; + case 6: + table.Insert(EI_XMAX_YMIN, EI_YMIN_ZMIN); + break; + case 9: + table.Insert(EI_XMIN_YMIN, EI_YMIN_ZMAX); + break; + case 10: + table.Insert(EI_XMAX_YMIN, EI_YMIN_ZMAX); + break; + case 12: + table.Insert(EI_YMIN_ZMIN, EI_YMIN_ZMAX); + break; + case 15: + { + // Four vertices, one per edge, need to disambiguate. + int i = x + this->mXBound * (y + this->mYBound * z); + // F(x,y,z) + int64_t f00 = this->mVoxels[i]; + ++i; + // F(x+1,y,z) + int64_t f10 = this->mVoxels[i]; + i += this->mXYBound; + // F(x+1,y,z+1) + int64_t f11 = this->mVoxels[i]; + --i; + // F(x,y,z+1) + int64_t f01 = this->mVoxels[i]; + int64_t det = f00 * f11 - f01 * f10; + + if (det > 0) + { + // Disjoint hyperbolic segments, pair , . + table.Insert(EI_XMIN_YMIN, EI_YMIN_ZMIN); + table.Insert(EI_XMAX_YMIN, EI_YMIN_ZMAX); + } + else if (det < 0) + { + // Disjoint hyperbolic segments, pair , . + table.Insert(EI_XMIN_YMIN, EI_YMIN_ZMAX); + table.Insert(EI_XMAX_YMIN, EI_YMIN_ZMIN); + } + else + { + // Plus-sign configuration, add branch point to tessellation. + table.Insert(FI_YMIN, Vertex( + table.GetXN(EI_YMIN_ZMIN), table.GetXD(EI_YMIN_ZMIN), + table.GetYN(EI_XMIN_YMIN), table.GetYD(EI_XMIN_YMIN), + table.GetZN(EI_XMIN_YMIN), table.GetZD(EI_XMIN_YMIN))); + + // Add edges sharing the branch point. + table.Insert(EI_XMIN_YMIN, FI_YMIN); + table.Insert(EI_XMAX_YMIN, FI_YMIN); + table.Insert(EI_YMIN_ZMIN, FI_YMIN); + table.Insert(EI_YMIN_ZMAX, FI_YMIN); + } + break; + } + default: + LogError("Unexpected condition."); + } + } + + void GetYMaxEdges(int x, int y, int z, int type, VETable& table) + { + int faceType = 0; + if (type & EB_XMIN_YMAX) + { + faceType |= 0x01; + } + if (type & EB_XMAX_YMAX) + { + faceType |= 0x02; + } + if (type & EB_YMAX_ZMIN) + { + faceType |= 0x04; + } + if (type & EB_YMAX_ZMAX) + { + faceType |= 0x08; + } + + switch (faceType) + { + case 0: + break; + case 3: + table.Insert(EI_XMIN_YMAX, EI_XMAX_YMAX); + break; + case 5: + table.Insert(EI_XMIN_YMAX, EI_YMAX_ZMIN); + break; + case 6: + table.Insert(EI_XMAX_YMAX, EI_YMAX_ZMIN); + break; + case 9: + table.Insert(EI_XMIN_YMAX, EI_YMAX_ZMAX); + break; + case 10: + table.Insert(EI_XMAX_YMAX, EI_YMAX_ZMAX); + break; + case 12: + table.Insert(EI_YMAX_ZMIN, EI_YMAX_ZMAX); + break; + case 15: + { + // Four vertices, one per edge, need to disambiguate. + int i = x + this->mXBound * ((y + 1) + this->mYBound * z); + // F(x,y,z) + int64_t f00 = this->mVoxels[i]; + ++i; + // F(x+1,y,z) + int64_t f10 = this->mVoxels[i]; + i += this->mXYBound; + // F(x+1,y,z+1) + int64_t f11 = this->mVoxels[i]; + --i; + // F(x,y,z+1) + int64_t f01 = this->mVoxels[i]; + int64_t det = f00 * f11 - f01 * f10; + + if (det > 0) + { + // Disjoint hyperbolic segments, pair , . + table.Insert(EI_XMIN_YMAX, EI_YMAX_ZMIN); + table.Insert(EI_XMAX_YMAX, EI_YMAX_ZMAX); + } + else if (det < 0) + { + // Disjoint hyperbolic segments, pair , . + table.Insert(EI_XMIN_YMAX, EI_YMAX_ZMAX); + table.Insert(EI_XMAX_YMAX, EI_YMAX_ZMIN); + } + else + { + // Plus-sign configuration, add branch point to tessellation. + table.Insert(FI_YMAX, Vertex( + table.GetXN(EI_YMAX_ZMIN), table.GetXD(EI_YMAX_ZMIN), + table.GetYN(EI_XMIN_YMAX), table.GetYD(EI_XMIN_YMAX), + table.GetZN(EI_XMIN_YMAX), table.GetZD(EI_XMIN_YMAX))); + + // Add edges sharing the branch point. + table.Insert(EI_XMIN_YMAX, FI_YMAX); + table.Insert(EI_XMAX_YMAX, FI_YMAX); + table.Insert(EI_YMAX_ZMIN, FI_YMAX); + table.Insert(EI_YMAX_ZMAX, FI_YMAX); + } + break; + } + default: + LogError("Unexpected condition."); + } + } + + void GetZMinEdges(int x, int y, int z, int type, VETable& table) + { + int faceType = 0; + if (type & EB_XMIN_ZMIN) + { + faceType |= 0x01; + } + if (type & EB_XMAX_ZMIN) + { + faceType |= 0x02; + } + if (type & EB_YMIN_ZMIN) + { + faceType |= 0x04; + } + if (type & EB_YMAX_ZMIN) + { + faceType |= 0x08; + } + + switch (faceType) + { + case 0: + break; + case 3: + table.Insert(EI_XMIN_ZMIN, EI_XMAX_ZMIN); + break; + case 5: + table.Insert(EI_XMIN_ZMIN, EI_YMIN_ZMIN); + break; + case 6: + table.Insert(EI_XMAX_ZMIN, EI_YMIN_ZMIN); + break; + case 9: + table.Insert(EI_XMIN_ZMIN, EI_YMAX_ZMIN); + break; + case 10: + table.Insert(EI_XMAX_ZMIN, EI_YMAX_ZMIN); + break; + case 12: + table.Insert(EI_YMIN_ZMIN, EI_YMAX_ZMIN); + break; + case 15: + { + // Four vertices, one per edge, need to disambiguate. + int i = x + this->mXBound * (y + this->mYBound * z); + // F(x,y,z) + int64_t f00 = this->mVoxels[i]; + ++i; + // F(x+1,y,z) + int64_t f10 = this->mVoxels[i]; + i += this->mXBound; + // F(x+1,y+1,z) + int64_t f11 = this->mVoxels[i]; + --i; + // F(x,y+1,z) + int64_t f01 = this->mVoxels[i]; + int64_t det = f00 * f11 - f01 * f10; + + if (det > 0) + { + // Disjoint hyperbolic segments, pair , . + table.Insert(EI_XMIN_ZMIN, EI_YMIN_ZMIN); + table.Insert(EI_XMAX_ZMIN, EI_YMAX_ZMIN); + } + else if (det < 0) + { + // Disjoint hyperbolic segments, pair , . + table.Insert(EI_XMIN_ZMIN, EI_YMAX_ZMIN); + table.Insert(EI_XMAX_ZMIN, EI_YMIN_ZMIN); + } + else + { + // Plus-sign configuration, add branch point to tessellation. + table.Insert(FI_ZMIN, Vertex( + table.GetXN(EI_YMIN_ZMIN), table.GetXD(EI_YMIN_ZMIN), + table.GetYN(EI_XMIN_ZMIN), table.GetYD(EI_XMIN_ZMIN), + table.GetZN(EI_XMIN_ZMIN), table.GetZD(EI_XMIN_ZMIN))); + + // Add edges sharing the branch point. + table.Insert(EI_XMIN_ZMIN, FI_ZMIN); + table.Insert(EI_XMAX_ZMIN, FI_ZMIN); + table.Insert(EI_YMIN_ZMIN, FI_ZMIN); + table.Insert(EI_YMAX_ZMIN, FI_ZMIN); + } + break; + } + default: + LogError("Unexpected condition."); + } + } + + void GetZMaxEdges(int x, int y, int z, int type, VETable& table) + { + int faceType = 0; + if (type & EB_XMIN_ZMAX) + { + faceType |= 0x01; + } + if (type & EB_XMAX_ZMAX) + { + faceType |= 0x02; + } + if (type & EB_YMIN_ZMAX) + { + faceType |= 0x04; + } + if (type & EB_YMAX_ZMAX) + { + faceType |= 0x08; + } + + switch (faceType) + { + case 0: + break; + case 3: + table.Insert(EI_XMIN_ZMAX, EI_XMAX_ZMAX); + break; + case 5: + table.Insert(EI_XMIN_ZMAX, EI_YMIN_ZMAX); + break; + case 6: + table.Insert(EI_XMAX_ZMAX, EI_YMIN_ZMAX); + break; + case 9: + table.Insert(EI_XMIN_ZMAX, EI_YMAX_ZMAX); + break; + case 10: + table.Insert(EI_XMAX_ZMAX, EI_YMAX_ZMAX); + break; + case 12: + table.Insert(EI_YMIN_ZMAX, EI_YMAX_ZMAX); + break; + case 15: + { + // Four vertices, one per edge, need to disambiguate. + int i = x + this->mXBound * (y + this->mYBound * (z + 1)); + // F(x,y,z) + int64_t f00 = this->mVoxels[i]; + ++i; + // F(x+1,y,z) + int64_t f10 = this->mVoxels[i]; + i += this->mXBound; + // F(x+1,y+1,z) + int64_t f11 = this->mVoxels[i]; + --i; + // F(x,y+1,z) + int64_t f01 = this->mVoxels[i]; + int64_t det = f00 * f11 - f01 * f10; + + if (det > 0) + { + // Disjoint hyperbolic segments, pair , . + table.Insert(EI_XMIN_ZMAX, EI_YMIN_ZMAX); + table.Insert(EI_XMAX_ZMAX, EI_YMAX_ZMAX); + } + else if (det < 0) + { + // Disjoint hyperbolic segments, pair , . + table.Insert(EI_XMIN_ZMAX, EI_YMAX_ZMAX); + table.Insert(EI_XMAX_ZMAX, EI_YMIN_ZMAX); + } + else + { + // Plus-sign configuration, add branch point to tessellation. + table.Insert(FI_ZMAX, Vertex( + table.GetXN(EI_YMIN_ZMAX), table.GetXD(EI_YMIN_ZMAX), + table.GetYN(EI_XMIN_ZMAX), table.GetYD(EI_XMIN_ZMAX), + table.GetZN(EI_XMIN_ZMAX), table.GetZD(EI_XMIN_ZMAX))); + + // Add edges sharing the branch point. + table.Insert(EI_XMIN_ZMAX, FI_ZMAX); + table.Insert(EI_XMAX_ZMAX, FI_ZMAX); + table.Insert(EI_YMIN_ZMAX, FI_ZMAX); + table.Insert(EI_YMAX_ZMAX, FI_ZMAX); + } + break; + } + default: + LogError("Unexpected condition."); + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SurfaceExtractorMC.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SurfaceExtractorMC.h new file mode 100644 index 0000000..c6c2575 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SurfaceExtractorMC.h @@ -0,0 +1,332 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class SurfaceExtractorMC : public MarchingCubes + { + public: + // Construction and destruction. + virtual ~SurfaceExtractorMC() + { + } + + SurfaceExtractorMC(Image3 const& image) + : + mImage(image) + { + } + + // Object copies are not allowed. + SurfaceExtractorMC() = delete; + SurfaceExtractorMC(SurfaceExtractorMC const&) = delete; + SurfaceExtractorMC const& operator=(SurfaceExtractorMC const&) = delete; + + struct Mesh + { + // All members are set to zeros. + Mesh() + { + std::array zero = { (Real)0, (Real)0, (Real)0 }; + std::fill(vertices.begin(), vertices.end(), zero); + } + + Topology topology; + std::array, MAX_VERTICES> vertices; + }; + + // Extract the triangle mesh approximating F = 0 for a single voxel. + // The input function values must be stored as + // F[0] = function(0,0,0), F[4] = function(0,0,1), + // F[1] = function(1,0,0), F[5] = function(1,0,1), + // F[2] = function(0,1,0), F[6] = function(0,1,1), + // F[3] = function(1,1,0), F[7] = function(1,1,1). + // Thus, F[k] = function(k & 1, (k & 2) >> 1, (k & 4) >> 2). + // The return value is 'true' iff the F[] values are all nonzero. + // If they are not, the returned 'mesh' has no vertices and no + // triangles--as if F[] had all positive or all negative values. + bool Extract(std::array const& F, Mesh& mesh) const + { + int entry = 0; + for (int i = 0, mask = 1; i < 8; ++i, mask <<= 1) + { + if (F[i] < (Real)0) + { + entry |= mask; + } + else if (F[i] == (Real)0) + { + return false; + } + } + + mesh.topology = GetTable(entry); + + for (int i = 0; i < mesh.topology.numVertices; ++i) + { + int j0 = mesh.topology.vpair[i][0]; + int j1 = mesh.topology.vpair[i][1]; + + Real corner0[3]; + corner0[0] = static_cast(j0 & 1); + corner0[1] = static_cast((j0 & 2) >> 1); + corner0[2] = static_cast((j0 & 4) >> 2); + + Real corner1[3]; + corner1[0] = static_cast(j1 & 1); + corner1[1] = static_cast((j1 & 2) >> 1); + corner1[2] = static_cast((j1 & 4) >> 2); + + Real invDenom = ((Real)1) / (F[j0] - F[j1]); + for (int k = 0; k < 3; ++k) + { + Real numer = F[j0] * corner1[k] - F[j1] * corner0[k]; + mesh.vertices[i][k] = numer * invDenom; + } + } + return true; + } + + // Extract the triangle mesh approximating F = 0 for all the voxels in + // a 3D image. The input image must be stored in a 1-dimensional + // array with lexicographical order; that is, image[i] corresponds to + // voxel location (x,y,z) where i = x + bound0 * (y + bound1 * z). + // The output 'indices' consists indices.size()/3 triangles, each a + // triple of indices into 'vertices' + bool Extract(Real level, std::vector>& vertices, std::vector& indices) const + { + vertices.clear(); + indices.clear(); + + for (int z = 0; z + 1 < mImage.GetDimension(2); ++z) + { + for (int y = 0; y + 1 < mImage.GetDimension(1); ++y) + { + for (int x = 0; x + 1 < mImage.GetDimension(0); ++x) + { + std::array corners; + mImage.GetCorners(x, y, z, corners); + + std::array F; + for (int k = 0; k < 8; ++k) + { + F[k] = mImage[corners[k]] - level; + } + + Mesh mesh; + + if (Extract(F, mesh)) + { + int vbase = static_cast(vertices.size()); + for (int i = 0; i < mesh.topology.numVertices; ++i) + { + Vector3 position = mesh.vertices[i]; + position[0] += static_cast(x); + position[1] += static_cast(y); + position[2] += static_cast(z); + vertices.push_back(position); + } + + for (int i = 0; i < mesh.topology.numTriangles; ++i) + { + for (int j = 0; j < 3; ++j) + { + indices.push_back(vbase + mesh.topology.itriple[i][j]); + } + } + } + else + { + vertices.clear(); + indices.clear(); + return false; + } + } + } + } + + return true; + } + + // The extraction has duplicate vertices on edges shared by voxels. + // This function will eliminate the duplication. + void MakeUnique(std::vector>& vertices, std::vector& indices) const + { + std::vector> outVertices; + std::vector outIndices; + UniqueVerticesTriangles>(vertices, indices, outVertices, outIndices); + vertices = std::move(outVertices); + indices = std::move(outIndices); + } + + // The extraction does not use any topological information about the + // level surface. The triangles can be a mixture of clockwise-ordered + // and counterclockwise-ordered. This function is an attempt to give + // the triangles a consistent ordering by selecting a normal in + // approximately the same direction as the average gradient at the + // vertices (when sameDir is true), or in the opposite direction (when + // sameDir is false). This might not always produce a consistent + // order, but is fast. A consistent order can be computed if you + // build a table of vertex, edge, and face adjacencies, but the + // resulting data structure is somewhat expensive to process to + // reorient triangles. + void OrientTriangles(std::vector> const& vertices, std::vector& indices, bool sameDir) const + { + int const numTriangles = static_cast(indices.size() / 3); + int* triangle = indices.data(); + for (int t = 0; t < numTriangles; ++t, triangle += 3) + { + // Get triangle vertices. + Vector3 v0 = vertices[triangle[0]]; + Vector3 v1 = vertices[triangle[1]]; + Vector3 v2 = vertices[triangle[2]]; + + // Construct triangle normal based on current orientation. + Vector3 edge1 = v1 - v0; + Vector3 edge2 = v2 - v0; + Vector3 normal = Cross(edge1, edge2); + + // Get the image gradient at the vertices. + Vector3 gradient0 = GetGradient(v0); + Vector3 gradient1 = GetGradient(v1); + Vector3 gradient2 = GetGradient(v2); + + // Compute the average gradient. + Vector3 gradientAvr = (gradient0 + gradient1 + gradient2) / (Real)3; + + // Compute the dot product of normal and average gradient. + Real dot = Dot(gradientAvr, normal); + + // Choose triangle orientation based on gradient direction. + if (sameDir) + { + if (dot < (Real)0) + { + // Wrong orientation, reorder it. + std::swap(triangle[1], triangle[2]); + } + } + else + { + if (dot > (Real)0) + { + // Wrong orientation, reorder it. + std::swap(triangle[1], triangle[2]); + } + } + } + } + + // Compute vertex normals for the mesh. + void ComputeNormals(std::vector> const& vertices, std::vector const& indices, + std::vector>& normals) const + { + // Maintain a running sum of triangle normals at each vertex. + int const numVertices = static_cast(vertices.size()); + normals.resize(numVertices); + Vector3 zero = Vector3::Zero(); + std::fill(normals.begin(), normals.end(), zero); + + int const numTriangles = static_cast(indices.size() / 3); + int const* current = indices.data(); + for (int i = 0; i < numTriangles; ++i) + { + int i0 = *current++; + int i1 = *current++; + int i2 = *current++; + Vector3 v0 = vertices[i0]; + Vector3 v1 = vertices[i1]; + Vector3 v2 = vertices[i2]; + + // Construct triangle normal. + Vector3 edge1 = v1 - v0; + Vector3 edge2 = v2 - v0; + Vector3 normal = Cross(edge1, edge2); + + // Maintain the sum of normals at each vertex. + normals[i0] += normal; + normals[i1] += normal; + normals[i2] += normal; + } + + // The normal vector storage was used to accumulate the sum of + // triangle normals. Now these vectors must be rescaled to be + // unit length. + for (auto& normal : normals) + { + Normalize(normal); + } + } + + protected: + Vector3 GetGradient(Vector3 position) const + { + int x = static_cast(std::floor(position[0])); + if (x < 0 || x >= mImage.GetDimension(0) - 1) + { + return Vector3::Zero(); + } + + int y = static_cast(std::floor(position[1])); + if (y < 0 || y >= mImage.GetDimension(1) - 1) + { + return Vector3::Zero(); + } + + int z = static_cast(std::floor(position[2])); + if (z < 0 || z >= mImage.GetDimension(2) - 1) + { + return Vector3::Zero(); + } + + position[0] -= static_cast(x); + position[1] -= static_cast(y); + position[2] -= static_cast(z); + Real oneMX = (Real)1 - position[0]; + Real oneMY = (Real)1 - position[1]; + Real oneMZ = (Real)1 - position[2]; + + // Get image values at corners of voxel. + std::array corners; + mImage.GetCorners(x, y, z, corners); + Real f000 = mImage[corners[0]]; + Real f100 = mImage[corners[1]]; + Real f010 = mImage[corners[2]]; + Real f110 = mImage[corners[3]]; + Real f001 = mImage[corners[4]]; + Real f101 = mImage[corners[5]]; + Real f011 = mImage[corners[6]]; + Real f111 = mImage[corners[7]]; + + Vector3 gradient; + + Real tmp0 = oneMY * (f100 - f000) + position[1] * (f110 - f010); + Real tmp1 = oneMY * (f101 - f001) + position[1] * (f111 - f011); + gradient[0] = oneMZ * tmp0 + position[2] * tmp1; + + tmp0 = oneMX * (f010 - f000) + position[0] * (f110 - f100); + tmp1 = oneMX * (f011 - f001) + position[0] * (f111 - f101); + gradient[1] = oneMZ * tmp0 + position[2] * tmp1; + + tmp0 = oneMX * (f001 - f000) + position[0] * (f101 - f100); + tmp1 = oneMX * (f011 - f010) + position[0] * (f111 - f110); + gradient[2] = oneMY * tmp0 + position[1] * tmp1; + + return gradient; + } + + Image3 const& mImage; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SurfaceExtractorTetrahedra.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SurfaceExtractorTetrahedra.h new file mode 100644 index 0000000..03989b8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SurfaceExtractorTetrahedra.h @@ -0,0 +1,995 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The level set extraction algorithm implemented here is described +// in the document +// https://www.geometrictools.com/Documentation/ExtractLevelSurfaces.pdf + +namespace WwiseGTE +{ + // The image type T must be one of the integer types: int8_t, int16_t, + // int32_t, uint8_t, uint16_t or uint32_t. Internal integer computations + // are performed using int64_t. The type Real is for extraction to + // floating-point vertices. + template + class SurfaceExtractorTetrahedra : public SurfaceExtractor + { + public: + // Convenience type definitions. + typedef typename SurfaceExtractor::Vertex Vertex; + typedef typename SurfaceExtractor::Triangle Triangle; + + // The input is a 3D image with lexicographically ordered voxels + // (x,y,z) stored in a linear array. Voxel (x,y,z) is stored in the + // array at location index = x + xBound * (y + yBound * z). The + // inputs xBound, yBound and zBound must each be 2 or larger so that + // there is at least one image cube to process. The inputVoxels must + // be nonnull and point to contiguous storage that contains at least + // xBound * yBound * zBound elements. + SurfaceExtractorTetrahedra(int xBound, int yBound, int zBound, T const* inputVoxels) + : + SurfaceExtractor(xBound, yBound, zBound, inputVoxels), + mNextVertex(0) + { + } + + // Extract level surfaces and return rational vertices. Use the + // base-class Extract if you want real-valued vertices. + virtual void Extract(T level, std::vector& vertices, + std::vector& triangles) override + { + // Adjust the image so that the level set is F(x,y,z) = 0. + int64_t levelI64 = static_cast(level); + for (size_t i = 0; i < this->mVoxels.size(); ++i) + { + int64_t inputI64 = static_cast(this->mInputVoxels[i]); + this->mVoxels[i] = inputI64 - levelI64; + } + + mVMap.clear(); + mESet.clear(); + mTSet.clear(); + mNextVertex = 0; + vertices.clear(); + triangles.clear(); + for (int z = 0, zp = 1; zp < this->mZBound; ++z, ++zp) + { + int zParity = (z & 1); + for (int y = 0, yp = 1; yp < this->mYBound; ++y, ++yp) + { + int yParity = (y & 1); + for (int x = 0, xp = 1; xp < this->mXBound; ++x, ++xp) + { + int xParity = (x & 1); + + int i000 = x + this->mXBound * (y + this->mYBound * z); + int i100 = i000 + 1; + int i010 = i000 + this->mXBound; + int i110 = i010 + 1; + int i001 = i000 + this->mXYBound; + int i101 = i001 + 1; + int i011 = i001 + this->mXBound; + int i111 = i011 + 1; + int64_t f000 = static_cast(this->mVoxels[i000]); + int64_t f100 = static_cast(this->mVoxels[i100]); + int64_t f010 = static_cast(this->mVoxels[i010]); + int64_t f110 = static_cast(this->mVoxels[i110]); + int64_t f001 = static_cast(this->mVoxels[i001]); + int64_t f101 = static_cast(this->mVoxels[i101]); + int64_t f011 = static_cast(this->mVoxels[i011]); + int64_t f111 = static_cast(this->mVoxels[i111]); + + if (xParity ^ yParity ^ zParity) + { + // 1205 + ProcessTetrahedron( + xp, y, z, f100, + xp, yp, z, f110, + x, y, z, f000, + xp, y, zp, f101); + + // 3027 + ProcessTetrahedron( + x, yp, z, f010, + x, y, z, f000, + xp, yp, z, f110, + x, yp, zp, f011); + + // 4750 + ProcessTetrahedron( + x, y, zp, f001, + x, yp, zp, f011, + xp, y, zp, f101, + x, y, z, f000); + + // 6572 + ProcessTetrahedron( + xp, yp, zp, f111, + xp, y, zp, f101, + x, yp, zp, f011, + xp, yp, z, f110); + + // 0752 + ProcessTetrahedron( + x, y, z, f000, + x, yp, zp, f011, + xp, y, zp, f101, + xp, yp, z, f110); + } + else + { + // 0134 + ProcessTetrahedron( + x, y, z, f000, + xp, y, z, f100, + x, yp, z, f010, + x, y, zp, f001); + + // 2316 + ProcessTetrahedron( + xp, yp, z, f110, + x, yp, z, f010, + xp, y, z, f100, + xp, yp, zp, f111); + + // 5461 + ProcessTetrahedron( + xp, y, zp, f101, + x, y, zp, f001, + xp, yp, zp, f111, + xp, y, z, f100); + + // 7643 + ProcessTetrahedron( + x, yp, zp, f011, + xp, yp, zp, f111, + x, y, zp, f001, + x, yp, z, f010); + + // 6314 + ProcessTetrahedron( + xp, yp, zp, f111, + x, yp, z, f010, + xp, y, z, f100, + x, y, zp, f001); + } + } + } + } + + // Pack vertices into an array. + vertices.resize(mVMap.size()); + for (auto const& element : mVMap) + { + vertices[element.second] = element.first; + } + + // Pack edges into an array (computed, but not reported to + // caller). + std::vector edges(mESet.size()); + size_t i = 0; + for (auto const& element : mESet) + { + edges[i++] = element; + } + + // Pack triangles into an array. + triangles.resize(mTSet.size()); + i = 0; + for (auto const& element : mTSet) + { + triangles[i++] = element; + } + } + + protected: + struct Edge + { + Edge() = default; + + Edge(int v0, int v1) + { + if (v0 < v1) + { + v[0] = v0; + v[1] = v1; + } + else + { + v[0] = v1; + v[1] = v0; + } + } + + bool operator==(Edge const& other) const + { + return v[0] == other.v[0] && v[1] == other.v[1]; + } + + bool operator<(Edge const& other) const + { + for (int i = 0; i < 2; ++i) + { + if (v[i] < other.v[i]) + { + return true; + } + if (v[i] > other.v[i]) + { + return false; + } + } + return false; + } + + std::array v; + }; + + virtual std::array GetGradient(std::array const& pos) override + { + std::array const zero{ (Real)0, (Real)0, (Real)0 }; + + int x = static_cast(pos[0]); + if (x < 0 || x + 1 >= this->mXBound) + { + return zero; + } + + int y = static_cast(pos[1]); + if (y < 0 || y + 1 >= this->mYBound) + { + return zero; + } + + int z = static_cast(pos[2]); + if (z < 0 || z + 1 >= this->mZBound) + { + return zero; + } + + // Get image values at corners of voxel. + int i000 = x + this->mXBound * (y + this->mYBound * z); + int i100 = i000 + 1; + int i010 = i000 + this->mXBound; + int i110 = i010 + 1; + int i001 = i000 + this->mXYBound; + int i101 = i001 + 1; + int i011 = i001 + this->mXBound; + int i111 = i011 + 1; + Real f000 = static_cast(this->mVoxels[i000]); + Real f100 = static_cast(this->mVoxels[i100]); + Real f010 = static_cast(this->mVoxels[i010]); + Real f110 = static_cast(this->mVoxels[i110]); + Real f001 = static_cast(this->mVoxels[i001]); + Real f101 = static_cast(this->mVoxels[i101]); + Real f011 = static_cast(this->mVoxels[i011]); + Real f111 = static_cast(this->mVoxels[i111]); + + Real dx = pos[0] - static_cast(x); + Real dy = pos[1] - static_cast(y); + Real dz = pos[2] - static_cast(z); + + std::array grad; + + if ((x & 1) ^ (y & 1) ^ (z & 1)) + { + if (dx - dy - dz >= (Real)0) + { + // 1205 + grad[0] = +f100 - f000; + grad[1] = -f100 + f110; + grad[2] = -f100 + f101; + } + else if (dx - dy + dz <= (Real)0) + { + // 3027 + grad[0] = -f010 + f110; + grad[1] = +f010 - f000; + grad[2] = -f010 + f011; + } + else if (dx + dy - dz <= (Real)0) + { + // 4750 + grad[0] = -f001 + f101; + grad[1] = -f001 + f011; + grad[2] = +f001 - f000; + } + else if (dx + dy + dz >= (Real)0) + { + // 6572 + grad[0] = +f111 - f011; + grad[1] = +f111 - f101; + grad[2] = +f111 - f110; + } + else + { + // 0752 + grad[0] = (Real)0.5 * (-f000 - f011 + f101 + f110); + grad[1] = (Real)0.5 * (-f000 + f011 - f101 + f110); + grad[2] = (Real)0.5 * (-f000 + f011 + f101 - f110); + } + } + else + { + if (dx + dy + dz <= (Real)1) + { + // 0134 + grad[0] = -f000 + f100; + grad[1] = -f000 + f010; + grad[2] = -f000 + f001; + } + else if (dx + dy - dz >= (Real)1) + { + // 2316 + grad[0] = +f110 - f010; + grad[1] = +f110 - f100; + grad[2] = -f110 + f111; + } + else if (dx - dy + dz >= (Real)1) + { + // 5461 + grad[0] = +f101 - f001; + grad[1] = -f101 + f111; + grad[2] = +f101 - f100; + } + else if (-dx + dy + dz >= (Real)1) + { + // 7643 + grad[0] = -f011 + f111; + grad[1] = +f011 - f001; + grad[2] = +f011 - f010; + } + else + { + // 6314 + grad[0] = (Real)0.5 * (f111 - f010 + f100 - f001); + grad[1] = (Real)0.5 * (f111 + f010 - f100 - f001); + grad[2] = (Real)0.5 * (f111 - f010 - f100 + f001); + } + } + + return grad; + } + + int AddVertex(Vertex const& v) + { + auto iter = mVMap.find(v); + if (iter != mVMap.end()) + { + // Vertex already in map, just return its unique index. + return iter->second; + } + else + { + // Vertex not in map, insert it and assign it a unique index. + int i = mNextVertex++; + mVMap.insert(std::make_pair(v, i)); + return i; + } + } + + void AddEdge(Vertex const& v0, Vertex const& v1) + { + int i0 = AddVertex(v0); + int i1 = AddVertex(v1); + mESet.insert(Edge(i0, i1)); + } + + void AddTriangle(Vertex const& v0, Vertex const& v1, Vertex const& v2) + { + int i0 = AddVertex(v0); + int i1 = AddVertex(v1); + int i2 = AddVertex(v2); + + // Nothing to do if triangle already exists. + Triangle triangle(i0, i1, i2); + if (mTSet.find(triangle) != mTSet.end()) + { + return; + } + + // Prevent double-sided triangles. + std::swap(triangle.v[1], triangle.v[2]); + if (mTSet.find(triangle) != mTSet.end()) + { + return; + } + + mESet.insert(Edge(i0, i1)); + mESet.insert(Edge(i1, i2)); + mESet.insert(Edge(i2, i0)); + + mTSet.insert(triangle); + } + + // Support for extraction with linear interpolation. + void ProcessTetrahedron( + int64_t x0, int64_t y0, int64_t z0, int64_t f0, + int64_t x1, int64_t y1, int64_t z1, int64_t f1, + int64_t x2, int64_t y2, int64_t z2, int64_t f2, + int64_t x3, int64_t y3, int64_t z3, int64_t f3) + { + int64_t xn0, yn0, zn0, d0; + int64_t xn1, yn1, zn1, d1; + int64_t xn2, yn2, zn2, d2; + int64_t xn3, yn3, zn3, d3; + if (f0 != 0) + { + // convert to case +*** + if (f0 < 0) + { + f0 = -f0; + f1 = -f1; + f2 = -f2; + f3 = -f3; + } + + if (f1 > 0) + { + if (f2 > 0) + { + if (f3 > 0) + { + // ++++ + return; + } + else if (f3 < 0) + { + // +++- + d0 = f0 - f3; + xn0 = f0 * x3 - f3 * x0; + yn0 = f0 * y3 - f3 * y0; + zn0 = f0 * z3 - f3 * z0; + d1 = f1 - f3; + xn1 = f1 * x3 - f3 * x1; + yn1 = f1 * y3 - f3 * y1; + zn1 = f1 * z3 - f3 * z1; + d2 = f2 - f3; + xn2 = f2 * x3 - f3 * x2; + yn2 = f2 * y3 - f3 * y2; + zn2 = f2 * z3 - f3 * z2; + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(xn1, d1, yn1, d1, zn1, d1), + Vertex(xn2, d2, yn2, d2, zn2, d2)); + } + else + { + // +++0 + AddVertex( + Vertex(x3, 1, y3, 1, z3, 1)); + } + } + else if (f2 < 0) + { + d0 = f0 - f2; + xn0 = f0 * x2 - f2 * x0; + yn0 = f0 * y2 - f2 * y0; + zn0 = f0 * z2 - f2 * z0; + d1 = f1 - f2; + xn1 = f1 * x2 - f2 * x1; + yn1 = f1 * y2 - f2 * y1; + zn1 = f1 * z2 - f2 * z1; + + if (f3 > 0) + { + // ++-+ + d2 = f3 - f2; + xn2 = f3 * x2 - f2 * x3; + yn2 = f3 * y2 - f2 * y3; + zn2 = f3 * z2 - f2 * z3; + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(xn1, d1, yn1, d1, zn1, d1), + Vertex(xn2, d2, yn2, d2, zn2, d2)); + } + else if (f3 < 0) + { + // ++-- + d2 = f0 - f3; + xn2 = f0 * x3 - f3 * x0; + yn2 = f0 * y3 - f3 * y0; + zn2 = f0 * z3 - f3 * z0; + d3 = f1 - f3; + xn3 = f1 * x3 - f3 * x1; + yn3 = f1 * y3 - f3 * y1; + zn3 = f1 * z3 - f3 * z1; + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(xn1, d1, yn1, d1, zn1, d1), + Vertex(xn2, d2, yn2, d2, zn2, d2)); + AddTriangle( + Vertex(xn1, d1, yn1, d1, zn1, d1), + Vertex(xn3, d3, yn3, d3, zn3, d3), + Vertex(xn2, d2, yn2, d2, zn2, d2)); + } + else + { + // ++-0 + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(xn1, d1, yn1, d1, zn1, d1), + Vertex(x3, 1, y3, 1, z3, 1)); + } + } + else + { + if (f3 > 0) + { + // ++0+ + AddVertex( + Vertex(x2, 1, y2, 1, z2, 1)); + } + else if (f3 < 0) + { + // ++0- + d0 = f0 - f3; + xn0 = f0 * x3 - f3 * x0; + yn0 = f0 * y3 - f3 * y0; + zn0 = f0 * z3 - f3 * z0; + d1 = f1 - f3; + xn1 = f1 * x3 - f3 * x1; + yn1 = f1 * y3 - f3 * y1; + zn1 = f1 * z3 - f3 * z1; + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(xn1, d1, yn1, d1, zn1, d1), + Vertex(x2, 1, y2, 1, z2, 1)); + } + else + { + // ++00 + AddEdge( + Vertex(x2, 1, y2, 1, z2, 1), + Vertex(x3, 1, y3, 1, z3, 1)); + } + } + } + else if (f1 < 0) + { + if (f2 > 0) + { + d0 = f0 - f1; + xn0 = f0 * x1 - f1 * x0; + yn0 = f0 * y1 - f1 * y0; + zn0 = f0 * z1 - f1 * z0; + d1 = f2 - f1; + xn1 = f2 * x1 - f1 * x2; + yn1 = f2 * y1 - f1 * y2; + zn1 = f2 * z1 - f1 * z2; + + if (f3 > 0) + { + // +-++ + d2 = f3 - f1; + xn2 = f3 * x1 - f1 * x3; + yn2 = f3 * y1 - f1 * y3; + zn2 = f3 * z1 - f1 * z3; + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(xn1, d1, yn1, d1, zn1, d1), + Vertex(xn2, d2, yn2, d2, zn2, d2)); + } + else if (f3 < 0) + { + // +-+- + d2 = f0 - f3; + xn2 = f0 * x3 - f3 * x0; + yn2 = f0 * y3 - f3 * y0; + zn2 = f0 * z3 - f3 * z0; + d3 = f2 - f3; + xn3 = f2 * x3 - f3 * x2; + yn3 = f2 * y3 - f3 * y2; + zn3 = f2 * z3 - f3 * z2; + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(xn1, d1, yn1, d1, zn1, d1), + Vertex(xn2, d2, yn2, d2, zn2, d2)); + AddTriangle( + Vertex(xn1, d1, yn1, d1, zn1, d1), + Vertex(xn3, d3, yn3, d3, zn3, d3), + Vertex(xn2, d2, yn2, d2, zn2, d2)); + } + else + { + // +-+0 + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(xn1, d1, yn1, d1, zn1, d1), + Vertex(x3, 1, y3, 1, z3, 1)); + } + } + else if (f2 < 0) + { + d0 = f1 - f0; + xn0 = f1 * x0 - f0 * x1; + yn0 = f1 * y0 - f0 * y1; + zn0 = f1 * z0 - f0 * z1; + d1 = f2 - f0; + xn1 = f2 * x0 - f0 * x2; + yn1 = f2 * y0 - f0 * y2; + zn1 = f2 * z0 - f0 * z2; + + if (f3 > 0) + { + // +--+ + d2 = f1 - f3; + xn2 = f1 * x3 - f3 * x1; + yn2 = f1 * y3 - f3 * y1; + zn2 = f1 * z3 - f3 * z1; + d3 = f2 - f3; + xn3 = f2 * x3 - f3 * x2; + yn3 = f2 * y3 - f3 * y2; + zn3 = f2 * z3 - f3 * z2; + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(xn1, d1, yn1, d1, zn1, d1), + Vertex(xn2, d2, yn2, d2, zn2, d2)); + AddTriangle( + Vertex(xn1, d1, yn1, d1, zn1, d1), + Vertex(xn3, d3, yn3, d3, zn3, d3), + Vertex(xn2, d2, yn2, d2, zn2, d2)); + } + else if (f3 < 0) + { + // +--- + d2 = f3 - f0; + xn2 = f3 * x0 - f0 * x3; + yn2 = f3 * y0 - f0 * y3; + zn2 = f3 * z0 - f0 * z3; + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(xn1, d1, yn1, d1, zn1, d1), + Vertex(xn2, d2, yn2, d2, zn2, d2)); + } + else + { + // +--0 + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(xn1, d1, yn1, d1, zn1, d1), + Vertex(x3, 1, y3, 1, z3, 1)); + } + } + else + { + d0 = f1 - f0; + xn0 = f1 * x0 - f0 * x1; + yn0 = f1 * y0 - f0 * y1; + zn0 = f1 * z0 - f0 * z1; + + if (f3 > 0) + { + // +-0+ + d1 = f1 - f3; + xn1 = f1 * x3 - f3 * x1; + yn1 = f1 * y3 - f3 * y1; + zn1 = f1 * z3 - f3 * z1; + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(xn1, d1, yn1, d1, zn1, d1), + Vertex(x2, 1, y2, 1, z2, 1)); + } + else if (f3 < 0) + { + // +-0- + d1 = f3 - f0; + xn1 = f3 * x0 - f0 * x3; + yn1 = f3 * y0 - f0 * y3; + zn1 = f3 * z0 - f0 * z3; + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(xn1, d1, yn1, d1, zn1, d1), + Vertex(x2, 1, y2, 1, z2, 1)); + } + else + { + // +-00 + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(x2, 1, y2, 1, z2, 1), + Vertex(x3, 1, y3, 1, z3, 1)); + } + } + } + else + { + if (f2 > 0) + { + if (f3 > 0) + { + // +0++ + AddVertex( + Vertex(x1, 1, y1, 1, z1, 1)); + } + else if (f3 < 0) + { + // +0+- + d0 = f0 - f3; + xn0 = f0 * x3 - f3 * x0; + yn0 = f0 * y3 - f3 * y0; + zn0 = f0 * z3 - f3 * z0; + d1 = f2 - f3; + xn1 = f2 * x3 - f3 * x2; + yn1 = f2 * y3 - f3 * y2; + zn1 = f2 * z3 - f3 * z2; + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(xn1, d1, yn1, d1, zn1, d1), + Vertex(x1, 1, y1, 1, z1, 1)); + } + else + { + // +0+0 + AddEdge( + Vertex(x1, 1, y1, 1, z1, 1), + Vertex(x3, 1, y3, 1, z3, 1)); + } + } + else if (f2 < 0) + { + d0 = f2 - f0; + xn0 = f2 * x0 - f0 * x2; + yn0 = f2 * y0 - f0 * y2; + zn0 = f2 * z0 - f0 * z2; + + if (f3 > 0) + { + // +0-+ + d1 = f2 - f3; + xn1 = f2 * x3 - f3 * x2; + yn1 = f2 * y3 - f3 * y2; + zn1 = f2 * z3 - f3 * z2; + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(xn1, d1, yn1, d1, zn1, d1), + Vertex(x1, 1, y1, 1, z1, 1)); + } + else if (f3 < 0) + { + // +0-- + d1 = f0 - f3; + xn1 = f0 * x3 - f3 * x0; + yn1 = f0 * y3 - f3 * y0; + zn1 = f0 * z3 - f3 * z0; + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(xn1, d1, yn1, d1, zn1, d1), + Vertex(x1, 1, y1, 1, z1, 1)); + } + else + { + // +0-0 + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(x1, 1, y1, 1, z1, 1), + Vertex(x3, 1, y3, 1, z3, 1)); + } + } + else + { + if (f3 > 0) + { + // +00+ + AddEdge( + Vertex(x1, 1, y1, 1, z1, 1), + Vertex(x2, 1, y2, 1, z2, 1)); + } + else if (f3 < 0) + { + // +00- + d0 = f0 - f3; + xn0 = f0 * x3 - f3 * x0; + yn0 = f0 * y3 - f3 * y0; + zn0 = f0 * z3 - f3 * z0; + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(x1, 1, y1, 1, z1, 1), + Vertex(x2, 1, y2, 1, z2, 1)); + } + else + { + // +000 + AddTriangle( + Vertex(x1, 1, y1, 1, z1, 1), + Vertex(x2, 1, y2, 1, z2, 1), + Vertex(x3, 1, y3, 1, z3, 1)); + } + } + } + } + else if (f1 != 0) + { + // convert to case 0+** + if (f1 < 0) + { + f1 = -f1; + f2 = -f2; + f3 = -f3; + } + + if (f2 > 0) + { + if (f3 > 0) + { + // 0+++ + AddVertex( + Vertex(x0, 1, y0, 1, z0, 1)); + } + else if (f3 < 0) + { + // 0++- + d0 = f2 - f3; + xn0 = f2 * x3 - f3 * x2; + yn0 = f2 * y3 - f3 * y2; + zn0 = f2 * z3 - f3 * z2; + d1 = f1 - f3; + xn1 = f1 * x3 - f3 * x1; + yn1 = f1 * y3 - f3 * y1; + zn1 = f1 * z3 - f3 * z1; + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(xn1, d1, yn1, d1, zn1, d1), + Vertex(x0, 1, y0, 1, z0, 1)); + } + else + { + // 0++0 + AddEdge( + Vertex(x0, 1, y0, 1, z0, 1), + Vertex(x3, 1, y3, 1, z3, 1)); + } + } + else if (f2 < 0) + { + d0 = f2 - f1; + xn0 = f2 * x1 - f1 * x2; + yn0 = f2 * y1 - f1 * y2; + zn0 = f2 * z1 - f1 * z2; + + if (f3 > 0) + { + // 0+-+ + d1 = f2 - f3; + xn1 = f2 * x3 - f3 * x2; + yn1 = f2 * y3 - f3 * y2; + zn1 = f2 * z3 - f3 * z2; + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(xn1, d1, yn1, d1, zn1, d1), + Vertex(x0, 1, y0, 1, z0, 1)); + } + else if (f3 < 0) + { + // 0+-- + d1 = f1 - f3; + xn1 = f1 * x3 - f3 * x1; + yn1 = f1 * y3 - f3 * y1; + zn1 = f1 * z3 - f3 * z1; + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(xn1, d1, yn1, d1, zn1, d1), + Vertex(x0, 1, y0, 1, z0, 1)); + } + else + { + // 0+-0 + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(x0, 1, y0, 1, z0, 1), + Vertex(x3, 1, y3, 1, z3, 1)); + } + } + else + { + if (f3 > 0) + { + // 0+0+ + AddEdge( + Vertex(x0, 1, y0, 1, z0, 1), + Vertex(x2, 1, y2, 1, z2, 1)); + } + else if (f3 < 0) + { + // 0+0- + d0 = f1 - f3; + xn0 = f1 * x3 - f3 * x1; + yn0 = f1 * y3 - f3 * y1; + zn0 = f1 * z3 - f3 * z1; + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(x0, 1, y0, 1, z0, 1), + Vertex(x2, 1, y2, 1, z2, 1)); + } + else + { + // 0+00 + AddTriangle( + Vertex(x0, 1, y0, 1, z0, 1), + Vertex(x2, 1, y2, 1, z2, 1), + Vertex(x3, 1, y3, 1, z3, 1)); + } + } + } + else if (f2 != 0) + { + // convert to case 00+* + if (f2 < 0) + { + f2 = -f2; + f3 = -f3; + } + + if (f3 > 0) + { + // 00++ + AddEdge( + Vertex(x0, 1, y0, 1, z0, 1), + Vertex(x1, 1, y1, 1, z1, 1)); + } + else if (f3 < 0) + { + // 00+- + d0 = f2 - f3; + xn0 = f2 * x3 - f3 * x2; + yn0 = f2 * y3 - f3 * y2; + zn0 = f2 * z3 - f3 * z2; + AddTriangle( + Vertex(xn0, d0, yn0, d0, zn0, d0), + Vertex(x0, 1, y0, 1, z0, 1), + Vertex(x1, 1, y1, 1, z1, 1)); + } + else + { + // 00+0 + AddTriangle( + Vertex(x0, 1, y0, 1, z0, 1), + Vertex(x1, 1, y1, 1, z1, 1), + Vertex(x3, 1, y3, 1, z3, 1)); + } + } + else if (f3 != 0) + { + // cases 000+ or 000- + AddTriangle( + Vertex(x0, 1, y0, 1, z0, 1), + Vertex(x1, 1, y1, 1, z1, 1), + Vertex(x2, 1, y2, 1, z2, 1)); + } + else + { + // case 0000 + AddTriangle( + Vertex(x0, 1, y0, 1, z0, 1), + Vertex(x1, 1, y1, 1, z1, 1), + Vertex(x2, 1, y2, 1, z2, 1)); + AddTriangle( + Vertex(x0, 1, y0, 1, z0, 1), + Vertex(x1, 1, y1, 1, z1, 1), + Vertex(x3, 1, y3, 1, z3, 1)); + AddTriangle( + Vertex(x0, 1, y0, 1, z0, 1), + Vertex(x2, 1, y2, 1, z2, 1), + Vertex(x3, 1, y3, 1, z3, 1)); + AddTriangle( + Vertex(x1, 1, y1, 1, z1, 1), + Vertex(x2, 1, y2, 1, z2, 1), + Vertex(x3, 1, y3, 1, z3, 1)); + } + } + + std::map mVMap; + std::set mESet; + std::set mTSet; + int mNextVertex; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SymmetricEigensolver.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SymmetricEigensolver.h new file mode 100644 index 0000000..5642808 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SymmetricEigensolver.h @@ -0,0 +1,802 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +// The SymmetricEigensolver class is an implementation of Algorithm 8.2.3 +// (Symmetric QR Algorithm) described in "Matrix Computations, 2nd edition" +// by G. H. Golub and C. F. Van Loan, The Johns Hopkins University Press, +// Baltimore MD, Fourth Printing 1993. Algorithm 8.2.1 (Householder +// Tridiagonalization) is used to reduce matrix A to tridiagonal T. +// Algorithm 8.2.2 (Implicit Symmetric QR Step with Wilkinson Shift) is +// used for the iterative reduction from tridiagonal to diagonal. If A is +// the original matrix, D is the diagonal matrix of eigenvalues, and Q is +// the orthogonal matrix of eigenvectors, then theoretically Q^T*A*Q = D. +// Numerically, we have errors E = Q^T*A*Q - D. Algorithm 8.2.3 mentions +// that one expects |E| is approximately u*|A|, where |M| denotes the +// Frobenius norm of M and where u is the unit roundoff for the +// floating-point arithmetic: 2^{-23} for 'float', which is FLT_EPSILON +// = 1.192092896e-7f, and 2^{-52} for'double', which is DBL_EPSILON +// = 2.2204460492503131e-16. +// +// The condition |a(i,i+1)| <= epsilon*(|a(i,i) + a(i+1,i+1)|) used to +// determine when the reduction decouples to smaller problems is implemented +// as: sum = |a(i,i)| + |a(i+1,i+1)|; sum + |a(i,i+1)| == sum. The idea is +// that the superdiagonal term is small relative to its diagonal neighbors, +// and so it is effectively zero. The unit tests have shown that this +// interpretation of decoupling is effective. +// +// The authors suggest that once you have the tridiagonal matrix, a practical +// implementation will store the diagonal and superdiagonal entries in linear +// arrays, ignoring the theoretically zero values not in the 3-band. This is +// good for cache coherence. The authors also suggest storing the Householder +// vectors in the lower-triangular portion of the matrix to save memory. The +// implementation uses both suggestions. +// +// For matrices with randomly generated values in [0,1], the unit tests +// produce the following information for N-by-N matrices. +// +// N |A| |E| |E|/|A| iterations +// ------------------------------------------- +// 2 1.2332 5.5511e-17 4.5011e-17 1 +// 3 2.0024 1.1818e-15 5.9020e-16 5 +// 4 2.8708 9.9287e-16 3.4585e-16 7 +// 5 2.9040 2.5958e-15 8.9388e-16 9 +// 6 4.0427 2.2434e-15 5.5493e-16 12 +// 7 5.0276 3.2456e-15 6.4555e-16 15 +// 8 5.4468 6.5626e-15 1.2048e-15 15 +// 9 6.1510 4.0317e-15 6.5545e-16 17 +// 10 6.7523 4.9334e-15 7.3062e-16 21 +// 11 7.1322 7.1347e-15 1.0003e-15 22 +// 12 7.8324 5.6106e-15 7.1633e-16 24 +// 13 8.1073 5.1366e-15 6.3357e-16 25 +// 14 8.6257 8.3496e-15 9.6798e-16 29 +// 15 9.2603 6.9526e-15 7.5080e-16 31 +// 16 9.9853 6.5807e-15 6.5904e-16 32 +// 17 10.5388 8.0931e-15 7.6793e-16 35 +// 18 11.2377 1.1149e-14 9.9218e-16 38 +// 19 11.7105 1.0711e-14 9.1470e-16 42 +// 20 12.2227 1.7723e-14 1.4500e-15 42 +// 21 12.7411 1.2515e-14 9.8231e-16 47 +// 22 13.4462 1.2645e-14 9.4046e-16 50 +// 23 13.9541 1.2899e-14 9.2444e-16 47 +// 24 14.3298 1.6337e-14 1.1400e-15 53 +// 25 14.8050 1.4760e-14 9.9701e-16 54 +// 26 15.3914 1.7076e-14 1.1094e-15 57 +// 27 15.8430 1.9714e-14 1.2443e-15 60 +// 28 16.4771 1.7148e-14 1.0407e-15 60 +// 29 16.9909 1.7309e-14 1.0187e-15 60 +// 30 17.4456 2.1453e-14 1.2297e-15 64 +// 31 17.9909 2.2069e-14 1.2267e-15 68 +// +// The eigenvalues and |E|/|A| values were compared to those generated by +// Mathematica Version 9.0; Wolfram Research, Inc., Champaign IL, 2012. +// The results were all comparable with eigenvalues agreeing to a large +// number of decimal places. +// +// Timing on an Intel (R) Core (TM) i7-3930K CPU @ 3.20 GHZ (in seconds): +// +// N |E|/|A| iters tridiag QR evecs evec[N] comperr +// -------------------------------------------------------------- +// 512 4.4149e-15 1017 0.180 0.005 1.151 0.848 2.166 +// 1024 6.1691e-15 1990 1.775 0.031 11.894 12.759 21.179 +// 2048 8.5108e-15 3849 16.592 0.107 119.744 116.56 212.227 +// +// where iters is the number of QR steps taken, tridiag is the computation +// of the Householder reflection vectors, evecs is the composition of +// Householder reflections and Givens rotations to obtain the matrix of +// eigenvectors, evec[N] is N calls to get the eigenvectors separately, and +// comperr is the computation E = Q^T*A*Q - D. The construction of the full +// eigenvector matrix is, of course, quite expensive. If you need only a +// small number of eigenvectors, use function GetEigenvector(int,Real*). + +namespace WwiseGTE +{ + template + class SymmetricEigensolver + { + public: + // The solver processes NxN symmetric matrices, where N > 1 ('size' + // is N) and the matrix is stored in row-major order. The maximum + // number of iterations ('maxIterations') must be specified for the + // reduction of a tridiagonal matrix to a diagonal matrix. The goal + // is to compute NxN orthogonal Q and NxN diagonal D for which + // Q^T*A*Q = D. + SymmetricEigensolver(int size, unsigned int maxIterations) + : + mSize(0), + mMaxIterations(0), + mEigenvectorMatrixType(-1) + { + if (size > 1 && maxIterations > 0) + { + mSize = size; + mMaxIterations = maxIterations; + mMatrix.resize(size * size); + mDiagonal.resize(size); + mSuperdiagonal.resize(size - 1); + mGivens.reserve(maxIterations * (size - 1)); + mPermutation.resize(size); + mVisited.resize(size); + mPVector.resize(size); + mVVector.resize(size); + mWVector.resize(size); + } + } + + // A copy of the NxN symmetric input is made internally. The order of + // the eigenvalues is specified by sortType: -1 (decreasing), 0 (no + // sorting), or +1 (increasing). When sorted, the eigenvectors are + // ordered accordingly. The return value is the number of iterations + // consumed when convergence occurred, 0xFFFFFFFF when convergence did + // not occur, or 0 when N <= 1 was passed to the constructor. + unsigned int Solve(Real const* input, int sortType) + { + mEigenvectorMatrixType = -1; + + if (mSize > 0) + { + std::copy(input, input + mSize * mSize, mMatrix.begin()); + Tridiagonalize(); + + mGivens.clear(); + for (unsigned int j = 0; j < mMaxIterations; ++j) + { + int imin = -1, imax = -1; + for (int i = mSize - 2; i >= 0; --i) + { + // When a01 is much smaller than its diagonal + // neighbors, it is effectively zero. + Real a00 = mDiagonal[i]; + Real a01 = mSuperdiagonal[i]; + Real a11 = mDiagonal[i + 1]; + Real sum = std::fabs(a00) + std::fabs(a11); + if (sum + std::fabs(a01) != sum) + { + if (imax == -1) + { + imax = i; + } + imin = i; + } + else + { + // The superdiagonal term is effectively zero + // compared to the neighboring diagonal terms. + if (imin >= 0) + { + break; + } + } + } + + if (imax == -1) + { + // The algorithm has converged. + ComputePermutation(sortType); + return j; + } + + // Process the lower-right-most unreduced tridiagonal + // block. + DoQRImplicitShift(imin, imax); + } + return 0xFFFFFFFF; + } + else + { + return 0; + } + } + + // Get the eigenvalues of the matrix passed to Solve(...). The input + // 'eigenvalues' must have N elements. + void GetEigenvalues(Real* eigenvalues) const + { + if (eigenvalues && mSize > 0) + { + if (mPermutation[0] >= 0) + { + // Sorting was requested. + for (int i = 0; i < mSize; ++i) + { + int p = mPermutation[i]; + eigenvalues[i] = mDiagonal[p]; + } + } + else + { + // Sorting was not requested. + size_t numBytes = mSize * sizeof(Real); + std::memcpy(eigenvalues, &mDiagonal[0], numBytes); + } + } + } + + // Accumulate the Householder reflections and Givens rotations to + // produce the orthogonal matrix Q for which Q^T*A*Q = D. The input + // 'eigenvectors' must have N*N elements. The array is filled in as + // if the eigenvector matrix is stored in row-major order. The i-th + // eigenvector is + // (eigenvectors[i+size*0], ... eigenvectors[i+size*(size - 1)]) + // which is the i-th column of 'eigenvectors' as an NxN matrix stored + // in row-major order. + void GetEigenvectors(Real* eigenvectors) const + { + mEigenvectorMatrixType = -1; + + if (eigenvectors && mSize > 0) + { + // Start with the identity matrix. + std::fill(eigenvectors, eigenvectors + mSize * mSize, (Real)0); + for (int d = 0; d < mSize; ++d) + { + eigenvectors[d + mSize * d] = (Real)1; + } + + // Multiply the Householder reflections using backward + // accumulation. + int r, c; + for (int i = mSize - 3, rmin = i + 1; i >= 0; --i, --rmin) + { + // Copy the v vector and 2/Dot(v,v) from the matrix. + Real const* column = &mMatrix[i]; + Real twoinvvdv = column[mSize * (i + 1)]; + for (r = 0; r < i + 1; ++r) + { + mVVector[r] = (Real)0; + } + mVVector[r] = (Real)1; + for (++r; r < mSize; ++r) + { + mVVector[r] = column[mSize * r]; + } + + // Compute the w vector. + for (r = 0; r < mSize; ++r) + { + mWVector[r] = (Real)0; + for (c = rmin; c < mSize; ++c) + { + mWVector[r] += mVVector[c] * eigenvectors[r + mSize * c]; + } + mWVector[r] *= twoinvvdv; + } + + // Update the matrix, Q <- Q - v*w^T. + for (r = rmin; r < mSize; ++r) + { + for (c = 0; c < mSize; ++c) + { + eigenvectors[c + mSize * r] -= mVVector[r] * mWVector[c]; + } + } + } + + // Multiply the Givens rotations. + for (auto const& givens : mGivens) + { + for (r = 0; r < mSize; ++r) + { + int j = givens.index + mSize * r; + Real& q0 = eigenvectors[j]; + Real& q1 = eigenvectors[j + 1]; + Real prd0 = givens.cs * q0 - givens.sn * q1; + Real prd1 = givens.sn * q0 + givens.cs * q1; + q0 = prd0; + q1 = prd1; + } + } + + // The number of Householder reflections is H = mSize - 2. If + // H is even, the product of Householder reflections is a + // rotation; otherwise, H is odd and the product is a + // reflection. The number of Givens rotations does not + // influence the type of the product of Householder + // reflections. + mEigenvectorMatrixType = 1 - (mSize & 1); + + if (mPermutation[0] >= 0) + { + // Sorting was requested. + std::fill(mVisited.begin(), mVisited.end(), 0); + for (int i = 0; i < mSize; ++i) + { + if (mVisited[i] == 0 && mPermutation[i] != i) + { + // The item starts a cycle with 2 or more + // elements. + int start = i, current = i, j, next; + for (j = 0; j < mSize; ++j) + { + mPVector[j] = eigenvectors[i + mSize * j]; + } + while ((next = mPermutation[current]) != start) + { + mEigenvectorMatrixType = 1 - mEigenvectorMatrixType; + mVisited[current] = 1; + for (j = 0; j < mSize; ++j) + { + eigenvectors[current + mSize * j] = + eigenvectors[next + mSize * j]; + } + current = next; + } + mVisited[current] = 1; + for (j = 0; j < mSize; ++j) + { + eigenvectors[current + mSize * j] = mPVector[j]; + } + } + } + } + } + } + + // The eigenvector matrix is a rotation (return +1) or a reflection + // (return 0). If the input 'size' to the constructor is 0 or the + // input 'eigenvectors' to GetEigenvectors is null, the returned value + // is -1. + inline int GetEigenvectorMatrixType() const + { + return mEigenvectorMatrixType; + } + + // Compute a single eigenvector, which amounts to computing column c + // of matrix Q. The reflections and rotations are applied + // incrementally. This is useful when you want only a small number of + // the eigenvectors. + void GetEigenvector(int c, Real* eigenvector) const + { + if (0 <= c && c < mSize) + { + // y = H*x, then x and y are swapped for the next H + Real* x = eigenvector; + Real* y = &mPVector[0]; + + // Start with the Euclidean basis vector. + std::memset(x, 0, mSize * sizeof(Real)); + if (mPermutation[0] >= 0) + { + // Sorting was requested. + x[mPermutation[c]] = (Real)1; + } + else + { + x[c] = (Real)1; + } + + // Apply the Givens rotations. + for (auto const& givens : WwiseGTE::reverse(mGivens)) + { + Real& xr = x[givens.index]; + Real& xrp1 = x[givens.index + 1]; + Real tmp0 = givens.cs * xr + givens.sn * xrp1; + Real tmp1 = -givens.sn * xr + givens.cs * xrp1; + xr = tmp0; + xrp1 = tmp1; + } + + // Apply the Householder reflections. + for (int i = mSize - 3; i >= 0; --i) + { + // Get the Householder vector v. + Real const* column = &mMatrix[i]; + Real twoinvvdv = column[mSize * (i + 1)]; + int r; + for (r = 0; r < i + 1; ++r) + { + y[r] = x[r]; + } + + // Compute s = Dot(x,v) * 2/v^T*v. + Real s = x[r]; // r = i+1, v[i+1] = 1 + for (int j = r + 1; j < mSize; ++j) + { + s += x[j] * column[mSize * j]; + } + s *= twoinvvdv; + + y[r] = x[r] - s; // v[i+1] = 1 + + // Compute the remaining components of y. + for (++r; r < mSize; ++r) + { + y[r] = x[r] - s * column[mSize * r]; + } + + std::swap(x, y); + } + + // The final product is stored in x. + if (x != eigenvector) + { + size_t numBytes = mSize * sizeof(Real); + std::memcpy(eigenvector, x, numBytes); + } + } + } + + Real GetEigenvalue(int c) const + { + if (mSize > 0) + { + if (mPermutation[0] >= 0) + { + // Sorting was requested. + return mDiagonal[mPermutation[c]]; + } + else + { + // Sorting was not requested. + return mDiagonal[c]; + } + } + else + { + return std::numeric_limits::max(); + } + } + + private: + // Tridiagonalize using Householder reflections. On input, mMatrix is + // a copy of the input matrix. On output, the upper-triangular part + // of mMatrix including the diagonal stores the tridiagonalization. + // The lower-triangular part contains 2/Dot(v,v) that are used in + // computing eigenvectors and the part below the subdiagonal stores + // the essential parts of the Householder vectors v (the elements of + // v after the leading 1-valued component). + void Tridiagonalize() + { + int r, c; + for (int i = 0, ip1 = 1; i < mSize - 2; ++i, ++ip1) + { + // Compute the Householder vector. Read the initial vector + // from the row of the matrix. + Real length = (Real)0; + for (r = 0; r < ip1; ++r) + { + mVVector[r] = (Real)0; + } + for (r = ip1; r < mSize; ++r) + { + Real vr = mMatrix[r + mSize * i]; + mVVector[r] = vr; + length += vr * vr; + } + Real vdv = (Real)1; + length = std::sqrt(length); + if (length > (Real)0) + { + Real& v1 = mVVector[ip1]; + Real sgn = (v1 >= (Real)0 ? (Real)1 : (Real)-1); + Real invDenom = ((Real)1) / (v1 + sgn * length); + v1 = (Real)1; + for (r = ip1 + 1; r < mSize; ++r) + { + Real& vr = mVVector[r]; + vr *= invDenom; + vdv += vr * vr; + } + } + + // Compute the rank-1 offsets v*w^T and w*v^T. + Real invvdv = (Real)1 / vdv; + Real twoinvvdv = invvdv * (Real)2; + Real pdvtvdv = (Real)0; + for (r = i; r < mSize; ++r) + { + mPVector[r] = (Real)0; + for (c = i; c < r; ++c) + { + mPVector[r] += mMatrix[r + mSize * c] * mVVector[c]; + } + for (/**/; c < mSize; ++c) + { + mPVector[r] += mMatrix[c + mSize * r] * mVVector[c]; + } + mPVector[r] *= twoinvvdv; + pdvtvdv += mPVector[r] * mVVector[r]; + } + + pdvtvdv *= invvdv; + for (r = i; r < mSize; ++r) + { + mWVector[r] = mPVector[r] - pdvtvdv * mVVector[r]; + } + + // Update the input matrix. + for (r = i; r < mSize; ++r) + { + Real vr = mVVector[r]; + Real wr = mWVector[r]; + Real offset = vr * wr * (Real)2; + mMatrix[r + mSize * r] -= offset; + for (c = r + 1; c < mSize; ++c) + { + offset = vr * mWVector[c] + wr * mVVector[c]; + mMatrix[c + mSize * r] -= offset; + } + } + + // Copy the vector to column i of the matrix. The 0-valued + // components at indices 0 through i are not stored. The + // 1-valued component at index i+1 is also not stored; + // instead, the quantity 2/Dot(v,v) is stored for use in + // eigenvector construction. That construction must take + // into account the implied components that are not stored. + mMatrix[i + mSize * ip1] = twoinvvdv; + for (r = ip1 + 1; r < mSize; ++r) + { + mMatrix[i + mSize * r] = mVVector[r]; + } + } + + // Copy the diagonal and subdiagonal entries for cache coherence + // in the QR iterations. + int k, ksup = mSize - 1, index = 0, delta = mSize + 1; + for (k = 0; k < ksup; ++k, index += delta) + { + mDiagonal[k] = mMatrix[index]; + mSuperdiagonal[k] = mMatrix[index + 1]; + } + mDiagonal[k] = mMatrix[index]; + } + + // A helper for generating Givens rotation sine and cosine robustly. + void GetSinCos(Real x, Real y, Real& cs, Real& sn) + { + // Solves sn*x + cs*y = 0 robustly. + Real tau; + if (y != (Real)0) + { + if (std::fabs(y) > std::fabs(x)) + { + tau = -x / y; + sn = (Real)1 / std::sqrt((Real)1 + tau * tau); + cs = sn * tau; + } + else + { + tau = -y / x; + cs = (Real)1 / std::sqrt((Real)1 + tau * tau); + sn = cs * tau; + } + } + else + { + cs = (Real)1; + sn = (Real)0; + } + } + + // The QR step with implicit shift. Generally, the initial T is + // unreduced tridiagonal (all subdiagonal entries are nonzero). If a + // QR step causes a superdiagonal entry to become zero, the matrix + // decouples into a block diagonal matrix with two tridiagonal blocks. + // These blocks can be reduced independently of each other, which + // allows for parallelization of the algorithm. The inputs imin and + // imax identify the subblock of T to be processed. That block has + // upper-left element T(imin,imin) and lower-right element + // T(imax,imax). + void DoQRImplicitShift(int imin, int imax) + { + // The implicit shift. Compute the eigenvalue u of the + // lower-right 2x2 block that is closer to a11. + Real a00 = mDiagonal[imax]; + Real a01 = mSuperdiagonal[imax]; + Real a11 = mDiagonal[imax + 1]; + Real dif = (a00 - a11) * (Real)0.5; + Real sgn = (dif >= (Real)0 ? (Real)1 : (Real)-1); + Real a01sqr = a01 * a01; + Real u = a11 - a01sqr / (dif + sgn * std::sqrt(dif * dif + a01sqr)); + Real x = mDiagonal[imin] - u; + Real y = mSuperdiagonal[imin]; + + Real a12, a22, a23, tmp11, tmp12, tmp21, tmp22, cs, sn; + Real a02 = (Real)0; + int i0 = imin - 1, i1 = imin, i2 = imin + 1; + for (/**/; i1 <= imax; ++i0, ++i1, ++i2) + { + // Compute the Givens rotation and save it for use in + // computing the eigenvectors. + GetSinCos(x, y, cs, sn); + mGivens.push_back(GivensRotation(i1, cs, sn)); + + // Update the tridiagonal matrix. This amounts to updating a + // 4x4 subblock, + // b00 b01 b02 b03 + // b01 b11 b12 b13 + // b02 b12 b22 b23 + // b03 b13 b23 b33 + // The four corners (b00, b03, b33) do not change values. The + // The interior block {{b11,b12},{b12,b22}} is updated on each + // pass. For the first pass, the b0c values are out of range, + // so only the values (b13, b23) change. For the last pass, + // the br3 values are out of range, so only the values + // (b01, b02) change. For passes between first and last, the + // values (b01, b02, b13, b23) change. + if (i1 > imin) + { + mSuperdiagonal[i0] = cs * mSuperdiagonal[i0] - sn * a02; + } + + a11 = mDiagonal[i1]; + a12 = mSuperdiagonal[i1]; + a22 = mDiagonal[i2]; + tmp11 = cs * a11 - sn * a12; + tmp12 = cs * a12 - sn * a22; + tmp21 = sn * a11 + cs * a12; + tmp22 = sn * a12 + cs * a22; + mDiagonal[i1] = cs * tmp11 - sn * tmp12; + mSuperdiagonal[i1] = sn * tmp11 + cs * tmp12; + mDiagonal[i2] = sn * tmp21 + cs * tmp22; + + if (i1 < imax) + { + a23 = mSuperdiagonal[i2]; + a02 = -sn * a23; + mSuperdiagonal[i2] = cs * a23; + + // Update the parameters for the next Givens rotation. + x = mSuperdiagonal[i1]; + y = a02; + } + } + } + + // Sort the eigenvalues and compute the corresponding permutation of + // the indices of the array storing the eigenvalues. The permutation + // is used for reordering the eigenvalues and eigenvectors in the + // calls to GetEigenvalues(...) and GetEigenvectors(...). + void ComputePermutation(int sortType) + { + // The number of Householder reflections is H = mSize - 2. If H + // is even, the product of Householder reflections is a rotation; + // otherwise, H is odd and the product is a reflection. The + // number of Givens rotations does not influence the type of the + // product of Householder reflections. + mEigenvectorMatrixType = 1 - (mSize & 1); + + if (sortType == 0) + { + // Set a flag for GetEigenvalues() and GetEigenvectors() to + // know that sorted output was not requested. + mPermutation[0] = -1; + return; + } + + // Compute the permutation induced by sorting. Initially, we + // start with the identity permutation I = (0,1,...,N-1). + struct SortItem + { + Real eigenvalue; + int index; + }; + + std::vector items(mSize); + int i; + for (i = 0; i < mSize; ++i) + { + items[i].eigenvalue = mDiagonal[i]; + items[i].index = i; + } + + if (sortType > 0) + { + std::sort(items.begin(), items.end(), + [](SortItem const& item0, SortItem const& item1) + { + return item0.eigenvalue < item1.eigenvalue; + } + ); + } + else + { + std::sort(items.begin(), items.end(), + [](SortItem const& item0, SortItem const& item1) + { + return item0.eigenvalue > item1.eigenvalue; + } + ); + } + + i = 0; + for (auto const& item : items) + { + mPermutation[i++] = item.index; + } + + // GetEigenvectors() has nontrivial code for computing the + // orthogonal Q from the reflections and rotations. To avoid + // complicating the code further when sorting is requested, Q is + // computed as in the unsorted case. We then need to swap columns + // of Q to be consistent with the sorting of the eigenvalues. To + // minimize copying due to column swaps, we use permutation P. + // The minimum number of transpositions to obtain P from I is N + // minus the number of cycles of P. Each cycle is reordered with + // a minimum number of transpositions; that is, the eigenitems are + // cyclically swapped, leading to a minimum amount of copying. + // For/ example, if there is a cycle i0 -> i1 -> i2 -> i3, then + // the copying is + // save = eigenitem[i0]; + // eigenitem[i1] = eigenitem[i2]; + // eigenitem[i2] = eigenitem[i3]; + // eigenitem[i3] = save; + } + + // The number N of rows and columns of the matrices to be processed. + int mSize; + + // The maximum number of iterations for reducing the tridiagonal + // matrix to a diagonal matrix. + unsigned int mMaxIterations; + + // The internal copy of a matrix passed to the solver. See the + // comments about function Tridiagonalize() about what is stored in + // the matrix. + std::vector mMatrix; // NxN elements + + // After the initial tridiagonalization by Householder reflections, we + // no longer need the full mMatrix. Copy the diagonal and + // superdiagonal entries to linear arrays in order to be cache + // friendly. + std::vector mDiagonal; // N elements + std::vector mSuperdiagonal; // N-1 elements + + // The Givens rotations used to reduce the initial tridiagonal matrix + // to a diagonal matrix. A rotation is the identity with the + // following replacement entries: R(index,index) = cs, + // R(index,index+1) = sn, R(index+1,index) = -sn and + // R(index+1,index+1) = cs. If N is the matrix size and K is the + // maximum number of iterations, the maximum number of Givens + // rotations is K*(N-1). The maximum amount of memory is allocated + // to store these. + struct GivensRotation + { + // No default initialization for fast creation of std::vector + // of objects of this type. + GivensRotation() = default; + + GivensRotation(int inIndex, Real inCs, Real inSn) + : + index(inIndex), + cs(inCs), + sn(inSn) + { + } + + int index; + Real cs, sn; + }; + + std::vector mGivens; // K*(N-1) elements + + // When sorting is requested, the permutation associated with the sort + // is stored in mPermutation. When sorting is not requested, + // mPermutation[0] is set to -1. mVisited is used for finding cycles + // in the permutation. mEigenvectorMatrixType is +1 if GetEigenvectors + // returns a rotation matrix, 0 if GetEigenvectors returns a + // reflection matrix or -1 if an input to the constructor or to + // GetEigenvectors is invalid. + std::vector mPermutation; // N elements + mutable std::vector mVisited; // N elements + mutable int mEigenvectorMatrixType; + + // Temporary storage to compute Householder reflections and to + // support sorting of eigenvectors. + mutable std::vector mPVector; // N elements + mutable std::vector mVVector; // N elements + mutable std::vector mWVector; // N elements + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SymmetricEigensolver2x2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SymmetricEigensolver2x2.h new file mode 100644 index 0000000..8166d24 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SymmetricEigensolver2x2.h @@ -0,0 +1,83 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class SymmetricEigensolver2x2 + { + public: + // The input matrix must be symmetric, so only the unique elements + // must be specified: a00, a01, and a11. + // + // The order of the eigenvalues is specified by sortType: + // -1 (decreasing), 0 (no sorting) or +1 (increasing). When sorted, + // the eigenvectors are ordered accordingly, and {evec[0], evec[1]} + // is guaranteed to be a right-handed orthonormal set. + + void operator()(Real a00, Real a01, Real a11, int sortType, + std::array& eval, std::array, 2>& evec) const + { + // Normalize (c2,s2) robustly, avoiding floating-point overflow + // in the sqrt call. + Real const zero = (Real)0, one = (Real)1, half = (Real)0.5; + Real c2 = half * (a00 - a11), s2 = a01; + Real maxAbsComp = std::max(std::fabs(c2), std::fabs(s2)); + if (maxAbsComp > zero) + { + c2 /= maxAbsComp; // in [-1,1] + s2 /= maxAbsComp; // in [-1,1] + Real length = std::sqrt(c2 * c2 + s2 * s2); + c2 /= length; + s2 /= length; + if (c2 > zero) + { + c2 = -c2; + s2 = -s2; + } + } + else + { + c2 = -one; + s2 = zero; + } + + Real s = std::sqrt(half * (one - c2)); // >= 1/sqrt(2) + Real c = half * s2 / s; + + Real diagonal[2]; + Real csqr = c * c, ssqr = s * s, mid = s2 * a01; + diagonal[0] = csqr * a00 + mid + ssqr * a11; + diagonal[1] = csqr * a11 - mid + ssqr * a00; + + if (sortType == 0 || sortType * diagonal[0] <= sortType * diagonal[1]) + { + eval[0] = diagonal[0]; + eval[1] = diagonal[1]; + evec[0][0] = c; + evec[0][1] = s; + evec[1][0] = -s; + evec[1][1] = c; + } + else + { + eval[0] = diagonal[1]; + eval[1] = diagonal[0]; + evec[0][0] = s; + evec[0][1] = -c; + evec[1][0] = c; + evec[1][1] = s; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SymmetricEigensolver3x3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SymmetricEigensolver3x3.h new file mode 100644 index 0000000..6dc0254 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/SymmetricEigensolver3x3.h @@ -0,0 +1,734 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.16 + +#pragma once + +#include +#include +#include + +// The document +// https://www.geometrictools.com/Documentation/RobustEigenSymmetric3x3.pdf +// describes algorithms for solving the eigensystem associated with a 3x3 +// symmetric real-valued matrix. The iterative algorithm is implemented +// by class SymmmetricEigensolver3x3. The noniterative algorithm is +// implemented by class NISymmetricEigensolver3x3. The code does not use +// GTEngine objects. + +namespace WwiseGTE +{ + template + class SortEigenstuff + { + public: + void operator()(int sortType, bool isRotation, + std::array& eval, std::array, 3>& evec) + { + if (sortType != 0) + { + // Sort the eigenvalues to eval[0] <= eval[1] <= eval[2]. + std::array index; + if (eval[0] < eval[1]) + { + if (eval[2] < eval[0]) + { + // even permutation + index[0] = 2; + index[1] = 0; + index[2] = 1; + } + else if (eval[2] < eval[1]) + { + // odd permutation + index[0] = 0; + index[1] = 2; + index[2] = 1; + isRotation = !isRotation; + } + else + { + // even permutation + index[0] = 0; + index[1] = 1; + index[2] = 2; + } + } + else + { + if (eval[2] < eval[1]) + { + // odd permutation + index[0] = 2; + index[1] = 1; + index[2] = 0; + isRotation = !isRotation; + } + else if (eval[2] < eval[0]) + { + // even permutation + index[0] = 1; + index[1] = 2; + index[2] = 0; + } + else + { + // odd permutation + index[0] = 1; + index[1] = 0; + index[2] = 2; + isRotation = !isRotation; + } + } + + if (sortType == -1) + { + // The request is for eval[0] >= eval[1] >= eval[2]. This + // requires an odd permutation, (i0,i1,i2) -> (i2,i1,i0). + std::swap(index[0], index[2]); + isRotation = !isRotation; + } + + std::array unorderedEVal = eval; + std::array, 3> unorderedEVec = evec; + for (size_t j = 0; j < 3; ++j) + { + size_t i = index[j]; + eval[j] = unorderedEVal[i]; + evec[j] = unorderedEVec[i]; + } + } + + // Ensure the ordered eigenvectors form a right-handed basis. + if (!isRotation) + { + for (size_t j = 0; j < 3; ++j) + { + evec[2][j] = -evec[2][j]; + } + } + } + }; + + template + class SymmetricEigensolver3x3 + { + public: + // The input matrix must be symmetric, so only the unique elements + // must be specified: a00, a01, a02, a11, a12, and a22. + // + // If 'aggressive' is 'true', the iterations occur until a + // superdiagonal entry is exactly zero. If 'aggressive' is 'false', + // the iterations occur until a superdiagonal entry is effectively + // zero compared to the/ sum of magnitudes of its diagonal neighbors. + // Generally, the nonaggressive convergence is acceptable. + // + // The order of the eigenvalues is specified by sortType: + // -1 (decreasing), 0 (no sorting) or +1 (increasing). When sorted, + // the eigenvectors are ordered accordingly, and + // {evec[0], evec[1], evec[2]} is guaranteed to/ be a right-handed + // orthonormal set. The return value is the number of iterations + // used by the algorithm. + + int operator()(Real a00, Real a01, Real a02, Real a11, Real a12, Real a22, + bool aggressive, int sortType, std::array& eval, + std::array, 3>& evec) const + { + // Compute the Householder reflection H and B = H*A*H, where + // b02 = 0. + Real const zero = (Real)0, one = (Real)1, half = (Real)0.5; + bool isRotation = false; + Real c, s; + GetCosSin(a12, -a02, c, s); + Real Q[3][3] = { { c, s, zero }, { s, -c, zero }, { zero, zero, one } }; + Real term0 = c * a00 + s * a01; + Real term1 = c * a01 + s * a11; + Real b00 = c * term0 + s * term1; + Real b01 = s * term0 - c * term1; + term0 = s * a00 - c * a01; + term1 = s * a01 - c * a11; + Real b11 = s * term0 - c * term1; + Real b12 = s * a02 - c * a12; + Real b22 = a22; + + // Givens reflections, B' = G^T*B*G, preserve tridiagonal + // matrices. + int const maxIteration = 2 * (1 + std::numeric_limits::digits - + std::numeric_limits::min_exponent); + int iteration; + Real c2, s2; + + if (std::fabs(b12) <= std::fabs(b01)) + { + Real saveB00, saveB01, saveB11; + for (iteration = 0; iteration < maxIteration; ++iteration) + { + // Compute the Givens reflection. + GetCosSin(half * (b00 - b11), b01, c2, s2); + s = std::sqrt(half * (one - c2)); // >= 1/sqrt(2) + c = half * s2 / s; + + // Update Q by the Givens reflection. + Update0(Q, c, s); + isRotation = !isRotation; + + // Update B <- Q^T*B*Q, ensuring that b02 is zero and + // |b12| has strictly decreased. + saveB00 = b00; + saveB01 = b01; + saveB11 = b11; + term0 = c * saveB00 + s * saveB01; + term1 = c * saveB01 + s * saveB11; + b00 = c * term0 + s * term1; + b11 = b22; + term0 = c * saveB01 - s * saveB00; + term1 = c * saveB11 - s * saveB01; + b22 = c * term1 - s * term0; + b01 = s * b12; + b12 = c * b12; + + if (Converged(aggressive, b00, b11, b01)) + { + // Compute the Householder reflection. + GetCosSin(half * (b00 - b11), b01, c2, s2); + s = std::sqrt(half * (one - c2)); + c = half * s2 / s; // >= 1/sqrt(2) + + // Update Q by the Householder reflection. + Update2(Q, c, s); + isRotation = !isRotation; + + // Update D = Q^T*B*Q. + saveB00 = b00; + saveB01 = b01; + saveB11 = b11; + term0 = c * saveB00 + s * saveB01; + term1 = c * saveB01 + s * saveB11; + b00 = c * term0 + s * term1; + term0 = s * saveB00 - c * saveB01; + term1 = s * saveB01 - c * saveB11; + b11 = s * term0 - c * term1; + break; + } + } + } + else + { + Real saveB11, saveB12, saveB22; + for (iteration = 0; iteration < maxIteration; ++iteration) + { + // Compute the Givens reflection. + GetCosSin(half * (b22 - b11), b12, c2, s2); + s = std::sqrt(half * (one - c2)); // >= 1/sqrt(2) + c = half * s2 / s; + + // Update Q by the Givens reflection. + Update1(Q, c, s); + isRotation = !isRotation; + + // Update B <- Q^T*B*Q, ensuring that b02 is zero and + // |b12| has strictly decreased. MODIFY... + saveB11 = b11; + saveB12 = b12; + saveB22 = b22; + term0 = c * saveB22 + s * saveB12; + term1 = c * saveB12 + s * saveB11; + b22 = c * term0 + s * term1; + b11 = b00; + term0 = c * saveB12 - s * saveB22; + term1 = c * saveB11 - s * saveB12; + b00 = c * term1 - s * term0; + b12 = s * b01; + b01 = c * b01; + + if (Converged(aggressive, b11, b22, b12)) + { + // Compute the Householder reflection. + GetCosSin(half * (b11 - b22), b12, c2, s2); + s = std::sqrt(half * (one - c2)); + c = half * s2 / s; // >= 1/sqrt(2) + + // Update Q by the Householder reflection. + Update3(Q, c, s); + isRotation = !isRotation; + + // Update D = Q^T*B*Q. + saveB11 = b11; + saveB12 = b12; + saveB22 = b22; + term0 = c * saveB11 + s * saveB12; + term1 = c * saveB12 + s * saveB22; + b11 = c * term0 + s * term1; + term0 = s * saveB11 - c * saveB12; + term1 = s * saveB12 - c * saveB22; + b22 = s * term0 - c * term1; + break; + } + } + } + + eval = { b00, b11, b22 }; + for (size_t row = 0; row < 3; ++row) + { + for (size_t col = 0; col < 3; ++col) + { + evec[row][col] = Q[col][row]; + } + } + + SortEigenstuff()(sortType, isRotation, eval, evec); + return iteration; + } + + private: + // Update Q = Q*G in-place using G = {{c,0,-s},{s,0,c},{0,0,1}}. + void Update0(Real Q[3][3], Real c, Real s) const + { + for (int r = 0; r < 3; ++r) + { + Real tmp0 = c * Q[r][0] + s * Q[r][1]; + Real tmp1 = Q[r][2]; + Real tmp2 = c * Q[r][1] - s * Q[r][0]; + Q[r][0] = tmp0; + Q[r][1] = tmp1; + Q[r][2] = tmp2; + } + } + + // Update Q = Q*G in-place using G = {{0,1,0},{c,0,s},{-s,0,c}}. + void Update1(Real Q[3][3], Real c, Real s) const + { + for (int r = 0; r < 3; ++r) + { + Real tmp0 = c * Q[r][1] - s * Q[r][2]; + Real tmp1 = Q[r][0]; + Real tmp2 = c * Q[r][2] + s * Q[r][1]; + Q[r][0] = tmp0; + Q[r][1] = tmp1; + Q[r][2] = tmp2; + } + } + + // Update Q = Q*H in-place using H = {{c,s,0},{s,-c,0},{0,0,1}}. + void Update2(Real Q[3][3], Real c, Real s) const + { + for (int r = 0; r < 3; ++r) + { + Real tmp0 = c * Q[r][0] + s * Q[r][1]; + Real tmp1 = s * Q[r][0] - c * Q[r][1]; + Q[r][0] = tmp0; + Q[r][1] = tmp1; + } + } + + // Update Q = Q*H in-place using H = {{1,0,0},{0,c,s},{0,s,-c}}. + void Update3(Real Q[3][3], Real c, Real s) const + { + for (int r = 0; r < 3; ++r) + { + Real tmp0 = c * Q[r][1] + s * Q[r][2]; + Real tmp1 = s * Q[r][1] - c * Q[r][2]; + Q[r][1] = tmp0; + Q[r][2] = tmp1; + } + } + + // Normalize (u,v) robustly, avoiding floating-point overflow in the + // sqrt call. The normalized pair is (cs,sn) with cs <= 0. If + // (u,v) = (0,0), the function returns (cs,sn) = (-1,0). When used + // to generate a Householder reflection, it does not matter whether + // (cs,sn) or (-cs,-sn) is used. When generating a Givens reflection, + // cs = cos(2*theta) and sn = sin(2*theta). Having a negative cosine + // for the double-angle term ensures that the single-angle terms + // c = cos(theta) and s = sin(theta) satisfy |c| <= |s|. + void GetCosSin(Real u, Real v, Real& cs, Real& sn) const + { + Real maxAbsComp = std::max(std::fabs(u), std::fabs(v)); + if (maxAbsComp > (Real)0) + { + u /= maxAbsComp; // in [-1,1] + v /= maxAbsComp; // in [-1,1] + Real length = std::sqrt(u * u + v * v); + cs = u / length; + sn = v / length; + if (cs > (Real)0) + { + cs = -cs; + sn = -sn; + } + } + else + { + cs = (Real)-1; + sn = (Real)0; + } + } + + // The convergence test. When 'aggressive' is 'true', the + // superdiagonal test is "bSuper == 0". When 'aggressive' is 'false', + // the superdiagonal test is + // |bDiag0| + |bDiag1| + |bSuper| == |bDiag0| + |bDiag1| + // which means bSuper is effectively zero compared to the sizes of the + // diagonal entries. + bool Converged(bool aggressive, Real bDiag0, Real bDiag1, Real bSuper) const + { + if (aggressive) + { + return bSuper == (Real)0; + } + else + { + Real sum = std::fabs(bDiag0) + std::fabs(bDiag1); + return sum + std::fabs(bSuper) == sum; + } + } + }; + + + template + class NISymmetricEigensolver3x3 + { + public: + // The input matrix must be symmetric, so only the unique elements + // must be specified: a00, a01, a02, a11, a12, and a22. The + // eigenvalues are sorted in ascending order: eval0 <= eval1 <= eval2. + + void operator()(Real a00, Real a01, Real a02, Real a11, Real a12, Real a22, + int sortType, std::array& eval, std::array, 3>& evec) const + { + // Precondition the matrix by factoring out the maximum absolute + // value of the components. This guards against floating-point + // overflow when computing the eigenvalues. + Real max0 = std::max(std::fabs(a00), std::fabs(a01)); + Real max1 = std::max(std::fabs(a02), std::fabs(a11)); + Real max2 = std::max(std::fabs(a12), std::fabs(a22)); + Real maxAbsElement = std::max(std::max(max0, max1), max2); + if (maxAbsElement == (Real)0) + { + // A is the zero matrix. + eval[0] = (Real)0; + eval[1] = (Real)0; + eval[2] = (Real)0; + evec[0] = { (Real)1, (Real)0, (Real)0 }; + evec[1] = { (Real)0, (Real)1, (Real)0 }; + evec[2] = { (Real)0, (Real)0, (Real)1 }; + return; + } + + Real invMaxAbsElement = (Real)1 / maxAbsElement; + a00 *= invMaxAbsElement; + a01 *= invMaxAbsElement; + a02 *= invMaxAbsElement; + a11 *= invMaxAbsElement; + a12 *= invMaxAbsElement; + a22 *= invMaxAbsElement; + + Real norm = a01 * a01 + a02 * a02 + a12 * a12; + if (norm > (Real)0) + { + // Compute the eigenvalues of A. + + // In the PDF mentioned previously, B = (A - q*I)/p, where + // q = tr(A)/3 with tr(A) the trace of A (sum of the diagonal + // entries of A) and where p = sqrt(tr((A - q*I)^2)/6). + Real q = (a00 + a11 + a22) / (Real)3; + + // The matrix A - q*I is represented by the following, where + // b00, b11 and b22 are computed after these comments, + // +- -+ + // | b00 a01 a02 | + // | a01 b11 a12 | + // | a02 a12 b22 | + // +- -+ + Real b00 = a00 - q; + Real b11 = a11 - q; + Real b22 = a22 - q; + + // The is the variable p mentioned in the PDF. + Real p = std::sqrt((b00 * b00 + b11 * b11 + b22 * b22 + norm * (Real)2) / (Real)6); + + // We need det(B) = det((A - q*I)/p) = det(A - q*I)/p^3. The + // value det(A - q*I) is computed using a cofactor expansion + // by the first row of A - q*I. The cofactors are c00, c01 + // and c02 and the determinant is b00*c00 - a01*c01 + a02*c02. + // The det(B) is then computed finally by the division + // with p^3. + Real c00 = b11 * b22 - a12 * a12; + Real c01 = a01 * b22 - a12 * a02; + Real c02 = a01 * a12 - b11 * a02; + Real det = (b00 * c00 - a01 * c01 + a02 * c02) / (p * p * p); + + // The halfDet value is cos(3*theta) mentioned in the PDF. The + // acos(z) function requires |z| <= 1, but will fail silently + // and return NaN if the input is larger than 1 in magnitude. + // To avoid this problem due to rounding errors, the halfDet + // value is clamped to [-1,1]. + Real halfDet = det * (Real)0.5; + halfDet = std::min(std::max(halfDet, (Real)-1), (Real)1); + + // The eigenvalues of B are ordered as + // beta0 <= beta1 <= beta2. The number of digits in + // twoThirdsPi is chosen so that, whether float or double, + // the floating-point number is the closest to theoretical + // 2*pi/3. + Real angle = std::acos(halfDet) / (Real)3; + Real const twoThirdsPi = (Real)2.09439510239319549; + Real beta2 = std::cos(angle) * (Real)2; + Real beta0 = std::cos(angle + twoThirdsPi) * (Real)2; + Real beta1 = -(beta0 + beta2); + + // The eigenvalues of A are ordered as + // alpha0 <= alpha1 <= alpha2. + eval[0] = q + p * beta0; + eval[1] = q + p * beta1; + eval[2] = q + p * beta2; + + // Compute the eigenvectors so that the set + // {evec[0], evec[1], evec[2]} is right handed and + // orthonormal. + if (halfDet >= (Real)0) + { + ComputeEigenvector0(a00, a01, a02, a11, a12, a22, eval[2], evec[2]); + ComputeEigenvector1(a00, a01, a02, a11, a12, a22, evec[2], eval[1], evec[1]); + evec[0] = Cross(evec[1], evec[2]); + } + else + { + ComputeEigenvector0(a00, a01, a02, a11, a12, a22, eval[0], evec[0]); + ComputeEigenvector1(a00, a01, a02, a11, a12, a22, evec[0], eval[1], evec[1]); + evec[2] = Cross(evec[0], evec[1]); + } + } + else + { + // The matrix is diagonal. + eval[0] = a00; + eval[1] = a11; + eval[2] = a22; + evec[0] = { (Real)1, (Real)0, (Real)0 }; + evec[1] = { (Real)0, (Real)1, (Real)0 }; + evec[2] = { (Real)0, (Real)0, (Real)1 }; + } + + // The preconditioning scaled the matrix A, which scales the + // eigenvalues. Revert the scaling. + eval[0] *= maxAbsElement; + eval[1] *= maxAbsElement; + eval[2] *= maxAbsElement; + + SortEigenstuff()(sortType, true, eval, evec); + } + + private: + static std::array Multiply(Real s, std::array const& U) + { + std::array product = { s * U[0], s * U[1], s * U[2] }; + return product; + } + + static std::array Subtract(std::array const& U, std::array const& V) + { + std::array difference = { U[0] - V[0], U[1] - V[1], U[2] - V[2] }; + return difference; + } + + static std::array Divide(std::array const& U, Real s) + { + Real invS = (Real)1 / s; + std::array division = { U[0] * invS, U[1] * invS, U[2] * invS }; + return division; + } + + static Real Dot(std::array const& U, std::array const& V) + { + Real dot = U[0] * V[0] + U[1] * V[1] + U[2] * V[2]; + return dot; + } + + static std::array Cross(std::array const& U, std::array const& V) + { + std::array cross = + { + U[1] * V[2] - U[2] * V[1], + U[2] * V[0] - U[0] * V[2], + U[0] * V[1] - U[1] * V[0] + }; + return cross; + } + + void ComputeOrthogonalComplement(std::array const& W, + std::array& U, std::array& V) const + { + // Robustly compute a right-handed orthonormal set { U, V, W }. + // The vector W is guaranteed to be unit-length, in which case + // there is no need to worry about a division by zero when + // computing invLength. + Real invLength; + if (std::fabs(W[0]) > std::fabs(W[1])) + { + // The component of maximum absolute value is either W[0] + // or W[2]. + invLength = (Real)1 / std::sqrt(W[0] * W[0] + W[2] * W[2]); + U = { -W[2] * invLength, (Real)0, +W[0] * invLength }; + } + else + { + // The component of maximum absolute value is either W[1] + // or W[2]. + invLength = (Real)1 / std::sqrt(W[1] * W[1] + W[2] * W[2]); + U = { (Real)0, +W[2] * invLength, -W[1] * invLength }; + } + V = Cross(W, U); + } + + void ComputeEigenvector0(Real a00, Real a01, Real a02, Real a11, Real a12, Real a22, + Real eval0, std::array& evec0) const + { + // Compute a unit-length eigenvector for eigenvalue[i0]. The + // matrix is rank 2, so two of the rows are linearly independent. + // For a robust computation of the eigenvector, select the two + // rows whose cross product has largest length of all pairs of + // rows. + std::array row0 = { a00 - eval0, a01, a02 }; + std::array row1 = { a01, a11 - eval0, a12 }; + std::array row2 = { a02, a12, a22 - eval0 }; + std::array r0xr1 = Cross(row0, row1); + std::array r0xr2 = Cross(row0, row2); + std::array r1xr2 = Cross(row1, row2); + Real d0 = Dot(r0xr1, r0xr1); + Real d1 = Dot(r0xr2, r0xr2); + Real d2 = Dot(r1xr2, r1xr2); + + Real dmax = d0; + int imax = 0; + if (d1 > dmax) + { + dmax = d1; + imax = 1; + } + if (d2 > dmax) + { + imax = 2; + } + + if (imax == 0) + { + evec0 = Divide(r0xr1, std::sqrt(d0)); + } + else if (imax == 1) + { + evec0 = Divide(r0xr2, std::sqrt(d1)); + } + else + { + evec0 = Divide(r1xr2, std::sqrt(d2)); + } + } + + void ComputeEigenvector1(Real a00, Real a01, Real a02, Real a11, Real a12, Real a22, + std::array const& evec0, Real eval1, std::array& evec1) const + { + // Robustly compute a right-handed orthonormal set + // { U, V, evec0 }. + std::array U, V; + ComputeOrthogonalComplement(evec0, U, V); + + // Let e be eval1 and let E be a corresponding eigenvector which + // is a solution to the linear system (A - e*I)*E = 0. The matrix + // (A - e*I) is 3x3, not invertible (so infinitely many + // solutions), and has rank 2 when eval1 and eval are different. + // It has rank 1 when eval1 and eval2 are equal. Numerically, it + // is difficult to compute robustly the rank of a matrix. Instead, + // the 3x3 linear system is reduced to a 2x2 system as follows. + // Define the 3x2 matrix J = [U V] whose columns are the U and V + // computed previously. Define the 2x1 vector X = J*E. The 2x2 + // system is 0 = M * X = (J^T * (A - e*I) * J) * X where J^T is + // the transpose of J and M = J^T * (A - e*I) * J is a 2x2 matrix. + // The system may be written as + // +- -++- -+ +- -+ + // | U^T*A*U - e U^T*A*V || x0 | = e * | x0 | + // | V^T*A*U V^T*A*V - e || x1 | | x1 | + // +- -++ -+ +- -+ + // where X has row entries x0 and x1. + + std::array AU = + { + a00 * U[0] + a01 * U[1] + a02 * U[2], + a01 * U[0] + a11 * U[1] + a12 * U[2], + a02 * U[0] + a12 * U[1] + a22 * U[2] + }; + + std::array AV = + { + a00 * V[0] + a01 * V[1] + a02 * V[2], + a01 * V[0] + a11 * V[1] + a12 * V[2], + a02 * V[0] + a12 * V[1] + a22 * V[2] + }; + + Real m00 = U[0] * AU[0] + U[1] * AU[1] + U[2] * AU[2] - eval1; + Real m01 = U[0] * AV[0] + U[1] * AV[1] + U[2] * AV[2]; + Real m11 = V[0] * AV[0] + V[1] * AV[1] + V[2] * AV[2] - eval1; + + // For robustness, choose the largest-length row of M to compute + // the eigenvector. The 2-tuple of coefficients of U and V in the + // assignments to eigenvector[1] lies on a circle, and U and V are + // unit length and perpendicular, so eigenvector[1] is unit length + // (within numerical tolerance). + Real absM00 = std::fabs(m00); + Real absM01 = std::fabs(m01); + Real absM11 = std::fabs(m11); + Real maxAbsComp; + if (absM00 >= absM11) + { + maxAbsComp = std::max(absM00, absM01); + if (maxAbsComp > (Real)0) + { + if (absM00 >= absM01) + { + m01 /= m00; + m00 = (Real)1 / std::sqrt((Real)1 + m01 * m01); + m01 *= m00; + } + else + { + m00 /= m01; + m01 = (Real)1 / std::sqrt((Real)1 + m00 * m00); + m00 *= m01; + } + evec1 = Subtract(Multiply(m01, U), Multiply(m00, V)); + } + else + { + evec1 = U; + } + } + else + { + maxAbsComp = std::max(absM11, absM01); + if (maxAbsComp > (Real)0) + { + if (absM11 >= absM01) + { + m01 /= m11; + m11 = (Real)1 / std::sqrt((Real)1 + m01 * m01); + m01 *= m11; + } + else + { + m11 /= m01; + m01 = (Real)1 / std::sqrt((Real)1 + m11 * m11); + m11 *= m01; + } + evec1 = Subtract(Multiply(m11, U), Multiply(m01, V)); + } + else + { + evec1 = U; + } + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TCBSplineCurve.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TCBSplineCurve.h new file mode 100644 index 0000000..79bf1d1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TCBSplineCurve.h @@ -0,0 +1,209 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class TCBSplineCurve : public ParametricCurve + { + public: + // Construction and destruction. The object copies the input arrays. + // The number of points must be at least 2. To validate construction, + // create an object as shown: + // TCBSplineCurve curve(parameters); + // if (!curve) { ; } + TCBSplineCurve(int numPoints, Vector const* points, + Real const* times, Real const* tension, Real const* continuity, + Real const* bias) + : + ParametricCurve(numPoints - 1, times) + { + LogAssert(numPoints >= 2 && points != nullptr, "Invalid input."); + + mPoints.resize(numPoints); + mTension.resize(numPoints); + mContinuity.resize(numPoints); + mBias.resize(numPoints); + std::copy(points, points + numPoints, mPoints.begin()); + std::copy(tension, tension + numPoints, mTension.begin()); + std::copy(continuity, continuity + numPoints, mContinuity.begin()); + std::copy(bias, bias + numPoints, mBias.begin()); + + int numSegments = numPoints - 1; + mA.resize(numSegments); + mB.resize(numSegments); + mC.resize(numSegments); + mD.resize(numSegments); + + // For now, treat the first point as if it occurred twice. + ComputePoly(0, 0, 1, 2); + + for (int i = 1; i < numSegments - 1; ++i) + { + ComputePoly(i - 1, i, i + 1, i + 2); + } + + // For now, treat the last point as if it occurred twice. + ComputePoly(numSegments - 2, numSegments - 1, numSegments, numSegments); + + this->mConstructed = true; + } + + virtual ~TCBSplineCurve() + { + } + + // Member access. + inline int GetNumPoints() const + { + return static_cast(mPoints.size()); + } + + inline Vector const* GetPoints() const + { + return &mPoints[0]; + } + + inline Real const* GetTensions() const + { + return &mTension[0]; + } + + inline Real const* GetContinuities() const + { + return &mContinuity[0]; + } + + inline Real const* GetBiases() const + { + return &mBias[0]; + } + + // Evaluation of the curve. The function supports derivative + // calculation through order 3; that is, order <= 3 is required. If + // you want/ only the position, pass in order of 0. If you want the + // position and first derivative, pass in order of 1, and so on. The + // output array 'jet' must have enough storage to support the maximum + // order. The values are ordered as: position, first derivative, + // second derivative, third derivative. + virtual void Evaluate(Real t, unsigned int order, Vector* jet) const override + { + unsigned int const supOrder = ParametricCurve::SUP_ORDER; + if (!this->mConstructed || order >= supOrder) + { + // Return a zero-valued jet for invalid state. + for (unsigned int i = 0; i < supOrder; ++i) + { + jet[i].MakeZero(); + } + return; + } + + int key; + Real dt; + GetKeyInfo(t, key, dt); + dt /= (this->mTime[key + 1] - this->mTime[key]); + + // Compute position. + jet[0] = mA[key] + dt * (mB[key] + dt * (mC[key] + dt * mD[key])); + if (order >= 1) + { + // Compute first derivative. + jet[1] = mB[key] + dt * ((Real)2 * mC[key] + ((Real)3 * dt) * mD[key]); + if (order >= 2) + { + // Compute second derivative. + jet[2] = (Real)2 * mC[key] + ((Real)6 * dt) * mD[key]; + if (order == 3) + { + jet[3] = (Real)6 * mD[key]; + } + } + } + } + + protected: + // Support for construction. + void ComputePoly(int i0, int i1, int i2, int i3) + { + Vector diff = mPoints[i2] - mPoints[i1]; + Real dt = this->mTime[i2] - this->mTime[i1]; + + // Build multipliers at P1. + Real oneMinusT0 = (Real)1 - mTension[i1]; + Real oneMinusC0 = (Real)1 - mContinuity[i1]; + Real onePlusC0 = (Real)1 + mContinuity[i1]; + Real oneMinusB0 = (Real)1 - mBias[i1]; + Real onePlusB0 = (Real)1 + mBias[i1]; + Real adj0 = (Real)2 * dt / (this->mTime[i2] - this->mTime[i0]); + Real out0 = (Real)0.5 * adj0 * oneMinusT0 * onePlusC0 * onePlusB0; + Real out1 = (Real)0.5 * adj0 * oneMinusT0 * oneMinusC0 * oneMinusB0; + + // Build outgoing tangent at P1. + Vector tOut = out1 * diff + out0 * (mPoints[i1] - mPoints[i0]); + + // Build multipliers at point P2. + Real oneMinusT1 = (Real)1 - mTension[i2]; + Real oneMinusC1 = (Real)1 - mContinuity[i2]; + Real onePlusC1 = (Real)1 + mContinuity[i2]; + Real oneMinusB1 = (Real)1 - mBias[i2]; + Real onePlusB1 = (Real)1 + mBias[i2]; + Real adj1 = (Real)2 * dt / (this->mTime[i3] - this->mTime[i1]); + Real in0 = (Real)0.5 * adj1 * oneMinusT1 * oneMinusC1 * onePlusB1; + Real in1 = (Real)0.5 * adj1 * oneMinusT1 * onePlusC1 * oneMinusB1; + + // Build incoming tangent at P2. + Vector tIn = in1 * (mPoints[i3] - mPoints[i2]) + in0 * diff; + + mA[i1] = mPoints[i1]; + mB[i1] = tOut; + mC[i1] = (Real)3 * diff - (Real)2 * tOut - tIn; + mD[i1] = (Real)-2 * diff + tOut + tIn; + } + + // Determine the index i for which times[i] <= t < times[i+1]. + void GetKeyInfo(Real t, int& key, Real& dt) const + { + int numSegments = static_cast(mA.size()); + if (t <= this->mTime[0]) + { + key = 0; + dt = (Real)0; + return; + } + + if (t < this->mTime[numSegments]) + { + for (int i = 0; i < numSegments; ++i) + { + if (t < this->mTime[i + 1]) + { + key = i; + dt = t - this->mTime[i]; + return; + } + } + } + + key = numSegments - 1; + dt = this->mTime[numSegments] - this->mTime[numSegments - 1]; + } + + std::vector> mPoints; + std::vector mTension, mContinuity, mBias; + + // Polynomial coefficients. mA are the degree 0 coefficients, mB are + // the degree 1 coefficients, mC are the degree 2 coefficients, and mD + // are the degree 3 coefficients. + std::vector> mA, mB, mC, mD; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TIQuery.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TIQuery.h new file mode 100644 index 0000000..17f91ca --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TIQuery.h @@ -0,0 +1,33 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + // Test-intersection queries. + + template + class TIQuery + { + public: + struct Result + { + // A TIQuery-base class B must define a B::Result struct with + // member 'bool intersect'. A TIQuery-derived class D must also + // derive a D::Result from B:Result but may have no members. The + // member 'intersect' is 'true' iff the primitives intersect. The + // operator() is const for conceptual constness, but derived + // classes can use internal data to support the queries and tag + // that data with the mutable modifier. + }; + + Result operator()(Type0 const& primitive0, Type1 const& primitive1) const; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TSManifoldMesh.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TSManifoldMesh.h new file mode 100644 index 0000000..b0ae4c4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TSManifoldMesh.h @@ -0,0 +1,320 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + class TSManifoldMesh + { + public: + // Triangle data types. + class Triangle; + typedef std::shared_ptr(*TCreator)(int, int, int); + typedef std::map, std::shared_ptr> TMap; + + // Tetrahedron data types. + class Tetrahedron; + typedef std::shared_ptr(*SCreator)(int, int, int, int); + typedef std::map, std::shared_ptr> SMap; + + // Triangle object. + class Triangle + { + public: + virtual ~Triangle() = default; + + Triangle(int v0, int v1, int v2) + : + V{ v0, v1, v2 } + { + } + + // Vertices of the face. + std::array V; + + // Tetrahedra sharing the face. + std::array, 2> T; + }; + + // Tetrahedron object. + class Tetrahedron + { + public: + virtual ~Tetrahedron() = default; + + Tetrahedron(int v0, int v1, int v2, int v3) + : + V{ v0, v1, v2, v3 } + { + } + + // Vertices, listed in an order so that each face vertices in + // counterclockwise order when viewed from outside the + // tetrahedron. + std::array V; + + // Adjacent faces. T[i] points to the triangle face + // opposite V[i]. + // T[0] points to face (V[1],V[2],V[3]) + // T[1] points to face (V[0],V[3],V[2]) + // T[2] points to face (V[0],V[1],V[3]) + // T[3] points to face (V[0],V[2],V[1]) + std::array, 4> T; + + // Adjacent tetrahedra. S[i] points to the adjacent tetrahedron + // sharing face T[i]. + std::array, 4> S; + }; + + + // Construction and destruction. + virtual ~TSManifoldMesh() = default; + + TSManifoldMesh(TCreator tCreator = nullptr, SCreator sCreator = nullptr) + : + mTCreator(tCreator ? tCreator : CreateTriangle), + mSCreator(sCreator ? sCreator : CreateTetrahedron), + mThrowOnNonmanifoldInsertion(true) + { + } + + // Support for a deep copy of the mesh. The mTMap and mSMap objects + // have dynamically allocated memory for triangles and tetrahedra. A + // shallow/ copy of the pointers to this memory is problematic. + // Allowing sharing, say, via std::shared_ptr, is an option but not + // really the intent of copying the mesh graph. + TSManifoldMesh(TSManifoldMesh const& mesh) + { + *this = mesh; + } + + TSManifoldMesh& operator=(TSManifoldMesh const& mesh) + { + Clear(); + + mTCreator = mesh.mTCreator; + mSCreator = mesh.mSCreator; + mThrowOnNonmanifoldInsertion = mesh.mThrowOnNonmanifoldInsertion; + for (auto const& element : mesh.mSMap) + { + Insert(element.first.V[0], element.first.V[1], element.first.V[2], element.first.V[3]); + } + + return *this; + } + + // Member access. + inline TMap const& GetTriangles() const + { + return mTMap; + } + + inline SMap const& GetTetrahedra() const + { + return mSMap; + } + + // If the insertion of a tetrahedron fails because the mesh would + // become nonmanifold, the default behavior is to throw an exception. + // You can disable this behavior and continue gracefully without an + // exception. + void ThrowOnNonmanifoldInsertion(bool doException) + { + mThrowOnNonmanifoldInsertion = doException; + } + + // If is not in the mesh, a Tetrahedron object is + // created and returned; otherwise, is in the mesh and + // nullptr is returned. If the insertion leads to a nonmanifold mesh, + // the call fails with a nullptr returned. + std::shared_ptr Insert(int v0, int v1, int v2, int v3) + { + TetrahedronKey skey(v0, v1, v2, v3); + if (mSMap.find(skey) != mSMap.end()) + { + // The tetrahedron already exists. Return a null pointer as + // a signal to the caller that the insertion failed. + return nullptr; + } + + // Add the new tetrahedron. + std::shared_ptr tetra = mSCreator(v0, v1, v2, v3); + mSMap[skey] = tetra; + + // Add the faces to the mesh if they do not already exist. + for (int i = 0; i < 4; ++i) + { + auto opposite = TetrahedronKey::GetOppositeFace()[i]; + TriangleKey tkey(tetra->V[opposite[0]], tetra->V[opposite[1]], tetra->V[opposite[2]]); + std::shared_ptr face; + auto titer = mTMap.find(tkey); + if (titer == mTMap.end()) + { + // This is the first time the face is encountered. + face = mTCreator(tetra->V[opposite[0]], tetra->V[opposite[1]], tetra->V[opposite[2]]); + mTMap[tkey] = face; + + // Update the face and tetrahedron. + face->T[0] = tetra; + tetra->T[i] = face; + } + else + { + // This is the second time the face is encountered. + face = titer->second; + LogAssert(face != nullptr, "Unexpected condition."); + + // Update the face. + if (face->T[1].lock()) + { + if (mThrowOnNonmanifoldInsertion) + { + LogError("Attempt to create nonmanifold mesh."); + } + else + { + return nullptr; + } + } + face->T[1] = tetra; + + // Update the adjacent tetrahedra. + auto adjacent = face->T[0].lock(); + LogAssert(adjacent != nullptr, "Unexpected condition."); + for (int j = 0; j < 4; ++j) + { + if (adjacent->T[j].lock() == face) + { + adjacent->S[j] = tetra; + break; + } + } + + // Update the tetrahedron. + tetra->T[i] = face; + tetra->S[i] = adjacent; + } + } + + return tetra; + } + + // If is in the mesh, it is removed and 'true' is + // returned; otherwise, is not in the mesh and 'false' + // is returned. + bool Remove(int v0, int v1, int v2, int v3) + { + TetrahedronKey skey(v0, v1, v2, v3); + auto siter = mSMap.find(skey); + if (siter == mSMap.end()) + { + // The tetrahedron does not exist. + return false; + } + + // Get the tetrahedron. + std::shared_ptr tetra = siter->second; + + // Remove the faces and update adjacent tetrahedra if necessary. + for (int i = 0; i < 4; ++i) + { + // Inform the faces the tetrahedron is being deleted. + auto face = tetra->T[i].lock(); + LogAssert(face != nullptr, "Unexpected condition."); + + if (face->T[0].lock() == tetra) + { + // One-tetrahedron faces always have pointer at index + // zero. + face->T[0] = face->T[1]; + face->T[1].reset(); + } + else if (face->T[1].lock() == tetra) + { + face->T[1].reset(); + } + else + { + LogError("Unexpected condition."); + } + + // Remove the face if you have the last reference to it. + if (!face->T[0].lock() && !face->T[1].lock()) + { + TriangleKey tkey(face->V[0], face->V[1], face->V[2]); + mTMap.erase(tkey); + } + + // Inform adjacent tetrahedra the tetrahedron is being + // deleted. + auto adjacent = tetra->S[i].lock(); + if (adjacent) + { + for (int j = 0; j < 4; ++j) + { + if (adjacent->S[j].lock() == tetra) + { + adjacent->S[j].reset(); + break; + } + } + } + } + + mSMap.erase(skey); + return true; + } + + // Destroy the triangles and tetrahedra to obtain an empty mesh. + virtual void Clear() + { + mTMap.clear(); + mSMap.clear(); + } + + // A manifold mesh is closed if each face is shared twice. + bool IsClosed() const + { + for (auto const& element : mSMap) + { + auto tri = element.second; + if (!tri->S[0].lock() || !tri->S[1].lock()) + { + return false; + } + } + return true; + } + + protected: + // The triangle data and default triangle creation. + static std::shared_ptr CreateTriangle(int v0, int v1, int v2) + { + return std::make_shared(v0, v1, v2); + } + + TCreator mTCreator; + TMap mTMap; + + // The tetrahedron data and default tetrahedron creation. + static std::shared_ptr CreateTetrahedron(int v0, int v1, int v2, int v3) + { + return std::make_shared(v0, v1, v2, v3); + } + + SCreator mSCreator; + SMap mSMap; + bool mThrowOnNonmanifoldInsertion; // default: true + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TanEstimate.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TanEstimate.h new file mode 100644 index 0000000..74fd135 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TanEstimate.h @@ -0,0 +1,160 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// Minimax polynomial approximations to tan(x). The polynomial p(x) of +// degree D has only odd-power terms, is required to have linear term x, +// and p(pi/4) = tan(pi/4) = 1. It minimizes the quantity +// maximum{|tan(x) - p(x)| : x in [-pi/4,pi/4]} over all polynomials of +// degree D subject to the constraints mentioned. + +namespace WwiseGTE +{ + template + class TanEstimate + { + public: + // The input constraint is x in [-pi/4,pi/4]. For example, + // float x; // in [-pi/4,pi/4] + // float result = TanEstimate::Degree<3>(x); + template + inline static Real Degree(Real x) + { + return Evaluate(degree(), x); + } + + // The input x can be any real number. Range reduction is used to + // generate a value y in [-pi/2,pi/2]. If |y| <= pi/4, then the + // polynomial is evaluated. If y in (pi/4,pi/2), set z = y - pi/4 + // and use the identity + // tan(y) = tan(z + pi/4) = [1 + tan(z)]/[1 - tan(z)] + // Be careful when evaluating at y nearly pi/2, because tan(y) + // becomes infinite. For example, + // float x; // x any real number + // float result = TanEstimate::DegreeRR<3>(x); + template + inline static Real DegreeRR(Real x) + { + Real y; + Reduce(x, y); + if (std::fabs(y) <= (Real)GTE_C_QUARTER_PI) + { + return Degree(y); + } + else if (y > (Real)GTE_C_QUARTER_PI) + { + Real poly = Degree(y - (Real)GTE_C_QUARTER_PI); + return ((Real)1 + poly) / ((Real)1 - poly); + } + else + { + Real poly = Degree(y + (Real)GTE_C_QUARTER_PI); + return -((Real)1 - poly) / ((Real)1 + poly); + } + } + + private: + // Metaprogramming and private implementation to allow specialization + // of a template member function. + template struct degree {}; + + inline static Real Evaluate(degree<3>, Real x) + { + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_TAN_DEG3_C1; + poly = (Real)GTE_C_TAN_DEG3_C0 + poly * xsqr; + poly = poly * x; + return poly; + } + + inline static Real Evaluate(degree<5>, Real x) + { + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_TAN_DEG5_C2; + poly = (Real)GTE_C_TAN_DEG5_C1 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG5_C0 + poly * xsqr; + poly = poly * x; + return poly; + } + + inline static Real Evaluate(degree<7>, Real x) + { + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_TAN_DEG7_C3; + poly = (Real)GTE_C_TAN_DEG7_C2 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG7_C1 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG7_C0 + poly * xsqr; + poly = poly * x; + return poly; + } + + inline static Real Evaluate(degree<9>, Real x) + { + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_TAN_DEG9_C4; + poly = (Real)GTE_C_TAN_DEG9_C3 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG9_C2 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG9_C1 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG9_C0 + poly * xsqr; + poly = poly * x; + return poly; + } + + inline static Real Evaluate(degree<11>, Real x) + { + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_TAN_DEG11_C5; + poly = (Real)GTE_C_TAN_DEG11_C4 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG11_C3 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG11_C2 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG11_C1 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG11_C0 + poly * xsqr; + poly = poly * x; + return poly; + } + + inline static Real Evaluate(degree<13>, Real x) + { + Real xsqr = x * x; + Real poly; + poly = (Real)GTE_C_TAN_DEG13_C6; + poly = (Real)GTE_C_TAN_DEG13_C5 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG13_C4 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG13_C3 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG13_C2 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG13_C1 + poly * xsqr; + poly = (Real)GTE_C_TAN_DEG13_C0 + poly * xsqr; + poly = poly * x; + return poly; + } + + // Support for range reduction. + inline static void Reduce(Real x, Real& y) + { + // Map x to y in [-pi,pi], x = pi*quotient + remainder. + y = std::fmod(x, (Real)GTE_C_PI); + + // Map y to [-pi/2,pi/2] with tan(y) = tan(x). + if (y > (Real)GTE_C_HALF_PI) + { + y -= (Real)GTE_C_PI; + } + else if (y < (Real)-GTE_C_HALF_PI) + { + y += (Real)GTE_C_PI; + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Tetrahedron3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Tetrahedron3.h new file mode 100644 index 0000000..ab0a513 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Tetrahedron3.h @@ -0,0 +1,148 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The tetrahedron is represented as an array of four vertices: V0, V1, V2, +// and V3. The vertices are ordered so that the triangular faces are +// counterclockwise-ordered triangles when viewed by an observer outside the +// tetrahedron: +// face 0 = +// face 1 = +// face 2 = +// face 3 = + +namespace WwiseGTE +{ + template + class Tetrahedron3 + { + public: + // Construction and destruction. The default constructor sets the + // vertices to (0,0,0), (1,0,0), (0,1,0), and (0,0,1). + Tetrahedron3() + : + v{ Vector3::Zero(), Vector3::Unit(0), + Vector3::Unit(1), Vector3::Unit(2) } + { + } + + Tetrahedron3(Vector3 const& v0, Vector3 const& v1, + Vector3 const& v2, Vector3 const& v3) + : + v{ v0, v1, v2, v3 } + { + } + + Tetrahedron3(std::array, 4> const& inV) + : + v(inV) + { + } + + + // Get the vertex indices for the specified face. The input 'face' + // must be in {0,1,2,3}. + void GetFaceIndices(int face, int index[3]) const + { + if (face == 0) + { + index[0] = 0; + index[1] = 2; + index[2] = 1; + } + else if (face == 1) + { + index[0] = 0; + index[1] = 1; + index[2] = 3; + } + else if (face == 2) + { + index[0] = 0; + index[1] = 3; + index[2] = 2; + } + else // face == 3 (no index validation is performed) + { + index[0] = 1; + index[1] = 2; + index[2] = 3; + } + } + + // Construct the planes of the faces. The planes have outer pointing + // normal vectors. The plane indexing is the same as the face + // indexing mentioned previously. + void GetPlanes(Plane3 plane[4]) const + { + Vector3 edge10 = v[1] - v[0]; + Vector3 edge20 = v[2] - v[0]; + Vector3 edge30 = v[3] - v[0]; + Vector3 edge21 = v[2] - v[1]; + Vector3 edge31 = v[3] - v[1]; + + plane[0].normal = UnitCross(edge20, edge10); // + plane[1].normal = UnitCross(edge10, edge30); // + plane[2].normal = UnitCross(edge30, edge20); // + plane[3].normal = UnitCross(edge21, edge31); // + + Real det = Dot(edge10, plane[3].normal); + if (det < (Real)0) + { + // The normals are inner pointing, reverse their directions. + for (int i = 0; i < 4; ++i) + { + plane[i].normal = -plane[i].normal; + } + } + + for (int i = 0; i < 4; ++i) + { + plane[i].constant = Dot(v[i], plane[i].normal); + } + } + + // Public member access. + std::array, 4> v; + + public: + // Comparisons to support sorted containers. + bool operator==(Tetrahedron3 const& tetrahedron) const + { + return v == tetrahedron.v; + } + + bool operator!=(Tetrahedron3 const& tetrahedron) const + { + return v != tetrahedron.v; + } + + bool operator< (Tetrahedron3 const& tetrahedron) const + { + return v < tetrahedron.v; + } + + bool operator<=(Tetrahedron3 const& tetrahedron) const + { + return v <= tetrahedron.v; + } + + bool operator> (Tetrahedron3 const& tetrahedron) const + { + return v > tetrahedron.v; + } + + bool operator>=(Tetrahedron3 const& tetrahedron) const + { + return v >= tetrahedron.v; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TetrahedronKey.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TetrahedronKey.h new file mode 100644 index 0000000..dd07a89 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TetrahedronKey.h @@ -0,0 +1,160 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// An ordered tetrahedron has V[0] = min(v0, v1, v2, v3). Let {u1, u2, u3} +// be the set of inputs excluding the one assigned to V[0] and define +// V[1] = min(u1, u2, u3). Choose (V[1], V[2], V[3]) to be a permutation +// of (u1, u2, u3) so that the final storage is one of +// (v0, v1, v2, v3), (v0, v2, v3, v1), (v0, v3, v1, v2) +// (v1, v3, v2, v0), (v1, v2, v0, v3), (v1, v0, v3, v2) +// (v2, v3, v0, v1), (v2, v0, v1, v3), (v2, v1, v3, v0) +// (v3, v1, v0, v2), (v3, v0, v2, v1), (v3, v2, v1, v0) +// The idea is that if v0 corresponds to (1, 0, 0, 0), v1 corresponds to +// (0, 1, 0, 0), v2 corresponds to (0, 0, 1, 0), and v3 corresponds to +// (0, 0, 0, 1), the ordering (v0, v1, v2, v3) corresponds to the 4x4 identity +// matrix I; the rows are the specified 4-tuples. The permutation +// (V[0], V[1], V[2], V[3]) induces a permutation of the rows of the identity +// matrix to form a permutation matrix P with det(P) = 1 = det(I). +// +// An unordered tetrahedron stores a permutation of (v0, v1, v2, v3) so +// that V[0] < V[1] < V[2] < V[3]. + +namespace WwiseGTE +{ + template + class TetrahedronKey : public FeatureKey<4, Ordered> + { + public: + TetrahedronKey() + { + this->V = { -1, -1, -1, -1 }; + } + + explicit TetrahedronKey(int v0, int v1, int v2, int v3) + { + Initialize(v0, v1, v2, v3); + } + + // Indexing for the vertices of the triangle opposite a vertex. The + // triangle opposite vertex j is + // + // and is listed in counterclockwise order when viewed from outside + // the tetrahedron. + static inline std::array, 4> const& GetOppositeFace() + { + static std::array, 4> const sOppositeFace = + {{ + { 1, 2, 3 }, + { 0, 3, 2 }, + { 0, 1, 3 }, + { 0, 2, 1 } + }}; + return sOppositeFace; + } + + private: + template + typename std::enable_if::type + Initialize(int v0, int v1, int v2, int v3) + { + int imin = 0; + this->V[0] = v0; + if (v1 < this->V[0]) + { + this->V[0] = v1; + imin = 1; + } + if (v2 < this->V[0]) + { + this->V[0] = v2; + imin = 2; + } + if (v3 < this->V[0]) + { + this->V[0] = v3; + imin = 3; + } + + if (imin == 0) + { + Permute(v1, v2, v3); + } + else if (imin == 1) + { + Permute(v0, v3, v2); + } + else if (imin == 2) + { + Permute(v0, v1, v3); + } + else // imin == 3 + { + Permute(v0, v2, v1); + } + } + + template + typename std::enable_if::type + Initialize(int v0, int v1, int v2, int v3) + { + this->V[0] = v0; + this->V[1] = v1; + this->V[2] = v2; + this->V[3] = v3; + std::sort(this->V.begin(), this->V.end()); + } + + template + typename std::enable_if::type + Permute(int u0, int u1, int u2) + { + // Once V[0] is determined, create a permutation + // (V[1], V[2], V[3]) so that (V[0], V[1], V[2], V[3]) is a + // permutation of (v0, v1, v2, v3) that corresponds to the + // identity matrix as mentioned in the comments at the beginning + // of this file. + if (u0 < u1) + { + if (u0 < u2) + { + // u0 is minimum + this->V[1] = u0; + this->V[2] = u1; + this->V[3] = u2; + } + else + { + // u2 is minimum + this->V[1] = u2; + this->V[2] = u0; + this->V[3] = u1; + } + } + else + { + if (u1 < u2) + { + // u1 is minimum + this->V[1] = u1; + this->V[2] = u2; + this->V[3] = u0; + } + else + { + // u2 is minimum + this->V[1] = u2; + this->V[2] = u0; + this->V[3] = u1; + } + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ThreadSafeMap.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ThreadSafeMap.h new file mode 100644 index 0000000..9d45519 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ThreadSafeMap.h @@ -0,0 +1,131 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +namespace WwiseGTE +{ + template + class ThreadSafeMap + { + public: + // Construction and destruction. + ThreadSafeMap() = default; + virtual ~ThreadSafeMap() = default; + + // All the operations are thread-safe. + bool HasElements() const + { + bool hasElements; + mMutex.lock(); + { + hasElements = (mMap.size() > 0); + } + mMutex.unlock(); + return hasElements; + } + + bool Exists(Key key) const + { + bool exists; + mMutex.lock(); + { + exists = (mMap.find(key) != mMap.end()); + } + mMutex.unlock(); + return exists; + } + + void Insert(Key key, Value value) + { + mMutex.lock(); + { + mMap[key] = value; + } + mMutex.unlock(); + } + + bool Remove(Key key, Value& value) + { + bool exists; + mMutex.lock(); + { + auto iter = mMap.find(key); + if (iter != mMap.end()) + { + value = iter->second; + mMap.erase(iter); + exists = true; + } + else + { + exists = false; + } + } + mMutex.unlock(); + return exists; + } + + void RemoveAll() + { + mMutex.lock(); + { + mMap.clear(); + } + mMutex.unlock(); + } + + bool Get(Key key, Value& value) const + { + bool exists; + mMutex.lock(); + { + auto iter = mMap.find(key); + if (iter != mMap.end()) + { + value = iter->second; + exists = true; + } + else + { + exists = false; + } + } + mMutex.unlock(); + return exists; + } + + void GatherAll(std::vector& values) const + { + mMutex.lock(); + { + if (mMap.size() > 0) + { + values.resize(mMap.size()); + auto viter = values.begin(); + for (auto const& m : mMap) + { + *viter++ = m.second; + } + } + else + { + values.clear(); + } + } + mMutex.unlock(); + } + + protected: + std::map mMap; + mutable std::mutex mMutex; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ThreadSafeQueue.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ThreadSafeQueue.h new file mode 100644 index 0000000..6194bb6 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/ThreadSafeQueue.h @@ -0,0 +1,95 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +namespace WwiseGTE +{ + template + class ThreadSafeQueue + { + public: + // Construction and destruction. + ThreadSafeQueue(size_t maxNumElements = 0) + : + mMaxNumElements(maxNumElements) + { + } + + virtual ~ThreadSafeQueue() = default; + + // All the operations are thread-safe. + size_t GetMaxNumElements() const + { + size_t maxNumElements; + mMutex.lock(); + { + maxNumElements = mMaxNumElements; + } + mMutex.unlock(); + return maxNumElements; + } + + size_t GetNumElements() const + { + size_t numElements; + mMutex.lock(); + { + numElements = mQueue.size(); + } + mMutex.unlock(); + return numElements; + } + + bool Push(Element const& element) + { + bool pushed; + mMutex.lock(); + { + if (mQueue.size() < mMaxNumElements) + { + mQueue.push(element); + pushed = true; + } + else + { + pushed = false; + } + } + mMutex.unlock(); + return pushed; + } + + bool Pop(Element& element) + { + bool popped; + mMutex.lock(); + { + if (mQueue.size() > 0) + { + element = mQueue.front(); + mQueue.pop(); + popped = true; + } + else + { + popped = false; + } + } + mMutex.unlock(); + return popped; + } + + protected: + size_t mMaxNumElements; + std::queue mQueue; + mutable std::mutex mMutex; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Torus3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Torus3.h new file mode 100644 index 0000000..8e8307e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Torus3.h @@ -0,0 +1,230 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// A torus with origin (0,0,0), outer radius r0 and inner radius r1 (with +// (r0 >= r1) is defined implicitly as follows. The point P0 = (x,y,z) is on +// the torus. Its projection onto the xy-plane is P1 = (x,y,0). The circular +// cross section of the torus that contains the projection has radius r0 and +// center P2 = r0*(x,y,0)/sqrt(x^2+y^2). The points triangle is a +// right triangle with right angle at P1. The hypotenuse has length +// r1, leg has length z and leg has length +// |r0 - sqrt(x^2+y^2)|. The Pythagorean theorem says +// z^2 + |r0 - sqrt(x^2+y^2)|^2 = r1^2. This can be algebraically +// manipulated to +// (x^2 + y^2 + z^2 + r0^2 - r1^2)^2 - 4 * r0^2 * (x^2 + y^2) = 0 +// +// A parametric form is +// x = (r0 + r1 * cos(v)) * cos(u) +// y = (r0 + r1 * cos(v)) * sin(u) +// z = r1 * sin(v) +// for u in [0,2*pi) and v in [0,2*pi). +// +// Generally, let the torus center be C with plane of symmetry containing C +// and having directions D0 and D1. The axis of symmetry is the line +// containing C and having direction N (the plane normal). The radius from +// the center of the torus is r0 and the radius of the tube of the torus is +// r1. A point P may be written as P = C + x*D0 + y*D1 + z*N, where matrix +// [D0 D1 N] is orthonormal and has determinant 1. Thus, x = Dot(D0,P-C), +// y = Dot(D1,P-C) and z = Dot(N,P-C). The implicit form is +// [|P-C|^2 + r0^2 - r1^2]^2 - 4*r0^2*[|P-C|^2 - (Dot(N,P-C))^2] = 0 +// Observe that D0 and D1 are not present in the equation, which is to be +// expected by the symmetry. The parametric form is +// P(u,v) = C + (r0 + r1*cos(v))*(cos(u)*D0 + sin(u)*D1) + r1*sin(v)*N +// for u in [0,2*pi) and v in [0,2*pi). +// +// In the class Torus3, the members are 'center' C, 'direction0' D0, +// 'direction1' D1, 'normal' N, 'radius0' r0 and 'radius1' r1. + +namespace WwiseGTE +{ + template + class Torus3 + { + public: + // Construction and destruction. The default constructor sets center + // to (0,0,0), direction0 to (1,0,0), direction1 to (0,1,0), normal + // to (0,0,1), radius0 to 2 and radius1 to 1. + Torus3() + : + center(Vector3::Zero()), + direction0(Vector3::Unit(0)), + direction1(Vector3::Unit(1)), + normal(Vector3::Unit(2)), + radius0((Real)2), + radius1((Real)1) + { + } + + Torus3(Vector3 const& inCenter, Vector3 const& inDirection0, + Vector3 const& inDirection1, Vector3 const& inNormal, + Real inRadius0, Real inRadius1) + : + center(inCenter), + direction0(inDirection0), + direction1(inDirection1), + normal(inNormal), + radius0(inRadius0), + radius1(inRadius1) + { + } + + // Evaluation of the surface. The function supports derivative + // calculation through order 2; that is, maxOrder <= 2 is required. + // If you want only the position, pass in maxOrder of 0. If you want + // the position and first-order derivatives, pass in maxOrder of 1, + // and so on. The output 'values' are ordered as: position X; + // first-order derivatives dX/du, dX/dv; second-order derivatives + // d2X/du2, d2X/dudv, d2X/dv2. The input array 'jet' must have enough + // storage for the specified order. + void Evaluate(Real u, Real v, unsigned int maxOrder, Vector3* jet) const + { + // Compute position. + Real csu = std::cos(u); + Real snu = std::sin(u); + Real csv = std::cos(v); + Real snv = std::sin(v); + Real r1csv = radius1 * csv; + Real r1snv = radius1 * snv; + Real r0pr1csv = radius0 + r1csv; + Vector3 combo0 = csu * direction0 + snu * direction1; + Vector3 r0pr1csvcombo0 = r0pr1csv * combo0; + Vector3 r1snvnormal = r1snv * normal; + jet[0] = center + r0pr1csvcombo0 + r1snvnormal; + + if (maxOrder >= 1) + { + // Compute first-order derivatives. + Vector3 combo1 = -snu * direction0 + csu * direction1; + jet[1] = r0pr1csv * combo1; + jet[2] = -r1snv * combo0 + r1csv * normal; + + if (maxOrder == 2) + { + // Compute second-order derivatives. + jet[3] = -r0pr1csvcombo0; + jet[4] = -r1snv * combo1; + jet[5] = -r1csv * combo0 - r1snvnormal; + } + } + } + + // Reverse lookup of parameters from position. + void GetParameters(Vector3 const& X, Real& u, Real& v) const + { + Vector3 delta = X - center; + + // (r0 + r1*cos(v))*cos(u) + Real dot0 = Dot(direction0, delta); + + // (r0 + r1*cos(v))*sin(u) + Real dot1 = Dot(direction1, delta); + + // r1*sin(v) + Real dot2 = Dot(normal, delta); + + // r1*cos(v) + Real r1csv = std::sqrt(dot0 * dot0 + dot1 * dot1) - radius0; + + u = std::atan2(dot1, dot0); + v = std::atan2(dot2, r1csv); + } + + Vector3 center, direction0, direction1, normal; + Real radius0, radius1; + + public: + // Comparisons to support sorted containers. + bool operator==(Torus3 const& torus) const + { + return center == torus.center + && direction0 == torus.direction0 + && direction1 == torus.direction1 + && normal == torus.normal + && radius0 == torus.radius0 + && radius1 == torus.radius1; + } + + bool operator!=(Torus3 const& torus) const + { + return !operator==(torus); + } + + bool operator< (Torus3 const& torus) const + { + if (center < torus.center) + { + return true; + } + + if (center > torus.center) + { + return false; + } + + if (direction0 < torus.direction0) + { + return true; + } + + if (direction0 > torus.direction0) + { + return false; + } + + if (direction1 < torus.direction1) + { + return true; + } + + if (direction1 > torus.direction1) + { + return false; + } + + if (normal < torus.normal) + { + return true; + } + + if (normal > torus.normal) + { + return false; + } + + if (radius0 < torus.radius0) + { + return true; + } + + if (radius0 > torus.radius0) + { + return false; + } + + return radius1 < torus.radius1; + } + + bool operator<=(Torus3 const& torus) const + { + return !torus.operator<(*this); + } + + bool operator> (Torus3 const& torus) const + { + return torus.operator<(*this); + } + + bool operator>=(Torus3 const& torus) const + { + return !operator<(torus); + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Transform.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Transform.h new file mode 100644 index 0000000..4b5d881 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Transform.h @@ -0,0 +1,824 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2020.01.10 + +#pragma once + +#include +#include +#include +#include + +// Transforms when GTE_USE_MAT_VEC is defined in the preprocessor symbols. +// +// The transform is Y = M*X+T, where M is a 3-by-3 matrix and T is a 3x1 +// translation. In most cases, M = R, a rotation matrix, or M = R*S, +// where R is a rotation matrix and S is a diagonal matrix whose diagonal +// entries are positive scales. To support modeling packages that allow +// general affine transforms, M can be any invertible 3x3 matrix. The vector +// X is transformed in the "forward" direction to Y. The "inverse" direction +// transforms Y to X, namely X = M^{-1}*(Y-T) in the general case. In the +// special case of M = R*S, the inverse direction is X = S^{-1}*R^t*(Y-T), +// where S^{-1} is the diagonal matrix whose diagonal entries are the +// reciprocoals of those of S and where R^t is the transpose of R. For SIMD +// support of matrix-vector and matrix-matrix multiplications, a homogeneous +// matrix H = {{M,T},{0,1}} is stored by this class. The forward transform is +// {Y,1} = H*{X,1} and the inverse transform is {X,1} = H^{-1}*{Y,1}. + +// Transforms when GTE_USE_MAT_VEC is NOT defined in the preprocessor symbols. +// +// The transform is Y = T + X*M, where M is a 3-by-3 matrix and T is a 1x3 +// translation. In most cases, M = R, a rotation matrix, or M = S*R, +// where R is a rotation matrix and S is a diagonal matrix whose diagonal +// entries are positive scales. To support modeling packages that allow +// general affine transforms, M can be any invertible 3x3 matrix. The vector +// X is transformed in the "forward" direction to Y. The "inverse" direction +// transforms Y to X, namely X = (Y-T)*M^{-1} in the general case. In the +// special case of M = S*R, the inverse direction is X = (Y-T)*R^t*S^{-1}, +// where S^{-1} is the diagonal matrix whose diagonal entries are the +// reciprocoals of those of S and where R^t is the transpose of R. For SIMD +// support of matrix-vector and matrix-matrix multiplications, a homogeneous +// matrix H = {{M,0},{T,1}} is stored by this class. The forward transform is +// {Y,1} = {X,1}*H and the inverse transform is {X,1} = {Y,1}*H^{-1}. + +// With either multiplication convention, a matrix M = R*S (GTE_USE_MAT_VEC) +// or a matrix M = S*R (!GTE_USE_VEC_MAT) is referred to as an "RS-matrix". +// The class does not provide a member function to compute the inverse of a +// transform: 'Transform GetInverse() const'. If one were to add this, +// be aware that the inverse of an RS-matrix is not generally an RS-matrix; +// that is, the inverse of R*S is S^{-1}*R^t which cannot always be factored +// as S^{-1} * R^t = R' * S'. You would need to SetMatrix using S^{-1}*R^t +// as the input. + +namespace WwiseGTE +{ + template + class Transform + { + public: + // The default constructor produces the identity transformation. The + // default copy constructor and assignment operator are generated by + // the compiler. + Transform() + : + mTranslate{ (Real)0, (Real)0, (Real)0, (Real)1 }, + mScale{ (Real)1, (Real)1, (Real)1, (Real)1 }, + mIsIdentity(true), + mIsRSMatrix(true), + mIsUniformScale(true), + mInverseNeedsUpdate(false) + { + mHMatrix.MakeIdentity(); + mInvHMatrix.MakeIdentity(); + mMatrix.MakeIdentity(); + } + + // Implicit conversion. + inline operator Matrix4x4 const& () const + { + return GetHMatrix(); + } + + // Set the transformation to the identity matrix. + void MakeIdentity() + { + mMatrix.MakeIdentity(); + mTranslate = { (Real)0, (Real)0, (Real)0, (Real)1 }; + mScale = { (Real)1, (Real)1, (Real)1, (Real)1 }; + mIsIdentity = true; + mIsRSMatrix = true; + mIsUniformScale = true; + UpdateHMatrix(); + } + + // Set the transformation to have scales of 1. + void MakeUnitScale() + { + LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); + mScale = { (Real)1, (Real)1, (Real)1, (Real)1 }; + mIsUniformScale = true; + UpdateHMatrix(); + } + + // Hints about the structure of the transformation. + + // M = I + inline bool IsIdentity() const + { + return mIsIdentity; + } + + // R*S (GTE_USE_MAT_VEC defined) or S*R (GTE_USE_MAT_VEC not defined) + inline bool IsRSMatrix() const + { + return mIsRSMatrix; + } + + // RS-matrix with S = c*I + inline bool IsUniformScale() const + { + return mIsRSMatrix && mIsUniformScale; + } + + // Member access. + // (1) The Set* functions set the is-identity hint to false. + // (2) The SetRotate function sets the is-rsmatrix hint to true. If this + // hint is false, GetRotate triggers an assertion in debug mode. + // (3) The SetMatrix function sets the is-rsmatrix and is-uniform-scale + // hints to false. + // (4) The SetScale function sets the is-uniform-scale hint to false. + // The SetUniformScale function sets the is-uniform-scale hint to + // true. If this hint is false, GetUniformScale triggers an assertion + // in debug mode. + // (5) All Set* functions set the inverse-needs-update to true. When + // GetHInverse is called, the inverse must be computed in this case + // and the inverse-needs-update is reset to false. + + // {{R,0},{0,1}} + void SetRotation(Matrix4x4 const& rotate) + { + mMatrix = rotate; + mIsIdentity = false; + mIsRSMatrix = true; + UpdateHMatrix(); + } + + // {{M,0},{0,1}} + void SetMatrix(Matrix4x4 const& matrix) + { + mMatrix = matrix; + mIsIdentity = false; + mIsRSMatrix = false; + mIsUniformScale = false; + UpdateHMatrix(); + } + + void SetTranslation(Real x0, Real x1, Real x2) + { + mTranslate = Vector4{ x0, x1, x2, (Real)1 }; + mIsIdentity = false; + UpdateHMatrix(); + } + + inline void SetTranslation(Vector3 const& translate) + { + SetTranslation(translate[0], translate[1], translate[2]); + } + + inline void SetTranslation(Vector4 const& translate) + { + SetTranslation(translate[0], translate[1], translate[2]); + } + + void SetScale(Real s0, Real s1, Real s2) + { + LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); + LogAssert(s0 != (Real)0 && s1 != (Real)0 && s2 != (Real)0, "Scales must be nonzero."); + mScale = { s0, s1, s2, (Real)1 }; + mIsIdentity = false; + mIsUniformScale = false; + UpdateHMatrix(); + } + + inline void SetScale(Vector3 const& scale) + { + SetScale(scale[0], scale[1], scale[2]); + } + + inline void SetScale(Vector4 const& scale) + { + SetScale(scale[0], scale[1], scale[2]); + } + + void SetUniformScale(Real scale) + { + LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); + LogAssert(scale != (Real)0, "Scale must be nonzero."); + mScale = { scale, scale, scale, (Real)1 }; + mIsIdentity = false; + mIsUniformScale = true; + UpdateHMatrix(); + } + + // {{R,0},{0,1}} + Matrix4x4 const& GetRotation() const + { + LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); + return mMatrix; + } + + // {{M,0},{0,1}} + inline Matrix4x4 const& GetMatrix() const + { + return mMatrix; + } + + // (x,y,z) + inline Vector3 GetTranslation() const + { + return Vector3{ mTranslate[0], mTranslate[1], mTranslate[2] }; + } + + // (x,y,z,0) + inline Vector4 GetTranslationW0() const + { + return Vector4{ mTranslate[0], mTranslate[1], mTranslate[2], (Real)0 }; + } + + // (x,y,z,1) + inline Vector4 GetTranslationW1() const + { + return Vector4{ mTranslate[0], mTranslate[1], mTranslate[2], (Real)1 }; + } + + // (s0,s1,s2) + Vector3 GetScale() const + { + LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); + return Vector3{ mScale[0], mScale[1], mScale[2] }; + } + + // (s0,s1,s2,1) + Vector4 GetScaleW1() const + { + LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); + return Vector4{ mScale[0], mScale[1], mScale[2], (Real)1 }; + } + + Real GetUniformScale() const + { + LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); + LogAssert(mIsUniformScale, "Transform is not uniform scale."); + return mScale[0]; + } + + // Alternate representations to set/get the rotation. + + // Set/get from 3x3 matrices. + void SetRotation(Matrix3x3 const& rotate) + { + mMatrix.MakeIdentity(); + for (int r = 0; r < 3; ++r) + { + for (int c = 0; c < 3; ++c) + { + mMatrix(r, c) = rotate(r, c); + } + } + + mIsIdentity = false; + mIsRSMatrix = true; + UpdateHMatrix(); + } + + void GetRotation(Matrix3x3& rotate) const + { + LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); + for (int r = 0; r < 3; ++r) + { + for (int c = 0; c < 3; ++c) + { + rotate(r, c) = mMatrix(r, c); + } + } + } + + // The quaternion is unit length. + void SetRotation(Quaternion const& q) + { + mMatrix = Rotation<4, Real>(q); + mIsIdentity = false; + mIsRSMatrix = true; + UpdateHMatrix(); + } + + void GetRotation(Quaternion& q) const + { + LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); + q = Rotation<4, Real>(mMatrix); + } + + // The axis is unit length and the angle is in radians. + void SetRotation(AxisAngle<3, Real> const& axisAngle) + { + AxisAngle<4, Real> aa4(HLift(axisAngle.axis, (Real)1), axisAngle.angle); + mMatrix = Rotation<4, Real>(aa4); + mIsIdentity = false; + mIsRSMatrix = true; + UpdateHMatrix(); + } + + void GetRotation(AxisAngle<3, Real>& axisAngle) const + { + LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); + AxisAngle<4, Real> aa4 = Rotation<4, Real>(mMatrix); + axisAngle.axis = HProject(aa4.axis); + axisAngle.angle = aa4.angle; + } + + void SetRotation(AxisAngle<4, Real> const& axisAngle) + { + mMatrix = Rotation<4, Real>(axisAngle); + mIsIdentity = false; + mIsRSMatrix = true; + UpdateHMatrix(); + } + + void GetRotation(AxisAngle<4, Real>& axisAngle) const + { + LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); + axisAngle = Rotation<4, Real>(mMatrix); + } + + // The Euler angles are in radians. The GetEulerAngles function + // expects the eulerAngles.axis[] values to be set to the axis order + // you want. + void SetRotation(EulerAngles const& eulerAngles) + { + mMatrix = Rotation<4, Real>(eulerAngles); + mIsIdentity = false; + mIsRSMatrix = true; + UpdateHMatrix(); + } + + void GetRotation(EulerAngles& eulerAngles) const + { + LogAssert(mIsRSMatrix, "Transform is not rotation-scale."); + eulerAngles = Rotation<4, Real>(mMatrix)(eulerAngles.axis[0], + eulerAngles.axis[1], eulerAngles.axis[2]); + } + + // For M = R*S or M = S*R, the largest value of S in absolute value is + // returned. For general M, the max-row-sum norm is returned when + // GTE_USE_MAT_VEC is defined or the max-col-sum norm is returned when + // the GTE_USE_MAT_VEC is not defined, which is a reasonable measure + // of maximum scale of the transformation. + Real GetNorm() const + { + Real sum0, sum1, sum2; + + if (mIsRSMatrix) + { + // A RS matrix (GTE_USE_MAT_VEC defined) or an SR matrix + // (GTE_USE_MAT_VEC is not defined). + sum0 = std::fabs(mScale[0]); + sum1 = std::fabs(mScale[1]); + sum2 = std::fabs(mScale[2]); + } + else + { + // The spectral norm (the maximum absolute value of the + // eigenvalues) is smaller or equal to this norm. Therefore, + // this function returns an approximation to the maximum + // scale. + +#if defined(GTE_USE_MAT_VEC) + // Use the max-row-sum matrix norm. + sum0 = std::fabs(mMatrix(0, 0)) + std::fabs(mMatrix(0, 1)) + std::fabs(mMatrix(0, 2)); + sum1 = std::fabs(mMatrix(1, 0)) + std::fabs(mMatrix(1, 1)) + std::fabs(mMatrix(1, 2)); + sum2 = std::fabs(mMatrix(2, 0)) + std::fabs(mMatrix(2, 1)) + std::fabs(mMatrix(2, 2)); +#else + // Use the max-col-sum matrix norm. + sum0 = std::fabs(mMatrix(0, 0)) + std::fabs(mMatrix(1, 0)) + std::fabs(mMatrix(2, 0)); + sum1 = std::fabs(mMatrix(0, 1)) + std::fabs(mMatrix(1, 1)) + std::fabs(mMatrix(2, 1)); + sum2 = std::fabs(mMatrix(0, 2)) + std::fabs(mMatrix(1, 2)) + std::fabs(mMatrix(2, 2)); +#endif + } + + return std::max(std::max(sum0, sum1), sum2); + } + + // Get the homogeneous matrix (composite of all channels). + inline Matrix4x4 const& GetHMatrix() const + { + return mHMatrix; + } + + // Get the inverse homogeneous matrix, recomputing it when necessary. + // GTE_USE_MAT_VEC + // H = {{M,T},{0,1}}, then H^{-1} = {{M^{-1},-M^{-1}*T},{0,1}} + // GTE_USE_VEC_MAT + // H = {{M,0},{T,1}}, then H^{-1} = {{M^{-1},0},{-M^{-1}*T,1}} + Matrix4x4 const& GetHInverse() const + { + if (mInverseNeedsUpdate) + { + if (mIsIdentity) + { + mInvHMatrix.MakeIdentity(); + } + else + { + if (mIsRSMatrix) + { + if (mIsUniformScale) + { + Real invScale = (Real)1 / mScale[0]; +#if defined(GTE_USE_MAT_VEC) + mInvHMatrix(0, 0) = invScale * mMatrix(0, 0); + mInvHMatrix(0, 1) = invScale * mMatrix(1, 0); + mInvHMatrix(0, 2) = invScale * mMatrix(2, 0); + mInvHMatrix(1, 0) = invScale * mMatrix(0, 1); + mInvHMatrix(1, 1) = invScale * mMatrix(1, 1); + mInvHMatrix(1, 2) = invScale * mMatrix(2, 1); + mInvHMatrix(2, 0) = invScale * mMatrix(0, 2); + mInvHMatrix(2, 1) = invScale * mMatrix(1, 2); + mInvHMatrix(2, 2) = invScale * mMatrix(2, 2); +#else + mInvHMatrix(0, 0) = mMatrix(0, 0) * invScale; + mInvHMatrix(0, 1) = mMatrix(1, 0) * invScale; + mInvHMatrix(0, 2) = mMatrix(2, 0) * invScale; + mInvHMatrix(1, 0) = mMatrix(0, 1) * invScale; + mInvHMatrix(1, 1) = mMatrix(1, 1) * invScale; + mInvHMatrix(1, 2) = mMatrix(2, 1) * invScale; + mInvHMatrix(2, 0) = mMatrix(0, 2) * invScale; + mInvHMatrix(2, 1) = mMatrix(1, 2) * invScale; + mInvHMatrix(2, 2) = mMatrix(2, 2) * invScale; +#endif + } + else + { + // Replace 3 reciprocals by 6 multiplies and + // 1 reciprocal. + Real s01 = mScale[0] * mScale[1]; + Real s02 = mScale[0] * mScale[2]; + Real s12 = mScale[1] * mScale[2]; + Real invs012 = (Real)1 / (s01 * mScale[2]); + Real invS0 = s12 * invs012; + Real invS1 = s02 * invs012; + Real invS2 = s01 * invs012; +#if defined(GTE_USE_MAT_VEC) + mInvHMatrix(0, 0) = invS0 * mMatrix(0, 0); + mInvHMatrix(0, 1) = invS0 * mMatrix(1, 0); + mInvHMatrix(0, 2) = invS0 * mMatrix(2, 0); + mInvHMatrix(1, 0) = invS1 * mMatrix(0, 1); + mInvHMatrix(1, 1) = invS1 * mMatrix(1, 1); + mInvHMatrix(1, 2) = invS1 * mMatrix(2, 1); + mInvHMatrix(2, 0) = invS2 * mMatrix(0, 2); + mInvHMatrix(2, 1) = invS2 * mMatrix(1, 2); + mInvHMatrix(2, 2) = invS2 * mMatrix(2, 2); +#else + mInvHMatrix(0, 0) = mMatrix(0, 0) * invS0; + mInvHMatrix(0, 1) = mMatrix(1, 0) * invS1; + mInvHMatrix(0, 2) = mMatrix(2, 0) * invS2; + mInvHMatrix(1, 0) = mMatrix(0, 1) * invS0; + mInvHMatrix(1, 1) = mMatrix(1, 1) * invS1; + mInvHMatrix(1, 2) = mMatrix(2, 1) * invS2; + mInvHMatrix(2, 0) = mMatrix(0, 2) * invS0; + mInvHMatrix(2, 1) = mMatrix(1, 2) * invS1; + mInvHMatrix(2, 2) = mMatrix(2, 2) * invS2; +#endif + } + } + else + { + Invert3x3(mHMatrix, mInvHMatrix); + } + +#if defined(GTE_USE_MAT_VEC) + mInvHMatrix(0, 3) = -( + mInvHMatrix(0, 0) * mTranslate[0] + + mInvHMatrix(0, 1) * mTranslate[1] + + mInvHMatrix(0, 2) * mTranslate[2] + ); + + mInvHMatrix(1, 3) = -( + mInvHMatrix(1, 0) * mTranslate[0] + + mInvHMatrix(1, 1) * mTranslate[1] + + mInvHMatrix(1, 2) * mTranslate[2] + ); + + mInvHMatrix(2, 3) = -( + mInvHMatrix(2, 0) * mTranslate[0] + + mInvHMatrix(2, 1) * mTranslate[1] + + mInvHMatrix(2, 2) * mTranslate[2] + ); + + // The last row of mHMatrix is always (0,0,0,1) for an + // affine transformation, so it is set once in the + // constructor. It is not necessary to reset it here. +#else + mInvHMatrix(3, 0) = -( + mInvHMatrix(0, 0) * mTranslate[0] + + mInvHMatrix(1, 0) * mTranslate[1] + + mInvHMatrix(2, 0) * mTranslate[2] + ); + + mInvHMatrix(3, 1) = -( + mInvHMatrix(0, 1) * mTranslate[0] + + mInvHMatrix(1, 1) * mTranslate[1] + + mInvHMatrix(2, 1) * mTranslate[2] + ); + + mInvHMatrix(3, 2) = -( + mInvHMatrix(0, 2) * mTranslate[0] + + mInvHMatrix(1, 2) * mTranslate[1] + + mInvHMatrix(2, 2) * mTranslate[2] + ); + + // The last column of mHMatrix is always (0,0,0,1) for an + // affine transformation, so it is set once in the + // constructor. It is not necessary to reset it here. +#endif + } + + mInverseNeedsUpdate = false; + } + + return mInvHMatrix; + } + + // Invert the transform. If possible, the channels are properly + // assigned. For example, if the input has mIsRSMatrix equal to + // 'true', then the inverse also has mIsRSMatrix equal to 'true' + // and the inverse's mMatrix is a rotation matrix and mScale is + // set accordingly. + Transform Inverse() const + { + Transform inverse; + + if (mIsIdentity) + { + inverse.MakeIdentity(); + } + else + { + if (mIsRSMatrix && mIsUniformScale) + { + inverse.SetRotation(Transpose(GetRotation())); + inverse.SetUniformScale(1.0f / GetUniformScale()); + } + else + { + Matrix4x4 invUpper; + Invert3x3(GetMatrix(), invUpper); + inverse.SetMatrix(invUpper); + } + Vector4 trn = -GetTranslationW0(); + inverse.SetTranslation(inverse.GetMatrix() * trn); + } + + mInverseNeedsUpdate = true; + return inverse; + } + + // The identity transformation. + static Transform Identity() + { + static Transform identity; + return identity; + } + + private: + // Fill in the entries of mHMatrix whenever one of the components + // mMatrix, mTranslate, or mScale changes. + void UpdateHMatrix() + { + if (mIsIdentity) + { + mHMatrix.MakeIdentity(); + } + else + { + if (mIsRSMatrix) + { +#if defined(GTE_USE_MAT_VEC) + mHMatrix(0, 0) = mMatrix(0, 0) * mScale[0]; + mHMatrix(0, 1) = mMatrix(0, 1) * mScale[1]; + mHMatrix(0, 2) = mMatrix(0, 2) * mScale[2]; + mHMatrix(1, 0) = mMatrix(1, 0) * mScale[0]; + mHMatrix(1, 1) = mMatrix(1, 1) * mScale[1]; + mHMatrix(1, 2) = mMatrix(1, 2) * mScale[2]; + mHMatrix(2, 0) = mMatrix(2, 0) * mScale[0]; + mHMatrix(2, 1) = mMatrix(2, 1) * mScale[1]; + mHMatrix(2, 2) = mMatrix(2, 2) * mScale[2]; +#else + mHMatrix(0, 0) = mScale[0] * mMatrix(0, 0); + mHMatrix(0, 1) = mScale[0] * mMatrix(0, 1); + mHMatrix(0, 2) = mScale[0] * mMatrix(0, 2); + mHMatrix(1, 0) = mScale[1] * mMatrix(1, 0); + mHMatrix(1, 1) = mScale[1] * mMatrix(1, 1); + mHMatrix(1, 2) = mScale[1] * mMatrix(1, 2); + mHMatrix(2, 0) = mScale[2] * mMatrix(2, 0); + mHMatrix(2, 1) = mScale[2] * mMatrix(2, 1); + mHMatrix(2, 2) = mScale[2] * mMatrix(2, 2); +#endif + } + else + { + mHMatrix(0, 0) = mMatrix(0, 0); + mHMatrix(0, 1) = mMatrix(0, 1); + mHMatrix(0, 2) = mMatrix(0, 2); + mHMatrix(1, 0) = mMatrix(1, 0); + mHMatrix(1, 1) = mMatrix(1, 1); + mHMatrix(1, 2) = mMatrix(1, 2); + mHMatrix(2, 0) = mMatrix(2, 0); + mHMatrix(2, 1) = mMatrix(2, 1); + mHMatrix(2, 2) = mMatrix(2, 2); + } + +#if defined(GTE_USE_MAT_VEC) + mHMatrix(0, 3) = mTranslate[0]; + mHMatrix(1, 3) = mTranslate[1]; + mHMatrix(2, 3) = mTranslate[2]; + + // The last row of mHMatrix is always (0,0,0,1) for an affine + // transformation, so it is set once in the constructor. It + // is not necessary to reset it here. +#else + mHMatrix(3, 0) = mTranslate[0]; + mHMatrix(3, 1) = mTranslate[1]; + mHMatrix(3, 2) = mTranslate[2]; + + // The last column of mHMatrix is always (0,0,0,1) for an + // affine transformation, so it is set once in the + // constructor. It is not necessary to reset it here. +#endif + } + + mInverseNeedsUpdate = true; + } + + // Invert the 3x3 upper-left block of the input matrix. + static void Invert3x3(Matrix4x4 const& mat, Matrix4x4& invMat) + { + // Compute the adjoint of M (3x3). + invMat(0, 0) = mat(1, 1) * mat(2, 2) - mat(1, 2) * mat(2, 1); + invMat(0, 1) = mat(0, 2) * mat(2, 1) - mat(0, 1) * mat(2, 2); + invMat(0, 2) = mat(0, 1) * mat(1, 2) - mat(0, 2) * mat(1, 1); + invMat(0, 3) = 0.0f; + invMat(1, 0) = mat(1, 2) * mat(2, 0) - mat(1, 0) * mat(2, 2); + invMat(1, 1) = mat(0, 0) * mat(2, 2) - mat(0, 2) * mat(2, 0); + invMat(1, 2) = mat(0, 2) * mat(1, 0) - mat(0, 0) * mat(1, 2); + invMat(1, 3) = 0.0f; + invMat(2, 0) = mat(1, 0) * mat(2, 1) - mat(1, 1) * mat(2, 0); + invMat(2, 1) = mat(0, 1) * mat(2, 0) - mat(0, 0) * mat(2, 1); + invMat(2, 2) = mat(0, 0) * mat(1, 1) - mat(0, 1) * mat(1, 0); + invMat(2, 3) = 0.0f; + invMat(3, 0) = 0.0f; + invMat(3, 1) = 0.0f; + invMat(3, 2) = 0.0f; + invMat(3, 3) = 1.0f; + + // Compute the reciprocal of the determinant of M. + Real invDet = (Real)1 / ( + mat(0, 0) * invMat(0, 0) + + mat(0, 1) * invMat(1, 0) + + mat(0, 2) * invMat(2, 0) + ); + + // inverse(M) = adjoint(M)/determinant(M). + invMat(0, 0) *= invDet; + invMat(0, 1) *= invDet; + invMat(0, 2) *= invDet; + invMat(1, 0) *= invDet; + invMat(1, 1) *= invDet; + invMat(1, 2) *= invDet; + invMat(2, 0) *= invDet; + invMat(2, 1) *= invDet; + invMat(2, 2) *= invDet; + } + + // The full 4x4 homogeneous matrix H and its inverse H^{-1}, stored + // according to the conventions (see GetHInverse description). The + // inverse is computed only on demand. + Matrix4x4 mHMatrix; + mutable Matrix4x4 mInvHMatrix; + + Matrix4x4 mMatrix; // M (general) or R (rotation) + Vector4 mTranslate; // T + Vector4 mScale; // S + bool mIsIdentity, mIsRSMatrix, mIsUniformScale; + mutable bool mInverseNeedsUpdate; + }; + + // Compute M*V. + template + Vector4 operator*(Transform const& M, Vector4 const& V) + { + return M.GetHMatrix() * V; + } + + // Compute V^T*M. + template + Vector4 operator*(Vector4 const& V, Transform const& M) + { + return V * M.GetHMatrix(); + } + + // Compute A*B. + template + Transform operator*(Transform const& A, Transform const& B) + { + if (A.IsIdentity()) + { + return B; + } + + if (B.IsIdentity()) + { + return A; + } + + Transform product; + + if (A.IsRSMatrix() && B.IsRSMatrix()) + { +#if defined(GTE_USE_MAT_VEC) + if (A.IsUniformScale()) + { + product.SetRotation(A.GetRotation() * B.GetRotation()); + + product.SetTranslation(A.GetUniformScale() * ( + A.GetRotation() * B.GetTranslationW0()) + + A.GetTranslationW1()); + + if (B.IsUniformScale()) + { + product.SetUniformScale(A.GetUniformScale() * B.GetUniformScale()); + } + else + { + product.SetScale(A.GetUniformScale() * B.GetScale()); + } + + return product; + } +#else + if (B.IsUniformScale()) + { + product.SetRotation(A.GetRotation() * B.GetRotation()); + + product.SetTranslation(B.GetUniformScale() * ( + A.GetTranslationW0() * B.GetRotation()) + + B.GetTranslationW1()); + + if (A.IsUniformScale()) + { + product.SetUniformScale(A.GetUniformScale() * B.GetUniformScale()); + } + else + { + product.SetScale(A.GetScale() * B.GetUniformScale()); + } + + return product; + } +#endif + } + + // In all remaining cases, the matrix cannot be written as R*S*X+T. + Matrix4x4 matMA; + if (A.IsRSMatrix()) + { +#if defined(GTE_USE_MAT_VEC) + matMA = MultiplyMD(A.GetRotation(), A.GetScaleW1()); +#else + matMA = MultiplyDM(A.GetScaleW1(), A.GetRotation()); +#endif + } + else + { + matMA = A.GetMatrix(); + } + + Matrix4x4 matMB; + if (B.IsRSMatrix()) + { +#if defined(GTE_USE_MAT_VEC) + matMB = MultiplyMD(B.GetRotation(), B.GetScaleW1()); +#else + matMB = MultiplyDM(B.GetScaleW1(), B.GetRotation()); +#endif + } + else + { + matMB = B.GetMatrix(); + } + + product.SetMatrix(matMA * matMB); +#if defined(GTE_USE_MAT_VEC) + product.SetTranslation(matMA * B.GetTranslationW0() + + A.GetTranslationW1()); +#else + product.SetTranslation(A.GetTranslationW0() * matMB + + B.GetTranslationW1()); +#endif + return product; + } + + template + inline Matrix4x4 operator*(Matrix4x4 const& A, Transform const& B) + { + return A * B.GetHMatrix(); + } + + template + inline Matrix4x4 operator*(Transform const& A, Matrix4x4 const& B) + { + return A.GetHMatrix()* B; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Triangle.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Triangle.h new file mode 100644 index 0000000..e13749a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Triangle.h @@ -0,0 +1,84 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The triangle is represented as an array of three vertices. The dimension +// N must be 2 or larger. + +namespace WwiseGTE +{ + template + class Triangle + { + public: + // Construction and destruction. The default constructor sets + // the/ vertices to (0,..,0), (1,0,...,0) and (0,1,0,...,0). + Triangle() + : + v{ Vector::Zero(), Vector::Unit(0), Vector::Unit(1) } + { + } + + Triangle(Vector const& v0, Vector const& v1, Vector const& v2) + : + v{ v0, v1, v2 } + { + } + + + Triangle(std::array, 3> const& inV) + : + v(inV) + { + } + + // Public member access. + std::array, 3> v; + + public: + // Comparisons to support sorted containers. + bool operator==(Triangle const& triangle) const + { + return v == triangle.v; + } + + bool operator!=(Triangle const& triangle) const + { + return v != triangle.v; + } + + bool operator< (Triangle const& triangle) const + { + return v < triangle.v; + } + + bool operator<=(Triangle const& triangle) const + { + return v <= triangle.v; + } + + bool operator> (Triangle const& triangle) const + { + return v > triangle.v; + } + + bool operator>=(Triangle const& triangle) const + { + return v >= triangle.v; + } + }; + + // Template aliases for convenience. + template + using Triangle2 = Triangle<2, Real>; + + template + using Triangle3 = Triangle<3, Real>; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TriangleKey.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TriangleKey.h new file mode 100644 index 0000000..6a1f5ce --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TriangleKey.h @@ -0,0 +1,122 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// An ordered triangle has V[0] = min(v0, v1, v2). Choose +// (V[0], V[1], V[2]) to be a permutation of (v0, v1, v2) so that the final +// is one of (v0, v1, v2), (v1, v2, v0) or (v2,v0,v1). The idea is that if +// v0 corresponds to (1,0,0), v1 corresponds to (0,1,0), and v2 corresponds +// to (0,0,1), the ordering (v0, v1, v2) corresponds to the 3x3 identity +// matrix I; the rows are the specified 3-tuples. The permutation +// (V[0], V[1], V[2]) induces a permutation of the rows of the identity +// matrix to form a permutation matrix P with det(P) = 1 = det(I). +// +// An unordered triangle stores a permutation of (v0, v1, v2) so that +// V[0] < V[1] < V[2]. + +namespace WwiseGTE +{ + template + class TriangleKey : public FeatureKey<3, Ordered> + { + public: + TriangleKey() + { + this->V = { -1, -1, -1 }; + } + + // This constructor is specialized based on Ordered. + explicit TriangleKey(int v0, int v1, int v2) + { + Initialize(v0, v1, v2); + } + + private: + template + typename std::enable_if::type + Initialize(int v0, int v1, int v2) + { + if (v0 < v1) + { + if (v0 < v2) + { + // v0 is minimum + this->V[0] = v0; + this->V[1] = v1; + this->V[2] = v2; + } + else + { + // v2 is minimum + this->V[0] = v2; + this->V[1] = v0; + this->V[2] = v1; + } + } + else + { + if (v1 < v2) + { + // v1 is minimum + this->V[0] = v1; + this->V[1] = v2; + this->V[2] = v0; + } + else + { + // v2 is minimum + this->V[0] = v2; + this->V[1] = v0; + this->V[2] = v1; + } + } + } + + template + typename std::enable_if::type + Initialize(int v0, int v1, int v2) + { + if (v0 < v1) + { + if (v0 < v2) + { + // v0 is minimum + this->V[0] = v0; + this->V[1] = std::min(v1, v2); + this->V[2] = std::max(v1, v2); + } + else + { + // v2 is minimum + this->V[0] = v2; + this->V[1] = std::min(v0, v1); + this->V[2] = std::max(v0, v1); + } + } + else + { + if (v1 < v2) + { + // v1 is minimum + this->V[0] = v1; + this->V[1] = std::min(v2, v0); + this->V[2] = std::max(v2, v0); + } + else + { + // v2 is minimum + this->V[0] = v2; + this->V[1] = std::min(v0, v1); + this->V[2] = std::max(v0, v1); + } + } + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TriangulateCDT.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TriangulateCDT.h new file mode 100644 index 0000000..dda9b60 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TriangulateCDT.h @@ -0,0 +1,497 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2020.01.10 + +#pragma once + +#include +#include +#include +#include +#include + +// The triangulation is based on constrained Delaunay triangulations (CDT), +// which does not use divisions, so ComputeType may be chosen using BSNumber. +// The input constraints are relaxed compared to TriangulateEC; specifically, +// the inner polygons are allowed to share vertices with the outer polygons. +// The CDT produces a triangulation of the convex hull of the input, which +// includes triangles outside the top-level outer polygon and inside the +// inner polygons. Only the triangles relevant to the input are returned +// via the 'std::vector& triangles', but the other triangles are +// retained so that you can perform linear walks in search of points inside +// the original polygon (nested polygon, tree of nested polygons). This is +// useful, for example, when subsampling the polygon triangles for +// interpolation of function data specified at the vertices. A linear walk +// does not work for a mesh consisting only of the polygon triangles, but +// with the additional triangles, the walk can navigate through holes in +// the polygon to find the containing triangle of the specified point. + +namespace WwiseGTE +{ + template + class TriangulateCDT + { + public: + // The class is a functor to support triangulating multiple polygons + // that share vertices in a collection of points. The interpretation + // of 'numPoints' and 'points' is described before each operator() + // function. Preconditions are numPoints >= 3 and points is a nonnull + // pointer to an array of at least numPoints elements. If the + // preconditions are satisfied, then operator() functions will return + // 'true'; otherwise, they return 'false'. + TriangulateCDT(int numPoints, Vector2 const* points) + : + mNumPoints(numPoints), + mPoints(points) + { + LogAssert(mNumPoints >= 3 && mPoints != nullptr, "Invalid input."); + } + + TriangulateCDT(std::vector> const& points) + : + mNumPoints(static_cast(points.size())), + mPoints(points.data()) + { + LogAssert(mNumPoints >= 3 && mPoints != nullptr, "Invalid input."); + } + + // The triangles of the polygon triangulation. + inline std::vector> const& GetTriangles() const + { + return mTriangles; + } + + // The triangles inside the convex hull of the points but outside the + // triangulation. + inline std::vector> const& GetOutsideTriangles() const + { + return mOutsideTriangles; + } + + // The triangles of the convex hull of the inputs to the constructor. + inline std::vector> const& GetAllTriangles() const + { + return mAllTriangles; + } + + // The classification of whether a triangle is part of the + // triangulation or outside the triangulation. These may be used in + // conjunction with the array returned by GetAllTriangles(). + inline std::vector const& GetIsInside() const + { + return mIsInside; + } + + inline bool IsInside(int triIndex) const + { + if (0 <= triIndex && triIndex < static_cast(mIsInside.size())) + { + return mIsInside[triIndex]; + } + else + { + return false; + } + } + + inline bool IsOutside(int triIndex) const + { + if (0 <= triIndex && triIndex < static_cast(mIsInside.size())) + { + return !mIsInside[triIndex]; + } + else + { + return false; + } + } + + // The outer polygons have counterclockwise ordered vertices. The + // inner polygons have clockwise ordered vertices. + typedef std::vector Polygon; + + // The input 'points' represents an array of vertices for a simple + // polygon. The vertices are points[0] through points[numPoints-1] and + // are listed in counterclockwise order. + bool operator()() + { + if (mPoints) + { + auto tree = std::make_shared(); + tree->polygon.resize(mNumPoints); + for (int i = 0; i < mNumPoints; ++i) + { + tree->polygon[i] = i; + } + + return operator()(tree); + } + return false; + } + + // The input 'points' represents an array of vertices that contains + // the vertices of a simple polygon. + bool operator()(Polygon const& polygon) + { + if (mPoints) + { + auto tree = std::make_shared(); + tree->polygon = polygon; + + return operator()(tree); + } + return false; + } + + // The input 'points' is a shared array of vertices that contains the + // vertices for two simple polygons, an outer polygon and an inner + // polygon. The inner polygon must be strictly inside the outer + // polygon. + bool operator()(Polygon const& outer, Polygon const& inner) + { + if (mPoints) + { + auto otree = std::make_shared(); + otree->polygon = outer; + otree->child.resize(1); + + auto itree = std::make_shared(); + itree->polygon = inner; + otree->child[0] = itree; + + return operator()(otree); + } + return false; + } + + // The input 'points' is a shared array of vertices that contains the + // vertices for multiple simple polygons, an outer polygon and one or + // more inner polygons. The inner polygons must be nonoverlapping and + // strictly inside the outer polygon. + bool operator()(Polygon const& outer, std::vector const& inners) + { + if (mPoints) + { + auto otree = std::make_shared(); + otree->polygon = outer; + otree->child.resize(inners.size()); + + std::vector> itree(inners.size()); + for (size_t i = 0; i < inners.size(); ++i) + { + itree[i] = std::make_shared(); + itree[i]->polygon = inners[i]; + otree->child[i] = itree[i]; + } + + return operator()(otree); + } + return false; + } + + // A tree of nested polygons. The root node corresponds to an outer + // polygon. The children of the root correspond to inner polygons, + // which are nonoverlapping polygons strictly contained in the outer + // polygon. Each inner polygon may itself contain an outer polygon, + // thus leading to a hierarchy of polygons. The outer polygons have + // vertices listed in counterclockwise order. The inner polygons have + // vertices listed in clockwise order. + class Tree + { + public: + Polygon polygon; + std::vector> child; + }; + + // The input 'positions' is a shared array of vertices that contains + // the vertices for multiple simple polygons in a tree of polygons. + bool operator()(std::shared_ptr const& tree) + { + if (mPoints) + { + std::map, int> offsets; + int numPoints = GetNumPointsAndOffsets(tree, offsets); + std::vector> points(numPoints); + PackPoints(tree, points); + + if (TriangulatePacked(numPoints, &points[0], tree, offsets)) + { + int numTriangles = static_cast(mAllTriangles.size()); + for (int t = 0; t < numTriangles; ++t) + { + auto& tri = mAllTriangles[t]; + for (int j = 0; j < 3; ++j) + { + LookupIndex(tree, tri[j], offsets); + } + + if (mIsInside[t]) + { + mTriangles.push_back(tri); + } + else + { + mOutsideTriangles.push_back(tri); + } + } + return true; + } + } + + return false; + } + + private: + // Triangulate the points referenced by an operator(...) query. The + // mAllTriangles and mIsInside are populated by this function, but the + // indices of mAllTriangles are relative to the packed 'points'. + // After the call, the indices need to be mapped back to the original + // set provided by the input arrays to operator(...). The mTriangles + // and mOutsideTriangles are generated after the call by examining + // mAllTriangles and mIsInside. + bool TriangulatePacked(int numPoints, Vector2 const* points, + std::shared_ptr const& tree, + std::map, int> const& offsets) + { + mTriangles.clear(); + mOutsideTriangles.clear(); + mAllTriangles.clear(); + mIsInside.clear(); + + mConstrainedDelaunay(numPoints, points, static_cast(0)); + InsertEdges(tree); + + ComputeType half = static_cast(0.5); + ComputeType fourth = static_cast(0.25); + auto const& query = mConstrainedDelaunay.GetQuery(); + auto const* ctPoints = query.GetVertices(); + int numTriangles = mConstrainedDelaunay.GetNumTriangles(); + int const* indices = &mConstrainedDelaunay.GetIndices()[0]; + mIsInside.resize(numTriangles); + for (int t = 0; t < numTriangles; ++t) + { + int v0 = *indices++; + int v1 = *indices++; + int v2 = *indices++; + auto ctInside = fourth * ctPoints[v0] + half * ctPoints[v1] + fourth * ctPoints[v2]; + mIsInside[t] = IsInside(tree, ctPoints, ctInside, offsets); + mAllTriangles.push_back( { v0, v1, v2 } ); + } + return true; + } + + int GetNumPointsAndOffsets(std::shared_ptr const& tree, + std::map, int>& offsets) const + { + int numPoints = 0; + std::queue> treeQueue; + treeQueue.push(tree); + while (treeQueue.size() > 0) + { + std::shared_ptr outer = treeQueue.front(); + treeQueue.pop(); + offsets.insert(std::make_pair(outer, numPoints)); + numPoints += static_cast(outer->polygon.size()); + + int numChildren = static_cast(outer->child.size()); + for (int c = 0; c < numChildren; ++c) + { + std::shared_ptr inner = outer->child[c]; + offsets.insert(std::make_pair(inner, numPoints)); + numPoints += static_cast(inner->polygon.size()); + + int numGrandChildren = static_cast(inner->child.size()); + for (int g = 0; g < numGrandChildren; ++g) + { + treeQueue.push(inner->child[g]); + } + } + } + return numPoints; + } + + void PackPoints(std::shared_ptr const& tree, + std::vector>& points) + { + int numPoints = 0; + std::queue> treeQueue; + treeQueue.push(tree); + while (treeQueue.size() > 0) + { + std::shared_ptr outer = treeQueue.front(); + treeQueue.pop(); + int const numOuterIndices = static_cast(outer->polygon.size()); + int const* outerIndices = outer->polygon.data(); + for (int i = 0; i < numOuterIndices; ++i) + { + points[numPoints++] = mPoints[outerIndices[i]]; + } + + int numChildren = static_cast(outer->child.size()); + for (int c = 0; c < numChildren; ++c) + { + std::shared_ptr inner = outer->child[c]; + int const numInnerIndices = static_cast(inner->polygon.size()); + int const* innerIndices = inner->polygon.data(); + for (int i = 0; i < numInnerIndices; ++i) + { + points[numPoints++] = mPoints[innerIndices[i]]; + } + + int numGrandChildren = static_cast(inner->child.size()); + for (int g = 0; g < numGrandChildren; ++g) + { + treeQueue.push(inner->child[g]); + } + } + } + } + + bool InsertEdges(std::shared_ptr const& tree) + { + int numPoints = 0; + std::array edge; + std::vector ignoreOutEdge; + std::queue> treeQueue; + treeQueue.push(tree); + while (treeQueue.size() > 0) + { + auto outer = treeQueue.front(); + treeQueue.pop(); + int numOuter = static_cast(outer->polygon.size()); + for (int i0 = numOuter - 1, i1 = 0; i1 < numOuter; i0 = i1++) + { + edge[0] = numPoints + i0; + edge[1] = numPoints + i1; + if (!mConstrainedDelaunay.Insert(edge, ignoreOutEdge)) + { + return false; + } + } + numPoints += numOuter; + + int numChildren = static_cast(outer->child.size()); + for (int c = 0; c < numChildren; ++c) + { + auto inner = outer->child[c]; + int numInner = static_cast(inner->polygon.size()); + for (int i0 = numInner - 1, i1 = 0; i1 < numInner; i0 = i1++) + { + edge[0] = numPoints + i0; + edge[1] = numPoints + i1; + if (!mConstrainedDelaunay.Insert(edge, ignoreOutEdge)) + { + return false; + } + } + numPoints += numInner; + + int numGrandChildren = static_cast(inner->child.size()); + for (int g = 0; g < numGrandChildren; ++g) + { + treeQueue.push(inner->child[g]); + } + } + } + return true; + } + + void LookupIndex(std::shared_ptr const& tree, int& v, + std::map, int> const& offsets) const + { + std::queue> treeQueue; + treeQueue.push(tree); + while (treeQueue.size() > 0) + { + auto outer = treeQueue.front(); + treeQueue.pop(); + int const numOuterIndices = static_cast(outer->polygon.size()); + int const* outerIndices = outer->polygon.data(); + int offset = offsets.find(outer)->second; + if (v < offset + numOuterIndices) + { + v = outerIndices[v - offset]; + return; + } + + int numChildren = static_cast(outer->child.size()); + for (int c = 0; c < numChildren; ++c) + { + auto inner = outer->child[c]; + int const numInnerIndices = static_cast(inner->polygon.size()); + int const* innerIndices = inner->polygon.data(); + offset = offsets.find(inner)->second; + if (v < offset + numInnerIndices) + { + v = innerIndices[v - offset]; + return; + } + + int numGrandChildren = static_cast(inner->child.size()); + for (int g = 0; g < numGrandChildren; ++g) + { + treeQueue.push(inner->child[g]); + } + } + } + } + + bool IsInside(std::shared_ptr const& tree, Vector2 const* points, + Vector2 const& test, + std::map, int> const& offsets) const + { + std::queue> treeQueue; + treeQueue.push(tree); + while (treeQueue.size() > 0) + { + auto outer = treeQueue.front(); + treeQueue.pop(); + int const numOuterIndices = static_cast(outer->polygon.size()); + int offset = offsets.find(outer)->second; + PointInPolygon2 piOuter(numOuterIndices, points + offset); + if (piOuter.Contains(test)) + { + int numChildren = static_cast(outer->child.size()); + int c; + for (c = 0; c < numChildren; ++c) + { + auto inner = outer->child[c]; + int const numInnerIndices = static_cast(inner->polygon.size()); + offset = offsets.find(inner)->second; + PointInPolygon2 piInner(numInnerIndices, points + offset); + if (piInner.Contains(test)) + { + int numGrandChildren = static_cast(inner->child.size()); + for (int g = 0; g < numGrandChildren; ++g) + { + treeQueue.push(inner->child[g]); + } + break; + } + } + if (c == numChildren) + { + return true; + } + } + } + return false; + } + + // The input polygon. + int mNumPoints; + Vector2 const* mPoints; + + // The output triangulation and those triangle inside the hull of the + // points but outside the triangulation. + std::vector> mTriangles; + std::vector> mOutsideTriangles; + std::vector> mAllTriangles; + std::vector mIsInside; + + ConstrainedDelaunay2 mConstrainedDelaunay; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TriangulateEC.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TriangulateEC.h new file mode 100644 index 0000000..c9bf2ff --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TriangulateEC.h @@ -0,0 +1,1239 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2020.01.10 + +#pragma once + +#include +#include +#include +#include +#include +#include + +// Triangulate polygons using ear clipping. The algorithm is described in +// https://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf +// The algorithm for processing nested polygons involves a division, so the +// ComputeType must be rational-based, say, BSRational. If you process only +// triangles that are simple, you may use BSNumber for the ComputeType. + +namespace WwiseGTE +{ + template + class TriangulateEC + { + public: + // The class is a functor to support triangulating multiple polygons + // that share vertices in a collection of points. The interpretation + // of 'numPoints' and 'points' is described before each operator() + // function. Preconditions are numPoints >= 3 and points is a nonnull + // pointer to an array of at least numPoints elements. If the + // preconditions are satisfied, then operator() functions will return + // 'true'; otherwise, they return 'false'. + TriangulateEC(int numPoints, Vector2 const* points) + : + mNumPoints(numPoints), + mPoints(points) + { + LogAssert(numPoints >= 3 && points != nullptr, "Invalid input."); + mComputePoints.resize(mNumPoints); + mIsConverted.resize(mNumPoints); + std::fill(mIsConverted.begin(), mIsConverted.end(), false); + mQuery.Set(mNumPoints, &mComputePoints[0]); + } + + TriangulateEC(std::vector> const& points) + : + mNumPoints(static_cast(points.size())), + mPoints(points.data()) + { + LogAssert(mNumPoints >= 3 && mPoints != nullptr, "Invalid input."); + mComputePoints.resize(mNumPoints); + mIsConverted.resize(mNumPoints); + std::fill(mIsConverted.begin(), mIsConverted.end(), false); + mQuery.Set(mNumPoints, &mComputePoints[0]); + } + + // Access the triangulation after each operator() call. + inline std::vector> const& GetTriangles() const + { + return mTriangles; + } + + // The outer polygons have counterclockwise ordered vertices. The + // inner polygons have clockwise ordered vertices. + typedef std::vector Polygon; + + // The input 'points' represents an array of vertices for a simple + // polygon. The vertices are points[0] through points[numPoints-1] and + // are listed in counterclockwise order. + bool operator()() + { + mTriangles.clear(); + if (mPoints) + { + // Compute the points for the queries. + for (int i = 0; i < mNumPoints; ++i) + { + if (!mIsConverted[i]) + { + mIsConverted[i] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[i][j] = mPoints[i][j]; + } + } + } + + // Triangulate the unindexed polygon. + InitializeVertices(mNumPoints, nullptr); + DoEarClipping(mNumPoints, nullptr); + return true; + } + else + { + return false; + } + } + + // The input 'points' represents an array of vertices that contains + // the vertices of a simple polygon. + bool operator()(Polygon const& polygon) + { + mTriangles.clear(); + if (mPoints) + { + // Compute the points for the queries. + int const numIndices = static_cast(polygon.size()); + int const* indices = polygon.data(); + for (int i = 0; i < numIndices; ++i) + { + int index = indices[i]; + if (!mIsConverted[index]) + { + mIsConverted[index] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[index][j] = mPoints[index][j]; + } + } + } + + // Triangulate the indexed polygon. + InitializeVertices(numIndices, indices); + DoEarClipping(numIndices, indices); + return true; + } + else + { + return false; + } + } + + // The input 'points' is a shared array of vertices that contains the + // vertices for two simple polygons, an outer polygon and an inner + // polygon. The inner polygon must be strictly inside the outer + // polygon. + bool operator()(Polygon const& outer, Polygon const& inner) + { + mTriangles.clear(); + if (mPoints) + { + // Two extra elements are needed to duplicate the endpoints of + // the edge introduced to combine outer and inner polygons. + int numPointsPlusExtras = mNumPoints + 2; + if (numPointsPlusExtras > static_cast(mComputePoints.size())) + { + mComputePoints.resize(numPointsPlusExtras); + mIsConverted.resize(numPointsPlusExtras); + mIsConverted[mNumPoints] = false; + mIsConverted[mNumPoints + 1] = false; + mQuery.Set(numPointsPlusExtras, &mComputePoints[0]); + } + + // Convert any points that have not been encountered in other + // triangulation calls. + int const numOuterIndices = static_cast(outer.size()); + int const* outerIndices = outer.data(); + for (int i = 0; i < numOuterIndices; ++i) + { + int index = outerIndices[i]; + if (!mIsConverted[index]) + { + mIsConverted[index] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[index][j] = mPoints[index][j]; + } + } + } + + int const numInnerIndices = static_cast(inner.size()); + int const* innerIndices = inner.data(); + for (int i = 0; i < numInnerIndices; ++i) + { + int index = innerIndices[i]; + if (!mIsConverted[index]) + { + mIsConverted[index] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[index][j] = mPoints[index][j]; + } + } + } + + // Combine the outer polygon and the inner polygon into a + // simple polygon by inserting two edges connecting mutually + // visible vertices, one from the outer polygon and one from + // the inner polygon. + int nextElement = mNumPoints; // The next available element. + std::map indexMap; + std::vector combined; + if (!CombinePolygons(nextElement, outer, inner, indexMap, combined)) + { + // An unexpected condition was encountered. + return false; + } + + // The combined polygon is now in the format of a simple + // polygon, albeit one with coincident edges. + int numVertices = static_cast(combined.size()); + int* const indices = &combined[0]; + InitializeVertices(numVertices, indices); + DoEarClipping(numVertices, indices); + + // Map the duplicate indices back to the original indices. + RemapIndices(indexMap); + return true; + } + else + { + return false; + } + } + + // The input 'points' is a shared array of vertices that contains the + // vertices for multiple simple polygons, an outer polygon and one or + // more inner polygons. The inner polygons must be nonoverlapping and + // strictly inside the outer polygon. + bool operator()(Polygon const& outer, std::vector const& inners) + { + mTriangles.clear(); + if (mPoints) + { + // Two extra elements per inner polygon are needed to + // duplicate the endpoints of the edges introduced to combine + // outer and inner polygons. + int numPointsPlusExtras = mNumPoints + 2 * (int)inners.size(); + if (numPointsPlusExtras > static_cast(mComputePoints.size())) + { + mComputePoints.resize(numPointsPlusExtras); + mIsConverted.resize(numPointsPlusExtras); + for (int i = mNumPoints; i < numPointsPlusExtras; ++i) + { + mIsConverted[i] = false; + } + mQuery.Set(numPointsPlusExtras, &mComputePoints[0]); + } + + // Convert any points that have not been encountered in other + // triangulation calls. + int const numOuterIndices = static_cast(outer.size()); + int const* outerIndices = outer.data(); + for (int i = 0; i < numOuterIndices; ++i) + { + int index = outerIndices[i]; + if (!mIsConverted[index]) + { + mIsConverted[index] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[index][j] = mPoints[index][j]; + } + } + } + + for (auto const& inner : inners) + { + int const numInnerIndices = static_cast(inner.size()); + int const* innerIndices = inner.data(); + for (int i = 0; i < numInnerIndices; ++i) + { + int index = innerIndices[i]; + if (!mIsConverted[index]) + { + mIsConverted[index] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[index][j] = mPoints[index][j]; + } + } + } + } + + // Combine the outer polygon and the inner polygons into a + // simple polygon by inserting two edges per inner polygon + // connecting mutually visible vertices. + int nextElement = mNumPoints; // The next available element. + std::map indexMap; + std::vector combined; + if (!ProcessOuterAndInners(nextElement, outer, inners, indexMap, combined)) + { + // An unexpected condition was encountered. + return false; + } + + // The combined polygon is now in the format of a simple + // polygon, albeit with coincident edges. + int numVertices = static_cast(combined.size()); + int* const indices = &combined[0]; + InitializeVertices(numVertices, indices); + DoEarClipping(numVertices, indices); + + // Map the duplicate indices back to the original indices. + RemapIndices(indexMap); + return true; + } + else + { + return false; + } + } + + // A tree of nested polygons. The root node corresponds to an outer + // polygon. The children of the root correspond to inner polygons, + // which are nonoverlapping polygons strictly contained in the outer + // polygon. Each inner polygon may itself contain an outer polygon, + // thus leading to a hierarchy of polygons. The outer polygons have + // vertices listed in counterclockwise order. The inner polygons have + // vertices listed in clockwise order. + class Tree + { + public: + Polygon polygon; + std::vector> child; + }; + + // The input 'positions' is a shared array of vertices that contains + // the vertices for multiple simple polygons in a tree of polygons. + bool operator()(std::shared_ptr const& tree) + { + mTriangles.clear(); + if (mPoints) + { + // Two extra elements per inner polygon are needed to + // duplicate the endpoints of the edges introduced to combine + // outer and inner polygons. + int numPointsPlusExtras = mNumPoints + InitializeFromTree(tree); + if (numPointsPlusExtras > static_cast(mComputePoints.size())) + { + mComputePoints.resize(numPointsPlusExtras); + mIsConverted.resize(numPointsPlusExtras); + for (int i = mNumPoints; i < numPointsPlusExtras; ++i) + { + mIsConverted[i] = false; + } + mQuery.Set(numPointsPlusExtras, &mComputePoints[0]); + } + + int nextElement = mNumPoints; + std::map indexMap; + + std::queue> treeQueue; + treeQueue.push(tree); + while (treeQueue.size() > 0) + { + std::shared_ptr outer = treeQueue.front(); + treeQueue.pop(); + + int numChildren = static_cast(outer->child.size()); + int numVertices; + int const* indices; + + if (numChildren == 0) + { + // The outer polygon is a simple polygon (no nested + // inner polygons). Triangulate the simple polygon. + numVertices = static_cast(outer->polygon.size()); + indices = outer->polygon.data(); + InitializeVertices(numVertices, indices); + DoEarClipping(numVertices, indices); + } + else + { + // Place the next level of outer polygon nodes on the + // queue for triangulation. + std::vector inners(numChildren); + for (int c = 0; c < numChildren; ++c) + { + std::shared_ptr inner = outer->child[c]; + inners[c] = inner->polygon; + int numGrandChildren = static_cast(inner->child.size()); + for (int g = 0; g < numGrandChildren; ++g) + { + treeQueue.push(inner->child[g]); + } + } + + // Combine the outer polygon and the inner polygons + // into a simple polygon by inserting two edges per + // inner polygon connecting mutually visible vertices. + std::vector combined; + ProcessOuterAndInners(nextElement, outer->polygon, inners, indexMap, combined); + + // The combined polygon is now in the format of a + // simple polygon, albeit with coincident edges. + numVertices = static_cast(combined.size()); + indices = &combined[0]; + InitializeVertices(numVertices, indices); + DoEarClipping(numVertices, indices); + } + } + + // Map the duplicate indices back to the original indices. + RemapIndices(indexMap); + return true; + } + else + { + return false; + } + } + + private: + // Create the vertex objects that store the various lists required by + // the ear-clipping algorithm. + void InitializeVertices(int numVertices, int const* indices) + { + mVertices.clear(); + mVertices.resize(numVertices); + mCFirst = -1; + mCLast = -1; + mRFirst = -1; + mRLast = -1; + mEFirst = -1; + mELast = -1; + + // Create a circular list of the polygon vertices for dynamic + // removal of vertices. + int numVerticesM1 = numVertices - 1; + for (int i = 0; i <= numVerticesM1; ++i) + { + Vertex& vertex = V(i); + vertex.index = (indices ? indices[i] : i); + vertex.vPrev = (i > 0 ? i - 1 : numVerticesM1); + vertex.vNext = (i < numVerticesM1 ? i + 1 : 0); + } + + // Create a circular list of the polygon vertices for dynamic + // removal of vertices. Keep track of two linear sublists, one + // for the convex vertices and one for the reflex vertices. + // This is an O(N) process where N is the number of polygon + // vertices. + for (int i = 0; i <= numVerticesM1; ++i) + { + if (IsConvex(i)) + { + InsertAfterC(i); + } + else + { + InsertAfterR(i); + } + } + } + + // Apply ear clipping to the input polygon. Polygons with holes are + // preprocessed to obtain an index array that is nearly a simple + // polygon. This outer polygon has a pair of coincident edges per + // inner polygon. + void DoEarClipping(int numVertices, int const* indices) + { + // If the polygon is convex, just create a triangle fan. + if (mRFirst == -1) + { + int numVerticesM1 = numVertices - 1; + if (indices) + { + for (int i = 1; i < numVerticesM1; ++i) + { + mTriangles.push_back( { indices[0], indices[i], indices[i + 1] } ); + } + } + else + { + for (int i = 1; i < numVerticesM1; ++i) + { + mTriangles.push_back( { 0, i, i + 1 } ); + } + } + return; + } + + // Identify the ears and build a circular list of them. Let V0, + // V1, and V2 be consecutive vertices forming a triangle T. The + // vertex V1 is an ear if no other vertices of the polygon lie + // inside T. Although it is enough to show that V1 is not an ear + // by finding at least one other vertex inside T, it is sufficient + // to search only the reflex vertices. This is an O(C*R) process, + // where C is the number of convex vertices and R is the number of + // reflex vertices with N = C+R. The order is O(N^2), for example + // when C = R = N/2. + for (int i = mCFirst; i != -1; i = V(i).sNext) + { + if (IsEar(i)) + { + InsertEndE(i); + } + } + V(mEFirst).ePrev = mELast; + V(mELast).eNext = mEFirst; + + // Remove the ears, one at a time. + bool bRemoveAnEar = true; + while (bRemoveAnEar) + { + // Add the triangle with the ear to the output list of + // triangles. + int iVPrev = V(mEFirst).vPrev; + int iVNext = V(mEFirst).vNext; + mTriangles.push_back( { V(iVPrev).index, V(mEFirst).index, V(iVNext).index } ); + + // Remove the vertex corresponding to the ear. + RemoveV(mEFirst); + if (--numVertices == 3) + { + // Only one triangle remains, just remove the ear and + // copy it. + mEFirst = RemoveE(mEFirst); + iVPrev = V(mEFirst).vPrev; + iVNext = V(mEFirst).vNext; + mTriangles.push_back( { V(iVPrev).index, V(mEFirst).index, V(iVNext).index } ); + bRemoveAnEar = false; + continue; + } + + // Removal of the ear can cause an adjacent vertex to become + // an ear or to stop being an ear. + Vertex& vPrev = V(iVPrev); + if (vPrev.isEar) + { + if (!IsEar(iVPrev)) + { + RemoveE(iVPrev); + } + } + else + { + bool wasReflex = !vPrev.isConvex; + if (IsConvex(iVPrev)) + { + if (wasReflex) + { + RemoveR(iVPrev); + } + + if (IsEar(iVPrev)) + { + InsertBeforeE(iVPrev); + } + } + } + + Vertex& vNext = V(iVNext); + if (vNext.isEar) + { + if (!IsEar(iVNext)) + { + RemoveE(iVNext); + } + } + else + { + bool wasReflex = !vNext.isConvex; + if (IsConvex(iVNext)) + { + if (wasReflex) + { + RemoveR(iVNext); + } + + if (IsEar(iVNext)) + { + InsertAfterE(iVNext); + } + } + } + + // Remove the ear. + mEFirst = RemoveE(mEFirst); + } + } + + // Given an outer polygon that contains an inner polygon, this + // function determines a pair of visible vertices and inserts two + // coincident edges to generate a nearly simple polygon. + bool CombinePolygons(int nextElement, Polygon const& outer, + Polygon const& inner, std::map& indexMap, + std::vector& combined) + { + int const numOuterIndices = static_cast(outer.size()); + int const* outerIndices = outer.data(); + int const numInnerIndices = static_cast(inner.size()); + int const* innerIndices = inner.data(); + + // Locate the inner-polygon vertex of maximum x-value, call this + // vertex M. + ComputeType xmax = mComputePoints[innerIndices[0]][0]; + int xmaxIndex = 0; + for (int i = 1; i < numInnerIndices; ++i) + { + ComputeType x = mComputePoints[innerIndices[i]][0]; + if (x > xmax) + { + xmax = x; + xmaxIndex = i; + } + } + Vector2 M = mComputePoints[innerIndices[xmaxIndex]]; + + // Find the edge whose intersection Intr with the ray M+t*(1,0) + // minimizes + // the ray parameter t >= 0. + ComputeType const cmax = static_cast(std::numeric_limits::max()); + ComputeType const zero = static_cast(0); + Vector2 intr{ cmax, M[1] }; + int v0min = -1, v1min = -1, endMin = -1; + int i0, i1; + ComputeType s = cmax; + ComputeType t = cmax; + for (i0 = numOuterIndices - 1, i1 = 0; i1 < numOuterIndices; i0 = i1++) + { + // Consider only edges for which the first vertex is below + // (or on) the ray and the second vertex is above (or on) + // the ray. + Vector2 diff0 = mComputePoints[outerIndices[i0]] - M; + if (diff0[1] > zero) + { + continue; + } + Vector2 diff1 = mComputePoints[outerIndices[i1]] - M; + if (diff1[1] < zero) + { + continue; + } + + // At this time, diff0.y <= 0 and diff1.y >= 0. + int currentEndMin = -1; + if (diff0[1] < zero) + { + if (diff1[1] > zero) + { + // The intersection of the edge and ray occurs at an + // interior edge point. + s = diff0[1] / (diff0[1] - diff1[1]); + t = diff0[0] + s * (diff1[0] - diff0[0]); + } + else // diff1.y == 0 + { + // The vertex Outer[i1] is the intersection of the + // edge and the ray. + t = diff1[0]; + currentEndMin = i1; + } + } + else // diff0.y == 0 + { + if (diff1[1] > zero) + { + // The vertex Outer[i0] is the intersection of the + // edge and the ray; + t = diff0[0]; + currentEndMin = i0; + } + else // diff1.y == 0 + { + if (diff0[0] < diff1[0]) + { + t = diff0[0]; + currentEndMin = i0; + } + else + { + t = diff1[0]; + currentEndMin = i1; + } + } + } + + if (zero <= t && t < intr[0]) + { + intr[0] = t; + v0min = i0; + v1min = i1; + if (currentEndMin == -1) + { + // The current closest point is an edge-interior + // point. + endMin = -1; + } + else + { + // The current closest point is a vertex. + endMin = currentEndMin; + } + } + else if (t == intr[0]) + { + // The current closest point is a vertex shared by + // multiple edges; thus, endMin and currentMin refer to + // the same point. + LogAssert(endMin != -1 && currentEndMin != -1, "Unexpected condition."); + + // We need to select the edge closest to M. The previous + // closest edge is . The + // current candidate is . + Vector2 shared = mComputePoints[outerIndices[i1]]; + + // For the previous closest edge, endMin refers to a + // vertex of the edge. Get the index of the other vertex. + int other = (endMin == v0min ? v1min : v0min); + + // The new edge is closer if the other vertex of the old + // edge is left-of the new edge. + diff0 = mComputePoints[outerIndices[i0]] - shared; + diff1 = mComputePoints[outerIndices[other]] - shared; + ComputeType dotperp = DotPerp(diff0, diff1); + if (dotperp > zero) + { + // The new edge is closer to M. + v0min = i0; + v1min = i1; + endMin = currentEndMin; + } + } + } + + // The intersection intr[0] stored only the t-value of the ray. + // The actual point is (mx,my)+t*(1,0), so intr[0] must be + // adjusted. + intr[0] += M[0]; + + int maxCosIndex; + if (endMin == -1) + { + // If you reach this assert, there is a good chance that you + // have two inner polygons that share a vertex or an edge. + LogAssert(v0min >= 0 && v1min >= 0, "Is this an invalid nested polygon?"); + + // Select one of Outer[v0min] and Outer[v1min] that has an + // x-value larger than M.x, call this vertex P. The triangle + // must contain an outer-polygon vertex that is + // visible to M, which is possibly P itself. + Vector2 sTriangle[3]; // or + int pIndex; + if (mComputePoints[outerIndices[v0min]][0] > mComputePoints[outerIndices[v1min]][0]) + { + sTriangle[0] = mComputePoints[outerIndices[v0min]]; + sTriangle[1] = intr; + sTriangle[2] = M; + pIndex = v0min; + } + else + { + sTriangle[0] = mComputePoints[outerIndices[v1min]]; + sTriangle[1] = M; + sTriangle[2] = intr; + pIndex = v1min; + } + + // If any outer-polygon vertices other than P are inside the + // triangle , then at least one of these vertices must + // be a reflex vertex. It is sufficient to locate the reflex + // vertex R (if any) in that minimizes the angle + // between R-M and (1,0). The data member mQuery is used for + // the reflex query. + Vector2 diff = sTriangle[0] - M; + ComputeType maxSqrLen = Dot(diff, diff); + ComputeType maxCos = diff[0] * diff[0] / maxSqrLen; + PrimalQuery2 localQuery(3, sTriangle); + maxCosIndex = pIndex; + for (int i = 0; i < numOuterIndices; ++i) + { + if (i == pIndex) + { + continue; + } + + int curr = outerIndices[i]; + int prev = outerIndices[(i + numOuterIndices - 1) % numOuterIndices]; + int next = outerIndices[(i + 1) % numOuterIndices]; + if (mQuery.ToLine(curr, prev, next) <= 0 + && localQuery.ToTriangle(mComputePoints[curr], 0, 1, 2) <= 0) + { + // The vertex is reflex and inside the + // triangle. + diff = mComputePoints[curr] - M; + ComputeType sqrLen = Dot(diff, diff); + ComputeType cs = diff[0] * diff[0] / sqrLen; + if (cs > maxCos) + { + // The reflex vertex forms a smaller angle with + // the positive x-axis, so it becomes the new + // visible candidate. + maxSqrLen = sqrLen; + maxCos = cs; + maxCosIndex = i; + } + else if (cs == maxCos && sqrLen < maxSqrLen) + { + // The reflex vertex has angle equal to the + // current minimum but the length is smaller, so + // it becomes the new visible candidate. + maxSqrLen = sqrLen; + maxCosIndex = i; + } + } + } + } + else + { + maxCosIndex = endMin; + } + + // The visible vertices are Position[Inner[xmaxIndex]] and + // Position[Outer[maxCosIndex]]. Two coincident edges with + // these endpoints are inserted to connect the outer and inner + // polygons into a simple polygon. Each of the two Position[] + // values must be duplicated, because the original might be + // convex (or reflex) and the duplicate is reflex (or convex). + // The ear-clipping algorithm needs to distinguish between them. + combined.resize(numOuterIndices + numInnerIndices + 2); + int cIndex = 0; + for (int i = 0; i <= maxCosIndex; ++i, ++cIndex) + { + combined[cIndex] = outerIndices[i]; + } + + for (int i = 0; i < numInnerIndices; ++i, ++cIndex) + { + int j = (xmaxIndex + i) % numInnerIndices; + combined[cIndex] = innerIndices[j]; + } + + int innerIndex = innerIndices[xmaxIndex]; + mComputePoints[nextElement] = mComputePoints[innerIndex]; + combined[cIndex] = nextElement; + auto iter = indexMap.find(innerIndex); + if (iter != indexMap.end()) + { + innerIndex = iter->second; + } + indexMap[nextElement] = innerIndex; + ++cIndex; + ++nextElement; + + int outerIndex = outerIndices[maxCosIndex]; + mComputePoints[nextElement] = mComputePoints[outerIndex]; + combined[cIndex] = nextElement; + iter = indexMap.find(outerIndex); + if (iter != indexMap.end()) + { + outerIndex = iter->second; + } + indexMap[nextElement] = outerIndex; + ++cIndex; + ++nextElement; + + for (int i = maxCosIndex + 1; i < numOuterIndices; ++i, ++cIndex) + { + combined[cIndex] = outerIndices[i]; + } + return true; + } + + // Given an outer polygon that contains a set of nonoverlapping inner + // polygons, this function determines pairs of visible vertices and + // inserts coincident edges to generate a nearly simple polygon. It + // repeatedly calls CombinePolygons for each inner polygon of the + // outer polygon. + bool ProcessOuterAndInners(int& nextElement, Polygon const& outer, + std::vector const& inners, std::map& indexMap, + std::vector& combined) + { + // Sort the inner polygons based on maximum x-values. + int numInners = static_cast(inners.size()); + std::vector> pairs(numInners); + for (int p = 0; p < numInners; ++p) + { + int numIndices = static_cast(inners[p].size()); + int const* indices = inners[p].data(); + ComputeType xmax = mComputePoints[indices[0]][0]; + for (int j = 1; j < numIndices; ++j) + { + ComputeType x = mComputePoints[indices[j]][0]; + if (x > xmax) + { + xmax = x; + } + } + pairs[p].first = xmax; + pairs[p].second = p; + } + std::sort(pairs.begin(), pairs.end()); + + // Merge the inner polygons with the outer polygon. + Polygon currentPolygon = outer; + for (int p = numInners - 1; p >= 0; --p) + { + Polygon const& polygon = inners[pairs[p].second]; + Polygon currentCombined; + if (!CombinePolygons(nextElement, currentPolygon, polygon, indexMap, currentCombined)) + { + return false; + } + currentPolygon = std::move(currentCombined); + nextElement += 2; + } + + for (auto index : currentPolygon) + { + combined.push_back(index); + } + return true; + } + + // The insertion of coincident edges to obtain a nearly simple polygon + // requires duplication of vertices in order that the ear-clipping + // algorithm work correctly. After the triangulation, the indices of + // the duplicated vertices are converted to the original indices. + void RemapIndices(std::map const& indexMap) + { + // The triangulation includes indices to the duplicated outer and + // inner vertices. These indices must be mapped back to the + // original ones. + for (auto& tri : mTriangles) + { + for (int i = 0; i < 3; ++i) + { + auto iter = indexMap.find(tri[i]); + if (iter != indexMap.end()) + { + tri[i] = iter->second; + } + } + } + } + + // Two extra elements are needed in the position array per + // outer-inners polygon. This function computes the total number of + // extra elements needed for the input tree and it converts InputType + // vertices to ComputeType values. + int InitializeFromTree(std::shared_ptr const& tree) + { + // Use a breadth-first search to process the outer-inners pairs + // of the tree of nested polygons. + int numExtraPoints = 0; + + std::queue> treeQueue; + treeQueue.push(tree); + while (treeQueue.size() > 0) + { + // The 'root' is an outer polygon. + std::shared_ptr outer = treeQueue.front(); + treeQueue.pop(); + + // Count number of extra points for this outer-inners pair. + int numChildren = static_cast(outer->child.size()); + numExtraPoints += 2 * numChildren; + + // Convert outer points from InputType to ComputeType. + int const numOuterIndices = static_cast(outer->polygon.size()); + int const* outerIndices = outer->polygon.data(); + for (int i = 0; i < numOuterIndices; ++i) + { + int index = outerIndices[i]; + if (!mIsConverted[index]) + { + mIsConverted[index] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[index][j] = mPoints[index][j]; + } + } + } + + // The grandchildren of the outer polygon are also outer + // polygons. Insert them into the queue for processing. + for (int c = 0; c < numChildren; ++c) + { + // The 'child' is an inner polygon. + std::shared_ptr inner = outer->child[c]; + + // Convert inner points from InputType to ComputeType. + int const numInnerIndices = static_cast(inner->polygon.size()); + int const* innerIndices = inner->polygon.data(); + for (int i = 0; i < numInnerIndices; ++i) + { + int index = innerIndices[i]; + if (!mIsConverted[index]) + { + mIsConverted[index] = true; + for (int j = 0; j < 2; ++j) + { + mComputePoints[index][j] = mPoints[index][j]; + } + } + } + + int numGrandChildren = static_cast(inner->child.size()); + for (int g = 0; g < numGrandChildren; ++g) + { + treeQueue.push(inner->child[g]); + } + } + } + + return numExtraPoints; + } + + // The input polygon. + int mNumPoints; + Vector2 const* mPoints; + + // The output triangulation. + std::vector> mTriangles; + + // The array of points used for geometric queries. If you want to be + // certain of a correct result, choose ComputeType to be BSNumber. + // The InputType points are convertex to ComputeType points on demand; + // the mIsConverted array keeps track of which input points have been + // converted. + std::vector> mComputePoints; + std::vector mIsConverted; + PrimalQuery2 mQuery; + + // Doubly linked lists for storing specially tagged vertices. + class Vertex + { + public: + Vertex() + : + index(-1), + vPrev(-1), + vNext(-1), + sPrev(-1), + sNext(-1), + ePrev(-1), + eNext(-1), + isConvex(false), + isEar(false) + { + } + + int index; // index of vertex in position array + int vPrev, vNext; // vertex links for polygon + int sPrev, sNext; // convex/reflex vertex links (disjoint lists) + int ePrev, eNext; // ear links + bool isConvex, isEar; + }; + + inline Vertex& V(int i) + { + return mVertices[i]; + } + + bool IsConvex(int i) + { + Vertex& vertex = V(i); + int curr = vertex.index; + int prev = V(vertex.vPrev).index; + int next = V(vertex.vNext).index; + vertex.isConvex = (mQuery.ToLine(curr, prev, next) > 0); + return vertex.isConvex; + } + + bool IsEar(int i) + { + Vertex& vertex = V(i); + + if (mRFirst == -1) + { + // The remaining polygon is convex. + vertex.isEar = true; + return true; + } + + // Search the reflex vertices and test if any are in the triangle + // . + int prev = V(vertex.vPrev).index; + int curr = vertex.index; + int next = V(vertex.vNext).index; + vertex.isEar = true; + for (int j = mRFirst; j != -1; j = V(j).sNext) + { + // Check if the test vertex is already one of the triangle + // vertices. + if (j == vertex.vPrev || j == i || j == vertex.vNext) + { + continue; + } + + // V[j] has been ruled out as one of the original vertices of + // the triangle . When triangulating + // polygons with holes, V[j] might be a duplicated vertex, in + // which case it does not affect the earness of V[curr]. + int test = V(j).index; + if (mComputePoints[test] == mComputePoints[prev] + || mComputePoints[test] == mComputePoints[curr] + || mComputePoints[test] == mComputePoints[next]) + { + continue; + } + + // Test if the vertex is inside or on the triangle. When it + // is, it causes V[curr] not to be an ear. + if (mQuery.ToTriangle(test, prev, curr, next) <= 0) + { + vertex.isEar = false; + break; + } + } + + return vertex.isEar; + } + + // insert convex vertex + void InsertAfterC(int i) + { + if (mCFirst == -1) + { + // Add the first convex vertex. + mCFirst = i; + } + else + { + V(mCLast).sNext = i; + V(i).sPrev = mCLast; + } + mCLast = i; + } + + // insert reflex vertesx + void InsertAfterR(int i) + { + if (mRFirst == -1) + { + // Add the first reflex vertex. + mRFirst = i; + } + else + { + V(mRLast).sNext = i; + V(i).sPrev = mRLast; + } + mRLast = i; + } + + // insert ear at end of list + void InsertEndE(int i) + { + if (mEFirst == -1) + { + // Add the first ear. + mEFirst = i; + mELast = i; + } + V(mELast).eNext = i; + V(i).ePrev = mELast; + mELast = i; + } + + // insert ear after efirst + void InsertAfterE(int i) + { + Vertex& first = V(mEFirst); + int currENext = first.eNext; + Vertex& vertex = V(i); + vertex.ePrev = mEFirst; + vertex.eNext = currENext; + first.eNext = i; + V(currENext).ePrev = i; + } + + // insert ear before efirst + void InsertBeforeE(int i) + { + Vertex& first = V(mEFirst); + int currEPrev = first.ePrev; + Vertex& vertex = V(i); + vertex.ePrev = currEPrev; + vertex.eNext = mEFirst; + first.ePrev = i; + V(currEPrev).eNext = i; + } + + // remove vertex + void RemoveV(int i) + { + int currVPrev = V(i).vPrev; + int currVNext = V(i).vNext; + V(currVPrev).vNext = currVNext; + V(currVNext).vPrev = currVPrev; + } + + // remove ear at i + int RemoveE(int i) + { + int currEPrev = V(i).ePrev; + int currENext = V(i).eNext; + V(currEPrev).eNext = currENext; + V(currENext).ePrev = currEPrev; + return currENext; + } + + // remove reflex vertex + void RemoveR(int i) + { + LogAssert(mRFirst != -1 && mRLast != -1, "Reflex vertices must exist."); + + if (i == mRFirst) + { + mRFirst = V(i).sNext; + if (mRFirst != -1) + { + V(mRFirst).sPrev = -1; + } + V(i).sNext = -1; + } + else if (i == mRLast) + { + mRLast = V(i).sPrev; + if (mRLast != -1) + { + V(mRLast).sNext = -1; + } + V(i).sPrev = -1; + } + else + { + int currSPrev = V(i).sPrev; + int currSNext = V(i).sNext; + V(currSPrev).sNext = currSNext; + V(currSNext).sPrev = currSPrev; + V(i).sNext = -1; + V(i).sPrev = -1; + } + } + + // The doubly linked list. + std::vector mVertices; + int mCFirst, mCLast; // linear list of convex vertices + int mRFirst, mRLast; // linear list of reflex vertices + int mEFirst, mELast; // cyclical list of ears + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TubeMesh.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TubeMesh.h new file mode 100644 index 0000000..283b65e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/TubeMesh.h @@ -0,0 +1,231 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class TubeMesh : public Mesh + { + public: + // Create a mesh (x(u,v),y(u,v),z(u,v)) defined by the specified + // medial curve and radial function. The mesh has torus topology + // when 'closed' is true and has cylinder topology when 'closed' is + // false. The client is responsible for setting the topology + // correctly in the 'description' input. The rows correspond to + // medial samples and the columns correspond to radial samples. The + // medial curve is sampled according to its natural t-parameter when + // 'sampleByArcLength' is false; otherwise, it is sampled uniformly + // in arclength. TODO: Allow TORUS and remove the 'closed' input + TubeMesh(MeshDescription const& description, + std::shared_ptr> const& medial, + std::function const& radial, bool closed, + bool sampleByArcLength, Vector3 upVector) + : + Mesh(description, { MeshTopology::CYLINDER }), + mMedial(medial), + mRadial(radial), + mClosed(closed), + mSampleByArcLength(sampleByArcLength), + mUpVector(upVector) + { + if (!this->mDescription.constructed) + { + // The logger system will report these errors in the Mesh + // constructor. + mMedial = nullptr; + return; + } + + LogAssert(mMedial != nullptr, "A nonnull medial curve is required."); + + mCosAngle.resize(this->mDescription.numCols); + mSinAngle.resize(this->mDescription.numCols); + Real invRadialSamples = (Real)1 / (Real)(this->mDescription.numCols - 1); + for (unsigned int i = 0; i < this->mDescription.numCols - 1; ++i) + { + Real angle = i * invRadialSamples * (Real)GTE_C_TWO_PI; + mCosAngle[i] = std::cos(angle); + mSinAngle[i] = std::sin(angle); + } + mCosAngle[this->mDescription.numCols - 1] = mCosAngle[0]; + mSinAngle[this->mDescription.numCols - 1] = mSinAngle[0]; + + Real invDenom; + if (mClosed) + { + invDenom = (Real)1 / (Real)this->mDescription.numRows; + } + else + { + invDenom = (Real)1 / (Real)(this->mDescription.numRows - 1); + } + + Real factor; + if (mSampleByArcLength) + { + factor = mMedial->GetTotalLength() * invDenom; + mTSampler = [this, factor](unsigned int row) + { + return mMedial->GetTime(row * factor); + }; + } + else + { + factor = (mMedial->GetTMax() - mMedial->GetTMin()) * invDenom; + mTSampler = [this, factor](unsigned int row) + { + return mMedial->GetTMin() + row * factor; + }; + } + + if (mUpVector != Vector3::Zero()) + { + mFSampler = [this](Real t) + { + std::array, 4> frame; + frame[0] = mMedial->GetPosition(t); + frame[1] = mMedial->GetTangent(t); + frame[3] = UnitCross(frame[1], mUpVector); + frame[2] = UnitCross(frame[3], frame[1]); + return frame; + }; + } + else + { + mFrenet = std::make_unique>(mMedial); + mFSampler = [this](Real t) + { + std::array, 4> frame; + (*mFrenet)(t, frame[0], frame[1], frame[2], frame[3]); + return frame; + }; + } + + if (!this->mTCoords) + { + mDefaultTCoords.resize(this->mDescription.numVertices); + this->mTCoords = mDefaultTCoords.data(); + this->mTCoordStride = sizeof(Vector2); + + this->mDescription.allowUpdateFrame = this->mDescription.wantDynamicTangentSpaceUpdate; + if (this->mDescription.allowUpdateFrame) + { + if (!this->mDescription.hasTangentSpaceVectors) + { + this->mDescription.allowUpdateFrame = false; + } + + if (!this->mNormals) + { + this->mDescription.allowUpdateFrame = false; + } + } + } + + this->ComputeIndices(); + InitializeTCoords(); + UpdatePositions(); + if (this->mDescription.allowUpdateFrame) + { + this->UpdateFrame(); + } + else if (this->mNormals) + { + this->UpdateNormals(); + } + } + + // Member access. + inline std::shared_ptr> const& GetMedial() const + { + return mMedial; + } + + inline std::function const& GetRadial() const + { + return mRadial; + } + + inline bool IsClosed() const + { + return mClosed; + } + + inline bool IsSampleByArcLength() const + { + return mSampleByArcLength; + } + + inline Vector3 const& GetUpVector() const + { + return mUpVector; + } + + private: + void InitializeTCoords() + { + Vector2tcoord; + for (unsigned int r = 0, i = 0; r < this->mDescription.numRows; ++r) + { + tcoord[1] = (Real)r / (Real)this->mDescription.rMax; + for (unsigned int c = 0; c <= this->mDescription.numCols; ++c, ++i) + { + tcoord[0] = (Real)c / (Real)this->mDescription.numCols; + this->TCoord(i) = tcoord; + } + } + } + + virtual void UpdatePositions() override + { + uint32_t row, col, v, save; + for (row = 0, v = 0; row < this->mDescription.numRows; ++row, ++v) + { + Real t = mTSampler(row); + Real radius = mRadial(t); + // frame = (position, tangent, normal, binormal) + std::array, 4> frame = mFSampler(t); + for (col = 0, save = v; col < this->mDescription.numCols; ++col, ++v) + { + this->Position(v) = frame[0] + radius * (mCosAngle[col] * frame[2] + + mSinAngle[col] * frame[3]); + } + this->Position(v) = this->Position(save); + } + + if (mClosed) + { + for (col = 0; col < this->mDescription.numCols; ++col) + { + uint32_t i0 = col; + uint32_t i1 = col + this->mDescription.numCols * (this->mDescription.numRows - 1); + this->Position(i1) = this->Position(i0); + } + } + } + + std::shared_ptr> mMedial; + std::function mRadial; + bool mClosed, mSampleByArcLength; + Vector3 mUpVector; + std::vector mCosAngle, mSinAngle; + std::function mTSampler; + std::function, 4>(Real)> mFSampler; + std::unique_ptr> mFrenet; + + // If the client does not request texture coordinates, they will be + // computed internally for use in evaluation of the surface geometry. + std::vector> mDefaultTCoords; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/UIntegerALU32.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/UIntegerALU32.h new file mode 100644 index 0000000..da8ff85 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/UIntegerALU32.h @@ -0,0 +1,547 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.09.03 + +#pragma once + +#include +#include + +// Support for unsigned integer arithmetic in BSNumber and BSRational. The +// Curiously Recurring Template Paradigm is used to allow the UInteger +// types to share code without introducing virtual functions. + +namespace WwiseGTE +{ + template + class UIntegerALU32 + { + public: + // Comparisons. These are not generic. They rely on their being + // called when the two BSNumber arguments to BSNumber::operatorX() + // are of the form 1.u*2^p and 1.v*2^p. The comparisons apply to + // 1.u and 1.v as unsigned integers with their leading 1-bits aligned. + bool operator==(UInteger const& number) const + { + UInteger const& self = *(UInteger const*)this; + int32_t numBits = self.GetNumBits(); + if (numBits != number.GetNumBits()) + { + return false; + } + + if (numBits > 0) + { + auto const& bits = self.GetBits(); + auto const& nBits = number.GetBits(); + int32_t const last = self.GetSize() - 1; + for (int32_t i = last; i >= 0; --i) + { + if (bits[i] != nBits[i]) + { + return false; + } + } + } + return true; + } + + bool operator!=(UInteger const& number) const + { + return !operator==(number); + } + + bool operator< (UInteger const& number) const + { + UInteger const& self = *(UInteger const*)this; + int32_t nNumBits = number.GetNumBits(); + auto const& nBits = number.GetBits(); + + int32_t numBits = self.GetNumBits(); + if (numBits > 0 && nNumBits > 0) + { + // The numbers must be compared as if they are left-aligned + // with each other. We got here because we had + // self = 1.u * 2^p and number = 1.v * 2^p. Although they + // have the same exponent, it is possible that + // 'self < number' but 'numBits(1u) > numBits(1v)'. Compare + // the bits one 32-bit block at a time. + auto const& bits = self.GetBits(); + int bitIndex0 = numBits - 1; + int bitIndex1 = nNumBits - 1; + int block0 = bitIndex0 / 32; + int block1 = bitIndex1 / 32; + int numBlockBits0 = 1 + (bitIndex0 % 32); + int numBlockBits1 = 1 + (bitIndex1 % 32); + uint64_t n0shift = bits[block0]; + uint64_t n1shift = nBits[block1]; + while (block0 >= 0 && block1 >= 0) + { + // Shift the bits in the leading blocks to the high-order bit. + uint32_t value0 = (uint32_t)((n0shift << (32 - numBlockBits0)) & 0x00000000FFFFFFFFull); + uint32_t value1 = (uint32_t)((n1shift << (32 - numBlockBits1)) & 0x00000000FFFFFFFFull); + + // Shift bits in the next block (if any) to fill the current + // block. + if (--block0 >= 0) + { + n0shift = bits[block0]; + value0 |= (uint32_t)((n0shift >> numBlockBits0) & 0x00000000FFFFFFFFull); + } + if (--block1 >= 0) + { + n1shift = nBits[block1]; + value1 |= (uint32_t)((n1shift >> numBlockBits1) & 0x00000000FFFFFFFFull); + } + if (value0 < value1) + { + return true; + } + if (value0 > value1) + { + return false; + } + } + return block0 < block1; + } + else + { + // One or both numbers are negative. The only time 'less than' is + // 'true' is when 'number' is positive. + return nNumBits > 0; + } + } + + bool operator<=(UInteger const& number) const + { + return operator<(number) || operator==(number); + } + + bool operator> (UInteger const& number) const + { + return !operator<=(number); + } + + bool operator>=(UInteger const& number) const + { + return !operator<(number); + } + + // Arithmetic operations. These are performed in-place; that is, the + // result is stored in 'this' object. The goal is to reduce the + // number of object copies, much like the goal is for std::move. The + // Sub function requires the inputs to satisfy n0 > n1. + void Add(UInteger const& n0, UInteger const& n1) + { + UInteger& self = *(UInteger*)this; + int32_t n0NumBits = n0.GetNumBits(); + int32_t n1NumBits = n1.GetNumBits(); + + // Add the numbers considered as positive integers. Set the last + // block to zero in case no carry-out occurs. + int numBits = std::max(n0NumBits, n1NumBits) + 1; + self.SetNumBits(numBits); + self.SetBack(0); + + // Get the input array sizes. + int32_t numElements0 = n0.GetSize(); + int32_t numElements1 = n1.GetSize(); + + // Order the inputs so that the first has the most blocks. + auto const& u0 = (numElements0 >= numElements1 ? n0.GetBits() : n1.GetBits()); + auto const& u1 = (numElements0 >= numElements1 ? n1.GetBits() : n0.GetBits()); + auto numElements = std::minmax(numElements0, numElements1); + + // Add the u1-blocks to u0-blocks. + auto& bits = self.GetBits(); + uint64_t carry = 0, sum; + int32_t i; + for (i = 0; i < numElements.first; ++i) + { + sum = u0[i] + (u1[i] + carry); + bits[i] = (uint32_t)(sum & 0x00000000FFFFFFFFull); + carry = (sum >> 32); + } + + // We have no more u1-blocks. Propagate the carry-out, if there is + // one, or copy the remaining blocks if there is not. + if (carry > 0) + { + for (/**/; i < numElements.second; ++i) + { + sum = u0[i] + carry; + bits[i] = (uint32_t)(sum & 0x00000000FFFFFFFFull); + carry = (sum >> 32); + } + if (carry > 0) + { + bits[i] = (uint32_t)(carry & 0x00000000FFFFFFFFull); + } + } + else + { + for (/**/; i < numElements.second; ++i) + { + bits[i] = u0[i]; + } + } + + // Reduce the number of bits if there was not a carry-out. + uint32_t firstBitIndex = (numBits - 1) % 32; + uint32_t mask = (1 << firstBitIndex); + if ((mask & self.GetBack()) == 0) + { + self.SetNumBits(--numBits); + } + } + + void Sub(UInteger const& n0, UInteger const& n1) + { + UInteger& self = *(UInteger*)this; + int32_t n0NumBits = n0.GetNumBits(); + auto const& n0Bits = n0.GetBits(); + auto const& n1Bits = n1.GetBits(); + + // Subtract the numbers considered as positive integers. We know + // that n0 > n1, so create a number n2 that has the same number of + // bits as n0 and use two's-complement to generate -n2, and then + // add n0 and -n2. The result is nonnegative, so we do not need + // to apply two's complement to a negative result to extract the + // sign and absolute value. + + // Get the input array sizes. We know + // numElements0 >= numElements1. + int32_t numElements0 = n0.GetSize(); + int32_t numElements1 = n1.GetSize(); + + // Create the two's-complement number n2. We know + // n2.GetNumElements() is the same as numElements0. + UInteger n2; + n2.SetNumBits(n0NumBits); + auto& n2Bits = n2.GetBits(); + int32_t i; + for (i = 0; i < numElements1; ++i) + { + n2Bits[i] = ~n1Bits[i]; + } + for (/**/; i < numElements0; ++i) + { + n2Bits[i] = ~0u; + } + + // Now add 1 to the bit-negated result to obtain -n1. + uint64_t carry = 1, sum; + for (i = 0; i < numElements0; ++i) + { + sum = n2Bits[i] + carry; + n2Bits[i] = (uint32_t)(sum & 0x00000000FFFFFFFFull); + carry = (sum >> 32); + } + + // Add the numbers as positive integers. Set the last block to + // zero in case no carry-out occurs. + self.SetNumBits(n0NumBits + 1); + self.SetBack(0); + + // Add the n0-blocks to n2-blocks. + auto & bits = self.GetBits(); + for (i = 0, carry = 0; i < numElements0; ++i) + { + sum = n2Bits[i] + (n0Bits[i] + carry); + bits[i] = (uint32_t)(sum & 0x00000000FFFFFFFFull); + carry = (sum >> 32); + } + + // Strip off the bits introduced by two's-complement. + int32_t block; + for (block = numElements0 - 1; block >= 0; --block) + { + if (bits[block] > 0) + { + break; + } + } + + if (block >= 0) + { + self.SetNumBits(32 * block + BitHacks::GetLeadingBit(bits[block]) + 1); + } + else + { + self.SetNumBits(0); + } + } + + void Mul(UInteger const& n0, UInteger const& n1) + { + UInteger& self = *(UInteger*)this; + int32_t n0NumBits = n0.GetNumBits(); + int32_t n1NumBits = n1.GetNumBits(); + auto const& n0Bits = n0.GetBits(); + auto const& n1Bits = n1.GetBits(); + + // The number of bits is at most this, possibly one bit smaller. + int numBits = n0NumBits + n1NumBits; + self.SetNumBits(numBits); + auto& bits = self.GetBits(); + + // Product of a single-block number with a multiple-block number. + UInteger product; + product.SetNumBits(numBits); + auto& pBits = product.GetBits(); + + // Get the array sizes. + int32_t const numElements0 = n0.GetSize(); + int32_t const numElements1 = n1.GetSize(); + int32_t const numElements = self.GetSize(); + + // Compute the product v = u0*u1. + int32_t i0, i1, i2; + uint64_t term, sum; + + // The case i0 == 0 is handled separately to initialize the + // accumulator with u0[0]*v. This avoids having to fill the bytes + // of 'bits' with zeros outside the double loop, something that + // can be a performance issue when 'numBits' is large. + uint64_t block0 = n0Bits[0]; + uint64_t carry = 0; + for (i1 = 0; i1 < numElements1; ++i1) + { + term = block0 * n1Bits[i1] + carry; + bits[i1] = (uint32_t)(term & 0x00000000FFFFFFFFull); + carry = (term >> 32); + } + if (i1 < numElements) + { + bits[i1] = (uint32_t)(carry & 0x00000000FFFFFFFFull); + } + + for (i0 = 1; i0 < numElements0; ++i0) + { + // Compute the product p = u0[i0]*u1. + block0 = n0Bits[i0]; + carry = 0; + for (i1 = 0, i2 = i0; i1 < numElements1; ++i1, ++i2) + { + term = block0 * n1Bits[i1] + carry; + pBits[i2] = (uint32_t)(term & 0x00000000FFFFFFFFull); + carry = (term >> 32); + } + if (i2 < numElements) + { + pBits[i2] = (uint32_t)(carry & 0x00000000FFFFFFFFull); + } + + // Add p to the accumulator v. + carry = 0; + for (i1 = 0, i2 = i0; i1 < numElements1; ++i1, ++i2) + { + sum = pBits[i2] + (bits[i2] + carry); + bits[i2] = (uint32_t)(sum & 0x00000000FFFFFFFFull); + carry = (sum >> 32); + } + if (i2 < numElements) + { + sum = pBits[i2] + carry; + bits[i2] = (uint32_t)(sum & 0x00000000FFFFFFFFull); + } + } + + // Reduce the number of bits if there was not a carry-out. + uint32_t firstBitIndex = (numBits - 1) % 32; + uint32_t mask = (1 << firstBitIndex); + if ((mask & self.GetBack()) == 0) + { + self.SetNumBits(--numBits); + } + } + + // The shift is performed in-place; that is, the result is stored in + // 'this' object. + void ShiftLeft(UInteger const& number, int32_t shift) + { + UInteger& self = *(UInteger*)this; + int32_t nNumBits = number.GetNumBits(); + auto const& nBits = number.GetBits(); + + // Shift the 'number' considered as an odd positive integer. + self.SetNumBits(nNumBits + shift); + + // Set the low-order bits to zero. + auto& bits = self.GetBits(); + int32_t const shiftBlock = shift / 32; + for (int32_t i = 0; i < shiftBlock; ++i) + { + bits[i] = 0; + } + + // Get the location of the low-order 1-bit within the result. + int32_t const numInElements = number.GetSize(); + int32_t const lshift = shift % 32; + int32_t i, j; + if (lshift > 0) + { + // The trailing 1-bits for source and target are at different + // relative indices. Each shifted source block straddles a + // boundary between two target blocks, so we must extract the + // subblocks and copy accordingly. + int32_t const rshift = 32 - lshift; + uint32_t prev = 0, curr; + for (i = shiftBlock, j = 0; j < numInElements; ++i, ++j) + { + curr = nBits[j]; + bits[i] = (curr << lshift) | (prev >> rshift); + prev = curr; + } + if (i < self.GetSize()) + { + // The leading 1-bit of the source is at a relative index + // such that when you add the shift amount, that bit + // occurs in a new block. + bits[i] = (prev >> rshift); + } + } + else + { + // The trailing 1-bits for source and target are at the same + // relative index. The shift reduces to a block copy. + for (i = shiftBlock, j = 0; j < numInElements; ++i, ++j) + { + bits[i] = nBits[j]; + } + } + } + + // The 'number' is even and positive. It is shifted right to become + // an odd number and the return value is the amount shifted. The + // operation is performed in-place; that is, the result is stored in + // 'this' object. + int32_t ShiftRightToOdd(UInteger const& number) + { + UInteger& self = *(UInteger*)this; + auto const& nBits = number.GetBits(); + + // Get the leading 1-bit. + int32_t const numElements = number.GetSize(); + int32_t const numM1 = numElements - 1; + int32_t firstBitIndex = 32 * numM1 + BitHacks::GetLeadingBit(nBits[numM1]); + + // Get the trailing 1-bit. + int32_t lastBitIndex = -1; + for (int32_t block = 0; block < numElements; ++block) + { + uint32_t value = nBits[block]; + if (value > 0) + { + lastBitIndex = 32 * block + BitHacks::GetTrailingBit(value); + break; + } + } + + // The right-shifted result. + self.SetNumBits(firstBitIndex - lastBitIndex + 1); + auto& bits = self.GetBits(); + int32_t const numBlocks = self.GetSize(); + + // Get the location of the low-order 1-bit within the result. + int32_t const shiftBlock = lastBitIndex / 32; + int32_t rshift = lastBitIndex % 32; + if (rshift > 0) + { + int32_t const lshift = 32 - rshift; + int32_t i, j = shiftBlock; + uint32_t curr = nBits[j++]; + for (i = 0; j < numElements; ++i, ++j) + { + uint32_t next = nBits[j]; + bits[i] = (curr >> rshift) | (next << lshift); + curr = next; + } + if (i < numBlocks) + { + bits[i] = (curr >> rshift); + } + } + else + { + for (int32_t i = 0, j = shiftBlock; i < numBlocks; ++i, ++j) + { + bits[i] = nBits[j]; + } + } + + return rshift + 32 * shiftBlock; + } + + // Add 1 to 'this', useful for rounding modes in conversions of + // BSNumber and BSRational. The operation is performed in-place; + // that is, the result is stored in 'this' object. The return value + // is the amount shifted after the addition in order to obtain an + // odd integer. + int32_t RoundUp() + { + UInteger const& self = *(UInteger const*)this; + UInteger rounded; + rounded.Add(self, UInteger(1u)); + return ShiftRightToOdd(rounded); + } + + // Get a block of numRequested bits starting with the leading 1-bit of + // the nonzero number. The returned number has the prefix stored in + // the high-order bits. Additional bits are copied and used by the + // caller for rounding. This function supports conversions from + // 'float' and 'double'. The input 'numRequested' is smaller than 64. + uint64_t GetPrefix(int32_t numRequested) const + { + UInteger const& self = *(UInteger const*)this; + auto const& bits = self.GetBits(); + + // Copy to 'prefix' the leading 32-bit block that is nonzero. + int32_t bitIndex = self.GetNumBits() - 1; + int32_t blockIndex = bitIndex / 32; + uint64_t prefix = bits[blockIndex]; + + // Get the number of bits in the block starting with the leading + // 1-bit. + int32_t firstBitIndex = bitIndex % 32; + int32_t numBlockBits = firstBitIndex + 1; + + // Shift the leading 1-bit to bit-63 of prefix. We have consumed + // numBlockBits, which might not be the entire budget. + int32_t targetIndex = 63; + prefix <<= targetIndex - firstBitIndex; + numRequested -= numBlockBits; + targetIndex -= numBlockBits; + + if (numRequested > 0 && --blockIndex >= 0) + { + // More bits are available. Copy and shift the entire 32-bit + // next block and OR it into the 'prefix'. For 'float', we + // will have consumed the entire budget. For 'double', we + // might have to get bits from a third block. + uint64_t nextBlock = bits[blockIndex]; + nextBlock <<= targetIndex - 31; // Shift amount is positive. + prefix |= nextBlock; + numRequested -= 32; + targetIndex -= 32; + + if (numRequested > 0 && --blockIndex >= 0) + { + // We know that targetIndex > 0; only 'double' allows us + // to get here, so numRequested is at most 53. We also + // know that targetIndex < 32 because we started with 63 + // and subtracted at least 32 from it. Thus, the shift + // amount is positive. + nextBlock = bits[blockIndex]; + nextBlock >>= 31 - targetIndex; + prefix |= nextBlock; + } + } + + return prefix; + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/UIntegerAP32.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/UIntegerAP32.h new file mode 100644 index 0000000..2417a2f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/UIntegerAP32.h @@ -0,0 +1,231 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.09.11 + +#pragma once + +#include +#include +#include +#include +#include +#include + +// Class UIntegerAP32 is designed to support arbitrary precision arithmetic +// using BSNumber and BSRational. It is not a general-purpose class for +// arithmetic of unsigned integers. + +// Uncomment this to collect statistics on how large the UIntegerAP32 storage +// becomes when using it for the UInteger of BSNumber. If you use this +// feature, you must define gsUIntegerAP32MaxSize somewhere in your code. +// After a sequence of BSNumber operations, look at gsUIntegerAP32MaxSize in +// the debugger watch window. If the number is not too large, you might be +// safe in replacing UIntegerAP32 by UIntegerFP32, where N is the value of +// gsUIntegerAP32MaxSize. This leads to much faster code because you no +// longer have dynamic memory allocations and deallocations that occur +// regularly with std::vector during BSNumber operations. A safer +// choice is to argue mathematically that the maximum size is bounded by N. +// This requires an analysis of how many bits of precision you need for the +// types of computation you perform. See class BSPrecision for code that +// allows you to compute maximum N. +// +//#define GTE_COLLECT_UINTEGERAP32_STATISTICS + +#if defined(GTE_COLLECT_UINTEGERAP32_STATISTICS) +#include +namespace WwiseGTE +{ + extern std::atomic gsUIntegerAP32MaxSize; +} +#endif + +namespace WwiseGTE +{ + class UIntegerAP32 : public UIntegerALU32 + { + public: + // Construction. + UIntegerAP32() + : + mNumBits(0) + { + } + + UIntegerAP32(UIntegerAP32 const& number) + { + *this = number; + } + + UIntegerAP32(uint32_t number) + { + if (number > 0) + { + int32_t first = BitHacks::GetLeadingBit(number); + int32_t last = BitHacks::GetTrailingBit(number); + mNumBits = first - last + 1; + mBits.resize(1); + mBits[0] = (number >> last); + } + else + { + mNumBits = 0; + } + +#if defined(GTE_COLLECT_UINTEGERAP32_STATISTICS) + AtomicMax(gsUIntegerAP32MaxSize, mBits.size()); +#endif + } + + UIntegerAP32(uint64_t number) + { + if (number > 0) + { + int32_t first = BitHacks::GetLeadingBit(number); + int32_t last = BitHacks::GetTrailingBit(number); + number >>= last; + mNumBits = first - last + 1; + mBits.resize(1 + (mNumBits - 1) / 32); + mBits[0] = (uint32_t)(number & 0x00000000FFFFFFFFull); + if (mBits.size() > 1) + { + mBits[1] = (uint32_t)((number >> 32) & 0x00000000FFFFFFFFull); + } + } + else + { + mNumBits = 0; + } + +#if defined(GTE_COLLECT_UINTEGERAP32_STATISTICS) + AtomicMax(gsUIntegerAP32MaxSize, mBits.size()); +#endif + } + + // Assignment. + UIntegerAP32& operator=(UIntegerAP32 const& number) + { + mNumBits = number.mNumBits; + mBits = number.mBits; + return *this; + } + + // Support for std::move. + UIntegerAP32(UIntegerAP32&& number) + { + *this = std::move(number); + } + + UIntegerAP32& operator=(UIntegerAP32&& number) + { + mNumBits = number.mNumBits; + mBits = std::move(number.mBits); + number.mNumBits = 0; + return *this; + } + + // Member access. + void SetNumBits(int32_t numBits) + { + if (numBits > 0) + { + mNumBits = numBits; + mBits.resize(1 + (numBits - 1) / 32); + } + else if (numBits == 0) + { + mNumBits = 0; + mBits.clear(); + } + else + { + LogError("The number of bits must be nonnegative."); + } + +#if defined(GTE_COLLECT_UINTEGERAP32_STATISTICS) + AtomicMax(gsUIntegerAP32MaxSize, mBits.size()); +#endif + } + + inline int32_t GetNumBits() const + { + return mNumBits; + } + + inline std::vector const& GetBits() const + { + return mBits; + } + + inline std::vector& GetBits() + { + return mBits; + } + + inline void SetBack(uint32_t value) + { + mBits.back() = value; + } + + inline uint32_t GetBack() const + { + return mBits.back(); + } + + inline int32_t GetSize() const + { + return static_cast(mBits.size()); + } + + inline static int32_t GetMaxSize() + { + return std::numeric_limits::max(); + } + + inline void SetAllBitsToZero() + { + std::fill(mBits.begin(), mBits.end(), 0u); + } + + // Disk input/output. The return value is 'true' iff the operation + // was successful. + bool Write(std::ostream& output) const + { + if (output.write((char const*)& mNumBits, sizeof(mNumBits)).bad()) + { + return false; + } + + std::size_t size = mBits.size(); + if (output.write((char const*)& size, sizeof(size)).bad()) + { + return false; + } + + return output.write((char const*)& mBits[0], size * sizeof(mBits[0])).good(); + } + + bool Read(std::istream& input) + { + if (input.read((char*)& mNumBits, sizeof(mNumBits)).bad()) + { + return false; + } + + std::size_t size; + if (input.read((char*)& size, sizeof(size)).bad()) + { + return false; + } + + mBits.resize(size); + return input.read((char*)& mBits[0], size * sizeof(mBits[0])).good(); + } + + private: + int32_t mNumBits; + std::vector mBits; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/UIntegerFP32.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/UIntegerFP32.h new file mode 100644 index 0000000..30d2bc0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/UIntegerFP32.h @@ -0,0 +1,226 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.09.11 + +#pragma once + +#include +#include +#include +#include +#include + +// Class UIntegerFP32 is designed to support fixed precision arithmetic +// using BSNumber and BSRational. It is not a general-purpose class for +// arithmetic of unsigned integers. The template parameter N is the +// number of 32-bit words required to store the precision for the desired +// computations (maximum number of bits is 32*N). + +// Uncomment this to trap when an attempt is made to create storage with +// more than N uint32_t items. +// +//#define GTE_THROW_ON_UINTEGERFP32_OUT_OF_RANGE + +namespace WwiseGTE +{ + template + class UIntegerFP32 : public UIntegerALU32> + { + public: + // Construction. + UIntegerFP32() + : + mNumBits(0), + mSize(0) + { + static_assert(N >= 1, "Invalid size N."); + } + + UIntegerFP32(UIntegerFP32 const& number) + { + static_assert(N >= 1, "Invalid size N."); + + *this = number; + } + + UIntegerFP32(uint32_t number) + { + static_assert(N >= 1, "Invalid size N."); + + if (number > 0) + { + int32_t first = BitHacks::GetLeadingBit(number); + int32_t last = BitHacks::GetTrailingBit(number); + mNumBits = first - last + 1; + mSize = 1; + mBits[0] = (number >> last); + } + else + { + mNumBits = 0; + mSize = 0; + } + } + + UIntegerFP32(uint64_t number) + { + static_assert(N >= 2, "N not large enough to store 64-bit integers."); + + if (number > 0) + { + int32_t first = BitHacks::GetLeadingBit(number); + int32_t last = BitHacks::GetTrailingBit(number); + number >>= last; + mNumBits = first - last + 1; + mSize = 1 + (mNumBits - 1) / 32; + mBits[0] = (uint32_t)(number & 0x00000000FFFFFFFFull); + if (mSize > 1) + { + mBits[1] = (uint32_t)((number >> 32) & 0x00000000FFFFFFFFull); + } + } + else + { + mNumBits = 0; + mSize = 0; + } + } + + // Assignment. Only mSize elements are copied. + UIntegerFP32& operator=(UIntegerFP32 const& number) + { + static_assert(N >= 1, "Invalid size N."); + + mNumBits = number.mNumBits; + mSize = number.mSize; + std::copy(number.mBits.begin(), number.mBits.begin() + mSize, mBits.begin()); + return *this; + } + + // Support for std::move. The interface is required by BSNumber, but + // the std::move of std::array is a copy (no pointer stealing). + // Moreover, a std::array object in this class typically uses smaller + // than N elements, the actual size stored in mSize, so we do not want + // to move everything. Therefore, the move operator only copies the + // bits BUT 'number' is modified as if you have stolen the data + // (mNumBits and mSize set to zero). + UIntegerFP32(UIntegerFP32&& number) + { + *this = std::move(number); + } + + UIntegerFP32& operator=(UIntegerFP32&& number) + { + mNumBits = number.mNumBits; + mSize = number.mSize; + std::copy(number.mBits.begin(), number.mBits.begin() + mSize, + mBits.begin()); + number.mNumBits = 0; + number.mSize = 0; + return *this; + } + + // Member access. + void SetNumBits(int32_t numBits) + { + if (numBits > 0) + { + mNumBits = numBits; + mSize = 1 + (numBits - 1) / 32; + } + else if (numBits == 0) + { + mNumBits = 0; + mSize = 0; + } + else + { + LogError("The number of bits must be nonnegative."); + } + +#if defined(GTE_THROW_ON_UINTEGERFP32_OUT_OF_RANGE) + LogAssert(mSize <= N, "N not large enough to store number of bits."); +#endif + } + + inline int32_t GetNumBits() const + { + return mNumBits; + } + + inline std::array const& GetBits() const + { + return mBits; + } + + inline std::array& GetBits() + { + return mBits; + } + + inline void SetBack(uint32_t value) + { + mBits[mSize - 1] = value; + } + + inline uint32_t GetBack() const + { + return mBits[mSize - 1]; + } + + inline int32_t GetSize() const + { + return mSize; + } + + inline static int32_t GetMaxSize() + { + return N; + } + + inline void SetAllBitsToZero() + { + std::fill(mBits.begin(), mBits.end(), 0u); + } + + // Disk input/output. The fstream objects should be created using + // std::ios::binary. The return value is 'true' iff the operation + // was successful. + bool Write(std::ostream& output) const + { + if (output.write((char const*)& mNumBits, sizeof(mNumBits)).bad()) + { + return false; + } + + if (output.write((char const*)& mSize, sizeof(mSize)).bad()) + { + return false; + } + + return output.write((char const*)& mBits[0], mSize * sizeof(mBits[0])).good(); + } + + bool Read(std::istream& input) + { + if (input.read((char*)& mNumBits, sizeof(mNumBits)).bad()) + { + return false; + } + + if (input.read((char*)& mSize, sizeof(mSize)).bad()) + { + return false; + } + + return input.read((char*)& mBits[0], mSize * sizeof(mBits[0])).good(); + } + + private: + int32_t mNumBits, mSize; + std::array mBits; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/UniqueVerticesTriangles.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/UniqueVerticesTriangles.h new file mode 100644 index 0000000..a5c20dc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/UniqueVerticesTriangles.h @@ -0,0 +1,133 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include + +// The VertexType must have an operator< member because it is used as the +// key in a std::map. For example, if VertexType is +// Vector3, the comparison operator already exist. Another example is +// that you have a vertex type used for vertex coloring, say, +// struct VertexType +// { +// Vector3 position; +// Vector4 color; +// bool operator< (VertexType const& v) const +// { +// return position < v.position; +// } +// } +// The comparision will guarantee unique vertex positions, although if you +// have two VertexType objects with the same position but different colors, +// there is no guarantee which color will occur in the final result. + +namespace WwiseGTE +{ + template + class UniqueVerticesTriangles + { + public: + // Triangle soup. The input vertex array consists of triples of + // vertices, each triple representing a triangle. The array + // 'inVertices' must have a multiple of 3 elements. An array + // 'outVertices' of unique vertices and an array 'outIndices' of + // 'inVertices.size()/3' unique index triples are computed. The + // indices are relative to the array of unique vertices and each + // index triple represents a triangle. + UniqueVerticesTriangles( + std::vector const& inVertices, + std::vector& outVertices, + std::vector& outIndices) + { + ConstructUniqueVertices(inVertices, outVertices); + + // The input index array is implicitly + // {<0,1,2>,<3,4,5>,...,} + // where n is the number of vertices. The output index array is + // the same as the mapping array. + outIndices.resize(inVertices.size()); + std::copy(mInToOutMapping.begin(), mInToOutMapping.end(), + outIndices.begin()); + } + + // Indexed triangles. The input vertex array consists of all vertices + // referenced by the input index array. The array 'inIndices' must + // have a multiple of 3 elements. An array 'outVertices' of unique + // vertices and an array 'outIndices' of 'indices.size()/3' unique + // index triples are computed. The indices are relative to the array + // of unique vertices and each index triple represents a triangle. + UniqueVerticesTriangles( + std::vector const& inVertices, + std::vector const& inIndices, + std::vector& outVertices, + std::vector& outIndices) + { + ConstructUniqueVertices(inVertices, outVertices); + + // The input index array needs it indices mapped to the unique + // vertex indices. + outIndices.resize(inIndices.size()); + for (size_t i = 0; i < inIndices.size(); ++i) + { + outIndices[i] = mInToOutMapping[inIndices[i]]; + } + } + + // The input vertices have indices 0 <= i < VInNum. The output + // vertices have indices 0 <= j < VOutNum. The construction leads to + // a mapping of input indices i to output indices j. Duplicate + // vertices have different input indices but the same output index. + // The following function gives you access to the mapping. If the + // input index is invalid (i < 0 or i >= VINum), the return value + // is -1. + inline int GetOutputIndexFor(int index) const + { + return mInToOutMapping[index]; + } + + private: + void ConstructUniqueVertices(std::vector const& inVertices, + std::vector& outVertices) + { + // Construct the unique vertices. + mNumInVertices = (int)inVertices.size(); + mInToOutMapping.resize(mNumInVertices); + std::map table; + mNumOutVertices = 0; + for (int i = 0; i < mNumInVertices; ++i) + { + auto const iter = table.find(inVertices[i]); + if (iter != table.end()) + { + // Vertex i is a duplicate of one inserted earlier into + // the table. Map vertex i to the first-found copy. + mInToOutMapping[i] = iter->second; + } + else + { + // Vertex i is the first occurrence of such a point. + table.insert(std::make_pair(inVertices[i], mNumOutVertices)); + mInToOutMapping[i] = mNumOutVertices; + ++mNumOutVertices; + } + } + + // Pack the unique vertices into an array in the correct order. + outVertices.resize(mNumOutVertices); + for (auto const& element : table) + { + outVertices[element.second] = element.first; + } + } + + int mNumInVertices, mNumOutVertices; + std::vector mInToOutMapping; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/UnsymmetricEigenvalues.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/UnsymmetricEigenvalues.h new file mode 100644 index 0000000..4fa4a17 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/UnsymmetricEigenvalues.h @@ -0,0 +1,374 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// An implementation of the QR algorithm described in "Matrix Computations, +// 2nd edition" by G. H. Golub and C. F. Van Loan, The Johns Hopkins +// University Press, Baltimore MD, Fourth Printing 1993. In particular, +// the implementation is based on Chapter 7 (The Unsymmetric Eigenvalue +// Problem), Section 7.5 (The Practical QR Algorithm). + +namespace WwiseGTE +{ + template + class UnsymmetricEigenvalues + { + public: + // The solver processes NxN matrices (not necessarily symmetric), + // where N >= 3 ('size' is N) and the matrix is stored in row-major + // order. The maximum number of iterations ('maxIterations') must + // be specified for reducing an upper Hessenberg matrix to an upper + // quasi-triangular matrix (upper triangular matrix of blocks where + // the diagonal blocks are 1x1 or 2x2). The goal is to compute the + // real-valued eigenvalues. + UnsymmetricEigenvalues(int32_t size, uint32_t maxIterations) + : + mSize(0), + mSizeM1(0), + mMaxIterations(0), + mNumEigenvalues(0) + { + if (size >= 3 && maxIterations > 0) + { + mSize = size; + mSizeM1 = size - 1; + mMaxIterations = maxIterations; + mMatrix.resize(size * size); + mX.resize(size); + mV.resize(size); + mScaledV.resize(size); + mW.resize(size); + mFlagStorage.resize(size + 1); + std::fill(mFlagStorage.begin(), mFlagStorage.end(), 0); + mSubdiagonalFlag = &mFlagStorage[1]; + mEigenvalues.resize(mSize); + } + } + + // A copy of the NxN input is made internally. The order of the + // eigenvalues is specified by sortType: -1 (decreasing), 0 (no + // sorting), or +1 (increasing). When sorted, the eigenvectors are + // ordered accordingly. The return value is the number of iterations + // consumed when convergence occurred, 0xFFFFFFFF when convergence did + // not occur, or 0 when N <= 1 was passed to the constructor. + uint32_t Solve(Real const* input, int32_t sortType) + { + if (mSize > 0) + { + std::copy(input, input + mSize * mSize, mMatrix.begin()); + ReduceToUpperHessenberg(); + + std::array block; + bool found = GetBlock(block); + uint32_t numIterations; + for (numIterations = 0; numIterations < mMaxIterations; ++numIterations) + { + if (found) + { + // Solve the current subproblem. + FrancisQRStep(block[0], block[1] + 1); + + // Find another subproblem (if any). + found = GetBlock(block); + } + else + { + break; + } + } + + // The matrix is fully uncoupled, upper Hessenberg with 1x1 or + // 2x2 diagonal blocks. Golub and Van Loan call this "upper + // quasi-triangular". + mNumEigenvalues = 0; + std::fill(mEigenvalues.begin(), mEigenvalues.end(), (Real)0); + for (int i = 0; i < mSizeM1; ++i) + { + if (mSubdiagonalFlag[i] == 0) + { + if (mSubdiagonalFlag[i - 1] == 0) + { + // We have a 1x1 block with a real eigenvalue. + mEigenvalues[mNumEigenvalues++] = A(i, i); + } + } + else + { + if (mSubdiagonalFlag[i - 1] == 0 && mSubdiagonalFlag[i + 1] == 0) + { + // We have a 2x2 block that might have real + // eigenvalues. + Real a00 = A(i, i); + Real a01 = A(i, i + 1); + Real a10 = A(i + 1, i); + Real a11 = A(i + 1, i + 1); + Real tr = a00 + a11; + Real det = a00 * a11 - a01 * a10; + Real halfTr = tr * (Real)0.5; + Real discr = halfTr * halfTr - det; + if (discr >= (Real)0) + { + Real rootDiscr = std::sqrt(discr); + mEigenvalues[mNumEigenvalues++] = halfTr - rootDiscr; + mEigenvalues[mNumEigenvalues++] = halfTr + rootDiscr; + } + } + // else: + // The QR iteration failed to converge at this block. + // It must also be the case that + // numIterations == mMaxIterations. TODO: The caller + // will be aware of this when testing the returned + // numIterations. Is there a remedy for such a case? + // This happened with root finding using the companion + // matrix of a polynomial.) + } + } + + if (sortType != 0 && mNumEigenvalues > 1) + { + if (sortType > 0) + { + std::sort(mEigenvalues.begin(), + mEigenvalues.begin() + mNumEigenvalues, std::less()); + } + else + { + std::sort(mEigenvalues.begin(), + mEigenvalues.begin() + mNumEigenvalues, std::greater()); + } + } + + return numIterations; + } + return 0; + } + + // Get the real-valued eigenvalues of the matrix passed to Solve(...). + // The input 'eigenvalues' must have at least N elements. + void GetEigenvalues(uint32_t& numEigenvalues, Real* eigenvalues) const + { + if (mSize > 0) + { + numEigenvalues = mNumEigenvalues; + std::memcpy(eigenvalues, mEigenvalues.data(), numEigenvalues * sizeof(Real)); + } + else + { + numEigenvalues = 0; + } + } + + private: + // 2D accessors to elements of mMatrix[]. + inline Real const& A(int r, int c) const + { + return mMatrix[c + r * mSize]; + } + + inline Real& A(int r, int c) + { + return mMatrix[c + r * mSize]; + } + + // Compute the Householder vector for (X[rmin],...,x[rmax]). The + // input vector is stored in mX in the index range [rmin,rmax]. The + // output vector V is stored in mV in the index range [rmin,rmax]. + // The scaled vector is S = (-2/Dot(V,V))*V and is stored in mScaledV + // in the index range [rmin,rmax]. + void House(int rmin, int rmax) + { + Real length = (Real)0; + for (int r = rmin; r <= rmax; ++r) + { + length += mX[r] * mX[r]; + } + length = std::sqrt(length); + if (length != (Real)0) + { + Real sign = (mX[rmin] >= (Real)0 ? (Real)1 : (Real)-1); + Real invDenom = (Real)1 / (mX[rmin] + sign * length); + for (int r = rmin + 1; r <= rmax; ++r) + { + mV[r] = mX[r] * invDenom; + } + } + mV[rmin] = (Real)1; + + Real dot = (Real)1; + for (int r = rmin + 1; r <= rmax; ++r) + { + dot += mV[r] * mV[r]; + } + Real scale = (Real)-2 / dot; + for (int r = rmin; r <= rmax; ++r) + { + mScaledV[r] = scale * mV[r]; + } + } + + // Support for replacing matrix A by P^T*A*P, where P is a Householder + // reflection computed using House(...). + void RowHouse(int rmin, int rmax, int cmin, int cmax) + { + for (int c = cmin; c <= cmax; ++c) + { + mW[c] = (Real)0; + for (int r = rmin; r <= rmax; ++r) + { + mW[c] += mScaledV[r] * A(r, c); + } + } + + for (int r = rmin; r <= rmax; ++r) + { + for (int c = cmin; c <= cmax; ++c) + { + A(r, c) += mV[r] * mW[c]; + } + } + } + + void ColHouse(int rmin, int rmax, int cmin, int cmax) + { + for (int r = rmin; r <= rmax; ++r) + { + mW[r] = (Real)0; + for (int c = cmin; c <= cmax; ++c) + { + mW[r] += mScaledV[c] * A(r, c); + } + } + + for (int r = rmin; r <= rmax; ++r) + { + for (int c = cmin; c <= cmax; ++c) + { + A(r, c) += mW[r] * mV[c]; + } + } + } + + void ReduceToUpperHessenberg() + { + for (int c = 0, cp1 = 1; c <= mSize - 3; ++c, ++cp1) + { + for (int r = cp1; r <= mSizeM1; ++r) + { + mX[r] = A(r, c); + } + + House(cp1, mSizeM1); + RowHouse(cp1, mSizeM1, c, mSizeM1); + ColHouse(0, mSizeM1, cp1, mSizeM1); + } + } + + void FrancisQRStep(int rmin, int rmax) + { + // Apply the double implicit shift step. + int const i0 = rmax - 1, i1 = rmax; + Real a00 = A(i0, i0); + Real a01 = A(i0, i1); + Real a10 = A(i1, i0); + Real a11 = A(i1, i1); + Real tr = a00 + a11; + Real det = a00 * a11 - a01 * a10; + + int const j0 = rmin, j1 = j0 + 1, j2 = j1 + 1; + Real b00 = A(j0, j0); + Real b01 = A(j0, j1); + Real b10 = A(j1, j0); + Real b11 = A(j1, j1); + Real b21 = A(j2, j1); + mX[rmin] = b00 * (b00 - tr) + b01 * b10 + det; + mX[rmin + 1] = b10 * (b00 + b11 - tr); + mX[rmin + 2] = b10 * b21; + + House(rmin, rmin + 2); + RowHouse(rmin, rmin + 2, rmin, rmax); + ColHouse(rmin, std::min(rmax, rmin + 3), rmin, rmin + 2); + + // Apply Householder reflections to restore the matrix to upper + // Hessenberg form. + for (int c = 0, cp1 = 1; c <= mSize - 3; ++c, ++cp1) + { + int kmax = std::min(cp1 + 2, mSizeM1); + for (int r = cp1; r <= kmax; ++r) + { + mX[r] = A(r, c); + } + + House(cp1, kmax); + RowHouse(cp1, kmax, c, mSizeM1); + ColHouse(0, mSizeM1, cp1, kmax); + } + } + + bool GetBlock(std::array& block) + { + for (int i = 0; i < mSizeM1; ++i) + { + Real a00 = A(i, i); + Real a11 = A(i + 1, i + 1); + Real a21 = A(i + 1, i); + Real sum0 = a00 + a11; + Real sum1 = sum0 + a21; + mSubdiagonalFlag[i] = (sum1 != sum0 ? 1 : 0); + } + + for (int i = 0; i < mSizeM1; ++i) + { + if (mSubdiagonalFlag[i] == 1) + { + block = { i, -1 }; + while (i < mSizeM1 && mSubdiagonalFlag[i] == 1) + { + block[1] = i++; + } + if (block[1] != block[0]) + { + return true; + } + } + } + return false; + } + + // The number N of rows and columns of the matrices to be processed. + int32_t mSize, mSizeM1; + + // The maximum number of iterations for reducing the tridiagonal + // matrix to a diagonal matrix. + uint32_t mMaxIterations; + + // The internal copy of a matrix passed to the solver. + std::vector mMatrix; // NxN elements + + // Temporary storage to compute Householder reflections. + std::vector mX, mV, mScaledV, mW; // N elements + + // Flags about the zeroness of the subdiagonal entries. This is used + // to detect uncoupled submatrices and apply the QR algorithm to the + // corresponding subproblems. The storage is padded on both ends with + // zeros to avoid additional code logic when packing the eigenvalues + // for access by the caller. + std::vector mFlagStorage; + int* mSubdiagonalFlag; + + int mNumEigenvalues; + std::vector mEigenvalues; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/VEManifoldMesh.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/VEManifoldMesh.h new file mode 100644 index 0000000..512455d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/VEManifoldMesh.h @@ -0,0 +1,271 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + class VEManifoldMesh + { + public: + // Vertex data types. + class Vertex; + typedef std::shared_ptr(*VCreator)(int); + typedef std::map> VMap; + + // Edge data types. + class Edge; + typedef std::shared_ptr(*ECreator)(int, int); + typedef std::map, std::shared_ptr> EMap; + + // Vertex object. + class Vertex + { + public: + virtual ~Vertex() = default; + + Vertex(int v) + : + V(v) + { + } + + // The unique vertex index. + int V; + + // The edges (if any) sharing the vertex. + std::array, 2> E; + }; + + // Edge object. + class Edge + { + public: + virtual ~Edge() = default; + + Edge(int v0, int v1) + : + V{ v0, v1 } + { + } + + // Vertices, listed as a directed edge . + std::array V; + + // Adjacent edges. E[i] points to edge sharing V[i]. + std::array, 2> E; + }; + + + // Construction and destruction. + virtual ~VEManifoldMesh() = default; + + VEManifoldMesh(VCreator vCreator = nullptr, ECreator eCreator = nullptr) + : + mVCreator(vCreator ? vCreator : CreateVertex), + mECreator(eCreator ? eCreator : CreateEdge), + mThrowOnNonmanifoldInsertion(true) + { + } + + // Member access. + inline VMap const& GetVertices() const + { + return mVMap; + } + + inline EMap const& GetEdges() const + { + return mEMap; + } + + // If the insertion of an edge fails because the mesh would become + // nonmanifold, the default behavior is to throw an exception. You + // can disable this behavior and continue gracefully without an + // exception. + void ThrowOnNonmanifoldInsertion(bool doException) + { + mThrowOnNonmanifoldInsertion = doException; + } + + // If is not in the mesh, an Edge object is created and + // returned; otherwise, is in the mesh and nullptr is + // returned. If the insertion leads to a nonmanifold mesh, the + // call fails with a nullptr returned. + std::shared_ptr Insert(int v0, int v1) + { + std::pair ekey(v0, v1); + if (mEMap.find(ekey) != mEMap.end()) + { + // The edge already exists. Return a null pointer as a + // signal to the caller that the insertion failed. + return nullptr; + } + + // Add the new edge. + std::shared_ptr edge = mECreator(v0, v1); + mEMap[ekey] = edge; + + // Add the vertices if they do not already exist. + for (int i = 0; i < 2; ++i) + { + int v = edge->V[i]; + std::shared_ptr vertex; + auto viter = mVMap.find(v); + if (viter == mVMap.end()) + { + // This is the first time the vertex is encountered. + vertex = mVCreator(v); + mVMap[v] = vertex; + + // Update the vertex. + vertex->E[0] = edge; + } + else + { + // This is the second time the vertex is encountered. + vertex = viter->second; + LogAssert(vertex != nullptr, "Unexpected condition."); + + // Update the vertex. + if (vertex->E[1].lock()) + { + if (mThrowOnNonmanifoldInsertion) + { + LogError("The mesh must be manifold."); + } + else + { + return nullptr; + } + } + vertex->E[1] = edge; + + // Update the adjacent edge. + auto adjacent = vertex->E[0].lock(); + LogAssert(adjacent != nullptr, "Unexpected condition."); + for (int j = 0; j < 2; ++j) + { + if (adjacent->V[j] == v) + { + adjacent->E[j] = edge; + break; + } + } + + // Update the edge. + edge->E[i] = adjacent; + } + } + + return edge; + } + + // If is in the mesh, it is removed and 'true' is returned; + // otherwise, is not in the mesh and 'false' is returned. + bool Remove(int v0, int v1) + { + std::pair ekey(v0, v1); + auto eiter = mEMap.find(ekey); + if (eiter == mEMap.end()) + { + // The edge does not exist. + return false; + } + + // Get the edge. + std::shared_ptr edge = eiter->second; + + // Remove the vertices if necessary (when they are not shared). + for (int i = 0; i < 2; ++i) + { + // Inform the vertices the edge is being deleted. + auto viter = mVMap.find(edge->V[i]); + LogAssert(viter != mVMap.end(), "Unexpected condition."); + + std::shared_ptr vertex = viter->second; + LogAssert(vertex != nullptr, "Unexpected condition."); + if (vertex->E[0].lock() == edge) + { + // One-edge vertices always have pointer at index zero. + vertex->E[0] = vertex->E[1]; + vertex->E[1].reset(); + } + else if (vertex->E[1].lock() == edge) + { + vertex->E[1].reset(); + } + else + { + LogError("Unexpected condition."); + } + + // Remove the vertex if you have the last reference to it. + if (!vertex->E[0].lock() && !vertex->E[1].lock()) + { + mVMap.erase(vertex->V); + } + + // Inform adjacent edges the edge is being deleted. + auto adjacent = edge->E[i].lock(); + if (adjacent) + { + for (int j = 0; j < 2; ++j) + { + if (adjacent->E[j].lock() == edge) + { + adjacent->E[j].reset(); + break; + } + } + } + } + + mEMap.erase(ekey); + return true; + } + + // A manifold mesh is closed if each vertex is shared twice. + bool IsClosed() const + { + for (auto const& element : mVMap) + { + auto vertex = element.second; + if (!vertex->E[0].lock() || !vertex->E[1].lock()) + { + return false; + } + } + return true; + } + + protected: + // The vertex data and default vertex creation. + static std::shared_ptr CreateVertex(int v0) + { + return std::make_shared(v0); + } + + VCreator mVCreator; + VMap mVMap; + + // The edge data and default edge creation. + static std::shared_ptr CreateEdge(int v0, int v1) + { + return std::make_shared(v0, v1); + } + + ECreator mECreator; + EMap mEMap; + bool mThrowOnNonmanifoldInsertion; // default: true + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/VETManifoldMesh.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/VETManifoldMesh.h new file mode 100644 index 0000000..aeac116 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/VETManifoldMesh.h @@ -0,0 +1,197 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// The VETManifoldMesh class represents an edge-triangle manifold mesh +// but additionally stores vertex adjacency information. + +namespace WwiseGTE +{ + class VETManifoldMesh : public ETManifoldMesh + { + public: + // Vertex data types. + class Vertex; + typedef std::shared_ptr(*VCreator)(int); + typedef std::map> VMap; + + // Vertex object. + class Vertex + { + public: + virtual ~Vertex() = default; + + Vertex(int vIndex) + : + V(vIndex) + { + } + + // The index into the vertex pool of the mesh. + int V; + + // Adjacent objects. + std::set VAdjacent; + std::set> EAdjacent; + std::set> TAdjacent; + }; + + + // Construction and destruction. + virtual ~VETManifoldMesh() = default; + + VETManifoldMesh(VCreator vCreator = nullptr, ECreator eCreator = nullptr, TCreator tCreator = nullptr) + : + ETManifoldMesh(eCreator, tCreator), + mVCreator(vCreator ? vCreator : CreateVertex) + { + } + + // Support for a deep copy of the mesh. The mVMap, mEMap, and mTMap + // objects have dynamically allocated memory for vertices, edges, and + // triangles. A shallow copy of the pointers to this memory is + // problematic. Allowing sharing, say, via std::shared_ptr, is an + // option but not really the intent of copying the mesh graph. + VETManifoldMesh(VETManifoldMesh const& mesh) + { + *this = mesh; + } + + VETManifoldMesh& operator=(VETManifoldMesh const& mesh) + { + Clear(); + mVCreator = mesh.mVCreator; + ETManifoldMesh::operator=(mesh); + return *this; + } + + // Member access. + inline VMap const& GetVertices() const + { + return mVMap; + } + + // If is not in the mesh, a Triangle object is created and + // returned; otherwise, is in the mesh and nullptr is + // returned. If the insertion leads to a nonmanifold mesh, the call + // fails with a nullptr returned. + virtual std::shared_ptr Insert(int v0, int v1, int v2) override + { + std::shared_ptr tri = ETManifoldMesh::Insert(v0, v1, v2); + if (!tri) + { + return nullptr; + } + + for (int i = 0; i < 3; ++i) + { + int vIndex = tri->V[i]; + auto vItem = mVMap.find(vIndex); + std::shared_ptr vertex; + if (vItem == mVMap.end()) + { + vertex = mVCreator(vIndex); + mVMap[vIndex] = vertex; + } + else + { + vertex = vItem->second; + } + + vertex->TAdjacent.insert(tri); + + for (int j = 0; j < 3; ++j) + { + auto edge = tri->E[j].lock(); + LogAssert(edge != nullptr, "Malformed mesh."); + if (edge->V[0] == vIndex) + { + vertex->VAdjacent.insert(edge->V[1]); + vertex->EAdjacent.insert(edge); + } + else if (edge->V[1] == vIndex) + { + vertex->VAdjacent.insert(edge->V[0]); + vertex->EAdjacent.insert(edge); + } + } + } + + return tri; + } + + // If is in the mesh, it is removed and 'true' is returned; + // otherwise, is not in the mesh and 'false' is returned. + virtual bool Remove(int v0, int v1, int v2) override + { + auto tItem = mTMap.find(TriangleKey(v0, v1, v2)); + if (tItem == mTMap.end()) + { + return false; + } + + std::shared_ptr tri = tItem->second; + for (int i = 0; i < 3; ++i) + { + int vIndex = tri->V[i]; + auto vItem = mVMap.find(vIndex); + LogAssert(vItem != mVMap.end(), "Malformed mesh."); + std::shared_ptr vertex = vItem->second; + for (int j = 0; j < 3; ++j) + { + auto edge = tri->E[j].lock(); + LogAssert(edge != nullptr, "Malformed mesh."); + if (edge->T[0].lock() && !edge->T[1].lock()) + { + if (edge->V[0] == vIndex) + { + vertex->VAdjacent.erase(edge->V[1]); + vertex->EAdjacent.erase(edge); + } + else if (edge->V[1] == vIndex) + { + vertex->VAdjacent.erase(edge->V[0]); + vertex->EAdjacent.erase(edge); + } + } + } + + vertex->TAdjacent.erase(tri); + + if (vertex->TAdjacent.size() == 0) + { + LogAssert(vertex->VAdjacent.size() == 0 && vertex->EAdjacent.size() == 0, + "Malformed mesh: Inconsistent vertex adjacency information."); + + mVMap.erase(vItem); + } + } + + return ETManifoldMesh::Remove(v0, v1, v2); + } + + // Destroy the vertices, edges, and triangles to obtain an empty mesh. + virtual void Clear() override + { + mVMap.clear(); + ETManifoldMesh::Clear(); + } + + protected: + // The vertex data and default vertex creation. + static std::shared_ptr CreateVertex(int vIndex) + { + return std::make_shared(vIndex); + } + + VCreator mVCreator; + VMap mVMap; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/VETNonmanifoldMesh.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/VETNonmanifoldMesh.h new file mode 100644 index 0000000..27c107d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/VETNonmanifoldMesh.h @@ -0,0 +1,213 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include + +// The VETNonmanifoldMesh class represents an edge-triangle nonmanifold mesh +// but additionally stores vertex adjacency information. + +namespace WwiseGTE +{ + class VETNonmanifoldMesh : public ETNonmanifoldMesh + { + public: + // Vertex data types. + class Vertex; + typedef std::shared_ptr(*VCreator)(int); + typedef std::map> VMap; + + // Vertex object. + class Vertex + { + public: + virtual ~Vertex() = default; + + Vertex(int vIndex) + : + V(vIndex) + { + } + + // The index into the vertex pool of the mesh. + int V; + + bool operator<(Vertex const& other) const + { + return V < other.V; + } + + // Adjacent objects. + std::set VAdjacent; + std::set, SharedPtrLT> EAdjacent; + std::set, SharedPtrLT> TAdjacent; + }; + + + // Construction and destruction. + virtual ~VETNonmanifoldMesh() = default; + + VETNonmanifoldMesh(VCreator vCreator = nullptr, ECreator eCreator = nullptr, TCreator tCreator = nullptr) + : + ETNonmanifoldMesh(eCreator, tCreator), + mVCreator(vCreator ? vCreator : CreateVertex) + { + } + + // Support for a deep copy of the mesh. The mVMap, mEMap, and mTMap + // objects have dynamically allocated memory for vertices, edges, and + // triangles. A shallow copy of the pointers to this memory is + // problematic. Allowing sharing, say, via std::shared_ptr, is an + // option but not really the intent of copying the mesh graph. + VETNonmanifoldMesh(VETNonmanifoldMesh const& mesh) + { + *this = mesh; + } + + VETNonmanifoldMesh& operator=(VETNonmanifoldMesh const& mesh) + { + Clear(); + mVCreator = mesh.mVCreator; + ETNonmanifoldMesh::operator=(mesh); + return *this; + } + + // Member access. + inline VMap const& GetVertices() const + { + return mVMap; + } + + // If is not in the mesh, a Triangle object is created and + // returned; otherwise, is in the mesh and nullptr is + // returned. + virtual std::shared_ptr Insert(int v0, int v1, int v2) override + { + std::shared_ptr tri = ETNonmanifoldMesh::Insert(v0, v1, v2); + if (!tri) + { + return nullptr; + } + + for (int i = 0; i < 3; ++i) + { + int vIndex = tri->V[i]; + auto vItem = mVMap.find(vIndex); + std::shared_ptr vertex; + if (vItem == mVMap.end()) + { + vertex = mVCreator(vIndex); + mVMap[vIndex] = vertex; + } + else + { + vertex = vItem->second; + } + + vertex->TAdjacent.insert(tri); + + for (int j = 0; j < 3; ++j) + { + auto edge = tri->E[j].lock(); + LogAssert(edge != nullptr, "Unexpected condition."); + if (edge->V[0] == vIndex) + { + vertex->VAdjacent.insert(edge->V[1]); + vertex->EAdjacent.insert(edge); + } + else if (edge->V[1] == vIndex) + { + vertex->VAdjacent.insert(edge->V[0]); + vertex->EAdjacent.insert(edge); + } + } + } + + return tri; + } + + // If is in the mesh, it is removed and 'true' is returned; + // otherwise, is not in the mesh and 'false' is returned. + virtual bool Remove(int v0, int v1, int v2) override + { + auto tItem = mTMap.find(TriangleKey(v0, v1, v2)); + if (tItem == mTMap.end()) + { + return false; + } + + std::shared_ptr tri = tItem->second; + for (int i = 0; i < 3; ++i) + { + int vIndex = tri->V[i]; + auto vItem = mVMap.find(vIndex); + LogAssert(vItem != mVMap.end(), "Unexpected condition."); + std::shared_ptr vertex = vItem->second; + for (int j = 0; j < 3; ++j) + { + auto edge = tri->E[j].lock(); + LogAssert(edge != nullptr, "Unexpected condition."); + + // If the edge will be removed by + // ETNonmanifoldMesh::Remove, remove the vertex + // references to it. + if (edge->T.size() == 1) + { + for (auto const& adjw : edge->T) + { + auto adj = adjw.lock(); + LogAssert(adj != nullptr, "Unexpected condition."); + if (edge->V[0] == vIndex) + { + vertex->VAdjacent.erase(edge->V[1]); + vertex->EAdjacent.erase(edge); + } + else if (edge->V[1] == vIndex) + { + vertex->VAdjacent.erase(edge->V[0]); + vertex->EAdjacent.erase(edge); + } + } + } + } + + vertex->TAdjacent.erase(tri); + + // If the vertex is no longer shared by any triangle, + // remove it. + if (vertex->TAdjacent.size() == 0) + { + LogAssert(vertex->VAdjacent.size() != 0 || vertex->EAdjacent.size() != 0, + "Malformed mesh."); + + mVMap.erase(vItem); + } + } + + return ETNonmanifoldMesh::Remove(v0, v1, v2); + } + + // Destroy the vertices, edges, and triangles to obtain an empty mesh. + virtual void Clear() override + { + mVMap.clear(); + ETNonmanifoldMesh::Clear(); + } + + protected: + // The vertex data and default vertex creation. + static std::shared_ptr CreateVertex(int vIndex) + { + return std::make_shared(vIndex); + } + + VCreator mVCreator; + VMap mVMap; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Vector.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Vector.h new file mode 100644 index 0000000..aa7217e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Vector.h @@ -0,0 +1,578 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class Vector + { + public: + // The tuple is uninitialized. + Vector() = default; + + // The tuple is fully initialized by the inputs. + Vector(std::array const& values) + : + mTuple(values) + { + } + + // At most N elements are copied from the initializer list, setting + // any remaining elements to zero. Create the zero vector using the + // syntax + // Vector zero{(Real)0}; + // WARNING: The C++ 11 specification states that + // Vector zero{}; + // will lead to a call of the default constructor, not the initializer + // constructor! + Vector(std::initializer_list values) + { + int const numValues = static_cast(values.size()); + if (N == numValues) + { + std::copy(values.begin(), values.end(), mTuple.begin()); + } + else if (N > numValues) + { + std::copy(values.begin(), values.end(), mTuple.begin()); + std::fill(mTuple.begin() + numValues, mTuple.end(), (Real)0); + } + else // N < numValues + { + std::copy(values.begin(), values.begin() + N, mTuple.begin()); + } + } + + // For 0 <= d < N, element d is 1 and all others are 0. If d is + // invalid, the zero vector is created. This is a convenience for + // creating the standard Euclidean basis vectors; see also + // MakeUnit(int) and Unit(int). + Vector(int d) + { + MakeUnit(d); + } + + // The copy constructor, destructor, and assignment operator are + // generated by the compiler. + + // Member access. The first operator[] returns a const reference + // rather than a Real value. This supports writing via standard file + // operations that require a const pointer to data. + inline int GetSize() const + { + return N; + } + + inline Real const& operator[](int i) const + { + return mTuple[i]; + } + + inline Real& operator[](int i) + { + return mTuple[i]; + } + + // Comparisons for sorted containers and geometric ordering. + inline bool operator==(Vector const& vec) const + { + return mTuple == vec.mTuple; + } + + inline bool operator!=(Vector const& vec) const + { + return mTuple != vec.mTuple; + } + + inline bool operator< (Vector const& vec) const + { + return mTuple < vec.mTuple; + } + + inline bool operator<=(Vector const& vec) const + { + return mTuple <= vec.mTuple; + } + + inline bool operator> (Vector const& vec) const + { + return mTuple > vec.mTuple; + } + + inline bool operator>=(Vector const& vec) const + { + return mTuple >= vec.mTuple; + } + + // Special vectors. + + // All components are 0. + void MakeZero() + { + std::fill(mTuple.begin(), mTuple.end(), (Real)0); + } + + // All components are 1. + void MakeOnes() + { + std::fill(mTuple.begin(), mTuple.end(), (Real)1); + } + + // Component d is 1, all others are zero. + void MakeUnit(int d) + { + std::fill(mTuple.begin(), mTuple.end(), (Real)0); + if (0 <= d && d < N) + { + mTuple[d] = (Real)1; + } + } + + static Vector Zero() + { + Vector v; + v.MakeZero(); + return v; + } + + static Vector Ones() + { + Vector v; + v.MakeOnes(); + return v; + } + + static Vector Unit(int d) + { + Vector v; + v.MakeUnit(d); + return v; + } + + protected: + // This data structure takes advantage of the built-in operator[], + // range checking, and visualizers in MSVS. + std::array mTuple; + }; + + // Unary operations. + template + Vector operator+(Vector const& v) + { + return v; + } + + template + Vector operator-(Vector const& v) + { + Vector result; + for (int i = 0; i < N; ++i) + { + result[i] = -v[i]; + } + return result; + } + + // Linear-algebraic operations. + template + Vector operator+(Vector const& v0, Vector const& v1) + { + Vector result = v0; + return result += v1; + } + + template + Vector operator-(Vector const& v0, Vector const& v1) + { + Vector result = v0; + return result -= v1; + } + + template + Vector operator*(Vector const& v, Real scalar) + { + Vector result = v; + return result *= scalar; + } + + template + Vector operator*(Real scalar, Vector const& v) + { + Vector result = v; + return result *= scalar; + } + + template + Vector operator/(Vector const& v, Real scalar) + { + Vector result = v; + return result /= scalar; + } + + template + Vector& operator+=(Vector& v0, Vector const& v1) + { + for (int i = 0; i < N; ++i) + { + v0[i] += v1[i]; + } + return v0; + } + + template + Vector& operator-=(Vector& v0, Vector const& v1) + { + for (int i = 0; i < N; ++i) + { + v0[i] -= v1[i]; + } + return v0; + } + + template + Vector& operator*=(Vector& v, Real scalar) + { + for (int i = 0; i < N; ++i) + { + v[i] *= scalar; + } + return v; + } + + template + Vector& operator/=(Vector& v, Real scalar) + { + if (scalar != (Real)0) + { + Real invScalar = (Real)1 / scalar; + for (int i = 0; i < N; ++i) + { + v[i] *= invScalar; + } + } + else + { + for (int i = 0; i < N; ++i) + { + v[i] = (Real)0; + } + } + return v; + } + + // Componentwise algebraic operations. + template + Vector operator*(Vector const& v0, Vector const& v1) + { + Vector result = v0; + return result *= v1; + } + + template + Vector operator/(Vector const& v0, Vector const& v1) + { + Vector result = v0; + return result /= v1; + } + + template + Vector& operator*=(Vector& v0, Vector const& v1) + { + for (int i = 0; i < N; ++i) + { + v0[i] *= v1[i]; + } + return v0; + } + + template + Vector& operator/=(Vector& v0, Vector const& v1) + { + for (int i = 0; i < N; ++i) + { + v0[i] /= v1[i]; + } + return v0; + } + + // Geometric operations. The functions with 'robust' set to 'false' use + // the standard algorithm for normalizing a vector by computing the length + // as a square root of the squared length and dividing by it. The results + // can be infinite (or NaN) if the length is zero. When 'robust' is set + // to 'true', the algorithm is designed to avoid floating-point overflow + // and sets the normalized vector to zero when the length is zero. + template + Real Dot(Vector const& v0, Vector const& v1) + { + Real dot = v0[0] * v1[0]; + for (int i = 1; i < N; ++i) + { + dot += v0[i] * v1[i]; + } + return dot; + } + + template + Real Length(Vector const& v, bool robust = false) + { + if (robust) + { + Real maxAbsComp = std::fabs(v[0]); + for (int i = 1; i < N; ++i) + { + Real absComp = std::fabs(v[i]); + if (absComp > maxAbsComp) + { + maxAbsComp = absComp; + } + } + + Real length; + if (maxAbsComp > (Real)0) + { + Vector scaled = v / maxAbsComp; + length = maxAbsComp * std::sqrt(Dot(scaled, scaled)); + } + else + { + length = (Real)0; + } + return length; + } + else + { + return std::sqrt(Dot(v, v)); + } + } + + template + Real Normalize(Vector& v, bool robust = false) + { + if (robust) + { + Real maxAbsComp = std::fabs(v[0]); + for (int i = 1; i < N; ++i) + { + Real absComp = std::fabs(v[i]); + if (absComp > maxAbsComp) + { + maxAbsComp = absComp; + } + } + + Real length; + if (maxAbsComp > (Real)0) + { + v /= maxAbsComp; + length = std::sqrt(Dot(v, v)); + v /= length; + length *= maxAbsComp; + } + else + { + length = (Real)0; + for (int i = 0; i < N; ++i) + { + v[i] = (Real)0; + } + } + return length; + } + else + { + Real length = std::sqrt(Dot(v, v)); + if (length > (Real)0) + { + v /= length; + } + else + { + for (int i = 0; i < N; ++i) + { + v[i] = (Real)0; + } + } + return length; + } + } + + // Gram-Schmidt orthonormalization to generate orthonormal vectors from + // the linearly independent inputs. The function returns the smallest + // length of the unnormalized vectors computed during the process. If + // this value is nearly zero, it is possible that the inputs are linearly + // dependent (within numerical round-off errors). On input, + // 1 <= numElements <= N and v[0] through v[numElements-1] must be + // initialized. On output, the vectors v[0] through v[numElements-1] + // form an orthonormal set. + template + Real Orthonormalize(int numInputs, Vector* v, bool robust = false) + { + if (v && 1 <= numInputs && numInputs <= N) + { + Real minLength = Normalize(v[0], robust); + for (int i = 1; i < numInputs; ++i) + { + for (int j = 0; j < i; ++j) + { + Real dot = Dot(v[i], v[j]); + v[i] -= v[j] * dot; + } + Real length = Normalize(v[i], robust); + if (length < minLength) + { + minLength = length; + } + } + return minLength; + } + + return (Real)0; + } + + // Construct a single vector orthogonal to the nonzero input vector. If + // the maximum absolute component occurs at index i, then the orthogonal + // vector U has u[i] = v[i+1], u[i+1] = -v[i], and all other components + // zero. The index addition i+1 is computed modulo N. + template + Vector GetOrthogonal(Vector const& v, bool unitLength) + { + Real cmax = std::fabs(v[0]); + int imax = 0; + for (int i = 1; i < N; ++i) + { + Real c = std::fabs(v[i]); + if (c > cmax) + { + cmax = c; + imax = i; + } + } + + Vector result; + result.MakeZero(); + int inext = imax + 1; + if (inext == N) + { + inext = 0; + } + result[imax] = v[inext]; + result[inext] = -v[imax]; + if (unitLength) + { + Real sqrDistance = result[imax] * result[imax] + result[inext] * result[inext]; + Real invLength = ((Real)1) / std::sqrt(sqrDistance); + result[imax] *= invLength; + result[inext] *= invLength; + } + return result; + } + + // Compute the axis-aligned bounding box of the vectors. The return value + // is 'true' iff the inputs are valid, in which case vmin and vmax have + // valid values. + template + bool ComputeExtremes(int numVectors, Vector const* v, + Vector& vmin, Vector& vmax) + { + if (v && numVectors > 0) + { + vmin = v[0]; + vmax = vmin; + for (int j = 1; j < numVectors; ++j) + { + Vector const& vec = v[j]; + for (int i = 0; i < N; ++i) + { + if (vec[i] < vmin[i]) + { + vmin[i] = vec[i]; + } + else if (vec[i] > vmax[i]) + { + vmax[i] = vec[i]; + } + } + } + return true; + } + + return false; + } + + // Lift n-tuple v to homogeneous (n+1)-tuple (v,last). + template + Vector HLift(Vector const& v, Real last) + { + Vector result; + for (int i = 0; i < N; ++i) + { + result[i] = v[i]; + } + result[N] = last; + return result; + } + + // Project homogeneous n-tuple v = (u,v[n-1]) to (n-1)-tuple u. + template + Vector HProject(Vector const& v) + { + static_assert(N >= 2, "Invalid dimension."); + Vector result; + for (int i = 0; i < N - 1; ++i) + { + result[i] = v[i]; + } + return result; + } + + // Lift n-tuple v = (w0,w1) to (n+1)-tuple u = (w0,u[inject],w1). By + // inference, w0 is a (inject)-tuple [nonexistent when inject=0] and w1 is + // a (n-inject)-tuple [nonexistent when inject=n]. + template + Vector Lift(Vector const& v, int inject, Real value) + { + Vector result; + int i; + for (i = 0; i < inject; ++i) + { + result[i] = v[i]; + } + result[i] = value; + int j = i; + for (++j; i < N; ++i, ++j) + { + result[j] = v[i]; + } + return result; + } + + // Project n-tuple v = (w0,v[reject],w1) to (n-1)-tuple u = (w0,w1). By + // inference, w0 is a (reject)-tuple [nonexistent when reject=0] and w1 is + // a (n-1-reject)-tuple [nonexistent when reject=n-1]. + template + Vector Project(Vector const& v, int reject) + { + static_assert(N >= 2, "Invalid dimension."); + Vector result; + for (int i = 0, j = 0; i < N - 1; ++i, ++j) + { + if (j == reject) + { + ++j; + } + result[i] = v[j]; + } + return result; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Vector2.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Vector2.h new file mode 100644 index 0000000..6a6bd33 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Vector2.h @@ -0,0 +1,254 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2020.01.10 + +#pragma once + +#include + +namespace WwiseGTE +{ + // Template alias for convenience. + template + using Vector2 = Vector<2, Real>; + + // Compute the perpendicular using the formal determinant, + // perp = det{{e0,e1},{x0,x1}} = (x1,-x0) + // where e0 = (1,0), e1 = (0,1), and v = (x0,x1). + template + Vector2 Perp(Vector2 const& v) + { + return Vector2{ v[1], -v[0] }; + } + + // Compute the normalized perpendicular. + template + Vector2 UnitPerp(Vector2 const& v, bool robust = false) + { + Vector2 unitPerp{ v[1], -v[0] }; + Normalize(unitPerp, robust); + return unitPerp; + } + + // Compute Dot((x0,x1),Perp(y0,y1)) = x0*y1 - x1*y0, where v0 = (x0,x1) + // and v1 = (y0,y1). + template + Real DotPerp(Vector2 const& v0, Vector2 const& v1) + { + return Dot(v0, Perp(v1)); + } + + // Compute a right-handed orthonormal basis for the orthogonal complement + // of the input vectors. The function returns the smallest length of the + // unnormalized vectors computed during the process. If this value is + // nearly zero, it is possible that the inputs are linearly dependent + // (within numerical round-off errors). On input, numInputs must be 1 and + // v[0] must be initialized. On output, the vectors v[0] and v[1] form an + // orthonormal set. + template + Real ComputeOrthogonalComplement(int numInputs, Vector2* v, bool robust = false) + { + if (numInputs == 1) + { + v[1] = -Perp(v[0]); + return Orthonormalize<2, Real>(2, v, robust); + } + + return (Real)0; + } + + // Compute the barycentric coordinates of the point P with respect to the + // triangle , P = b0*V0 + b1*V1 + b2*V2, where b0 + b1 + b2 = 1. + // The return value is 'true' iff {V0,V1,V2} is a linearly independent + // set. Numerically, this is measured by |det[V0 V1 V2]| <= epsilon. The + // values bary[] are valid only when the return value is 'true' but set to + // zero when the return value is 'false'. + template + bool ComputeBarycentrics(Vector2 const& p, Vector2 const& v0, + Vector2 const& v1, Vector2 const& v2, Real bary[3], + Real epsilon = (Real)0) + { + // Compute the vectors relative to V2 of the triangle. + Vector2 diff[3] = { v0 - v2, v1 - v2, p - v2 }; + + Real det = DotPerp(diff[0], diff[1]); + if (det < -epsilon || det > epsilon) + { + Real invDet = (Real)1 / det; + bary[0] = DotPerp(diff[2], diff[1]) * invDet; + bary[1] = DotPerp(diff[0], diff[2]) * invDet; + bary[2] = (Real)1 - bary[0] - bary[1]; + return true; + } + + for (int i = 0; i < 3; ++i) + { + bary[i] = (Real)0; + } + return false; + } + + // Get intrinsic information about the input array of vectors. The return + // value is 'true' iff the inputs are valid (numVectors > 0, v is not + // null, and epsilon >= 0), in which case the class members are valid. + template + class IntrinsicsVector2 + { + public: + // The constructor sets the class members based on the input set. + IntrinsicsVector2(int numVectors, Vector2 const* v, Real inEpsilon) + : + epsilon(inEpsilon), + dimension(0), + maxRange((Real)0), + origin{ (Real)0, (Real)0 }, + extremeCCW(false) + { + min[0] = (Real)0; + min[1] = (Real)0; + direction[0] = { (Real)0, (Real)0 }; + direction[1] = { (Real)0, (Real)0 }; + extreme[0] = 0; + extreme[1] = 0; + extreme[2] = 0; + + if (numVectors > 0 && v && epsilon >= (Real)0) + { + // Compute the axis-aligned bounding box for the input + // vectors. Keep track of the indices into 'vectors' for the + // current min and max. + int j, indexMin[2], indexMax[2]; + for (j = 0; j < 2; ++j) + { + min[j] = v[0][j]; + max[j] = min[j]; + indexMin[j] = 0; + indexMax[j] = 0; + } + + int i; + for (i = 1; i < numVectors; ++i) + { + for (j = 0; j < 2; ++j) + { + if (v[i][j] < min[j]) + { + min[j] = v[i][j]; + indexMin[j] = i; + } + else if (v[i][j] > max[j]) + { + max[j] = v[i][j]; + indexMax[j] = i; + } + } + } + + // Determine the maximum range for the bounding box. + maxRange = max[0] - min[0]; + extreme[0] = indexMin[0]; + extreme[1] = indexMax[0]; + Real range = max[1] - min[1]; + if (range > maxRange) + { + maxRange = range; + extreme[0] = indexMin[1]; + extreme[1] = indexMax[1]; + } + + // The origin is either the vector of minimum x0-value or + // vector of minimum x1-value. + origin = v[extreme[0]]; + + // Test whether the vector set is (nearly) a vector. + if (maxRange <= epsilon) + { + dimension = 0; + for (j = 0; j < 2; ++j) + { + extreme[j + 1] = extreme[0]; + } + return; + } + + // Test whether the vector set is (nearly) a line segment. We + // need direction[1] to span the orthogonal complement of + // direction[0]. + direction[0] = v[extreme[1]] - origin; + Normalize(direction[0], false); + direction[1] = -Perp(direction[0]); + + // Compute the maximum distance of the points from the line + // origin+t*direction[0]. + Real maxDistance = (Real)0; + Real maxSign = (Real)0; + extreme[2] = extreme[0]; + for (i = 0; i < numVectors; ++i) + { + Vector2 diff = v[i] - origin; + Real distance = Dot(direction[1], diff); + Real sign = (distance > (Real)0 ? (Real)1 : + (distance < (Real)0 ? (Real)-1 : (Real)0)); + distance = std::fabs(distance); + if (distance > maxDistance) + { + maxDistance = distance; + maxSign = sign; + extreme[2] = i; + } + } + + if (maxDistance <= epsilon * maxRange) + { + // The points are (nearly) on the line + // origin + t * direction[0]. + dimension = 1; + extreme[2] = extreme[1]; + return; + } + + dimension = 2; + extremeCCW = (maxSign > (Real)0); + return; + } + } + + // A nonnegative tolerance that is used to determine the intrinsic + // dimension of the set. + Real epsilon; + + // The intrinsic dimension of the input set, computed based on the + // nonnegative tolerance mEpsilon. + int dimension; + + // Axis-aligned bounding box of the input set. The maximum range is + // the larger of max[0]-min[0] and max[1]-min[1]. + Real min[2], max[2]; + Real maxRange; + + // Coordinate system. The origin is valid for any dimension d. The + // unit-length direction vector is valid only for 0 <= i < d. The + // extreme index is relative to the array of input points, and is also + // valid only for 0 <= i < d. If d = 0, all points are effectively + // the same, but the use of an epsilon may lead to an extreme index + // that is not zero. If d = 1, all points effectively lie on a line + // segment. If d = 2, the points are not collinear. + Vector2 origin; + Vector2 direction[2]; + + // The indices that define the maximum dimensional extents. The + // values extreme[0] and extreme[1] are the indices for the points + // that define the largest extent in one of the coordinate axis + // directions. If the dimension is 2, then extreme[2] is the index + // for the point that generates the largest extent in the direction + // perpendicular to the line through the points corresponding to + // extreme[0] and extreme[1]. The triangle formed by the points + // V[extreme[0]], V[extreme[1]], and V[extreme[2]] is clockwise or + // counterclockwise, the condition stored in extremeCCW. + int extreme[3]; + bool extremeCCW; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Vector3.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Vector3.h new file mode 100644 index 0000000..af2440b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Vector3.h @@ -0,0 +1,358 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2020.01.10 + +#pragma once + +#include + +namespace WwiseGTE +{ + // Template alias for convenience. + template + using Vector3 = Vector<3, Real>; + + // Cross, UnitCross, and DotCross have a template parameter N that should + // be 3 or 4. The latter case supports affine vectors in 4D (last + // component w = 0) when you want to use 4-tuples and 4x4 matrices for + // affine algebra. + + // Compute the cross product using the formal determinant: + // cross = det{{e0,e1,e2},{x0,x1,x2},{y0,y1,y2}} + // = (x1*y2-x2*y1, x2*y0-x0*y2, x0*y1-x1*y0) + // where e0 = (1,0,0), e1 = (0,1,0), e2 = (0,0,1), v0 = (x0,x1,x2), and + // v1 = (y0,y1,y2). + template + Vector Cross(Vector const& v0, Vector const& v1) + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + + Vector result; + result.MakeZero(); + result[0] = v0[1] * v1[2] - v0[2] * v1[1]; + result[1] = v0[2] * v1[0] - v0[0] * v1[2]; + result[2] = v0[0] * v1[1] - v0[1] * v1[0]; + return result; + } + + // Compute the normalized cross product. + template + Vector UnitCross(Vector const& v0, Vector const& v1, bool robust = false) + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + Vector unitCross = Cross(v0, v1); + Normalize(unitCross, robust); + return unitCross; + } + + // Compute Dot((x0,x1,x2),Cross((y0,y1,y2),(z0,z1,z2)), the triple scalar + // product of three vectors, where v0 = (x0,x1,x2), v1 = (y0,y1,y2), and + // v2 is (z0,z1,z2). + template + Real DotCross(Vector const& v0, Vector const& v1, + Vector const& v2) + { + static_assert(N == 3 || N == 4, "Dimension must be 3 or 4."); + return Dot(v0, Cross(v1, v2)); + } + + // Compute a right-handed orthonormal basis for the orthogonal complement + // of the input vectors. The function returns the smallest length of the + // unnormalized vectors computed during the process. If this value is + // nearly zero, it is possible that the inputs are linearly dependent + // (within numerical round-off errors). On input, numInputs must be 1 or + // 2 and v[0] through v[numInputs-1] must be initialized. On output, the + // vectors v[0] through v[2] form an orthonormal set. + template + Real ComputeOrthogonalComplement(int numInputs, Vector3* v, bool robust = false) + { + if (numInputs == 1) + { + if (std::fabs(v[0][0]) > std::fabs(v[0][1])) + { + v[1] = { -v[0][2], (Real)0, +v[0][0] }; + } + else + { + v[1] = { (Real)0, +v[0][2], -v[0][1] }; + } + numInputs = 2; + } + + if (numInputs == 2) + { + v[2] = Cross(v[0], v[1]); + return Orthonormalize<3, Real>(3, v, robust); + } + + return (Real)0; + } + + // Compute the barycentric coordinates of the point P with respect to the + // tetrahedron , P = b0*V0 + b1*V1 + b2*V2 + b3*V3, where + // b0 + b1 + b2 + b3 = 1. The return value is 'true' iff {V0,V1,V2,V3} is + // a linearly independent set. Numerically, this is measured by + // |det[V0 V1 V2 V3]| <= epsilon. The values bary[] are valid only when + // the return value is 'true' but set to zero when the return value is + // 'false'. + template + bool ComputeBarycentrics(Vector3 const& p, Vector3 const& v0, + Vector3 const& v1, Vector3 const& v2, Vector3 const& v3, + Real bary[4], Real epsilon = (Real)0) + { + // Compute the vectors relative to V3 of the tetrahedron. + Vector3 diff[4] = { v0 - v3, v1 - v3, v2 - v3, p - v3 }; + + Real det = DotCross(diff[0], diff[1], diff[2]); + if (det < -epsilon || det > epsilon) + { + Real invDet = ((Real)1) / det; + bary[0] = DotCross(diff[3], diff[1], diff[2]) * invDet; + bary[1] = DotCross(diff[3], diff[2], diff[0]) * invDet; + bary[2] = DotCross(diff[3], diff[0], diff[1]) * invDet; + bary[3] = (Real)1 - bary[0] - bary[1] - bary[2]; + return true; + } + + for (int i = 0; i < 4; ++i) + { + bary[i] = (Real)0; + } + return false; + } + + // Get intrinsic information about the input array of vectors. The return + // value is 'true' iff the inputs are valid (numVectors > 0, v is not + // null, and epsilon >= 0), in which case the class members are valid. + template + class IntrinsicsVector3 + { + public: + // The constructor sets the class members based on the input set. + IntrinsicsVector3(int numVectors, Vector3 const* v, Real inEpsilon) + : + epsilon(inEpsilon), + dimension(0), + maxRange((Real)0), + origin{ (Real)0, (Real)0, (Real)0 }, + extremeCCW(false) + { + min[0] = (Real)0; + min[1] = (Real)0; + min[2] = (Real)0; + direction[0] = { (Real)0, (Real)0, (Real)0 }; + direction[1] = { (Real)0, (Real)0, (Real)0 }; + direction[2] = { (Real)0, (Real)0, (Real)0 }; + extreme[0] = 0; + extreme[1] = 0; + extreme[2] = 0; + extreme[3] = 0; + + if (numVectors > 0 && v && epsilon >= (Real)0) + { + // Compute the axis-aligned bounding box for the input vectors. + // Keep track of the indices into 'vectors' for the current + // min and max. + int j, indexMin[3], indexMax[3]; + for (j = 0; j < 3; ++j) + { + min[j] = v[0][j]; + max[j] = min[j]; + indexMin[j] = 0; + indexMax[j] = 0; + } + + int i; + for (i = 1; i < numVectors; ++i) + { + for (j = 0; j < 3; ++j) + { + if (v[i][j] < min[j]) + { + min[j] = v[i][j]; + indexMin[j] = i; + } + else if (v[i][j] > max[j]) + { + max[j] = v[i][j]; + indexMax[j] = i; + } + } + } + + // Determine the maximum range for the bounding box. + maxRange = max[0] - min[0]; + extreme[0] = indexMin[0]; + extreme[1] = indexMax[0]; + Real range = max[1] - min[1]; + if (range > maxRange) + { + maxRange = range; + extreme[0] = indexMin[1]; + extreme[1] = indexMax[1]; + } + range = max[2] - min[2]; + if (range > maxRange) + { + maxRange = range; + extreme[0] = indexMin[2]; + extreme[1] = indexMax[2]; + } + + // The origin is either the vector of minimum x0-value, vector + // of minimum x1-value, or vector of minimum x2-value. + origin = v[extreme[0]]; + + // Test whether the vector set is (nearly) a vector. + if (maxRange <= epsilon) + { + dimension = 0; + for (j = 0; j < 3; ++j) + { + extreme[j + 1] = extreme[0]; + } + return; + } + + // Test whether the vector set is (nearly) a line segment. We + // need {direction[2],direction[3]} to span the orthogonal + // complement of direction[0]. + direction[0] = v[extreme[1]] - origin; + Normalize(direction[0], false); + if (std::fabs(direction[0][0]) > std::fabs(direction[0][1])) + { + direction[1][0] = -direction[0][2]; + direction[1][1] = (Real)0; + direction[1][2] = +direction[0][0]; + } + else + { + direction[1][0] = (Real)0; + direction[1][1] = +direction[0][2]; + direction[1][2] = -direction[0][1]; + } + Normalize(direction[1], false); + direction[2] = Cross(direction[0], direction[1]); + + // Compute the maximum distance of the points from the line + // origin + t * direction[0]. + Real maxDistance = (Real)0; + Real distance, dot; + extreme[2] = extreme[0]; + for (i = 0; i < numVectors; ++i) + { + Vector3 diff = v[i] - origin; + dot = Dot(direction[0], diff); + Vector3 proj = diff - dot * direction[0]; + distance = Length(proj, false); + if (distance > maxDistance) + { + maxDistance = distance; + extreme[2] = i; + } + } + + if (maxDistance <= epsilon * maxRange) + { + // The points are (nearly) on the line + // origin + t * direction[0]. + dimension = 1; + extreme[2] = extreme[1]; + extreme[3] = extreme[1]; + return; + } + + // Test whether the vector set is (nearly) a planar polygon. + // The point v[extreme[2]] is farthest from the line: + // origin + t * direction[0]. The vector + // v[extreme[2]] - origin is not necessarily perpendicular to + // direction[0], so project out the direction[0] component so + // that the result is perpendicular to direction[0]. + direction[1] = v[extreme[2]] - origin; + dot = Dot(direction[0], direction[1]); + direction[1] -= dot * direction[0]; + Normalize(direction[1], false); + + // We need direction[2] to span the orthogonal complement of + // {direction[0],direction[1]}. + direction[2] = Cross(direction[0], direction[1]); + + // Compute the maximum distance of the points from the plane + // origin+t0 * direction[0] + t1 * direction[1]. + maxDistance = (Real)0; + Real maxSign = (Real)0; + extreme[3] = extreme[0]; + for (i = 0; i < numVectors; ++i) + { + Vector3 diff = v[i] - origin; + distance = Dot(direction[2], diff); + Real sign = (distance > (Real)0 ? (Real)1 : + (distance < (Real)0 ? (Real)-1 : (Real)0)); + distance = std::fabs(distance); + if (distance > maxDistance) + { + maxDistance = distance; + maxSign = sign; + extreme[3] = i; + } + } + + if (maxDistance <= epsilon * maxRange) + { + // The points are (nearly) on the plane + // origin + t0 * direction[0] + t1 * direction[1]. + dimension = 2; + extreme[3] = extreme[2]; + return; + } + + dimension = 3; + extremeCCW = (maxSign > (Real)0); + return; + } + } + + // A nonnegative tolerance that is used to determine the intrinsic + // dimension of the set. + Real epsilon; + + // The intrinsic dimension of the input set, computed based on the + // nonnegative tolerance mEpsilon. + int dimension; + + // Axis-aligned bounding box of the input set. The maximum range is + // the larger of max[0]-min[0], max[1]-min[1], and max[2]-min[2]. + Real min[3], max[3]; + Real maxRange; + + // Coordinate system. The origin is valid for any dimension d. The + // unit-length direction vector is valid only for 0 <= i < d. The + // extreme index is relative to the array of input points, and is also + // valid only for 0 <= i < d. If d = 0, all points are effectively + // the same, but the use of an epsilon may lead to an extreme index + // that is not zero. If d = 1, all points effectively lie on a line + // segment. If d = 2, all points effectively line on a plane. If + // d = 3, the points are not coplanar. + Vector3 origin; + Vector3 direction[3]; + + // The indices that define the maximum dimensional extents. The + // values extreme[0] and extreme[1] are the indices for the points + // that define the largest extent in one of the coordinate axis + // directions. If the dimension is 2, then extreme[2] is the index + // for the point that generates the largest extent in the direction + // perpendicular to the line through the points corresponding to + // extreme[0] and extreme[1]. Furthermore, if the dimension is 3, + // then extreme[3] is the index for the point that generates the + // largest extent in the direction perpendicular to the triangle + // defined by the other extreme points. The tetrahedron formed by the + // points V[extreme[0]], V[extreme[1]], V[extreme[2]], and + // V[extreme[3]] is clockwise or counterclockwise, the condition + // stored in extremeCCW. + int extreme[4]; + bool extremeCCW; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Vector4.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Vector4.h new file mode 100644 index 0000000..880c1b1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/Vector4.h @@ -0,0 +1,167 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + // Template alias for convenience. + template + using Vector4 = Vector<4, Real>; + + // In Vector3.h, the Vector3 Cross, UnitCross, and DotCross have a + // template parameter N that should be 3 or 4. The latter case supports + // affine vectors in 4D (last component w = 0) when you want to use + // 4-tuples and 4x4 matrices for affine algebra. Thus, you may use those + // template functions for Vector4. + + // Compute the hypercross product using the formal determinant: + // hcross = det{{e0,e1,e2,e3},{x0,x1,x2,x3},{y0,y1,y2,y3},{z0,z1,z2,z3}} + // where e0 = (1,0,0,0), e1 = (0,1,0,0), e2 = (0,0,1,0), e3 = (0,0,0,1), + // v0 = (x0,x1,x2,x3), v1 = (y0,y1,y2,y3), and v2 = (z0,z1,z2,z3). + template + Vector4 HyperCross(Vector4 const& v0, Vector4 const& v1, Vector4 const& v2) + { + Real m01 = v0[0] * v1[1] - v0[1] * v1[0]; // x0*y1 - y0*x1 + Real m02 = v0[0] * v1[2] - v0[2] * v1[0]; // x0*z1 - z0*x1 + Real m03 = v0[0] * v1[3] - v0[3] * v1[0]; // x0*w1 - w0*x1 + Real m12 = v0[1] * v1[2] - v0[2] * v1[1]; // y0*z1 - z0*y1 + Real m13 = v0[1] * v1[3] - v0[3] * v1[1]; // y0*w1 - w0*y1 + Real m23 = v0[2] * v1[3] - v0[3] * v1[2]; // z0*w1 - w0*z1 + return Vector4 + { + +m23 * v2[1] - m13 * v2[2] + m12 * v2[3], // +m23*y2 - m13*z2 + m12*w2 + -m23 * v2[0] + m03 * v2[2] - m02 * v2[3], // -m23*x2 + m03*z2 - m02*w2 + +m13 * v2[0] - m03 * v2[1] + m01 * v2[3], // +m13*x2 - m03*y2 + m01*w2 + -m12 * v2[0] + m02 * v2[1] - m01 * v2[2] // -m12*x2 + m02*y2 - m01*z2 + }; + } + + // Compute the normalized hypercross product. + template + Vector4 UnitHyperCross(Vector4 const& v0, + Vector4 const& v1, Vector4 const& v2, bool robust = false) + { + Vector4 unitHyperCross = HyperCross(v0, v1, v2); + Normalize(unitHyperCross, robust); + return unitHyperCross; + } + + // Compute Dot(HyperCross((x0,x1,x2,x3),(y0,y1,y2,y3),(z0,z1,z2,z3)), + // (w0,w1,w2,w3)), where v0 = (x0,x1,x2,x3), v1 = (y0,y1,y2,y3), + // v2 = (z0,z1,z2,z3), and v3 = (w0,w1,w2,w3). + template + Real DotHyperCross(Vector4 const& v0, Vector4 const& v1, + Vector4 const& v2, Vector4 const& v3) + { + return Dot(HyperCross(v0, v1, v2), v3); + } + + // Compute a right-handed orthonormal basis for the orthogonal complement + // of the input vectors. The function returns the smallest length of the + // unnormalized vectors computed during the process. If this value is + // nearly zero, it is possible that the inputs are linearly dependent + // (within numerical round-off errors). On input, numInputs must be 1, 2 + // or 3, and v[0] through v[numInputs-1] must be initialized. On output, + // the vectors v[0] through v[3] form an orthonormal set. + template + Real ComputeOrthogonalComplement(int numInputs, Vector4* v, bool robust = false) + { + if (numInputs == 1) + { + int maxIndex = 0; + Real maxAbsValue = std::fabs(v[0][0]); + for (int i = 1; i < 4; ++i) + { + Real absValue = std::fabs(v[0][i]); + if (absValue > maxAbsValue) + { + maxIndex = i; + maxAbsValue = absValue; + } + } + + if (maxIndex < 2) + { + v[1][0] = -v[0][1]; + v[1][1] = +v[0][0]; + v[1][2] = (Real)0; + v[1][3] = (Real)0; + } + else if (maxIndex == 3) + { + // Generally, you can skip this clause and swap the last two + // components. However, by swapping 2 and 3 in this case, we + // allow the function to work properly when the inputs are 3D + // vectors represented as 4D affine vectors (w = 0). + v[1][0] = (Real)0; + v[1][1] = +v[0][2]; + v[1][2] = -v[0][1]; + v[1][3] = (Real)0; + } + else + { + v[1][0] = (Real)0; + v[1][1] = (Real)0; + v[1][2] = -v[0][3]; + v[1][3] = +v[0][2]; + } + + numInputs = 2; + } + + if (numInputs == 2) + { + Real det[6] = + { + v[0][0] * v[1][1] - v[1][0] * v[0][1], + v[0][0] * v[1][2] - v[1][0] * v[0][2], + v[0][0] * v[1][3] - v[1][0] * v[0][3], + v[0][1] * v[1][2] - v[1][1] * v[0][2], + v[0][1] * v[1][3] - v[1][1] * v[0][3], + v[0][2] * v[1][3] - v[1][2] * v[0][3] + }; + + int maxIndex = 0; + Real maxAbsValue = std::fabs(det[0]); + for (int i = 1; i < 6; ++i) + { + Real absValue = std::fabs(det[i]); + if (absValue > maxAbsValue) + { + maxIndex = i; + maxAbsValue = absValue; + } + } + + if (maxIndex == 0) + { + v[2] = { -det[4], +det[2], (Real)0, -det[0] }; + } + else if (maxIndex <= 2) + { + v[2] = { +det[5], (Real)0, -det[2], +det[1] }; + } + else + { + v[2] = { (Real)0, -det[5], +det[4], -det[3] }; + } + + numInputs = 3; + } + + if (numInputs == 3) + { + v[3] = HyperCross(v[0], v[1], v[2]); + return Orthonormalize<4, Real>(4, v, robust); + } + + return (Real)0; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/VertexAttribute.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/VertexAttribute.h new file mode 100644 index 0000000..1b506a2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/VertexAttribute.h @@ -0,0 +1,42 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +namespace WwiseGTE +{ + struct VertexAttribute + { + VertexAttribute(std::string inSemantic = "", void* inSource = nullptr, size_t inStride = 0) + : + semantic(inSemantic), + source(inSource), + stride(inStride) + { + } + + // The 'semantic' string allows you to query for a specific vertex + // attribute and use the 'source' and 'stride' to access the data + // of the attribute. For example, you might use the semantics + // "position" (px,py,pz), "normal" (nx,ny,nz), "tcoord" (texture + // coordinates (u,v)), "dpdu" (derivative of position with respect + // to u), or "dpdv" (derivative of position with respect to v) for + // mesh vertices. + // + // The source pointer must be 4-byte aligned. The stride must be + // positive and a multiple of 4. The pointer alignment constraint is + // guaranteed on 32-bit and 64-bit architectures. The stride constraint + // is reasonable given that (usually) geometric attributes are usually + // arrays of 'float' or 'double'. + + std::string semantic; + void* source; + size_t stride; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/VertexCollapseMesh.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/VertexCollapseMesh.h new file mode 100644 index 0000000..e6e612d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/VertexCollapseMesh.h @@ -0,0 +1,517 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace WwiseGTE +{ + template + class VertexCollapseMesh + { + public: + // Construction. + VertexCollapseMesh(int numPositions, Vector3 const* positions, + int numIndices, int const* indices) + : + mNumPositions(numPositions), + mPositions(positions), + mMesh(VCVertex::Create) + { + if (numPositions <= 0 || !positions || numIndices < 3 || !indices) + { + mNumPositions = 0; + mPositions = nullptr; + return; + } + + // Build the manifold mesh from the inputs. + int numTriangles = numIndices / 3; + int const* current = indices; + for (int t = 0; t < numTriangles; ++t) + { + int v0 = *current++; + int v1 = *current++; + int v2 = *current++; + mMesh.Insert(v0, v1, v2); + } + + // Locate the vertices (if any) on the mesh boundary. + auto const& vmap = mMesh.GetVertices(); + for (auto const& eelement : mMesh.GetEdges()) + { + auto edge = eelement.second; + if (!edge->T[1].lock()) + { + for (int i = 0; i < 2; ++i) + { + auto velement = vmap.find(edge->V[i]); + auto vertex = std::static_pointer_cast(velement->second); + vertex->isBoundary = true; + } + } + } + + // Build the priority queue of weights for the interior vertices. + mMinHeap.Reset((int)vmap.size()); + for (auto const& velement : vmap) + { + auto vertex = std::static_pointer_cast(velement.second); + + Real weight; + if (vertex->isBoundary) + { + weight = std::numeric_limits::max(); + } + else + { + weight = vertex->ComputeWeight(mPositions); + } + + auto record = mMinHeap.Insert(velement.first, weight); + mHeapRecords.insert(std::make_pair(velement.first, record)); + } + } + + // Decimate the mesh using vertex collapses + struct Record + { + // The index of the interior vertex that is removed from the mesh. + // The triangles adjacent to the vertex are 'removed' from the + // mesh. The polygon boundary of the adjacent triangles is + // triangulated and the new triangles are 'inserted' into the + // mesh. + int vertex; + std::vector> removed; + std::vector> inserted; + }; + + // Return 'true' when a vertex collapse occurs. Once the function + // returns 'false', no more vertex collapses are allowed so you may + // then stop calling the function. The implementation has several + // consistency tests that should not fail with a theoretically correct + // implementation. If a test fails, the function returns 'false' and + // the record.vertex is set to the invalid integer 0x80000000. When + // the Logger system is enabled, the failed tests are reported to any + // Logger listeners. + bool DoCollapse(Record& record) + { + record.vertex = 0x80000000; + record.removed.clear(); + record.inserted.clear(); + + if (mNumPositions == 0) + { + // The constructor failed, so there is nothing to collapse. + return false; + } + + while (mMinHeap.GetNumElements() > 0) + { + int v = -1; + Real weight = std::numeric_limits::max(); + mMinHeap.GetMinimum(v, weight); + if (weight == std::numeric_limits::max()) + { + // There are no more interior vertices to collapse. + return false; + } + + auto const& vmap = mMesh.GetVertices(); + auto velement = vmap.find(v); + if (velement == vmap.end()) + { + // Unexpected condition. + return false; + } + + auto vertex = std::static_pointer_cast(velement->second); + std::vector> removed, inserted; + std::vector linkVertices; + int result = TriangulateLink(vertex, removed, inserted, linkVertices); + if (result == VCM_UNEXPECTED_ERROR) + { + return false; + } + + if (result == VCM_ALLOWED) + { + result = Collapsed(removed, inserted, linkVertices); + if (result == VCM_UNEXPECTED_ERROR) + { + return false; + } + + if (result == VCM_ALLOWED) + { + // Remove the vertex and associated weight. + mMinHeap.Remove(v, weight); + mHeapRecords.erase(v); + + // Update the weights of the link vertices. + for (auto vlink : linkVertices) + { + velement = vmap.find(vlink); + if (velement == vmap.end()) + { + // Unexpected condition. + return false; + } + + vertex = std::static_pointer_cast(velement->second); + if (!vertex->isBoundary) + { + auto iter = mHeapRecords.find(vlink); + if (iter == mHeapRecords.end()) + { + // Unexpected condition. + return false; + } + + weight = vertex->ComputeWeight(mPositions); + mMinHeap.Update(iter->second, weight); + } + } + + record.vertex = v; + record.removed = std::move(removed); + record.inserted = std::move(inserted); + return true; + } + // else: result == VCM_DEFERRED + } + + // To get here, result must be VCM_DEFERRED. The vertex + // collapse would cause mesh fold-over. Temporarily set the + // edge weight to infinity. After removal of other triangles, + // the vertex weight will be updated to a finite value and the + // vertex possibly can be removed at that time. + auto iter = mHeapRecords.find(v); + if (iter == mHeapRecords.end()) + { + // Unexpected condition. + return false; + } + mMinHeap.Update(iter->second, std::numeric_limits::max()); + } + + // We do not expect to reach this line of code, even for a closed + // mesh. However, the compiler does not know this, yet requires + // a return value. + return false; + } + + // Access the current state of the mesh, whether the original built + // in the constructor or a decimated mesh during DoCollapse calls. + inline ETManifoldMesh const& GetMesh() const + { + return mMesh; + } + + private: + struct VCVertex : public VETManifoldMesh::Vertex + { + VCVertex(int v) + : + VETManifoldMesh::Vertex(v), + normal(Vector3::Zero()), + isBoundary(false) + { + } + + static std::shared_ptr Create(int v) + { + return std::make_shared(v); + } + + // The weight depends on the area of the triangles sharing the + // vertex and the lengths of the projections of the adjacent + // vertices onto the vertex normal line. A side effect of the + // call is that the vertex normal is computed and stored. + Real ComputeWeight(Vector3 const* positions) + { + Real weight = (Real)0; + + normal = { (Real)0, (Real)0, (Real)0 }; + for (auto const& tri : TAdjacent) + { + Vector3 E0 = positions[tri->V[1]] - positions[tri->V[0]]; + Vector3 E1 = positions[tri->V[2]] - positions[tri->V[0]]; + Vector3 N = Cross(E0, E1); + normal += N; + weight += Length(N); + } + Normalize(normal); + + for (int index : VAdjacent) + { + Vector3 diff = positions[index] - positions[V]; + weight += std::fabs(Dot(normal, diff)); + } + + return weight; + } + + Vector3 normal; + bool isBoundary; + }; + + // The functions TriangulateLink and Collapsed return one of the + // enumerates described next. + // + // VCM_NO_MORE_ALLOWED: + // Either the mesh has no more interior vertices or a collapse + // will lead to a mesh fold-over or to a nonmanifold mesh. The + // returned value 'v' is invalid (0x80000000) and 'removed' and + // 'inserted' are empty. + // + // VCM_ALLOWED: + // An interior vertex v has been removed. This is allowed using + // the following algorithm. The vertex normal is the weighted + // average of non-unit-length normals of triangles sharing v. The + // weights are the triangle areas. The adjacent vertices are + // projected onto a plane containing v and having normal equal to + // the vertex normal. If the projection is a simple polygon in + // the plane, the collapse is allowed. The triangles sharing v + // are 'removed', the polygon is triangulated, and the new + // triangles are 'inserted' into the mesh. + // + // VCM_DEFERRED: + // If the projection polygon described in the previous case is not + // simple (at least one pair of edges overlaps at some + // edge-interior point), the collapse would produce a fold-over in + // the mesh. We do not collapse in this case. It is possible + // that such a vertex occurs in a later collapse as its neighbors + // are adjusted by collapses. When this case occurs, v is valid + // (even though the collapse was not allowed) but 'removed' and + // 'inserted' are empty. + // + // VCM_UNEXPECTED_ERROR: + // The code has several tests for conditions that are not expected + // to occur for a theoretically correct implementation. If you + // receive this error, file a bug report and provide a data set + // that caused the error. + enum + { + VCM_NO_MORE_ALLOWED, + VCM_ALLOWED, + VCM_DEFERRED, + VCM_UNEXPECTED_ERROR + }; + + int TriangulateLink(std::shared_ptr const& vertex, std::vector>& removed, + std::vector>& inserted, std::vector& linkVertices) const + { + // Create the (CCW) polygon boundary of the link of the vertex. + // The incoming vertex is interior, so the number of triangles + // sharing the vertex is equal to the number of vertices of the + // polygon. A precondition of the function call is that the + // vertex normal has already been computed. + + // Get the edges of the link that are opposite the incoming + // vertex. + int const numVertices = static_cast(vertex->TAdjacent.size()); + removed.resize(numVertices); + int j = 0; + std::map edgeMap; + for (auto tri : vertex->TAdjacent) + { + for (int i = 0; i < 3; ++i) + { + if (tri->V[i] == vertex->V) + { + edgeMap.insert(std::make_pair(tri->V[(i + 1) % 3], tri->V[(i + 2) % 3])); + break; + } + } + removed[j++] = TriangleKey(tri->V[0], tri->V[1], tri->V[2]); + } + if (edgeMap.size() != vertex->TAdjacent.size()) + { + return VCM_UNEXPECTED_ERROR; + } + + // Connect the edges into a polygon. + linkVertices.resize(numVertices); + auto iter = edgeMap.begin(); + for (int i = 0; i < numVertices; ++i) + { + linkVertices[i] = iter->first; + iter = edgeMap.find(iter->second); + if (iter == edgeMap.end()) + { + return VCM_UNEXPECTED_ERROR; + } + } + if (iter->first != linkVertices[0]) + { + return VCM_UNEXPECTED_ERROR; + } + + // Project the polygon onto the plane containing the incoming + // vertex and having the vertex normal. The projected polygon + // is computed so that the incoming vertex is projected to (0,0). + Vector3 center = mPositions[vertex->V]; + Vector3 basis[3]; + basis[0] = vertex->normal; + ComputeOrthogonalComplement(1, basis); + std::vector> projected(numVertices); + std::vector indices(numVertices); + for (int i = 0; i < numVertices; ++i) + { + Vector3 diff = mPositions[linkVertices[i]] - center; + projected[i][0] = Dot(basis[1], diff); + projected[i][1] = Dot(basis[2], diff); + indices[i] = i; + } + + // The polygon must be simple in order to triangulate it. + Polygon2 polygon(projected.data(), numVertices, indices.data(), true); + if (polygon.IsSimple()) + { + TriangulateEC triangulator(numVertices, projected.data()); + triangulator(); + auto const& triangles = triangulator.GetTriangles(); + if (triangles.size() == 0) + { + return VCM_UNEXPECTED_ERROR; + } + + int const numTriangles = static_cast(triangles.size()); + inserted.resize(numTriangles); + for (int t = 0; t < numTriangles; ++t) + { + inserted[t] = TriangleKey( + linkVertices[triangles[t][0]], + linkVertices[triangles[t][1]], + linkVertices[triangles[t][2]]); + } + return VCM_ALLOWED; + } + else + { + return VCM_DEFERRED; + } + } + + int Collapsed(std::vector> const& removed, + std::vector> const& inserted, std::vector const& linkVertices) + { + // The triangles that were disconnected from the link edges are + // guaranteed to allow manifold reconnection to 'inserted' + // triangles. On the insertion, each diagonal of the link becomes + // a mesh edge and shares two (link) triangles. It is possible + // that the mesh already contains the (diagonal) edge, which will + // lead to a nonmanifold connection, which we cannot allow. The + // following code traps this condition and restores the mesh to + // its state before the 'Remove(...)' call. + bool isCollapsible = true; + auto const& emap = mMesh.GetEdges(); + std::set> edges; + for (auto const& tri : inserted) + { + for (int k0 = 2, k1 = 0; k1 < 3; k0 = k1++) + { + EdgeKey edge(tri.V[k0], tri.V[k1]); + if (edges.find(edge) == edges.end()) + { + edges.insert(edge); + } + else + { + // The edge has been visited twice, so it is a + // diagonal of the link. + + auto eelement = emap.find(edge); + if (eelement != emap.end()) + { + if (eelement->second->T[1].lock()) + { + // The edge will not allow a manifold + // connection. + isCollapsible = false; + break; + } + } + + edges.erase(edge); + } + }; + + if (!isCollapsible) + { + return VCM_DEFERRED; + } + } + + // Remove the old triangle neighborhood, which will lead to the + // vertex itself being removed from the mesh. + for (auto tri : removed) + { + mMesh.Remove(tri.V[0], tri.V[1], tri.V[2]); + } + + // Insert the new triangulation. + for (auto const& tri : inserted) + { + mMesh.Insert(tri.V[0], tri.V[1], tri.V[2]); + } + + // If the Remove(...) calls remove a boundary vertex that is in + // the link vertices, the Insert(...) calls will insert the + // boundary vertex again. We must re-tag those boundary + // vertices. + auto const& vmap = mMesh.GetVertices(); + size_t const numVertices = linkVertices.size(); + for (size_t i0 = numVertices - 1, i1 = 0; i1 < numVertices; i0 = i1++) + { + EdgeKey ekey(linkVertices[i0], linkVertices[i1]); + auto eelement = emap.find(ekey); + if (eelement == emap.end()) + { + return VCM_UNEXPECTED_ERROR; + } + + auto edge = eelement->second; + if (!edge) + { + return VCM_UNEXPECTED_ERROR; + } + + if (edge->T[0].lock() && !edge->T[1].lock()) + { + for (int k = 0; k < 2; ++k) + { + auto velement = vmap.find(edge->V[k]); + if (velement == vmap.end()) + { + return VCM_UNEXPECTED_ERROR; + } + + auto vertex = std::static_pointer_cast(velement->second); + vertex->isBoundary = true; + } + } + } + + return VCM_ALLOWED; + } + + int mNumPositions; + Vector3 const* mPositions; + VETManifoldMesh mMesh; + + MinHeap mMinHeap; + std::map::Record*> mHeapRecords; + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/WeakPtrCompare.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/WeakPtrCompare.h new file mode 100644 index 0000000..c88036d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/GTE/Mathematics/WeakPtrCompare.h @@ -0,0 +1,81 @@ +// David Eberly, Geometric Tools, Redmond WA 98052 +// Copyright (c) 1998-2020 +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt +// Version: 4.0.2019.08.13 + +#pragma once + +#include + +// Comparison operators for std::weak_ptr objects. The type T must implement +// comparison operators. You must be careful when managing containers whose +// ordering is based on std::weak_ptr comparisons. The underlying objects +// can change, which invalidates the container ordering. If objects do not +// change while the container persists, these are safe to use. + +namespace WwiseGTE +{ + // wp0 == wp1 + template + struct WeakPtrEQ + { + bool operator()(std::weak_ptr const& wp0, std::weak_ptr const& wp1) const + { + auto sp0 = wp0.lock(), sp1 = wp1.lock(); + return (sp0 ? (sp1 ? *sp0 == *sp1 : false) : !sp1); + } + }; + + // wp0 != wp1 + template + struct WeakPtrNEQ + { + bool operator()(std::weak_ptr const& wp0, std::weak_ptr const& wp1) const + { + return !WeakPtrEQ()(wp0, wp1); + } + }; + + // wp0 < wp1 + template + struct WeakPtrLT + { + bool operator()(std::weak_ptr const& wp0, std::weak_ptr const& wp1) const + { + auto sp0 = wp0.lock(), sp1 = wp1.lock(); + return (sp1 ? (!sp0 || *sp0 < *sp1) : false); + } + }; + + // wp0 <= wp1 + template + struct WeakPtrLTE + { + bool operator()(std::weak_ptr const& wp0, std::weak_ptr const& wp1) const + { + return !WeakPtrLT()(wp1, wp0); + } + }; + + // wp0 > wp1 + template + struct WeakPtrGT + { + bool operator()(std::weak_ptr const& wp0, std::weak_ptr const& wp1) const + { + return WeakPtrLT()(wp1, wp0); + } + }; + + // wp0 >= wp1 + template + struct WeakPtrGTE + { + bool operator()(std::weak_ptr const& wp0, std::weak_ptr const& wp1) const + { + return !WeakPtrLT()(wp0, wp1); + } + }; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/ISoundBankInfoCache.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/ISoundBankInfoCache.h new file mode 100644 index 0000000..396d241 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/ISoundBankInfoCache.h @@ -0,0 +1,30 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkInclude.h" + +struct FGuid; +class FString; + +class AKAUDIO_API ISoundBankInfoCache +{ +public: + virtual ~ISoundBankInfoCache() {} + virtual bool IsSoundBankUpToUpdate(const FGuid& Id, const FString& Platform, const FString& Language, const uint32 Hash) const = 0; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/InitializationSettings/AkAudioSession.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/InitializationSettings/AkAudioSession.h new file mode 100644 index 0000000..e092f82 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/InitializationSettings/AkAudioSession.h @@ -0,0 +1,72 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Engine/EngineTypes.h" +#include "UObject/Object.h" + +#include "AkInclude.h" +#include "AkAudioSession.generated.h" + +UENUM() +enum class EAkAudioSessionCategory +{ + Ambient, + SoloAmbient, + PlayAndRecord, +}; + +UENUM(Meta = (Bitmask)) +enum class EAkAudioSessionCategoryOptions : uint32 +{ + MixWithOthers, + DuckOthers, + AllowBluetooth, + DefaultToSpeaker, +}; + +UENUM() +enum class EAkAudioSessionMode +{ + Default, + VoiceChat, + GameChat, + VideoRecording, + Measurement, + MoviePlayback, + VideoChat, +}; + +struct FAkInitializationStructure; + +USTRUCT() +struct FAkAudioSession +{ + GENERATED_BODY() + + UPROPERTY(Config, Category = "Ak Initialization Settings|Audio Session", EditAnywhere, meta = (ToolTip = "The IDs of the iOS audio session categories, useful for defining app-level audio behaviours such as inter-app audio mixing policies and audio routing behaviours.These IDs are functionally equivalent to the corresponding constants defined by the iOS audio session service back-end (AVAudioSession). Refer to Xcode documentation for details on the audio session categories.")) + EAkAudioSessionCategory AudioSessionCategory = EAkAudioSessionCategory::Ambient; + + UPROPERTY(Config, Category = "Ak Initialization Settings|Audio Session", EditAnywhere, meta = (Bitmask, BitmaskEnum = "/Script/AkAudio.EAkAudioSessionCategoryOptions", ToolTip = "The IDs of the iOS audio session category options, used for customizing the audio session category features. These IDs are functionally equivalent to the corresponding constants defined by the iOS audio session service back-end (AVAudioSession). Refer to Xcode documentation for details on the audio session category options.")) + uint32 AudioSessionCategoryOptions = 1 << (uint32)EAkAudioSessionCategoryOptions::DuckOthers; + + UPROPERTY(Config, Category = "Ak Initialization Settings|Audio Session", EditAnywhere, meta = (ToolTip = "The IDs of the iOS audio session modes, used for customizing the audio session for typical app types. These IDs are functionally equivalent to the corresponding constants defined by the iOS audio session service back-end (AVAudioSession). Refer to Xcode documentation for details on the audio session category options.")) + EAkAudioSessionMode AudioSessionMode = EAkAudioSessionMode::Default; + + void FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/InitializationSettings/AkInitializationSettings.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/InitializationSettings/AkInitializationSettings.h new file mode 100644 index 0000000..eb5806f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/InitializationSettings/AkInitializationSettings.h @@ -0,0 +1,341 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Engine/EngineTypes.h" +#include "AkInclude.h" +#include "AkInitializationSettings.generated.h" + +UENUM() +enum class EAkPanningRule +{ + Speakers = AkPanningRule::AkPanningRule_Speakers, + Headphones = AkPanningRule::AkPanningRule_Headphones, +}; + +UENUM() +enum class EAkChannelConfigType +{ + Anonymous = AkChannelConfigType::AK_ChannelConfigType_Anonymous, + Standard = AkChannelConfigType::AK_ChannelConfigType_Standard, + Ambisonic = AkChannelConfigType::AK_ChannelConfigType_Ambisonic, +}; + +UENUM(Meta = (Bitmask)) +enum class EAkChannelMask : uint32 +{ + FrontLeft, + FrontRight, + FrontCenter, + LowFrequency, + BackLeft, + BackRight, + BackCenter = BackRight + 3, + SideLeft, + SideRight, + + Top, + HeightFrontLeft, + HeightFrontCenter, + HeightFrontRight, + HeightBackLeft, + HeightBackCenter, + HeightBackRight, +}; + +static_assert((1 << (uint32)EAkChannelMask::FrontLeft) == AK_SPEAKER_FRONT_LEFT, "Review constants defined in \"include\\AK\\SoundEngine\\Common\\AkSpeakerConfig.h\""); +static_assert((1 << (uint32)EAkChannelMask::FrontRight) == AK_SPEAKER_FRONT_RIGHT, "Review constants defined in \"include\\AK\\SoundEngine\\Common\\AkSpeakerConfig.h\""); +static_assert((1 << (uint32)EAkChannelMask::FrontCenter) == AK_SPEAKER_FRONT_CENTER, "Review constants defined in \"include\\AK\\SoundEngine\\Common\\AkSpeakerConfig.h\""); +static_assert((1 << (uint32)EAkChannelMask::LowFrequency) == AK_SPEAKER_LOW_FREQUENCY, "Review constants defined in \"include\\AK\\SoundEngine\\Common\\AkSpeakerConfig.h\""); +static_assert((1 << (uint32)EAkChannelMask::BackLeft) == AK_SPEAKER_BACK_LEFT, "Review constants defined in \"include\\AK\\SoundEngine\\Common\\AkSpeakerConfig.h\""); +static_assert((1 << (uint32)EAkChannelMask::BackRight) == AK_SPEAKER_BACK_RIGHT, "Review constants defined in \"include\\AK\\SoundEngine\\Common\\AkSpeakerConfig.h\""); +static_assert((1 << (uint32)EAkChannelMask::BackCenter) == AK_SPEAKER_BACK_CENTER, "Review constants defined in \"include\\AK\\SoundEngine\\Common\\AkSpeakerConfig.h\""); +static_assert((1 << (uint32)EAkChannelMask::SideLeft) == AK_SPEAKER_SIDE_LEFT, "Review constants defined in \"include\\AK\\SoundEngine\\Common\\AkSpeakerConfig.h\""); +static_assert((1 << (uint32)EAkChannelMask::SideRight) == AK_SPEAKER_SIDE_RIGHT, "Review constants defined in \"include\\AK\\SoundEngine\\Common\\AkSpeakerConfig.h\""); +static_assert((1 << (uint32)EAkChannelMask::Top) == AK_SPEAKER_TOP, "Review constants defined in \"include\\AK\\SoundEngine\\Common\\AkSpeakerConfig.h\""); +static_assert((1 << (uint32)EAkChannelMask::HeightFrontLeft) == AK_SPEAKER_HEIGHT_FRONT_LEFT, "Review constants defined in \"include\\AK\\SoundEngine\\Common\\AkSpeakerConfig.h\""); +static_assert((1 << (uint32)EAkChannelMask::HeightFrontCenter) == AK_SPEAKER_HEIGHT_FRONT_CENTER, "Review constants defined in \"include\\AK\\SoundEngine\\Common\\AkSpeakerConfig.h\""); +static_assert((1 << (uint32)EAkChannelMask::HeightFrontRight) == AK_SPEAKER_HEIGHT_FRONT_RIGHT, "Review constants defined in \"include\\AK\\SoundEngine\\Common\\AkSpeakerConfig.h\""); +static_assert((1 << (uint32)EAkChannelMask::HeightBackLeft) == AK_SPEAKER_HEIGHT_BACK_LEFT, "Review constants defined in \"include\\AK\\SoundEngine\\Common\\AkSpeakerConfig.h\""); +static_assert((1 << (uint32)EAkChannelMask::HeightBackCenter) == AK_SPEAKER_HEIGHT_BACK_CENTER, "Review constants defined in \"include\\AK\\SoundEngine\\Common\\AkSpeakerConfig.h\""); +static_assert((1 << (uint32)EAkChannelMask::HeightBackRight) == AK_SPEAKER_HEIGHT_BACK_RIGHT, "Review constants defined in \"include\\AK\\SoundEngine\\Common\\AkSpeakerConfig.h\""); + + +UENUM() +enum class EAkCommSystem +{ + Socket = 0, // AkCommSettings::AkCommSystem_Socket, + HTCS = 1 // AkCommSettings::AkCommSystem_HTCS, +}; + +#if AK_ENABLE_COMMUNICATION +static_assert((uint32)EAkCommSystem::Socket == (uint32)AkCommSettings::AkCommSystem_Socket, "Review constants defined in \"include\\AK\\Comm\\AkCommunication.h\""); +static_assert((uint32)EAkCommSystem::HTCS == (uint32)AkCommSettings::AkCommSystem_HTCS, "Review constants defined in \"include\\AK\\Comm\\AkCommunication.h\""); +#endif + +struct FAkInitializationStructure +{ + FAkInitializationStructure(); + ~FAkInitializationStructure(); + + AkMemSettings MemSettings; + AkStreamMgrSettings StreamManagerSettings; + AkDeviceSettings DeviceSettings; + AkInitSettings InitSettings; + AkPlatformInitSettings PlatformInitSettings; + AkMusicSettings MusicSettings; + AkSpatialAudioInitSettings SpatialAudioInitSettings; +#ifndef AK_OPTIMIZED + AkCommSettings CommSettings; +#endif + + void SetPluginDllPath(const FString& PlatformArchitecture); + + typedef void* (*MemoryAllocFunction)(size_t); + typedef void(*MemoryFreeFunction)(void*, size_t); + + void SetupLLMAllocFunctions(MemoryAllocFunction alloc = nullptr, MemoryFreeFunction free = nullptr, bool UseMemTracker = true); +}; + + +USTRUCT() +struct FAkMainOutputSettings +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, Category="Ak Initialization Settings|Main Output Settings", meta = (ToolTip = "The name of a custom audio device to use. Custom audio devices are defined in the Audio Device ShareSet section of the Wwise project. Leave this empty to output normally through the default audio device.")) + FString AudioDeviceShareSet; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings|Main Output Settings", meta = (ToolTip = "Device-specific identifier when you are using multiple devices of the same type. Leave the setting at 0 (default) if you are using only one device.")) + uint32 DeviceID = AK_INVALID_UNIQUE_ID; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings|Main Output Settings", meta = (ToolTip = "Rule for 3D panning of signals routed to a stereo bus. In \"Speakers\" mode, the angle of the front loudspeakers is used. In \"Headphones\" mode, the speaker angles are superseded by constant power panning between two virtual microphones spaced 180 degrees apart.")) + EAkPanningRule PanningRule = EAkPanningRule::Speakers; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings|Main Output Settings", meta = (ToolTip = "A code that completes the identification of channels by uChannelMask. Anonymous: Channel mask == 0 and channels. Standard: Channels must be identified with standard defines in AkSpeakerConfigs. Ambisonic: Channel mask == 0 and channels follow standard ambisonic order.")) + EAkChannelConfigType ChannelConfigType = EAkChannelConfigType::Anonymous; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings|Main Output Settings", meta = (Bitmask, BitmaskEnum = "/Script/AkAudio.EAkChannelMask", ToolTip = "A bit field, whose channel identifiers depend on AkChannelConfigType (up to 20).")) + uint32 ChannelMask = 0; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings|Main Output Settings", meta = (ToolTip = "The number of channels, identified (deduced from channel mask) or anonymous (set directly).")) + uint32 NumberOfChannels = 0; + + void FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const; +}; + + +USTRUCT() +struct FAkSpatialAudioSettings +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings|Spatial Audio Settings", meta = (ToolTip = "Maximum number of portals that sound can propagate through.", ClampMin = "0", ClampMax = "8")) + uint32 MaxSoundPropagationDepth = AK_MAX_SOUND_PROPAGATION_DEPTH; + static_assert(AK_MAX_SOUND_PROPAGATION_DEPTH == 8, "AK_MAX_SOUND_PROPAGATION_DEPTH has changed values. Ensure that the limits of MaxSoundPropagationDepth are equally reflected."); + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings|Spatial Audio Settings", meta = (ToolTip = "Distance (in game units) that an emitter or listener has to move to trigger a recalculation of reflections/diffraction. Larger values can reduce the CPU load at the cost of reduced accuracy.", ClampMin = "0")) + float MovementThreshold = 10.f; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings|Spatial Audio Settings", meta = (ToolTip = "The number of primary rays used in stochastic ray casting. A larger number of rays will increase the chances of finding reflection and diffraction paths, but will result in higher CPU usage.")) + uint32 NumberOfPrimaryRays = 100; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings|Spatial Audio Settings", meta = (ToolTip = "Maximum reflection order - the number of 'bounces' in a reflection path. A higher reflection order renders more details at the expense of higher CPU usage.", ClampMin = "0", ClampMax = "4")) + uint32 ReflectionOrder = 1; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings|Spatial Audio Settings", meta = (ToolTip = "Maximum diffraction order: 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. 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.", ClampMin = "0", ClampMax = "8")) + uint32 DiffractionOrder = 4; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings|Spatial Audio Settings", meta = (ToolTip = "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. Set to 0 to disable diffraction on reflections.", ClampMin = "0", ClampMax = "4")) + uint32 DiffractionOnReflectionsOrder = 2; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings|Spatial Audio Settings", meta = (ToolTip = "Length of the rays that are cast inside Spatial Audio. Effectively caps the maximum length of an individual segment in a reflection or diffraction path.", ClampMin = "0")) + float MaximumPathLength = 10000.0f; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings|Spatial Audio Settings", meta = (DisplayName = "CPU Limit Percentage", ToolTip = "Controls the maximum percentage of an audio frame the raytracing engine can use. Percentage [0, 100] of the current audio frame. A value of 0 indicates no limit on the amount of CPU used for raytracing.", ClampMin = "0", ClampMax = "100")) + float CPULimitPercentage = 0.0f; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings|Spatial Audio Settings", meta = (DisplayName = "Load Balancing Spread", ToolTip = "The computation of spatial audio paths is spread on LoadBalancingSpread frames. Spreading the computation of paths over several frames can prevent CPU peaks. The spread introduces a delay in path computation.", ClampMin = "1")) + uint32 LoadBalancingSpread = 1; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings|Spatial Audio Settings", meta = (ToolTip = "Enable computation of geometric diffraction and transmission paths for all sources that have 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 thorugh (transmission) geometry. Setting to EnableGeometricDiffractionAndTransmission to false implies that geometry is only to be used for reflection calculation. Diffraction edges must be enabled on geometry for diffraction calculation. If EnableGeometricDiffractionAndTransmission is false but a sound has \"Enable Diffraction and Transmission\" checked in the positioning tab of the authoring tool, the sound will only diffract through portals but pass through geometry as if it is not there. One would typically disable this setting if the game intends to perform its own obstruction calculation, but in the situation where geometry is still passed to spatial audio for reflection calculation.")) + bool EnableGeometricDiffractionAndTransmission = true; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings|Spatial Audio Settings", meta = (ToolTip = "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 CalcEmitterVirtualPosition = true; + + void FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const; +}; + + +USTRUCT() +struct FAkCommunicationSettings +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings|Communication Settings", meta = (ToolTip = "Size of the communication pool.")) + uint32 PoolSize = 256 * 1024; + + enum { DefaultDiscoveryBroadcastPort = 24024, }; + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings|Communication Settings", meta = (ToolTip = "The port where the authoring application broadcasts \"Game Discovery\" requests to discover games running on the network. Default value: 24024. (Cannot be set to 0).")) + uint16 DiscoveryBroadcastPort = DefaultDiscoveryBroadcastPort; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings|Communication Settings", meta = (ToolTip = "The \"command\" channel port. Set to 0 to request a dynamic/ephemeral port.")) + uint16 CommandPort = 0; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings|Communication Settings", meta = (ToolTip = "The name used to identify this game within the authoring application. Leave empty to use FApp::GetProjectName().")) + FString NetworkName; + + void FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const; + +protected: + FString GetCommsNetworkName() const; +}; + + +USTRUCT() +struct FAkCommunicationSettingsWithSystemInitialization : public FAkCommunicationSettings +{ + GENERATED_BODY() + + UPROPERTY(Config, Category = "Ak Initialization Settings|Communication Settings", EditAnywhere, meta = (ToolTip = "Indicates whether or not to initialize the communication system. Some consoles have critical requirements for initialization of their communications systems. Set to false only if your game already uses sockets before sound engine initialization.")) + bool InitializeSystemComms = true; + + void FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const; +}; + +USTRUCT() +struct FAkCommunicationSettingsWithCommSelection : public FAkCommunicationSettings +{ + GENERATED_BODY() + + UPROPERTY(Config, Category = "Ak Initialization Settings|Communication Settings", EditAnywhere, meta = (ToolTip = "Select between Socket and HTCS communication protocol. Socket is the Default option.")) + EAkCommSystem CommunicationSystem = EAkCommSystem::Socket; + + void FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const; +}; + + +USTRUCT() +struct FAkCommonInitializationSettings +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (ToolTip = "Maximum number of memory pools. A memory pool is required for each loaded bank.")) + uint32 MaximumNumberOfMemoryPools = 256; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (ToolTip = "Maximum number of automation paths for positioning sounds.")) + uint32 MaximumNumberOfPositioningPaths = 255; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (ToolTip = "Size of the command queue.")) + uint32 CommandQueueSize = 256 * 1024; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (ToolTip = "Number of samples per audio frame (256, 512, 1024, or 2048).")) + uint32 SamplesPerFrame = 512; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (ToolTip = "Platform-independent initialization settings of output devices.")) + FAkMainOutputSettings MainOutputSettings; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (ToolTip = "Multiplication factor for all streaming look-ahead heuristic values.", ClampMin = "0.0", ClampMax = "1.0")) + float StreamingLookAheadRatio = 1.0f; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (ToolTip = "Number of refill buffers in voice buffer. Set to 2 for double-buffered. The default value is 4.")) + uint16 NumberOfRefillsInVoice = 4; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings") + FAkSpatialAudioSettings SpatialAudioSettings; + + void FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const; +}; + + +USTRUCT() +struct FAkCommonInitializationSettingsWithSampleRate : public FAkCommonInitializationSettings +{ + GENERATED_BODY() + + UPROPERTY(Config, EditAnywhere, Category = "Common Settings", meta = (ToolTip = "Sampling Rate. Default is 48000 Hz. Use 24000hz for low quality. Any positive reasonable sample rate is supported; however, be careful setting a custom value. Using an odd or really low sample rate may cause the sound engine to malfunction.")) + uint32 SampleRate = 48000; +}; + + +USTRUCT() +struct FAkAdvancedInitializationSettings +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (ToolTip = "Size of memory pool for I/O (for automatic streams). It is rounded down to a multiple of uGranularity and then passed directly to AK::MemoryMgr::CreatePool().")) + uint32 IO_MemorySize = 2 * 1024 * 1024; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (ToolTip = "I/O requests granularity (typical bytes/request).")) + uint32 IO_Granularity = 32 * 1024; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (ToolTip = "Targeted automatic stream buffer length (ms). When a stream reaches that buffering, it stops being scheduled for I/O except if the scheduler is idle.")) + float TargetAutoStreamBufferLength = 380.0f; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (ToolTip = "If true, the device attempts to reuse I/O buffers that have already been streamed from disk. This is particularly useful when streaming small looping sounds. However, there is a small increase in CPU usage when allocating memory, and a slightly larger memory footprint in the StreamManager pool.")) + bool UseStreamCache = false; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (ToolTip = "Maximum number of bytes that can be \"pinned\" using AK::SoundEngine::PinEventInStreamCache() or AK::IAkStreamMgr::PinFileInCache().")) + uint32 MaximumPinnedBytesInCache = (uint32)-1; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (ToolTip = "Set to true to enable AK::SoundEngine::PrepareGameSync usage.")) + bool EnableGameSyncPreparation = false; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (ToolTip = "Number of quanta ahead when continuous containers instantiate a new voice before the following sounds start playing. This look-ahead time allows I/O to occur, and is especially useful to reduce the latency of continuous containers with trigger rate or sample-accurate transitions.")) + uint32 ContinuousPlaybackLookAhead = 1; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (ToolTip = "Size of the monitoring queue pool. This parameter is ignored in Release builds.")) + uint32 MonitorQueuePoolSize = 1024 * 1024; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (ToolTip = "Time (in milliseconds) to wait for hardware devices to trigger an audio interrupt. If there is no interrupt after that time, the sound engine reverts to silent mode and continues operating until the hardware responds.")) + uint32 MaximumHardwareTimeoutMs = 1000; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (ToolTip = "Debug setting: Enable checks for out-of-range (and NAN) floats in the processing code. Do not enable in any normal usage because this setting uses a lot of CPU. It prints error messages in the log if invalid values are found at various points in the pipeline. Contact AK Support with the new error messages for more information.")) + bool DebugOutOfRangeCheckEnabled = false; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (EditCondition = "DebugOutOfRangeCheckEnabled", ToolTip = "Debug setting: Only used when Debug Out Of Range Check Enabled is true. This defines the maximum values samples can have. Normal audio must be contained within +1/-1. Set this limit to a value greater than 1 to allow temporary or short excursions out of range. The default value is 16.")) + float DebugOutOfRangeLimit = 16.f; + + void FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const; +}; + +USTRUCT() +struct FAkAdvancedInitializationSettingsWithMultiCoreRendering : public FAkAdvancedInitializationSettings +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (ToolTip = "Enable to run SoundEngine processing tasks on the Unreal Engine worker threads. Requires Editor restart.")) + bool EnableMultiCoreRendering = true; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (EditCondition = "EnableMultiCoreRendering", ToolTip = "Configure the maximum number of workers that the Sound Engine will request at any given time. Requires Editor restart.")) + uint32 MaxNumJobWorkers = 1; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (EditCondition = "EnableMultiCoreRendering", ToolTip = "Maximum time allotted for each Sound Engine job in microseconds (0 is unlimited). Requires Editor restart.")) + uint32 JobWorkerMaxExecutionTimeUSec = 0; + + void FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const; +}; + +class FWwiseIOHook; + +namespace FAkSoundEngineInitialization +{ + bool Initialize(FWwiseIOHook* IOHook); + void Finalize(FWwiseIOHook* IOHook); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/InitializationSettings/AkPlatformInitialisationSettingsBase.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/InitializationSettings/AkPlatformInitialisationSettingsBase.h new file mode 100644 index 0000000..db61c7e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/InitializationSettings/AkPlatformInitialisationSettingsBase.h @@ -0,0 +1,40 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkInclude.h" +#include "AkInitializationSettings.h" + +#include "Engine/Engine.h" + +#include "AkPlatformInitialisationSettingsBase.generated.h" + +UINTERFACE() +class AKAUDIO_API UAkPlatformInitialisationSettingsBase : public UInterface +{ + GENERATED_BODY() +}; + + +class AKAUDIO_API IAkPlatformInitialisationSettingsBase +{ + GENERATED_BODY() + +public: + virtual void FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const = 0; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/MovieSceneAkAudioEventSection.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/MovieSceneAkAudioEventSection.h new file mode 100644 index 0000000..6f71133 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/MovieSceneAkAudioEventSection.h @@ -0,0 +1,232 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "MovieSceneSection.h" +#include "AkInclude.h" +#include "AkAudioEvent.h" +#include "WwiseEventTracking.h" +#include "Dom/JsonObject.h" +#include "MovieSceneAkAudioEventSection.generated.h" + +class FAkAudioDevice; +struct FWwiseEventTracker; + +enum class AkEventSectionState : uint8 +{ + EUninitialized = 0, + EUnrecognized, + EInitialized +}; + +/** +* A single floating point section that triggers a Wwise event. +*/ +UCLASS(MinimalAPI) +class UMovieSceneAkAudioEventSection : public UMovieSceneSection +{ + GENERATED_BODY() + + FCriticalSection WAAPISection; + + /** The AkAudioEvent represented by this section */ + UPROPERTY(EditAnywhere, Category = "AkAudioEvent", meta = (NoResetToDefault)) + UAkAudioEvent* Event = nullptr; + + /* Indicates whether the Wwise event will be re-triggered when the end is reached. */ + UPROPERTY(EditAnywhere, Category = "AkAudioEvent") + bool RetriggerEvent = false; + + /* The length, in ms, of scrub snippets */ + UPROPERTY(EditAnywhere, Category = "AkAudioEvent", + meta = (ClampMin = "30", ClampMax = "500", UIMin = "30", UIMax = "500")) + int ScrubTailLengthMs = FWwiseEventTracker::GetScrubTimeMs(); + + /** Indicates whether the Wwise event should be stopped when the section stops in the Unreal Sequencer. */ + UPROPERTY(EditAnywhere, Category = "AkAudioEvent") + bool StopAtSectionEnd = true; + + /** The name of the AkAudioEvent represented by this section */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "AkAudioEvent") + FString EventName = ""; + + /** The duration of the longest Wwise source that the Wwise event contains (taking trim into account). */ + UPROPERTY(VisibleAnywhere, Category = "AkAudioEvent") + float MaxSourceDuration = -1.0f; + + /** The ID of the longest Wwise source that the Wwise event contains. */ + UPROPERTY() + FString MaxDurationSourceID = ""; + +public: + + /** Update the audio source info and register the waapi connection callbacks. */ + AKAUDIO_API void Initialize(); + + /** Returns the UAkAudioEvent that this section triggers. */ + AKAUDIO_API UAkAudioEvent* GetEvent() const { return Event; } + + AKAUDIO_API FString GetEventName() const { return (Event == nullptr) ? EventName : Event->GetName(); } + + AKAUDIO_API bool EventShouldStopAtSectionEnd() const; + + AKAUDIO_API int32 GetMaxEventDuration() const; + + AKAUDIO_API float GetStartTime() const; + + AKAUDIO_API float GetEndTime() const; + + /** Returns the minimum and maximum durations for the specified Event or EventName. This uses the generated XML data, not WAAPI. */ + AKAUDIO_API FFloatRange GetEventDuration() const; + + bool RequiresUpdate = false; + + AkEventSectionState InitState = AkEventSectionState::EUninitialized; + + /** A structure that tracks Wwise events as they are played, scrubbed, and stopped. */ + TSharedPtr EventTracker = MakeShareable(new FWwiseEventTracker()); + + /** Indicates whether the Wwise event will be re-triggered when the end is reached. */ + bool ShouldRetriggerEvent() const { return RetriggerEvent; } + + /** Gets the section's Wwise event's GUID. */ + FGuid GetEventWwiseGUID() const; + + /** Gets the section's Wwise event's name. */ + FString GetEventWwiseName() const; + + virtual void BeginDestroy() override; + + +#if WITH_EDITOR + + /* A collection of subscription IDs for WAAPI callbacks. */ + + uint64 iTrimBeginSubscriptionID = 0; + + uint64 iTrimEndSubscriptionID = 0; + + uint64 iChildAddedSubscriptionID = 0; + + uint64 iChildRemovedSubscriptionID = 0; + + uint64 iChildAddedInitializeSubscriptionID = 0; + + TArray EventActionSubscriptionIDs; + + FThreadSafeCounter iCallbackCounter = 0; + + /* An array of min-max magnitude pairs from the audio source data. */ + TArray AudioSourcePeaks; + + /* We only keep track of the TrimBegin value (not TrimEnd), as the waveform length is calculated using the duration value. */ + float TrimBegin = 0.0f; + + /** Use WAAPI to update the MaxDurationSourceID and MaxSourceDuration. */ + AKAUDIO_API void UpdateAudioSourceInfo(); + + /** Matches the duration of the Unreal Section to that of the Wwise Event. */ + AKAUDIO_API void MatchSectionLengthToEventLength(); + + /** Get the audio peaks data (min max magnitude pairs) for the current MaxDurationSourceID, using WAAPI. + * @param in_iNumPeaks - The number of peaks required. + */ + AKAUDIO_API void UpdateAudioSourcePeaks(int in_iNumPeaks); + + /** Get the audio peaks data (min max magnitude pairs) for the current MaxDurationSourceID, using WAAPI. + * @param in_iNumPeaks - The number of peaks required. + * @param in_dTimeFrom - The start time of the time period for which peaks are required + * @param in_dTimeTo - The end time of the time period for which peaks are required. + */ + AKAUDIO_API void UpdateAudioSourcePeaks(int in_iNumPeaks, double in_dTimeFrom, double in_dTimeTo); + + /** Returns the number of min max magnitude pairs in the current peaks array. */ + AKAUDIO_API const int GetNumMinMaxPairs() const; + + /** Returns the current peaks array. */ + AKAUDIO_API const TArray& GetAudioSourcePeaks() const; + + /** Returns the duration of the longest audio source that is used in the Wwise event that this section triggers. */ + AKAUDIO_API const float GetMaxSourceDuration() const; + + /** Resets the audio source information to an uninitialized state. */ + AKAUDIO_API void InvalidateAudioSourceInfo(); + + /** Returns true if the audio source information is initialized and valid. */ + AKAUDIO_API bool AudioSourceInfoIsValid() const; + +#if !UE_4_26_OR_LATER + /* UMovieSceneSection interface */ + AKAUDIO_API virtual FMovieSceneEvalTemplatePtr GenerateTemplate() const override; +#endif + + /** Check if the workunit is dirty. If so, enable the soundbank generation notification in the event section. */ + AKAUDIO_API void CheckForWorkunitChanges(bool in_bNotifyTrack = false); + + /** Update the AK event info using the UAkAudioEvent Event member. This should be called when the event is changed. */ + AKAUDIO_API bool UpdateAkEventInfo(); + + bool IsValid() const { return Event != nullptr || !EventName.IsEmpty(); } + + bool GetStopAtSectionEnd() const { return StopAtSectionEnd; } + + /** Returns the trim begin value for the Wwise event that this section triggers. */ + float GetTrimBegin() const { return TrimBegin; } + + /** Returns the duration of the scrub snippets that this section produces when scrubbed. + * This can be changed using the ScrubTailLengthMs property. + */ + int GetScrubTailLength() const { return ScrubTailLengthMs; } + + /** Returns true if this section is set to retrigger Wwise events after they finish. Change using the RetriggerEvent property. */ + bool DoesEventRetrigger() const { return RetriggerEvent; } + + virtual void PostEditChangeProperty(struct FPropertyChangedEvent& e) override; + + /** Associate a new AK audio event with this section. Also updates section time and audio source info. */ + bool SetEvent(UAkAudioEvent* AudioEvent, const FString& Name); + + /** Use WAAPI to get the peak data for the Wwise event, with the given arguments and options. This is called by GetAudioSourcePeaks(). */ + void WAAPIGetPeaks(const char* in_uri, TSharedRef in_getPeaksArgs, + TSharedRef in_getPeaksOptions, TSharedPtr in_getPeaksResults); + /** Update the trim data for the longest audio source used by the Wwise event that this section triggers. */ + void UpdateTrimData(); + + /** Subscribe to TrimBegin and TrimEnd properties via WAAPI for the longest audio source that the Wwise event contains. */ + void SubscribeToTrimChanges(); + + /** Subscribes to child added and child removed for each of the action targets in the Wwise event that this section triggers. */ + void SubscribeToEventChildren(); + + /** Registers a call to update the audio source info when a child is added or removed from in_sParentID */ + void SubscribeToChildAddedRemoved(FString in_sParentID, uint64& in_iAddedSubID, uint64& in_iRemovedSubID); + + /** Subscribed to child added and child removed for the event ID. When a child is added or removed, UpdateAudioSourceInfo() is called. */ + void SubscribeToEventChildAddedRemoved(); + + /** Unsubscribes a specfic WAAPI subscription ID. */ + void UnsubscribeWAAPICallback(uint64& in_iSubID); + + /** Unsubscribes from all existing WAAPI subscriptions for this section. */ + void UnsubscribeAllWAAPICallbacks(); + + /** Gets the containing work unit for the given object ID, and checks whether that workunit is dirty. */ + bool CheckWorkunitChangesForID(FGuid in_objectGUID); + +#endif //WITH_EDITOR +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/MovieSceneAkAudioEventTrack.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/MovieSceneAkAudioEventTrack.h new file mode 100644 index 0000000..0c7056f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/MovieSceneAkAudioEventTrack.h @@ -0,0 +1,66 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "MovieSceneAkTrack.h" +#include "IMovieScenePlayer.h" +#include "AkInclude.h" +#include "AkAudioEvent.h" +#if UE_4_26_OR_LATER +#include "Compilation/IMovieSceneTrackTemplateProducer.h" +#else +#include "MovieSceneBackwardsCompatibility.h" +#endif +#include "MovieSceneAkAudioEventTrack.generated.h" + +class UMovieSceneAkAudioEventSection; + +UCLASS(MinimalAPI) +class UMovieSceneAkAudioEventTrack + : public UMovieSceneAkTrack + , public IMovieSceneTrackTemplateProducer +{ + GENERATED_BODY() + +public: + UMovieSceneAkAudioEventTrack() + { +#if WITH_EDITORONLY_DATA + SetColorTint(FColor(0, 156, 255, 65)); +#endif + } + + AKAUDIO_API virtual UMovieSceneSection* CreateNewSection() override; + virtual bool SupportsMultipleRows() const override { return true; } + virtual bool SupportsType(TSubclassOf SectionClass) const override; + + AKAUDIO_API virtual FName GetTrackName() const override; + +#if WITH_EDITORONLY_DATA + AKAUDIO_API virtual FText GetDisplayName() const override; +#endif + +#if WITH_EDITOR + AKAUDIO_API bool AddNewEvent(FFrameNumber Time, UAkAudioEvent* Event, const FString& EventName = FString()); + + void WorkUnitChangesDetectedFromSection(UMovieSceneAkAudioEventSection* in_pSection); +#endif + +protected: + AKAUDIO_API virtual FMovieSceneEvalTemplatePtr CreateTemplateForSection(const UMovieSceneSection& InSection) const override; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/MovieSceneAkAudioRTPCSection.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/MovieSceneAkAudioRTPCSection.h new file mode 100644 index 0000000..efef6e6 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/MovieSceneAkAudioRTPCSection.h @@ -0,0 +1,92 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkInclude.h" +#include "WwiseDefines.h" +#include "AkRtpc.h" +#include "Curves/RichCurve.h" +#include "MovieSceneSection.h" +#include "MovieSceneFloatChannelSerializationHelper.h" + +#include "Channels/MovieSceneFloatChannel.h" +#include "MovieSceneAkAudioRTPCSection.generated.h" + +/** +* A single floating point section +*/ +UCLASS() +class AKAUDIO_API UMovieSceneAkAudioRTPCSection + : public UMovieSceneSection +{ + GENERATED_BODY() + UMovieSceneAkAudioRTPCSection(const FObjectInitializer& Init); + virtual void PostLoad() override; + virtual void Serialize(FArchive& Ar) override; + +public: + FMovieSceneFloatChannel GetChannel() const { return RTPCChannel; } + float GetStartTime() const; + float GetEndTime() const; + +#if !UE_4_26_OR_LATER + virtual FMovieSceneEvalTemplatePtr GenerateTemplate() const override; +#endif + + /** @return the name of the RTPC being modified by this track */ + FString GetRTPCName() const { return RTPC ? RTPC->GetName() : Name; } + + /** + * Sets the name of the RTPC being modified by this track + * + * @param InRTPCName The RTPC being modified + */ + void SetRTPCName(const FString& InRTPCName) { Name = InRTPCName; } + void SetRTPC(UAkRtpc* InRTPC) { RTPC = InRTPC; } + +#if WITH_EDITOR + virtual void PreEditChange(FProperty* PropertyAboutToChange) override; + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + +protected: + UPROPERTY(EditAnywhere, Category = "AkAudioRTPC", meta = (NoResetToDefault)) + UAkRtpc* RTPC = nullptr; + + /** Name of the RTPC to modify. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "AkAudioRTPC", meta = (NoResetToDefault)) + FString Name; + + /** Curve data */ + UPROPERTY() + FRichCurve FloatCurve; + + // Enabled serialization of RTPCChannel when 4.24 support was added. + UPROPERTY() + FMovieSceneFloatChannelSerializationHelper FloatChannelSerializationHelper; + + UPROPERTY() + FMovieSceneFloatChannel RTPCChannel; + +private: +#if WITH_EDITOR + bool IsRTPCNameValid(); + + FString PreviousName; +#endif +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/MovieSceneAkAudioRTPCTrack.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/MovieSceneAkAudioRTPCTrack.h new file mode 100644 index 0000000..32fbdde --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/MovieSceneAkAudioRTPCTrack.h @@ -0,0 +1,59 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "MovieSceneAkTrack.h" +#include "AkInclude.h" +#include "AkAudioEvent.h" +#if UE_4_26_OR_LATER +#include "Compilation/IMovieSceneTrackTemplateProducer.h" +#else +#include "MovieSceneBackwardsCompatibility.h" +#endif +#include "MovieSceneAkAudioRTPCTrack.generated.h" + +/** + * Handles manipulation of float properties in a movie scene + */ +UCLASS(MinimalAPI) +class UMovieSceneAkAudioRTPCTrack + : public UMovieSceneAkTrack + , public IMovieSceneTrackTemplateProducer +{ + GENERATED_BODY() + +public: + + UMovieSceneAkAudioRTPCTrack() + { +#if WITH_EDITORONLY_DATA + SetColorTint(FColor(58, 111, 143, 65)); +#endif + } + + AKAUDIO_API virtual FMovieSceneEvalTemplatePtr CreateTemplateForSection(const UMovieSceneSection& InSection) const override; + + AKAUDIO_API virtual UMovieSceneSection* CreateNewSection() override; + + AKAUDIO_API virtual FName GetTrackName() const override; + virtual bool SupportsType(TSubclassOf SectionClass) const override; + +#if WITH_EDITORONLY_DATA + AKAUDIO_API virtual FText GetDisplayName() const override; +#endif +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/MovieSceneAkTrack.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/MovieSceneAkTrack.h new file mode 100644 index 0000000..3538c66 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/MovieSceneAkTrack.h @@ -0,0 +1,60 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "MovieScene.h" +#include "MovieSceneTrack.h" +#include "WwiseDefines.h" + +#include "MovieSceneAkTrack.generated.h" + + +/** + * Handles manipulation of an Ak track in a movie scene + */ +UCLASS(abstract, MinimalAPI) +class UMovieSceneAkTrack + : public UMovieSceneTrack +{ + GENERATED_BODY() + +public: + + /** begin UMovieSceneTrack interface */ + + virtual void RemoveAllAnimationData() override { Sections.Empty(); } + virtual bool HasSection(const UMovieSceneSection& Section) const override { return Sections.Contains(&Section); } + virtual void AddSection(UMovieSceneSection& Section) override { Sections.Add(&Section); } + virtual void RemoveSection(UMovieSceneSection& Section) override { Sections.Remove(&Section); } + virtual bool IsEmpty() const override { return Sections.Num() == 0; } + virtual const TArray& GetAllSections() const override { return Sections; } + + /** end UMovieSceneTrack interface */ + + void SetIsAMasterTrack(bool AMasterTrack) { bIsAMasterTrack = AMasterTrack; } + bool IsAMasterTrack() const { return bIsAMasterTrack; } + +protected: + + /** All the sections in this track */ + UPROPERTY() + TArray Sections; + + UPROPERTY() + bool bIsAMasterTrack = false; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/MovieSceneBackwardsCompatibility.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/MovieSceneBackwardsCompatibility.h new file mode 100644 index 0000000..74fe028 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/MovieSceneBackwardsCompatibility.h @@ -0,0 +1,21 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#if !UE_4_26_OR_LATER +class IMovieSceneTrackTemplateProducer {}; +#endif \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/MovieSceneFloatChannelSerializationHelper.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/MovieSceneFloatChannelSerializationHelper.h new file mode 100644 index 0000000..1a3876e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/MovieSceneFloatChannelSerializationHelper.h @@ -0,0 +1,167 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkInclude.h" +#include "MovieSceneSection.h" +#include "Channels/MovieSceneFloatChannel.h" +#include "MovieSceneFloatChannelSerializationHelper.generated.h" + +USTRUCT() +struct FMovieSceneTangentDataSerializationHelper +{ + GENERATED_BODY() + + FMovieSceneTangentDataSerializationHelper() {} + + FMovieSceneTangentDataSerializationHelper(const FMovieSceneTangentData TangentData) + : ArriveTangent(TangentData.ArriveTangent) + , LeaveTangent(TangentData.LeaveTangent) + , TangentWeightMode(TangentData.TangentWeightMode) + , ArriveTangentWeight(TangentData.ArriveTangentWeight) + , LeaveTangentWeight(TangentData.LeaveTangentWeight) + { + } + + UPROPERTY() + float ArriveTangent = 0.f; + + UPROPERTY() + float LeaveTangent = 0.f; + + UPROPERTY() + TEnumAsByte TangentWeightMode = ERichCurveTangentWeightMode(); + + UPROPERTY() + float ArriveTangentWeight = 0.f; + + UPROPERTY() + float LeaveTangentWeight = 0.f; +}; + +USTRUCT() +struct FMovieSceneFloatValueSerializationHelper +{ + GENERATED_BODY() + + FMovieSceneFloatValueSerializationHelper() {} + + FMovieSceneFloatValueSerializationHelper(FMovieSceneFloatValue FloatValue) + : Value(FloatValue.Value) + , InterpMode(FloatValue.InterpMode) + , TangentMode(FloatValue.TangentMode) + , Tangent(FloatValue.Tangent) + { + } + + FMovieSceneFloatValue ToFloatValue() + { + FMovieSceneFloatValue ValueToReturn(Value); + ValueToReturn.InterpMode = InterpMode; + ValueToReturn.TangentMode = TangentMode; + ValueToReturn.Tangent.ArriveTangent = Tangent.ArriveTangent; + ValueToReturn.Tangent.ArriveTangentWeight = Tangent.ArriveTangentWeight; + ValueToReturn.Tangent.LeaveTangent = Tangent.LeaveTangent; + ValueToReturn.Tangent.LeaveTangentWeight = Tangent.LeaveTangentWeight; + ValueToReturn.Tangent.TangentWeightMode = Tangent.TangentWeightMode; + return ValueToReturn; + } + + UPROPERTY() + float Value = 0.f; + + UPROPERTY() + TEnumAsByte InterpMode = ERichCurveInterpMode(); + + UPROPERTY() + TEnumAsByte TangentMode = ERichCurveTangentMode(); + + UPROPERTY() + FMovieSceneTangentDataSerializationHelper Tangent; +}; + +USTRUCT() +struct FMovieSceneFloatChannelSerializationHelper +{ + GENERATED_BODY() + + FMovieSceneFloatChannelSerializationHelper() {} + const FMovieSceneFloatChannelSerializationHelper& operator= (const FMovieSceneFloatChannel& FloatChannel) + { + Times.Reset(); + Values.Reset(); + + PreInfinityExtrap = FloatChannel.PreInfinityExtrap; + PostInfinityExtrap = FloatChannel.PostInfinityExtrap; + + auto FloatChannelTimes = FloatChannel.GetTimes(); + auto FloatChannelValues = FloatChannel.GetValues(); + for (int i = 0; i < FloatChannelTimes.Num(); i++) + { + Times.Add(FloatChannelTimes[i].Value); + Values.Add(FMovieSceneFloatValueSerializationHelper(FloatChannelValues[i])); + } + + bHasDefaultValue = FloatChannel.GetDefault().IsSet(); + if (bHasDefaultValue) + { + DefaultValue = FloatChannel.GetDefault().GetValue(); + } + return *this; + } + + void ToFloatChannel(FMovieSceneFloatChannel& FloatChannel) + { + FloatChannel.Reset(); + FloatChannel.PreInfinityExtrap = PreInfinityExtrap; + FloatChannel.PostInfinityExtrap = PostInfinityExtrap; + + TArray FloatChannelTimes; + TArray FloatChannelValues; + for (int i = 0; i < Times.Num(); i++) + { + FloatChannelTimes.Add(Times[i]); + FloatChannelValues.Add(Values[i].ToFloatValue()); + } + + FloatChannel.Set(FloatChannelTimes, FloatChannelValues); + + if (bHasDefaultValue) + { + FloatChannel.SetDefault(DefaultValue); + } + } + + UPROPERTY() + TEnumAsByte PreInfinityExtrap = ERichCurveExtrapolation(); + + UPROPERTY() + TEnumAsByte PostInfinityExtrap = ERichCurveExtrapolation(); + + UPROPERTY() + TArray Times; + + UPROPERTY() + TArray Values; + + UPROPERTY() + float DefaultValue = 0.f; + + UPROPERTY() + bool bHasDefaultValue = false; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/ObstructionAndOcclusionService/AkComponentObstructionAndOcclusionService.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/ObstructionAndOcclusionService/AkComponentObstructionAndOcclusionService.h new file mode 100644 index 0000000..ae2565a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/ObstructionAndOcclusionService/AkComponentObstructionAndOcclusionService.h @@ -0,0 +1,43 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= +AkComponentObstructionAndOcclusionService.h: +=============================================================================*/ + +#pragma once + +#include "AkInclude.h" +#include "AkAudioDevice.h" +#include "WorldCollision.h" +#include "HAL/ThreadSafeBool.h" +#include "ObstructionAndOcclusionService/AkObstructionAndOcclusionService.h" + +class UAkComponent; + +class AkComponentObstructionAndOcclusionService : public AkObstructionAndOcclusionService +{ +public: + void Init(UAkComponent* in_akComponent, float in_refreshInterval); + + virtual void SetObstructionAndOcclusion(AkGameObjectID ListenerID, float Value); + + virtual ~AkComponentObstructionAndOcclusionService() {} + +private: + UAkComponent * AssociatedComponent; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/ObstructionAndOcclusionService/AkObstructionAndOcclusionService.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/ObstructionAndOcclusionService/AkObstructionAndOcclusionService.h new file mode 100644 index 0000000..b39b2aa --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/ObstructionAndOcclusionService/AkObstructionAndOcclusionService.h @@ -0,0 +1,131 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= +AkObstructionAndOcclusionService.h: +=============================================================================*/ + +#pragma once + +#include "AkInclude.h" +#include "AkAudioDevice.h" +#include "WorldCollision.h" +#include "HAL/ThreadSafeBool.h" + +#define NUM_BOUNDING_BOX_TRACE_POINTS 12 +class UAkComponent; +class AActor; +class AAkAcousticPortal; + +struct FAkListenerObstructionAndOcclusion +{ + float CurrentValue; + float TargetValue; + float Rate; + + FAkListenerObstructionAndOcclusion(float in_TargetValue = 0.0f, float in_CurrentValue = 0.0f); + + void SetTarget(float in_TargetValue); + bool ReachedTarget(); + bool Update(float DeltaTime); +}; + +struct FAkListenerObstructionAndOcclusionPair +{ + FAkListenerObstructionAndOcclusionPair(); + + FAkListenerObstructionAndOcclusion Occ; + FAkListenerObstructionAndOcclusion Obs; + FVector Position; + + bool Update(float DeltaTime); + + bool ReachedTarget(); + + /** Trace a ray from a source position to a bounding box point asynchronously */ + void AsyncTraceFromSource(const FVector& SourcePosition, const FVector& EndPosition, int BoundingBoxPointIndex, ECollisionChannel CollisionChannel, UWorld* World, const FCollisionQueryParams& CollisionParams); + /** Trace a ray from a listener position to a bounding box point asynchronously */ + void AsyncTraceFromListener(const FVector& ListenerPosition, const FVector& EndPosition, int BoundingBoxPointIndex, ECollisionChannel CollisionChannel, UWorld* World, const FCollisionQueryParams& CollisionParams); + + /** Get the total number of listener OR source collisions. */ + int GetCollisionCount(); + + void Reset(); + + + /** Iterate through all trace handles and handle the results if ready */ + void CheckTraceResults(UWorld* World); + + +private: + /** Used to check when obstruction and occlusion targets need to be updated (when GetCollisionCount() != CurrentCollisionCount) */ + int CurrentCollisionCount = 0; + TArray SourceTraceHandles; + TArray ListenerTraceHandles; + + /** Iterate through all listener trace handles and handle the trace results if ready */ + void CheckListenerTraceHandles(UWorld* World); + /** Iterate through all source trace handles and handle the trace results if ready */ + void CheckSourceTraceHandles(UWorld* World); + + TArray SourceRayCollisions; + TArray ListenerRayCollisions; +}; + +class AKAUDIO_API AkObstructionAndOcclusionService +{ +public: + void Tick(const UAkComponentSet& in_Listeners, const FVector& SourcePosition, const AActor* Actor, AkRoomID RoomID, ECollisionChannel in_collisionChannel, float DeltaTime, float OcclusionRefreshInterval); + + /** + * Calculates updated occlusion and obstruction values synchronously and then sends them to the Wwise engine. + */ + void UpdateObstructionAndOcclusion(const UAkComponentSet& in_Listeners, const FVector& SourcePosition, const AActor* Actor, AkRoomID RoomID, ECollisionChannel in_collisionChannel, float OcclusionRefreshInterval); + + void ClearOcclusionValues(); + + virtual void SetObstructionAndOcclusion(AkGameObjectID ListenerID, float Value) = 0; + + virtual ~AkObstructionAndOcclusionService() {} + +protected: + void _Init(UWorld* in_world, float in_refreshInterval); + +private: + /** + * Fades active occlusions towards targets, sends updated values to the Wwise engine, then calculates refreshed occlusion and obstruction values asynchronously. + */ + void RefreshObstructionAndOcclusion(const UAkComponentSet& in_Listeners, const FVector& SourcePosition, const AActor* Actor, AkRoomID RoomID, ECollisionChannel in_collisionChannel, const float DeltaTime, float OcclusionRefreshInterval); + /** + * Loops through in_Listeners and sends the obstruction occlusion values on each to the Wwise engine. + */ + void SetObstructionAndOcclusion(const UAkComponentSet& in_Listeners, AkRoomID RoomID); + /** + * Calculates updated occlusion and obstruction values. + */ + void CalculateObstructionAndOcclusionValues(const UAkComponentSet& in_Listeners, const FVector& SourcePosition, const AActor* Actor, AkRoomID RoomID, ECollisionChannel in_collisionChannel, bool bAsync = true); + + + /** Last time occlusion was refreshed */ + float LastObstructionAndOcclusionRefresh = -1; + float PreviousRefreshInterval = -1.0f; + + bool ClearingObstructionAndOcclusion = false; + + typedef AkGameObjectIdKeyFuncs ListenerOccObsPairGameObjectIDKeyFuncs; + TMap ListenerInfoMap; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/ObstructionAndOcclusionService/AkPortalObstructionAndOcclusionService.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/ObstructionAndOcclusionService/AkPortalObstructionAndOcclusionService.h new file mode 100644 index 0000000..c6fc308 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/ObstructionAndOcclusionService/AkPortalObstructionAndOcclusionService.h @@ -0,0 +1,44 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= +AkPortalObstructionAndOcclusionService.h: +=============================================================================*/ + +#pragma once + +#include "AkInclude.h" +#include "AkAudioDevice.h" +#include "WorldCollision.h" +#include "HAL/ThreadSafeBool.h" +#include "ObstructionAndOcclusionService/AkObstructionAndOcclusionService.h" + +class AActor; +class UAkPortalComponent; + +class AkPortalObstructionAndOcclusionService : public AkObstructionAndOcclusionService +{ +public: + void Init(UAkPortalComponent* in_portalId, float in_refreshInterval); + + virtual void SetObstructionAndOcclusion(AkGameObjectID ListenerID, float Value); + + virtual ~AkPortalObstructionAndOcclusionService() {} + +private: + UAkPortalComponent * AssociatedPortal; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatformBase.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatformBase.h new file mode 100644 index 0000000..b8c2c96 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatformBase.h @@ -0,0 +1,28 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#include "Containers/UnrealString.h" + +struct FAkInitializationStructure; + +struct AKAUDIO_API FAkPlatformBase +{ + static FString GetWwisePluginDirectory(); + static FString GetDSPPluginsDirectory(const FString& PlatformArchitecture); + static void PreInitialize(const FAkInitializationStructure& InitSettings) {} +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatformInfo.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatformInfo.h new file mode 100644 index 0000000..307002a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatformInfo.h @@ -0,0 +1,122 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Engine/GameEngine.h" +#include "AkAudioDevice.h" + +#if WITH_EDITORONLY_DATA +#include "Wwise/WwiseProjectDatabase.h" +#include "Wwise/WwiseSharedPlatformId.h" +#endif + +#include "AkPlatformInfo.generated.h" + +UCLASS() +class AKAUDIO_API UAkPlatformInfo : public UObject +{ + GENERATED_BODY() + +public: +#if WITH_EDITORONLY_DATA + static TMap UnrealTargetNameToSharedPlatformId; + static TMap UnrealNameToPlatformInfo; + + virtual FString GetWwiseBankPlatformName(const TArray& AvailableWwisePlatforms) const + { + if (AvailableWwisePlatforms.Contains(WwisePlatform)) + { + return WwisePlatform; + } + return {}; + } + + static FWwiseSharedPlatformId GetSharedPlatformInfo(const FString& PlatformName) + { + if (UnrealTargetNameToSharedPlatformId.Contains(PlatformName)) + { + return UnrealTargetNameToSharedPlatformId[PlatformName]; + } + + const auto ProjectDatabase = FWwiseProjectDatabase::Get(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAkAudio, Warning, TEXT("ProjectDatabase is not initialized")); + return {}; + } + + const FWwiseDataStructureScopeLock DataStructure(*ProjectDatabase); + auto Platforms = DataStructure.GetPlatforms(); + + if (const auto* CurrentPlatformInfo = GetAkPlatformInfo(PlatformName)) + { + TArray AvailablePlatforms; + for (auto WwisePlatform : Platforms) + { + AvailablePlatforms.Add(WwisePlatform.GetPlatformName().ToString()); + } + const FString WwisePlatformName = CurrentPlatformInfo->GetWwiseBankPlatformName(AvailablePlatforms); + for (auto WwisePlatform : Platforms) + { + if (WwisePlatform.GetPlatformName().ToString() == WwisePlatformName) + { + UnrealTargetNameToSharedPlatformId.Add(PlatformName, WwisePlatform); + return UnrealTargetNameToSharedPlatformId[PlatformName]; + } + } + UE_LOG(LogAkAudio, Warning, TEXT("Could not find parsed platform that matches %s"), *CurrentPlatformInfo->WwisePlatform); + return {}; + } + + UE_LOG(LogAkAudio, Warning, TEXT("Could not find platform info for %s"), *PlatformName) + return {}; + } +#endif + + static UAkPlatformInfo* GetAkPlatformInfo(const FString& PlatformName) + { + UAkPlatformInfo* RetVal = nullptr; +#if WITH_EDITORONLY_DATA + auto** FoundInfo = UnrealNameToPlatformInfo.Find(PlatformName); + RetVal = FoundInfo ? *FoundInfo : nullptr; +#endif + if (!RetVal) + { + const FString PlatformInfoClassName = FString::Format(TEXT("Ak{0}PlatformInfo"), { *PlatformName }); +#if UE_5_1_OR_LATER + auto* PlatformInfoClass = UClass::TryFindTypeSlow(*PlatformInfoClassName); +#else + auto* PlatformInfoClass = FindObject(ANY_PACKAGE, *PlatformInfoClassName); +#endif + if (PlatformInfoClass) + { + RetVal = PlatformInfoClass->GetDefaultObject(); + } + } + + return RetVal; + } + + FString WwisePlatform; + FString Architecture; + FString LibraryFileNameFormat; + FString DebugFileNameFormat; + bool bSupportsUPL = false; + bool bUsesStaticLibraries = false; + bool bForceReleaseConfig = false; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Android/AkAndroidInitializationSettings.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Android/AkAndroidInitializationSettings.h new file mode 100644 index 0000000..2bce5e1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Android/AkAndroidInitializationSettings.h @@ -0,0 +1,78 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Engine/EngineTypes.h" +#include "AkInclude.h" +#include "InitializationSettings/AkInitializationSettings.h" +#include "InitializationSettings/AkPlatformInitialisationSettingsBase.h" + +#include "AkAndroidInitializationSettings.generated.h" + +UENUM(Meta = (Bitmask)) +enum class EAkAndroidAudioAPI : uint32 +{ + AAudio, + OpenSL_ES +}; + +USTRUCT() +struct FAkAndroidAdvancedInitializationSettings : public FAkAdvancedInitializationSettingsWithMultiCoreRendering +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (Bitmask, BitmaskEnum = "/Script/AkAudio.EAkAndroidAudioAPI", ToolTip = "Main audio API to use. Leave set to \"Default\" for the default audio sink.")) + uint32 AudioAPI = (1 << (uint32)EAkAndroidAudioAPI::AAudio) | (1 << (uint32)EAkAndroidAudioAPI::OpenSL_ES); + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (ToolTip = "Used when hardware-preferred frame size and user-preferred frame size are not compatible. If true (default), the sound engine will initialize to a multiple of the HW setting, close to the user setting. If false, the user setting is used as is, regardless of the HW preference (might incur a performance hit).")) + bool RoundFrameSizeToHardwareSize = true; + + void FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const; +}; + + +UCLASS(config = Game, defaultconfig) +class AKAUDIO_API UAkAndroidInitializationSettings : public UObject, public IAkPlatformInitialisationSettingsBase +{ + GENERATED_BODY() + +public: + virtual const TCHAR* GetConfigOverridePlatform() const override + { + return TEXT("Android"); + } + + UAkAndroidInitializationSettings(const class FObjectInitializer& ObjectInitializer); + + void FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const override; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization") + FAkCommonInitializationSettingsWithSampleRate CommonSettings; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization") + FAkCommunicationSettingsWithSystemInitialization CommunicationSettings; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization", AdvancedDisplay) + FAkAndroidAdvancedInitializationSettings AdvancedSettings; + + UFUNCTION() + void MigrateMultiCoreRendering(bool NewValue) + { + AdvancedSettings.EnableMultiCoreRendering = NewValue; + } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Android/AkAndroidPlatform.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Android/AkAndroidPlatform.h new file mode 100644 index 0000000..22ac36e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Android/AkAndroidPlatform.h @@ -0,0 +1,44 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#if PLATFORM_ANDROID + +#include "Platforms/AkPlatformBase.h" +#include "AkAndroidInitializationSettings.h" + +#define TCHAR_TO_AK(Text) (const ANSICHAR*)(TCHAR_TO_ANSI(Text)) + +using UAkInitializationSettings = UAkAndroidInitializationSettings; + +struct AKAUDIO_API FAkAndroidPlatform : FAkPlatformBase +{ + static const UAkInitializationSettings* GetInitializationSettings() + { + return GetDefault(); + } + + static const FString GetPlatformBasePath() + { + return FString("Android"); + } +}; + +using FAkPlatform = FAkAndroidPlatform; + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Android/AkAndroidPlatformInfo.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Android/AkAndroidPlatformInfo.h new file mode 100644 index 0000000..b2bc175 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Android/AkAndroidPlatformInfo.h @@ -0,0 +1,39 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Platforms/AkPlatformInfo.h" +#include "AkAndroidPlatformInfo.generated.h" + +UCLASS() +class UAkAndroidPlatformInfo : public UAkPlatformInfo +{ + GENERATED_BODY() + +public: + UAkAndroidPlatformInfo() + { + WwisePlatform = "Android"; + bSupportsUPL = true; + +#if WITH_EDITORONLY_DATA + UAkPlatformInfo::UnrealNameToPlatformInfo.Add("Android", this); +#endif + } +}; + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Hololens/AkHololensInitializationSettings.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Hololens/AkHololensInitializationSettings.h new file mode 100644 index 0000000..3b3188c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Hololens/AkHololensInitializationSettings.h @@ -0,0 +1,66 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkInclude.h" +#include "InitializationSettings/AkInitializationSettings.h" +#include "InitializationSettings/AkPlatformInitialisationSettingsBase.h" + +#include "AkHololensInitializationSettings.generated.h" + +USTRUCT() +struct FAkHololensAdvancedInitializationSettings : public FAkAdvancedInitializationSettingsWithMultiCoreRendering +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings") + bool UseHeadMountedDisplayAudioDevice = false; + + void FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const; +}; + + +UCLASS(config = Game, defaultconfig) +class AKAUDIO_API UAkHololensInitializationSettings : public UObject, public IAkPlatformInitialisationSettingsBase +{ + GENERATED_BODY() + +public: + virtual const TCHAR* GetConfigOverridePlatform() const override + { + return TEXT("HoloLens"); + } + + UAkHololensInitializationSettings(const FObjectInitializer& ObjectInitializer); + void FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const override; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization") + FAkCommonInitializationSettingsWithSampleRate CommonSettings; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization") + FAkCommunicationSettingsWithSystemInitialization CommunicationSettings; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization", AdvancedDisplay) + FAkHololensAdvancedInitializationSettings AdvancedSettings; + + UFUNCTION() + void MigrateMultiCoreRendering(bool NewValue) + { + AdvancedSettings.EnableMultiCoreRendering = NewValue; + } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Hololens/AkHololensPlatform.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Hololens/AkHololensPlatform.h new file mode 100644 index 0000000..296fc0f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Hololens/AkHololensPlatform.h @@ -0,0 +1,44 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#ifdef PLATFORM_HOLOLENS + +#include "Platforms/AkPlatformBase.h" +#include "AkHololensInitializationSettings.h" + +#define TCHAR_TO_AK(Text) (const WIDECHAR*)(Text) + +using UAkInitializationSettings = UAkHololensInitializationSettings; + +struct FAkHololensPlatform : FAkPlatformBase +{ + static const UAkInitializationSettings* GetInitializationSettings() + { + return GetDefault(); + } + + static const FString GetPlatformBasePath() + { + return FString("Hololens"); + } +}; + +using FAkPlatform = FAkHololensPlatform; + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Hololens/AkHololensPlatformInfo.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Hololens/AkHololensPlatformInfo.h new file mode 100644 index 0000000..1f228b5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Hololens/AkHololensPlatformInfo.h @@ -0,0 +1,45 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Platforms/AkPlatformInfo.h" +#include "AkHololensPlatformInfo.generated.h" + +UCLASS() +class UAkHololensPlatformInfo : public UAkPlatformInfo +{ + GENERATED_BODY() + +public: + UAkHololensPlatformInfo() + { + WwisePlatform = "Hololens"; + +#ifdef AK_HOLOLENS_VS_VERSION + Architecture = "UWP_ARM64_" AK_HOLOLENS_VS_VERSION; +#else + Architecture = "UWP_ARM64_vc160"; +#endif + + LibraryFileNameFormat = "{0}.dll"; + DebugFileNameFormat = "{0}.pdb"; +#if WITH_EDITORONLY_DATA + UAkPlatformInfo::UnrealNameToPlatformInfo.Add("Hololens", this); +#endif + } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Linux/AkLinuxInitializationSettings.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Linux/AkLinuxInitializationSettings.h new file mode 100644 index 0000000..2f019cd --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Linux/AkLinuxInitializationSettings.h @@ -0,0 +1,54 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkInclude.h" +#include "InitializationSettings/AkInitializationSettings.h" +#include "InitializationSettings/AkPlatformInitialisationSettingsBase.h" + +#include "AkLinuxInitializationSettings.generated.h" + +UCLASS(config = Game, defaultconfig) +class AKAUDIO_API UAkLinuxInitializationSettings : public UObject, public IAkPlatformInitialisationSettingsBase +{ + GENERATED_BODY() + +public: + virtual const TCHAR* GetConfigOverridePlatform() const override + { + return TEXT("Linux"); + } + + UAkLinuxInitializationSettings(const FObjectInitializer& ObjectInitializer); + void FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const override; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization") + FAkCommonInitializationSettingsWithSampleRate CommonSettings; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization") + FAkCommunicationSettingsWithSystemInitialization CommunicationSettings; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization", AdvancedDisplay) + FAkAdvancedInitializationSettingsWithMultiCoreRendering AdvancedSettings; + + UFUNCTION() + void MigrateMultiCoreRendering(bool NewValue) + { + AdvancedSettings.EnableMultiCoreRendering = NewValue; + } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Linux/AkLinuxPlatform.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Linux/AkLinuxPlatform.h new file mode 100644 index 0000000..33cb290 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Linux/AkLinuxPlatform.h @@ -0,0 +1,46 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#if PLATFORM_LINUX + +#include "Platforms/AkPlatformBase.h" +#include "AkLinuxInitializationSettings.h" + +#define TCHAR_TO_AK(Text) (const ANSICHAR*)(TCHAR_TO_ANSI(Text)) + +using UAkInitializationSettings = UAkLinuxInitializationSettings; + +struct AKAUDIO_API FAkLinuxPlatform : FAkPlatformBase +{ + static const UAkInitializationSettings* GetInitializationSettings() + { + return GetDefault(); + } + + static const FString GetPlatformBasePath() + { + return FString("Linux"); + } + + static FString GetDSPPluginsDirectory(const FString& PlatformArchitecture); +}; + +using FAkPlatform = FAkLinuxPlatform; + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Linux/AkLinuxPlatformInfo.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Linux/AkLinuxPlatformInfo.h new file mode 100644 index 0000000..0eb8f22 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Linux/AkLinuxPlatformInfo.h @@ -0,0 +1,40 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Platforms/AkPlatformInfo.h" +#include "AkLinuxPlatformInfo.generated.h" + +UCLASS() +class UAkLinuxPlatformInfo : public UAkPlatformInfo +{ + GENERATED_BODY() + +public: + UAkLinuxPlatformInfo() + { + WwisePlatform = "Linux"; + Architecture = "Linux_x64"; + LibraryFileNameFormat = "lib{0}.so"; + bForceReleaseConfig = true; + +#if WITH_EDITORONLY_DATA + UAkPlatformInfo::UnrealNameToPlatformInfo.Add("Linux", this); +#endif + } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Mac/AkMacInitializationSettings.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Mac/AkMacInitializationSettings.h new file mode 100644 index 0000000..bd16a4a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Mac/AkMacInitializationSettings.h @@ -0,0 +1,54 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkInclude.h" +#include "InitializationSettings/AkInitializationSettings.h" +#include "InitializationSettings/AkPlatformInitialisationSettingsBase.h" + +#include "AkMacInitializationSettings.generated.h" + +UCLASS(config = Game, defaultconfig) +class AKAUDIO_API UAkMacInitializationSettings : public UObject, public IAkPlatformInitialisationSettingsBase +{ + GENERATED_BODY() + +public: + virtual const TCHAR* GetConfigOverridePlatform() const override + { + return TEXT("Mac"); + } + + UAkMacInitializationSettings(const FObjectInitializer& ObjectInitializer); + void FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const override; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization") + FAkCommonInitializationSettingsWithSampleRate CommonSettings; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization") + FAkCommunicationSettingsWithSystemInitialization CommunicationSettings; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization", AdvancedDisplay) + FAkAdvancedInitializationSettingsWithMultiCoreRendering AdvancedSettings; + + UFUNCTION() + void MigrateMultiCoreRendering(bool NewValue) + { + AdvancedSettings.EnableMultiCoreRendering = NewValue; + } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Mac/AkMacPlatform.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Mac/AkMacPlatform.h new file mode 100644 index 0000000..0548020 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Mac/AkMacPlatform.h @@ -0,0 +1,44 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#if PLATFORM_MAC + +#include "Platforms/AkPlatformBase.h" +#include "AkMacInitializationSettings.h" + +#define TCHAR_TO_AK(Text) (const ANSICHAR*)(TCHAR_TO_ANSI(Text)) + +using UAkInitializationSettings = UAkMacInitializationSettings; + +struct AKAUDIO_API FAkMacPlatform : FAkPlatformBase +{ + static const UAkInitializationSettings* GetInitializationSettings() + { + return GetDefault(); + } + + static const FString GetPlatformBasePath() + { + return FString("Mac"); + } +}; + +using FAkPlatform = FAkMacPlatform; + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Mac/AkMacPlatformInfo.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Mac/AkMacPlatformInfo.h new file mode 100644 index 0000000..aa8b9f9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Mac/AkMacPlatformInfo.h @@ -0,0 +1,39 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Platforms/AkPlatformInfo.h" +#include "AkMacPlatformInfo.generated.h" + +UCLASS() +class UAkMacPlatformInfo : public UAkPlatformInfo +{ + GENERATED_BODY() + +public: + UAkMacPlatformInfo() + { + WwisePlatform = "Mac"; + Architecture = "Mac"; + LibraryFileNameFormat = "lib{0}.dylib"; + +#if WITH_EDITORONLY_DATA + UAkPlatformInfo::UnrealNameToPlatformInfo.Add("Mac", this); +#endif + } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Windows/AkWindowsInitializationSettings.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Windows/AkWindowsInitializationSettings.h new file mode 100644 index 0000000..1a632ff --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Windows/AkWindowsInitializationSettings.h @@ -0,0 +1,69 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkInclude.h" +#include "InitializationSettings/AkInitializationSettings.h" +#include "InitializationSettings/AkPlatformInitialisationSettingsBase.h" + +#include "AkWindowsInitializationSettings.generated.h" + +USTRUCT() +struct FAkWindowsAdvancedInitializationSettings : public FAkAdvancedInitializationSettingsWithMultiCoreRendering +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings") + bool UseHeadMountedDisplayAudioDevice = false; + + UPROPERTY(EditAnywhere, Category = "Ak Initialization Settings", meta = (ToolTip = "Maximum number of System Audio Objects to reserve. Other processes will not be able to use them. Default is 128.")) + uint32 MaxSystemAudioObjects = 128; + + void FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const; +}; + + +UCLASS(config = Game, defaultconfig) +class AKAUDIO_API UAkWindowsInitializationSettings : public UObject, public IAkPlatformInitialisationSettingsBase +{ + GENERATED_BODY() + +public: + virtual const TCHAR* GetConfigOverridePlatform() const override + { + return TEXT("Windows"); + } + + UAkWindowsInitializationSettings(const FObjectInitializer& ObjectInitializer); + void FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const override; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization") + FAkCommonInitializationSettingsWithSampleRate CommonSettings; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization") + FAkCommunicationSettingsWithSystemInitialization CommunicationSettings; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization", AdvancedDisplay) + FAkWindowsAdvancedInitializationSettings AdvancedSettings; + + UFUNCTION() + void MigrateMultiCoreRendering(bool NewValue) + { + AdvancedSettings.EnableMultiCoreRendering = NewValue; + } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Windows/AkWindowsPlatform.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Windows/AkWindowsPlatform.h new file mode 100644 index 0000000..3b9f568 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Windows/AkWindowsPlatform.h @@ -0,0 +1,44 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#if PLATFORM_WINDOWS + +#include "Platforms/AkPlatformBase.h" +#include "AkWindowsInitializationSettings.h" + +#define TCHAR_TO_AK(Text) (const WIDECHAR*)(Text) + +using UAkInitializationSettings = UAkWindowsInitializationSettings; + +struct AKAUDIO_API FAkWindowsPlatform : FAkPlatformBase +{ + static const UAkInitializationSettings* GetInitializationSettings() + { + return GetDefault(); + } + + static const FString GetPlatformBasePath() + { + return FString("Windows"); + } +}; + +using FAkPlatform = FAkWindowsPlatform; + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Windows/AkWindowsPlatformInfo.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Windows/AkWindowsPlatformInfo.h new file mode 100644 index 0000000..680fd3a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_Windows/AkWindowsPlatformInfo.h @@ -0,0 +1,82 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Platforms/AkPlatformInfo.h" +#include "AkWindowsPlatformInfo.generated.h" + +UCLASS() +class UAkWin32PlatformInfo : public UAkPlatformInfo +{ + GENERATED_BODY() + +public: + UAkWin32PlatformInfo() + { + WwisePlatform = "Windows"; + +#ifdef AK_WINDOWS_VS_VERSION + Architecture = "Win32_" AK_WINDOWS_VS_VERSION; +#else + Architecture = "Win32_vc160"; +#endif + + LibraryFileNameFormat = "{0}.dll"; + DebugFileNameFormat = "{0}.pdb"; +#if WITH_EDITORONLY_DATA + UAkPlatformInfo::UnrealNameToPlatformInfo.Add("Win32", this); +#endif + } +}; + +UCLASS() +class UAkWin64PlatformInfo : public UAkPlatformInfo +{ + GENERATED_BODY() + +public: + UAkWin64PlatformInfo() + { + WwisePlatform = "Windows"; + +#ifdef AK_WINDOWS_VS_VERSION + Architecture = "x64_" AK_WINDOWS_VS_VERSION; +#else + Architecture = "x64_vc160"; +#endif + + LibraryFileNameFormat = "{0}.dll"; + DebugFileNameFormat = "{0}.pdb"; + +#if WITH_EDITORONLY_DATA + UAkPlatformInfo::UnrealNameToPlatformInfo.Add("Win64", this); +#endif + } +}; + +UCLASS() +class UAkWindowsPlatformInfo : public UAkWin64PlatformInfo +{ + GENERATED_BODY() + UAkWindowsPlatformInfo() + { +#if WITH_EDITORONLY_DATA + UAkPlatformInfo::UnrealNameToPlatformInfo.Add("Windows", this); +#endif + } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_iOS/AkIOSInitializationSettings.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_iOS/AkIOSInitializationSettings.h new file mode 100644 index 0000000..bab3ec9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_iOS/AkIOSInitializationSettings.h @@ -0,0 +1,52 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkInclude.h" +#include "InitializationSettings/AkInitializationSettings.h" +#include "InitializationSettings/AkAudioSession.h" +#include "InitializationSettings/AkPlatformInitialisationSettingsBase.h" + +#include "AkIOSInitializationSettings.generated.h" + +UCLASS(config = Game, defaultconfig) +class AKAUDIO_API UAkIOSInitializationSettings : public UObject, public IAkPlatformInitialisationSettingsBase +{ + GENERATED_BODY() + +public: + virtual const TCHAR* GetConfigOverridePlatform() const override + { + return TEXT("iOS"); + } + + UAkIOSInitializationSettings(const FObjectInitializer& ObjectInitializer); + void FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const override; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization") + FAkCommonInitializationSettingsWithSampleRate CommonSettings; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization") + FAkAudioSession AudioSession; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization") + FAkCommunicationSettingsWithSystemInitialization CommunicationSettings; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization", AdvancedDisplay) + FAkAdvancedInitializationSettingsWithMultiCoreRendering AdvancedSettings; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_iOS/AkIOSPlatform.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_iOS/AkIOSPlatform.h new file mode 100644 index 0000000..de897c2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_iOS/AkIOSPlatform.h @@ -0,0 +1,44 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#if PLATFORM_IOS && !PLATFORM_TVOS + +#include "Platforms/AkPlatformBase.h" +#include "AkIOSInitializationSettings.h" + +#define TCHAR_TO_AK(Text) (const ANSICHAR*)(TCHAR_TO_ANSI(Text)) + +using UAkInitializationSettings = UAkIOSInitializationSettings; + +struct AKAUDIO_API FAkIOSPlatform : FAkPlatformBase +{ + static const UAkInitializationSettings* GetInitializationSettings() + { + return GetDefault(); + } + + static const FString GetPlatformBasePath() + { + return FString("iOS"); + } +}; + +using FAkPlatform = FAkIOSPlatform; + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_iOS/AkIOSPlatformInfo.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_iOS/AkIOSPlatformInfo.h new file mode 100644 index 0000000..ebe3189 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_iOS/AkIOSPlatformInfo.h @@ -0,0 +1,39 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Platforms/AkPlatformInfo.h" +#include "AkIOSPlatformInfo.generated.h" + +UCLASS() +class UAkIOSPlatformInfo : public UAkPlatformInfo +{ + GENERATED_BODY() + +public: + UAkIOSPlatformInfo() + { + WwisePlatform = "iOS"; + Architecture = "iOS"; + bUsesStaticLibraries = true; + +#if WITH_EDITORONLY_DATA + UAkPlatformInfo::UnrealNameToPlatformInfo.Add("IOS", this); +#endif + } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_tvOS/AkTVOSInitializationSettings.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_tvOS/AkTVOSInitializationSettings.h new file mode 100644 index 0000000..d919b61 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_tvOS/AkTVOSInitializationSettings.h @@ -0,0 +1,52 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkInclude.h" +#include "InitializationSettings/AkInitializationSettings.h" +#include "InitializationSettings/AkAudioSession.h" +#include "InitializationSettings/AkPlatformInitialisationSettingsBase.h" + +#include "AkTVOSInitializationSettings.generated.h" + +UCLASS(config = Game, defaultconfig) +class AKAUDIO_API UAkTVOSInitializationSettings : public UObject, public IAkPlatformInitialisationSettingsBase +{ + GENERATED_BODY() + +public: + virtual const TCHAR* GetConfigOverridePlatform() const override + { + return TEXT("TVOS"); + } + + UAkTVOSInitializationSettings(const FObjectInitializer& ObjectInitializer); + void FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const override; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization") + FAkCommonInitializationSettingsWithSampleRate CommonSettings; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization") + FAkAudioSession AudioSession; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization") + FAkCommunicationSettingsWithSystemInitialization CommunicationSettings; + + UPROPERTY(Config, EditAnywhere, Category = "Initialization", AdvancedDisplay) + FAkAdvancedInitializationSettingsWithMultiCoreRendering AdvancedSettings; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_tvOS/AkTVOSPlatform.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_tvOS/AkTVOSPlatform.h new file mode 100644 index 0000000..a4b8844 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_tvOS/AkTVOSPlatform.h @@ -0,0 +1,44 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#if PLATFORM_TVOS + +#include "Platforms/AkPlatformBase.h" +#include "AkTVOSInitializationSettings.h" + +#define TCHAR_TO_AK(Text) (const ANSICHAR*)(TCHAR_TO_ANSI(Text)) + +using UAkInitializationSettings = UAkTVOSInitializationSettings; + +struct FAkTVOSPlatform : FAkPlatformBase +{ + static const UAkInitializationSettings* GetInitializationSettings() + { + return GetDefault(); + } + + static const FString GetPlatformBasePath() + { + return FString("tvOS"); + } +}; + +using FAkPlatform = FAkTVOSPlatform; + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_tvOS/AkTVOSPlatformInfo.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_tvOS/AkTVOSPlatformInfo.h new file mode 100644 index 0000000..4c42485 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkPlatform_tvOS/AkTVOSPlatformInfo.h @@ -0,0 +1,39 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Platforms/AkPlatformInfo.h" +#include "AkTVOSPlatformInfo.generated.h" + +UCLASS() +class UAkTVOSPlatformInfo : public UAkPlatformInfo +{ + GENERATED_BODY() + +public: + UAkTVOSPlatformInfo() + { + WwisePlatform = "tvOS"; + Architecture = "tvOS"; + bUsesStaticLibraries = true; + +#if WITH_EDITORONLY_DATA + UAkPlatformInfo::UnrealNameToPlatformInfo.Add("TVOS", this); +#endif + } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkUEPlatform.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkUEPlatform.h new file mode 100644 index 0000000..49cbc8a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/Platforms/AkUEPlatform.h @@ -0,0 +1,62 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkInclude.h" + +#if PLATFORM_ANDROID +#include "AkPlatform_Android/AkAndroidPlatform.h" +#elif PLATFORM_TVOS +#include "AkPlatform_tvOS/AkTVOSPlatform.h" +#elif PLATFORM_IOS && !PLATFORM_TVOS +#include "AkPlatform_iOS/AkIOSPlatform.h" +#elif PLATFORM_LINUX +#include "AkPlatform_Linux/AkLinuxPlatform.h" +#elif PLATFORM_MAC +#include "AkPlatform_Mac/AkMacPlatform.h" +#elif defined(PLATFORM_PS4) && PLATFORM_PS4 +#include "AkPlatform_PS4/AkPS4Platform.h" +#elif defined(AK_PS5) +#include "AkPlatform_PS5/AkPS5Platform.h" +#elif defined(PLATFORM_STADIA) && PLATFORM_STADIA +#include "AkPlatform_Stadia/AkStadiaPlatform.h" +#elif defined(PLATFORM_SWITCH) && PLATFORM_SWITCH +#include "AkPlatform_Switch/AkSwitchPlatform.h" +#elif defined(PLATFORM_HOLOLENS) && PLATFORM_HOLOLENS +#include "AkPlatform_Hololens/AkHololensPlatform.h" +#elif defined(AK_WINDOWSGC) +#include "AkPlatform_WinGC/AkWinGCPlatform.h" +#elif PLATFORM_WINDOWS +#include "AkPlatform_Windows/AkWindowsPlatform.h" +#elif (defined(PLATFORM_XBOXONE) && PLATFORM_XBOXONE) && !((defined(PLATFORM_XBOXONEGDK) && PLATFORM_XBOXONEGDK) || (defined(PLATFORM_XB1) && PLATFORM_XB1)) +#include "AkPlatform_XboxOne/AkXboxOnePlatform.h" +#elif ((defined(PLATFORM_XBOXONEGDK) && PLATFORM_XBOXONEGDK) || (defined(PLATFORM_XB1) && PLATFORM_XB1)) +#include "AkPlatform_XboxOneGC/AkXboxOneGDKPlatform.h" +#elif defined(PLATFORM_XSX) && PLATFORM_XSX +#include "AkPlatform_XboxSeriesX/AkXboxSeriesXPlatform.h" +#else +#error "The Wwise plug-in does not support the current build platform." +#endif + +namespace AkUnrealPlatformHelper +{ + AKAUDIO_API TSet GetAllSupportedUnrealPlatforms(); + AKAUDIO_API TSet GetAllSupportedUnrealPlatformsForProject(); + AKAUDIO_API TArray > GetAllSupportedWwisePlatforms(bool ProjectScope = false); + AKAUDIO_API bool IsEditorPlatform(FString Platform); +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/WaapiPicker/SWaapiPicker.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/WaapiPicker/SWaapiPicker.h new file mode 100644 index 0000000..889fa69 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/WaapiPicker/SWaapiPicker.h @@ -0,0 +1,369 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ + SWaapiPicker.h +------------------------------------------------------------------------------------*/ +#pragma once + +/*------------------------------------------------------------------------------------ + SWaapiPicker +------------------------------------------------------------------------------------*/ +#include "WwiseTreeItem.h" +#include "Misc/TextFilter.h" +#include "Widgets/Views/STableRow.h" +#include "AkWaapiClient.h" +#include "Dom/JsonObject.h" +#include "Widgets/Input/SSearchBox.h" +#include "Widgets/Views/STreeView.h" +#include "Framework/Commands/UICommandList.h" + +DECLARE_DELEGATE_OneParam(FOnImportWwiseAssetsClicked, const FString&); + +typedef TTextFilter< const FString& > StringFilter; + +struct TransformStringField +{ + const FString keyArg; + const TArray valueStringArgs; + const TArray valueNumberArgs; +}; + +class AKAUDIO_API SWaapiPicker : public SCompoundWidget +{ +public: + typedef TSlateDelegates< TSharedPtr< FWwiseTreeItem > >::FOnSelectionChanged FOnSelectionChanged; + + DECLARE_DELEGATE(FOnGenerateSoundBankClicked); + DECLARE_DELEGATE(FOnRefreshClicked); + +public: + SLATE_BEGIN_ARGS( SWaapiPicker ) + : _FocusSearchBoxWhenOpened(false) + , _ShowTreeTitle(true) + , _ShowSearchBar(true) + , _ShowSeparator(true) + , _AllowContextMenu(true) + , _RestrictContextMenu(false) + , _ShowGenerateSoundBanksButton(false) + , _SelectionMode( ESelectionMode::Multi ) + {} + + /** Content displayed to the left of the search bar */ + SLATE_NAMED_SLOT( FArguments, SearchContent ) + + /** If true, the search box will be focus the frame after construction */ + SLATE_ARGUMENT( bool, FocusSearchBoxWhenOpened ) + + /** If true, The tree title will be displayed */ + SLATE_ARGUMENT( bool, ShowTreeTitle ) + + /** If true, The tree search bar will be displayed */ + SLATE_ARGUMENT( bool, ShowSearchBar ) + + /** If true, The tree search bar separator be displayed */ + SLATE_ARGUMENT( bool, ShowSeparator ) + + /** If false, the context menu will be suppressed */ + SLATE_ARGUMENT( bool, AllowContextMenu ) + + /** If true, editor options (like explore section) will be restricted from the context menu */ + SLATE_ARGUMENT(bool, RestrictContextMenu) + + /** If true, it will show the Generate SoundBanks button */ + SLATE_ARGUMENT(bool, ShowGenerateSoundBanksButton) + + /** The selection mode for the tree view */ + SLATE_ARGUMENT( ESelectionMode::Type, SelectionMode ) + + /** Handles the drag and drop operations */ + SLATE_EVENT(FOnDragDetected, OnDragDetected) + + /** Handles the selection operation */ + SLATE_EVENT(FOnSelectionChanged, OnSelectionChanged) + + /** Handles the Generate SoundBanks click operation */ + SLATE_EVENT(FOnGenerateSoundBankClicked, OnGenerateSoundBanksClicked) + + /** Handles the Refresh click operation */ + SLATE_EVENT(FOnRefreshClicked, OnRefreshClicked) + + /** Handles the Import asset operation */ + SLATE_EVENT(FOnImportWwiseAssetsClicked, OnImportWwiseAssetsClicked) + + SLATE_END_ARGS( ) + + void Construct(const FArguments& InArgs); + SWaapiPicker(void); + ~SWaapiPicker(); + + static const FName WaapiPickerTabName; + static const FText ModalWarning; + + virtual void Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) override; + + /** + * Call WAAPI to get information about an object form the path or the id of the object (inFrom). + * + * @param inFromField The path or the id from which the data will be get. + * @param outJsonResult A JSON object that contains useful informations about the call process, gets the object infos or gets an error infos in case the call failed. + * @return A boolean to ensure that the call was successfully done. + */ + static bool CallWaapiGetInfoFrom(const FString& inFromField, const FString& inFromString, TSharedPtr& outJsonResult, const TArray& TransformFields); + + /** + * Check if an item exists in the ItemTree, using the item path. + * + * @param RootItem The root of the item we are looking for. + * @param CurrentItemPath The path of the item we are looking for. + * @return The item that we are looking for if it exists, otherwise an invalid one. + */ + TSharedPtr FindItemFromPath(const TSharedPtr& RootItem, const FString& CurrentItemPath); + + /** + * Construct the tree items within the CurrentItem path. + * + * @param CurrentItem the current item already created and need to create his parents from his path. + */ + void FindAndCreateItems(TSharedPtr CurrentItem); + + /** + * Allows to get the root of an item from the path specified. + * + * @param InFullPath A path used to search for the right root item from the root list. + * @return The root item correspondent to the full path. + */ + inline TSharedPtr GetRootItem(const FString& InFullPath); + + /** + * Allows to get information from a FJsonValue object and use it to create an FWwiseTreeItem. + * + * @param inJsonItem An FJsonValue from which we get utile data to construct the FWwiseTreeItem object. + * @return An FWwiseTreeItem that will be added to the root items. + */ + TSharedPtr ConstructWwiseTreeItem(const TSharedPtr& InJsonItem); + TSharedPtr ConstructWwiseTreeItem(const TSharedPtr& ItemInfoObj); + + virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyboardEvent) override; + + /** Returns all the items currently selected in the Waapi Picker view */ + const TArray> GetSelectedItems() const; + + const FString GetSearchText() const; + const void SetSearchText(const FString& newText); + +private: + /** The tree view widget */ + TSharedPtr< STreeView< TSharedPtr> > TreeViewPtr; + + /** The asset tree search box */ + TSharedPtr< SSearchBox > SearchBoxPtr; + + /** Filter for the search box */ + TSharedPtr SearchBoxFilter; + + /** Root items, one for each type of Wwise object */ + FCriticalSection RootItemsLock; + TArray< TSharedPtr > RootItems; + + FGraphEventRef ConstructTreeTask; + + /** Bool to prevent the selection changed callback from running */ + bool AllowTreeViewDelegates; + + /** Remember the selected items. Useful when filtering to preserve selection status. */ + TSet< FGuid > LastSelectedItems; + + /** Remember the expanded items. Useful when filtering to preserve expansion status. */ + TSet< FGuid > LastExpandedItems; + + struct TransportInfo + { + int32 TransportID; + uint64 SubscriptionID; + + TransportInfo(int32 transID, uint64 subsID) : TransportID(transID), SubscriptionID(subsID) {} + }; + + /** Remember the played items. Useful to play/stop and event. */ + TMap ItemToTransport; + + /** Commands handled by this widget */ + TSharedRef CommandList; + + /** Delegate to invoke when drag drop detected. */ + FOnDragDetected OnDragDetected; + + /** Delegate to invoke when an item is selected. */ + FOnSelectionChanged OnSelectionChanged; + + FOnGenerateSoundBankClicked OnGenerateSoundBanksClicked; + + FOnRefreshClicked OnRefreshClicked; + + /** Delegate to invoke when assets are imported. */ + FOnImportWwiseAssetsClicked OnImportWwiseAssetsClicked; + + /** Whether to disable the context menu and keyboard controls of the explore section*/ + bool bRestrictContextMenu; + + /* Callback handles. */ + FDelegateHandle ProjectLoadedHandle; + FDelegateHandle ConnectionLostHandle; + FDelegateHandle ClientBeginDestroyHandle; + + //Waapi Client callbacks + void RemoveClientCallbacks(); + void OnProjectLoadedCallback(); + void OnConnectionLostCallback(); + + /* Used to show/hide the Picker/Warning */ + EVisibility isPickerAllowed() const; + EVisibility isWarningVisible() const; + FText GetWarningText() const; + bool isPickerVisible; + bool isModalActiveInWwise = false; + + /** One-off active timer to focus the widget post-construct */ + EActiveTimerReturnType SetFocusPostConstruct(double InCurrentTime, float InDeltaTime); + + /** Ran when the Refresh button is clicked. Populates the window. */ + FReply OnRefreshButtonClicked(); + + FReply OnGenerateSoundBanksButtonClicked(); + + /** Populates the picker window only (does not parse the Wwise project) */ + void ConstructTree(); + + /** Generate a row in the tree view */ + TSharedRef GenerateRow( TSharedPtr TreeItem, const TSharedRef& OwnerTable ); + + /** Get the children of a specific tree element */ + void GetChildrenForTree( TSharedPtr< FWwiseTreeItem > TreeItem, TArray< TSharedPtr >& OutChildren ); + + /** Handle Drag & Drop */ + FReply HandleOnDragDetected(const FGeometry& Geometry, const FPointerEvent& MouseEvent); + + void ExpandFirstLevel(); + void ExpandParents(TSharedPtr Item); + + FText GetProjectName() const; + + /** Used by the search filter */ + void PopulateSearchStrings( const FString& FolderName, OUT TArray< FString >& OutSearchStrings ) const; + void OnSearchBoxChanged( const FText& InSearchText ); + FText GetHighlightText() const; + void FilterUpdated(); + void SetItemVisibility(TSharedPtr Item, bool IsVisible); + void ApplyFilter(); + void RestoreTreeExpansion(const TArray< TSharedPtr >& Items); + + /** Handler for tree view selection changes */ + void TreeSelectionChanged( TSharedPtr< FWwiseTreeItem > TreeItem, ESelectInfo::Type SelectInfo ); + + /** Handler for tree view expansion changes */ + void TreeExpansionChanged( TSharedPtr< FWwiseTreeItem > TreeItem, bool bIsExpanded ); + + FString ProjectFolder; + FString ProjectName; + + /** True if the specified item is selected in the asset tree */ + bool IsTreeItemSelected(TSharedPtr TreeItem) const; + + /** Builds the command list for the context menu on Waapi Picker items. */ + void CreateWaapiPickerCommands(); + + /** Callback for creating a context menu for the Wwise items list. */ + TSharedPtr MakeWaapiPickerContextMenu(); + + /** Helper functions for playback */ + int32 CreateTransport(const FGuid& ItemID); + void DestroyTransport(const FGuid& ItemID); + void TogglePlayStop(int32 TransportID); + void StopTransport(int32 TransportID); + uint64 SubscribeToTransportStateChanged(int32 TransportID); + void HandleStateChanged(TSharedPtr UEJsonObject); + + /** Callback returns true if the rename command can be executed. */ + bool HandleRenameWwiseItemCommandCanExecute() const; + + /** Callback to execute the rename command from the context menu. */ + void HandleRenameWwiseItemCommandExecute() const; + + /** Callback returns true if the play command can be executed. */ + bool HandlePlayWwiseItemCommandCanExecute() const; + + /** Callback to execute the play command from the context menu. */ + void HandlePlayWwiseItemCommandExecute(); + + /** Callback to execute the stop all command from the context menu. */ + void StopAndDestroyAllTransports(); + + /** Callback returns true if the delete command can be executed. */ + bool HandleDeleteWwiseItemCommandCanExecute() const; + + /** Callback to execute the delete command from the context menu. */ + void HandleDeleteWwiseItemCommandExecute(); + + /** Callback to execute the explore item command from the context menu. */ + void HandleExploreWwiseItemCommandExecute() const; + + /** Callback returns true if the command can be executed. */ + bool HandleWwiseCommandCanExecute() const; + + /** Callback to execute the find item in project explorer command from the context menu. */ + void HandleFindWwiseItemInProjectExplorerCommandExecute() const; + + /** Callback to execute the refresh command from the context menu. */ + void HandleRefreshWaapiPickerCommandExecute(); + + /** Callback to execute the undo command */ + void HandleUndoWaapiPickerCommandExecute() const; + + /** Callback to execute the redo command */ + void HandleRedoWaapiPickerCommandExecute() const; + + /** Callback to import a Wwise item into the project's Contents*/ + void HandleImportWwiseItemCommandExecute() const; + + void SubscribeWaapiCallbacks(); + void UnsubscribeWaapiCallbacks(); + + void OnWaapiRenamed(uint64_t Id, TSharedPtr Response); + void OnWaapiChildAdded(uint64_t Id, TSharedPtr Response); + void OnWaapiChildRemoved(uint64_t Id, TSharedPtr Response); + void OnWwiseSelectionChanged(uint64_t Id, TSharedPtr Response); + + void CreateTreeItemWaapi(const TSharedPtr& parentTreeItem, const TSharedPtr& childJson); + + template + void HandleOnWaapiChildResponse(TSharedPtr Response, const ActionFunctor& Action); + + TSharedPtr FindTreeItemFromJsonObject(const TSharedPtr& Object, const FString& OverrideLastPart = FString()); + TSharedPtr FindOrConstructTreeItemFromJsonObject(const TSharedPtr& Object); + +private: + struct FWaapiSubscriptionIds + { + uint64 Renamed = 0; + uint64 ChildAdded = 0; + uint64 ChildRemoved = 0; + uint64 SelectionChanged = 0; + } WaapiSubscriptionIds; + + TMap> pendingTreeItems; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/WaapiPicker/SWaapiPickerRow.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/WaapiPicker/SWaapiPickerRow.h new file mode 100644 index 0000000..97bd008 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/WaapiPicker/SWaapiPickerRow.h @@ -0,0 +1,122 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ + SWaapiPickerRow.h +------------------------------------------------------------------------------------*/ +#pragma once + +/*------------------------------------------------------------------------------------ + SWaapiPickerRow +------------------------------------------------------------------------------------*/ +#include "WwiseTreeItem.h" +#include "AkWaapiClient.h" +#include "Dom/JsonObject.h" +#include "Widgets/Text/SInlineEditableTextBlock.h" +#include "Widgets/SCompoundWidget.h" + +/** A single item in the Wwise tree. */ +class AKAUDIO_API SWaapiPickerRow : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SWaapiPickerRow) + : _WaapiPickerItem(TSharedPtr()) + , _ParentWidget() + {} + + /** Data that represents the WwiseItem */ + SLATE_ARGUMENT(TSharedPtr, WaapiPickerItem) + + /** The parent widget */ + SLATE_ARGUMENT(TSharedPtr, ParentWidget) + + /** Callback to check if the widget is selected, should only be hooked up if parent widget is handling selection or focus. */ + SLATE_EVENT(FIsSelected, IsSelected) + + /** Text to highlight for this WwiseItem */ + SLATE_ATTRIBUTE(FText, HighlightText) + + SLATE_END_ARGS() + + /** Constructs this widget with InArgs */ + void Construct(const FArguments& InArgs); + + /** Trigger entry into edit mode */ + void EnterEditingMode(); + + ~SWaapiPickerRow(); + +private: + + /** Handles committing a name change */ + void HandleNameCommitted(const FText& NewText, ETextCommit::Type CommitInfo); + + /** Handles verifying a name change */ + bool HandleVerifyNameChanged(const FText& NewText, FText& OutErrorMessage); + + /** Checks whether the selected collection is not allowed to be renamed */ + bool IsWiseItemNameReadOnly() const; + + /** Returns the text of the WwiseItem name */ + FText GetNameText() const; + + /** Returns the text to use for the Wwise item tooltip */ + FText GetToolTipText() const; + +private: + /** Handler for when a name was given to a new item */ + bool OnVerifyItemNameChanged(const TSharedPtr< FWwiseTreeItem >& WwiseItem, const FString& InNewItemName, FText& OutErrorMessage); + + /** Handler for when a name was given to an item */ + bool OnItemRenameCommitted(const TSharedPtr< FWwiseTreeItem >& WwiseItem, const FString& InNewItemName, FText& OutWarningMessage); + +public: + + struct KeyValueArgs + { + const FString keyArg; + const FString valueArg; + }; + + /** + * Call WAAPI to change the object name form the path or the id of the object (inFromIdOrPath). + * + * @param inUri The Unique Resource Identifier used to indicate a specific action to WAAPI; i.e. ak::wwise::core::object::setName + * @param values An array that contains the pair of field and field value; e.i. when asking WAAPI to rename an item, the arguments are like this : {{object,id},{value,newname}} + * @return A boolean to ensure that the call was successfully done. + */ + static bool CallWaapiExecuteUri(const char* inUri, const TArray& values, TSharedPtr& outJsonResult); + +private: + /** A shared pointer to the parent widget. */ + TSharedPtr ParentWidget; + + /** The data for this item */ + TWeakPtr WaapiPickerItem; + + /** Widget to display the name of the item and allows for renaming */ + TSharedPtr< SInlineEditableTextBlock > InlineRenameWidget; + + /** Handle to the registered EnterEditingMode delegate. */ + FDelegateHandle EnterEditingModeDelegateHandle; + + /** Broadcasts whenever renaming a tree item is requested */ + DECLARE_MULTICAST_DELEGATE(FRenamedRequestEvent) + + /** Broadcasts whenever a rename is requested */ + FRenamedRequestEvent OnRenamedRequestEvent; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/WaapiPicker/WaapiPickerViewCommands.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/WaapiPicker/WaapiPickerViewCommands.h new file mode 100644 index 0000000..5221cb9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/WaapiPicker/WaapiPickerViewCommands.h @@ -0,0 +1,89 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "InputCoreTypes.h" +#include "AkAudioStyle.h" +#include "Framework/Commands/InputChord.h" +#include "Framework/Commands/Commands.h" + +#define LOCTEXT_NAMESPACE "WaapiPickerViewCommands" + +/** + * The set of commands supported by the WaapiPickerView + */ +class AKAUDIO_API FWaapiPickerViewCommands : public TCommands +{ + +public: + + /** FWaapiPickerViewCommands Constructor */ + FWaapiPickerViewCommands() : TCommands + ( + "WaapiPickerViewCommand", // Context name for fast lookup + NSLOCTEXT("Contexts", "WaapiPickerViewCommand", "Waapi Picker Command"), // Localized context name for displaying + NAME_None, // Parent + FAkAudioStyle::GetStyleSetName() // Icon Style Set + ) + { + } + + /** + * Initialize the commands + */ + virtual void RegisterCommands() override + { + UI_COMMAND(RequestRenameWwiseItem, "Rename", "Renames the selected item.", EUserInterfaceActionType::Button, FInputChord(EKeys::F2)); + UI_COMMAND(RequestPlayWwiseItem, "Play/Stop", "Plays or stops the selected item.", EUserInterfaceActionType::Button, FInputChord(EKeys::SpaceBar)); + UI_COMMAND(RequestStopAllWwiseItem, "Stop All", "Stop all playing events", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(RequestDeleteWwiseItem, "Delete", "Deletes the selected item(s).", EUserInterfaceActionType::Button, FInputChord(EKeys::Delete)); + UI_COMMAND(RequestExploreWwiseItem, "Show in Folder", "Finds this item on disk.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(RequestFindInProjectExplorerWwisetem, "Find in the Project Explorer", "Finds the specified object in the Project Explorer (Sync Group 1).", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control | EModifierKey::Shift, EKeys::One)); + UI_COMMAND(RequestRefreshWaapiPicker, "Refresh All", "Populates the Waapi Picker.", EUserInterfaceActionType::Button, FInputChord(EKeys::F5)); + UI_COMMAND(RequestImportWwiseItem, "Import Selected Assets", "Imports the selected assets from the Waapi Picker.", EUserInterfaceActionType::Button, FInputChord()); + } + +public: + + /** Requests a rename on the Item */ + TSharedPtr< FUICommandInfo > RequestRenameWwiseItem; + + /** Requests a play action on a Wwise item */ + TSharedPtr< FUICommandInfo > RequestPlayWwiseItem; + + /** Requests a stop playing on all Wwise items */ + TSharedPtr< FUICommandInfo > RequestStopAllWwiseItem; + + /** Requests a delete action on a Wwise item(s) */ + TSharedPtr< FUICommandInfo > RequestDeleteWwiseItem; + + /** Requests an explore action on the Item */ + TSharedPtr< FUICommandInfo > RequestExploreWwiseItem; + + /** Requests a Find in the Project Explorer action on the Item */ + TSharedPtr< FUICommandInfo > RequestFindInProjectExplorerWwisetem; + + /** Requests a refresh on the Waapi Picker */ + TSharedPtr< FUICommandInfo > RequestRefreshWaapiPicker; + + /** Imports the selected asset into the project's Contents */ + TSharedPtr< FUICommandInfo > RequestImportWwiseItem; +}; + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/WaapiPicker/WwiseTreeItem.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/WaapiPicker/WwiseTreeItem.h new file mode 100644 index 0000000..b9beb29 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/WaapiPicker/WwiseTreeItem.h @@ -0,0 +1,306 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ + WwiseTreeItem.h +------------------------------------------------------------------------------------*/ +#pragma once + +#include "AkAudioType.h" +#include "AssetRegistry/AssetData.h" +#include "Engine/GameEngine.h" +#include "Widgets/Views/STableRow.h" +#include "WwiseItemType.h" +#if WITH_EDITORONLY_DATA +#include "Wwise/Metadata/WwiseMetadataBasicReference.h" +#endif +/*------------------------------------------------------------------------------------ + WwiseTreeItem +------------------------------------------------------------------------------------*/ + +struct AKAUDIO_API FWwiseTreeItem : public TSharedFromThis +{ +private: + TArray< TSharedPtr > m_Children; + +public: + /** Name to display */ + FString DisplayName; + /** The path of the tree item including the name */ + FString FolderPath; + /** Type of the item */ + EWwiseItemType::Type ItemType = EWwiseItemType::None; + /** Id of the item */ + FGuid ItemId; + /** ShortId of the item*/ + uint32 ShortId = 0; + + /** The children of this tree item */ + const TArray< TSharedPtr > GetChildren() { return m_Children; } + TArray< TSharedPtr >* GetChildrenMutable() { return &m_Children; } + + /** The number of children of this tree item requested from Wwise*/ + uint32_t ChildCountInWwise = 0; + + /** The parent folder for this item */ + TWeakPtr Parent; + + /** The row in the tree view associated to this item */ + TWeakPtr TreeRow; + + /** Should this item be visible? */ + bool IsVisible = true; + + /** The Assets associated with the Tree Item*/ + TArray Assets; + + /** The name of the UAsset referenced by this item. If there are more than one, takes the first one found*/ + FName UAssetName; + + /** The name of the item in the Wwise project*/ + FString WaapiName; + +#if WITH_EDITORONLY_DATA + /** Reference to the item in the Wwise Project database */ + TSharedPtr WwiseItemRef; +#endif + + /** Is this item active in the currently opened project? */ + bool bWaapiRefExists = false; + + /** Is this item in the same path in Wwise and the SoundBanks? */ + bool bSameLocation = true; + + bool IsExpanded = false; + + bool UEAssetExists() const; + + bool WwiseBankRefExists() const; + + bool WaapiRefExists() const; + + bool IsRenamedInWwise() const; + + bool IsDeletedInWwise() const; + + bool IsNotInWwiseOrSoundBank() const; + + bool IsNewInWwise() const; + + bool IsMovedInWwise() const; + + bool IsSoundBankUpToDate() const; + + bool IsRenamedInSoundBank() const; + + bool IsUAssetMissing() const; + + bool IsUAssetOrphaned() const; + + bool IsNotInSoundBankOrUnreal() const; + + bool IsUAssetUpToDate() const; + + bool HasUniqueUAsset() const; + + bool HasMultipleUAssets() const; + + bool IsItemUpToDate() const; + + bool IsFolder() const; + + bool IsAuxBus() const; + + bool ShouldDisplayInfo() const; + + bool IsRootItem() const; + + TSharedPtr GetRoot(); + + void SetWaapiRef(bool bExistsInWaapi); + + FString GetSwitchAssetName() const; + + const FString GetDefaultAssetName() const; + + /** Constructor */ + FWwiseTreeItem(FString InDisplayName, FString InFolderPath, TSharedPtr InParent, EWwiseItemType::Type InItemType, const FGuid& InItemId) + : DisplayName(MoveTemp(InDisplayName)) + , FolderPath(MoveTemp(InFolderPath)) + , ItemType(MoveTemp(InItemType)) + , ItemId(InItemId) + , ChildCountInWwise(m_Children.Num()) + , Parent(MoveTemp(InParent)) + { + } + +#if WITH_EDITORONLY_DATA + FWwiseTreeItem(const FWwiseMetadataBasicReference& ItemRef, TSharedPtr InParent, EWwiseItemType::Type InItemType) + : ItemType(InItemType) + , ChildCountInWwise(m_Children.Num()) + , Parent(MoveTemp(InParent)) + { + WwiseItemRef = MakeShared(ItemRef.Id, ItemRef.Name, ItemRef.ObjectPath, ItemRef.GUID); + ItemId = WwiseItemRef->GUID; + DisplayName = WwiseItemRef->Name.ToString(); + FolderPath = WwiseItemRef->ObjectPath.ToString(); + } +#endif + + void AddChild(TSharedPtr Child); + + void AddChildren(TArray> Children); + + void EmptyChildren(); + + void RemoveChild(const FGuid& childGuid); + + void RemoveChild(const TSharedPtr< FWwiseTreeItem> child); + + void RemoveChildren(const TArray> Children); + + /** Returns true if this item is a child of the specified item */ + bool IsChildOf(const FWwiseTreeItem& InParent); + + bool IsBrowserType() const; + + bool IsOfType(const TArray& Types) const; + + bool IsNotOfType(const TArray& Types) const; + + /** Returns the child item by name or NULL if the child does not exist */ + TSharedPtr GetChild(const FString& InChildName); + + /** Returns the child item by name or NULL if the child does not exist */ + TSharedPtr GetChild(const FGuid& InGuid, const AkUInt32 InShortId, const FString& InChildName); + + /** Finds the child who's path matches the one specified */ + TSharedPtr FindItemRecursive(const FString& InFullPath); + + /** Finds the child who's Guid matches the one specified */ + TSharedPtr FindItemRecursive(const TSharedPtr& InItem); + + struct FCompareWwiseTreeItem + { + template + inline int StringCompareLogical(const CT* pA1, const CT* pA2) const + { + if (pA1 && pA2) + { + while (*pA1) + { + if (!*pA2) + { + // We've iterated through all the characters of the RHS but + // there are characters left on the LHS + return 1; + } + else if (TChar::IsDigit(*pA1)) + { + // LHS is a digit but RHS is not + if (!TChar::IsDigit(*pA2)) + return -1; + + // Both sides are digits, parse the numbers and compare them + CT* pEnd1 = nullptr; + CT* pEnd2 = nullptr; + const auto i1 = TCString::Strtoi(pA1, &pEnd1, 10); + const auto i2 = TCString::Strtoi(pA2, &pEnd2, 10); + + if (i1 < i2) + return -1; + else if (i1 > i2) + return 1; + + pA1 = pEnd1; + pA2 = pEnd2; + } + else if (TChar::IsDigit(*pA2)) + { + // LHS is not a digit but RHS is + return 1; + } + else + { + // Neither side is a digit, do a case-insensitive comparison + int diff = TChar::ToLower(*pA1) - TChar::ToLower(*pA2); + if (diff > 0) + return 1; + else if (diff < 0) + return -1; + + ++pA1; + ++pA2; + } + } + + if (*pA2) + { + // We've iterated through all the characters of the LHS but + // there are characters left on the RHS + return -1; + } + } + + return 0; + } + + FORCEINLINE bool operator()( TSharedPtr A, TSharedPtr B ) const + { + // Items are sorted like so: + // 1- Physical folders, sorted alphabetically + // 1- WorkUnits, sorted alphabetically + // 2- Virtual folders, sorted alphabetically + // 3- Normal items, sorted alphabetically + if( A->ItemType == B->ItemType) + { + return StringCompareLogical(*A->DisplayName, *B->DisplayName) < 0; + } + else if( A->ItemType == EWwiseItemType::PhysicalFolder ) + { + return true; + } + else if( B->ItemType == EWwiseItemType::PhysicalFolder ) + { + return false; + } + else if( A->ItemType == EWwiseItemType::StandaloneWorkUnit || A->ItemType == EWwiseItemType::NestedWorkUnit ) + { + return true; + } + else if( B->ItemType == EWwiseItemType::StandaloneWorkUnit || B->ItemType == EWwiseItemType::NestedWorkUnit ) + { + return false; + } + else if( A->ItemType == EWwiseItemType::Folder ) + { + return true; + } + else if( B->ItemType == EWwiseItemType::Folder ) + { + return false; + } + else + { + return true; + } + } + }; + + /** Sort the children by name */ + void SortChildren(); +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/WwiseEventTracking.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/WwiseEventTracking.h new file mode 100644 index 0000000..e095525 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/WwiseEventTracking.h @@ -0,0 +1,106 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkAudioDevice.h" +#include "AkComponent.h" + +/** This can be used to track a Wwise event as it is triggered and stopped. + * Maintains a collection of playing IDs and a collection of IDs that have scheduled stop calls. + * Also maintains a collection of Vector2Ds that indicate the history of start times and durations + * of event retriggers. + */ +struct AKAUDIO_API FWwiseEventTracker +{ + static int GetScrubTimeMs() { return 100; } + + /** Callback receieved at various points during lifetime Wwise event. + * The FWwiseEventTracker is stored in the AkCallbackInfo as pCookie. + */ + static void PostEventCallbackHandler(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo); + + void RemoveScheduledStop(AkPlayingID InID); + + void RemovePlayingID(AkPlayingID InID); + + void TryAddPlayingID(const AkPlayingID& PlayingID); + + void EmptyPlayingIDs(); + + void EmptyScheduledStops(); + + bool PlayingIDHasScheduledStop(AkPlayingID InID); + + void AddScheduledStop(AkPlayingID InID); + + bool IsDirty = false; + + bool IsPlaying() const { FScopeLock autoLock(&PlayingIDsLock); return PlayingIDs.Num() > 0; } + bool HasScheduledStop() const { FScopeLock autoLock(&ScheduledStopsLock); return ScheduledStops.Num() > 0; } + float GetClipDuration() const { return ClipEndTime - ClipStartTime; } + + TArray PlayingIDs; + TArray ScheduledStops; + FFloatRange EventDuration; + FString EventName; + UAkAudioEvent* Event; + mutable FCriticalSection PlayingIDsLock; + mutable FCriticalSection ScheduledStopsLock; + float ClipStartTime = 0.0f; + float ClipEndTime = 0.0f; + int ScrubTailLengthMs = GetScrubTimeMs(); + float PreviousEventStartTime = -1.0f; + float PreviousPlayingTime = -1.0f; + float CurrentDurationEstimation = -1.0f; + float CurrentDurationProportionRemaining = 1.0f; + bool bStopAtSectionEnd = true; +}; + +/** A collection of helper functions for triggering tracked Wwise events */ +namespace WwiseEventTriggering +{ + AKAUDIO_API TArray> GetPlayingIds(FWwiseEventTracker& EventTracker); + + AKAUDIO_API void LogDirtyPlaybackWarning(); + + AKAUDIO_API void StopAllPlayingIDs(FAkAudioDevice* AudioDevice, FWwiseEventTracker& EventTracker); + + AKAUDIO_API AkPlayingID PostEventOnDummyObject(FAkAudioDevice* AudioDevice, FWwiseEventTracker& EventTracker, float CurrentTime); + + AKAUDIO_API AkPlayingID PostEvent(UObject* Object, FAkAudioDevice* AudioDevice, FWwiseEventTracker& EventTracker, float CurrentTime); + + AKAUDIO_API void StopEvent(FAkAudioDevice* AudioDevice, AkPlayingID InPlayingID, FWwiseEventTracker* EventTracker); + + AKAUDIO_API void TriggerStopEvent(FAkAudioDevice* AudioDevice, FWwiseEventTracker& EventTracker, AkPlayingID PlayingID); + + AKAUDIO_API void ScheduleStopEventsForCurrentlyPlayingIDs(FAkAudioDevice* AudioDevice, FWwiseEventTracker& EventTracker); + + /** Trigger and EventTracker's Wwise event and schedule an accompanying stop event. */ + AKAUDIO_API void TriggerScrubSnippetOnDummyObject(FAkAudioDevice* AudioDevice, FWwiseEventTracker& EventTracker); + + /** Trigger and EventTracker's Wwise event and schedule an accompanying stop event. */ + AKAUDIO_API void TriggerScrubSnippet(UObject* Object, FAkAudioDevice* AudioDevice, FWwiseEventTracker& EventTracker); + + AKAUDIO_API void SeekOnEvent(UObject* Object, FAkAudioDevice* AudioDevice, AkReal32 in_fPercent, FWwiseEventTracker& EventTracker, AkPlayingID InPlayingID); + + AKAUDIO_API void SeekOnEvent(UObject* Object, FAkAudioDevice* AudioDevice, AkReal32 in_fPercent, FWwiseEventTracker& EventTracker); + + AKAUDIO_API void SeekOnEventWithDummyObject(FAkAudioDevice* AudioDevice, AkReal32 ProportionalTime, FWwiseEventTracker& EventTracker, AkPlayingID InPlayingID); + + AKAUDIO_API void SeekOnEventWithDummyObject(FAkAudioDevice* AudioDevice, AkReal32 ProportionalTime, FWwiseEventTracker& EventTracker); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/WwiseInitBankLoader/WwiseInitBankLoader.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/WwiseInitBankLoader/WwiseInitBankLoader.h new file mode 100644 index 0000000..f4310a3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/WwiseInitBankLoader/WwiseInitBankLoader.h @@ -0,0 +1,44 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkInitBank.h" + +#include "UObject/StrongObjectPtr.h" + +struct AKAUDIO_API FWwiseInitBankLoader +{ +public: + static FWwiseInitBankLoader* Get(); + + FWwiseInitBankLoader(); + +#if WITH_EDITORONLY_DATA + void UpdateInitBankInSettings(); +#endif + + void LoadInitBank() const; + void UnloadInitBank() const; + UAkInitBank* GetInitBankAsset() const; + +#if WITH_EDITORONLY_DATA +private: + FDelegateHandle PostInitDelegate; + void OnPostInitSavePackage() const; +#endif +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/WwiseItemType.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/WwiseItemType.h new file mode 100644 index 0000000..618631a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Classes/WwiseItemType.h @@ -0,0 +1,194 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Containers/UnrealString.h" +#include "Engine/EngineTypes.h" + +namespace EWwiseItemType +{ + UENUM() + enum Type + { + Event, + AuxBus, + AcousticTexture, + State, + Switch, + GameParameter, + Trigger, + EffectShareSet, + ActorMixer, + Bus, + Project, + StandaloneWorkUnit, + NestedWorkUnit, + PhysicalFolder, + Folder, + Sound, + SwitchContainer, + RandomSequenceContainer, + BlendContainer, + MotionBus, + StateGroup, + SwitchGroup, + InitBank, + LastWwiseBrowserType = EffectShareSet, + + None = -1, + }; + + static const FString EventsBrowserName = TEXT("Events"); + static const FString BussesBrowserName = TEXT("Busses"); + static const FString AcousticTexturesBrowserName = TEXT("AcousticTextures"); + static const FString StatesBrowserName = TEXT("States"); + static const FString SwitchesBrowserName = TEXT("Switches"); + static const FString GameParametersBrowserName = TEXT("GameParameters"); + static const FString TriggersBrowserName = TEXT("Triggers"); + static const FString ShareSetsBrowserName = TEXT("Effect ShareSets"); + static const FString OrphanAssetsBrowserName = TEXT("Orphan Assets"); + + //Name to show in the Browser + static const FString BrowserDisplayNames[] = { + EventsBrowserName, + BussesBrowserName, + AcousticTexturesBrowserName, + StatesBrowserName, + SwitchesBrowserName, + GameParametersBrowserName, + TriggersBrowserName, + ShareSetsBrowserName, + OrphanAssetsBrowserName + }; + + //Tag in the work unit XML for this WwiseObjectType + static const FString WorkUnitTagNames[] = { + TEXT("Events"), + TEXT("Busses"), + TEXT("VirtualAcoustics"), + TEXT("States"), + TEXT("Switches"), + TEXT("GameParameters"), + TEXT("Triggers"), + TEXT("Effects"), + }; + + //Name of the folder containing the work units of this WwiseObjectType + static const FString FolderNames[] = { + TEXT("Events"), + TEXT("Master-Mixer Hierarchy"), + TEXT("Virtual Acoustics"), + TEXT("States"), + TEXT("Switches"), + TEXT("Game Parameters"), + TEXT("Triggers"), + TEXT("Effects"), + TEXT("Actor-Mixer Hierarchy"), + }; + + static const TArray PhysicalFoldersToIgnore = { + TEXT("Actor-Mixer Hierarchy"), + TEXT("Attenuations"), + TEXT("Audio Devices"), + TEXT("Control Surface Sessions"), + TEXT("Conversion Settings"), + TEXT("Dynamic Dialogue"), + TEXT("Interactive Music Hierarchy"), + TEXT("Metadata"), + TEXT("Mixing Sessions"), + TEXT("Modulators"), + TEXT("Presets"), + TEXT("Queries"), + TEXT("SoundBanks"), + TEXT("Soundcaster Sessions"), + }; + + inline Type FromString(const FString& ItemName) + { + struct TypePair + { + FString Name; + Type Value; + }; + + static const TypePair ValidTypes[] = { + {TEXT("AcousticTexture"), Type::AcousticTexture}, + {TEXT("ActorMixer"), Type::ActorMixer}, + {TEXT("AuxBus"), Type::AuxBus}, + {TEXT("BlendContainer"), Type::BlendContainer}, + {TEXT("Bus"), Type::Bus}, + {TEXT("Event"), Type::Event}, + {TEXT("Folder"), Type::Folder}, + {TEXT("GameParameter"), Type::GameParameter}, + {TEXT("MotionBus"), Type::MotionBus}, + {TEXT("PhysicalFolder"), Type::PhysicalFolder}, + {TEXT("Project"), Type::Project}, + {TEXT("RandomSequenceContainer"), Type::RandomSequenceContainer}, + {TEXT("Sound"), Type::Sound}, + {TEXT("State"), Type::State}, + {TEXT("StateGroup"), Type::StateGroup}, + {TEXT("Switch"), Type::Switch}, + {TEXT("SwitchContainer"), Type::SwitchContainer}, + {TEXT("SwitchGroup"), Type::SwitchGroup}, + {TEXT("Trigger"), Type::Trigger}, + {TEXT("WorkUnit"), Type::StandaloneWorkUnit}, + {TEXT("Effect"), Type::EffectShareSet}, + + }; + + for (const auto& type : ValidTypes) + { + if (type.Name == ItemName) + { + return type.Value; + } + } + + return Type::None; + } + + inline Type FromFolderName(const FString& ItemName) + { + struct TypePair + { + FString Name; + Type Value; + }; + + static const TypePair ValidTypes[] = { + {TEXT("Virtual Acoustics"), Type::AcousticTexture}, + {TEXT("Master-Mixer Hierarchy"), Type::AuxBus}, + {TEXT("Events"), Type::Event}, + {TEXT("Game Parameters"), Type::GameParameter}, + {TEXT("States"), Type::State}, + {TEXT("Switches"), Type::Switch}, + {TEXT("Triggers"), Type::Trigger}, + {TEXT("Effects"), Type::EffectShareSet}, + }; + + for (const auto& type : ValidTypes) + { + if (type.Name == ItemName) + { + return type.Value; + } + } + + return Type::None; + } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAcousticPortal.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAcousticPortal.cpp new file mode 100644 index 0000000..cada302 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAcousticPortal.cpp @@ -0,0 +1,1026 @@ +/******************************************************************************* +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()->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& 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(); + 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(); + ShowPortalsChangedHandle = AkSettings->OnShowRoomsPortalsChanged.AddLambda([this, AkSettings]() + { + if (AkSettings->VisualizeRoomsAndPortals) + { + InitializeDrawComponent(); + } + else + { + DestroyDrawComponent(); + } + }); + } +} + + +void UAkPortalComponent::InitializeDrawComponent() +{ + if (AActor* Owner = GetOwner()) + { + if (DrawPortalComponent == nullptr) + { + DrawPortalComponent = NewObject(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(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 +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 front = RoomIndex.Query(frontPoint, GetWorld()); + if (front.Num() > 0) + out_pFront = front[0]; + + TArray back = RoomIndex.Query(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(FAkAudioStyle::GetAkForegroundTextMaterial()); + FrontRoomText = NewObject(GetOuter(), *(NamePrefix + "_FrontRoomName")); + BackRoomText = NewObject(GetOuter(), *(NamePrefix + "_BackRoomName")); + FrontRoomText->SetText(FText::FromString("")); + BackRoomText->SetText(FText::FromString("")); + TArray 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(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> 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& A, TTuple& 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 + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAcousticTexture.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAcousticTexture.cpp new file mode 100644 index 0000000..1d6303b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAcousticTexture.cpp @@ -0,0 +1,129 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkAcousticTexture.h" +#include "Wwise/Stats/AkAudio.h" + +#if WITH_EDITORONLY_DATA +#include "Wwise/WwiseProjectDatabase.h" +#include "Wwise/WwiseResourceCooker.h" +#include "AkAudioDevice.h" +#endif + +void UAkAcousticTexture::Serialize(FArchive& Ar) +{ + Super::Serialize(Ar); + + if (HasAnyFlags(RF_ClassDefaultObject)) + { + return; + } +#if !UE_SERVER +#if WITH_EDITORONLY_DATA + if (Ar.IsCooking() && Ar.IsSaving() && !Ar.CookingTarget()->IsServerOnly()) + { + FWwiseAcousticTextureCookedData CookedDataToArchive; + if (auto* ResourceCooker = FWwiseResourceCooker::GetForArchive(Ar)) + { + ResourceCooker->PrepareCookedData(CookedDataToArchive, GetValidatedInfo(AcousticTextureInfo)); + } + CookedDataToArchive.Serialize(Ar); + } +#else + AcousticTextureCookedData.Serialize(Ar); +#endif +#endif + +} + +#if WITH_EDITORONLY_DATA +bool UAkAcousticTexture::ObjectIsInSoundBanks() +{ + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkAcousticTexture::GetWwiseRef: ResourceCooker not initialized")); + return false; + } + + auto ProjectDatabase = ResourceCooker->GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkAcousticTexture::GetWwiseRef: ProjectDatabase not initialized")); + return false; + } + + FWwiseObjectInfo* AudioTypeInfo = &AcousticTextureInfo; + const FWwiseRefAcousticTexture AcousticTextureRef = FWwiseDataStructureScopeLock(*ProjectDatabase).GetAcousticTexture( + GetValidatedInfo(AcousticTextureInfo)); + + return AcousticTextureRef.IsValid(); +} + +void UAkAcousticTexture::FillInfo() +{ + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkAcousticTexture::FillInfo: ResourceCooker not initialized")); + return; + } + + auto ProjectDatabase = ResourceCooker->GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkAcousticTexture::FillInfo: ProjectDatabase not initialized")); + return; + } + + FWwiseObjectInfo* AudioTypeInfo = &AcousticTextureInfo; + const FWwiseRefAcousticTexture AcousticTextureRef = FWwiseDataStructureScopeLock(*ProjectDatabase).GetAcousticTexture( + GetValidatedInfo(AcousticTextureInfo)); + + if (AcousticTextureRef.AcousticTextureName().IsNone() || !AcousticTextureRef.AcousticTextureGuid().IsValid() || AcousticTextureRef.AcousticTextureId() == AK_INVALID_UNIQUE_ID) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkAcousticTexture::FillInfo: Valid object not found in Project Database")); + return; + } + + AudioTypeInfo->WwiseName = AcousticTextureRef.AcousticTextureName(); + AudioTypeInfo->WwiseGuid = AcousticTextureRef.AcousticTextureGuid(); + AudioTypeInfo->WwiseShortId = AcousticTextureRef.AcousticTextureId(); +} + +void UAkAcousticTexture::GetAcousticTextureCookedData() +{ + SCOPED_AKAUDIO_EVENT_2(TEXT("GetAcousticTextureCookedData")); + if (IWwiseProjectDatabaseModule::IsInACookingCommandlet()) + { + return; + } + auto* ProjectDatabase = FWwiseProjectDatabase::Get(); + if (!ProjectDatabase || !ProjectDatabase->IsProjectDatabaseParsed()) + { + UE_LOG(LogAkAudio, VeryVerbose, TEXT("UAkAcousticTexture::GetAcousticTextureCookedData: Not loading '%s' because project database is not parsed."), *GetName()) + return; + } + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + return; + } + + ResourceCooker->PrepareCookedData(AcousticTextureCookedData, GetValidatedInfo(AcousticTextureInfo)); +} +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAcousticTextureSetComponent.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAcousticTextureSetComponent.cpp new file mode 100644 index 0000000..bb2155d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAcousticTextureSetComponent.cpp @@ -0,0 +1,252 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkAcousticTextureSetComponent.h" +#include "AkAudioDevice.h" +#include "AkRoomComponent.h" +#include "AkReverbDescriptor.h" +#include "AkComponentHelpers.h" +#include "AkLateReverbComponent.h" +#include "AkSpatialAudioHelper.h" + +UAkAcousticTextureSetComponent::UAkAcousticTextureSetComponent(const class FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = true; + bTickInEditor = true; +#if WITH_EDITOR + if (AkSpatialAudioHelper::GetObjectReplacedEvent()) + { + AkSpatialAudioHelper::GetObjectReplacedEvent()->AddUObject(this, &UAkAcousticTextureSetComponent::HandleObjectsReplaced); + } +#endif +} + +void UAkAcousticTextureSetComponent::OnRegister() +{ + Super::OnRegister(); +#if WITH_EDITOR + RegisterAllTextureParamCallbacks(); + RegisterReverbRTPCChangedCallback(); +#endif + // In the case where a blueprint class has a texture set component and a late reverb component as siblings, We can't know which will be registered first. + // We need to check for the sibling in each OnRegister function and associate the texture set component to the late reverb when they are both registered. + if (USceneComponent* parent = GetAttachParent()) + { + if (UAkLateReverbComponent* reverbComp = AkComponentHelpers::GetChildComponentOfType(*parent)) + { + reverbComp->AssociateAkTextureSetComponent(this); + } + } + DampingEstimationNeedsUpdate = true; +} + +void UAkAcousticTextureSetComponent::OnUnregister() +{ +#if WITH_EDITOR + UnregisterTextureParamChangeCallbacks(); + UAkSettings* AkSettings = GetMutableDefault(); + if (AkSettings != nullptr) + { + if (RTPCChangedHandle.IsValid()) + AkSettings->OnReverbRTPCChanged.Remove(RTPCChangedHandle); + } +#endif + Super::OnUnregister(); +} + +void UAkAcousticTextureSetComponent::BeginPlay() +{ + Super::BeginPlay(); + DampingEstimationNeedsUpdate = true; +} + +void UAkAcousticTextureSetComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + if (SecondsSinceDampingUpdate < PARAM_ESTIMATION_UPDATE_PERIOD) + { + SecondsSinceDampingUpdate += DeltaTime; + } + if (DampingEstimationNeedsUpdate && SecondsSinceDampingUpdate >= PARAM_ESTIMATION_UPDATE_PERIOD) + { + RecalculateHFDamping(); + DampingEstimationNeedsUpdate = false; + } +} + +void UAkAcousticTextureSetComponent::SetReverbDescriptor(FAkReverbDescriptor* reverbDescriptor) +{ + ReverbDescriptor = reverbDescriptor; +#if WITH_EDITOR + UnregisterTextureParamChangeCallbacks(); + if (reverbDescriptor != nullptr) + RegisterAllTextureParamCallbacks(); +#endif + if (reverbDescriptor != nullptr) + DampingEstimationNeedsUpdate = true; +} + +void UAkAcousticTextureSetComponent::RecalculateHFDamping() +{ + if (ReverbDescriptor != nullptr && ReverbDescriptor->ShouldEstimateDamping()) + { + ReverbDescriptor->CalculateHFDamping(this); + SecondsSinceDampingUpdate = 0.0f; + } +} + +#if WITH_EDITOR +void UAkAcousticTextureSetComponent::BeginDestroy() +{ + Super::BeginDestroy(); + if (AkSpatialAudioHelper::GetObjectReplacedEvent()) + { + AkSpatialAudioHelper::GetObjectReplacedEvent()->RemoveAll(this); + } +} + +void UAkAcousticTextureSetComponent::HandleObjectsReplaced(const TMap& ReplacementMap) +{ + if (ReplacementMap.Contains(this)) + { + UAkAcousticTextureSetComponent* NewTextureSetComponent = Cast(ReplacementMap[this]); + if (USceneComponent* Parent = NewTextureSetComponent->GetAttachParent()) + { + if (UAkLateReverbComponent* ReverbComp = AkComponentHelpers::GetChildComponentOfType(*Parent)) + { + ReverbComp->AssociateAkTextureSetComponent(NewTextureSetComponent); + } + } + } +} + +void UAkAcousticTextureSetComponent::RegisterReverbRTPCChangedCallback() +{ + UAkSettings* AkSettings = GetMutableDefault(); + if (AkSettings != nullptr) + { + if (RTPCChangedHandle.IsValid()) + AkSettings->OnReverbRTPCChanged.Remove(RTPCChangedHandle); + RTPCChangedHandle = AkSettings->OnReverbRTPCChanged.AddLambda([this]() + { + DampingEstimationNeedsUpdate = true; + }); + } +} + +void UAkAcousticTextureSetComponent::RegisterTextureParamChangeCallback(FGuid textureID) +{ + UAkSettings* AkSettings = GetMutableDefault(); + if (AkSettings != nullptr) + { + if (TextureDelegateHandles.Find(textureID) != nullptr) + { + if (TextureDelegateHandles[textureID].IsValid()) + { + AkSettings->OnTextureParamsChanged.Remove(TextureDelegateHandles[textureID]); + } + TextureDelegateHandles.Remove(textureID); + } + TextureDelegateHandles.Add(textureID, AkSettings->OnTextureParamsChanged.AddLambda([&](const FGuid& textureID) + { + if (ContainsTexture(textureID) && ReverbDescriptor != nullptr) + DampingEstimationNeedsUpdate = ReverbDescriptor->ShouldEstimateDamping(); + })); + } +} + +void UAkAcousticTextureSetComponent::UnregisterTextureParamChangeCallbacks() +{ + UAkSettings* AkSettings = GetMutableDefault(); + if (AkSettings != nullptr) + { + for (auto it = TextureDelegateHandles.CreateIterator(); it; ++it) + { + if (it->Value.IsValid()) + AkSettings->OnTextureParamsChanged.Remove(it->Value); + } + TextureDelegateHandles.Empty(); + } +} +#endif + +bool UAkAcousticTextureSetComponent::ShouldSendGeometry() const +{ + UWorld* CurrentWorld = GetWorld(); + if (CurrentWorld && !IsRunningCommandlet()) + { + return CurrentWorld->WorldType == EWorldType::Game || CurrentWorld->WorldType == EWorldType::PIE; + } + return false; +} + +void UAkAcousticTextureSetComponent::SendGeometryToWwise(const AkGeometryParams& params) +{ + if (ShouldSendGeometry()) + { + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (AkAudioDevice != nullptr && AkAudioDevice->SetGeometry(GetGeometrySetID(), params) == AK_Success) + GeometryHasBeenSent = true; + } +} + +void UAkAcousticTextureSetComponent::SendGeometryInstanceToWwise(const FRotator& rotation, const FVector& location, const FVector& scale, const AkRoomID roomID) +{ + if (ShouldSendGeometry() && GeometryHasBeenSent) + { + AkVector front, up; + AkVector64 position; + FAkAudioDevice::FVectorToAKVector(rotation.RotateVector(FVector::ForwardVector), front); + FAkAudioDevice::FVectorToAKVector(rotation.RotateVector(FVector::UpVector), up); + FAkAudioDevice::FVectorToAKVector64(location, position); + + AkGeometryInstanceParams params; + params.PositionAndOrientation.Set(position, front, up); + FAkAudioDevice::FVectorToAKVector(scale, params.Scale); + params.GeometrySetID = GetGeometrySetID(); + params.RoomID = roomID; + + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (AkAudioDevice != nullptr && AkAudioDevice->SetGeometryInstance(GetGeometrySetID(), params) == AK_Success) + GeometryInstanceHasBeenSent = true; + } +} + +void UAkAcousticTextureSetComponent::RemoveGeometryFromWwise() +{ + if (ShouldSendGeometry() && GeometryHasBeenSent) + { + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (AkAudioDevice != nullptr && AkAudioDevice->RemoveGeometrySet(GetGeometrySetID()) == AK_Success) + { + GeometryHasBeenSent = false; + GeometryInstanceHasBeenSent = false; + } + } +} + +void UAkAcousticTextureSetComponent::RemoveGeometryInstanceFromWwise() +{ + if (ShouldSendGeometry() && GeometryInstanceHasBeenSent) + { + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (AkAudioDevice != nullptr && AkAudioDevice->RemoveGeometrySet(GetGeometrySetID()) == AK_Success) + GeometryInstanceHasBeenSent = false; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAmbientSound.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAmbientSound.cpp new file mode 100644 index 0000000..edf7d00 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAmbientSound.cpp @@ -0,0 +1,136 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkAmbientSound.cpp: +=============================================================================*/ + +#include "AkAmbientSound.h" +#include "AkAudioDevice.h" +#include "AkComponent.h" +#include "AkAudioEvent.h" +#include "Wwise/WwiseExternalSourceManager.h" + +/*------------------------------------------------------------------------------------ + AAkAmbientSound +------------------------------------------------------------------------------------*/ + +AAkAmbientSound::AAkAmbientSound(const class FObjectInitializer& ObjectInitializer) : +Super(ObjectInitializer) +{ + // Property initialization + StopWhenOwnerIsDestroyed = true; + CurrentlyPlaying = false; + + static const FName ComponentName = TEXT("AkAudioComponent0"); + AkComponent = ObjectInitializer.CreateDefaultSubobject(this, ComponentName); + + AkComponent->StopWhenOwnerDestroyed = StopWhenOwnerIsDestroyed; + + RootComponent = AkComponent; + + AkComponent->AttenuationScalingFactor = 1.f; + + //bNoDelete = true; + SetHidden(true); + AutoPost = false; +} + +void AAkAmbientSound::PostLoad() +{ + Super::PostLoad(); +#if WITH_EDITOR + if( AkAudioEvent_DEPRECATED ) + { + AkComponent->AkAudioEvent = AkAudioEvent_DEPRECATED; + } +#endif +} + +void AAkAmbientSound::PostInitializeComponents() +{ + Super::PostInitializeComponents(); + AkComponent->UpdateAkLateReverbComponentList(AkComponent->GetComponentLocation()); +} + +void AAkAmbientSound::BeginPlay() +{ + Super::BeginPlay(); + if (AutoPost) + { + StartAmbientSound(); + } +} + + +#if WITH_EDITOR +void AAkAmbientSound::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + if( AkComponent ) + { + // Reset audio component. + if( IsCurrentlyPlaying() ) + { + StartPlaying(); + } + } + Super::PostEditChangeProperty(PropertyChangedEvent); +} +#endif + +void AAkAmbientSound::StartAmbientSound() +{ + StartPlaying(); +} + +void AAkAmbientSound::StopAmbientSound() +{ + StopPlaying(); +} + +void AAkAmbientSound::StartPlaying() +{ + if( !IsCurrentlyPlaying() ) + { + if (AkComponent->AkAudioEvent) + { + AkComponent->AkAudioEvent->PostOnActor(this, nullptr, nullptr, nullptr, (AkCallbackType)0, nullptr, StopWhenOwnerIsDestroyed); + return; + } + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (AkAudioDevice) + { + AkAudioDevice->SetAttenuationScalingFactor(this, AkComponent->AttenuationScalingFactor); + + AkPlayingID pID = AkAudioDevice->PostEventOnActor(AkAudioDevice->GetShortID(AkComponent->AkAudioEvent, AkComponent->EventName), this, 0, NULL, NULL, StopWhenOwnerIsDestroyed, {}); + } + } +} + +void AAkAmbientSound::StopPlaying() +{ + if( IsCurrentlyPlaying() ) + { + // State of CurrentlyPlaying gets updated in UAkComponent::Stop() through the EndOfEvent callback. + AkComponent->Stop(); + } +} + +bool AAkAmbientSound::IsCurrentlyPlaying() +{ + return AkComponent != nullptr && AkComponent->HasActiveEvents(); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAudioDevice.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAudioDevice.cpp new file mode 100644 index 0000000..92cb507 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAudioDevice.cpp @@ -0,0 +1,4791 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkAudioDevice.cpp: Audiokinetic Audio interface object. +=============================================================================*/ + +#define AK_ENABLE_ROOMS +#define AK_ENABLE_PORTALS + +#include "AkAudioDevice.h" + +#include "AkAcousticPortal.h" +#include "AkAudioEvent.h" +#include "AkAudioModule.h" +#include "Wwise/WwiseFileHandlerModule.h" +#include "Wwise/WwiseIOHook.h" +#include "Wwise/WwiseResourceLoader.h" +#include "Wwise/WwiseResourceLoaderImpl.h" +#include "Wwise/API/WAAPI.h" +#include "Wwise/API/WwiseMonitorAPI.h" +#include "Wwise/API/WwiseSoundEngineAPI.h" +#include "Wwise/API/WwiseSpatialAudioAPI.h" +#include "Wwise/API/WwiseStreamMgrAPI.h" +#include "Wwise/Stats/Global.h" +#include "WwiseInitBankLoader/WwiseInitBankLoader.h" + +#include "AkCallbackInfoPool.h" +#include "AkComponent.h" +#include "AkComponentCallbackManager.h" +#include "AkComponentHelpers.h" +#include "AkGameObject.h" +#include "AkLateReverbComponent.h" +#include "AkRoomComponent.h" +#include "AkRtpc.h" +#include "AkSettings.h" + +#include "AkSpotReflector.h" +#include "AkStateValue.h" +#include "AkSwitchValue.h" +#include "AkTrigger.h" +#include "AkUnrealHelper.h" +#include "AkWaapiClient.h" +#include "AkWaapiUtils.h" + +#include "EngineUtils.h" +#include "Async/Async.h" +#include "Async/TaskGraphInterfaces.h" +#include "Camera/PlayerCameraManager.h" +#include "Components/BrushComponent.h" +#include "Engine/GameEngine.h" +#include "GameFramework/PlayerController.h" +#include "GameFramework/WorldSettings.h" +#include "InitializationSettings/AkInitializationSettings.h" +#include "Internationalization/Culture.h" +#include "Internationalization/Internationalization.h" +#include "Misc/App.h" +#include "Misc/ScopeLock.h" +#include "Runtime/Launch/Resources/Version.h" +#include "UObject/Object.h" +#include "UObject/UObjectGlobals.h" +#include "UObject/UObjectIterator.h" +#include "Wwise/WwiseExternalSourceManager.h" + +#if WITH_EDITOR +#include "Editor.h" +#include "EditorSupportDelegates.h" +#include "LevelEditor.h" +#include "UnrealEdMisc.h" + +#ifndef AK_OPTIMIZED +#include "AkSettingsPerUser.h" +#endif + +#endif + +#if WITH_EDITORONLY_DATA && !defined(AK_OPTIMIZED) +#include "Wwise/WwiseProjectDatabase.h" +#endif + +#include + +/*------------------------------------------------------------------------------------ + Statics and Globals +------------------------------------------------------------------------------------*/ + +bool FAkAudioDevice::m_bSoundEngineInitialized = false; +bool FAkAudioDevice::m_EngineExiting = false; +TMap> FAkAudioDevice::EventToPlayingIDMap; +TMap FAkAudioDevice::PlayingIDToAudioContextMap; +TMap FAkAudioDevice::OnSwitchValueLoadedMap; +TArray> FAkAudioDevice::AudioObjectsToLoadAfterInitialization; + +FCriticalSection FAkAudioDevice::EventToPlayingIDMapCriticalSection; + +/*------------------------------------------------------------------------------------ + Defines +------------------------------------------------------------------------------------*/ + +static constexpr auto InvariantLCID = 0x7F; + +/*------------------------------------------------------------------------------------ + Helpers +------------------------------------------------------------------------------------*/ + +namespace FAkAudioDevice_Helpers +{ +#if WITH_EDITORONLY_DATA && !defined(AK_OPTIMIZED) + static TMap ComponentNameMap; +#endif + + AKRESULT RegisterGameObject(AkGameObjectID in_gameObjId, const FString& Name) + { + AKRESULT Result; + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + +#ifdef AK_OPTIMIZED + Result = SoundEngine->RegisterGameObj(in_gameObjId); +#else + if (Name.Len() > 0) + { + Result = SoundEngine->RegisterGameObj(in_gameObjId, TCHAR_TO_ANSI(*Name)); + } + else + { + Result = SoundEngine->RegisterGameObj(in_gameObjId); + } +#endif +#if WITH_EDITORONLY_DATA && !defined(AK_OPTIMIZED) + if (Result == AK_Success) + { + ComponentNameMap.Add(in_gameObjId, FName(Name)); + } +#endif + UE_CLOG(LIKELY(Result == AK_Success), LogAkAudio, VeryVerbose, TEXT("Registered Object ID %" PRIu64 " (%s)"), in_gameObjId, *Name); + UE_CLOG(UNLIKELY(Result != AK_Success), LogAkAudio, Warning, TEXT("Error registering Object ID %" PRIu64 " (%s): (%" PRIu32 ") %s"), in_gameObjId, *Name, Result, AkUnrealHelper::GetResultString(Result)); + + return Result; + } + + typedef TMap FDelegateLocationMap; + FDelegateLocationMap DelegateLocationMap; + + void GlobalCallback(AK::IAkGlobalPluginContext* Context, AkGlobalCallbackLocation Location, void* Cookie) + { + const FAkAudioDeviceDelegates::FOnAkGlobalCallback* Delegate = DelegateLocationMap.Find(Location); + if (Delegate && Delegate->IsBound()) + { + Delegate->Broadcast(Context, Location); + } + } + + void UnregisterGlobalCallbackDelegate(FAkAudioDeviceDelegates::FOnAkGlobalCallback* Delegate, FDelegateHandle Handle, AkGlobalCallbackLocation Location) + { + if (!Delegate) + return; + + Delegate->Remove(Handle); + if (Delegate->IsBound()) + return; + + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + SoundEngine->UnregisterGlobalCallback(GlobalCallback, Location); + } + + void UnregisterAllGlobalCallbacks() + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + + if (LIKELY(SoundEngine)) + { + for (auto DelegateLocationPair : FAkAudioDevice_Helpers::DelegateLocationMap) + { + auto Location = DelegateLocationPair.Key; + SoundEngine->UnregisterGlobalCallback(GlobalCallback, Location); + } + } + FAkAudioDevice_Helpers::DelegateLocationMap.Empty(); + } +} + +#if WITH_EDITORONLY_DATA && !defined(AK_OPTIMIZED) +static bool GetInfoErrorMessageTranslatorFunction(IWwiseSoundEngineAPI::TagInformationBridge::Type* in_pTagList, AkUInt32 in_uCount, AkUInt32& out_uTranslated) +{ + FString Name; + FWwiseDataStructureScopeLock DB(*FWwiseProjectDatabase::Get()); + for (AkUInt32 i = 0; i < in_uCount && out_uTranslated != in_uCount; i++) + { + auto& Tag = in_pTagList[i]; + if (Tag.m_infoIsParsed) + continue; + + const AkUInt32 ID = FPlatformMemory::ReadUnaligned((AkUInt8*)Tag.m_args); + const auto AssetInfo = FWwiseObjectInfo(ID); + + switch (*Tag.m_pTag) + { + case 'b': //Bank ID + { + const auto RefBank = DB.GetSoundBank(AssetInfo); + if (LIKELY(RefBank.IsValid())) + { + Name = RefBank.SoundBankShortName().ToString(); + } + break; + } + case 'g': //The only tag in 64 bits + { + const AkUInt64 GoId = FPlatformMemory::ReadUnaligned((AkUInt8*)Tag.m_args); + const auto* NamePtr = FAkAudioDevice_Helpers::ComponentNameMap.Find(GoId); + if (LIKELY(NamePtr)) + { + Name = NamePtr->ToString(); + } + break; + } + case 'm': //Media ID + { + const auto RefMedia = DB.GetMediaFile(AssetInfo); + if (LIKELY(RefMedia.IsValid())) + { + Name = RefMedia.MediaShortName().ToString(); + } + break; + } + case 'p': //Plugin + { + const auto RefPlugin = DB.GetCustomPlugin(AssetInfo); + if (LIKELY(RefPlugin.IsValid())) + { + Name = RefPlugin.CustomPluginName().ToString(); + } + break; + } + case 's': //SwitchStates ID + { + const auto RefSwitchGroup = DB.GetSwitchGroup(AssetInfo); + if (RefSwitchGroup.IsValid()) + { + Name = RefSwitchGroup.SwitchGroupName().ToString(); + break; + } + + const auto RefStateGroup = DB.GetStateGroup(AssetInfo); + if (RefStateGroup.IsValid()) + { + Name = RefStateGroup.StateGroupName().ToString(); + } + break; + } + case 'w': //WwiseObject ID + { + //$w is generic, it can mean a lot of unrelated types. + const TCHAR* FoundType = TEXT(""); + FString FoundName; + + const auto EventInfo = FWwiseEventInfo(ID); + const auto RefEvents = DB.GetEvent(EventInfo); + if (RefEvents.Num() > 0) + { + FoundType = TEXT("Event"); + FoundName = RefEvents.Array()[0].EventName().ToString(); + } + + const auto RefGameParameter = DB.GetGameParameter(AssetInfo); + if (RefGameParameter.IsValid()) + { + const auto NewName = RefGameParameter.GameParameterName().ToString(); + if (UNLIKELY(!FoundName.IsEmpty() && NewName != FoundName)) + { + UE_LOG(LogAkAudio, Warning, TEXT("Found two different names for the same object ID %" PRIu32 ": %s %s and GameParameter %s. Ignoring."), FoundType, *FoundName, *NewName); + continue; + } + FoundType = TEXT("GameParameter"); + FoundName = NewName; + } + + const auto RefShareSet = DB.GetPluginShareSet(AssetInfo); + if (RefShareSet.IsValid()) + { + const auto NewName = RefShareSet.PluginShareSetName().ToString(); + if (UNLIKELY(!FoundName.IsEmpty() && NewName != FoundName)) + { + UE_LOG(LogAkAudio, Warning, TEXT("Found two different names for the same object ID %" PRIu32 ": %s %s and ShareSet %s. Ignoring."), FoundType, *FoundName, *NewName); + continue; + } + FoundType = TEXT("ShareSet"); + FoundName = NewName; + } + + const auto RefBus = DB.GetBus(AssetInfo); + if (RefBus.IsValid()) + { + const auto NewName = RefBus.BusName().ToString(); + if (UNLIKELY(!FoundName.IsEmpty() && NewName != FoundName)) + { + UE_LOG(LogAkAudio, Warning, TEXT("Found two different names for the same object ID %" PRIu32 ": %s %s and Bus %s. Ignoring."), FoundType, *FoundName, *NewName); + continue; + } + FoundType = TEXT("Bus"); + FoundName = NewName; + } + + const auto RefAuxBus = DB.GetAuxBus(AssetInfo); + if (RefAuxBus.IsValid()) + { + const auto NewName = RefAuxBus.AuxBusName().ToString(); + if (UNLIKELY(!FoundName.IsEmpty() && NewName != FoundName)) + { + UE_LOG(LogAkAudio, Warning, TEXT("Found two different names for the same object ID %" PRIu32 ": %s %s and AuxBus %s. Ignoring."), FoundType, *FoundName, *NewName); + continue; + } + FoundType = TEXT("AuxBus"); + FoundName = NewName; + } + // Can be other things, such as Sound. + + if (!FoundName.IsEmpty()) + { + Name = FoundName; + break; + } + else + { + continue; + } + } + default: + continue; //Not supported, ignore. + } + + if (!Name.IsEmpty()) + { + AKPLATFORM::SafeStrCpy(Tag.m_parsedInfo, TCHAR_TO_AK(*Name), AK_TRANSLATOR_MAX_NAME_SIZE); + Tag.m_len = Name.Len(); + Tag.m_infoIsParsed = true; + out_uTranslated++; + } + } + return out_uTranslated == in_uCount; +} +#endif + +/*------------------------------------------------------------------------------------ + Implementation +------------------------------------------------------------------------------------*/ + +FDelegateHandle FAkAudioDevice::RegisterGlobalCallback(FAkAudioDeviceDelegates::FOnAkGlobalCallback::FDelegate Callback, AkGlobalCallbackLocation Location) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return {}; + + auto& Delegate = FAkAudioDevice_Helpers::DelegateLocationMap.FindOrAdd(Location); + FDelegateHandle Handle = Delegate.Add(Callback); + auto result = SoundEngine->RegisterGlobalCallback(FAkAudioDevice_Helpers::GlobalCallback, Location); + if (result != AK_Success) + { + FAkAudioDevice_Helpers::UnregisterGlobalCallbackDelegate(&Delegate, Handle, Location); + Handle.Reset(); + } + + return Handle; +} + +void FAkAudioDevice::UnregisterGlobalCallback(FDelegateHandle Handle, AkGlobalCallbackLocation Location) +{ + const auto& Delegate = FAkAudioDevice_Helpers::DelegateLocationMap.Find(Location); + FAkAudioDevice_Helpers::UnregisterGlobalCallbackDelegate(Delegate, Handle, Location); +} + +AKRESULT FAkAudioDevice::RegisterOutputDeviceMeteringCallback(AkOutputDeviceID OutputID, + AkOutputDeviceMeteringCallbackFunc Callback, + AkMeteringFlags MeteringFlags, + void* Cookie) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + return SoundEngine->RegisterOutputDeviceMeteringCallback(OutputID, Callback, MeteringFlags, Cookie); +} + +AKRESULT FAkAudioDevice::UnregisterOutputDeviceMeteringCallback(AkOutputDeviceID OutputID) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + return SoundEngine->RegisterOutputDeviceMeteringCallback(OutputID, nullptr, AK_NoMetering, nullptr); +} + +#if WITH_EDITORONLY_DATA +UAkComponent* FAkAudioDevice::CreateListener(UWorld* World, FEditorViewportClient* ViewportClient) +{ + if (!IsRunningGame()) + { + FString ComponentName = TEXT("AkListener_") + World->GetName(); + if (ViewportClient) + { + ComponentName = TEXT("AkListener_") + FString::FromInt(ViewportClient->ViewIndex) + World->GetName(); + } + UAkComponent* Listener = NewObject(World->GetWorldSettings(), FName(*ComponentName), RF_Transient); + if (Listener != nullptr) + { + Listener->MarkAsEditorOnlySubobject(); + Listener->RegisterComponentWithWorld(World); + AddDefaultListener(Listener); + } + + return Listener; + } + else + { + return nullptr; + } +} + +FTransform FAkAudioDevice::GetEditorListenerPosition(int32 ViewIndex) const +{ + if (ViewIndex < ListenerTransforms.Num()) + { + return ListenerTransforms[ViewIndex]; + } + + return FTransform(); +} + +#endif + +bool FAkAudioDevice::ShouldNotifySoundEngine(EWorldType::Type WorldType) { return WorldType == EWorldType::PIE || WorldType == EWorldType::Game; } + +void FAkAudioDevice::LoadAudioObjectsAfterInitialization(TWeakObjectPtr&& InAudioType) +{ + AudioObjectsToLoadAfterInitialization.Add(InAudioType); +} + +void FAkAudioDevice::LoadDelayedObjects() +{ + if (AudioObjectsToLoadAfterInitialization.Num() > 0) + { + SCOPED_AKAUDIO_EVENT_F_3(TEXT("LoadDelayedObjects: Loading %d Object%s"), (int)AudioObjectsToLoadAfterInitialization.Num(), AudioObjectsToLoadAfterInitialization.Num() > 1 ? TEXT("s") : TEXT("")); + UE_LOG(LogAkAudio, Log, TEXT("FAkAudioDevice::LoadDelayedObjects: Loading %d delayed Wwise Object%s after initialization."), AudioObjectsToLoadAfterInitialization.Num(), AudioObjectsToLoadAfterInitialization.Num() > 1 ? TEXT("s") : TEXT("")); + for (auto& WeakAudioType : AudioObjectsToLoadAfterInitialization) + { + auto* AudioType = WeakAudioType.Get(); + if (LIKELY(AudioType)) + { + AudioType->LoadData(); + } + } + AudioObjectsToLoadAfterInitialization.Empty(); + } +} + +namespace FAkAudioDevice_WaapiHelper +{ + void Subscribe(FAkWaapiClient* waapiClient, uint64& subscriptionId, const char* uri, const TSharedRef& options, WampEventCallback callback) + { + if (subscriptionId == 0) + { + TSharedPtr result; + waapiClient->Subscribe(uri, options, callback, subscriptionId, result); + } + } + + void RemoveCallback(FAkWaapiClient* waapiClient, uint64& subscriptionId) + { + if (subscriptionId > 0) + { + waapiClient->RemoveWampEventCallback(subscriptionId); + subscriptionId = 0; + } + } + + void Unsubscribe(FAkWaapiClient* waapiClient, uint64& subscriptionId) + { + if (subscriptionId > 0) + { + TSharedPtr result; + waapiClient->Unsubscribe(subscriptionId, result); + + RemoveCallback(waapiClient, subscriptionId); + } + } +} + +/** + * Initializes the audio device and creates sources. + * + * @return true if initialization was successful, false otherwise + */ +bool FAkAudioDevice::Init() +{ + SCOPED_AKAUDIO_EVENT_2(TEXT("FAkAudioDevice::Init")); +#if UE_SERVER + return false; +#endif + if (!EnsureInitialized()) // ensure audiolib is initialized + { + UE_LOG(LogAkAudio, Log, TEXT("Audiokinetic Audio Device initialization failed.")); + return false; + } + +#if !WITH_EDITOR + if (auto* akSettings = GetDefault()) + { + for (auto& entry : akSettings->UnrealCultureToWwiseCulture) + { + auto culturePtr = FInternationalization::Get().GetCulture(entry.Key); + if (culturePtr && culturePtr->GetLCID() != InvariantLCID) + { + CachedUnrealToWwiseCulture.Add(culturePtr, entry.Value); + } + } + } +#endif + +#if AK_SUPPORT_WAAPI + if (auto waapiClient = FAkWaapiClient::Get()) + { + ProjectLoadedHandle = waapiClient->OnProjectLoaded.AddLambda([this, waapiClient] + { + if (!waapiClient->IsConnected()) + return; + + TSharedRef options = MakeShareable(new FJsonObject()); + options->SetArrayField(WwiseWaapiHelper::RETURN, TArray> { MakeShareable(new FJsonValueString(WwiseWaapiHelper::PARENT)) }); + + auto wampEventCallback = WampEventCallback::CreateLambda([this](uint64_t id, TSharedPtr in_UEJsonObject) + { + AsyncTask(ENamedThreads::GameThread, [this] + { +#if WITH_EDITOR + FEditorSupportDelegates::RedrawAllViewports.Broadcast(); +#endif + OnWwiseProjectModification.Broadcast(); + }); + }); + + FAkAudioDevice_WaapiHelper::Subscribe(waapiClient, WaapiSubscriptionIds.Renamed, ak::wwise::core::object::nameChanged, options, wampEventCallback); + FAkAudioDevice_WaapiHelper::Subscribe(waapiClient, WaapiSubscriptionIds.PreDeleted, ak::wwise::core::object::preDeleted, options, wampEventCallback); + FAkAudioDevice_WaapiHelper::Subscribe(waapiClient, WaapiSubscriptionIds.ChildRemoved, ak::wwise::core::object::childRemoved, options, wampEventCallback); + FAkAudioDevice_WaapiHelper::Subscribe(waapiClient, WaapiSubscriptionIds.ChildAdded, ak::wwise::core::object::childAdded, options, wampEventCallback); + FAkAudioDevice_WaapiHelper::Subscribe(waapiClient, WaapiSubscriptionIds.Created, ak::wwise::core::object::created, options, wampEventCallback); + }); + + ConnectionLostHandle = waapiClient->OnConnectionLost.AddLambda([this, waapiClient] + { + if (!waapiClient->IsConnected()) + return; + + FAkAudioDevice_WaapiHelper::RemoveCallback(waapiClient, WaapiSubscriptionIds.Renamed); + FAkAudioDevice_WaapiHelper::RemoveCallback(waapiClient, WaapiSubscriptionIds.PreDeleted); + FAkAudioDevice_WaapiHelper::RemoveCallback(waapiClient, WaapiSubscriptionIds.ChildRemoved); + FAkAudioDevice_WaapiHelper::RemoveCallback(waapiClient, WaapiSubscriptionIds.ChildAdded); + FAkAudioDevice_WaapiHelper::RemoveCallback(waapiClient, WaapiSubscriptionIds.Created); + }); + + ClientBeginDestroyHandle = waapiClient->OnClientBeginDestroy.AddLambda([this, waapiClient] + { + if (ProjectLoadedHandle.IsValid()) + { + waapiClient->OnProjectLoaded.Remove(ProjectLoadedHandle); + ProjectLoadedHandle.Reset(); + } + + if (ConnectionLostHandle.IsValid()) + { + waapiClient->OnConnectionLost.Remove(ConnectionLostHandle); + ConnectionLostHandle.Reset(); + } + }); + } +#endif // AK_SUPPORT_WAAPI + + FWorldDelegates::OnPreWorldInitialization.AddLambda( + [&](UWorld* World, const UWorld::InitializationValues IVS) + { + WorldVolumesUpdatedMap.Add(World, false); + } + ); + + FWorldDelegates::OnPostWorldInitialization.AddLambda( + [&](UWorld* World, const UWorld::InitializationValues IVS) + { + World->AddOnActorSpawnedHandler(FOnActorSpawned::FDelegate::CreateRaw(this, &FAkAudioDevice::OnActorSpawned)); + } + ); + + FWorldDelegates::OnWorldCleanup.AddLambda( + [this](UWorld* World, bool bSessionEnded, bool bCleanupResources) + { + CleanupComponentMapsForWorld(World); + if (WorldVolumesUpdatedMap.Contains(World)) + WorldVolumesUpdatedMap.Remove(World); + } + ); + + FWorldDelegates::OnWorldPostActorTick.AddLambda( + [&](UWorld* World, ELevelTick TickType, float DeltaSeconds) + { + if (WorldVolumesUpdatedMap.Contains(World)) + WorldVolumesUpdatedMap[World] = false; + else + WorldVolumesUpdatedMap.Add(World, false); + } + ); + + m_SpatialAudioListener = nullptr; + +#if WITH_EDITORONLY_DATA + if (!IsRunningGame()) + { + static const FName kLevelEditorModuleName = TEXT("LevelEditor"); + + auto MapChangedHandler = [this](UWorld* World, EMapChangeType MapChangeType) + { + if (World && World->AllowAudioPlayback() && World->WorldType == EWorldType::Editor) + { + if (MapChangeType == EMapChangeType::TearDownWorld) + { + if (EditorListener && World == EditorListener->GetWorld()) + { + EditorListener->DestroyComponent(); + EditorListener = nullptr; + } + } + else if (EditorListener == nullptr && MapChangeType != EMapChangeType::SaveMap) + { + EditorListener = CreateListener(World); + + // The Editor Listener should NEVER be the spatial audio listener + if (m_SpatialAudioListener == EditorListener) + { + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (LIKELY(SpatialAudio)) + { + SpatialAudio->UnregisterListener(m_SpatialAudioListener->GetAkGameObjectID()); + } + m_SpatialAudioListener = nullptr; + } + } + } + }; + + auto LevelEditorModulePtr = FModuleManager::Get().GetModulePtr(kLevelEditorModuleName); + if (LevelEditorModulePtr) + { + LevelEditorModulePtr->OnMapChanged().AddLambda(MapChangedHandler); + } + else + { + FModuleManager::Get().OnModulesChanged().AddLambda([this, MapChangedHandler](FName InModuleName, EModuleChangeReason Reason) + { + if (InModuleName == kLevelEditorModuleName && Reason == EModuleChangeReason::ModuleLoaded) + { + auto& LevelEditorModule = FModuleManager::GetModuleChecked(kLevelEditorModuleName); + LevelEditorModule.OnMapChanged().AddLambda(MapChangedHandler); + } + }); + } + + FWorldDelegates::OnPostWorldInitialization.AddLambda( + [this](UWorld* World, const UWorld::InitializationValues IVS) + { + if (World && World->AllowAudioPlayback() && World->WorldType == EWorldType::Editor && EditorListener == nullptr) + { + EditorListener = CreateListener(World); + + // The Editor Listener should NEVER be the spatial audio listener + if (m_SpatialAudioListener == EditorListener) + { + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (LIKELY(SpatialAudio)) + { + SpatialAudio->UnregisterListener(m_SpatialAudioListener->GetAkGameObjectID()); + } + m_SpatialAudioListener = nullptr; + } + } + } + ); + + FEditorDelegates::OnEditorCameraMoved.AddLambda( + [&](const FVector& Location, const FRotator& Rotation, ELevelViewportType ViewportType, int32 ViewIndex) + { + auto& allViewportClient = GEditor->GetAllViewportClients(); + if (allViewportClient[ViewIndex]->Viewport && allViewportClient[ViewIndex]->Viewport->HasFocus()) + { + if (ListenerTransforms.Num() <= ViewIndex) + { + ListenerTransforms.AddDefaulted(ViewIndex - ListenerTransforms.Num() + 1); + } + ListenerTransforms[ViewIndex].SetLocation(Location); + ListenerTransforms[ViewIndex].SetRotation(Rotation.Quaternion()); + + UWorld * ViewportWorld = allViewportClient[ViewIndex]->GetWorld(); + if (ViewportWorld && ViewportWorld->WorldType != EWorldType::PIE) + { + auto Quat = Rotation.Quaternion(); + AkSoundPosition soundpos; + FVectorsToAKWorldTransform( + Location, + Quat.GetForwardVector(), + Quat.GetUpVector(), + soundpos + ); + + SetPosition(EditorListener, soundpos); + } + } + } + ); + + FEditorDelegates::BeginPIE.AddRaw(this, &FAkAudioDevice::BeginPIE); + FEditorDelegates::EndPIE.AddRaw(this, &FAkAudioDevice::EndPIE); + FEditorDelegates::PausePIE.AddRaw(this, &FAkAudioDevice::PausePIE); + FEditorDelegates::ResumePIE.AddRaw(this, &FAkAudioDevice::ResumePie); + FEditorDelegates::OnSwitchBeginPIEAndSIE.AddRaw(this, &FAkAudioDevice::OnSwitchBeginPIEAndSIE); + } +#endif + UE_LOG(LogAkAudio, Log, TEXT("Audiokinetic Audio Device initialized.")); + + return 1; +} + +#if WITH_EDITORONLY_DATA +void FAkAudioDevice::BeginPIE(const bool bIsSimulating) +{ + if (!bIsSimulating) + { + RemoveDefaultListener(EditorListener); + } +} + +void FAkAudioDevice::PausePIE(const bool bIsSimulating) +{ + for (auto& Event: EventToPlayingIDMap) + { + for (auto PlayingID: Event.Value) + { + EAkAudioContext* Context = PlayingIDToAudioContextMap.Find(PlayingID); + if (Context) + { + if (*Context == EAkAudioContext::GameplayAudio) + { + ExecuteActionOnPlayingID(AkActionOnEventType::Pause, PlayingID, 0.0f, EAkCurveInterpolation::Linear); + } + } + } + } +} + +void FAkAudioDevice::ResumePie(const bool bIsSimulating) +{ + for (auto& Event: EventToPlayingIDMap) + { + for (auto PlayingID: Event.Value) + { + EAkAudioContext* Context = PlayingIDToAudioContextMap.Find(PlayingID); + if (Context) + { + if (*Context == EAkAudioContext::GameplayAudio) + { + ExecuteActionOnPlayingID(AkActionOnEventType::Resume, PlayingID, 0.0f, EAkCurveInterpolation::Linear); + } + } + } + } +} + +void FAkAudioDevice::OnSwitchBeginPIEAndSIE(const bool bIsSimulating) +{ + if (bIsSimulating) + { + AddDefaultListener(EditorListener); + // The Editor Listener should NEVER be the spatial audio listener + if (m_SpatialAudioListener == EditorListener) + { + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (LIKELY(SpatialAudio)) + { + SpatialAudio->UnregisterListener(m_SpatialAudioListener->GetAkGameObjectID()); + } + m_SpatialAudioListener = nullptr; + } + } + else + { + RemoveDefaultListener(EditorListener); + } +} + +void FAkAudioDevice::EndPIE(const bool bIsSimulating) +{ + //Reset Unreal Global gameobject to avoid complications from removing Spatial Audio listener + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (LIKELY(SoundEngine)) + { + AkGameObjectID tempID = DUMMY_GAMEOBJ; + SoundEngine->SetListeners(DUMMY_GAMEOBJ, &tempID, 1); + } + + if (!bIsSimulating) + { + AddDefaultListener(EditorListener); + + // The Editor Listener should NEVER be the spatial audio listener + if (m_SpatialAudioListener == EditorListener) + { + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (LIKELY(SpatialAudio)) + { + SpatialAudio->UnregisterListener(m_SpatialAudioListener->GetAkGameObjectID()); + } + m_SpatialAudioListener = nullptr; + } + } + StopAllSounds(); +} +#endif +void FAkAudioDevice::UpdateRoomsForPortals() +{ +#ifdef AK_ENABLE_ROOMS + if (WorldsInNeedOfPortalRoomsUpdate.Num() == 0) + { + return; + } + + for (auto& World : WorldsInNeedOfPortalRoomsUpdate) + { + auto Portals = WorldPortalsMap.Find(World); + if (Portals != nullptr) + { + for (auto Portal : *Portals) + { + const bool RoomsChanged = Portal->UpdateConnectedRooms(); + if (RoomsChanged) + SetSpatialAudioPortal(Portal); + } + } + } + + WorldsInNeedOfPortalRoomsUpdate.Empty(); +#endif +} + +void FAkAudioDevice::CleanupComponentMapsForWorld(UWorld* World) +{ + LateReverbIndex.Clear(World); + RoomIndex.Clear(World); + WorldPortalsMap.Remove(World); +} + +/** + * Update the audio device and calculates the cached inverse transform later + * on used for spatialization. + */ +bool FAkAudioDevice::Update( float DeltaTime ) +{ + SCOPED_AKAUDIO_EVENT_2(TEXT("FAkAudioDevice::Update")); + if (m_bSoundEngineInitialized) + { + UpdateRoomsForPortals(); + + // Suspend audio when not in VR focus + if (FApp::UseVRFocus()) + { + if (FApp::HasVRFocus()) + { + WakeupFromSuspend(); + } + else + { + Suspend(true); + } + } + + UpdateSetCurrentAudioCultureAsyncTasks(); + + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return false; + + SoundEngine->RenderAudio(); + } + + return true; +} + +/** + * Tears down audio device by stopping all sounds, removing all buffers, + * destroying all sources, ... Called by both Destroy and ShutdownAfterError + * to perform the actual tear down. + */ +void FAkAudioDevice::Teardown() +{ + SCOPED_AKAUDIO_EVENT_2(TEXT("FAkAudioDevice::Teardown")); +#if AK_SUPPORT_WAAPI + if (auto waapiClient = FAkWaapiClient::Get()) + { + FAkAudioDevice_WaapiHelper::Unsubscribe(waapiClient, WaapiSubscriptionIds.Renamed); + FAkAudioDevice_WaapiHelper::Unsubscribe(waapiClient, WaapiSubscriptionIds.PreDeleted); + FAkAudioDevice_WaapiHelper::Unsubscribe(waapiClient, WaapiSubscriptionIds.ChildRemoved); + FAkAudioDevice_WaapiHelper::Unsubscribe(waapiClient, WaapiSubscriptionIds.ChildAdded); + FAkAudioDevice_WaapiHelper::Unsubscribe(waapiClient, WaapiSubscriptionIds.Created); + + if (ProjectLoadedHandle.IsValid()) + { + waapiClient->OnProjectLoaded.Remove(ProjectLoadedHandle); + ProjectLoadedHandle.Reset(); + } + + if (ConnectionLostHandle.IsValid()) + { + waapiClient->OnConnectionLost.Remove(ConnectionLostHandle); + ConnectionLostHandle.Reset(); + } + + if (ClientBeginDestroyHandle.IsValid()) + { + waapiClient->OnClientBeginDestroy.Remove(ClientBeginDestroyHandle); + ClientBeginDestroyHandle.Reset(); + } + + OnWwiseProjectModification.Clear(); + } +#endif // AK_SUPPORT_WAAPI + + if (m_bSoundEngineInitialized) + { + m_EngineExiting = true; + + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (LIKELY(SoundEngine)) + { + SoundEngine->UnregisterGameObj(DUMMY_GAMEOBJ); + + if (SoundEngine->IsInitialized()) + { + FAkAudioDevice_Helpers::UnregisterAllGlobalCallbacks(); + + SoundEngine->StopAll(); + SoundEngine->RenderAudio(); + } + } + + FAkSoundEngineInitialization::Finalize(IOHook); + + if (LIKELY(CallbackManager)) + { + delete CallbackManager; + CallbackManager = nullptr; + } + + if (LIKELY(CallbackInfoPool)) + { + delete CallbackInfoPool; + CallbackInfoPool = nullptr; + } + + if (LIKELY(IOHook)) + { + delete IOHook; + IOHook = nullptr; + } + + auto* Monitor = IWwiseMonitorAPI::Get(); + if (LIKELY(Monitor)) + { + Monitor->TerminateDefaultWAAPIErrorTranslator(); + Monitor->ResetTranslator(); +#if WITH_EDITORONLY_DATA && !defined(AK_OPTIMIZED) + delete m_UnrealErrorTranslator; + m_UnrealErrorTranslator = nullptr; +#endif + } + + m_bSoundEngineInitialized = false; + } + + FWorldDelegates::LevelRemovedFromWorld.RemoveAll(this); + +#if !WITH_EDITOR + CachedUnrealToWwiseCulture.Empty(); +#endif + + UE_LOG(LogAkAudio, Log, TEXT("Audiokinetic Audio Device terminated.")); +} + +/** + * Stops all game sounds (and possibly UI) sounds + * + * @param bShouldStopUISounds If true, this function will stop UI sounds as well + */ +void FAkAudioDevice::StopAllSounds(bool bShouldStopUISounds) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + FScopeLock Lock(&EventToPlayingIDMapCriticalSection); + for (const auto& Event: EventToPlayingIDMap) + { + for (const auto& PlayingID: Event.Value) + { + if (EAkAudioContext* Context = PlayingIDToAudioContextMap.Find(PlayingID)) + { + if (*Context == EAkAudioContext::GameplayAudio) + { + SoundEngine->StopPlayingID(PlayingID); + } + if (bShouldStopUISounds && *Context == EAkAudioContext::EditorAudio) + { + SoundEngine->StopPlayingID(PlayingID); + } + } + } + } +} + +void FAkAudioDevice::StopAllSounds(EAkAudioContext AudioContext) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + FScopeLock Lock(&EventToPlayingIDMapCriticalSection); + for (const auto& Event: EventToPlayingIDMap) + { + for (const auto& PlayingID: Event.Value) + { + if (EAkAudioContext* Context = PlayingIDToAudioContextMap.Find(PlayingID)) + { + if (*Context == AudioContext) + { + SoundEngine->StopPlayingID(PlayingID); + } + } + } + } +} + +/** + * Stop all audio associated with a scene + * + * @param SceneToFlush Interface of the scene to flush + */ +void FAkAudioDevice::Flush(UWorld* WorldToFlush) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + StopAllSounds(); +} + +/** +* Determine if any rooms or reverb volumes have been added to World during the current frame +*/ +bool FAkAudioDevice::WorldSpatialAudioVolumesUpdated(UWorld* World) +{ + if (World == nullptr) + return false; + + if (WorldVolumesUpdatedMap.Contains(World)) + return WorldVolumesUpdatedMap[World]; + + return false; +} + +/** + * Clears all loaded SoundBanks and associated media + * + * @return Result from ak sound engine + */ +AKRESULT FAkAudioDevice::ClearSoundBanksAndMedia() +{ + if (m_bSoundEngineInitialized) + { + StopAllSounds(); + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (LIKELY(SoundEngine)) + { + SoundEngine->RenderAudio(); + FPlatformProcess::Sleep(0.1f); + } + + for (TObjectIterator AudioAssetIt; AudioAssetIt; ++AudioAssetIt) + { + AudioAssetIt->UnloadData(); + } + return AK_Success; + } + + return AK_NotInitialized; +} + +AKRESULT FAkAudioDevice::ClearBanks() +{ + return ClearSoundBanksAndMedia(); +} + + +AKRESULT FAkAudioDevice::LoadBank( + const FString& in_BankName, + AkBankID& out_bankID +) +{ + AKRESULT eResult = AK_Fail; + if (EnsureInitialized()) // ensure audiolib is initialized + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + eResult = SoundEngine->LoadBank(TCHAR_TO_AK(*in_BankName), out_bankID); + } + + if (eResult != AK_Success) + { + UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioDevice::LoadBank: Failed to load bank %s. %s"), *in_BankName, AkUnrealHelper::GetResultString(eResult)); + } + + return eResult; +} + +AKRESULT FAkAudioDevice::LoadBank( + const FString& in_BankName, + AkBankCallbackFunc in_pfnBankCallback, + void * in_pCookie, + AkBankID & out_bankID +) +{ + if (EnsureInitialized()) // ensure audiolib is initialized + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + return SoundEngine->LoadBank(TCHAR_TO_AK(*in_BankName), in_pfnBankCallback, in_pCookie, out_bankID); + } + + return AK_Fail; +} + +AKRESULT FAkAudioDevice::LoadBank( + const FString& in_BankName, + FWaitEndBankAction* LoadBankLatentAction +) +{ + if (EnsureInitialized()) // ensure audiolib is initialized + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + AkBankID BankId; + return SoundEngine->LoadBank(TCHAR_TO_AK(*in_BankName), nullptr, LoadBankLatentAction, BankId); + } + + return AK_Fail; +} + +AKRESULT FAkAudioDevice::LoadBankAsync( + const FString& in_BankName, + const FOnAkBankCallback& BankLoadedCallback, + AkBankID & out_bankID +) +{ + if (EnsureInitialized()) // ensure audiolib is initialized + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + return SoundEngine->LoadBank(TCHAR_TO_AK(*in_BankName), out_bankID); + } + + return AK_Fail; +} + +AKRESULT FAkAudioDevice::LoadBankFromMemory( + const void* MemoryPtr, + uint32 MemorySize, + AkBankType BankType, + AkBankID& OutBankID +) +{ + if (EnsureInitialized() && MemoryPtr && MemorySize > 0) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + return SoundEngine->LoadBankMemoryView(MemoryPtr, MemorySize, OutBankID, BankType); + } + + return AK_Fail; +} + +AKRESULT FAkAudioDevice::UnloadBank( + const FString& in_BankName + ) +{ + AKRESULT eResult = AK_Fail; + if ( m_bSoundEngineInitialized ) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + eResult = SoundEngine->UnloadBank(TCHAR_TO_AK(*in_BankName), nullptr ); + } + if (eResult != AK_Success) + { + UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioDevice::UnloadBank: Failed to unload bank %s. %s"), *in_BankName, AkUnrealHelper::GetResultString(eResult)); + } + return eResult; +} + +AKRESULT FAkAudioDevice::UnloadBank( + const FString& in_BankName, + AkBankCallbackFunc in_pfnBankCallback, + void * in_pCookie +) +{ + if (m_bSoundEngineInitialized) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + return SoundEngine->UnloadBank(TCHAR_TO_AK(*in_BankName), NULL, in_pfnBankCallback, in_pCookie); + } + return AK_Fail; +} + +AKRESULT FAkAudioDevice::UnloadBank( + const FString& in_BankName, + FWaitEndBankAction* UnloadBankLatentAction +) +{ + if (m_bSoundEngineInitialized) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + return SoundEngine->UnloadBank(TCHAR_TO_AK(*in_BankName), nullptr); + } + return AK_Fail; +} + +AKRESULT FAkAudioDevice::UnloadBankFromMemory( + AkBankID in_bankID, + const void* in_memoryPtr +) +{ + if (EnsureInitialized() && in_memoryPtr) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + return SoundEngine->UnloadBank(in_bankID, in_memoryPtr); + } + + return AK_Fail; +} + +AKRESULT FAkAudioDevice::UnloadBankFromMemoryAsync( + AkBankID in_bankID, + const void* in_memoryPtr, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + uint32 BankType +) +{ + if (EnsureInitialized() && in_memoryPtr) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + return SoundEngine->UnloadBank(in_bankID, in_memoryPtr, in_pfnBankCallback, in_pCookie, BankType); + } + + return AK_Fail; +} + +AKRESULT FAkAudioDevice::UnloadBankAsync( + const FString& in_BankName, + const FOnAkBankCallback& BankUnloadedCallback +) +{ + if (m_bSoundEngineInitialized) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + return SoundEngine->UnloadBank(TCHAR_TO_AK(*in_BankName), nullptr); + } + return AK_Fail; +} + +AkUInt32 FAkAudioDevice::GetShortIDFromString(const FString& InString) +{ + if (InString.IsEmpty()) + { + return AK_INVALID_UNIQUE_ID; + } + + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_INVALID_UNIQUE_ID; + + return SoundEngine->GetIDFromString(TCHAR_TO_ANSI(*InString)); +} + +AkUInt32 FAkAudioDevice::GetShortID(UAkAudioType* AudioAsset, const FString& BackupName) +{ + AkUInt32 ShortId; + if (AudioAsset) + { + ShortId = AudioAsset->GetShortID(); + } + else + { + ShortId = GetShortIDFromString(BackupName); + } + + if (ShortId == AK_INVALID_UNIQUE_ID) + { + UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioDevice::GetShortID : Returning invalid ShortId for Wwise Object named %s."), AudioAsset? *AudioAsset->GetName() : *BackupName); + } + + return ShortId; +} + +AKRESULT FAkAudioDevice::SetMedia(AkSourceSettings* in_pSourceSettings, uint32 in_uNumSourceSettings) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + return SoundEngine->SetMedia(in_pSourceSettings, in_uNumSourceSettings); +} + +AKRESULT FAkAudioDevice::TryUnsetMedia(AkSourceSettings* in_pSourceSettings, uint32 in_uNumSourceSettings, AKRESULT* out_pUnsetResults) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + return SoundEngine->TryUnsetMedia(in_pSourceSettings, in_uNumSourceSettings, out_pUnsetResults); +} + +AKRESULT FAkAudioDevice::UnsetMedia(AkSourceSettings* in_pSourceSettings, uint32 in_uNumSourceSettings) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + return SoundEngine->UnsetMedia(in_pSourceSettings, in_uNumSourceSettings); +} + +FString FAkAudioDevice::GetCurrentAudioCulture() const +{ + auto* StreamMgr = IWwiseStreamMgrAPI::Get(); + if (UNLIKELY(!StreamMgr)) + { + return {}; + } + return FString(StreamMgr->GetCurrentLanguage()); +} + +FString FAkAudioDevice::GetDefaultLanguage() +{ + auto* WwiseInitBankLoader = FWwiseInitBankLoader::Get(); + if (LIKELY(WwiseInitBankLoader)) + { + auto* InitBankAsset = WwiseInitBankLoader->GetInitBankAsset(); + if (LIKELY(InitBankAsset)) + { + TArray Languages = InitBankAsset->GetLanguages(); + for (auto& Language : Languages) + { + if (Language.LanguageRequirement == EWwiseLanguageRequirement::IsDefault) + { + return Language.LanguageName.ToString(); + } + } + } + else + { + UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioDevice::GetDefaultLanguage: Could not get AkInitBank asset, returning empty language.")); + } + } + else + { + UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioDevice::GetDefaultLanguage: Could not get WwiseInitBankLoader, returning empty language.")); + } + + UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioDevice::GetDefaultLanguage: Could not find default language in available languages list.")); + return {}; +} + +TArray FAkAudioDevice::GetAvailableAudioCultures() const +{ + auto* WwiseInitBankLoader = FWwiseInitBankLoader::Get(); + if (UNLIKELY(!WwiseInitBankLoader)) + { + UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioDevice::GetAvailableAudioCultures: Could not get WwiseInitBankLoader, returning empty list.")); + return {}; + } + + auto* InitBankAsset = WwiseInitBankLoader->GetInitBankAsset(); + if (UNLIKELY(!InitBankAsset)) + { + UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioDevice::GetAvailableAudioCultures: Could not get AkInitBank asset, returning empty list.")); + return {}; + } + + TSet LanguageNames; + for(const auto& Language : InitBankAsset->InitBankCookedData.Language) + { + LanguageNames.Add(Language.LanguageName.ToString()); + } + return LanguageNames.Array(); +} + +FWwiseLanguageCookedData FAkAudioDevice::GetLanguageCookedDataFromString(const FString& WwiseLanguage) +{ + auto* WwiseInitBankLoader = FWwiseInitBankLoader::Get(); + if (LIKELY(WwiseInitBankLoader)) + { + auto* InitBankAsset = WwiseInitBankLoader->GetInitBankAsset(); + if (LIKELY(InitBankAsset)) + { + for (auto& Language : InitBankAsset->InitBankCookedData.Language) + { + if (Language.LanguageName.ToString() == WwiseLanguage) + { + return FWwiseLanguageCookedData(Language.LanguageId, Language.LanguageName, Language.LanguageRequirement); + } + } + } + } + + return FWwiseLanguageCookedData(GetShortIDFromString(WwiseLanguage), FName(WwiseLanguage), EWwiseLanguageRequirement::IsOptional); +} + +void FAkAudioDevice::SetCurrentAudioCulture(const FString& NewAudioCulture) +{ + FString NewWwiseLanguage; + if (FindWwiseLanguage(NewAudioCulture, NewWwiseLanguage)) + { + auto* ResourceLoader = FWwiseResourceLoader::Get(); + if (UNLIKELY(!ResourceLoader)) + { + return; + } + ResourceLoader->SetLanguage(GetLanguageCookedDataFromString(NewWwiseLanguage), EWwiseReloadLanguage::Immediate); + + auto* StreamMgr = IWwiseStreamMgrAPI::Get(); + if (UNLIKELY(!StreamMgr)) + { + return; + } + StreamMgr->SetCurrentLanguage(TCHAR_TO_AK(*NewWwiseLanguage)); + } +} + +void FAkAudioDevice::SetCurrentAudioCultureAsync(const FString& NewAudioCulture, FSetCurrentAudioCultureAction* LatentAction) +{ + FString NewWwiseLanguage; + + if (FindWwiseLanguage(NewAudioCulture, NewWwiseLanguage)) + { + SetCurrentAudioCultureAsyncTask* newTask = new SetCurrentAudioCultureAsyncTask(GetLanguageCookedDataFromString(NewWwiseLanguage), LatentAction); + if (newTask->Start()) + { + AudioCultureAsyncTasks.Add(newTask); + } + else + { + LatentAction->ActionDone = true; + delete newTask; + } + } + else + { + LatentAction->ActionDone = true; + } +} + +void FAkAudioDevice::SetCurrentAudioCultureAsync(const FString& NewAudioCulture, const FOnSetCurrentAudioCultureCompleted& CompletedCallback) +{ + FString NewWwiseLanguage; + + if (FindWwiseLanguage(NewAudioCulture, NewWwiseLanguage)) + { + SetCurrentAudioCultureAsyncTask* newTask = new SetCurrentAudioCultureAsyncTask(GetLanguageCookedDataFromString(NewWwiseLanguage), CompletedCallback); + if (newTask->Start()) + { + AudioCultureAsyncTasks.Add(newTask); + } + else + { + CompletedCallback.ExecuteIfBound(false); + delete newTask; + } + } + else + { + CompletedCallback.ExecuteIfBound(true); + } +} + +bool FAkAudioDevice::FindWwiseLanguage(const FString& NewAudioCulture, FString& FoundWwiseLanguage) +{ + auto oldAudioCulture = GetCurrentAudioCulture(); + + FoundWwiseLanguage = NewAudioCulture; + + auto newCulturePtr = FInternationalization::Get().GetCulture(FoundWwiseLanguage); + if (newCulturePtr && newCulturePtr->GetLCID() != InvariantLCID) + { + auto& newLanguage = newCulturePtr->GetTwoLetterISOLanguageName(); + auto& newRegion = newCulturePtr->GetRegion(); + auto& newScript = newCulturePtr->GetScript(); + + int maxMatch = 0; + + auto findMostCompatibleCulture = [&maxMatch, &newLanguage, &newRegion, &newScript, &FoundWwiseLanguage](const FCulturePtr& currentCulture, const FString& currentValue) { + auto& currentLanguageName = currentCulture->GetTwoLetterISOLanguageName(); + auto& currentRegionName = currentCulture->GetRegion(); + auto& currentScript = currentCulture->GetScript(); + + int currentMatch = 0; + + if (currentLanguageName == newLanguage) + { + currentMatch = 1; + + // This is inspired by how UE processes culture from most to least specific. For example: + // zh - Hans - CN is processed as "zh-Hans-CN", then "zh-CN", then "zh-Hans", then "zh". + // This is how I selected the weight for each match. + + if (!currentScript.IsEmpty() && !newScript.IsEmpty() + && !currentRegionName.IsEmpty() && !newRegion.IsEmpty()) + { + if (currentScript == newScript && currentRegionName == newRegion) + { + currentMatch += 4; + } + else + { + --currentMatch; + } + } + + if (!currentRegionName.IsEmpty() && !newRegion.IsEmpty()) + { + if (currentRegionName == newRegion) + { + currentMatch += 3; + } + else + { + --currentMatch; + } + } + + if (!currentScript.IsEmpty() && !newScript.IsEmpty()) + { + if (currentScript == newScript) + { + currentMatch += 2; + } + else + { + --currentMatch; + } + } + } + + if (currentMatch > 0) + { + // When the current culture is missing script or region but the new culture + // has it, give a weight boost of 1 + + if (!newScript.IsEmpty() && currentScript.IsEmpty()) + { + ++currentMatch; + } + + if (!newRegion.IsEmpty() && currentRegionName.IsEmpty()) + { + ++currentMatch; + } + } + + if (maxMatch < currentMatch) + { + FoundWwiseLanguage = currentValue; + maxMatch = currentMatch; + } + }; + +#if WITH_EDITOR + if (auto* akSettings = GetDefault()) + { + for (auto& entry : akSettings->UnrealCultureToWwiseCulture) + { + auto currentCulture = FInternationalization::Get().GetCulture(entry.Key); + if (currentCulture && currentCulture->GetLCID() != InvariantLCID) + { + findMostCompatibleCulture(currentCulture, entry.Value); + } + } + } +#else + for (auto& entry : CachedUnrealToWwiseCulture) + { + findMostCompatibleCulture(entry.Key, entry.Value); + } +#endif + } + + return oldAudioCulture != FoundWwiseLanguage; +} + +void FAkAudioDevice::UpdateSetCurrentAudioCultureAsyncTasks() +{ + if(AudioCultureAsyncTasks.Num() == 0) + { + return; + } + + for (auto task : AudioCultureAsyncTasks) + { + task->Update(); + } + + for (int32 i = AudioCultureAsyncTasks.Num() - 1; i >= 0; --i) + { + if (AudioCultureAsyncTasks[i]->IsDone) + { + delete AudioCultureAsyncTasks[i]; + AudioCultureAsyncTasks[i] = nullptr; + } + } + + AudioCultureAsyncTasks.RemoveAll([](SetCurrentAudioCultureAsyncTask* Task) { return Task == nullptr; }); +} + +template +AkPlayingID FAkAudioDevice::PostEventWithCallbackPackageOnGameObjectId( + const AkUInt32 EventShortID, + const AkGameObjectID GameObjectID, + const TArray& ExternalSources, + FCreateCallbackPackage CreateCallbackPackage, + EAkAudioContext AudioContext +) +{ + UE_LOG(LogWwiseHints, Warning, TEXT("[Deprecated 22.1] Posting Event(%" PRIu32 ") GameObject(%" PRIu64 "). Should use Event posting through an AkAudioEvent class instead."), EventShortID, GameObjectID); + AkPlayingID PlayingID = AK_INVALID_PLAYING_ID; + if (m_bSoundEngineInitialized && CallbackManager) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_INVALID_PLAYING_ID; + + auto pPackage = CreateCallbackPackage(GameObjectID); + if (pPackage) + { + PlayingID = SoundEngine->PostEvent( + EventShortID + , GameObjectID + , pPackage->uUserFlags | AK_EndOfEvent + , &FAkComponentCallbackManager::AkComponentCallback + , pPackage + , ExternalSources.Num() + , const_cast(ExternalSources.GetData()) + ); + if (PlayingID == AK_INVALID_PLAYING_ID) + { + CallbackManager->RemoveCallbackPackage(pPackage, GameObjectID); + } + else + { + FScopeLock Lock(&EventToPlayingIDMapCriticalSection); + auto& PlayingIDArray = EventToPlayingIDMap.FindOrAdd(EventShortID); + PlayingIDArray.Add(PlayingID); + PlayingIDToAudioContextMap.Add(PlayingID, AudioContext); + } + } + } + + return PlayingID; +} + +template +AkPlayingID FAkAudioDevice::PostEventWithCallbackPackageOnAkGameObject( + const AkUInt32 EventShortID, + UAkGameObject* GameObject, + const TArray& ExternalSources, + FCreateCallbackPackage CreateCallbackPackage, + EAkAudioContext AudioContext +) +{ + AkPlayingID PlayingID = AK_INVALID_PLAYING_ID; + + if (m_bSoundEngineInitialized && GameObject && CallbackManager) + { + if (EventShortID != AK_INVALID_UNIQUE_ID && GameObject->AllowAudioPlayback()) + { + GameObject->UpdateObstructionAndOcclusion(); + + auto gameObjID = GameObject->GetAkGameObjectID(); + + PlayingID = PostEventWithCallbackPackageOnGameObjectId(EventShortID, gameObjID, ExternalSources, CreateCallbackPackage, AudioContext); + if (PlayingID != AK_INVALID_PLAYING_ID) + { + GameObject->EventPosted(); + } + } + } + + return PlayingID; +} + +AkPlayingID FAkAudioDevice::PostAkAudioEventOnActor( + UAkAudioEvent* AkEvent, + AActor* Actor, + AkUInt32 Flags /*= 0*/, + AkCallbackFunc Callback /*= NULL*/, + void* Cookie /*= NULL*/, + bool bStopWhenOwnerDestroyed, /*= false*/ + EAkAudioContext AudioContext /* = GameplayAudio*/ +) +{ + if (UNLIKELY(!AkEvent)) + { + UE_LOG(LogAkAudio, Verbose, TEXT("Failed to post AkAudioEvent without an AkAudioEvent.")) + return AK_INVALID_PLAYING_ID; + } + + if (UNLIKELY(!IsValid(AkEvent))) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to post with invalid AkAudioEvent.")) + return AK_INVALID_PLAYING_ID; + } + + return AkEvent->PostOnActor(Actor, nullptr, Callback, Cookie, (AkCallbackType)Flags, nullptr, bStopWhenOwnerDestroyed, AudioContext); +} + +AkPlayingID FAkAudioDevice::PostAkAudioEventOnComponent( + UAkAudioEvent* AkEvent, + UAkComponent* Component, + AkUInt32 Flags, + AkCallbackFunc Callback, + void* Cookie, + bool bStopWhenOwnerDestroyed, + EAkAudioContext AudioContext) +{ + if (UNLIKELY(!AkEvent)) + { + UE_LOG(LogAkAudio, Verbose, TEXT("Failed to post AkAudioEvent without an AkAudioEvent.")) + return AK_INVALID_PLAYING_ID; + } + + if (UNLIKELY(!IsValid(AkEvent))) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to post with invalid AkAudioEvent.")) + return AK_INVALID_PLAYING_ID; + } + + return AkEvent->PostOnComponent(Component, nullptr, Callback, Cookie, (AkCallbackType)Flags, nullptr, bStopWhenOwnerDestroyed, AudioContext); +} + +AkPlayingID FAkAudioDevice::PostEventOnActor( + const AkUInt32 EventShortID, + AActor * Actor, + AkUInt32 Flags /*= 0*/, + AkCallbackFunc Callback /*= NULL*/, + void * Cookie /*= NULL*/, + bool bStopWhenOwnerDestroyed /*= false*/, + const TArray ExternalSources, /* = TArray()*/ + EAkAudioContext AudioContext +) +{ + if (m_bSoundEngineInitialized) + { + if (!Actor) + { + return PostEventWithCallbackPackageOnGameObjectId(EventShortID, DUMMY_GAMEOBJ, ExternalSources, [Callback, Cookie, Flags, this, HasExtSrc=ExternalSources.Num()>0](AkGameObjectID gameObjID) { + return CallbackManager->CreateCallbackPackage(Callback, Cookie, Flags, gameObjID, HasExtSrc); + }, AudioContext); + } + else if (!Actor->IsActorBeingDestroyed() && IsValid(Actor)) + { + UAkComponent* pComponent = GetAkComponent(Actor->GetRootComponent(), FName(), NULL, EAttachLocation::KeepRelativeOffset); + if (pComponent) + { + pComponent->StopWhenOwnerDestroyed = bStopWhenOwnerDestroyed; + return PostEventOnAkComponent(EventShortID, pComponent, Flags, Callback, Cookie, ExternalSources, AudioContext); + } + } + } + + return AK_INVALID_PLAYING_ID; +} + +AkPlayingID FAkAudioDevice::PostEventOnActor( + const AkUInt32 EventShortID, + AActor* Actor, + const FOnAkPostEventCallback& PostEventCallback, + AkUInt32 Flags /*= 0*/, + bool bStopWhenOwnerDestroyed, + /*= false*/ + const TArray& ExternalSources, /* = TArray()*/ + EAkAudioContext AudioContext +) +{ + if (m_bSoundEngineInitialized) + { + if (!Actor) + { + UE_LOG(LogAkAudio, Error, TEXT("PostEvent accepting a FOnAkPostEventCallback delegate requires a valid actor")); + } + else if (!Actor->IsActorBeingDestroyed() && IsValid(Actor)) + { + UAkComponent* pComponent = GetAkComponent(Actor->GetRootComponent(), FName(), NULL, EAttachLocation::KeepRelativeOffset); + if (pComponent) + { + pComponent->StopWhenOwnerDestroyed = bStopWhenOwnerDestroyed; + return PostEventOnAkGameObject(EventShortID, pComponent, PostEventCallback, Flags, ExternalSources, AudioContext); + } + } + } + + return AK_INVALID_PLAYING_ID; +} + +AkPlayingID FAkAudioDevice::PostEventOnActorWithLatentAction( + const AkUInt32 EventShortID, + AActor* Actor, + bool bStopWhenOwnerDestroyed, + FWaitEndOfEventAction* LatentAction, + const TArray& ExternalSources, /* = TArray()*/ + EAkAudioContext AudioContext +) +{ + if (m_bSoundEngineInitialized) + { + if (!Actor) + { + UE_LOG(LogAkAudio, Error, TEXT("PostEvent accepting a FWaitEndOfEventAction requires a valid actor")); + } + else if (!Actor->IsActorBeingDestroyed() && IsValid(Actor)) + { + UAkComponent* pComponent = GetAkComponent(Actor->GetRootComponent(), FName(), NULL, EAttachLocation::KeepRelativeOffset); + if (pComponent) + { + pComponent->StopWhenOwnerDestroyed = bStopWhenOwnerDestroyed; + return PostEventOnComponentWithLatentAction(EventShortID, pComponent, LatentAction, ExternalSources, AudioContext); + } + } + } + + return AK_INVALID_PLAYING_ID; +} + +/** + * Post an event to ak soundengine by name + * + * @param EventShortID Name of the event to post + * @param in_pGameObject UAkGameObject on which to play the event + * @param in_uFlags Bitmask: see \ref AkCallbackType + * @param in_pfnCallback Callback function + * @param in_pCookie Callback cookie that will be sent to the callback function along with additional information. + * @return ID assigned by ak soundengine + */ +AkPlayingID FAkAudioDevice::PostEventOnAkComponent( + const AkUInt32 EventShortID, + UAkComponent* Component, + AkUInt32 Flags /*= 0*/, + AkCallbackFunc Callback /*= NULL*/, + void * Cookie, + /*= NULL*/ + const TArray& ExternalSources, /*= TArray()*/ + EAkAudioContext AudioContext +) +{ + return PostEventWithCallbackPackageOnAkGameObject(EventShortID, Component, ExternalSources, [Callback, Cookie, Flags, this, HasExtSrc = ExternalSources.Num() > 0](AkGameObjectID gameObjID) { + return CallbackManager->CreateCallbackPackage(Callback, Cookie, Flags, gameObjID, HasExtSrc); + }, AudioContext); +} + +AkPlayingID FAkAudioDevice::PostEventOnGameObjectID( + const AkUInt32 EventShortID, + AkGameObjectID GameObject, + AkUInt32 Flags /*= 0*/, + AkCallbackFunc Callback /*= NULL*/, + void* Cookie, + /*= NULL*/ + const TArray& ExternalSources, /*= TArray()*/ + EAkAudioContext AudioContext +) +{ + return PostEventWithCallbackPackageOnGameObjectId(EventShortID, GameObject, ExternalSources, [Callback, Cookie, Flags, this, HasExtSrc = ExternalSources.Num() > 0](AkGameObjectID gameObjID) { + return CallbackManager->CreateCallbackPackage(Callback, Cookie, Flags, gameObjID, HasExtSrc); + }, AudioContext); +} + +AkPlayingID FAkAudioDevice::PostEventOnAkGameObject( + const AkUInt32 EventShortID, + UAkGameObject* AkGameObject, + const FOnAkPostEventCallback& PostEventCallback, + AkUInt32 Flags, + /*= 0*/ + const TArray& ExternalSources, /*= TArray()*/ + EAkAudioContext AudioContext +) +{ + return PostEventWithCallbackPackageOnAkGameObject(EventShortID, AkGameObject, ExternalSources, [PostEventCallback, Flags, this, HasExtSrc = ExternalSources.Num() > 0](AkGameObjectID gameObjID) { + return CallbackManager->CreateCallbackPackage(PostEventCallback, Flags, gameObjID, HasExtSrc); + }, AudioContext); +} + +AkPlayingID FAkAudioDevice::PostEventOnComponentWithLatentAction( + const AkUInt32 EventShortID, + UAkComponent* AkComponent, + FWaitEndOfEventAction* LatentAction, + const TArray& ExternalSources, /*= TArray()*/ + EAkAudioContext AudioContext +) +{ + return PostEventWithCallbackPackageOnAkGameObject(EventShortID, AkComponent, ExternalSources, [LatentAction, this, HasExtSrc = ExternalSources.Num() > 0](AkGameObjectID gameObjID) { + return CallbackManager->CreateCallbackPackage(LatentAction, gameObjID, HasExtSrc); + }, AudioContext); +} + +void FAkAudioDevice::SAComponentAddedRemoved(UWorld* World) +{ + if (World != nullptr) + { + if (WorldVolumesUpdatedMap.Contains(World)) + WorldVolumesUpdatedMap[World] = true; + else + WorldVolumesUpdatedMap.Add(World, true); + } +} + + +TFuture FAkAudioDevice::PostAkAudioEventOnActorAsync( + UAkAudioEvent* AkEvent, + AActor* Actor, + const FOnAkPostEventCallback& PostEventCallback, + AkUInt32 CallbackFlags, + bool bStopWhenOwnerDestroyed, + EAkAudioContext AudioContext +) +{ + UE_CLOG(AkEvent && Actor, LogWwiseHints, Log, TEXT("[Deprecated 22.1] PostAsync on Event(%s; %" PRIu32 ") Actor(%s). Async operations on Events are deprecated."), *AkEvent->GetName(), AkEvent->GetShortID(), *Actor->GetName()); + TPromise PlayingIDPromise; + auto PlayingIDFuture = PlayingIDPromise.GetFuture(); + + if (UNLIKELY(!AkEvent)) + { + UE_LOG(LogAkAudio, Verbose, TEXT("Failed to post AkAudioEvent without an AkAudioEvent.")) + PlayingIDPromise.SetValue(AK_INVALID_PLAYING_ID); + return PlayingIDFuture; + } + + if (!Actor) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to post AkAudioEvent '%s': accepting a FOnAkPostEventCallback delegate requires a valid actor"), *AkEvent->GetName()); + PlayingIDPromise.SetValue(AK_INVALID_PLAYING_ID); + return PlayingIDFuture; + } + + const auto PollMediaReadyTask = FFunctionGraphTask::CreateAndDispatchWhenReady([Actor, AkEvent]() + { + while (LIKELY(!Actor->IsActorBeingDestroyed() && IsValid(Actor)) + && LIKELY(IsValid(AkEvent)) + && !AkEvent->IsDataFullyLoaded()) + { + FPlatformProcess::Sleep(1.f / 60.f); + } + }, GET_STATID(STAT_PostEventAsync), nullptr, ENamedThreads::AnyThread); + + FFunctionGraphTask::CreateAndDispatchWhenReady([this, AkEvent, Actor, PostEventCallback, CallbackFlags, bStopWhenOwnerDestroyed, PlayingIDPromise = MoveTemp(PlayingIDPromise), AudioContext]() mutable + { + if (UNLIKELY(!IsValid(AkEvent))) + { + UE_LOG(LogAkAudio, Verbose, TEXT("Failed to post invalid AkAudioEvent.")) + PlayingIDPromise.SetValue(AK_INVALID_PLAYING_ID); + return; + } + + PlayingIDPromise.SetValue(AkEvent->PostOnActor(Actor, &PostEventCallback, nullptr, nullptr, (AkCallbackType)CallbackFlags, nullptr, bStopWhenOwnerDestroyed, AudioContext)); + }, GET_STATID(STAT_PostEventAsync), PollMediaReadyTask, ENamedThreads::GameThread); + + return PlayingIDFuture; +} + + +TFuture FAkAudioDevice::PostAkAudioEventOnAkGameObjectAsync( + UAkAudioEvent* AudioEvent, + UAkGameObject* GameObject, + const FOnAkPostEventCallback& PostEventCallback, + AkUInt32 CallbackFlags, + EAkAudioContext AudioContext) +{ + UE_CLOG(AudioEvent && GameObject, LogWwiseHints, Log, TEXT("[Deprecated 22.1] PostAsync on Event(%s; %" PRIu32 ") GameObject(%s). Async operations on Events are deprecated."), *AudioEvent->GetName(), AudioEvent->GetShortID(), *GameObject->GetName()); + auto PlayingIDFuture = Async(EAsyncExecution::TaskGraph, [AudioEvent] { + while (IsValid(AudioEvent) && !AudioEvent->IsDataFullyLoaded()) + { + FPlatformProcess::Sleep(1.f / 60.f); + } + }).Then([this, AudioEvent, GameObject, PostEventCallback, CallbackFlags, AudioContext](auto PreviousFuture) { + if (UNLIKELY(!IsValid(AudioEvent))) + { + UE_LOG(LogAkAudio, Verbose, TEXT("Failed to post invalid AkAudioEvent.")) + return AK_INVALID_PLAYING_ID; + } + + return AudioEvent->PostOnGameObject(GameObject, &PostEventCallback, nullptr, nullptr, (AkCallbackType)CallbackFlags, nullptr, AudioContext); + }); + + return PlayingIDFuture; +} + +TFuture FAkAudioDevice::PostAkAudioEventAtLocationAsync( + UAkAudioEvent* Event, + FVector Location, + FRotator Orientation, + class UWorld* World, + EAkAudioContext AudioContext +) +{ + UE_CLOG(Event, LogWwiseHints, Log, TEXT("[Deprecated 22.1] PostAsync on Event(%s; %" PRIu32 ") AtLocation. Async operations on Events are deprecated."), *Event->GetName(), Event->GetShortID()); + TPromise playingIDPromise; + auto playingIDFuture = playingIDPromise.GetFuture(); + + auto pollMediaReadyTask = FFunctionGraphTask::CreateAndDispatchWhenReady([Event]() + { + while (IsValid(Event) && !Event->IsDataFullyLoaded()) + { + FPlatformProcess::Sleep(1.f / 60.f); + } + }, GET_STATID(STAT_PostEventAsync), nullptr, ENamedThreads::AnyThread); + + FFunctionGraphTask::CreateAndDispatchWhenReady([this, Event, Location, Orientation, World, playingIDPromiseCopy(MoveTemp(playingIDPromise)), AudioContext]() mutable { + if (UNLIKELY(!IsValid(Event))) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to post invalid AkAudioEvent.")) + playingIDPromiseCopy.SetValue(AK_INVALID_PLAYING_ID); + return; + } + + playingIDPromiseCopy.SetValue(Event->PostAtLocation(Location, Orientation, World, nullptr, nullptr, nullptr, (AkCallbackType)0, nullptr, AudioContext)); + }, GET_STATID(STAT_PostEventAsync), pollMediaReadyTask, ENamedThreads::GameThread); + + return playingIDFuture; +} + +TFuture FAkAudioDevice::PostAkAudioEventWithLatentActionOnActorAsync( + UAkAudioEvent* AudioEvent, + AActor* Actor, + bool bStopWhenOwnerDestroyed, + FWaitEndOfEventAction* LatentAction, + EAkAudioContext AudioContext) +{ + UE_CLOG(AudioEvent && Actor, LogWwiseHints, Log, TEXT("[Deprecated 22.1] PostAsync on Event(%s; %" PRIu32 ") Actor(%s). Async operations on Events are deprecated."), *AudioEvent->GetName(), AudioEvent->GetShortID(), *Actor->GetName()); + TPromise PlayingIDPromise; + auto PlayingIDFuture = PlayingIDPromise.GetFuture(); + + auto PollMediaReadyTask = FFunctionGraphTask::CreateAndDispatchWhenReady([AudioEvent]() + { + while (IsValid(AudioEvent) && !AudioEvent->IsDataFullyLoaded()) + { + FPlatformProcess::Sleep(1.f / 60.f); + } + }, GET_STATID(STAT_PostEventAsync), nullptr, ENamedThreads::AnyThread); + + FFunctionGraphTask::CreateAndDispatchWhenReady([this, AudioEvent, Actor, bStopWhenOwnerDestroyed, LatentAction, PlayingIDPromiseCopy(MoveTemp(PlayingIDPromise)), AudioContext]() mutable { + if (UNLIKELY(!IsValid(AudioEvent))) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to post invalid AkAudioEvent.")) + PlayingIDPromiseCopy.SetValue(AK_INVALID_PLAYING_ID); + return; + } + + PlayingIDPromiseCopy.SetValue(AudioEvent->PostOnActor(Actor, nullptr, nullptr, nullptr, (AkCallbackType)0, LatentAction, bStopWhenOwnerDestroyed, AudioContext)); + }, GET_STATID(STAT_PostEventAsync), PollMediaReadyTask, ENamedThreads::GameThread); + + return PlayingIDFuture; +} + +TFuture FAkAudioDevice::PostAkAudioEventWithLatentActionOnAkComponentAsync( + UAkAudioEvent* AudioEvent, + UAkComponent* AkComponent, + bool bStopWhenOwnerDestroyed, + FWaitEndOfEventAction* LatentAction, + EAkAudioContext AudioContext) +{ + UE_CLOG(AudioEvent && AkComponent, LogWwiseHints, Log, TEXT("[Deprecated 22.1] PostAsync on Event(%s; %" PRIu32 ") AkComponent(%s). Async operations on Events are deprecated."), *AudioEvent->GetName(), AudioEvent->GetShortID(), *AkComponent->GetName()); + auto PlayingIDFuture = Async(EAsyncExecution::TaskGraph, [AudioEvent] + { + while (AudioEvent && !AudioEvent->IsDataFullyLoaded()) + { + FPlatformProcess::Sleep(1.f / 60.f); + } + }).Then([this, AudioEvent, AkComponent, bStopWhenOwnerDestroyed, LatentAction, AudioContext](auto PreviousFuture) + { + if (UNLIKELY(!IsValid(AudioEvent))) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to post invalid AkAudioEvent.")) + return AK_INVALID_PLAYING_ID; + } + + return AudioEvent->PostOnComponent(AkComponent, nullptr, nullptr, nullptr, (AkCallbackType)0, LatentAction, bStopWhenOwnerDestroyed, AudioContext); + }); + + return PlayingIDFuture; +} + +/** Find UAkLateReverbComponents at a given location. */ +TArray FAkAudioDevice::FindLateReverbComponentsAtLocation(const FVector& Loc, const UWorld* World) +{ + return LateReverbIndex.Query(Loc, World); +} + +/** Add a UAkLateReverbComponent to the linked list. */ +void FAkAudioDevice::IndexLateReverb(class UAkLateReverbComponent* ComponentToAdd) +{ + check(!ComponentToAdd->IsIndexed); + LateReverbIndex.Add(ComponentToAdd); + SAComponentAddedRemoved(ComponentToAdd->GetWorld()); + ComponentToAdd->IsIndexed = true; +} + +/** Remove a UAkLateReverbComponent from the linked list. */ +void FAkAudioDevice::UnindexLateReverb(class UAkLateReverbComponent* ComponentToRemove) +{ + check(ComponentToRemove->IsIndexed); + if (LateReverbIndex.Remove(ComponentToRemove)) + { + SAComponentAddedRemoved(ComponentToRemove->GetWorld()); + } + ComponentToRemove->IsIndexed = false; +} + +void FAkAudioDevice::ReindexLateReverb(class UAkLateReverbComponent* ComponentToUpdate) +{ + check(ComponentToUpdate->IsIndexed); + LateReverbIndex.Update(ComponentToUpdate); + SAComponentAddedRemoved(ComponentToUpdate->GetWorld()); +} + +bool FAkAudioDevice::WorldHasActiveRooms(UWorld* World) +{ + return !RoomIndex.IsEmpty(World); +} + +/** Find UAkRoomComponents at a given location. */ +TArray FAkAudioDevice::FindRoomComponentsAtLocation(const FVector& Loc, const UWorld* World) +{ + return RoomIndex.Query(Loc, World); +} + +/** Add a UAkRoomComponent to the linked list. */ +void FAkAudioDevice::IndexRoom(class UAkRoomComponent* ComponentToAdd) +{ + RoomIndex.Add(ComponentToAdd); + SAComponentAddedRemoved(ComponentToAdd->GetWorld()); +} + +/** Remove a UAkRoomComponent from the linked list. */ +void FAkAudioDevice::UnindexRoom(class UAkRoomComponent* ComponentToRemove) +{ + if (RoomIndex.Remove(ComponentToRemove)) + { + SAComponentAddedRemoved(ComponentToRemove->GetWorld()); + } +} + +void FAkAudioDevice::ReindexRoom(class UAkRoomComponent* ComponentToAdd) +{ + RoomIndex.Update(ComponentToAdd); + SAComponentAddedRemoved(ComponentToAdd->GetWorld()); +} + +/** Return true if any UAkRoomComponents have been added to the prioritized list of rooms **/ +bool FAkAudioDevice::UsingSpatialAudioRooms(const UWorld* World) +{ + return !RoomIndex.IsEmpty(World); +} + +AKRESULT FAkAudioDevice::ExecuteActionOnEvent( + const AkUInt32 EventShortID, + AkActionOnEventType ActionType, + AActor* Actor, + AkTimeMs TransitionDuration, + EAkCurveInterpolation FadeCurve, + AkPlayingID PlayingID +) +{ + UE_CLOG(Actor, LogWwiseHints, Warning, TEXT("[Deprecated 22.1] Executing action on Event(%" PRIu32 ") PlayingID(%" PRIu32 ") for Actor(%s). Should use Event posting through an AkAudioEvent class instead."), EventShortID, PlayingID, *Actor->GetName()); + UE_CLOG(!Actor, LogWwiseHints, Warning, TEXT("[Deprecated 22.1] Executing action on Event(%" PRIu32 ") PlayingID(%" PRIu32 ") on Ambient sound. Should use Event posting through an AkAudioEvent class instead."), EventShortID, PlayingID); + + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + if (!Actor) + { + return SoundEngine->ExecuteActionOnEvent(EventShortID, + static_cast(ActionType), + DUMMY_GAMEOBJ, + TransitionDuration, + static_cast(FadeCurve), + PlayingID + ); + } + else if (!Actor->IsActorBeingDestroyed() && IsValid(Actor)) + { + UAkComponent* pComponent = GetAkComponent(Actor->GetRootComponent(), FName(), NULL, EAttachLocation::KeepRelativeOffset); + if (pComponent) + { + return SoundEngine->ExecuteActionOnEvent(EventShortID, + static_cast(ActionType), + pComponent->GetAkGameObjectID(), + TransitionDuration, + static_cast(FadeCurve), + PlayingID + ); + } + } + + return AKRESULT::AK_Fail; +} + +void FAkAudioDevice::ExecuteActionOnPlayingID( + AkActionOnEventType in_ActionType, + AkPlayingID in_PlayingID, + AkTimeMs in_uTransitionDuration, + EAkCurveInterpolation in_eFadeCuve +) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + SoundEngine->ExecuteActionOnPlayingID( + static_cast(in_ActionType), + in_PlayingID, + in_uTransitionDuration, + static_cast(in_eFadeCuve) + ); +} + +/** Seek on an event in the ak soundengine. +* @param EventShortID Name of the event on which to seek. +* @param Actor The associated Actor. If this is nullptr, defaul object will be used. +* @param in_fPercent Desired percent where playback should restart. +* @param in_bSeekToNearestMarker If true, the final seeking position will be made equal to the nearest marker. +* +* @return Success or failure. +*/ +AKRESULT FAkAudioDevice::SeekOnEvent( + const AkUInt32 EventShortID, + AActor* Actor, + AkReal32 Percent, + bool bSeekToNearestMarker /*= false*/, + AkPlayingID PlayingID /*= AK_INVALID_PLAYING_ID*/ +) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + if (!Actor) + { + // SeekOnEvent must be bound to a game object. Passing DUMMY_GAMEOBJ as default game object. + return SoundEngine->SeekOnEvent(EventShortID, DUMMY_GAMEOBJ, Percent, bSeekToNearestMarker, PlayingID); + } + else if (!Actor->IsActorBeingDestroyed() && IsValid(Actor)) + { + UAkComponent* pComponent = GetAkComponent(Actor->GetRootComponent(), FName(), NULL, EAttachLocation::KeepRelativeOffset); + if (pComponent) + { + return SeekOnEvent(EventShortID, pComponent, Percent, bSeekToNearestMarker, PlayingID); + } + } + + return AKRESULT::AK_Fail; +} + +/** Seek on an event in the ak soundengine. +* @param EventShortID Name of the event on which to seek. +* @param Component The associated AkComponent. +* @param in_fPercent Desired percent where playback should restart. +* @param in_bSeekToNearestMarker If true, the final seeking position will be made equal to the nearest marker. +* +* @return Success or failure. +*/ +AKRESULT FAkAudioDevice::SeekOnEvent( + const AkUInt32 EventShortID, + UAkComponent* Component, + AkReal32 Percent, + bool bSeekToNearestMarker /*= false*/, + AkPlayingID PlayingID /*= AK_INVALID_PLAYING_ID*/ +) +{ + if (m_bSoundEngineInitialized && Component) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + if (Component->AllowAudioPlayback()) + { + return SoundEngine->SeekOnEvent(EventShortID, Component->GetAkGameObjectID(), Percent, bSeekToNearestMarker, PlayingID); + } + } + return AKRESULT::AK_Fail; +} + +void FAkAudioDevice::UpdateAllSpatialAudioPortals(UWorld* InWorld) +{ +#ifdef AK_ENABLE_PORTALS + auto Portals = WorldPortalsMap.Find(InWorld); + if (Portals != nullptr) + { + for (auto Portal : *Portals) + { + SetSpatialAudioPortal(Portal); + } + } +#endif +} + +void FAkAudioDevice::SetSpatialAudioPortal(UAkPortalComponent* in_Portal) +{ + if(!IsValid(in_Portal) || IsRunningCommandlet()) + return; + + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return; + +#ifdef AK_ENABLE_PORTALS + UWorld* World = in_Portal->GetWorld(); + + auto Portals = WorldPortalsMap.Find(World); + if (Portals == nullptr) + Portals = &WorldPortalsMap.Add(World, TArray()); + if (Portals != nullptr) + { + if (!Portals->Contains(in_Portal)) + { + Portals->Add(in_Portal); + } + } + + /* Only update the sound engine for game worlds. */ + if (ShouldNotifySoundEngine(World->WorldType)) + { + AkPortalID portalID = in_Portal->GetPortalID(); + + if (!in_Portal->PortalPlacementValid()) + { + SpatialAudio->RemovePortal(portalID); + UE_LOG(LogAkAudio, Log, TEXT("UAkPortalComponent %s must have a front room which is distinct from its back room."), *(in_Portal->GetOwner() != nullptr ? in_Portal->GetOwner()->GetName() : in_Portal->GetName())); + } + else + { + FString nameStr = in_Portal->GetName(); + + AActor* portalOwner = in_Portal->GetOwner(); + UPrimitiveComponent* primitiveParent = in_Portal->GetPrimitiveParent(); + if (portalOwner != nullptr) + { +#if WITH_EDITOR + nameStr = portalOwner->GetActorLabel(); +#else + nameStr = portalOwner->GetName(); +#endif + if (primitiveParent != nullptr) + { + // ensures unique and meaningful names when we have multiple portals in the same actor. + TInlineComponentArray PortalComponents; + portalOwner->GetComponents(PortalComponents); + if (PortalComponents.Num() > 1) + nameStr.Append(FString("_").Append(primitiveParent->GetName())); + } + } + + AkPortalParams Params; + UPrimitiveComponent* Parent = in_Portal->GetPrimitiveParent(); + if (IsValid(Parent)) + { + AkComponentHelpers::GetPrimitiveTransformAndExtent(*Parent, Params.Transform, Params.Extent); + } + + Params.bEnabled = in_Portal->GetCurrentState() == AkAcousticPortalState::Open; + Params.FrontRoom = in_Portal->GetFrontRoom(); + Params.BackRoom = in_Portal->GetBackRoom(); + + SpatialAudio->SetPortal(portalID, Params, TCHAR_TO_ANSI(*nameStr)); + } + } +#endif +} + +void FAkAudioDevice::RemoveSpatialAudioPortal(UAkPortalComponent* in_Portal) +{ + if (IsRunningCommandlet()) + return; + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return; + +#ifdef AK_ENABLE_PORTALS + auto Portals = WorldPortalsMap.Find(in_Portal->GetWorld()); + if (Portals != nullptr && Portals->Contains(in_Portal)) + { + Portals->Remove(in_Portal); + } + + if (ShouldNotifySoundEngine(in_Portal->GetWorld()->WorldType)) + { + AkPortalID portalID = in_Portal->GetPortalID(); + SpatialAudio->RemovePortal(portalID); + } +#endif +} + +/** Get a sorted list of AkAuxSendValue at a location + * + * @param Loc Location at which to find Reverb Volumes + * @param AkReverbVolumes Array of AkAuxSendValue at this location + */ +void FAkAudioDevice::GetAuxSendValuesAtLocation(FVector Loc, TArray& AkAuxSendValues, const UWorld* in_World) +{ + // Check if there are AkReverbVolumes at this location + TArray FoundComponents = LateReverbIndex.Query(Loc, in_World); + + // Sort the found Volumes + if (FoundComponents.Num() > 1) + { + FoundComponents.Sort([](const UAkLateReverbComponent& A, const UAkLateReverbComponent& B) + { + return A.Priority > B.Priority; + }); + } + + // Apply the found Aux Sends + AkAuxSendValue TmpSendValue; + // Build a list to set as AuxBusses + for( uint8 Idx = 0; Idx < FoundComponents.Num() && Idx < MaxAuxBus; Idx++ ) + { + TmpSendValue.listenerID = AK_INVALID_GAME_OBJECT; + TmpSendValue.auxBusID = FoundComponents[Idx]->GetAuxBusId(); + TmpSendValue.fControlValue = FoundComponents[Idx]->SendLevel; + AkAuxSendValues.Add(TmpSendValue); + } +} + +/** + * Post an event and location to ak soundengine + * + * @param Event Name of the event to post + * @param in_Location Location at which to play the event + * @return ID assigned by ak soundengine + */ +AkPlayingID FAkAudioDevice::PostAkAudioEventAtLocation( + UAkAudioEvent * Event, + FVector Location, + FRotator Orientation, + UWorld* World, + EAkAudioContext AudioContext + ) +{ + if (UNLIKELY(!Event)) + { + UE_LOG(LogAkAudio, Verbose, TEXT("Failed to post AkAudioEvent without an AkAudioEvent.")) + return AK_INVALID_PLAYING_ID; + } + + if (UNLIKELY(!IsValid(Event))) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to post invalid AkAudioEvent.")) + return AK_INVALID_PLAYING_ID; + } + + return Event->PostAtLocation(Location, Orientation, World, nullptr, nullptr, nullptr, (AkCallbackType)0, nullptr, AudioContext); +} + +void FAkAudioDevice::PostEventAtLocationEndOfEventCallback(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo) +{ + if (auto* Device = FAkAudioDevice::Get()) + { + Device->RemovePlayingID(((AkEventCallbackInfo*)in_pCallbackInfo)->eventID, ((AkEventCallbackInfo*)in_pCallbackInfo)->playingID); + + auto pPackage = (IAkUserEventCallbackPackage*)in_pCallbackInfo->pCookie; + if (pPackage && pPackage->HasExternalSources) + { + if (auto* ExternalSourceManager = IWwiseExternalSourceManager::Get()) + { + ExternalSourceManager->OnEndOfEvent(((AkEventCallbackInfo*)in_pCallbackInfo)->playingID); + } + } + } +} + +/** + * Post an event by name at location to ak soundengine + * + * @param in_pEvent Name of the event to post + * @param Location Location at which to play the event + * @return ID assigned by ak soundengine + */ +AkPlayingID FAkAudioDevice::PostEventAtLocation( + const FString& EventName, + const AkUInt32 EventShortID, + FVector Location, + FRotator Orientation, + UWorld* World, + const TArray& ExternalSources, + EAkAudioContext AudioContext +) +{ + UE_LOG(LogWwiseHints, Warning, TEXT("[Deprecated 22.1] Posting Event(%s; %" PRIu32 ") At Location. Should use Event posting through an AkAudioEvent class instead."), *EventName, EventShortID); + AkPlayingID PlayingID = AK_INVALID_PLAYING_ID; + + if ( m_bSoundEngineInitialized ) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_INVALID_PLAYING_ID; + + const AkGameObjectID objId = (AkGameObjectID)&EventName; + FAkAudioDevice_Helpers::RegisterGameObject(objId, EventName); + + TArray AkReverbVolumes; + GetAuxSendValuesAtLocation(Location, AkReverbVolumes, World); + SoundEngine->SetGameObjectAuxSendValues(objId, AkReverbVolumes.GetData(), AkReverbVolumes.Num()); + AkRoomID RoomID; + TArray AkRooms = RoomIndex.Query(Location, World); + if (AkRooms.Num() > 0) + RoomID = AkRooms[0]->GetRoomID(); + + SetInSpatialAudioRoom(objId, RoomID); + + AkSoundPosition soundpos; + FQuat tempQuat(Orientation); + FVectorsToAKWorldTransform(Location, tempQuat.GetForwardVector(), tempQuat.GetUpVector(), soundpos); + + SoundEngine->SetPosition(objId, soundpos); + + PlayingID = SoundEngine->PostEvent(EventShortID, objId, AK_EndOfEvent, &FAkAudioDevice::PostEventAtLocationEndOfEventCallback, nullptr, + ExternalSources.Num(), const_cast(ExternalSources.GetData())); + if (PlayingID != AK_INVALID_PLAYING_ID) + { + FScopeLock Lock(&EventToPlayingIDMapCriticalSection); + auto& PlayingIDs = EventToPlayingIDMap.FindOrAdd(EventShortID); + PlayingIDs.Add(PlayingID); + PlayingIDToAudioContextMap.Add(PlayingID, AudioContext); + } + SoundEngine->UnregisterGameObj( objId ); + } + + return PlayingID; +} + +UAkComponent* FAkAudioDevice::SpawnAkComponentAtLocation( class UAkAudioEvent* in_pAkEvent, FVector Location, FRotator Orientation, bool AutoPost, const FString& EventName, bool AutoDestroy, UWorld* in_World) +{ + UAkComponent * AkComponent = NULL; + if (in_World) + { + AkComponent = NewObject(in_World->GetWorldSettings()); + } + else + { + AkComponent = NewObject(); + } + + if( AkComponent ) + { + AkComponent->AkAudioEvent = in_pAkEvent; + AkComponent->EventName = EventName; + AkComponent->SetWorldLocationAndRotation(Location, Orientation.Quaternion()); + if(in_World) + { + AkComponent->RegisterComponentWithWorld(in_World); + } + + AkComponent->SetAutoDestroy(AutoDestroy); + + if(AutoPost) + { + if (AkComponent->PostAssociatedAkEvent(0, FOnAkPostEventCallback()) == AK_INVALID_PLAYING_ID && AutoDestroy) + { + AkComponent->ConditionalBeginDestroy(); + AkComponent = NULL; + } + } + } + + return AkComponent; +} + +/** + * Post a trigger to ak soundengine + * + * @param in_pszTrigger Name of the trigger + * @param in_pAkComponent AkComponent on which to post the trigger + * @return Result from ak sound engine + */ +AKRESULT FAkAudioDevice::PostTrigger( + const TCHAR * in_pszTrigger, + AActor * in_pActor + ) +{ + AkGameObjectID GameObjID = AK_INVALID_GAME_OBJECT; + AKRESULT eResult = GetGameObjectID( in_pActor, GameObjID ); + if ( m_bSoundEngineInitialized && eResult == AK_Success) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + eResult = SoundEngine->PostTrigger(TCHAR_TO_AK(in_pszTrigger), GameObjID ); + } + return eResult; +} + +AKRESULT FAkAudioDevice::PostTrigger( + const UAkTrigger* in_TriggerValue, + AActor* in_pActor +) +{ + AkGameObjectID GameObjID = AK_INVALID_GAME_OBJECT; + AKRESULT eResult = GetGameObjectID(in_pActor, GameObjID); + if (m_bSoundEngineInitialized && in_TriggerValue && eResult == AK_Success) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + eResult = SoundEngine->PostTrigger(in_TriggerValue->GetShortID(), GameObjID); + } + return eResult; +} + +/** +* Set a RTPC in ak soundengine +* +* @param in_pszRtpcName Name of the RTPC +* @param in_value Value to set +* @param in_pActor Actor on which to set the RTPC +* @return Result from ak sound engine +*/ +AKRESULT FAkAudioDevice::SetRTPCValue( + const TCHAR * in_pszRtpcName, + AkRtpcValue in_value, + int32 in_interpolationTimeMs = 0, + AActor * in_pActor = NULL +) +{ + AKRESULT eResult = AK_Success; + if (m_bSoundEngineInitialized) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + auto RtpcID = SoundEngine->GetIDFromString(TCHAR_TO_AK(in_pszRtpcName)); + + eResult = SetRTPCValue(RtpcID, in_value, in_interpolationTimeMs, in_pActor); + } + return eResult; +} + +/** + * Set a RTPC in ak soundengine + * + * @param in_Rtpc RTPC Short ID + * @param in_value Value to set + * @param in_interpolationTimeMs - Duration during which the RTPC is interpolated towards in_value (in ms) + * @param in_pActor AActor on which to set the RTPC + * @return Result from ak sound engine + */ +AKRESULT FAkAudioDevice::SetRTPCValue( + AkRtpcID in_Rtpc, + AkRtpcValue in_value, + int32 in_interpolationTimeMs = 0, + AActor * in_pActor = NULL +) +{ + AKRESULT eResult = AK_Success; + if (m_bSoundEngineInitialized) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + AkGameObjectID GameObjID = AK_INVALID_GAME_OBJECT; // RTPC at global scope is supported + if (in_pActor) + { + eResult = GetGameObjectID(in_pActor, GameObjID); + if (eResult != AK_Success) + return eResult; + } + + eResult = SoundEngine->SetRTPCValue(in_Rtpc, in_value, GameObjID, in_interpolationTimeMs); + } + return eResult; +} + +AKRESULT FAkAudioDevice::SetRTPCValue( + const UAkRtpc* in_RtpcValue, + AkRtpcValue in_value, + int32 in_interpolationTimeMs = 0, + AActor * in_pActor = NULL +) +{ + + AKRESULT eResult = AK_Success; + if (m_bSoundEngineInitialized && in_RtpcValue) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + AkGameObjectID GameObjID = AK_INVALID_GAME_OBJECT; // RTPC at global scope is supported + if (in_pActor) + { + eResult = GetGameObjectID(in_pActor, GameObjID); + if (eResult != AK_Success) + return eResult; + } + + eResult = SoundEngine->SetRTPCValue(in_RtpcValue->GetShortID(), in_value, GameObjID, in_interpolationTimeMs); + } + return eResult; +} + +AKRESULT FAkAudioDevice::SetRTPCValueByPlayingID( + AkRtpcID in_Rtpc, + AkRtpcValue in_value, + AkPlayingID in_playingID, + int32 in_interpolationTimeMs +) +{ + AKRESULT eResult = AK_Success; + if (m_bSoundEngineInitialized) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + eResult = SoundEngine->SetRTPCValueByPlayingID(in_Rtpc, in_value, in_playingID, in_interpolationTimeMs); + } + return eResult; +} + +/** + * Get the value of a real-time parameter control (by ID) + * An RTPC can have a any combination of a global value, a unique value for each game object, or a unique value for each playing ID. + * The value requested is determined by RTPCValue_type, in_gameObjectID and in_playingID. + * If a value at the requested scope (determined by RTPCValue_type) is not found, the value that is available at the the next broadest scope will be returned, and io_rValueType will be changed to indicate this. + * @note + * When looking up RTPC values via playing ID (ie. io_rValueType is RTPC_PlayingID), in_gameObjectID can be set to a specific game object (if it is available to the caller) to use as a fall back value. + * If the game object is unknown or unavailable, AK_INVALID_GAME_OBJECT can be passed in in_gameObjectID, and the game object will be looked up via in_playingID. + * However in this case, it is not possible to retrieve a game object value as a fall back value if the playing id does not exist. It is best to pass in the game object if possible. + * + * @return AK_Success if succeeded, AK_IDNotFound if the game object was not registered, or AK_Fail if the RTPC value could not be obtained + */ +AKRESULT FAkAudioDevice::GetRTPCValue( + const TCHAR * in_pszRtpcName, + AkGameObjectID in_gameObjectID, ///< Associated game object ID, ignored if io_rValueType is RTPCValue_Global. + AkPlayingID in_playingID, ///< Associated playing ID, ignored if io_rValueType is not RTPC_PlayingID. + AkRtpcValue& out_rValue, ///< Value returned + AK::SoundEngine::Query::RTPCValue_type& io_rValueType ///< In/Out value, the user must specify the requested type. The function will return in this variable the type of the returned value. ); +) +{ + AKRESULT eResult = AK_Success; + if (m_bSoundEngineInitialized) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + eResult = SoundEngine->Query->GetRTPCValue(TCHAR_TO_AK(in_pszRtpcName), in_gameObjectID, in_playingID, out_rValue, io_rValueType); + } + return eResult; +} + +/** + * Get the value of a real-time parameter control (by ID) + * An RTPC can have a any combination of a global value, a unique value for each game object, or a unique value for each playing ID. + * The value requested is determined by RTPCValue_type, in_gameObjectID and in_playingID. + * If a value at the requested scope (determined by RTPCValue_type) is not found, the value that is available at the the next broadest scope will be returned, and io_rValueType will be changed to indicate this. + * @note + * When looking up RTPC values via playing ID (ie. io_rValueType is RTPC_PlayingID), in_gameObjectID can be set to a specific game object (if it is available to the caller) to use as a fall back value. + * If the game object is unknown or unavailable, AK_INVALID_GAME_OBJECT can be passed in in_gameObjectID, and the game object will be looked up via in_playingID. + * However in this case, it is not possible to retrieve a game object value as a fall back value if the playing id does not exist. It is best to pass in the game object if possible. + * + * @return AK_Success if succeeded, AK_IDNotFound if the game object was not registered, or AK_Fail if the RTPC value could not be obtained + */ +AKRESULT FAkAudioDevice::GetRTPCValue( + AkRtpcID in_Rtpc, + AkGameObjectID in_gameObjectID, ///< Associated game object ID, ignored if io_rValueType is RTPCValue_Global. + AkPlayingID in_playingID, ///< Associated playing ID, ignored if io_rValueType is not RTPC_PlayingID. + AkRtpcValue& out_rValue, ///< Value returned + AK::SoundEngine::Query::RTPCValue_type& io_rValueType ///< In/Out value, the user must specify the requested type. The function will return in this variable the type of the returned value. ); +) +{ + AKRESULT eResult = AK_Success; + if (m_bSoundEngineInitialized) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + eResult = SoundEngine->Query->GetRTPCValue(in_Rtpc, in_gameObjectID, in_playingID, out_rValue, io_rValueType); + } + return eResult; +} + +AKRESULT FAkAudioDevice::GetRTPCValue( + const UAkRtpc* in_RtpcValue, + AkGameObjectID in_gameObjectID, ///< Associated game object ID, ignored if io_rValueType is RTPCValue_Global. + AkPlayingID in_playingID, ///< Associated playing ID, ignored if io_rValueType is not RTPC_PlayingID. + AkRtpcValue& out_rValue, ///< Value returned + AK::SoundEngine::Query::RTPCValue_type& io_rValueType ///< In/Out value, the user must specify the requested type. The function will return in this variable the type of the returned value. ); +) +{ + AKRESULT eResult = AK_Success; + if (m_bSoundEngineInitialized && in_RtpcValue) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + eResult = SoundEngine->Query->GetRTPCValue(in_RtpcValue->GetShortID(), in_gameObjectID, in_playingID, out_rValue, io_rValueType); + } + return eResult; +} + +AKRESULT FAkAudioDevice::ResetRTPCValue(const UAkRtpc* in_RtpcValue, AkGameObjectID in_gameObjectID, int32 in_interpolationTimeMs) +{ + AKRESULT eResult = AK_Success; + if (m_bSoundEngineInitialized && in_RtpcValue) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + eResult = SoundEngine->ResetRTPCValue(in_RtpcValue->GetShortID(), in_gameObjectID, in_interpolationTimeMs); + } + return eResult; +} + +AKRESULT FAkAudioDevice::ResetRTPCValue(AkRtpcID in_rtpcID, AkGameObjectID in_gameObjectID, int32 in_interpolationTimeMs) +{ + AKRESULT eResult = AK_Success; + if (m_bSoundEngineInitialized && in_rtpcID) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + eResult = SoundEngine->ResetRTPCValue(in_rtpcID, in_gameObjectID, in_interpolationTimeMs); + } + return eResult; +} + +AKRESULT FAkAudioDevice::ResetRTPCValue(const TCHAR* in_pszRtpcName, AkGameObjectID in_gameObjectID, int32 in_interpolationTimeMs) +{ + AKRESULT eResult = AK_Success; + if (m_bSoundEngineInitialized) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + eResult = SoundEngine->ResetRTPCValue(TCHAR_TO_AK(in_pszRtpcName), in_gameObjectID, in_interpolationTimeMs); + } + return eResult; +} + +/** + * Set a state in ak soundengine + * + * @param in_pszStateGroup Name of the state group + * @param in_pszState Name of the state + * @return Result from ak sound engine + */ +AKRESULT FAkAudioDevice::SetState( + const TCHAR * in_pszStateGroup, + const TCHAR * in_pszState + ) +{ + AKRESULT eResult = AK_Success; + if ( m_bSoundEngineInitialized ) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + auto StateGroupID = SoundEngine->GetIDFromString(TCHAR_TO_AK(in_pszStateGroup)); + auto StateID = SoundEngine->GetIDFromString(TCHAR_TO_AK(in_pszState)); + eResult = SoundEngine->SetState(StateGroupID, StateID); + } + return eResult; +} + +/** + * Set a state in ak soundengine + * + * @param in_StateGroup State group short ID + * @param in_State State short ID + * @return Result from ak sound engine + */ +AKRESULT FAkAudioDevice::SetState( + AkStateGroupID in_StateGroup, + AkStateID in_State +) +{ + AKRESULT eResult = AK_Success; + if ( m_bSoundEngineInitialized ) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + eResult = SoundEngine->SetState(in_StateGroup, in_State); + } + return eResult; +} + +AKRESULT FAkAudioDevice::SetState( + const UAkStateValue* in_stateValue +) +{ + AKRESULT eResult = AK_Success; + if (m_bSoundEngineInitialized && in_stateValue) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + eResult = SoundEngine->SetState(in_stateValue->GetGroupID(), in_stateValue->GetShortID()); + } + return eResult; +} + +/** + * Set a switch in ak soundengine + * + * @param in_pszSwitchGroup Name of the switch group + * @param in_pszSwitchState Name of the switch + * @param in_pComponent AkComponent on which to set the switch + * @return Result from ak sound engine + */ +AKRESULT FAkAudioDevice::SetSwitch( + const TCHAR * in_pszSwitchGroup, + const TCHAR * in_pszSwitchState, + AActor * in_pActor + ) +{ + AKRESULT eResult = AK_Success; + if ( m_bSoundEngineInitialized) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + auto SwitchGroupID = SoundEngine->GetIDFromString(TCHAR_TO_AK(in_pszSwitchGroup)); + auto SwitchStateID = SoundEngine->GetIDFromString(TCHAR_TO_AK(in_pszSwitchState)); + eResult = SetSwitch(SwitchGroupID, SwitchStateID, in_pActor); + } + return eResult; +} + +/** + * Set a switch in ak soundengine + * + * @param in_SwitchGroup Short ID of the switch group + * @param in_SwitchState Short ID of the switch + * @param in_pComponent AkComponent on which to set the switch + * @return Result from ak sound engine + */ +AKRESULT FAkAudioDevice::SetSwitch( + AkSwitchGroupID in_SwitchGroup, + AkSwitchStateID in_SwitchState, + AActor * in_pActor + ) +{ + AkGameObjectID GameObjID = DUMMY_GAMEOBJ; + // Switches must be bound to a game object. passing DUMMY_GAMEOBJ as default game object. + AKRESULT eResult = GetGameObjectID( in_pActor, GameObjID ); + if ( m_bSoundEngineInitialized && eResult == AK_Success) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + eResult = SoundEngine->SetSwitch(in_SwitchGroup, in_SwitchState, GameObjID); + } + return eResult; +} + +AKRESULT FAkAudioDevice::SetSwitch( + const UAkSwitchValue* in_switchValue, + AActor * in_pActor +) +{ + AkGameObjectID GameObjID = DUMMY_GAMEOBJ; + // Switches must be bound to a game object. passing DUMMY_GAMEOBJ as default game object. + AKRESULT eResult = GetGameObjectID(in_pActor, GameObjID); + if (m_bSoundEngineInitialized && in_switchValue && eResult == AK_Success) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + eResult = SoundEngine->SetSwitch(in_switchValue->GetGroupID(), in_switchValue->GetShortID(), GameObjID); + } + return eResult; +} + +static AK::SoundEngine::MultiPositionType GetSoundEngineMultiPositionType(AkMultiPositionType in_eType) +{ + switch (in_eType) + { + case AkMultiPositionType::SingleSource: return AK::SoundEngine::MultiPositionType_SingleSource; + case AkMultiPositionType::MultiSources: return AK::SoundEngine::MultiPositionType_MultiSources; + case AkMultiPositionType::MultiDirections: return AK::SoundEngine::MultiPositionType_MultiDirections; + // Unknown multi position type! + default: AKASSERT(false); return AK::SoundEngine::MultiPositionType_SingleSource; + } +} + +/** Sets multiple positions to a single game object. +* Setting multiple positions on a single game object is a way to simulate multiple emission sources while using the resources of only one voice. +* This can be used to simulate wall openings, area sounds, or multiple objects emitting the same sound in the same area. +* Note: Calling AK::SoundEngine::SetMultiplePositions() with only one position is the same as calling AK::SoundEngine::SetPosition() +* @param in_pGameObjectAkComponent Game Object AkComponent. +* @param in_pPositions Array of positions to apply. +* @param in_eMultiPositionType Position type +* @return AK_Success when successful, AK_InvalidParameter if parameters are not valid. +* +*/ +AKRESULT FAkAudioDevice::SetMultiplePositions( + UAkComponent* in_pGameObjectAkComponent, + TArray in_aPositions, + AkMultiPositionType in_eMultiPositionType /*= AkMultiPositionType::MultiDirections*/ +) +{ + if (!in_pGameObjectAkComponent) + { + return AK_Fail; + } + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + + const int numPositions = in_aPositions.Num(); + TArray aPositions; + aPositions.Empty(); + for (int i = 0; i < numPositions; ++i) + { + AkSoundPosition soundpos; + FAkAudioDevice::FVectorsToAKWorldTransform(in_aPositions[i].GetLocation(), in_aPositions[i].GetRotation().GetForwardVector(), in_aPositions[i].GetRotation().GetUpVector(), soundpos); + aPositions.Add(soundpos); + } + return SoundEngine->SetMultiplePositions(in_pGameObjectAkComponent->GetAkGameObjectID(), aPositions.GetData(), + aPositions.Num(), GetSoundEngineMultiPositionType(in_eMultiPositionType)); +} + +template +AKRESULT FAkAudioDevice::SetMultiplePositions( + UAkComponent* in_pGameObjectAkComponent, + const TArray& in_aChannelConfigurations, + const TArray& in_aPositions, + AkMultiPositionType in_eMultiPositionType /*= AkMultiPositionType::MultiDirections*/ +) +{ + if (!in_pGameObjectAkComponent) + { + return AK_Fail; + } + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + const int32 numPositions = FMath::Min(in_aPositions.Num(), in_aChannelConfigurations.Num()); + + TArray emitters; + emitters.Reserve(numPositions); + for (int i = 0; i < numPositions; ++i) + { + AkSoundPosition soundpos; + FAkAudioDevice::FVectorsToAKWorldTransform(in_aPositions[i].GetLocation(), in_aPositions[i].GetRotation().GetForwardVector(), in_aPositions[i].GetRotation().GetUpVector(), soundpos); + + AkChannelConfig config; + GetChannelConfig(in_aChannelConfigurations[i], config); + + emitters.Add(AkChannelEmitter()); + emitters[i].uInputChannels = config.uChannelMask; + emitters[i].position = soundpos; + } + + return SoundEngine->SetMultiplePositions(in_pGameObjectAkComponent->GetAkGameObjectID(), emitters.GetData(), + emitters.Num(), GetSoundEngineMultiPositionType(in_eMultiPositionType)); +} + +AKRESULT FAkAudioDevice::SetMultiplePositions( + UAkComponent* in_pGameObjectAkComponent, + const TArray& in_aChannelConfigurations, + const TArray& in_aPositions, + AkMultiPositionType in_eMultiPositionType +) +{ + return SetMultiplePositions(in_pGameObjectAkComponent, in_aChannelConfigurations, in_aPositions, in_eMultiPositionType); +} + +AKRESULT FAkAudioDevice::SetMultiplePositions( + UAkComponent* in_pGameObjectAkComponent, + const TArray& in_channelMasks, + const TArray& in_aPositions, + AkMultiPositionType in_eMultiPositionType +) +{ + return SetMultiplePositions(in_pGameObjectAkComponent, in_channelMasks, in_aPositions, in_eMultiPositionType); +} + +/** Sets multiple positions to a single game object. +* Setting multiple positions on a single game object is a way to simulate multiple emission sources while using the resources of only one voice. +* This can be used to simulate wall openings, area sounds, or multiple objects emitting the same sound in the same area. +* Note: Calling AK::SoundEngine::SetMultiplePositions() with only one position is the same as calling AK::SoundEngine::SetPosition() +* @param in_GameObjectID Game Object identifier. +* @param in_pPositions Array of positions to apply. +* @param in_NumPositions Number of positions specified in the provided array. +* @param in_eMultiPositionType Position type +* @return AK_Success when successful, AK_InvalidParameter if parameters are not valid. +*/ +AKRESULT FAkAudioDevice::SetMultiplePositions( + AkGameObjectID in_GameObjectID, + const AkSoundPosition * in_pPositions, + AkUInt16 in_NumPositions, + AK::SoundEngine::MultiPositionType in_eMultiPositionType /*= AK::SoundEngine::MultiDirections*/ + ) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + return SoundEngine->SetMultiplePositions(in_GameObjectID, in_pPositions, in_NumPositions, in_eMultiPositionType); +} + +/** Sets multiple positions to a single game object, with flexible assignment of input channels. +* Setting multiple positions on a single game object is a way to simulate multiple emission sources while using the resources of only one voice. +* This can be used to simulate wall openings, area sounds, or multiple objects emitting the same sound in the same area. +* Note: Calling AK::SoundEngine::SetMultiplePositions() with only one position is the same as calling AK::SoundEngine::SetPosition() +* @param in_GameObjectID Game Object identifier. +* @param in_pPositions Array of positions to apply. +* @param in_NumPositions Number of positions specified in the provided array. +* @param in_eMultiPositionType Position type +* @return AK_Success when successful, AK_InvalidParameter if parameters are not valid. +*/ +AKRESULT FAkAudioDevice::SetMultiplePositions( + AkGameObjectID in_GameObjectID, + const AkChannelEmitter * in_pPositions, + AkUInt16 in_NumPositions, + AK::SoundEngine::MultiPositionType in_eMultiPositionType /*= AK::SoundEngine::MultiDirections*/ + ) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + return SoundEngine->SetMultiplePositions(in_GameObjectID, in_pPositions, in_NumPositions, in_eMultiPositionType); +} + +/** + * Set auxiliary sends + * + * @param in_GameObjId Wwise Game Object ID + * @param in_AuxSendValues Array of AkAuxSendValue, containins all Aux Sends to set on the game objectt + * @return Result from ak sound engine + */ +AKRESULT FAkAudioDevice::SetAuxSends( + const UAkComponent* in_akComponent, + TArray& in_AuxSendValues + ) +{ + AKRESULT eResult = AK_Success; + if ( m_bSoundEngineInitialized ) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + eResult = SoundEngine->SetGameObjectAuxSendValues(in_akComponent->GetAkGameObjectID(), in_AuxSendValues.GetData(), in_AuxSendValues.Num()); + } + + return eResult; +} + +void FAkAudioDevice::GetChannelConfig(AkChannelConfiguration ChannelConfiguration, AkChannelConfig& config) +{ + switch (ChannelConfiguration) + { + case AkChannelConfiguration::Ak_MainMix: + config.eConfigType = AK_ChannelConfigType_UseDeviceMain; + break; + case AkChannelConfiguration::Ak_Passthrough: + config.eConfigType = AK_ChannelConfigType_UseDevicePassthrough; + break; + case AkChannelConfiguration::Ak_LFE: + config.SetStandard(AK_SPEAKER_SETUP_0POINT1); + break; + case AkChannelConfiguration::AK_Audio_Objects: + config.SetObject(); + break; + case AkChannelConfiguration::Ak_1_0: + config.SetStandard(AK_SPEAKER_SETUP_MONO); + break; + case AkChannelConfiguration::Ak_2_0: + config.SetStandard(AK_SPEAKER_SETUP_STEREO); + break; + case AkChannelConfiguration::Ak_2_1: + config.SetStandard(AK_SPEAKER_SETUP_2POINT1); + break; + case AkChannelConfiguration::Ak_3_0: + config.SetStandard(AK_SPEAKER_SETUP_3STEREO); + break; + case AkChannelConfiguration::Ak_3_1: + config.SetStandard(AK_SPEAKER_SETUP_3POINT1); + break; + case AkChannelConfiguration::Ak_4_0: + config.SetStandard(AK_SPEAKER_SETUP_4); + break; + case AkChannelConfiguration::Ak_4_1: + config.SetStandard(AK_SPEAKER_SETUP_4POINT1); + break; + case AkChannelConfiguration::Ak_5_0: + config.SetStandard(AK_SPEAKER_SETUP_5); + break; + case AkChannelConfiguration::Ak_5_1: + config.SetStandard(AK_SPEAKER_SETUP_5POINT1); + break; + case AkChannelConfiguration::Ak_7_1: + config.SetStandard(AK_SPEAKER_SETUP_7POINT1); + break; + case AkChannelConfiguration::Ak_5_1_2: + config.SetStandard(AK_SPEAKER_SETUP_DOLBY_5_1_2); + break; + case AkChannelConfiguration::Ak_7_1_2: + config.SetStandard(AK_SPEAKER_SETUP_DOLBY_7_1_2); + break; + case AkChannelConfiguration::Ak_7_1_4: + config.SetStandard(AK_SPEAKER_SETUP_DOLBY_7_1_4); + break; + case AkChannelConfiguration::Ak_Auro_9_1: + config.SetStandard(AK_SPEAKER_SETUP_AURO_9POINT1); + break; + case AkChannelConfiguration::Ak_Auro_10_1: + config.SetStandard(AK_SPEAKER_SETUP_AURO_10POINT1); + break; + case AkChannelConfiguration::Ak_Auro_11_1: + config.SetStandard(AK_SPEAKER_SETUP_AURO_11POINT1); + break; + case AkChannelConfiguration::Ak_Auro_13_1: + config.SetStandard(AK_SPEAKER_SETUP_AURO_13POINT1_751); + break; + case AkChannelConfiguration::Ak_Ambisonics_1st_order: + config.SetAmbisonic(4); + break; + case AkChannelConfiguration::Ak_Ambisonics_2nd_order: + config.SetAmbisonic(9); + break; + case AkChannelConfiguration::Ak_Ambisonics_3rd_order: + config.SetAmbisonic(16); + break; + case AkChannelConfiguration::Ak_Ambisonics_4th_order: + config.SetAmbisonic(25); + break; + case AkChannelConfiguration::Ak_Ambisonics_5th_order: + config.SetAmbisonic(36); + break; + + case AkChannelConfiguration::Ak_Parent: + default: + config.Clear(); + break; + } +} + +void FAkAudioDevice::GetChannelConfig(FAkChannelMask SpeakerConfig, AkChannelConfig& config) +{ + config.SetStandard(SpeakerConfig.ChannelMask); +} + +/** +* Set spatial audio room +* +* @param in_GameObjId Wwise Game Object ID +* @param in_RoomID ID of the room that the game object is inside. +* @return Result from ak sound engine +*/ +AKRESULT FAkAudioDevice::SetInSpatialAudioRoom( + const AkGameObjectID in_GameObjId, + AkRoomID in_RoomID +) +{ + AKRESULT eResult = AK_Success; +#ifdef AK_ENABLE_ROOMS + if (m_bSoundEngineInitialized) + { + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; + + eResult = SpatialAudio->SetGameObjectInRoom(in_GameObjId, in_RoomID); + } +#endif + return eResult; +} + +AKRESULT FAkAudioDevice::SetBusConfig( + const FString& in_BusName, + AkChannelConfig in_Config + ) +{ + AKRESULT eResult = AK_Fail; + if (in_BusName.IsEmpty()) + { + return eResult; + } + + if (m_bSoundEngineInitialized) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + AkUniqueID BusId = GetShortIDFromString(in_BusName); + eResult = SoundEngine->SetBusConfig(BusId, in_Config); + } + + return eResult; +} + +AKRESULT FAkAudioDevice::SetPanningRule( + AkPanningRule in_ePanningRule + ) +{ + AKRESULT eResult = AK_Fail; + if (m_bSoundEngineInitialized) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + eResult = SoundEngine->SetPanningRule(in_ePanningRule); + } + + return eResult; +} + +AkOutputDeviceID FAkAudioDevice::GetOutputID( + const FString& in_szShareSet, + AkUInt32 in_idDevice + ) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_INVALID_OUTPUT_DEVICE_ID; + + return SoundEngine->GetOutputID(TCHAR_TO_AK(*in_szShareSet), in_idDevice); +} + +AKRESULT FAkAudioDevice::ReplaceMainOutput(const AkOutputSettings& MainOutputSettings) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + AKRESULT Result = SoundEngine->ReplaceOutput(MainOutputSettings, 0); + SoundEngine->RenderAudio(); + return Result; +} + +AKRESULT FAkAudioDevice::GetSpeakerAngles( + TArray& out_pfSpeakerAngles, + AkReal32& out_fHeightAngle, + AkOutputDeviceID in_idOutput + ) +{ + AKRESULT eResult = AK_Fail; + + if (m_bSoundEngineInitialized) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + AkUInt32 numSpeakers; + + // Retrieve the number of speaker and height angle + eResult = SoundEngine->GetSpeakerAngles(NULL, numSpeakers, out_fHeightAngle); + if (eResult != AK_Success) + return eResult; + + // Retrieve the speaker angles + out_pfSpeakerAngles.SetNum(numSpeakers); + eResult = SoundEngine->GetSpeakerAngles(out_pfSpeakerAngles.GetData(), numSpeakers, out_fHeightAngle, in_idOutput); + } + + return eResult; +} + +AKRESULT FAkAudioDevice::SetSpeakerAngles( + const TArray& in_pfSpeakerAngles, + AkReal32 in_fHeightAngle, + AkOutputDeviceID in_idOutput + ) +{ + AKRESULT eResult = AK_Fail; + + if (m_bSoundEngineInitialized) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + eResult = SoundEngine->SetSpeakerAngles(in_pfSpeakerAngles.GetData(), in_pfSpeakerAngles.Num(), in_fHeightAngle, in_idOutput); + } + + return eResult; +} + +AKRESULT FAkAudioDevice::SetGameObjectOutputBusVolume( + const UAkComponent* in_pEmitter, + const UAkComponent* in_pListener, + float in_fControlValue + ) +{ + AKRESULT eResult = AK_Success; + + if (m_bSoundEngineInitialized) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + const AkGameObjectID emitterId = in_pEmitter ? in_pEmitter->GetAkGameObjectID() : DUMMY_GAMEOBJ; + const AkGameObjectID listenerId = in_pListener ? in_pListener->GetAkGameObjectID() : DUMMY_GAMEOBJ; + eResult = SoundEngine->SetGameObjectOutputBusVolume(emitterId, listenerId, in_fControlValue); + } + + return eResult; +} + +/** + * Obtain a pointer to the singleton instance of FAkAudioDevice + * + * @return Pointer to the singleton instance of FAkAudioDevice + */ +FAkAudioDevice * FAkAudioDevice::Get() +{ + if (UNLIKELY(m_EngineExiting)) + { + return nullptr; + } + + if (LIKELY(FAkAudioModule::AkAudioModuleInstance)) + { + return FAkAudioModule::AkAudioModuleInstance->GetAkAudioDevice(); + } + else + { + FAkAudioModule* ModulePtr = FModuleManager::LoadModulePtr(TEXT("AkAudio")); + UE_CLOG(!ModulePtr, LogAkAudio, Warning, TEXT("No AkAudio module")); + UE_CLOG(FAkAudioModule::AkAudioModuleInstance != ModulePtr, LogAkAudio, Warning, TEXT("AkAudio instance (%p) differs from loaded module (%p)."), FAkAudioModule::AkAudioModuleInstance, ModulePtr); + return ModulePtr ? ModulePtr->GetAkAudioDevice() : nullptr; + } +} + +/** + * Gets the system sample rate + * + * @return Sample rate + */ +AkUInt32 FAkAudioDevice::GetSampleRate() +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return 0; + + return m_bSoundEngineInitialized ? SoundEngine->GetSampleRate() : 0; +} + +/** + * Enables/disables offline rendering + * + * @param bEnable Set to true to enable offline rendering + */ +AKRESULT FAkAudioDevice::SetOfflineRendering(bool bEnable) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + return m_bSoundEngineInitialized ? SoundEngine->SetOfflineRendering(bEnable) : AK_Fail; +} + +/** + * Sets the offline rendering frame time in seconds. + * + * @param FrameTimeInSeconds Frame time in seconds used during offline rendering + */ +AKRESULT FAkAudioDevice::SetOfflineRenderingFrameTime(AkReal32 FrameTimeInSeconds) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + return m_bSoundEngineInitialized ? SoundEngine->SetOfflineRenderingFrameTime(FrameTimeInSeconds) : AK_Fail; +} + +/** + * Registers a callback used for retrieving audio samples. + * + * @param Callback Capture callback function to register + * @param OutputId The audio device specific id, return by AK::SoundEngine::AddOutput or AK::SoundEngine::GetOutputID + * @param Cookie Callback cookie that will be sent to the callback function along with additional information + */ +AKRESULT FAkAudioDevice::RegisterCaptureCallback(AkCaptureCallbackFunc Callback, AkOutputDeviceID OutputId /*= AK_INVALID_OUTPUT_DEVICE_ID*/, void* Cookie /*= nullptr*/) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + return m_bSoundEngineInitialized ? SoundEngine->RegisterCaptureCallback(Callback, OutputId, Cookie) : AK_Fail; +} + +/** + * Unregisters a callback used for retrieving audio samples. + * + * @param Callback Capture callback function to register + * @param OutputId The audio device specific id, return by AK::SoundEngine::AddOutput or AK::SoundEngine::GetOutputID + * @param Cookie Callback cookie that will be sent to the callback function along with additional information + */ +AKRESULT FAkAudioDevice::UnregisterCaptureCallback(AkCaptureCallbackFunc Callback, AkOutputDeviceID OutputId /*= AK_INVALID_OUTPUT_DEVICE_ID*/, void* Cookie /*= nullptr*/) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + return m_bSoundEngineInitialized ? SoundEngine->UnregisterCaptureCallback(Callback, OutputId, Cookie) : AK_Fail; +} + +/** + * Stop all audio associated with a game object + * + * @param in_GameObjID ID of the game object + */ +void FAkAudioDevice::StopGameObject( UAkComponent * in_pComponent ) +{ + AkGameObjectID gameObjId = DUMMY_GAMEOBJ; + if ( in_pComponent ) + { + gameObjId = in_pComponent->GetAkGameObjectID(); + } + if ( m_bSoundEngineInitialized ) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + SoundEngine->StopAll( gameObjId ); + } +} + +/** + * Stop all audio associated with a playing ID + * + * @param in_playingID Playing ID to stop + * @param in_uTransitionDuration Fade duration + * @param in_eFadeCurve Curve type to be used for the transition + */ +void FAkAudioDevice::StopPlayingID( AkPlayingID in_playingID, + AkTimeMs in_uTransitionDuration /*= 0*/, + AkCurveInterpolation in_eFadeCurve /*= AkCurveInterpolation_Linear*/) +{ + if ( m_bSoundEngineInitialized ) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + SoundEngine->ExecuteActionOnPlayingID(AK::SoundEngine::AkActionOnEventType_Stop, in_playingID, in_uTransitionDuration, in_eFadeCurve ); + } +} + +/** + * Register an ak audio component with ak sound engine + * + * @param in_pComponent Pointer to the component to register + */ +void FAkAudioDevice::RegisterComponent( UAkComponent * in_pComponent ) +{ + if (m_bSoundEngineInitialized && in_pComponent) + { + if (in_pComponent->UseDefaultListeners()) + m_defaultEmitters.Add(in_pComponent); + + FString WwiseGameObjectName = TEXT(""); + in_pComponent->GetAkGameObjectName(WwiseGameObjectName); + + const AkGameObjectID gameObjId = in_pComponent->GetAkGameObjectID(); + FAkAudioDevice_Helpers::RegisterGameObject(gameObjId, WwiseGameObjectName); + + if (CallbackManager != nullptr) + CallbackManager->RegisterGameObject(gameObjId); + } +} + +/** + * Register a game object with ak sound engine + * + * @param GameObjectID ID of the game object to register + */ +void FAkAudioDevice::RegisterComponent(AkGameObjectID GameObjectID) +{ + if (m_bSoundEngineInitialized && GameObjectID) + { + FAkAudioDevice_Helpers::RegisterGameObject(GameObjectID, ""); + + if (CallbackManager != nullptr) + CallbackManager->RegisterGameObject(GameObjectID); + } +} + +/** + * Unregister an ak audio component with ak sound engine + * + * @param in_pComponent Pointer to the component to unregister + */ +void FAkAudioDevice::UnregisterComponent( UAkComponent * in_pComponent ) +{ + if (m_bSoundEngineInitialized && in_pComponent) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (LIKELY(SoundEngine)) + { + const AkGameObjectID gameObjId = in_pComponent->GetAkGameObjectID(); + SoundEngine->UnregisterGameObj(gameObjId); + + if (CallbackManager != nullptr) + { + CallbackManager->UnregisterGameObject(gameObjId); + } + } + } + + if (m_defaultListeners.Contains(in_pComponent)) + { + RemoveDefaultListener(in_pComponent); + } + + if (in_pComponent->UseDefaultListeners()) + { + m_defaultEmitters.Remove(in_pComponent); + } + + check(!m_defaultListeners.Contains(in_pComponent) && !m_defaultEmitters.Contains(in_pComponent)); + + if (m_SpatialAudioListener == in_pComponent) + m_SpatialAudioListener = nullptr; +} + +void FAkAudioDevice::UnregisterComponent( AkGameObjectID GameObjectId ) +{ + if (m_bSoundEngineInitialized && GameObjectId) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (LIKELY(SoundEngine)) + { + SoundEngine->UnregisterGameObj(GameObjectId); + } + + if (CallbackManager != nullptr) + { + CallbackManager->UnregisterGameObject(GameObjectId); + } + } +} + +AKRESULT FAkAudioDevice::SetGeometry(AkGeometrySetID GeometrySetID, const AkGeometryParams& Params) +{ + AKRESULT eResult = AK_Fail; + if (m_bSoundEngineInitialized) + { + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; + + eResult = SpatialAudio->SetGeometry(GeometrySetID, Params); + } + + return eResult; +} + +AKRESULT FAkAudioDevice::SetGeometryInstance(AkGeometryInstanceID GeometryInstanceID, const AkGeometryInstanceParams& Params) +{ + AKRESULT eResult = AK_Fail; + if (m_bSoundEngineInitialized) + { + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; + + eResult = SpatialAudio->SetGeometryInstance(GeometryInstanceID, Params); + } + + return eResult; +} + +AKRESULT FAkAudioDevice::RemoveGeometrySet(AkGeometrySetID GeometrySetID) +{ + AKRESULT eResult = AK_Fail; + if (m_bSoundEngineInitialized) + { + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; + + eResult = SpatialAudio->RemoveGeometry(GeometrySetID); + } + + return eResult; +} + +AKRESULT FAkAudioDevice::RemoveGeometryInstance(AkGeometryInstanceID GeometryInstanceID) +{ + AKRESULT eResult = AK_Fail; + if (m_bSoundEngineInitialized) + { + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; + + eResult = SpatialAudio->RemoveGeometryInstance(GeometryInstanceID); + } + + return eResult; +} + +AKRESULT FAkAudioDevice::SetEarlyReflectionsAuxBus(UAkComponent* in_pComponent, const AkUInt32 AuxBusID) +{ + AKRESULT eResult = AK_Fail; + if (m_bSoundEngineInitialized && in_pComponent) + { + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; + + const AkGameObjectID gameObjId = in_pComponent->GetAkGameObjectID(); + eResult = SpatialAudio->SetEarlyReflectionsAuxSend(gameObjId, AuxBusID); + } + + return eResult; +} + +AKRESULT FAkAudioDevice::SetEarlyReflectionsVolume(UAkComponent* in_pComponent, float in_fSendVolume) +{ + AKRESULT eResult = AK_Fail; + if (m_bSoundEngineInitialized && in_pComponent) + { + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; + + const AkGameObjectID gameObjId = in_pComponent->GetAkGameObjectID(); + eResult = SpatialAudio->SetEarlyReflectionsVolume(gameObjId, in_fSendVolume); + } + + return eResult; +} + +AKRESULT FAkAudioDevice::SetReflectionsOrder(int Order, bool RefreshPaths) +{ + AKRESULT eResult = AK_Fail; + if (m_bSoundEngineInitialized) + { + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; + + eResult = SpatialAudio->SetReflectionsOrder(Order, RefreshPaths); + } + return eResult; +} + +AKRESULT FAkAudioDevice::SetMultipleObstructionAndOcclusion(AkGameObjectID in_Object, AkGameObjectID in_listener, AkObstructionOcclusionValues* ObstructionAndOcclusionValues, AkUInt32 in_uNumObstructionAndOcclusion) +{ + AKRESULT eResult = AK_Fail; + if(m_bSoundEngineInitialized) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + eResult = SoundEngine->SetMultipleObstructionAndOcclusion(in_Object, in_listener, ObstructionAndOcclusionValues, in_uNumObstructionAndOcclusion); + } + + return eResult; +} + +AKRESULT FAkAudioDevice::SetObjectObstructionAndOcclusion(AkGameObjectID in_Object, AkGameObjectID in_listener, AkReal32 Obstruction, AkReal32 Occlusion) +{ + AKRESULT eResult = AK_Fail; + if (m_bSoundEngineInitialized) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + eResult = SoundEngine->SetObjectObstructionAndOcclusion(in_Object, in_listener, Obstruction, Occlusion); + } + + return eResult; +} + +AKRESULT FAkAudioDevice::SetPortalObstructionAndOcclusion(UAkPortalComponent* in_pPortal, float in_fObstructionValue, float in_fOcclusionValue) +{ + AKRESULT eResult = AK_Fail; + if (m_bSoundEngineInitialized && in_pPortal) + { + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; + + const AkPortalID portalID = in_pPortal->GetPortalID(); + eResult = SpatialAudio->SetPortalObstructionAndOcclusion(portalID, in_fObstructionValue, in_fOcclusionValue); + } + return eResult; +} + +AKRESULT FAkAudioDevice::SetGameObjectToPortalObstruction(UAkComponent* in_pComponent, UAkPortalComponent* in_pPortal, float in_fObstructionValue) +{ + AKRESULT eResult = AK_Fail; + if (m_bSoundEngineInitialized && in_pComponent && in_pPortal) + { + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; + + const AkGameObjectID gameObjId = in_pComponent->GetAkGameObjectID(); + const AkPortalID portalID = in_pPortal->GetPortalID(); + eResult = SpatialAudio->SetGameObjectToPortalObstruction(gameObjId, portalID, in_fObstructionValue); + } + return eResult; +} + +AKRESULT FAkAudioDevice::SetPortalToPortalObstruction(UAkPortalComponent* in_pPortal0, UAkPortalComponent* in_pPortal1, float in_fObstructionValue) +{ + AKRESULT eResult = AK_Fail; + if (m_bSoundEngineInitialized && in_pPortal0 && in_pPortal1) + { + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; + + const AkPortalID portalID0 = in_pPortal0->GetPortalID(); + const AkPortalID portalID1 = in_pPortal1->GetPortalID(); + eResult = SpatialAudio->SetPortalToPortalObstruction(portalID0, portalID1, in_fObstructionValue); + } + return eResult; +} + +void FAkAudioDevice::UpdateDefaultActiveListeners() +{ + if (m_bSoundEngineInitialized) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + auto NumDefaultListeners = m_defaultListeners.Num(); + auto pListenerIds = (AkGameObjectID*)alloca(NumDefaultListeners * sizeof(AkGameObjectID)); + int index = 0; + for (auto DefaultListenerIter = m_defaultListeners.CreateConstIterator(); DefaultListenerIter; ++DefaultListenerIter) + pListenerIds[index++] = (*DefaultListenerIter)->GetAkGameObjectID(); + + if (NumDefaultListeners > 0) + { + SoundEngine->SetDefaultListeners(pListenerIds, NumDefaultListeners); + } + } +} + +AKRESULT FAkAudioDevice::SetPosition(UAkComponent* in_akComponent, const AkSoundPosition& in_SoundPosition) +{ + if (m_bSoundEngineInitialized) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + return SoundEngine->SetPosition(in_akComponent->GetAkGameObjectID(), in_SoundPosition); + } + + return AK_Fail; +} + +AKRESULT FAkAudioDevice::AddRoom(UAkRoomComponent* in_pRoom, const AkRoomParams& in_RoomParams) +{ + if (ShouldNotifySoundEngine(in_pRoom->GetWorld()->WorldType)) + { + AKRESULT result = AK_Fail; + if (m_bSoundEngineInitialized) + { + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; + + UAkLateReverbComponent* ReverbComp = in_pRoom->GetReverbComponent(); + UE_CLOG(UNLIKELY(ReverbComp && ReverbComp->bEnable && in_RoomParams.ReverbAuxBus == AK_INVALID_AUX_ID), LogAkAudio, Warning, TEXT("Enabled Late Reverb component for room %s without an assigned Late Reverb Aux Bus"), *in_pRoom->GetRoomName()); + + result = SpatialAudio->SetRoom(in_pRoom->GetRoomID(), in_RoomParams, TCHAR_TO_ANSI(*in_pRoom->GetRoomName())); + if (result == AK_Success) + { + IndexRoom(in_pRoom); + PortalsNeedRoomUpdate(in_pRoom->GetWorld()); + } + } + return result; + } + + IndexRoom(in_pRoom); + PortalsNeedRoomUpdate(in_pRoom->GetWorld()); + return AK_Success; +} + +AKRESULT FAkAudioDevice::UpdateRoom(UAkRoomComponent* in_pRoom, const AkRoomParams& in_RoomParams) +{ + if (ShouldNotifySoundEngine(in_pRoom->GetWorld()->WorldType)) + { + AKRESULT result = AK_Fail; + if (m_bSoundEngineInitialized) + { + check(in_pRoom->HasBeenRegisteredWithWwise()); + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; + + check(in_pRoom->HasBeenRegisteredWithWwise()); + + result = SpatialAudio->SetRoom(in_pRoom->GetRoomID(), in_RoomParams, TCHAR_TO_ANSI(*in_pRoom->GetRoomName())); + if (result == AK_Success) + PortalsNeedRoomUpdate(in_pRoom->GetWorld()); + } + return result; + } + + PortalsNeedRoomUpdate(in_pRoom->GetWorld()); + return AK_Success; +} + +AKRESULT FAkAudioDevice::RemoveRoom(UAkRoomComponent* in_pRoom) +{ + if (ShouldNotifySoundEngine(in_pRoom->GetWorld()->WorldType)) + { + AKRESULT result = AK_Fail; + if (m_bSoundEngineInitialized) + { + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; + + result = SpatialAudio->RemoveRoom(in_pRoom->GetRoomID()); + if (result == AK_Success) + { + UnindexRoom(in_pRoom); + PortalsNeedRoomUpdate(in_pRoom->GetWorld()); + } + } + + return result; + } + + UnindexRoom(in_pRoom); + PortalsNeedRoomUpdate(in_pRoom->GetWorld()); + return AK_Success; +} + +AKRESULT FAkAudioDevice::SetGameObjectRadius(UAkComponent* in_akComponent, float in_outerRadius, float in_innerRadius) +{ + if (!m_bSoundEngineInitialized) + return AK_Fail; + + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; + + return SpatialAudio->SetGameObjectRadius(AkGameObjectID(in_akComponent), in_outerRadius, in_innerRadius); +} + +AKRESULT FAkAudioDevice::SetImageSource(AAkSpotReflector* in_pSpotReflector, const AkImageSourceSettings& in_ImageSourceInfo, AkUniqueID in_AuxBusID, UAkComponent* in_AkComponent) +{ + if (m_bSoundEngineInitialized) + { + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; + + return SpatialAudio->SetImageSource(in_pSpotReflector->GetImageSourceID(), in_ImageSourceInfo, TCHAR_TO_ANSI(*in_pSpotReflector->GetSpotReflectorName()), in_AuxBusID, in_AkComponent->GetAkGameObjectID()); + } + + return AK_Fail; +} + +AKRESULT FAkAudioDevice::RemoveImageSource(AAkSpotReflector* in_pSpotReflector, AkUniqueID in_AuxBusID, UAkComponent* in_AkComponent) +{ + if (m_bSoundEngineInitialized) + { + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; + + return SpatialAudio->RemoveImageSource(in_pSpotReflector->GetImageSourceID(), in_AuxBusID, in_AkComponent->GetAkGameObjectID()); + } + + return AK_Fail; +} + +AKRESULT FAkAudioDevice::ClearImageSources(AkUniqueID in_AuxBusID, UAkComponent* in_AkComponent) +{ + if (m_bSoundEngineInitialized) + { + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; + + return SpatialAudio->ClearImageSources(in_AuxBusID, in_AkComponent == NULL ? AK_INVALID_GAME_OBJECT : in_AkComponent->GetAkGameObjectID()); + } + + return AK_Fail; +} + +void FAkAudioDevice::SetListeners(UAkComponent* in_pEmitter, const TArray& in_listenerSet) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + check(!in_pEmitter->UseDefaultListeners()); + + m_defaultEmitters.Remove(in_pEmitter); //This emitter is no longer using the default listener set. + + auto NumListeners = in_listenerSet.Num(); + auto pListenerIds = (AkGameObjectID*)alloca(NumListeners * sizeof(AkGameObjectID)); + int index = 0; + for (const auto& Listener : in_listenerSet) + pListenerIds[index++] = Listener->GetAkGameObjectID(); + + SoundEngine->SetListeners(in_pEmitter->GetAkGameObjectID(), pListenerIds, NumListeners); +} + +bool FAkAudioDevice::SetSpatialAudioListener(UAkComponent* in_pListener) +{ +#if WITH_EDITOR + if (in_pListener == EditorListener) + { + return false; + } +#endif + m_SpatialAudioListener = in_pListener; + + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return false; + + SpatialAudio->RegisterListener((AkGameObjectID)m_SpatialAudioListener); + return true; +} + +UAkComponent* FAkAudioDevice::GetSpatialAudioListener() const +{ + return m_SpatialAudioListener; +} + +UAkComponent* FAkAudioDevice::GetAkComponent(class USceneComponent* AttachToComponent, FName AttachPointName, const FVector * Location, EAttachLocation::Type LocationType) +{ + bool ComponentCreated; + return GetAkComponent(AttachToComponent, AttachPointName, Location, LocationType, ComponentCreated); +} + +UAkComponent* FAkAudioDevice::GetAkComponent( class USceneComponent* AttachToComponent, FName AttachPointName, const FVector * Location, EAttachLocation::Type LocationType, bool& ComponentCreated ) +{ + if (!AttachToComponent) + { + return NULL; + } + + UAkComponent* AkComponent = NULL; + FAttachmentTransformRules AttachRules = FAttachmentTransformRules::KeepRelativeTransform; + + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return nullptr; + + if( GEngine && SoundEngine->IsInitialized()) + { + AActor * Actor = AttachToComponent->GetOwner(); + if( Actor ) + { + if( !IsValid(Actor) ) + { + // Avoid creating component if we're trying to play a sound on an already destroyed actor. + return NULL; + } + + TArray AkComponents; + Actor->GetComponents(AkComponents); + for ( int32 CompIdx = 0; CompIdx < AkComponents.Num(); CompIdx++ ) + { + UAkComponent* pCompI = AkComponents[CompIdx]; + if ( pCompI && pCompI->IsRegistered() ) + { + if ( AttachToComponent == pCompI ) + { + return pCompI; + } + + if ( AttachToComponent != pCompI->GetAttachParent() + || AttachPointName != pCompI->GetAttachSocketName() ) + { + continue; + } + + // If a location is requested, try to match location. + if ( Location ) + { + if (LocationType == EAttachLocation::KeepWorldPosition) + { + AttachRules = FAttachmentTransformRules::KeepWorldTransform; + if ( !FVector::PointsAreSame(*Location, pCompI->GetComponentLocation()) ) + continue; + } + else + { + AttachRules = FAttachmentTransformRules::KeepRelativeTransform; + auto RelLoc = pCompI->GetRelativeLocation(); + if ( !FVector::PointsAreSame(*Location, RelLoc) ) + continue; + } + } + + // AkComponent found which exactly matches the attachment: reuse it. + ComponentCreated = false; + return pCompI; + } + } + } + else + { + // Try to find if there is an AkComponent attached to AttachToComponent (will be the case if AttachToComponent has no owner) + const TArray AttachChildren = AttachToComponent->GetAttachChildren(); + for(int32 CompIdx = 0; CompIdx < AttachChildren.Num(); CompIdx++) + { + UAkComponent* pCompI = Cast(AttachChildren[CompIdx]); + if ( pCompI && pCompI->IsRegistered() ) + { + // There is an associated AkComponent to AttachToComponent, no need to add another one. + ComponentCreated = false; + return pCompI; + } + } + } + + if ( AkComponent == NULL ) + { + if( Actor ) + { + AkComponent = NewObject(Actor); + } + else + { + AkComponent = NewObject(); + } + } + + ComponentCreated = true; + check( AkComponent ); + + if (Location) + { + if (LocationType == EAttachLocation::KeepWorldPosition) + { + AttachRules = FAttachmentTransformRules::KeepWorldTransform; + AkComponent->SetWorldLocation(*Location); + } + else + { + AttachRules = FAttachmentTransformRules::KeepRelativeTransform; + AkComponent->SetRelativeLocation(*Location); + } + } + + AkComponent->RegisterComponentWithWorld(AttachToComponent->GetWorld()); + AkComponent->AttachToComponent(AttachToComponent, AttachRules, AttachPointName); + } + + return( AkComponent ); +} + + +/** +* Cancel the callback cookie for a dispatched event +* +* @param in_cookie The cookie to cancel +*/ +void FAkAudioDevice::CancelEventCallbackCookie(void* in_cookie) +{ + if (m_bSoundEngineInitialized) + { + CallbackManager->CancelEventCallback(in_cookie); + } +} + +/** +* Cancel the callback cookie for a dispatched event +* +* @param in_cookie The cookie to cancel +*/ +void FAkAudioDevice::CancelEventCallbackDelegate(const FOnAkPostEventCallback& in_Delegate) +{ + if (m_bSoundEngineInitialized) + { + CallbackManager->CancelEventCallback(in_Delegate); + } +} + +AKRESULT FAkAudioDevice::SetAttenuationScalingFactor(AActor* Actor, float ScalingFactor) +{ + AKRESULT eResult = AK_Fail; + if ( m_bSoundEngineInitialized ) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + AkGameObjectID GameObjID = DUMMY_GAMEOBJ; + eResult = GetGameObjectID( Actor, GameObjID ); + if( eResult == AK_Success ) + { + eResult = SoundEngine->SetScalingFactor(GameObjID, ScalingFactor); + } + } + + return eResult; +} + +AKRESULT FAkAudioDevice::SetAttenuationScalingFactor(UAkComponent* AkComponent, float ScalingFactor) +{ + AKRESULT eResult = AK_Fail; + if ( m_bSoundEngineInitialized && AkComponent) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + eResult = SoundEngine->SetScalingFactor(AkComponent->GetAkGameObjectID(), ScalingFactor); + } + return eResult; +} + +AKRESULT FAkAudioDevice::SetDistanceProbe(UAkComponent* Listener, UAkComponent* DistanceProbe) +{ + AKRESULT eResult = AK_Fail; + if (m_bSoundEngineInitialized && Listener) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + eResult = SoundEngine->SetDistanceProbe(Listener->GetAkGameObjectID(), DistanceProbe != nullptr ? DistanceProbe->GetAkGameObjectID() : AK_INVALID_GAME_OBJECT ); + } + return eResult; +} + +#if WITH_EDITORONLY_DATA && !defined(AK_OPTIMIZED) +AkErrorMessageTranslator* FAkAudioDevice::m_UnrealErrorTranslator; +#endif + +AKRESULT FAkAudioDevice::RegisterGameObject(AkGameObjectID GameObjectID, const FString& Name) +{ + return FAkAudioDevice_Helpers::RegisterGameObject(GameObjectID, Name); +} + +bool FAkAudioDevice::EnsureInitialized() +{ + static bool bPermanentInitializationFailure = false; + static bool bLogWwiseVersionOnce = true; + + if (LIKELY(m_bSoundEngineInitialized)) + { + return true; + } + if (UNLIKELY(bPermanentInitializationFailure)) + { + return false; + } + + SCOPED_AKAUDIO_EVENT_2(TEXT("FAkAudioDevice::EnsureInitialized")); + + UE_CLOG(bLogWwiseVersionOnce, LogAkAudio, Log, + TEXT("Wwise(R) SDK Version %d.%d.%d Build %d. Copyright (c) 2006-%d Audiokinetic Inc."), + AK_WWISESDK_VERSION_MAJOR, + AK_WWISESDK_VERSION_MINOR, + AK_WWISESDK_VERSION_SUBMINOR, + AK_WWISESDK_VERSION_BUILD, + AK_WWISESDK_VERSION_MAJOR); + bLogWwiseVersionOnce = false; + + auto* ResourceLoader = FWwiseResourceLoader::Get(); + if (UNLIKELY(!ResourceLoader)) + { + UE_LOG(LogAkAudio, Error, TEXT("Wwise Initialization Error: No ResourceLoader module")); + bPermanentInitializationFailure = true; + return false; + } + + // We don't want sound in those cases. + if (AK_USE_NULL_SOUNDENGINE) + { + UE_LOG(LogAkAudio, Display, TEXT("Wwise SoundEngine is disabled: Using the null SoundEngine.")); + bPermanentInitializationFailure = true; + ResourceLoader->Disable(); + return false; + } + if (FParse::Param(FCommandLine::Get(), TEXT("nosound"))) + { + UE_LOG(LogAkAudio, Display, TEXT("Wwise SoundEngine is disabled: \"nosound\" command line parameter.")); + bPermanentInitializationFailure = true; + ResourceLoader->Disable(); + return false; + } + if (FApp::IsBenchmarking()) + { + UE_LOG(LogAkAudio, Display, TEXT("Wwise SoundEngine is disabled: App is benchmarking.")); + bPermanentInitializationFailure = true; + ResourceLoader->Disable(); + return false; + } + if (IsRunningDedicatedServer()) + { + UE_LOG(LogAkAudio, Display, TEXT("Wwise SoundEngine is disabled: Running a dedicated server.")); + bPermanentInitializationFailure = true; + ResourceLoader->Disable(); + return false; + } + if (IsRunningCommandlet()) + { + UE_LOG(LogAkAudio, Display, TEXT("Wwise SoundEngine is disabled: Running a commandlet.")); + bPermanentInitializationFailure = true; + ResourceLoader->Disable(); + return false; + } + const UAkSettings* AkSettings = GetDefault(); + if (UNLIKELY(!AkSettings)) + { + UE_LOG(LogAkAudio, Error, TEXT("Wwise Initialization Error: No default settings.")); + bPermanentInitializationFailure = true; + ResourceLoader->Disable(); + return false; + } + if (!AkSettings->bWwiseSoundEngineEnabled) + { + UE_LOG(LogAkAudio, Display, TEXT("Wwise SoundEngine is disabled: Audio Routing is set to Enable Unreal Audio only.")); + bPermanentInitializationFailure = true; + ResourceLoader->Disable(); + return false; + } + + auto* FileHandlerModule = IWwiseFileHandlerModule::GetModule(); + if (UNLIKELY(!FileHandlerModule)) + { + UE_LOG(LogAkAudio, Error, TEXT("Wwise Initialization Error: No file handling module")); + bPermanentInitializationFailure = true; + ResourceLoader->Disable(); + return false; + } + + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) + { + UE_LOG(LogAkAudio, Error, TEXT("Wwise Initialization Error: No Sound Engine")); + bPermanentInitializationFailure = true; + ResourceLoader->Disable(); + return false; + } + + IOHook = FileHandlerModule->InstantiateIOHook(); + if (UNLIKELY(!IOHook)) + { + UE_LOG(LogAkAudio, Error, TEXT("Wwise Initialization Error: No IO Hook")); + bPermanentInitializationFailure = true; + ResourceLoader->Disable(); + return false; + } + + // From this point on, if we get an error, we can try initializing later + if (UNLIKELY(!FAkSoundEngineInitialization::Initialize(IOHook))) + { + UE_LOG(LogAkAudio, Display, TEXT("Wwise Initialization Error.")); + FAkSoundEngineInitialization::Finalize(IOHook); + delete IOHook; + IOHook = nullptr; + ResourceLoader->Disable(); + return false; + } + + UE_LOG(LogAkAudio, Log, TEXT("Wwise SoundEngine successfully initialized.")); + + SetLocalOutput(); + + // Init dummy game object + SoundEngine->RegisterGameObj(DUMMY_GAMEOBJ, "Unreal Global"); + +#if WITH_EDITOR + if (!IsRunningGame()) + { + AkGameObjectID tempID = DUMMY_GAMEOBJ; + SoundEngine->SetListeners(DUMMY_GAMEOBJ, &tempID, 1); + } +#endif + + m_bSoundEngineInitialized = true; + + CallbackInfoPool = new AkCallbackInfoPool; + // Go get the max number of Aux busses + MaxAuxBus = AkSettings->MaxSimultaneousReverbVolumes; + + //TUniquePtr + CallbackManager = new FAkComponentCallbackManager(); + SetCurrentAudioCulture(GetDefaultLanguage()); + + UE_LOG(LogAkAudio, Log, TEXT("Initialization complete.")); + + return CallbackManager != nullptr; +} + +void FAkAudioDevice::SetLocalOutput() +{ + auto* Monitor = IWwiseMonitorAPI::Get(); + if (UNLIKELY(!Monitor)) + { + return; + } + + Monitor->ResetTranslator(); +#if WITH_EDITORONLY_DATA && !defined(AK_OPTIMIZED) + const UAkSettingsPerUser* AkSettingsPerUser = GetDefault(); + + if (AkSettingsPerUser->WaapiTranslatorTimeout > 0) + { +#if AK_SUPPORT_WAAPI + Monitor->SetupDefaultWAAPIErrorTranslator(AkSettingsPerUser->WaapiIPAddress, AkSettingsPerUser->WaapiPort, AkSettingsPerUser->WaapiTranslatorTimeout); +#endif //AK_SUPPORT_WAAPI + } + + if (!m_UnrealErrorTranslator) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) + { + return; + } + m_UnrealErrorTranslator = SoundEngine->NewErrorMessageTranslator(&GetInfoErrorMessageTranslatorFunction); + } + if (m_UnrealErrorTranslator) + { + Monitor->AddTranslator(m_UnrealErrorTranslator); + } + +#endif +} + +void FAkAudioDevice::AddDefaultListener(UAkComponent* in_pListener) +{ + bool bAlreadyInSet; + m_defaultListeners.Add(in_pListener, &bAlreadyInSet); + if (!bAlreadyInSet) + { + for (auto& Emitter : m_defaultEmitters) + Emitter->OnDefaultListenerAdded(in_pListener); + + in_pListener->IsDefaultListener = true; + UpdateDefaultActiveListeners(); + + if (m_SpatialAudioListener == nullptr) + SetSpatialAudioListener(in_pListener); + } +} + +void FAkAudioDevice::RemoveDefaultListener(UAkComponent* in_pListener) +{ + for (auto& Emitter : m_defaultEmitters) + { + Emitter->OnListenerUnregistered(in_pListener); + } + + m_defaultListeners.Remove(in_pListener); + in_pListener->IsDefaultListener = false; + UpdateDefaultActiveListeners(); + + // We are setting Aux Sends with the SpatialAudio API, and that requires a Spatial Audio listener. + // When running dedicated server, Unreal creates a camera manager (default listener 1 gets set as spatial audio listener), then another one (default listener 2), and then destroys the first. This leaves us with a default listener, but no spatial audio listener. This fix targets that issue. + if (m_SpatialAudioListener == in_pListener ) + { + // Unregister the Spatial Audio Listener if its game object is unregistered + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (LIKELY(SpatialAudio)) + { + SpatialAudio->UnregisterListener(m_SpatialAudioListener->GetAkGameObjectID()); + } + m_SpatialAudioListener = nullptr; + + if (m_defaultListeners.Num() > 0) + { + for (auto listener : m_defaultListeners) + { + if (SetSpatialAudioListener(m_defaultListeners.Array()[0])) + { + break; + } + } + } + } +} + +void FAkAudioDevice::OnActorSpawned(AActor* SpawnedActor) +{ + APlayerCameraManager* AsPlayerCameraManager = Cast(SpawnedActor); + if (AsPlayerCameraManager && AsPlayerCameraManager->GetWorld()->AllowAudioPlayback()) + { + APlayerController* CameraOwner = Cast(AsPlayerCameraManager->GetOwner()); + if (CameraOwner && CameraOwner->IsLocalPlayerController()) + { + UAkComponent* pAkComponent = NewObject(SpawnedActor); + if (pAkComponent != nullptr) + { + pAkComponent->RegisterComponentWithWorld(SpawnedActor->GetWorld()); + pAkComponent->AttachToComponent(SpawnedActor->GetRootComponent(), FAttachmentTransformRules::KeepWorldTransform, FName()); + AddDefaultListener(pAkComponent); + } + } + } +} + +FString FAkAudioDevice::GetBasePath() +{ + return AkUnrealHelper::GetSoundBankDirectory(); +} + +/** + * Allocates memory from permanent pool. This memory will NEVER be freed. + * + * @param Size Size of allocation. + * + * @return pointer to a chunk of memory with size Size + */ +void* FAkAudioDevice::AllocatePermanentMemory( int32 Size, bool& AllocatedInPool ) +{ + return 0; +} + +AKRESULT FAkAudioDevice::GetGameObjectID( AActor * in_pActor, AkGameObjectID& io_GameObject ) +{ + if ( IsValid(in_pActor) ) + { + UAkComponent * pComponent = GetAkComponent( in_pActor->GetRootComponent(), FName(), NULL, EAttachLocation::KeepRelativeOffset ); + if ( pComponent ) + { + io_GameObject = pComponent->GetAkGameObjectID(); + return AK_Success; + } + else + return AK_Fail; + } + + // we do not modify io_GameObject, letting it to the specified default value. + return AK_Success; +} + +AKRESULT FAkAudioDevice::GetGameObjectID( AActor * in_pActor, AkGameObjectID& io_GameObject, bool in_bStopWhenOwnerDestroyed ) +{ + if ( IsValid(in_pActor) ) + { + UAkComponent * pComponent = GetAkComponent( in_pActor->GetRootComponent(), FName(), NULL, EAttachLocation::KeepRelativeOffset ); + if ( pComponent ) + { + pComponent->StopWhenOwnerDestroyed = in_bStopWhenOwnerDestroyed; + io_GameObject = pComponent->GetAkGameObjectID(); + return AK_Success; + } + else + return AK_Fail; + } + + // we do not modify io_GameObject, letting it to the specified default value. + return AK_Success; +} + +void FAkAudioDevice::Suspend(bool in_bRenderAnyway /* = false */) +{ + if (!m_isSuspended) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + SoundEngine->Suspend(in_bRenderAnyway); + m_isSuspended = true; + } +} + +void FAkAudioDevice::WakeupFromSuspend() +{ + if (m_isSuspended) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + SoundEngine->WakeupFromSuspend(); + m_isSuspended = false; + } +} + +void FAkAudioDevice::StartOutputCapture(const FString& Filename) +{ + if ( m_bSoundEngineInitialized ) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + SoundEngine->StartOutputCapture(TCHAR_TO_AK(*Filename)); + } +} + +void FAkAudioDevice::StopOutputCapture() +{ + if ( m_bSoundEngineInitialized ) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + SoundEngine->StopOutputCapture(); + } +} + +void FAkAudioDevice::StartProfilerCapture(const FString& Filename) +{ + if ( m_bSoundEngineInitialized ) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + SoundEngine->StartProfilerCapture(TCHAR_TO_AK(*Filename)); + } +} + +void FAkAudioDevice::AddOutputCaptureMarker(const FString& MarkerText) +{ + if ( m_bSoundEngineInitialized ) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + SoundEngine->AddOutputCaptureMarker(TCHAR_TO_ANSI(*MarkerText)); + } +} + +void FAkAudioDevice::StopProfilerCapture() +{ + if ( m_bSoundEngineInitialized ) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + SoundEngine->StopProfilerCapture(); + } +} + +AKRESULT FAkAudioDevice::RegisterPluginDLL(const FString& in_DllName, const FString& in_DllPath) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; + + AkOSChar* szPath = nullptr; + + if (!in_DllPath.IsEmpty()) + { + auto Length = in_DllPath.Len() + 1; + szPath = new AkOSChar[Length]; + AKPLATFORM::SafeStrCpy(szPath, TCHAR_TO_AK(*in_DllPath), Length); + } + + AKRESULT eResult = SoundEngine->RegisterPluginDLL(TCHAR_TO_AK(*in_DllName), szPath); + delete[] szPath; + return eResult; +} +// end + +FAkAudioDevice::SetCurrentAudioCultureAsyncTask::SetCurrentAudioCultureAsyncTask(FWwiseLanguageCookedData NewLanguage, FSetCurrentAudioCultureAction* LatentAction) + : Language(NewLanguage) + , SetAudioCultureLatentAction(LatentAction) +{ + CompletionActionType = CompletionType::LatentAction; + LatentActionValidityToken = MakeShared(); + SetAudioCultureLatentAction->ValidityToken = LatentActionValidityToken; +} + +FAkAudioDevice::SetCurrentAudioCultureAsyncTask::SetCurrentAudioCultureAsyncTask(FWwiseLanguageCookedData NewLanguage, const FOnSetCurrentAudioCultureCompleted& CompletedCallback) + : Language(NewLanguage) + , SetAudioCultureCompletedCallback(CompletedCallback) +{ + CompletionActionType = CompletionType::Callback; +} + +bool FAkAudioDevice::SetCurrentAudioCultureAsyncTask::Start() +{ + UE_LOG(LogAkAudio, Verbose, TEXT("Switching Wwise language to '%s'"), *Language.GetLanguageName().ToString()); + + auto* StreamMgr = IWwiseStreamMgrAPI::Get(); + if (UNLIKELY(!StreamMgr)) + { + return false; + } + StreamMgr->SetCurrentLanguage(TCHAR_TO_AK(*Language.GetLanguageName().ToString())); + + AsyncTask(ENamedThreads::AnyNormalThreadNormalTask, [this]() + { + auto* ResourceLoader = FWwiseResourceLoader::Get(); + if (UNLIKELY(!ResourceLoader)) + { + UE_LOG(LogAkAudio, Error, TEXT("SetCurrentAudioCultureAsync: Could not get resource loader, cannot change language.")); + Succeeded = false; + IsDone = true; + return; + } + + ResourceLoader->SetLanguage(Language, EWwiseReloadLanguage::Immediate); + IsDone = true; + Succeeded = true; + }); + + return true; +} + +void FAkAudioDevice::SetCurrentAudioCultureAsyncTask::Update() +{ + if (IsDone) + { + switch (CompletionActionType) + { + case CompletionType::Callback: + SetAudioCultureCompletedCallback.ExecuteIfBound(Succeeded); + break; + case CompletionType::LatentAction: + if (LatentActionValidityToken->bValid && SetAudioCultureLatentAction) + { + SetAudioCultureLatentAction->ActionDone = true; + } + break; + } + } +} + +void FAkAudioDevice::AddPlayingID(uint32 EventID, uint32 PlayingID, EAkAudioContext AudioContext) +{ + FScopeLock Lock(&EventToPlayingIDMapCriticalSection); + auto& PlayingIDArray = EventToPlayingIDMap.FindOrAdd(EventID); + PlayingIDArray.Add(PlayingID); + PlayingIDToAudioContextMap.Add(PlayingID, AudioContext); +} + +bool FAkAudioDevice::IsPlayingIDActive(uint32 EventID, uint32 PlayingID) +{ + FScopeLock Lock(&EventToPlayingIDMapCriticalSection); + auto* PlayingIDArray = EventToPlayingIDMap.Find(EventID); + if (PlayingIDArray && PlayingIDArray->Contains(PlayingID)) + { + return true; + } + + return false; +} + +bool FAkAudioDevice::IsEventIDActive(uint32 EventID) +{ + FScopeLock Lock(&EventToPlayingIDMapCriticalSection); + return EventToPlayingIDMap.Contains(EventID); +} + +void FAkAudioDevice::RemovePlayingID(uint32 EventID, uint32 PlayingID) +{ + FScopeLock Lock(&EventToPlayingIDMapCriticalSection); + auto* PlayingIDArray = EventToPlayingIDMap.Find(EventID); + if (PlayingIDArray) + { + PlayingIDArray->Remove(PlayingID); + + if (PlayingIDArray->Num() == 0) + { + EventToPlayingIDMap.Remove(EventID); + PlayingIDToAudioContextMap.Remove(PlayingID); + } + } +} + +void FAkAudioDevice::StopEventID(uint32 EventID) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + FScopeLock Lock(&EventToPlayingIDMapCriticalSection); + + auto* PlayingIDs = EventToPlayingIDMap.Find(EventID); + if (PlayingIDs) + { + for (auto pID : *PlayingIDs) + { + StopPlayingID(pID); + } + SoundEngine->RenderAudio(); + } +} + +FOnSwitchValueLoaded& FAkAudioDevice::GetOnSwitchValueLoaded(uint32 SwitchID) +{ + return OnSwitchValueLoadedMap.FindOrAdd(SwitchID); +} + +void FAkAudioDevice::BroadcastOnSwitchValueLoaded(UAkGroupValue* GroupValue) +{ + FOnSwitchValueLoaded* EventToBroadcast = OnSwitchValueLoadedMap.Find(GroupValue->GetShortID()); + if (EventToBroadcast) + { + EventToBroadcast->Broadcast(GroupValue); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAudioEvent.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAudioEvent.cpp new file mode 100644 index 0000000..9500301 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAudioEvent.cpp @@ -0,0 +1,856 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkEvent.cpp: +=============================================================================*/ + +#include "AkAudioEvent.h" +#include "AkAudioBank.h" +#include "AkAudioDevice.h" +#include "AkComponent.h" +#include "AkComponentCallbackManager.h" +#include "AkGameObject.h" +#include "AkRoomComponent.h" +#include "AkUnrealHelper.h" +#include "Wwise/WwiseExternalSourceManager.h" +#include "Wwise/WwiseResourceLoader.h" +#include "Wwise/API/WwiseSoundEngineAPI.h" +#include "Wwise/Stats/Global.h" +#include "Wwise/Stats/AkAudio.h" + +#include + +#if WITH_EDITORONLY_DATA +#include "Wwise/WwiseProjectDatabase.h" +#include "Wwise/WwiseResourceCooker.h" +#endif + +int32 UAkAudioEvent::PostOnActor(const AActor* Actor, const FOnAkPostEventCallback& Delegate, const int32 CallbackMask, + const bool bStopWhenAttachedObjectDestroyed) +{ + return PostOnActor(Actor, &Delegate, nullptr, nullptr, + AkCallbackTypeHelpers::GetCallbackMaskFromBlueprintMask(CallbackMask), nullptr, bStopWhenAttachedObjectDestroyed); +} + +int32 UAkAudioEvent::PostOnComponent(UAkComponent* Component, const FOnAkPostEventCallback& Delegate, + const int32 CallbackMask, const bool bStopWhenAttachedObjectDestroyed) +{ + return PostOnComponent(Component, &Delegate, nullptr, nullptr, + AkCallbackTypeHelpers::GetCallbackMaskFromBlueprintMask(CallbackMask), nullptr, bStopWhenAttachedObjectDestroyed); +} + +int32 UAkAudioEvent::PostOnGameObject(UAkGameObject* GameObject, const FOnAkPostEventCallback& Delegate, const int32 CallbackMask) +{ + return PostOnGameObject(GameObject, &Delegate, nullptr, nullptr, + AkCallbackTypeHelpers::GetCallbackMaskFromBlueprintMask(CallbackMask), nullptr); +} + +int32 UAkAudioEvent::PostOnActorAndWait(const AActor* Actor, const bool bStopWhenAttachedObjectDestroyed, + const FLatentActionInfo LatentActionInfo) +{ + SCOPED_AKAUDIO_EVENT(TEXT("UAkAudioEvent::PostOnActorAndWait")); + if (UNLIKELY(!IsValid(Actor) || Actor->IsActorBeingDestroyed())) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to post latent AkAudioEvent '%s' with an actor that's not valid. The actor needs to be valid in order to wait for it."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + + auto* World = Actor->GetWorld(); + if (UNLIKELY(!World)) + { + UE_LOG(LogAkAudio, Log, TEXT("Failed to post latent AkAudioEvent '%s' with an actor '%s' world that's not valid."), *GetName(), *Actor->GetName()); + return AK_INVALID_PLAYING_ID; + } + + FLatentActionManager& LatentActionManager = World->GetLatentActionManager(); + FWaitEndOfEventAction* LatentAction = new FWaitEndOfEventAction(LatentActionInfo); + LatentActionManager.AddNewAction(LatentActionInfo.CallbackTarget, LatentActionInfo.UUID, LatentAction); + + if (UNLIKELY(!World->AllowAudioPlayback())) + { + UE_LOG(LogAkAudio, Verbose, TEXT("Failed to post AkAudioEvent '%s' with an actor '%s' world '%s' that doesn't allow audio playback."), *GetName(), *Actor->GetName(), *World->GetName()); + LatentAction->EventFinished = true; + return AK_INVALID_PLAYING_ID; + } + + const auto PlayingID = PostOnActor(Actor, nullptr, nullptr, nullptr, (AkCallbackType)0, LatentAction, bStopWhenAttachedObjectDestroyed); + if (UNLIKELY(PlayingID == AK_INVALID_PLAYING_ID)) + { + LatentAction->EventFinished = true; + } + return PlayingID; +} + +int32 UAkAudioEvent::PostOnComponentAndWait(UAkComponent* Component, const bool bStopWhenAttachedObjectDestroyed, + const FLatentActionInfo LatentActionInfo) +{ + SCOPED_AKAUDIO_EVENT(TEXT("UAkAudioEvent::PostOnComponentAndWait")); + if (UNLIKELY(!IsValid(Component))) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to post latent AkAudioEvent '%s' with a component that's not valid. The component needs to be valid in order to wait for it."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + + auto* World = Component->GetWorld(); + if (UNLIKELY(!World)) + { + UE_LOG(LogAkAudio, Log, TEXT("Failed to post latent AkAudioEvent '%s' with a component '%s' world that's not valid."), *GetName(), *Component->GetName()); + return AK_INVALID_PLAYING_ID; + } + + FLatentActionManager& LatentActionManager = World->GetLatentActionManager(); + FWaitEndOfEventAction* LatentAction = new FWaitEndOfEventAction(LatentActionInfo); + LatentActionManager.AddNewAction(LatentActionInfo.CallbackTarget, LatentActionInfo.UUID, LatentAction); + + if (UNLIKELY(!World->AllowAudioPlayback())) + { + UE_LOG(LogAkAudio, Verbose, TEXT("Failed to post AkAudioEvent '%s' with a component '%s' world '%s' that doesn't allow audio playback."), *GetName(), *Component->GetName(), *World->GetName()); + LatentAction->EventFinished = true; + return AK_INVALID_PLAYING_ID; + } + + const auto PlayingID = PostOnComponent(Component, nullptr, nullptr, nullptr, (AkCallbackType)0, LatentAction, bStopWhenAttachedObjectDestroyed); + if (UNLIKELY(PlayingID == AK_INVALID_PLAYING_ID)) + { + LatentAction->EventFinished = true; + } + return PlayingID; +} + +int32 UAkAudioEvent::PostOnGameObjectAndWait(UAkGameObject* GameObject, const FLatentActionInfo LatentActionInfo) +{ + SCOPED_AKAUDIO_EVENT(TEXT("UAkAudioEvent::PostOnGameObjectAndWait")); + if (UNLIKELY(!IsValid(GameObject))) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to post latent AkAudioEvent '%s' with a game object that's not valid. The gane object needs to be valid in order to wait for it."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + + auto* World = GameObject->GetWorld(); + if (UNLIKELY(!World)) + { + UE_LOG(LogAkAudio, Log, TEXT("Failed to post latent AkAudioEvent '%s' with a game object '%s' world that's not valid."), *GetName(), *GameObject->GetName()); + return AK_INVALID_PLAYING_ID; + } + + FLatentActionManager& LatentActionManager = World->GetLatentActionManager(); + FWaitEndOfEventAction* LatentAction = new FWaitEndOfEventAction(LatentActionInfo); + LatentActionManager.AddNewAction(LatentActionInfo.CallbackTarget, LatentActionInfo.UUID, LatentAction); + + if (UNLIKELY(!World->AllowAudioPlayback())) + { + UE_LOG(LogAkAudio, Verbose, TEXT("Failed to post AkAudioEvent '%s' with a game object '%s' world '%s' that doesn't allow audio playback."), *GetName(), *GameObject->GetName(), *World->GetName()); + LatentAction->EventFinished = true; + return AK_INVALID_PLAYING_ID; + } + + const auto PlayingID = PostOnGameObject(GameObject, nullptr, nullptr, nullptr, (AkCallbackType)0, LatentAction); + if (UNLIKELY(PlayingID == AK_INVALID_PLAYING_ID)) + { + LatentAction->EventFinished = true; + } + return PlayingID; +} + +int32 UAkAudioEvent::PostAtLocation(const FVector Location, const FRotator Orientation, const FOnAkPostEventCallback& Callback, + const int32 CallbackMask, const UObject* WorldContextObject) +{ + SCOPED_AKAUDIO_EVENT(TEXT("UAkAudioEvent::PostAtLocation")); + if (UNLIKELY(!IsValid(WorldContextObject))) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to post AkAudioEvent '%s' at location with a World Context Object that's not valid."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + + const auto* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull); + return PostAtLocation(Location, Orientation, World, &Callback, nullptr, nullptr, + AkCallbackTypeHelpers::GetCallbackMaskFromBlueprintMask(CallbackMask), nullptr); +} + +int32 UAkAudioEvent::ExecuteAction(const AkActionOnEventType ActionType, const AActor* Actor, const int32 PlayingID, + const int32 TransitionDuration, const EAkCurveInterpolation FadeCurve) +{ + SCOPED_AKAUDIO_EVENT(TEXT("UAkAudioEvent::ExecuteAction")); + const auto* AudioDevice = FAkAudioDevice::Get(); + if (UNLIKELY(!AudioDevice)) + { + UE_LOG(LogAkAudio, Verbose, TEXT("Failed to execute an action on AkAudioEvent '%s' without an Audio Device."), *GetName()); + return AK_NotInitialized; + } + + if (UNLIKELY(!AudioDevice->IsInitialized())) + { + UE_LOG(LogAkAudio, Verbose, TEXT("Failed to execute an action on AkAudioEvent '%s' with the Sound Engine uninitialized."), *GetName()); + return AK_NotInitialized; + } + + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) + { + UE_LOG(LogAkAudio, Warning, TEXT("Failed to execute an action on AkAudioEvent '%s' without a Sound Engine."), *GetName()); + return AK_NotInitialized; + } + + if (!Actor) + { + return SoundEngine->ExecuteActionOnEvent(GetShortID(), + static_cast(ActionType), + AK_INVALID_GAME_OBJECT, + TransitionDuration, + static_cast(FadeCurve), + PlayingID + ); + } + + if (UNLIKELY(Actor->IsActorBeingDestroyed() || !IsValid(Actor))) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to execute on AkAudioEvent '%s' with an actor that's not valid."), *GetName()); + return AK_InvalidParameter; + } + + UAkComponent* Component = AudioDevice->GetAkComponent(Actor->GetRootComponent(), FName(), nullptr, EAttachLocation::KeepRelativeOffset); + if (UNLIKELY(!Component)) + { + UE_LOG(LogAkAudio, Warning, TEXT("Failed to execute on AkAudioEvent '%s' with an actor that doesn't have an AkComponent on Root."), *GetName()); + return AK_InvalidParameter; + } + + return SoundEngine->ExecuteActionOnEvent(GetShortID(), + static_cast(ActionType), + Component->GetAkGameObjectID(), + TransitionDuration, + static_cast(FadeCurve), + PlayingID + ); +} + +AkPlayingID UAkAudioEvent::PostOnActor(const AActor* Actor, const FOnAkPostEventCallback* Delegate, + const AkCallbackFunc Callback, void* Cookie, const AkCallbackType CallbackMask, FWaitEndOfEventAction* LatentAction, + const bool bStopWhenAttachedObjectDestroyed, const EAkAudioContext AudioContext) +{ + SCOPED_AKAUDIO_EVENT(TEXT("UAkAudioEvent::PostOnActor")); + const auto* AudioDevice = FAkAudioDevice::Get(); + if (UNLIKELY(!AudioDevice)) + { + UE_LOG(LogAkAudio, Verbose, TEXT("Failed to post AkAudioEvent '%s' on actor without an Audio Device."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + + if (UNLIKELY(!AudioDevice->IsInitialized())) + { + UE_LOG(LogAkAudio, Verbose, TEXT("Failed to post AkAudioEvent '%s' with the Sound Engine uninitialized."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + + if (!Actor) + { + return PostAmbient(Delegate, Callback, Cookie, CallbackMask, LatentAction, AudioContext); + } + + if (UNLIKELY(Actor->IsActorBeingDestroyed() || !IsValid(Actor))) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to post AkAudioEvent '%s' with an actor that's not valid."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + + const auto* World = Actor->GetWorld(); + if (UNLIKELY(!World)) + { + UE_LOG(LogAkAudio, Log, TEXT("Failed to post AkAudioEvent '%s' with an actor '%s' world that's not valid."), *GetName(), *Actor->GetName()); + return AK_INVALID_PLAYING_ID; + } + + if (UNLIKELY(!World->AllowAudioPlayback())) + { + UE_LOG(LogAkAudio, Verbose, TEXT("Failed to post AkAudioEvent '%s' with an actor '%s' world '%s' that doesn't allow audio playback."), *GetName(), *Actor->GetName(), *World->GetName()); + return AK_INVALID_PLAYING_ID; + } + + UAkComponent* Component = AudioDevice->GetAkComponent(Actor->GetRootComponent(), FName(), nullptr, EAttachLocation::KeepRelativeOffset); + if (UNLIKELY(!Component)) + { + UE_LOG(LogAkAudio, Warning, TEXT("Failed to post AkAudioEvent '%s' with an actor that doesn't have an AkComponent on Root."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + + return PostOnComponent(Component, Delegate, Callback, Cookie, CallbackMask, LatentAction, bStopWhenAttachedObjectDestroyed, AudioContext); +} + +AkPlayingID UAkAudioEvent::PostOnComponent(UAkComponent* Component, const FOnAkPostEventCallback* Delegate, + const AkCallbackFunc Callback, void* Cookie, const AkCallbackType CallbackMask, FWaitEndOfEventAction* LatentAction, + const bool bStopWhenAttachedObjectDestroyed, const EAkAudioContext AudioContext) +{ + SCOPED_AKAUDIO_EVENT(TEXT("UAkAudioEvent::PostOnComponent")); + if (UNLIKELY(!Component)) + { + UE_LOG(LogAkAudio, Verbose, TEXT("Failed to post AkAudioEvent '%s' with null AkComponent."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + + if (UNLIKELY(!IsValid(Component))) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to post AkAudioEvent '%s' with an AkComponent that's not valid."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + + if (UNLIKELY(Component->StopWhenOwnerDestroyed != bStopWhenAttachedObjectDestroyed)) + { + UE_LOG(LogWwiseHints, VeryVerbose, TEXT("Updating AkComponent(%s) StopWhenOwnerDestroyed (%s->%s) because of AkAudioEvent '%s'. You should not modify the StopWhenOwnerDestroyed through a PostEvent unless you know what you are doing."), + *Component->GetName(), + Component->StopWhenOwnerDestroyed ? TEXT("true") : TEXT("false"), + bStopWhenAttachedObjectDestroyed ? TEXT("true") : TEXT("false"), + *GetName()); + Component->StopWhenOwnerDestroyed = bStopWhenAttachedObjectDestroyed; + } + return PostOnGameObject(Component, Delegate, Callback, Cookie, CallbackMask, LatentAction, AudioContext); +} + +AkPlayingID UAkAudioEvent::PostAtLocation(const FVector& Location, const FRotator& Orientation, const UWorld* World, + const FOnAkPostEventCallback* Delegate, const AkCallbackFunc Callback, void* Cookie, const AkCallbackType CallbackMask, + FWaitEndOfEventAction* LatentAction, const EAkAudioContext AudioContext) +{ + SCOPED_AKAUDIO_EVENT(TEXT("UAkAudioEvent::PostAtLocation")); + auto* AudioDevice = FAkAudioDevice::Get(); + if (UNLIKELY(!AudioDevice)) + { + UE_LOG(LogAkAudio, Verbose, TEXT("Failed to post AkAudioEvent '%s' at a location without an Audio Device."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + + if (UNLIKELY(!AudioDevice->IsInitialized())) + { + UE_LOG(LogAkAudio, Verbose, TEXT("Failed to post AkAudioEvent '%s' at a location with the Sound Engine uninitialized."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) + { + UE_LOG(LogAkAudio, Warning, TEXT("Failed to post AkAudioEvent '%s' at a location without a Sound Engine."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + + if (UNLIKELY(!World)) + { + UE_LOG(LogAkAudio, Log, TEXT("Failed to post AkAudioEvent '%s' at a location without a world world."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + + if (UNLIKELY(!World->AllowAudioPlayback())) + { + UE_LOG(LogAkAudio, Verbose, TEXT("Failed to post AkAudioEvent '%s' with a world '%s' that doesn't allow audio playback."), *GetName(), *World->GetName()); + return AK_INVALID_PLAYING_ID; + } + + const AkGameObjectID ObjectID = (AkGameObjectID)this; + AKRESULT Result = AudioDevice->RegisterGameObject(ObjectID, GetName()); + if (UNLIKELY(Result != AK_Success)) + { + return AK_INVALID_PLAYING_ID; + } + + TArray AkReverbVolumes; + AudioDevice->GetAuxSendValuesAtLocation(Location, AkReverbVolumes, World); + Result = SoundEngine->SetGameObjectAuxSendValues(ObjectID, AkReverbVolumes.GetData(), AkReverbVolumes.Num()); + UE_CLOG(UNLIKELY(Result != AK_Success), LogAkAudio, Log, TEXT("Could not Set AuxSend Values while PostOnLocation for AkAudioEvent '%s' (ObjId: %" PRIu64 "): (%" PRIu32 ") %s."), *GetName(), ObjectID, Result, AkUnrealHelper::GetResultString(Result)); + + AkRoomID RoomID = 0; + auto& RoomIndex = AudioDevice->GetRoomIndex(); + TArray AkRooms = RoomIndex.Query(Location, World); + if (LIKELY(AkRooms.Num() > 0)) + { + UE_CLOG(AkRooms.Num() > 1, LogAkAudio, Verbose, TEXT("There are %d rooms while PostOnLocation for AkAudioEvent '%s' (ObjId: %" PRIu64 "). Picking the first one."), (int)AkRooms.Num(), *GetName(), ObjectID); + RoomID = AkRooms[0]->GetRoomID(); + AudioDevice->SetInSpatialAudioRoom(ObjectID, RoomID); + } + else + { + UE_LOG(LogAkAudio, Verbose, TEXT("No Spatial Audio Room while PostOnLocation AkAudioEvent '%s' (ObjId: %" PRIu64 ")"), *GetName(), ObjectID); + } + + AkSoundPosition SoundPosition; + FQuat OrientationQuat(Orientation); + AudioDevice->FVectorsToAKWorldTransform(Location, OrientationQuat.GetForwardVector(), OrientationQuat.GetUpVector(), SoundPosition); + Result = SoundEngine->SetPosition(ObjectID, SoundPosition); + UE_CLOG(UNLIKELY(Result != AK_Success), LogAkAudio, Log, TEXT("Could not Set Position for AkAudioEvent '%s' (ObjId: %" PRIu64 "): (%" PRIu32 ") %s."), *GetName(), ObjectID, Result, AkUnrealHelper::GetResultString(Result)); + + const auto PlayingID = PostOnGameObjectID(ObjectID, Delegate, Callback, Cookie, CallbackMask, LatentAction, AudioContext); + + Result = SoundEngine->UnregisterGameObj( ObjectID ); + UE_CLOG(UNLIKELY(Result != AK_Success), LogAkAudio, Log, TEXT("Could not Unregister GameObject after PostOnLocation for AkAudioEvent '%s' (ObjId: %" PRIu64 "): (%" PRIu32 ") %s."), *GetName(), ObjectID, Result, AkUnrealHelper::GetResultString(Result)); + + return PlayingID; +} + +AkPlayingID UAkAudioEvent::PostAmbient(const FOnAkPostEventCallback* Delegate, AkCallbackFunc Callback, void* Cookie, + const AkCallbackType CallbackMask, FWaitEndOfEventAction* LatentAction, const EAkAudioContext AudioContext) +{ + SCOPED_AKAUDIO_EVENT(TEXT("UAkAudioEvent::PostAmbient")); + return PostOnGameObjectID(DUMMY_GAMEOBJ, Delegate, Callback, Cookie, CallbackMask, LatentAction, AudioContext); +} + +AkPlayingID UAkAudioEvent::PostOnGameObject(UAkGameObject* GameObject, const FOnAkPostEventCallback* Delegate, + const AkCallbackFunc Callback, void* Cookie, const AkCallbackType CallbackMask, FWaitEndOfEventAction* LatentAction, + const EAkAudioContext AudioContext) +{ + SCOPED_AKAUDIO_EVENT(TEXT("UAkAudioEvent::PostOnGameObject")); + if (UNLIKELY(!GameObject)) + { + UE_LOG(LogAkAudio, VeryVerbose, TEXT("Failed to post AkAudioEvent without a GameObject.")) + return AK_INVALID_PLAYING_ID; + } + + UE_CLOG(LIKELY(GameObject->AkAudioEvent) && UNLIKELY(GameObject->AkAudioEvent != this), + LogWwiseHints, VeryVerbose, TEXT("Posting AkAudioEvent(%s) that is different than the one associated (%s) in the GameObject(%s). You should not have an associated AkAudioEvent if you want to play different Events on the same GameObject."), + *GetName(), + *GameObject->AkAudioEvent->GetName(), + *GameObject->GetName()); + + GameObject->UpdateObstructionAndOcclusion(); + const auto GameObjectID = GameObject->GetAkGameObjectID(); + const AkPlayingID PlayingID = PostOnGameObjectID(GameObjectID, Delegate, Callback, Cookie, CallbackMask, LatentAction, AudioContext); + if (PlayingID != AK_INVALID_PLAYING_ID) + { + GameObject->EventPosted(); + } + return PlayingID; +} + +AkPlayingID UAkAudioEvent::PostOnGameObjectID(const AkGameObjectID GameObjectID, const FOnAkPostEventCallback* Delegate, + const AkCallbackFunc Callback, void* Cookie, const AkCallbackType CallbackMask, FWaitEndOfEventAction* LatentAction, + const EAkAudioContext AudioContext) +{ + SCOPED_AKAUDIO_EVENT(TEXT("UAkAudioEvent::PostOnGameObjectID")); + if (Delegate) + { + UE_CLOG(UNLIKELY(Callback), LogAkAudio, Error, TEXT("Both Delegate and Callback used for AkAudioEvent('%s').Post(GameObjectID=%" PRIu64 "). Ignoring AkCallback."), *GetName(), GameObjectID); + UE_CLOG(UNLIKELY(LatentAction), LogAkAudio, Error, TEXT("Both Delegate and LatentAction used for AkAudioEvent('%s').Post(GameObjectID=%" PRIu64 "). Ignoring LatentAction."), *GetName(), GameObjectID); + UE_CLOG(UNLIKELY(Cookie), LogAkAudio, Error, TEXT("Blueprint Delegate ignores Cookie for AkAudioEvent('%s').Post(GameObjectID=%" PRIu64 ")."), *GetName(), GameObjectID); + + const auto Result = PostEvent(GameObjectID, + [this, Delegate, CallbackMask](AkGameObjectID GameObjectID) + { + auto* AudioDevice = FAkAudioDevice::Get(); + auto* CallbackManager = AudioDevice->GetCallbackManager(); + return CallbackManager->CreateCallbackPackage(*Delegate, CallbackMask, GameObjectID, true); + }, + AudioContext); + return Result; + } + else if (LatentAction) + { + UE_CLOG(UNLIKELY(Callback), LogAkAudio, Error, TEXT("Both LatentAction and Callback used for AkAudioEvent('%s').Post(GameObjectID=%" PRIu64 "). Ignoring AkCallback."), *GetName(), GameObjectID); + UE_CLOG(UNLIKELY(CallbackMask), LogAkAudio, Error, TEXT("LatentAction ignores CallbackMask for AkAudioEvent('%s').Post(GameObjectID=%" PRIu64 ")."), *GetName(), GameObjectID); + UE_CLOG(UNLIKELY(Cookie), LogAkAudio, Error, TEXT("LatentAction ignores Cookie for AkAudioEvent('%s').Post(GameObjectID=%" PRIu64 ")."), *GetName(), GameObjectID); + + const auto Result = PostEvent(GameObjectID, + [this, LatentAction](AkGameObjectID GameObjectID) + { + auto* AudioDevice = FAkAudioDevice::Get(); + auto* CallbackManager = AudioDevice->GetCallbackManager(); + return CallbackManager->CreateCallbackPackage(LatentAction, GameObjectID, true); + }, + AudioContext); + return Result; + } + else + { + UE_CLOG(UNLIKELY(!Callback && CallbackMask), LogAkAudio, Error, TEXT("Callback is not set, but there's a CallbackMask for AkAudioEvent('%s').Post(GameObjectID=%" PRIu64 ")."), *GetName(), GameObjectID); + UE_CLOG(UNLIKELY(!Callback && Cookie), LogAkAudio, Error, TEXT("Callback is not set, but there's a Cookie for AkAudioEvent('%s').Post(GameObjectID=%" PRIu64 ")."), *GetName(), GameObjectID); + + const auto Result = PostEvent(GameObjectID, + [this, Callback, Cookie, CallbackMask](AkGameObjectID GameObjectID) + { + auto* AudioDevice = FAkAudioDevice::Get(); + auto* CallbackManager = AudioDevice->GetCallbackManager(); + return CallbackManager->CreateCallbackPackage(Callback, Cookie, CallbackMask, GameObjectID, true); + }, + AudioContext); + return Result; + } +} + +template +AkPlayingID UAkAudioEvent::PostEvent(const AkGameObjectID GameObjectID, FCreateCallbackPackage&& CreateCallbackPackage, + const EAkAudioContext AudioContext) +{ + SCOPED_AKAUDIO_EVENT_2(TEXT("UAkAudioEvent::PostEvent")); + auto* AudioDevice = FAkAudioDevice::Get(); + if (UNLIKELY(!AudioDevice)) + { + UE_LOG(LogAkAudio, Verbose, TEXT("Failed to post AkAudioEvent '%s' without an Audio Device."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + + if (UNLIKELY(!AudioDevice->IsInitialized())) + { + UE_LOG(LogAkAudio, Verbose, TEXT("Failed to post AkAudioEvent '%s' with the Sound Engine uninitialized."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + + if (!IsLoaded()) + { + UE_LOG(LogAkAudio, Warning, TEXT("Failed to post AkAudioEvent: Data for '%s' wasn't found. Make sure the GeneratedSoundBanks folder (%s) exists and is properly set in the project settings."), *GetName(), *AkUnrealHelper::GetSoundBankDirectory()); + return AK_INVALID_PLAYING_ID; + } + + if (!IsDataFullyLoaded()) + { + UE_LOG(LogAkAudio, Warning, TEXT("Failed to post AkAudioEvent: Not all localization data for '%s' are loaded. Consider using PostEventAsync()."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + + auto* CallbackManager = AudioDevice->GetCallbackManager(); + if (UNLIKELY(!CallbackManager)) + { + UE_LOG(LogAkAudio, Warning, TEXT("Failed to post AkAudioEvent '%s' without a Callback Manager."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) + { + UE_LOG(LogAkAudio, Warning, TEXT("Failed to post AkAudioEvent '%s' without a Sound Engine."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + + auto* ExternalSourceManager = IWwiseExternalSourceManager::Get(); + if (UNLIKELY(!ExternalSourceManager)) + { + UE_LOG(LogAkAudio, Warning, TEXT("Failed to post AkAudioEvent '%s' without the External Source Manager."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + + auto CallbackPackage = CreateCallbackPackage(GameObjectID); + if (UNLIKELY(!CallbackPackage)) + { + UE_LOG(LogAkAudio, Warning, TEXT("Failed to post AkAudioEvent '%s': Could not create CallbackPackage."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + + TArray ExternalSources; + const TArray ExternalSourceMedia = ExternalSourceManager->PrepareExternalSourceInfos(ExternalSources, GetAllExternalSources()); + + const auto PlayingID = SoundEngine->PostEvent( + GetShortID() + , GameObjectID + , CallbackPackage->uUserFlags | AK_EndOfEvent + , &FAkComponentCallbackManager::AkComponentCallback + , CallbackPackage + , ExternalSources.Num() + , ExternalSources.GetData() + ); + + ExternalSourceManager->BindPlayingIdToExternalSources(PlayingID, ExternalSourceMedia); + + if (UNLIKELY(PlayingID == AK_INVALID_PLAYING_ID)) + { + UE_LOG(LogAkAudio, Log, TEXT("Failed to post AkAudioEvent '%s'."), *GetName()); + CallbackManager->RemoveCallbackPackage(CallbackPackage, GameObjectID); + return AK_INVALID_PLAYING_ID; + } + + AudioDevice->AddPlayingID(GetShortID(), PlayingID, AudioContext); + + UE_LOG(LogAkAudio, VeryVerbose, TEXT("Posted AkAudioEvent '%s' as PlayingId %" PRIu32 "."), *GetName(), PlayingID); + return PlayingID; +} + +void UAkAudioEvent::Serialize(FArchive& Ar) +{ + Super::Serialize(Ar); + + if (HasAnyFlags(RF_ClassDefaultObject)) + { + return; + } + +#if !UE_SERVER +#if WITH_EDITORONLY_DATA + if (Ar.IsCooking() && Ar.IsSaving() && !Ar.CookingTarget()->IsServerOnly()) + { + FWwiseLocalizedEventCookedData CookedDataToArchive; + if (auto* ResourceCooker = FWwiseResourceCooker::GetForArchive(Ar)) + { + ResourceCooker->PrepareCookedData(CookedDataToArchive, GetValidatedInfo(EventInfo)); + FillMetadata(ResourceCooker->GetProjectDatabase()); + } + CookedDataToArchive.Serialize(Ar); + Ar << MaximumDuration; + Ar << MinimumDuration; + Ar << IsInfinite; + Ar << MaxAttenuationRadius; + } +#else + EventCookedData.Serialize(Ar); + Ar << MaximumDuration; + Ar << MinimumDuration; + Ar << IsInfinite; + Ar << MaxAttenuationRadius; +#endif +#endif +} + +#if WITH_EDITORONLY_DATA +void UAkAudioEvent::FillInfo() +{ + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkAudioEvent::FillInfo: ResourceCooker not initialized")); + return; + } + + auto ProjectDatabase = ResourceCooker->GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkAudioEvent::FillInfo: ProjectDatabase not initialized")); + return; + } + + const TSet EventRef = FWwiseDataStructureScopeLock(*ProjectDatabase).GetEvent(GetValidatedInfo(EventInfo)); + if (UNLIKELY(EventRef.Num() == 0)) + { + UE_LOG(LogAkAudio, Log, TEXT("UAkAudioEvent::FillInfo (%s): Cannot fill Asset Info - Event is not loaded"), *GetName()); + return; + } + + const FWwiseMetadataEvent* EventMetadata = EventRef.Array()[0].GetEvent(); + if (EventMetadata->Name.IsNone() || !EventMetadata->GUID.IsValid() || EventMetadata->Id == AK_INVALID_UNIQUE_ID) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkAudioEvent::FillInfo: Valid object not found in Project Database")); + return; + } + EventInfo.WwiseGuid = EventMetadata->GUID; + EventInfo.WwiseShortId = EventMetadata->Id; + EventInfo.WwiseName = EventMetadata->Name; +} + +void UAkAudioEvent::FillMetadata(FWwiseProjectDatabase* ProjectDatabase) +{ + Super::FillMetadata(ProjectDatabase); + + const TSet EventRef = FWwiseDataStructureScopeLock(*ProjectDatabase).GetEvent(GetValidatedInfo(EventInfo)); + if (UNLIKELY(EventRef.Num() == 0)) + { + UE_LOG(LogAkAudio, Log, TEXT("UAkAudioEvent::FillMetadata (%s): Cannot fill Metadata - Event not found in Project Database"), *GetName()); + return; + } + + const FWwiseMetadataEvent* EventMetadata = EventRef.Array()[0].GetEvent(); + if (EventMetadata->Name.IsNone() || !EventMetadata->GUID.IsValid() || EventMetadata->Id == AK_INVALID_UNIQUE_ID) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkAudioEvent::FillMetadata: Valid object not found in Project Database")); + return; + } + + MaximumDuration = EventMetadata->DurationMax; + MinimumDuration = EventMetadata->DurationMin; + IsInfinite = EventMetadata->DurationType == EWwiseMetadataEventDurationType::Infinite; + MaxAttenuationRadius = EventMetadata->MaxAttenuation; +} + +#endif + +void UAkAudioEvent::LoadEventData() +{ + SCOPED_AKAUDIO_EVENT_2(TEXT("LoadEventData")); + auto* ResourceLoader = FWwiseResourceLoader::Get(); + if (UNLIKELY(!ResourceLoader)) + { + return; + } + + if (LoadedEvent) + { + UnloadEventData(false); + } + +#if WITH_EDITORONLY_DATA + if (IWwiseProjectDatabaseModule::IsInACookingCommandlet()) + { + return; + } + auto* ProjectDatabase = FWwiseProjectDatabase::Get(); + if (!ProjectDatabase || !ProjectDatabase->IsProjectDatabaseParsed()) + { + UE_LOG(LogAkAudio, VeryVerbose, TEXT("UAkAudioEvent::LoadEventData: Not loading '%s' because project database is not parsed."), *GetName()) + return; + } + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + return; + } + if (UNLIKELY(!ResourceCooker->PrepareCookedData(EventCookedData, GetValidatedInfo(EventInfo)))) + { + return; + } + FillMetadata(ResourceCooker->GetProjectDatabase()); +#endif + + UE_LOG(LogAkAudio, Verbose, TEXT("%s - LoadEventData"), *GetName()); + LoadedEvent = ResourceLoader->LoadEvent(EventCookedData); +} + +#if WITH_EDITOR +void UAkAudioEvent::LoadEventDataForContentBrowserPreview() +{ + if(!bAutoLoad) + { + OnBeginPIEDelegateHandle = FEditorDelegates::BeginPIE.AddUObject(this, &UAkAudioEvent::OnBeginPIE); + } + + LoadEventData(); +} + +void UAkAudioEvent::OnBeginPIE(const bool bIsSimulating) +{ + FEditorDelegates::BeginPIE.Remove(OnBeginPIEDelegateHandle); + OnBeginPIEDelegateHandle.Reset(); + UnloadEventData(false); +} + +#endif + +void UAkAudioEvent::BeginDestroy() +{ + Super::BeginDestroy(); + +#if WITH_EDITOR + if (OnBeginPIEDelegateHandle.IsValid()) + { + FEditorDelegates::BeginPIE.Remove(OnBeginPIEDelegateHandle); + OnBeginPIEDelegateHandle.Reset(); + } +#endif +} + +void UAkAudioEvent::UnloadEventData(bool bAsync) +{ + if (LoadedEvent) + { + auto* ResourceLoader = FWwiseResourceLoader::Get(); + if (UNLIKELY(!ResourceLoader)) + { + return; + } + UE_LOG(LogAkAudio, Verbose, TEXT("%s - UnloadEventData"), *GetName()); + if (bAsync) + { + FWwiseLoadedEventPromise Promise; + Promise.EmplaceValue(MoveTemp(LoadedEvent)); + ResourceUnload = ResourceLoader->UnloadEventAsync(Promise.GetFuture()); + } + else + { + ResourceLoader->UnloadEvent(MoveTemp(LoadedEvent)); + } + LoadedEvent = nullptr; + } +} + +bool UAkAudioEvent::IsDataFullyLoaded() const +{ + if (!LoadedEvent) + { + return false; + } + + return LoadedEvent->GetValue().LoadedData.IsLoaded(); +} + +bool UAkAudioEvent::IsLoaded() const +{ + return LoadedEvent != nullptr; +} + +#if WITH_EDITORONLY_DATA +bool UAkAudioEvent::ObjectIsInSoundBanks() +{ + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkAudioEvent::GetWwiseRef: ResourceCooker not initialized")); + return false; + } + + auto ProjectDatabase = ResourceCooker->GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkAudioEvent::GetWwiseRef: ProjectDatabase not initialized")); + return false; + } + + const TSet EventRef = FWwiseDataStructureScopeLock(*ProjectDatabase).GetEvent(GetValidatedInfo(EventInfo)); + if (UNLIKELY(EventRef.Num() == 0)) + { + UE_LOG(LogAkAudio, Log, TEXT("UAkAudioEvent::GetWwiseRef (%s): Event is not loaded"), *GetName()); + return false; + } + + return EventRef.Array()[0].IsValid(); +} +#endif + +TArray UAkAudioEvent::GetAllExternalSources() const +{ + if (!LoadedEvent) + { + return {}; + } + + const auto& EventData = LoadedEvent->GetValue(); + if (!EventData.LoadedData.IsLoaded()) + { + return {}; + } + + const auto& LoadedLanguage = EventData.LanguageRef; + const FWwiseEventCookedData* CookedData = EventCookedData.EventLanguageMap.Find(LoadedLanguage); + if (UNLIKELY(!CookedData)) + { + return {}; + } + + auto Result = CookedData->ExternalSources; + for (const auto& Leaf : CookedData->SwitchContainerLeaves) + { + Result.Append(Leaf.ExternalSources); + } + return Result; +} + +#if WITH_EDITORONLY_DATA +void UAkAudioEvent::CookAdditionalFilesOverride(const TCHAR* PackageFilename, const ITargetPlatform* TargetPlatform, + TFunctionRef WriteAdditionalFile) +{ + if (HasAnyFlags(RF_ClassDefaultObject)) + { + return; + } + + FWwiseResourceCooker* ResourceCooker = FWwiseResourceCooker::GetForPlatform(TargetPlatform); + if (!ResourceCooker) + { + return; + } + ResourceCooker->SetSandboxRootPath(PackageFilename); + ResourceCooker->CookEvent(GetValidatedInfo(EventInfo), WriteAdditionalFile); +} +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAudioInputComponent.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAudioInputComponent.cpp new file mode 100644 index 0000000..fffc53a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAudioInputComponent.cpp @@ -0,0 +1,75 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= +AkAudioInputComponent.cpp: +=============================================================================*/ + +#include "AkAudioInputComponent.h" +#include "AkAudioDevice.h" +#include "AkAudioEvent.h" + +UAkAudioInputComponent::UAkAudioInputComponent(const class FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer) +{} + +int32 UAkAudioInputComponent::PostAssociatedAudioInputEvent() +{ + AudioInputDelegate = FAkGlobalAudioInputDelegate::CreateLambda( + [this](uint32 NumChannels, uint32 NumSamples, float** BufferToFill) -> bool + { + return FillSamplesBuffer(NumChannels, NumSamples, BufferToFill); + }); + + AudioFormatDelegate = FAkGlobalAudioFormatDelegate::CreateLambda([this](AkAudioFormat& AudioFormat) + { + return GetChannelConfig(AudioFormat); + }); + + + AkPlayingID PlayingID = FAkAudioInputManager::PostAudioInputEvent( + AkAudioEvent, EventName, this, AudioInputDelegate, AudioFormatDelegate); + + if (PlayingID != AK_INVALID_PLAYING_ID) + { + CurrentlyPlayingIDs.Add(PlayingID); + } + return PlayingID; +} + +void UAkAudioInputComponent::PostUnregisterGameObject() +{ + if (AudioInputDelegate.IsBound()) + { + AudioInputDelegate.Unbind(); + } + + if (AudioFormatDelegate.IsBound()) + { + AudioFormatDelegate.Unbind(); + } + + auto Device = FAkAudioDevice::Get(); + if (Device != nullptr) + { + for (int i = 0; i < CurrentlyPlayingIDs.Num(); ++i) + { + Device->StopPlayingID(CurrentlyPlayingIDs[i]); + } + } + CurrentlyPlayingIDs.Empty(); +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAudioInputManager.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAudioInputManager.cpp new file mode 100644 index 0000000..54f72ad --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAudioInputManager.cpp @@ -0,0 +1,395 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkAudioInputManager.h" +#include "AkAudioDevice.h" +#include "AkAudioEvent.h" +#if WITH_EDITOR +#include "Editor.h" +#endif +#include "Wwise/API/WwiseSoundEngineAPI.h" + +#include "Misc/ScopeLock.h" + +#include + +#include "AkComponent.h" + +/*------------------------------------------------------------------------------------ +FAudioInputDelegates +Helper struct that contains an audio samples delegate and an audio format delegate +------------------------------------------------------------------------------------*/ + +struct FAudioInputDelegates +{ + FAkGlobalAudioInputDelegate AudioSamplesDelegate; + FAkGlobalAudioFormatDelegate AudioFormatDelegate; +}; + +/*------------------------------------------------------------------------------------ +FAkAudioInputHelpers +------------------------------------------------------------------------------------*/ + +namespace FAkAudioInputHelpers +{ + static FCriticalSection MapSection; + static TArray AudioData = TArray(); + /* A Map of playing ids to input delegates */ + static TMap AudioInputDelegates = TMap(); + + static void UpdateDataPointers(AkAudioBuffer* BufferToFill) + { + AkUInt32 NumChannels = BufferToFill->NumChannels(); + for (AkUInt32 c = 0; c < NumChannels; ++c) + { + AudioData[c] = BufferToFill->GetChannel(c); + } + } + + /* The global audio samples callback that searches AudioInputDelegates for + the key PlayingID and executes the corresponding delegate*/ + static void GetAudioSamples(AkPlayingID PlayingID, AkAudioBuffer* BufferToFill) + { + if (!BufferToFill) + { + return; + } + + BufferToFill->eState = AK_NoMoreData; + + AkUInt32 NumChannels = BufferToFill->NumChannels(); + const AkUInt16 NumFrames = BufferToFill->MaxFrames(); + + BufferToFill->uValidFrames = NumFrames; + + FAkGlobalAudioInputDelegate SamplesCallback; + + { + FScopeLock MapLock(&MapSection); + auto Delegates = AudioInputDelegates.Find((uint32)PlayingID); + if (Delegates) + { + SamplesCallback = Delegates->AudioSamplesDelegate; + } + } + + if (SamplesCallback.IsBound()) + { + UpdateDataPointers(BufferToFill); + if (SamplesCallback.Execute((int)NumChannels, (int)NumFrames, AudioData.GetData())) + { + BufferToFill->eState = AK_DataReady; + } + } + else + { + BufferToFill->ZeroPadToMaxFrames(); + } + } + + /* The global audio format callback that searches AudioInputDelegates for + the key PlayingID and executes the corresponding delegate*/ + static void GetAudioFormat(AkPlayingID PlayingID, AkAudioFormat& AudioFormat) + { + FAkGlobalAudioFormatDelegate FormatCallback; + + { + FScopeLock MapLock(&MapSection); + auto Delegates = AudioInputDelegates.Find((uint32)PlayingID); + if (Delegates) + { + FormatCallback = Delegates->AudioFormatDelegate; + } + } + + if (FormatCallback.IsBound()) + { + FormatCallback.Execute(AudioFormat); + } + const uint32 NumChannels = AudioFormat.channelConfig.uNumChannels; + if (AudioData.Max() < (int32)NumChannels) + { + AudioData.Reserve(NumChannels); + AudioData.AddUninitialized(AudioData.GetSlack()); + } + } + + /** + * Sets the main callbacks for the Wwise engine audio input plugin. + * + */ + static void SetAkAudioInputCallbacks() + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + SoundEngine->AudioInputPlugin->SetAudioInputCallbacks( + &FAkAudioInputHelpers::GetAudioSamples, + &FAkAudioInputHelpers::GetAudioFormat, + nullptr); + } + /* Protects against calling Wwise sound engine SetAudioInputCallbacks function more than once */ + static bool bIsInitialized = false; + /* Calls the Wwise sound engine SetAudioInputCallbacks function*/ + static void TryInitialize() + { + if (!bIsInitialized) + { + SetAkAudioInputCallbacks(); + bIsInitialized = true; + } +#if WITH_EDITOR + FEditorDelegates::EndPIE.AddLambda([](const bool bIsSimulating) + { + bIsInitialized = false; + }); +#endif + } + + static void AddAudioInputPlayingID(AkPlayingID PlayingID, + FAkGlobalAudioInputDelegate AudioSamplesDelegate, + FAkGlobalAudioFormatDelegate AudioFormatDelegate) + { + FScopeLock MapLock(&MapSection); + AudioInputDelegates.Add((uint32)PlayingID, { AudioSamplesDelegate, AudioFormatDelegate }); + } + + /* Posts an event and associates the AudioSamplesDelegate and AudioFormatDelegate delegates with the resulting playing id. */ + AkPlayingID PostAudioInputEvent(TFunction PostEventCall, + FAkGlobalAudioInputDelegate AudioSamplesDelegate, + FAkGlobalAudioFormatDelegate AudioFormatDelegate) + { + TryInitialize(); + AkPlayingID PlayingID = AK_INVALID_PLAYING_ID; + FAkAudioDevice* AkDevice = FAkAudioDevice::Get(); + if (AkDevice != nullptr) + { + PlayingID = PostEventCall(AkDevice); + if (PlayingID != AK_INVALID_PLAYING_ID) + { + AddAudioInputPlayingID(PlayingID, AudioSamplesDelegate, AudioFormatDelegate); + } + } + return PlayingID; + } + + static void EventCallback(AkCallbackType CallbackType, AkCallbackInfo *CallbackInfo) + { + if (CallbackType == AkCallbackType::AK_EndOfEvent) + { + AkEventCallbackInfo* EventInfo = (AkEventCallbackInfo*)CallbackInfo; + if (EventInfo != nullptr) + { + uint32 PlayingID = (uint32)EventInfo->playingID; + + { + FScopeLock MapLock(&MapSection); + AudioInputDelegates.Remove(PlayingID); + } + } + } + } +} + +/*------------------------------------------------------------------------------------ +FAkAudioInputManager +------------------------------------------------------------------------------------*/ + +AkPlayingID FAkAudioInputManager::PostAudioInputEvent( + UAkAudioEvent * Event, + AActor * Actor, + FAkGlobalAudioInputDelegate AudioSamplesDelegate, + FAkGlobalAudioFormatDelegate AudioFormatDelegate, + EAkAudioContext AudioContext +) +{ + if (!IsValid(Event)) + { + UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioInputManager::PostAudioInputEvent: Invalid AkEvent.")) + return AK_INVALID_PLAYING_ID; + } + if (!IsValid(Actor)) + { + UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioInputManager::PostAudioInputEvent: Invalid Actor playing AkEvent %s."), *Event->GetName()) + return AK_INVALID_PLAYING_ID; + } + + return FAkAudioInputHelpers::PostAudioInputEvent([Event, Actor, AudioContext](FAkAudioDevice* AkDevice) + { + const auto Result = Event->PostOnActor( + Actor, + nullptr, + &FAkAudioInputHelpers::EventCallback, + nullptr, + AkCallbackType::AK_EndOfEvent, + nullptr, + false, + AudioContext); + UE_CLOG(UNLIKELY(Result == AK_INVALID_PLAYING_ID), LogAkAudio, Warning, + TEXT("FAkAudioInputManager::PostAudioInputEvent: Failed posting input event %s to actor %s."), *Event->GetName(), *Actor->GetName()); + UE_CLOG(LIKELY(Result != AK_INVALID_PLAYING_ID), LogAkAudio, VeryVerbose, + TEXT("FAkAudioInputManager::PostAudioInputEvent: Posted input event %s to actor %s. PlayId=%" PRIu32), *Event->GetName(), *Actor->GetName(), Result); + return Result; + }, AudioSamplesDelegate, AudioFormatDelegate); +} + +AkPlayingID FAkAudioInputManager::PostAudioInputEvent( + UAkAudioEvent* Event, + UAkComponent* Component, + FAkGlobalAudioInputDelegate AudioSamplesDelegate, + FAkGlobalAudioFormatDelegate AudioFormatDelegate, + EAkAudioContext AudioContext) +{ + if (!IsValid(Event)) + { + UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioInputManager::PostAudioInputEvent: Invalid AkEvent.")) + return AK_INVALID_PLAYING_ID; + } + if (!Component) + { + UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioInputManager::PostAudioInputEvent: Invalid Component playing AkEvent %s."), *Event->GetName()) + return AK_INVALID_PLAYING_ID; + } + + return FAkAudioInputHelpers::PostAudioInputEvent([Event, Component, AudioContext](FAkAudioDevice* AkDevice) + { + const auto Result = Event->PostOnComponent( + Component, + nullptr, + &FAkAudioInputHelpers::EventCallback, + nullptr, + AkCallbackType::AK_EndOfEvent, + nullptr, + false, + AudioContext); + UE_CLOG(UNLIKELY(Result == AK_INVALID_PLAYING_ID), LogAkAudio, Warning, + TEXT("FAkAudioInputManager::PostAudioInputEvent: Failed posting input event %s to component %s."), *Event->GetName(), *Component->GetName()); + UE_CLOG(LIKELY(Result != AK_INVALID_PLAYING_ID), LogAkAudio, VeryVerbose, + TEXT("FAkAudioInputManager::PostAudioInputEvent: Posted input event %s to component %s. PlayId=%" PRIu32), *Event->GetName(), *Component->GetName(), Result); + return Result; + }, AudioSamplesDelegate, AudioFormatDelegate); +} + +AkPlayingID FAkAudioInputManager::PostAudioInputEvent( + UAkAudioEvent* Event, + AkGameObjectID GameObject, + FAkGlobalAudioInputDelegate AudioSamplesDelegate, + FAkGlobalAudioFormatDelegate AudioFormatDelegate, + EAkAudioContext AudioContext) +{ + if (!IsValid(Event)) + { + UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioInputManager::PostAudioInputEvent: Invalid AkEvent.")) + return AK_INVALID_PLAYING_ID; + } + if (GameObject == AK_INVALID_GAME_OBJECT) + { + UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioInputManager::PostAudioInputEvent: Invalid GameObject playing AkEvent %s."), *Event->GetName()) + return AK_INVALID_PLAYING_ID; + } + return FAkAudioInputHelpers::PostAudioInputEvent([Event, GameObject, AudioContext](FAkAudioDevice* AkDevice) + { + const auto Result = Event->PostOnGameObjectID( + GameObject, + nullptr, + &FAkAudioInputHelpers::EventCallback, + nullptr, + AkCallbackType::AK_EndOfEvent, + nullptr, + AudioContext); + UE_CLOG(UNLIKELY(Result == AK_INVALID_PLAYING_ID), LogAkAudio, Warning, + TEXT("FAkAudioInputManager::PostAudioInputEvent: Failed posting input event %s to %" PRIu64 "."), *Event->GetName(), GameObject); + UE_CLOG(LIKELY(Result != AK_INVALID_PLAYING_ID), LogAkAudio, VeryVerbose, + TEXT("FAkAudioInputManager::PostAudioInputEvent: Posted input event %s to %" PRIu64 ". PlayId=%" PRIu32), *Event->GetName(), GameObject, Result); + return Result; + }, AudioSamplesDelegate, AudioFormatDelegate); +} + +AkPlayingID FAkAudioInputManager::PostAudioInputEvent(UAkAudioEvent* Event, + FAkGlobalAudioInputDelegate AudioSamplesDelegate, FAkGlobalAudioFormatDelegate AudioFormatDelegate, + EAkAudioContext AudioContext) +{ + if (!IsValid(Event)) + { + UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioInputManager::PostAudioInputEvent: Invalid AkEvent.")) + return AK_INVALID_PLAYING_ID; + } + return FAkAudioInputHelpers::PostAudioInputEvent([Event, AudioContext](FAkAudioDevice* AkDevice) + { + const auto Result = Event->PostAmbient( + nullptr, + &FAkAudioInputHelpers::EventCallback, + nullptr, + AkCallbackType::AK_EndOfEvent, + nullptr, + AudioContext); + UE_CLOG(UNLIKELY(Result == AK_INVALID_PLAYING_ID), LogAkAudio, Warning, + TEXT("FAkAudioInputManager::PostAudioInputEvent: Failed posting ambient input event %s."), *Event->GetName()); + UE_CLOG(LIKELY(Result != AK_INVALID_PLAYING_ID), LogAkAudio, VeryVerbose, + TEXT("FAkAudioInputManager::PostAudioInputEvent: Posted ambient input event %s. PlayId=%" PRIu32), *Event->GetName(), Result); + return Result; + }, AudioSamplesDelegate, AudioFormatDelegate); +} + +AkPlayingID FAkAudioInputManager::PostAudioInputEvent( + UAkAudioEvent* AkEvent, + const FString& EventName, + AActor * Actor, + FAkGlobalAudioInputDelegate AudioSamplesDelegate, FAkGlobalAudioFormatDelegate AudioFormatDelegate +) +{ + return FAkAudioInputHelpers::PostAudioInputEvent([EventName, AkEvent, Actor](FAkAudioDevice* AkDevice) + { + const AkUInt32 ShortID = AkDevice->GetShortID(AkEvent, EventName); + return AkDevice->PostEventOnActor(ShortID, Actor, AkCallbackType::AK_EndOfEvent, &FAkAudioInputHelpers::EventCallback); + }, AudioSamplesDelegate, AudioFormatDelegate); +} + +AkPlayingID FAkAudioInputManager::PostAudioInputEvent( + UAkAudioEvent* AkEvent, + const FString& EventName, + UAkComponent* Component, + FAkGlobalAudioInputDelegate AudioSamplesDelegate, + FAkGlobalAudioFormatDelegate AudioFormatDelegate + ) +{ + return FAkAudioInputHelpers::PostAudioInputEvent([EventName, AkEvent, Component](FAkAudioDevice* AkDevice) + { + const AkUInt32 ShortID = AkDevice->GetShortID(AkEvent, EventName); + return AkDevice->PostEventOnAkComponent(ShortID, Component, AkCallbackType::AK_EndOfEvent, &FAkAudioInputHelpers::EventCallback); + }, AudioSamplesDelegate, AudioFormatDelegate); +} + +AkPlayingID FAkAudioInputManager::PostAudioInputEvent( + const FString& EventName, + AkGameObjectID GameObject, + FAkGlobalAudioInputDelegate AudioSamplesDelegate, + FAkGlobalAudioFormatDelegate AudioFormatDelegate) +{ + return FAkAudioInputHelpers::PostAudioInputEvent([EventName, GameObject](FAkAudioDevice* AkDevice) + { + TArray ExternSource; + return AkDevice->PostEventOnGameObjectID( + AkDevice->GetShortID(nullptr, EventName), + GameObject, + AkCallbackType::AK_EndOfEvent, + &FAkAudioInputHelpers::EventCallback, + nullptr, + ExternSource + ); + }, AudioSamplesDelegate, AudioFormatDelegate); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAudioModule.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAudioModule.cpp new file mode 100644 index 0000000..7fe77eb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAudioModule.cpp @@ -0,0 +1,321 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkAudioModule.h" +#include "AkAudioDevice.h" +#include "AkAudioStyle.h" +#include "AkSettings.h" +#include "AkSettingsPerUser.h" +#include "AkUnrealHelper.h" + +#include "Wwise/WwiseResourceLoader.h" +#include "Wwise/WwiseSoundEngineModule.h" +#include "WwiseInitBankLoader/WwiseInitBankLoader.h" + +#include "Misc/ScopedSlowTask.h" + +#include "UObject/UObjectIterator.h" + + +#include "Wwise/API/WwiseSoundEngineAPI.h" + +#if WITH_EDITORONLY_DATA +#include "Wwise/WwiseFileHandlerModule.h" +#include "Wwise/WwiseProjectDatabase.h" +#include "Wwise/WwiseDataStructure.h" +#include "Wwise/WwiseResourceCooker.h" +#endif + +#if WITH_EDITOR +#include "AssetRegistry/AssetRegistryModule.h" +#include "HAL/FileManager.h" +#endif +#include + +IMPLEMENT_MODULE(FAkAudioModule, AkAudio) +#define LOCTEXT_NAMESPACE "AkAudio" + +FAkAudioModule* FAkAudioModule::AkAudioModuleInstance = nullptr; +FSimpleMulticastDelegate FAkAudioModule::OnModuleInitialized; + +// AkUnrealHelper overrides + +namespace AkUnrealHelper +{ + static FString GetWwisePluginDirectoryImpl() + { + return FAkPlatform::GetWwisePluginDirectory(); + } + + static FString GetWwiseProjectPathImpl() + { + FString projectPath; + + if (auto* settings = GetDefault()) + { + projectPath = settings->WwiseProjectPath.FilePath; + + if (FPaths::IsRelative(projectPath)) + { + projectPath = FPaths::ConvertRelativePathToFull(GetProjectDirectory(), projectPath); + } + +#if PLATFORM_WINDOWS + projectPath.ReplaceInline(TEXT("/"), TEXT("\\")); +#endif + } + + return projectPath; + } + + static FString GetSoundBankDirectoryImpl() + { + const UAkSettingsPerUser* UserSettings = GetDefault(); + FString SoundBankDirectory; + if (UserSettings && !UserSettings->GeneratedSoundBanksFolderUserOverride.Path.IsEmpty()) + { + SoundBankDirectory = FPaths::Combine(GetContentDirectory(), UserSettings->GeneratedSoundBanksFolderUserOverride.Path); + } + else if (const UAkSettings* AkSettings = GetDefault()) + { + SoundBankDirectory = FPaths::Combine(GetContentDirectory(), AkSettings->GeneratedSoundBanksFolder.Path); + } + else + { + UE_LOG(LogAkAudio, Warning, TEXT("AkUnrealHelper::GetSoundBankDirectory : Please set the Generated Soundbanks Folder in Wwise settings. Otherwise, sound will not function.")); + return {}; + } + FPaths::CollapseRelativeDirectories(SoundBankDirectory); + if(!SoundBankDirectory.EndsWith(TEXT("/"))) + { + SoundBankDirectory.AppendChar('/'); + } + + return SoundBankDirectory; + } + + static FString GetStagePathImpl() + { + const UAkSettings* Settings = GetDefault(); +#if WITH_EDITORONLY_DATA + if (Settings && !Settings->WwiseStagingDirectory.Path.IsEmpty()) + { + return Settings->WwiseStagingDirectory.Path; + } + return TEXT("WwiseAudio"); +#endif + if (Settings && !Settings->WwiseStagingDirectory.Path.IsEmpty()) + { + return FPaths::ProjectContentDir() / Settings->WwiseStagingDirectory.Path; + } + return FPaths::ProjectContentDir() / TEXT("WwiseAudio"); + } +} + +void FAkAudioModule::StartupModule() +{ + IWwiseSoundEngineModule::ForceLoadModule(); + AkUnrealHelper::SetHelperFunctions( + AkUnrealHelper::GetWwisePluginDirectoryImpl, + AkUnrealHelper::GetWwiseProjectPathImpl, + AkUnrealHelper::GetSoundBankDirectoryImpl, + AkUnrealHelper::GetStagePathImpl); + +#if WITH_EDITOR + // It is not wanted to initialize the SoundEngine while running the GenerateSoundBanks commandlet. + if (IsRunningCommandlet()) + { + // We COULD use GetRunningCommandletClass(), but unfortunately it is set to nullptr in OnPostEngineInit. + // We need to parse the command line. + FString CmdLine(FCommandLine::Get()); + if (CmdLine.Contains(TEXT("-run=GenerateSoundBanks"))) + { + UE_LOG(LogAkAudio, Log, TEXT("FAkAudioModule::StartupModule: Detected GenerateSoundBanks commandlet is running. AkAudioModule will not be initialized.")); + return; + } + +#if WITH_EDITORONLY_DATA + if(IWwiseProjectDatabaseModule::IsInACookingCommandlet()) + { + // Initialize the Rersource Cooker + IWwiseResourceCookerModule::GetModule(); + } +#endif + } +#endif + + if (AkAudioModuleInstance == this) + { + UE_LOG(LogAkAudio, Log, TEXT("FAkAudioModule::StartupModule: AkAudioModuleInstance already exists.")); + return; + } + UE_CLOG(AkAudioModuleInstance, LogAkAudio, Warning, TEXT("FAkAudioModule::StartupModule: Updating AkAudioModuleInstance from (%p) to (%p)! Previous Module instance was improperly shut down!"), AkAudioModuleInstance, this); + + AkAudioModuleInstance = this; + + FScopedSlowTask SlowTask(0, LOCTEXT("InitWwisePlugin", "Initializing Wwise Plug-in AkAudioModule...")); + + UpdateWwiseResourceLoaderSettings(); + +#if WITH_EDITORONLY_DATA + ParseGeneratedSoundBankData(); + FWwiseInitBankLoader::Get()->UpdateInitBankInSettings(); + + // Loading the File Handler Module, in case it loads a different module with UStructs, so it gets packaged (Ex.: Simple External Source Manager) + IWwiseFileHandlerModule::GetModule(); +#endif + + AkAudioDevice = new FAkAudioDevice; + if (!AkAudioDevice) + { + UE_LOG(LogAkAudio, Log, TEXT("FAkAudioModule::StartupModule: Couldn't create FAkAudioDevice. AkAudioModule will not be fully initialized.")); + bModuleInitialized = true; + return; + } + + if (!AkAudioDevice->Init()) + { + UE_LOG(LogAkAudio, Log, TEXT("FAkAudioModule::StartupModule: Couldn't initialize FAkAudioDevice. AkAudioModule will not be fully initialized.")); + bModuleInitialized = true; + delete AkAudioDevice; + AkAudioDevice = nullptr; + return; + } + + //Load init bank in Runtime (will be loaded by AudiokineticTools module calling ReloadWwiseAssets when in editor) + UE_LOG(LogAkAudio, VeryVerbose, TEXT("FAkAudioModule::StartupModule: Loading Init Bank.")); + FWwiseInitBankLoader::Get()->LoadInitBank(); + + OnTick = FTickerDelegate::CreateRaw(AkAudioDevice, &FAkAudioDevice::Update); + TickDelegateHandle = FCoreTickerType::GetCoreTicker().AddTicker(OnTick); + + AkAudioDevice->LoadDelayedObjects(); + + UE_LOG(LogAkAudio, VeryVerbose, TEXT("FAkAudioModule::StartupModule: Module Initialized.")); + OnModuleInitialized.Broadcast(); + bModuleInitialized = true; +} + +void FAkAudioModule::ShutdownModule() +{ + UE_CLOG(AkAudioModuleInstance && AkAudioModuleInstance != this, LogAkAudio, Warning, TEXT("FAkAudioModule::ShutdownModule: Shutting down a different instance (%p) that was initially instantiated (%p)!"), this, AkAudioModuleInstance); + + FCoreTickerType::GetCoreTicker().RemoveTicker(TickDelegateHandle); + + if (AkAudioDevice) + { + AkAudioDevice->Teardown(); + delete AkAudioDevice; + AkAudioDevice = nullptr; + } + + if (IWwiseSoundEngineModule::IsAvailable()) + { + AkUnrealHelper::SetHelperFunctions(nullptr, nullptr, nullptr, nullptr); + } + + AkAudioModuleInstance = nullptr; +} + +FAkAudioDevice* FAkAudioModule::GetAkAudioDevice() const +{ + return AkAudioDevice; +} + +void FAkAudioModule::ReloadWwiseAssetData() const +{ + SCOPED_AKAUDIO_EVENT(TEXT("ReloadWwiseAssetData")); + if (FAkAudioDevice::IsInitialized()) + { + UE_LOG(LogAkAudio, Log, TEXT("FAkAudioModule::ReloadWwiseAssetData : Reloading Wwise asset data.")); + if (AkAudioDevice) + { + AkAudioDevice->ClearSoundBanksAndMedia(); + } + + auto* InitBankLoader = FWwiseInitBankLoader::Get(); + if (LIKELY(InitBankLoader)) + { + InitBankLoader->LoadInitBank(); + } + else + { + UE_LOG(LogAkAudio, Error, TEXT("LoadInitBank: WwiseInitBankLoader is not initialized.")); + } + + for (TObjectIterator AudioAssetIt; AudioAssetIt; ++AudioAssetIt) + { + AudioAssetIt->LoadData(); + } + } + else + { + UE_LOG(LogAkAudio, Verbose, TEXT("FAkAudioModule::ReloadWwiseAssetData : Skipping asset data reload because the SoundEngine is not initialized.")); + } +} + +void FAkAudioModule::UpdateWwiseResourceLoaderSettings() +{ + SCOPED_AKAUDIO_EVENT(TEXT("UpdateWwiseResourceLoaderSettings")); + UE_LOG(LogAkAudio, Log, TEXT("FAkAudioModule::UpdateWwiseResourceLoaderSettings : Updating Resource Loader settings.")); + + auto* ResourceLoader = FWwiseResourceLoader::Get(); + if (!ResourceLoader) + { + UE_LOG(LogAkAudio, Error, TEXT("FAkAudioModule::UpdateWwiseResourceLoaderSettings : No Resource Loader!")); + return; + } + auto* ResourceLoaderImpl = ResourceLoader->ResourceLoaderImpl.Get(); + if (!ResourceLoaderImpl) + { + UE_LOG(LogAkAudio, Error, TEXT("FAkAudioModule::UpdateWwiseResourceLoaderSettings : No Resource Loader Impl!")); + return; + } + + ResourceLoaderImpl->StagePath = AkUnrealHelper::GetStagePathImpl(); + +#if WITH_EDITORONLY_DATA + ResourceLoaderImpl->GeneratedSoundBanksPath = FDirectoryPath{AkUnrealHelper::GetSoundBankDirectory()}; +#endif +} + +#if WITH_EDITORONLY_DATA +void FAkAudioModule::ParseGeneratedSoundBankData() +{ + SCOPED_AKAUDIO_EVENT(TEXT("ParseGeneratedSoundBankData")); + if (auto* AkSettings = GetDefault()) + { + if (!AkSettings->AreSoundBanksGenerated()) + { + UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioModule::ParseGeneratedSoundBankData: SoundBanks are not yet generated, nothing to parse.\nCurrent Generated SoundBanks path is: %s"), *AkUnrealHelper::GetSoundBankDirectory()); + return; + } + } + UE_LOG(LogAkAudio, Log, TEXT("FAkAudioModule::ParseGeneratedSoundBankData : Parsing Wwise project data.")); + auto* ProjectDatabase = FWwiseProjectDatabase::Get(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioModule::ParseGeneratedSoundBankData : Could not get FWwiseProjectDatabase instance. Generated sound data will not be parsed.")); + } + else + { + ProjectDatabase->UpdateDataStructure(); + } +} +#endif + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAudioStyle.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAudioStyle.cpp new file mode 100644 index 0000000..3282d04 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAudioStyle.cpp @@ -0,0 +1,349 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkAudioStyle.h" +#include "AkAudioDevice.h" +#include "Framework/Notifications/NotificationManager.h" +#include "WaapiPicker/WwiseTreeItem.h" +#include "Styling/SlateStyle.h" +#include "Styling/SlateStyleRegistry.h" +#include "Engine/Texture2D.h" +#include "Widgets/Notifications/SNotificationList.h" + +TSharedPtr< FSlateStyleSet > FAkAudioStyle::StyleInstance = nullptr; +UMaterial* FAkAudioStyle::TextMaterial = nullptr; + +/* Taken from the Wwise Authoring dark theme. @TODO: Take this from the theme's main.json file. */ +TArray WwiseUnrealColorPalette = { + FColor(191, 191, 191, 255), + FColor(82, 90, 255, 1), + FColor(41, 169, 255, 1), + FColor(0, 255, 255, 1), + FColor(0, 255, 0, 1), + FColor(183, 255, 0, 1), + FColor(255, 238, 0, 1), + FColor(255, 170, 0, 1), + FColor(255, 128, 0, 1), + FColor(255, 85, 0, 1), + FColor(255, 0, 0, 1), + FColor(255, 0, 255, 1), + FColor(190, 61, 255, 1), + FColor(184, 107, 255, 1), + FColor(112, 119, 255, 1), + FColor(119, 164, 253, 1), + FColor(132, 240, 240, 1), + FColor(136, 242, 136, 1), + FColor(209, 240, 122, 1), + FColor(240, 192, 96, 1), + FColor(255, 179, 102, 1), + FColor(240, 192, 96, 1), + FColor(255, 179, 102, 1), + FColor(255, 146, 92, 1), + FColor(255, 112, 255, 1), + FColor(214, 133, 255, 1), + FColor(166, 128, 255, 1), + FColor(191, 191, 191, 1) +}; + +namespace AkAudioStyle_Helpers +{ + template + auto LoadAkTexture(T1&& RelativePath, T2&& TextureName) + { + return LoadObject(nullptr, *(FString("/Wwise/") / FString(Forward(RelativePath)) / FString(Forward(TextureName)) + FString(".") + FString(Forward(TextureName)))); + } + + UMaterial* LoadAkMaterial(const FString& RelativePath, const FString& MaterialName) + { + return LoadObject(nullptr, *(FString("/Wwise/") + RelativePath + MaterialName + FString(".") + MaterialName)); + } + + auto CreateAkImageBrush(UTexture2D* Texture, const FVector2D& TextureSize) + { + return new FSlateImageBrush(Texture, TextureSize); + } + + template + auto CreateEngineBoxBrush(FSlateStyleSet& Style, StringType& RelativePath, Args&&... args) + { + return new FSlateBoxBrush(Style.RootToContentDir(Forward(RelativePath), TEXT(".png")), Forward(args)...); + } + + template + auto EngineBoxBrush(FSlateStyleSet& Style, StringType&& RelativePath, Args&&... args) + { + return FSlateBoxBrush(Style.RootToContentDir(Forward(RelativePath), TEXT(".png")), Forward(args)...); + } + + template + auto CreateEngineImageBrush(FSlateStyleSet& Style, StringType& RelativePath, Args&&... args) + { + return new FSlateImageBrush(Style.RootToContentDir(Forward(RelativePath), TEXT(".png")), Forward(args)...); + } +} + +void SetAkBrush(FSlateStyleSet& Style, const char* BrushName, const char* TextureName) +{ + using namespace AkAudioStyle_Helpers; + + const FVector2D Icon15x15(15.0f, 15.0f); + + auto Texture = LoadAkTexture("WwiseTree/Icons", TextureName); + if (Texture != nullptr) + { + Texture->AddToRoot(); + Style.Set(BrushName, CreateAkImageBrush(Texture, Icon15x15)); + } +} + +void SetClassThumbnail(FSlateStyleSet& Style, const char* BrushName, const char* TextureName) +{ + using namespace AkAudioStyle_Helpers; + + const FVector2D ThumbnailSize(256.0f, 256.0f); + + auto Texture = LoadAkTexture("WwiseTypes", TextureName); + if (Texture != nullptr) + { + Texture->AddToRoot(); + Style.Set(BrushName, CreateAkImageBrush(Texture, ThumbnailSize)); + } +} + +void SetClassIcon(FSlateStyleSet& Style, const char* BrushName, const char* TextureName) +{ + using namespace AkAudioStyle_Helpers; + + const FVector2D ThumbnailSize(15.0f, 15.0f); + + auto Texture = LoadAkTexture("WwiseTypes", TextureName); + if (Texture != nullptr) + { + Texture->AddToRoot(); + Style.Set(BrushName, CreateAkImageBrush(Texture, ThumbnailSize)); + } +} + +void SetAkResourceBrushes(FSlateStyleSet& Style) +{ + SetAkBrush(Style, "AudiokineticTools.WwiseIcon", "Titlebar_WwiseAppIcon"); + + SetAkBrush(Style, "AudiokineticTools.ActorMixerIcon", "actor_mixer_nor"); + SetAkBrush(Style, "AudiokineticTools.SoundIcon", "sound_fx_nor"); + SetAkBrush(Style, "AudiokineticTools.SwitchContainerIcon", "container_switch_nor"); + SetAkBrush(Style, "AudiokineticTools.RandomSequenceContainerIcon", "container_random_sequence_nor"); + SetAkBrush(Style, "AudiokineticTools.BlendContainerIcon", "layer_container_nor"); + SetAkBrush(Style, "AudiokineticTools.MotionBusIcon", "motion_bus_nor"); + SetAkBrush(Style, "AudiokineticTools.AkBrowserTabIcon", "wwise_logo_32"); + SetAkBrush(Style, "AudiokineticTools.EventIcon", "event_nor"); + SetAkBrush(Style, "AudiokineticTools.AcousticTextureIcon", "acoutex_nor"); + SetAkBrush(Style, "AudiokineticTools.AuxBusIcon", "auxbus_nor"); + SetAkBrush(Style, "AudiokineticTools.BusIcon", "bus_nor"); + SetAkBrush(Style, "AudiokineticTools.FolderIcon", "folder_nor"); + SetAkBrush(Style, "AudiokineticTools.PhysicalFolderIcon", "physical_folder_nor"); + SetAkBrush(Style, "AudiokineticTools.WorkUnitIcon", "workunit_nor"); + SetAkBrush(Style, "AudiokineticTools.ProjectIcon", "wproj"); + SetAkBrush(Style, "AudiokineticTools.RTPCIcon", "gameparameter_nor"); + SetAkBrush(Style, "AudiokineticTools.StateIcon", "state_nor"); + SetAkBrush(Style, "AudiokineticTools.StateGroupIcon", "stategroup_nor"); + SetAkBrush(Style, "AudiokineticTools.SwitchIcon", "switch_nor"); + SetAkBrush(Style, "AudiokineticTools.SwitchGroupIcon", "switchgroup_nor"); + SetAkBrush(Style, "AudiokineticTools.TriggerIcon", "trigger_nor"); + SetAkBrush(Style, "AudiokineticTools.EffectShareSetIcon", "effect_shareset_nor"); + + SetClassThumbnail(Style, "ClassThumbnail.AkAcousticTexture", "AkAcousticTexture"); + SetClassThumbnail(Style, "ClassThumbnail.AkAudioEvent", "AkAudioEvent"); + SetClassThumbnail(Style, "ClassThumbnail.AkAuxBus", "AkAuxBus"); + SetClassThumbnail(Style, "ClassThumbnail.AkAudioBank", "AkAudioBank"); + SetClassThumbnail(Style, "ClassThumbnail.AkInitBank", "AkAudioBank"); + SetClassThumbnail(Style, "ClassThumbnail.AkLocalizedMediaAsset", "AkLocalizedMediaAsset"); + SetClassThumbnail(Style, "ClassThumbnail.AkMediaAsset", "AkMediaAsset"); + SetClassThumbnail(Style, "ClassThumbnail.AkExternalMediaAsset", "AkExternalMediaAsset"); + SetClassThumbnail(Style, "ClassThumbnail.AkRtpc", "AkRtpc"); + SetClassThumbnail(Style, "ClassThumbnail.AkStateValue", "AkStateValue"); + SetClassThumbnail(Style, "ClassThumbnail.AkSwitchValue", "AkSwitchValue"); + SetClassThumbnail(Style, "ClassThumbnail.AkTrigger", "AkTrigger"); + SetClassThumbnail(Style, "ClassThumbnail.AkEffectShareSet", "AkEffectShareSet"); + + SetClassThumbnail(Style, "ClassThumbnail.AkAcousticPortal", "AK_Acoustic_Portal"); + SetClassIcon(Style, "ClassIcon.AkAcousticPortal", "AK_Acoustic_Portal_Explorer"); + SetClassThumbnail(Style, "ClassThumbnail.AkSpatialAudioVolume", "AK_Spatial_Audio_Volume"); + SetClassIcon(Style, "ClassIcon.AkSpatialAudioVolume", "AK_Spatial_Audio_Volume_Explorer"); + SetClassThumbnail(Style, "ClassThumbnail.AkReverbVolume", "AK_Reverb_Volume"); + SetClassIcon(Style, "ClassIcon.AkReverbVolume", "AK_Reverb_Volume_Explorer"); +} + +void FAkAudioStyle::Initialize() +{ + using namespace AkAudioStyle_Helpers; + + if (!StyleInstance.IsValid()) + { + StyleInstance = MakeShareable(new FSlateStyleSet(FAkAudioStyle::GetStyleSetName())); + auto ContentRoot = FPaths::EngineContentDir() / TEXT("Slate"); + StyleInstance->SetContentRoot(ContentRoot); + StyleInstance->SetCoreContentRoot(ContentRoot); + + FSlateStyleSet& Style = *StyleInstance.Get(); + { + SetAkResourceBrushes(Style); + + Style.Set("AudiokineticTools.GroupBorder", CreateEngineBoxBrush(Style, "Common/GroupBorder", FMargin(4.0f / 16.0f))); + Style.Set("AudiokineticTools.AssetDragDropTooltipBackground", CreateEngineBoxBrush(Style, "Old/Menu_Background", FMargin(8.0f / 64.0f))); + + FButtonStyle HoverHintOnly = FButtonStyle() + .SetNormal(FSlateNoResource()) + .SetHovered(EngineBoxBrush(Style, "Common/Button_Hovered", FMargin(4 / 16.0f), FLinearColor(1, 1, 1, 0.15f))) + .SetPressed(EngineBoxBrush(Style, "Common/Button_Pressed", FMargin(4 / 16.0f), FLinearColor(1, 1, 1, 0.25f))) + .SetNormalPadding(FMargin(0, 0, 0, 1)) + .SetPressedPadding(FMargin(0, 1, 0, 0)); + Style.Set("AudiokineticTools.HoverHintOnly", HoverHintOnly); + + Style.Set("AudiokineticTools.SourceTitleFont", FCoreStyle::GetDefaultFontStyle("Regular", 12)); + Style.Set("AudiokineticTools.SourceTreeItemFont", FCoreStyle::GetDefaultFontStyle("Regular", 10)); + Style.Set("AudiokineticTools.SourceTreeRootItemFont", FCoreStyle::GetDefaultFontStyle("Regular", 12)); + + const FVector2D Icon12x12(12.0f, 12.0f); + Style.Set("AudiokineticTools.Button_EllipsisIcon", CreateEngineImageBrush(Style, "Icons/ellipsis_12x", Icon12x12)); + } + + FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance); + } +} +#undef IMAGE_BRUSH + +void FAkAudioStyle::Shutdown() +{ + if (StyleInstance.IsValid()) + { + FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance); + ensure(StyleInstance.IsUnique()); + StyleInstance.Reset(); + } +} + +void FAkAudioStyle::DisplayEditorMessage(const FText& messageText, EWwiseItemType::Type wwiseItemType /* = EWwiseItemType::Type::None*/, float duration /* = 1.5f */) +{ + if (!FApp::CanEverRender()) + { + UE_LOG(LogAkAudio, Display, TEXT("DisplayEditorMessage: %s"), *messageText.ToString()); + return; + } + FNotificationInfo Info(messageText); + if (wwiseItemType == EWwiseItemType::None) + Info.Image = FAkAudioStyle::GetWwiseIcon(); + else + Info.Image = FAkAudioStyle::GetBrush(wwiseItemType); + Info.FadeInDuration = 0.1f; + Info.FadeOutDuration = 0.5f; + Info.ExpireDuration = duration; + Info.bUseThrobber = false; + Info.bUseSuccessFailIcons = false; + Info.bUseLargeFont = true; + Info.bFireAndForget = false; + Info.bAllowThrottleWhenFrameRateIsLow = false; + auto NotificationItem = FSlateNotificationManager::Get().AddNotification(Info); + NotificationItem->SetCompletionState(SNotificationItem::CS_None); + NotificationItem->ExpireAndFadeout(); +} + +FName FAkAudioStyle::GetStyleSetName() +{ + static FName StyleSetName(TEXT("AudiokineticToolsStyle")); + return StyleSetName; +} + +const ISlateStyle& FAkAudioStyle::Get() +{ + Initialize(); + return *StyleInstance; +} + +const FSlateBrush* FAkAudioStyle::GetWwiseIcon() +{ + auto& Style = Get(); + return Style.GetBrush("AudiokineticTools.WwiseIcon"); +} + +const FSlateBrush* FAkAudioStyle::GetBrush(EWwiseItemType::Type ItemType) +{ + auto& Style = Get(); + switch (ItemType) + { + case EWwiseItemType::Event: return Style.GetBrush("AudiokineticTools.EventIcon"); + case EWwiseItemType::AcousticTexture: return Style.GetBrush("AudiokineticTools.AcousticTextureIcon"); + case EWwiseItemType::AuxBus: return Style.GetBrush("AudiokineticTools.AuxBusIcon"); + case EWwiseItemType::Bus: return Style.GetBrush("AudiokineticTools.BusIcon"); + case EWwiseItemType::Folder: return Style.GetBrush("AudiokineticTools.FolderIcon"); + case EWwiseItemType::Project: return Style.GetBrush("AudiokineticTools.ProjectIcon"); + case EWwiseItemType::PhysicalFolder: return Style.GetBrush("AudiokineticTools.PhysicalFolderIcon"); + case EWwiseItemType::StandaloneWorkUnit: + case EWwiseItemType::NestedWorkUnit: return Style.GetBrush("AudiokineticTools.WorkUnitIcon"); + case EWwiseItemType::ActorMixer: return Style.GetBrush("AudiokineticTools.ActorMixerIcon"); + case EWwiseItemType::Sound: return Style.GetBrush("AudiokineticTools.SoundIcon"); + case EWwiseItemType::SwitchContainer: return Style.GetBrush("AudiokineticTools.SwitchContainerIcon"); + case EWwiseItemType::RandomSequenceContainer: return Style.GetBrush("AudiokineticTools.RandomSequenceContainerIcon"); + case EWwiseItemType::BlendContainer: return Style.GetBrush("AudiokineticTools.BlendContainerIcon"); + case EWwiseItemType::MotionBus: return Style.GetBrush("AudiokineticTools.MotionBusIcon"); + case EWwiseItemType::GameParameter: return Style.GetBrush("AudiokineticTools.RTPCIcon"); + case EWwiseItemType::State: return Style.GetBrush("AudiokineticTools.StateIcon"); + case EWwiseItemType::StateGroup: return Style.GetBrush("AudiokineticTools.StateGroupIcon"); + case EWwiseItemType::Switch: return Style.GetBrush("AudiokineticTools.SwitchIcon"); + case EWwiseItemType::SwitchGroup: return Style.GetBrush("AudiokineticTools.SwitchGroupIcon"); + case EWwiseItemType::Trigger: return Style.GetBrush("AudiokineticTools.TriggerIcon"); + case EWwiseItemType::EffectShareSet: return Style.GetBrush("AudiokineticTools.EffectShareSetIcon"); + + default: + return nullptr; + } +} + +const FSlateBrush* FAkAudioStyle::GetBrush(FName PropertyName, const ANSICHAR* Specifier) +{ + return Get().GetBrush(PropertyName, Specifier); +} + +const FSlateFontInfo FAkAudioStyle::GetFontStyle(FName PropertyName, const ANSICHAR* Specifier) +{ + return Get().GetFontStyle(PropertyName, Specifier); +} + +UMaterial* FAkAudioStyle::GetAkForegroundTextMaterial() +{ + if (TextMaterial == nullptr) + { + TextMaterial = AkAudioStyle_Helpers::LoadAkMaterial(FString(""), FString("DefaultForegroundTextMaterial")); + if (TextMaterial != nullptr) + TextMaterial->AddToRoot(); + } + + return TextMaterial; +} + +FLinearColor FAkAudioStyle::GetWwiseObjectColor(int colorIndex) +{ + if (WwiseUnrealColorPalette.Num() > 0) + { + if (colorIndex == -1) + colorIndex = WwiseUnrealColorPalette.Num() - 1; + if (colorIndex < WwiseUnrealColorPalette.Num()) + { + return WwiseUnrealColorPalette[colorIndex]; + } + } + return FLinearColor(); +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAudioType.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAudioType.cpp new file mode 100644 index 0000000..3ea3cd4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAudioType.cpp @@ -0,0 +1,328 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkAudioType.h" + +#include "AkAudioModule.h" +#include "AkAudioDevice.h" +#include "AkSettings.h" +#include "AkCustomVersion.h" +#include "Platforms/AkPlatformInfo.h" + +#if WITH_EDITORONLY_DATA +#include "Wwise/WwiseResourceCooker.h" +#endif + +UAkAudioType::~UAkAudioType() +{ + SCOPED_AKAUDIO_EVENT_3(TEXT("UAkAudioType Dtor")); + ResourceUnload.Wait(); +} + +void UAkAudioType::Serialize(FArchive& Ar) +{ + Super::Serialize(Ar); + + if (HasAnyFlags(RF_ClassDefaultObject)) + { + return; + } + + Ar.UsingCustomVersion(FAkCustomVersion::GUID); + +#if WITH_EDITORONLY_DATA + CheckWwiseObjectInfo(); +#endif + LogSerializationState(Ar); +} + +void UAkAudioType::PostLoad() +{ + Super::PostLoad(); + + if (LIKELY(bAutoLoad)) + { + if (LIKELY(CanLoadObjects())) + { + LoadData(); + } + else + { + LoadOnceSoundEngineReady(); + } + } +} + +void UAkAudioType::BeginDestroy() +{ + if (HasAnyFlags(RF_ClassDefaultObject)) + { + return Super::BeginDestroy(); + } + + SCOPED_AKAUDIO_EVENT_F_2(TEXT("UAkAudioType::BeginDestroy %s"), *GetClass()->GetName()); + UE_LOG(LogAkAudio, Verbose, TEXT("UAkAudioType::BeginDestroy %s %s"), *GetClass()->GetName(), *GetName()); + + UnloadData(true); + Super::BeginDestroy(); +} + +void UAkAudioType::LogSerializationState(const FArchive& Ar) +{ + FString SerializationState = TEXT(""); + if (Ar.IsLoading()) + { + SerializationState += TEXT("Loading"); + } + + if (Ar.IsSaving()) + { + SerializationState += TEXT("Saving"); + } + + if (Ar.IsCooking()) + { + SerializationState += TEXT("Cooking"); + } + + UE_LOG(LogAkAudio, VeryVerbose, TEXT("%s - Serialization - %s"), *GetName(), *SerializationState); + +} + +AkUInt32 UAkAudioType::GetShortID() const +{ + UE_LOG(LogAkAudio, Error, TEXT("Trying to GetShortID without an overridden Short ID for %s."), *GetName()); + return AK_INVALID_UNIQUE_ID; +} + +#if WITH_EDITORONLY_DATA +void UAkAudioType::MigrateWwiseObjectInfo() +{ + if (FWwiseObjectInfo* Info = GetInfoMutable()) + { + Info->WwiseName = FName(GetName()); + + if ( ID_DEPRECATED.IsValid()) + { + Info->WwiseGuid = ID_DEPRECATED; + } + + if (ShortID_DEPRECATED != 0) + { + Info->WwiseShortId = ShortID_DEPRECATED; + } + else + { + Info->WwiseShortId = FAkAudioDevice::GetShortIDFromString(GetName()); + } + } +} + +void UAkAudioType::WaitForResourceUnloaded() +{ + SCOPED_AKAUDIO_EVENT_3(TEXT("UAkAudioType::WaitForResourceUnloaded")); + ResourceUnload.Wait(); + ResourceUnload.Reset(); +} + +void UAkAudioType::CheckWwiseObjectInfo() +{ + if (FWwiseObjectInfo* WwiseInfo = GetInfoMutable()) + { + if (!WwiseInfo->WwiseGuid.IsValid() || WwiseInfo->WwiseShortId == 0 || WwiseInfo->WwiseName.ToString().IsEmpty()) + { + UE_LOG(LogAkAudio, Log, TEXT("CheckWwiseObjectInfo: Wwise Asset %s has empty WwiseObjectInfo fields. WwiseName: '%s' - WwiseShortId: '%d' - WwiseGuid: '%s'"), + *GetName(), *WwiseInfo->WwiseName.ToString(), WwiseInfo->WwiseShortId, *WwiseInfo->WwiseGuid.ToString()); + } + } +} + +FWwiseObjectInfo* UAkAudioType::GetInfoMutable() +{ + UE_LOG(LogAkAudio, Error, TEXT("GetInfoMutable not implemented")); + check(false); + return nullptr; +} + +void UAkAudioType::ValidateShortID(FWwiseObjectInfo& WwiseInfo) const +{ + if (WwiseInfo.WwiseShortId == AK_INVALID_UNIQUE_ID) + { + if (!WwiseInfo.WwiseName.ToString().IsEmpty()) + { + WwiseInfo.WwiseShortId = FAkAudioDevice::GetShortIDFromString(WwiseInfo.WwiseName.ToString()); + } + else + { + WwiseInfo.WwiseShortId = FAkAudioDevice::GetShortIDFromString(GetName()); + UE_LOG(LogAkAudio, Warning, TEXT("UAkAudioType::ValidateShortID : Using ShortID based on asset name for %s."), *GetName()); + } + } +} + +#if WITH_EDITOR +void UAkAudioType::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + FWwiseObjectInfo* AudioTypeInfo = GetInfoMutable(); + + if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet) + { + if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(FWwiseObjectInfo, WwiseName)) + { + AudioTypeInfo->WwiseGuid = {}; + AudioTypeInfo->WwiseShortId = 0; + } + else if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(FWwiseEventInfo, WwiseShortId)) + { + AudioTypeInfo->WwiseGuid= {}; + AudioTypeInfo->WwiseName = ""; + } + // The first check should be sufficient, but the property's FName for GUIDs is A/B/C/D, + // depending on which part of the GUID was modified + else if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(FWwiseEventInfo, WwiseGuid) || + PropertyChangedEvent.Property->Owner.GetFName() == "Guid") + { + AudioTypeInfo->WwiseName = ""; + AudioTypeInfo->WwiseShortId = {}; + } + else if(PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UAkAudioType, bAutoLoad)) + { + if(!bAutoLoad) + { + UnloadData(); + } + // Else not necessary, we will load the data just before exiting the function + } + FillInfo(); + } + + if (bAutoLoad) + { + LoadData(); + } +} + +void UAkAudioType::GetAssetRegistryTags(TArray& OutTags) const +{ + UObject::GetAssetRegistryTags(OutTags); + + auto WwiseInfo = GetInfo(); + if (WwiseInfo.WwiseGuid.IsValid()) + { + //This seems to be more reliable than putting the AssetRegistrySearchable tag on FWwiseObjectInfo::WwiseGuid + OutTags.Add(FAssetRegistryTag(GET_MEMBER_NAME_CHECKED(FWwiseObjectInfo, WwiseGuid), WwiseInfo.WwiseGuid.ToString(), FAssetRegistryTag::ETagType::TT_Hidden)); + } + OutTags.Add(FAssetRegistryTag(GET_MEMBER_NAME_CHECKED(FWwiseObjectInfo, WwiseShortId), FString::FromInt(WwiseInfo.WwiseShortId), FAssetRegistryTag::ETagType::TT_Hidden)); +} +#endif + +void UAkAudioType::BeginCacheForCookedPlatformData(const ITargetPlatform* TargetPlatform) +{ + UObject::BeginCacheForCookedPlatformData(TargetPlatform); + if (HasAnyFlags(RF_ClassDefaultObject)) + { + return; + } + auto PlatformID = UAkPlatformInfo::GetSharedPlatformInfo(TargetPlatform->IniPlatformName()); + FWwiseResourceCooker::CreateForPlatform(TargetPlatform, PlatformID, EWwiseExportDebugNameRule::Name); +} + +bool UAkAudioType::IsAssetOutOfDate(const FWwiseAnyRef& CurrentWwiseRef) +{ + FWwiseObjectInfo* ObjectInfo = GetInfoMutable(); + if (CurrentWwiseRef.GetGuid() != ObjectInfo->WwiseGuid + || CurrentWwiseRef.GetName() != ObjectInfo->WwiseName + || CurrentWwiseRef.GetId() != ObjectInfo->WwiseShortId) + { + return true; + } + + return false; +} + +void UAkAudioType::FillInfo(const FWwiseAnyRef& CurrentWwiseRef) +{ + FWwiseObjectInfo* ObjectInfo = GetInfoMutable(); + + ObjectInfo->WwiseGuid = CurrentWwiseRef.GetGuid(); + ObjectInfo->WwiseShortId = CurrentWwiseRef.GetId(); + ObjectInfo->WwiseName = CurrentWwiseRef.GetName(); +} + +FName UAkAudioType::GetAssetDefaultName() +{ + const FName WwiseGroupName = GetWwiseGroupName(); + const FName WwiseAssetName = GetWwiseName(); + + if (!WwiseGroupName.IsNone()) + { + FNameBuilder DefaultName; + DefaultName << WwiseGroupName << TEXT("-") << WwiseAssetName; +#if UE_5_0_OR_LATER + return FName(DefaultName.ToView()); +#else + return FName(DefaultName.ToString()); +#endif + } + + return WwiseAssetName; +} + +FName UAkAudioType::GetAssetDefaultPackagePath() +{ + auto AkSettings = GetMutableDefault(); + if (!AkSettings) + { + UE_LOG(LogAkAudio, Error, TEXT("Could not fetch AkSettings")); + return {}; + } + + FName GroupName = GetWwiseGroupName(); + + if (GroupName.IsNone()) + { + return FName(AkSettings->DefaultAssetCreationPath); + } + + return FName(AkSettings->DefaultAssetCreationPath / GroupName.ToString()); +} + +#endif + +bool UAkAudioType::CanLoadObjects() +{ + if (UNLIKELY(!FAkAudioModule::AkAudioModuleInstance)) + { + return false; + } + + auto AudioDevice = FAkAudioDevice::Get(); + if (UNLIKELY(!AudioDevice)) + { + return false; + } + + return LIKELY(!IsEngineExitRequested()) && LIKELY(AudioDevice->IsInitialized()); +} + +void UAkAudioType::LoadOnceSoundEngineReady() +{ + UE_LOG(LogAkAudio, Verbose, TEXT("UAkAudioType::LoadOnceSoundEngineReady: Delay loading %s once SoundEngine is initialized."), *GetName()); + FAkAudioDevice::LoadAudioObjectsAfterInitialization(MakeWeakObjectPtr(this)); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAuxBus.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAuxBus.cpp new file mode 100644 index 0000000..18e97cc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkAuxBus.cpp @@ -0,0 +1,191 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkAuxBus.h" +#include "AkAudioBank.h" +#include "Wwise/WwiseResourceLoader.h" +#include "AkInclude.h" +#include "AkAudioDevice.h" +#include "Wwise/Stats/AkAudio.h" + +#if WITH_EDITORONLY_DATA +#include "Wwise/WwiseProjectDatabase.h" +#include "Wwise/WwiseResourceCooker.h" +#endif + +void UAkAuxBus::Serialize(FArchive& Ar) +{ + Super::Serialize(Ar); + + if (HasAnyFlags(RF_ClassDefaultObject)) + { + return; + } +#if !UE_SERVER +#if WITH_EDITORONLY_DATA + if (Ar.IsCooking() && Ar.IsSaving() && !Ar.CookingTarget()->IsServerOnly()) + { + FWwiseLocalizedAuxBusCookedData CookedDataToArchive; + if (auto* ResourceCooker = FWwiseResourceCooker::GetForArchive(Ar)) + { + ResourceCooker->PrepareCookedData(CookedDataToArchive, GetValidatedInfo(AuxBusInfo)); + } + CookedDataToArchive.Serialize(Ar); + } +#else + AuxBusCookedData.Serialize(Ar); +#endif +#endif + +} + +void UAkAuxBus::LoadAuxBus() +{ + SCOPED_AKAUDIO_EVENT_2(TEXT("LoadAuxBus")); + auto* ResourceLoader = FWwiseResourceLoader::Get(); + if (UNLIKELY(!ResourceLoader)) + { + return; + } + + if (LoadedAuxBus) + { + UnloadAuxBus(false); + } + +#if WITH_EDITORONLY_DATA + if (IWwiseProjectDatabaseModule::IsInACookingCommandlet()) + { + return; + } + auto* ProjectDatabase = FWwiseProjectDatabase::Get(); + if (!ProjectDatabase || !ProjectDatabase->IsProjectDatabaseParsed()) + { + UE_LOG(LogAkAudio, VeryVerbose, TEXT("UAkAuxBus::LoadAuxBus: Not loading '%s' because project database is not parsed."), *GetName()) + return; + } + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + return; + } + if (UNLIKELY(!ResourceCooker->PrepareCookedData(AuxBusCookedData, GetValidatedInfo(AuxBusInfo)))) + { + return; + } +#endif + + LoadedAuxBus = ResourceLoader->LoadAuxBus(AuxBusCookedData); +} + +void UAkAuxBus::UnloadAuxBus(bool bAsync) +{ + if (LoadedAuxBus) + { + auto* ResourceLoader = FWwiseResourceLoader::Get(); + if (UNLIKELY(!ResourceLoader)) + { + return; + } + + if (bAsync) + { + FWwiseLoadedAuxBusPromise Promise; + Promise.EmplaceValue(MoveTemp(LoadedAuxBus)); + ResourceUnload = ResourceLoader->UnloadAuxBusAsync(Promise.GetFuture()); + } + else + { + ResourceLoader->UnloadAuxBus(MoveTemp(LoadedAuxBus)); + } + LoadedAuxBus = nullptr; + } +} + +#if WITH_EDITORONLY_DATA +void UAkAuxBus::CookAdditionalFilesOverride(const TCHAR* PackageFilename, const ITargetPlatform* TargetPlatform, + TFunctionRef WriteAdditionalFile) +{ + if (HasAnyFlags(RF_ClassDefaultObject)) + { + return; + } + + FWwiseResourceCooker* ResourceCooker = FWwiseResourceCooker::GetForPlatform(TargetPlatform); + if (!ResourceCooker) + { + return; + } + ResourceCooker->SetSandboxRootPath(PackageFilename); + + ResourceCooker->CookAuxBus(GetValidatedInfo(AuxBusInfo), WriteAdditionalFile); +} + +bool UAkAuxBus::ObjectIsInSoundBanks() +{ + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkAuxBus::GetWwiseRef: ResourceCooker not initialized")); + return false; + } + + auto ProjectDatabase = ResourceCooker->GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkAuxBus::GetWwiseRef: ProjectDatabase not initialized")); + return false; + } + + FWwiseObjectInfo* AudioTypeInfo = &AuxBusInfo; + const FWwiseRefAuxBus AudioTypeRef = FWwiseDataStructureScopeLock(*ProjectDatabase).GetAuxBus( + GetValidatedInfo(AuxBusInfo)); + + return AudioTypeRef.IsValid(); +} + +void UAkAuxBus::FillInfo() +{ + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkAuxBus::FillInfo: ResourceCooker not initialized")); + return; + } + + auto ProjectDatabase = ResourceCooker->GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkAuxBus::FillInfo: ProjectDatabase not initialized")); + return; + } + + FWwiseObjectInfo* AudioTypeInfo = &AuxBusInfo; + const FWwiseRefAuxBus AudioTypeRef = FWwiseDataStructureScopeLock(*ProjectDatabase).GetAuxBus( + GetValidatedInfo(AuxBusInfo)); + + if (AudioTypeRef.AuxBusName().IsNone() || !AudioTypeRef.AuxBusGuid().IsValid() || AudioTypeRef.AuxBusId() == AK_INVALID_UNIQUE_ID) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkAuxBus::FillInfo: Valid object not found in Project Database")); + return; + } + + AudioTypeInfo->WwiseName = AudioTypeRef.AuxBusName(); + AudioTypeInfo->WwiseGuid = AudioTypeRef.AuxBusGuid(); + AudioTypeInfo->WwiseShortId = AudioTypeRef.AuxBusId(); +} +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkCallbackInfoPool.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkCallbackInfoPool.cpp new file mode 100644 index 0000000..8299077 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkCallbackInfoPool.cpp @@ -0,0 +1,43 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkCallbackInfoPool.h" +#include "AkGameplayTypes.h" + +UAkCallbackInfo* AkCallbackInfoPool::InternalAcquire(UClass* type) +{ + ensure(IsInGameThread()); + auto& poolArray = Pool.FindOrAdd(type); + if (poolArray.Num() > 0) + { + return poolArray.Pop(); + } + + auto instance = NewObject((UObject*)GetTransientPackage(), type, NAME_None, RF_Public | RF_Standalone); + gcStorage.Emplace(instance); + return instance; +} + +void AkCallbackInfoPool::Release(UAkCallbackInfo* instance) +{ + ensure(IsInGameThread()); + if (Pool.Contains(instance->GetClass())) + { + instance->Reset(); + Pool[instance->GetClass()].Push(instance); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkComponent.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkComponent.cpp new file mode 100644 index 0000000..dc476c9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkComponent.cpp @@ -0,0 +1,1126 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkComponent.cpp: +=============================================================================*/ + +#include "AkComponent.h" + +#include "AkAudioDevice.h" +#include "AkAudioEvent.h" +#include "AkAuxBus.h" +#include "AkLateReverbComponent.h" +#include "AkRoomComponent.h" +#include "AkGameplayTypes.h" +#include "AkSettings.h" +#include "AkSpotReflector.h" +#include "AkSwitchValue.h" +#include "AkTrigger.h" +#include "Components/BillboardComponent.h" +#include "DrawDebugHelpers.h" +#include "Engine/Texture2D.h" +#include "Engine/World.h" +#include "GameFramework/PlayerController.h" +#include "Wwise/WwiseExternalSourceManager.h" +#include "Wwise/API/WwiseSoundEngineAPI.h" +#include "Wwise/API/WwiseSpatialAudioAPI.h" + +#if WITH_EDITOR +#include "LevelEditorViewport.h" +#include "Editor.h" +#endif + +/*------------------------------------------------------------------------------------ +Component Helpers +------------------------------------------------------------------------------------*/ +namespace UAkComponentUtils +{ + APlayerController* GetAPlayerController(const UActorComponent* Component) + { + const APlayerCameraManager* AsPlayerCameraManager = Cast(Component->GetOwner()); + return AsPlayerCameraManager ? AsPlayerCameraManager->GetOwningPlayerController() : nullptr; + } + + void GetListenerPosition(const UAkComponent* Component, FVector& Location, FVector& Front, FVector& Up) + { + APlayerController* pPlayerController = GetAPlayerController(Component); + if (pPlayerController != nullptr) + { + FVector Right; + pPlayerController->GetAudioListenerPosition(Location, Front, Right); + Up = FVector::CrossProduct(Front, Right); + return; + } + +#if WITH_EDITORONLY_DATA + auto& Clients = GEditor->GetAllViewportClients(); + static FTransform LastKnownEditorTransform; + for (int i = 0; i < Clients.Num(); i++) + { + FEditorViewportClient* ViewportClient = Clients[i]; + UWorld* World = ViewportClient->GetWorld(); + if (ViewportClient->Viewport && ViewportClient->Viewport->HasFocus() && World->AllowAudioPlayback()) + { + EWorldType::Type WorldType = World->WorldType; + if (WorldType == EWorldType::Editor || WorldType == EWorldType::PIE) + { + LastKnownEditorTransform = FAkAudioDevice::Get()->GetEditorListenerPosition(i); + Location = LastKnownEditorTransform.GetLocation(); + Front = LastKnownEditorTransform.GetRotation().GetForwardVector(); + Up = LastKnownEditorTransform.GetRotation().GetUpVector(); + return; + } + else if (WorldType != EWorldType::Game && WorldType != EWorldType::GamePreview) + { + Location = ViewportClient->GetViewLocation(); + Front = ViewportClient->GetViewRotation().Quaternion().GetForwardVector(); + Up = ViewportClient->GetViewRotation().Quaternion().GetUpVector(); + LastKnownEditorTransform.SetLocation(Location); + LastKnownEditorTransform.SetRotation(ViewportClient->GetViewRotation().Quaternion()); + return; + } + } + } + + Location = LastKnownEditorTransform.GetLocation(); + Front = LastKnownEditorTransform.GetRotation().GetForwardVector(); + Up = LastKnownEditorTransform.GetRotation().GetUpVector(); +#endif + } + + void GetLocationFrontUp(const UAkComponent* Component, FVector& Location, FVector& Front, FVector& Up) + { + if (Component->IsDefaultListener) + { + GetListenerPosition(Component, Location, Front, Up); + } + else + { + auto& Transform = Component->GetComponentTransform(); + Location = Transform.GetTranslation(); + Front = Transform.GetUnitAxis(EAxis::X); + Up = Transform.GetUnitAxis(EAxis::Z); + } + } +} + +AkReverbFadeControl::AkReverbFadeControl(const UAkLateReverbComponent& LateReverbComponent) + : AuxBusId(LateReverbComponent.GetAuxBusId()) + , bIsFadingOut(false) + , FadeControlUniqueId((void*)&LateReverbComponent) + , CurrentControlValue(0.f) + , TargetControlValue(LateReverbComponent.SendLevel) + , FadeRate(LateReverbComponent.FadeRate) + , Priority(LateReverbComponent.Priority) +{} + +void AkReverbFadeControl::UpdateValues(const UAkLateReverbComponent& LateReverbComponent) +{ + AuxBusId = LateReverbComponent.GetAuxBusId(); + TargetControlValue = LateReverbComponent.SendLevel; + FadeRate = LateReverbComponent.FadeRate; + Priority = LateReverbComponent.Priority; +} + +bool AkReverbFadeControl::Update(float DeltaTime) +{ + if (CurrentControlValue != TargetControlValue || bIsFadingOut) + { + // Rate (%/s) * Delta (s) = % for given delta, apply to target. + const float Increment = DeltaTime * FadeRate * TargetControlValue; + if (bIsFadingOut) + { + CurrentControlValue -= Increment; + if (CurrentControlValue <= 0.f) + return false; + } + else + CurrentControlValue = FMath::Min(CurrentControlValue + Increment, TargetControlValue); + } + + return true; +} + +AkAuxSendValue AkReverbFadeControl::ToAkAuxSendValue() const +{ + AkAuxSendValue ret; + ret.listenerID = AK_INVALID_GAME_OBJECT; + ret.auxBusID = AuxBusId; + ret.fControlValue = CurrentControlValue; + return ret; +} + +bool AkReverbFadeControl::Prioritize(const AkReverbFadeControl& A, const AkReverbFadeControl& B) +{ + if (A.bIsFadingOut == B.bIsFadingOut) + { + if (A.Priority == B.Priority) + { + // Sort by bus id if priority and fade are equal, to ensure comparisons in UAkComponent::NeedToUpdateAuxSends dont lead to continuous aux sends updates, when there are overlapping reverbs. + return A.AuxBusId < B.AuxBusId; + } + return A.Priority > B.Priority; + } + // Ensure the fading out buffers are sent to the end of the array. + return A.bIsFadingOut < B.bIsFadingOut; +} + +/*------------------------------------------------------------------------------------ + UAkComponent +------------------------------------------------------------------------------------*/ + +UAkComponent::UAkComponent(const class FObjectInitializer& ObjectInitializer) : +Super(ObjectInitializer) +{ + // Property initialization + + DrawFirstOrderReflections = false; + DrawSecondOrderReflections = false; + DrawHigherOrderReflections = false; + DrawDiffraction = false; + + EarlyReflectionBusSendGain = 1.f; + + StopWhenOwnerDestroyed = true; + bUseReverbVolumes = true; + OcclusionRefreshInterval = 0.2f; + + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.TickGroup = TG_DuringPhysics; + PrimaryComponentTick.bAllowTickOnDedicatedServer = false; + bTickInEditor = true; + + bAutoActivate = true; + bNeverNeedsRenderUpdate = true; + bWantsOnUpdateTransform = true; + +#if WITH_EDITORONLY_DATA + bVisualizeComponent = true; +#endif + + AttenuationScalingFactor = 1.0f; + bAutoDestroy = false; + bUseDefaultListeners = true; + + OcclusionCollisionChannel = EAkCollisionChannel::EAKCC_UseIntegrationSettingsDefault; + + outerRadius = 0.0f; + innerRadius = 0.0f; +} + +ECollisionChannel UAkComponent::GetOcclusionCollisionChannel() +{ + return UAkSettings::ConvertOcclusionCollisionChannel(OcclusionCollisionChannel.GetValue()); +} + + +void UAkComponent::PostAssociatedAkEventAndWaitForEndAsync(int32& PlayingID, FLatentActionInfo LatentInfo) +{ + PostAkEventAndWaitForEndAsync(AkAudioEvent, PlayingID, LatentInfo); +} + +int32 UAkComponent::PostAssociatedAkEventAndWaitForEnd(FLatentActionInfo LatentInfo) +{ + return PostAkEventAndWaitForEnd(AkAudioEvent, EventName, LatentInfo); +} + +AkPlayingID UAkComponent::PostAkEventByNameWithDelegate(UAkAudioEvent* AkEvent, const FString& in_EventName, int32 CallbackMask, + const FOnAkPostEventCallback& PostEventCallback) +{ + AkPlayingID PlayingID = AK_INVALID_PLAYING_ID; + + if (AkEvent) + { + return AkEvent->PostOnComponent(this, PostEventCallback, CallbackMask, StopWhenOwnerDestroyed); + } + + auto AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + const AkUInt32 ShortID = AudioDevice->GetShortID(AkEvent, in_EventName); + PlayingID = AudioDevice->PostEventOnAkGameObject(ShortID, this, PostEventCallback, CallbackMask, {}); + } + + return PlayingID; +} + +AkPlayingID UAkComponent::PostAkEventByIdWithCallback(const AkUInt32 EventShortID, AkUInt32 Flags /*= 0*/, + AkCallbackFunc UserCallback/*= NULL*/, void * UserCookie /*= NULL*/, const TArray& ExternalSources) +{ + AkPlayingID PlayingID = AK_INVALID_PLAYING_ID; + + auto AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + PlayingID = AudioDevice->PostEventOnAkComponent(EventShortID, this, Flags, UserCallback, UserCookie, ExternalSources); + } + + return PlayingID; +} + +int32 UAkComponent::PostAkEventAndWaitForEnd(class UAkAudioEvent * AkEvent, const FString& in_EventName, FLatentActionInfo LatentInfo) +{ + if (LIKELY(!AkEvent)) + { + return AkEvent->PostOnComponentAndWait(this, StopWhenOwnerDestroyed, LatentInfo); + } + + AkPlayingID PlayingID = AK_INVALID_PLAYING_ID; + + auto AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + const AkUInt32 ShortID = AudioDevice->GetShortID(AkEvent, in_EventName); + auto* World = GetWorld(); + if (UNLIKELY(!World)) + { + UE_LOG(LogAkAudio, Log, TEXT("Failed to post latent AkAudioEvent with actor '%s' world that's not valid."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + FLatentActionManager& LatentActionManager = World->GetLatentActionManager(); + FWaitEndOfEventAction* LatentAction = new FWaitEndOfEventAction(LatentInfo); + LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, LatentAction); + PlayingID = AudioDevice->PostEventOnComponentWithLatentAction(ShortID, this, LatentAction); + if (PlayingID == AK_INVALID_PLAYING_ID) + { + LatentAction->EventFinished = true; + } + } + + return PlayingID; +} + +void UAkComponent::PostAkEventAndWaitForEndAsync(UAkAudioEvent* AkEvent, int32& PlayingID, FLatentActionInfo LatentInfo) +{ + if (!AkEvent) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkComponent::PostAkEventAndWaitForEnd: No Event specified!")); + PlayingID = AK_INVALID_PLAYING_ID; + return; + } + + PlayingID = AkEvent->PostOnComponentAndWait(this, StopWhenOwnerDestroyed, LatentInfo); +} + +int32 UAkComponent::PostAkEvent(UAkAudioEvent* AkEvent, int32 CallbackMask, + const FOnAkPostEventCallback& PostEventCallback, const FString& InEventName) +{ + if (LIKELY(IsValid(AkEvent))) + { + return AkEvent->PostOnComponent(this, PostEventCallback, CallbackMask, StopWhenOwnerDestroyed); + } + + AkPlayingID playingID = AK_INVALID_PLAYING_ID; + + auto AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + playingID = AudioDevice->PostEventOnAkGameObject(AudioDevice->GetShortID(AkEvent, InEventName), this, PostEventCallback, CallbackMask, {}); + if (playingID != AK_INVALID_PLAYING_ID) + { + bEventPosted = true; + } + } + + return playingID; + +} + +AkPlayingID UAkComponent::PostAkEvent(UAkAudioEvent* AkEvent, AkUInt32 Flags, AkCallbackFunc UserCallback, + void* UserCookie) +{ + if (UNLIKELY(!IsValid(AkEvent))) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to post invalid AkAudioEvent on component '%s'."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + return AkEvent->PostOnComponent(this, nullptr, UserCallback, UserCookie, static_cast(Flags), nullptr, StopWhenOwnerDestroyed); +} + +AkRoomID UAkComponent::GetSpatialAudioRoom() const +{ + AkRoomID RoomID; + if (CurrentRoom) + { + RoomID = CurrentRoom->GetRoomID(); + } + return RoomID; +} + +void UAkComponent::PostTrigger(const UAkTrigger* TriggerValue, FString Trigger) +{ + if (FAkAudioDevice::Get()) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + if (TriggerValue) + { + SoundEngine->PostTrigger(TriggerValue->TriggerCookedData.TriggerId, GetAkGameObjectID()); + } + else + { + SoundEngine->PostTrigger(TCHAR_TO_AK(*Trigger), GetAkGameObjectID()); + } + } +} + +void UAkComponent::SetSwitch(const UAkSwitchValue* SwitchValue, FString SwitchGroup, FString SwitchState) +{ + if (FAkAudioDevice::Get()) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + if (SwitchValue) + { + SoundEngine->SetSwitch(SwitchValue->GroupValueCookedData.GroupId, SwitchValue->GroupValueCookedData.Id, GetAkGameObjectID()); + } + else + { + uint32 SwitchGroupID = SoundEngine->GetIDFromString(TCHAR_TO_AK(*SwitchGroup)); + uint32 SwitchStateID = SoundEngine->GetIDFromString(TCHAR_TO_AK(*SwitchState)); + + SoundEngine->SetSwitch(SwitchGroupID, SwitchStateID, GetAkGameObjectID()); + } + } +} + +void UAkComponent::SetStopWhenOwnerDestroyed(bool bStopWhenOwnerDestroyed) +{ + StopWhenOwnerDestroyed = bStopWhenOwnerDestroyed; +} + +void UAkComponent::SetListeners(const TArray& NewListeners) +{ + auto AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + if (!bUseDefaultListeners) + { + for (auto Listener : Listeners) + { + Listener->Emitters.Remove(this); + } + } + + bUseDefaultListeners = false; + + Listeners.Reset(); + Listeners.Append(NewListeners); + + for (auto Listener : Listeners) + { + Listener->Emitters.Add(this); + } + + AudioDevice->SetListeners(this, Listeners.Array()); + } +} + +void UAkComponent::UseReverbVolumes(bool inUseReverbVolumes) +{ + bUseReverbVolumes = inUseReverbVolumes; +} + +void UAkComponent::UseEarlyReflections( + class UAkAuxBus* AuxBus, + int Order, + float BusSendGain, + float MaxPathLength, + bool SpotReflectors, + const FString& AuxBusName) +{ + // Deprecated +} + +void UAkComponent::SetEarlyReflectionsAuxBus(const FString& AuxBusName) +{ + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + AudioDevice->SetEarlyReflectionsAuxBus(this, FAkAudioDevice::GetShortID(nullptr, AuxBusName)); + } +} + +void UAkComponent::SetEarlyReflectionsVolume(float SendVolume) +{ + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + AudioDevice->SetEarlyReflectionsVolume(this, SendVolume); + } +} + +float UAkComponent::GetAttenuationRadius() const +{ + return AkAudioEvent ? AttenuationScalingFactor * AkAudioEvent->MaxAttenuationRadius : 0.f; +} + +void UAkComponent::SetOutputBusVolume(float BusVolume) +{ + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + for (auto It = Listeners.CreateIterator(); It; ++It) + { + AudioDevice->SetGameObjectOutputBusVolume(this, *It, BusVolume); + } + } +} + +void UAkComponent::OnRegister() +{ + UWorld* CurrentWorld = GetWorld(); + if(!IsRegisteredWithWwise && CurrentWorld->WorldType != EWorldType::Inactive && CurrentWorld->WorldType != EWorldType::None) + RegisterGameObject(); // Done before parent so that OnUpdateTransform follows registration and updates position correctly. + + ObstructionService.Init(this, OcclusionRefreshInterval); + + // It's possible for OnRegister to be called while the WorldType is inactive. + // The game object will be registered again later when the WorldType is active. + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice && IsRegisteredWithWwise) + { + if (EarlyReflectionAuxBus || !EarlyReflectionAuxBusName.IsEmpty()) + { + AkUInt32 AuxBusID = FAkAudioDevice::GetShortID(EarlyReflectionAuxBus, EarlyReflectionAuxBusName); + if (AuxBusID != AK_INVALID_UNIQUE_ID) + AudioDevice->SetEarlyReflectionsAuxBus(this, AuxBusID); + } + if (EarlyReflectionBusSendGain != 1.0) + AudioDevice->SetEarlyReflectionsVolume(this, EarlyReflectionBusSendGain); + } + + Super::OnRegister(); + +#if WITH_EDITORONLY_DATA + UpdateSpriteTexture(); +#endif +} + +#if WITH_EDITORONLY_DATA +void UAkComponent::UpdateSpriteTexture() +{ + if (SpriteComponent) + { + SpriteComponent->SetSprite(LoadObject(NULL, TEXT("/Wwise/S_AkComponent.S_AkComponent"))); + } +} +#endif + +void UAkComponent::OnUnregister() +{ + // Route OnUnregister event. + Super::OnUnregister(); + + // Don't stop audio and clean up component if owner has been destroyed (default behaviour). This function gets + // called from AActor::ClearComponents when an actor gets destroyed which is not usually what we want for one- + // shot sounds. + AActor* Owner = GetOwner(); + UWorld* CurrentWorld = GetWorld(); + if( !Owner || !CurrentWorld || StopWhenOwnerDestroyed || CurrentWorld->bIsTearingDown || (Owner->GetClass() == APlayerController::StaticClass() && CurrentWorld->WorldType == EWorldType::PIE)) + { + Stop(); + } +} + +void UAkComponent::OnComponentDestroyed( bool bDestroyingHierarchy ) +{ + UnregisterGameObject(); + Super::OnComponentDestroyed(bDestroyingHierarchy); +} + +void UAkComponent::ShutdownAfterError( void ) +{ + UnregisterGameObject(); + + Super::ShutdownAfterError(); +} + +bool UAkComponent::NeedToUpdateAuxSends(const TArray& NewValues) +{ + if (NewValues.Num() != CurrentAuxSendValues.Num()) + return true; + + for (int32 i = 0; i < NewValues.Num(); i++) + { + if (NewValues[i].listenerID != CurrentAuxSendValues[i].listenerID || + NewValues[i].auxBusID != CurrentAuxSendValues[i].auxBusID || + NewValues[i].fControlValue != CurrentAuxSendValues[i].fControlValue) + { + return true; + } + } + + return false; +} + +void UAkComponent::ApplyAkReverbVolumeList(float DeltaTime) +{ + for (int32 Idx = 0; Idx < ReverbFadeControls.Num(); ) + { + if (!ReverbFadeControls[Idx].Update(DeltaTime)) + ReverbFadeControls.RemoveAt(Idx); + else + ++Idx; + } + + if (ReverbFadeControls.Num() > 1) + ReverbFadeControls.Sort(AkReverbFadeControl::Prioritize); + + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (AkAudioDevice) + { + TArray NewAuxSendValues; + for (int32 Idx = 0; Idx < ReverbFadeControls.Num() && Idx < AkAudioDevice->GetMaxAuxBus(); Idx++) + { + AkAuxSendValue* FoundAuxSend = NewAuxSendValues.FindByPredicate([=](const AkAuxSendValue& ItemInArray) { return ItemInArray.auxBusID == ReverbFadeControls[Idx].AuxBusId; }); + if (FoundAuxSend) + { + FoundAuxSend->fControlValue += ReverbFadeControls[Idx].ToAkAuxSendValue().fControlValue; + } + else + { + NewAuxSendValues.Add(ReverbFadeControls[Idx].ToAkAuxSendValue()); + } + } + + if (NeedToUpdateAuxSends(NewAuxSendValues)) + { + AkAudioDevice->SetAuxSends(this, NewAuxSendValues); + CurrentAuxSendValues = NewAuxSendValues; + } + } +} + +void UAkComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + if (SoundEngine->IsInitialized()) + { + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + // If we're a listener, update our position here instead of in OnUpdateTransform. + // This is because PlayerController->GetAudioListenerPosition caches its value, and it can be out of sync + if (IsDefaultListener && HasMoved()) + UpdateGameObjectPosition(); + + if (AkAudioDevice && AkAudioDevice->WorldSpatialAudioVolumesUpdated(GetWorld())) + { + UpdateSpatialAudioRoom(GetComponentLocation()); + // Find and apply all AkReverbVolumes at this location + if (bUseReverbVolumes && AkAudioDevice->GetMaxAuxBus() > 0) + { + UpdateAkLateReverbComponentList(GetComponentLocation()); + } + } + + if (AkAudioDevice && bUseReverbVolumes && AkAudioDevice->GetMaxAuxBus() > 0) + ApplyAkReverbVolumeList(DeltaTime); + + ObstructionService.Tick(Listeners, GetPosition(), GetOwner(), GetSpatialAudioRoom(), GetOcclusionCollisionChannel(), DeltaTime, OcclusionRefreshInterval); + + if (bAutoDestroy && bEventPosted && !HasActiveEvents()) + { + DestroyComponent(); + } + +#if !UE_BUILD_SHIPPING + if (DrawFirstOrderReflections || DrawSecondOrderReflections || DrawHigherOrderReflections) + { + DebugDrawReflections(); + } + if (DrawDiffraction) + { + DebugDrawDiffraction(); + } +#endif + } +} + +void UAkComponent::BeginPlay() +{ + Super::BeginPlay(); + UpdateGameObjectPosition(); + + // If spawned inside AkReverbVolume(s), we do not want the fade in effect to kick in. + UpdateAkLateReverbComponentList(GetComponentLocation()); + for (auto& ReverbFadeControl : ReverbFadeControls) + ReverbFadeControl.ForceCurrentToTargetValue(); + + SetAttenuationScalingFactor(AttenuationScalingFactor); + + if (EnableSpotReflectors) + AAkSpotReflector::UpdateSpotReflectors(this); +} + +void UAkComponent::SetAttenuationScalingFactor(float Value) +{ + AttenuationScalingFactor = Value; + FAkAudioDevice* AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + AudioDevice->SetAttenuationScalingFactor(this, AttenuationScalingFactor); +} + +void UAkComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) +{ + Super::OnUpdateTransform(UpdateTransformFlags, Teleport); + + // If we're a listener, our position will be updated from Tick instead of here. + // This is because PlayerController->GetAudioListenerPosition caches its value, and it can be out of sync + if(!IsDefaultListener) + UpdateGameObjectPosition(); +} + +UAkComponent* UAkComponent::GetAkComponent(AkGameObjectID GameObjectID) +{ + return GameObjectID == DUMMY_GAMEOBJ ? nullptr : (UAkComponent*)GameObjectID; +} + +void UAkComponent::GetAkGameObjectName(FString& Name) const +{ + AActor* parentActor = GetOwner(); + if (parentActor) + { +#if WITH_EDITOR + Name = parentActor->GetActorLabel() + "."; +#else + Name = parentActor->GetName() + "."; +#endif + } + + Name += GetName(); + + UWorld* CurrentWorld = GetWorld(); + switch (CurrentWorld->WorldType) + { + case EWorldType::Editor: + Name += "(Editor)"; + break; + case EWorldType::EditorPreview: + Name += "(EditorPreview)"; + break; + case EWorldType::GamePreview: + Name += "(GamePreview)"; + break; + case EWorldType::Inactive: + Name += "(Inactive)"; + break; + } +} + +void UAkComponent::PostRegisterGameObject() {} + +void UAkComponent::PostUnregisterGameObject() {} + +void UAkComponent::RegisterGameObject() +{ + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if ( AkAudioDevice ) + { + if ( bUseDefaultListeners ) + { + const auto& DefaultListeners = AkAudioDevice->GetDefaultListeners(); + Listeners.Empty(DefaultListeners.Num()); + + for (auto Listener : DefaultListeners) + { + Listeners.Add(Listener); + // NOTE: We do not add this to Listener's emitter list, the list is only for user specified (non-default) emitters. + } + } + + AkAudioDevice->RegisterComponent(this); + IsRegisteredWithWwise = true; + + AkAudioDevice->SetGameObjectRadius(this, outerRadius, innerRadius); + } + + PostRegisterGameObject(); +} + +void UAkComponent::UnregisterGameObject() +{ + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (AkAudioDevice) + { + AkAudioDevice->UnregisterComponent(this); + IsRegisteredWithWwise = false; + } + + for (auto Listener : Listeners) + Listener->Emitters.Remove(this); + + for (auto Emitter : Emitters) + Emitter->Listeners.Remove(this); + + PostUnregisterGameObject(); +} + +void UAkComponent::UpdateAkLateReverbComponentList( FVector Loc ) +{ + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (!AkAudioDevice) + return; + + TArray FoundComponents = AkAudioDevice->FindLateReverbComponentsAtLocation(Loc, GetWorld()); + + // Add the new volumes to the current list + for (const auto& LateReverbComponent : FoundComponents) + { + const auto AuxBusId = LateReverbComponent->GetAuxBusId(); + const int32 FoundIdx = ReverbFadeControls.IndexOfByPredicate([=](const AkReverbFadeControl& Candidate) + { + return Candidate.FadeControlUniqueId == (void*)LateReverbComponent; + }); + + if (FoundIdx == INDEX_NONE) + { + // The volume was not found, add it to the list + ReverbFadeControls.Add(AkReverbFadeControl(*LateReverbComponent)); + } + else + { + // The volume was found. We still have to check if it is currently fading out, in case we are + // getting back in a volume we just exited. + ReverbFadeControls[FoundIdx].bIsFadingOut = false; + // We need to update the late reverb values in case they have changed on the reverb component. + ReverbFadeControls[FoundIdx].UpdateValues(*LateReverbComponent); + } + } + + // Fade out the current volumes not found in the new list + for (auto& ReverbFadeControl : ReverbFadeControls) + { + const int32 FoundIdx = FoundComponents.IndexOfByPredicate([=](const UAkLateReverbComponent* const Candidate) + { + return ReverbFadeControl.FadeControlUniqueId == (void*)Candidate; + }); + + if (FoundIdx == INDEX_NONE) + ReverbFadeControl.bIsFadingOut = true; + } +} + +FVector UAkComponent::GetPosition() const +{ + return FAkAudioDevice::AKVector64ToFVector(CurrentSoundPosition.Position()); +} + +bool UAkComponent::HasMoved() +{ + AkSoundPosition soundpos; + FVector Location, Front, Up; + UAkComponentUtils::GetLocationFrontUp(this, Location, Front, Up); + FAkAudioDevice::FVectorsToAKWorldTransform(Location, Front, Up, soundpos); + + return CurrentSoundPosition.Position().X != soundpos.Position().X || CurrentSoundPosition.Position().Y != soundpos.Position().Y || CurrentSoundPosition.Position().Z != soundpos.Position().Z || + CurrentSoundPosition.OrientationTop().X != soundpos.OrientationTop().X || CurrentSoundPosition.OrientationTop().Y != soundpos.OrientationTop().Y || CurrentSoundPosition.OrientationTop().Z != soundpos.OrientationTop().Z || + CurrentSoundPosition.OrientationFront().X != soundpos.OrientationFront().X || CurrentSoundPosition.OrientationFront().Y != soundpos.OrientationFront().Y || CurrentSoundPosition.OrientationFront().Z != soundpos.OrientationFront().Z; +} + +void UAkComponent::UpdateGameObjectPosition() +{ +#ifdef _DEBUG + CheckEmitterListenerConsistancy(); +#endif + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (IsActive() && AkAudioDevice) + { + if (AllowAudioPlayback()) + { + AkSoundPosition soundpos; + FVector Location, Front, Up; + UAkComponentUtils::GetLocationFrontUp(this, Location, Front, Up); + FAkAudioDevice::FVectorsToAKWorldTransform(Location, Front, Up, soundpos); + + UpdateSpatialAudioRoom(Location); + + AkAudioDevice->SetPosition(this, soundpos); + CurrentSoundPosition = soundpos; + } + + // Find and apply all AkReverbVolumes at this location + if (bUseReverbVolumes && AkAudioDevice->GetMaxAuxBus() > 0) + { + UpdateAkLateReverbComponentList(GetComponentLocation()); + } + } +} + +void UAkComponent::UpdateSpatialAudioRoom(FVector Location) +{ + if (IsRegisteredWithWwise) + { + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (AkAudioDevice) + { + AKRESULT result = AK_Fail; + TArray RoomComponents = AkAudioDevice->FindRoomComponentsAtLocation(Location, GetWorld()); + if (RoomComponents.Num() == 0) + { + if (AkAudioDevice->WorldHasActiveRooms(GetWorld()) && CurrentRoom != nullptr) + { + CurrentRoom = nullptr; + result = AkAudioDevice->SetInSpatialAudioRoom(GetAkGameObjectID(), GetSpatialAudioRoom()); + } + } + else if (CurrentRoom != RoomComponents[0]) + { + CurrentRoom = RoomComponents[0]; + result = AkAudioDevice->SetInSpatialAudioRoom(GetAkGameObjectID(), GetSpatialAudioRoom()); + } + + if (EnableSpotReflectors && result == AK_Success) + AAkSpotReflector::UpdateSpotReflectors(this); + } + } +} + +const TSet& UAkComponent::GetEmitters() +{ + FAkAudioDevice* Device = FAkAudioDevice::Get(); + if (Device) + { + auto DefaultListeners = Device->GetDefaultListeners(); + if (DefaultListeners.Contains(this)) + return Device->GetDefaultEmitters(); + else + return Emitters; + } + return Emitters; +} + +void UAkComponent::CheckEmitterListenerConsistancy() +{ + for (auto Emitter : GetEmitters()) + { + check(Emitter->Listeners.Contains(this)); + } + + for (auto Listener : Listeners) + { + check(Listener->GetEmitters().Contains(this)); + } +} + +void UAkComponent::_DebugDrawReflections( const AkVector64& akEmitterPos, const AkVector64& akListenerPos, const AkReflectionPathInfo* paths, AkUInt32 uNumPaths) const +{ + ::FlushDebugStrings(GWorld); + + for (AkInt32 idxPath = uNumPaths-1; idxPath >= 0; --idxPath) + { + const AkReflectionPathInfo& path = paths[idxPath]; + + unsigned int order = path.numReflections; + + if ((DrawFirstOrderReflections && order == 1) || + (DrawSecondOrderReflections && order == 2) || + (DrawHigherOrderReflections && order > 2)) + { + FColor colorLight; + FColor colorMed; + FColor colorDark; + + switch ((order - 1)) + { + case 0: + colorLight = FColor(0x9DEBF3); + colorMed = FColor(0x318087); + colorDark = FColor(0x186067); + break; + case 1: + colorLight = FColor(0xFCDBA2); + colorMed = FColor(0xDEAB4E); + colorDark = FColor(0xA97B27); + break; + case 2: + default: + colorLight = FColor(0xFCB1A2); + colorMed = FColor(0xDE674E); + colorDark = FColor(0xA93E27); + break; + } + + FColor colorLightGrey(75, 75, 75); + FColor colorMedGrey(50, 50, 50); + FColor colorDarkGrey(35, 35, 35); + + const int kPathThickness = 5.f; + const float kRadiusSphere = 25.f; + const int kNumSphereSegments = 8; + + const FVector emitterPos = FAkAudioDevice::AKVector64ToFVector(akEmitterPos); + FVector listenerPt = FAkAudioDevice::AKVector64ToFVector(akListenerPos); + + for (int idxSeg = path.numPathPoints-1; idxSeg >= 0; --idxSeg) + { + const FVector reflectionPt = FAkAudioDevice::AKVector64ToFVector(path.pathPoint[idxSeg]); + + if (idxSeg != path.numPathPoints - 1) + { + // Note: Not drawing the first leg of the path from the listener. Often hard to see because it is typically the camera position. + ::DrawDebugLine(GWorld, listenerPt, reflectionPt, path.isOccluded ? colorLightGrey : colorLight, false, -1.f, (uint8)'\000', kPathThickness / order); + + ::DrawDebugSphere(GWorld, reflectionPt, (kRadiusSphere/2) / order, kNumSphereSegments, path.isOccluded ? colorLightGrey : colorLight); + } + else + { + ::DrawDebugSphere(GWorld, reflectionPt, kRadiusSphere / order, kNumSphereSegments, path.isOccluded ? colorMedGrey : colorMed); + } + + // Draw image source point. Not as useful as I had hoped. + //const FVector imageSrc = FAkAudioDevice::AKVectorToFVector(path.imageSource); + //::DrawDebugSphere(GWorld, imageSrc, kRadiusSphere/order, kNumSphereSegments, colorDark); + + listenerPt = reflectionPt; + } + + if (!path.isOccluded) + { + // Finally the last path segment towards the emitter. + ::DrawDebugLine(GWorld, listenerPt, emitterPos, path.isOccluded ? colorLightGrey : colorLight, false, -1.f, (uint8)'\000', kPathThickness / order); + } + } + } + +} + +void UAkComponent::_DebugDrawDiffraction(const AkVector64& akEmitterPos, const AkVector64& akListenerPos, const AkDiffractionPathInfo* paths, AkUInt32 uNumPaths) const +{ + ::FlushDebugStrings(GWorld); + + for (AkInt32 idxPath = uNumPaths - 1; idxPath >= 0; --idxPath) + { + const AkDiffractionPathInfo& path = paths[idxPath]; + + FColor purple(0x492E74); + FColor green(0x267158); + + if (path.nodeCount > 0) + { + const int kPathThickness = 5.f; + const float kRadiusSphereMax = 35.f; + const float kRadiusSphereMin = 2.f; + + const FVector emitterPos = FAkAudioDevice::AKVector64ToFVector(akEmitterPos); + const FVector listenerPos = FAkAudioDevice::AKVector64ToFVector(akListenerPos); + FVector prevPt = FAkAudioDevice::AKVector64ToFVector(akListenerPos); + + for (int idxSeg = 0; idxSeg < (int)path.nodeCount; ++idxSeg) + { + const FVector pt = FAkAudioDevice::AKVector64ToFVector(path.nodes[idxSeg]); + + if (idxSeg != 0) + { + ::DrawDebugLine(GWorld, prevPt, pt, green, false, -1.f, (uint8)'\000', kPathThickness); + } + + float rad = kRadiusSphereMin + (1.f - path.angles[idxSeg] / PI) * (kRadiusSphereMax - kRadiusSphereMin); + ::DrawDebugSphere(GWorld, pt, rad, 8, path.portals[idxSeg].IsValid() ? green : purple ); + + prevPt = pt; + } + + // Finally the last path segment towards the emitter. + ::DrawDebugLine(GWorld, prevPt, emitterPos, green, false, -1.f, (uint8)'\000', kPathThickness); + } + } +} + +void UAkComponent::DebugDrawReflections() const +{ + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return; + + enum { kMaxPaths = 64 }; + AkReflectionPathInfo paths[kMaxPaths]; + AkUInt32 uNumPaths = kMaxPaths; + AkVector64 listenerPos, emitterPos; + + if (SpatialAudio->QueryReflectionPaths(GetAkGameObjectID(), 0, listenerPos, emitterPos, paths, uNumPaths) == AK_Success && uNumPaths > 0) + _DebugDrawReflections(emitterPos, listenerPos, paths, uNumPaths); +} + +void UAkComponent::DebugDrawDiffraction() const +{ + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (UNLIKELY(!SpatialAudio)) return; + + enum { kMaxPaths = 16 }; + AkDiffractionPathInfo paths[kMaxPaths]; + AkUInt32 uNumPaths = kMaxPaths; + + AkVector64 listenerPos, emitterPos; + + if (SpatialAudio->QueryDiffractionPaths(GetAkGameObjectID(), 0, listenerPos, emitterPos, paths, uNumPaths) == AK_Success) + { + if (uNumPaths > 0) + _DebugDrawDiffraction(emitterPos, listenerPos, paths, uNumPaths); + } +} + +void UAkComponent::SetGameObjectRadius(float in_outerRadius, float in_innerRadius) +{ + outerRadius = in_outerRadius; + innerRadius = in_innerRadius; + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (AkAudioDevice && IsRegisteredWithWwise) + { + AkAudioDevice->SetGameObjectRadius(this, outerRadius, innerRadius); + } +} + +void UAkComponent::SetEnableSpotReflectors(bool in_enable) +{ + if (EnableSpotReflectors != in_enable) + { + EnableSpotReflectors = in_enable; + AAkSpotReflector::UpdateSpotReflectors(this); + } +} + +#if WITH_EDITOR +void UAkComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + if (PropertyChangedEvent.Property) + { + if ((PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UAkComponent, outerRadius) || + PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UAkComponent, innerRadius)) && + PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet) + { + if (innerRadius > outerRadius) + innerRadius = outerRadius; + + SetGameObjectRadius(outerRadius, innerRadius); + } + if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UAkComponent, EnableSpotReflectors) && + PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet) + { + AAkSpotReflector::UpdateSpotReflectors(this); + } + } +} +#endif + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkComponentCallbackManager.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkComponentCallbackManager.cpp new file mode 100644 index 0000000..6b148a5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkComponentCallbackManager.cpp @@ -0,0 +1,343 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkComponentCallbackManager.h" +#include "AkAudioDevice.h" +#include "AkInclude.h" +#include "Misc/ScopeLock.h" +#include "Misc/ScopeExit.h" +#include "Async/Async.h" +#include "AkCallbackInfoPool.h" +#include "AkComponent.h" +#include "Wwise/WwiseExternalSourceManager.h" +#include "UObject/UObjectThreadContext.h" + +struct FAkComponentCallbackManager_Constants +{ + /// Optimization policy + enum class Optimize + { + MemoryUsage, + Speed, + + Value = MemoryUsage, ///< Set to either MemoryUsage or Speed + }; + + /// The default number of expected simultaneously playing sounds on a specific GameObject + enum { ReserveSize = 8, }; +}; + +FAkComponentCallbackManager* FAkComponentCallbackManager::Instance = nullptr; + +FAkComponentCallbackManager* FAkComponentCallbackManager::GetInstance() +{ + return Instance; +} + +void FAkFunctionPtrEventCallbackPackage::HandleAction(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo) +{ + if (pfnUserCallback) + { + in_pCallbackInfo->pCookie = pUserCookie; + pfnUserCallback(in_eType, in_pCallbackInfo); + in_pCallbackInfo->pCookie = (void*)this; + } +} + +void FAkFunctionPtrEventCallbackPackage::CancelCallback() +{ + pfnUserCallback = nullptr; + uUserFlags = 0; +} + +void FAkBlueprintDelegateEventCallbackPackage::HandleAction(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo) +{ + if (BlueprintCallback.IsBound()) + { + AkCallbackInfo* cbInfoCopy = AkCallbackTypeHelpers::CopyWwiseCallbackInfo(in_eType, in_pCallbackInfo); + EAkCallbackType BlueprintCallbackType = AkCallbackTypeHelpers::GetBlueprintCallbackTypeFromAkCallbackType(in_eType); + auto CachedBlueprintCallback = BlueprintCallback; + AsyncTask(ENamedThreads::GameThread, [cbInfoCopy, BlueprintCallbackType, CachedBlueprintCallback] + { + if (!cbInfoCopy) + { + return; + } + + ON_SCOPE_EXIT { + FMemory::Free(cbInfoCopy); + }; + + if (!CachedBlueprintCallback.IsBound()) + { + return; + } + + UAkComponent* akComponent = (UAkComponent*)cbInfoCopy->gameObjID; + if (!IsValid(akComponent)) + { + return; + } + + UAkCallbackInfo* BlueprintAkCallbackInfo = nullptr; + if (cbInfoCopy) + { + BlueprintAkCallbackInfo = AkCallbackTypeHelpers::GetBlueprintableCallbackInfo(BlueprintCallbackType, cbInfoCopy); + } + CachedBlueprintCallback.ExecuteIfBound(BlueprintCallbackType, BlueprintAkCallbackInfo); + + if (auto AudioDevice = FAkAudioDevice::Get()) + { + if (auto CallbackInfoPool = AudioDevice->GetAkCallbackInfoPool()) + { + CallbackInfoPool->Release(BlueprintAkCallbackInfo); + } + } + }); + } +} + +void FAkBlueprintDelegateEventCallbackPackage::CancelCallback() +{ + BlueprintCallback.Clear(); + uUserFlags = 0; +} + +void FAkLatentActionEventCallbackPackage::HandleAction(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo) +{ + // Don't access EndOfEventLatentAction if it's been deleted already + if (!LatentActionValidityToken->bValid) + { + return; + } + + if (EndOfEventLatentAction) + { + EndOfEventLatentAction->EventFinished = true; + } +} + +void FAkComponentCallbackManager::AkComponentCallback(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo) +{ + auto pPackage = (IAkUserEventCallbackPackage*)in_pCallbackInfo->pCookie; + + if (Instance && pPackage) + { + const auto& gameObjID = in_pCallbackInfo->gameObjID; + bool deletePackage = false; + + { + FScopeLock Lock(&Instance->CriticalSection); + auto pPackageSet = Instance->GameObjectToPackagesMap.Find(gameObjID); + if (pPackageSet && in_eType == AK_EndOfEvent) + { + Instance->RemovePackageFromSet(pPackageSet, pPackage, gameObjID); + } + } + + if (in_eType == AK_EndOfEvent) + { + deletePackage = true; + if (auto* Device = FAkAudioDevice::Get()) + { + Device->RemovePlayingID(((AkEventCallbackInfo*)in_pCallbackInfo)->eventID, ((AkEventCallbackInfo*)in_pCallbackInfo)->playingID); + } + + if(pPackage->HasExternalSources) + { + if (auto* ExternalSourceMananger = IWwiseExternalSourceManager::Get()) + { + ExternalSourceMananger->OnEndOfEvent(((AkEventCallbackInfo*)in_pCallbackInfo)->playingID); + } + } + } + + if ((pPackage->uUserFlags & in_eType) != 0) + { + pPackage->HandleAction(in_eType, in_pCallbackInfo); + } + + if (deletePackage) + { + delete pPackage; + } + } +} + +FAkComponentCallbackManager::FAkComponentCallbackManager() +{ + if (Instance != nullptr) + { + UE_LOG(LogAkAudio, Error, TEXT("FAkComponentCallbackManager has already been instantiated.")); + } + + Instance = this; +} + +FAkComponentCallbackManager::~FAkComponentCallbackManager() +{ + for (auto& Item : GameObjectToPackagesMap) + { + for (auto pPackage : Item.Value) + { + delete pPackage; + } + } + + Instance = nullptr; +} + +IAkUserEventCallbackPackage* FAkComponentCallbackManager::CreateCallbackPackage(AkCallbackFunc in_cbFunc, void* in_Cookie, uint32 in_Flags, AkGameObjectID in_gameObjID, bool HasExternalSources) +{ + uint32 KeyHash = GetKeyHash(in_Cookie); + auto pPackage = new FAkFunctionPtrEventCallbackPackage(in_cbFunc, in_Cookie, in_Flags, KeyHash, HasExternalSources); + if (pPackage) + { + FScopeLock Lock(&CriticalSection); + GameObjectToPackagesMap.FindOrAdd(in_gameObjID).Add(pPackage); + UserCookieHashToPackageMap.Add(KeyHash, pPackage); + } + + return pPackage; +} + +IAkUserEventCallbackPackage* FAkComponentCallbackManager::CreateCallbackPackage(FOnAkPostEventCallback BlueprintCallback, uint32 in_Flags, AkGameObjectID in_gameObjID, bool HasExternalSources) +{ + uint32 KeyHash = GetKeyHash(BlueprintCallback); + auto pPackage = new FAkBlueprintDelegateEventCallbackPackage(BlueprintCallback, in_Flags, KeyHash, HasExternalSources); + if (pPackage) + { + FScopeLock Lock(&CriticalSection); + GameObjectToPackagesMap.FindOrAdd(in_gameObjID).Add(pPackage); + UserCookieHashToPackageMap.Add(KeyHash, pPackage); + } + + return pPackage; +} + +IAkUserEventCallbackPackage* FAkComponentCallbackManager::CreateCallbackPackage(FWaitEndOfEventAction* LatentAction, AkGameObjectID in_gameObjID, bool HasExternalSources) +{ + auto pPackage = new FAkLatentActionEventCallbackPackage(LatentAction, 0, HasExternalSources); + if (pPackage) + { + FScopeLock Lock(&CriticalSection); + GameObjectToPackagesMap.FindOrAdd(in_gameObjID).Add(pPackage); + } + + return pPackage; +} + +void FAkComponentCallbackManager::RemoveCallbackPackage(IAkUserEventCallbackPackage* in_Package, AkGameObjectID in_gameObjID) +{ + { + FScopeLock Lock(&CriticalSection); + auto pPackageSet = GameObjectToPackagesMap.Find(in_gameObjID); + if (pPackageSet) + { + RemovePackageFromSet(pPackageSet, in_Package, in_gameObjID); + } + } + + delete in_Package; +} + +void FAkComponentCallbackManager::CancelEventCallback(void* in_Cookie) +{ + CancelKeyHash(GetKeyHash(in_Cookie)); +} + +void FAkComponentCallbackManager::CancelEventCallback(const FOnAkPostEventCallback& in_Delegate) +{ + CancelKeyHash(GetKeyHash(in_Delegate)); +} + +void FAkComponentCallbackManager::CancelKeyHash(uint32 HashToCancel) +{ + FScopeLock AutoLock(&CriticalSection); + + TArray PackagesToCancel; + UserCookieHashToPackageMap.MultiFind(HashToCancel, PackagesToCancel); + + for (auto iter = PackagesToCancel.CreateConstIterator(); iter; ++iter) + { + if (*iter) + { + (*iter)->CancelCallback(); + } + } +} + +void FAkComponentCallbackManager::RegisterGameObject(AkGameObjectID in_gameObjID) +{ + if (FAkComponentCallbackManager_Constants::Optimize::Value == FAkComponentCallbackManager_Constants::Optimize::Speed) + { + FScopeLock Lock(&CriticalSection); + GameObjectToPackagesMap.FindOrAdd(in_gameObjID).Reserve(FAkComponentCallbackManager_Constants::ReserveSize); + } +} + +void FAkComponentCallbackManager::UnregisterGameObject(AkGameObjectID in_gameObjID) +{ + // Do not cancel callbacks with the SoundEngine, as we need them for the + // playingID bookkeeping. Deleting the packages will ensure we do not callback + // into objects that may have been destroyed. + + FScopeLock Lock(&CriticalSection); + auto pPackageSet = GameObjectToPackagesMap.Find(in_gameObjID); + if (pPackageSet) + { + for (auto pPackage : *pPackageSet) + { + pPackage->CancelCallback(); + UserCookieHashToPackageMap.Remove(pPackage->KeyHash, pPackage); + } + + GameObjectToPackagesMap.Remove(in_gameObjID); + } +} + +bool FAkComponentCallbackManager::HasActiveEvents(AkGameObjectID in_gameObjID) +{ + FScopeLock Lock(&CriticalSection); + auto pPackageSet = GameObjectToPackagesMap.Find(in_gameObjID); + return pPackageSet && pPackageSet->Num() > 0; +} + +void FAkComponentCallbackManager::RemovePackageFromSet(FAkComponentCallbackManager::PackageSet* in_pPackageSet, IAkUserEventCallbackPackage* in_pPackage, AkGameObjectID in_gameObjID) +{ + // No need for a lock here because those calling this function are already locking + in_pPackageSet->Remove(in_pPackage); + UserCookieHashToPackageMap.Remove(in_pPackage->KeyHash, in_pPackage); + if (FAkComponentCallbackManager_Constants::Optimize::Value == FAkComponentCallbackManager_Constants::Optimize::MemoryUsage) + { + if (in_pPackageSet->Num() == 0) + { + GameObjectToPackagesMap.Remove(in_gameObjID); + } + } +} + +uint32 FAkComponentCallbackManager::GetKeyHash(void* Key) +{ + return GetTypeHash(Key); +} + +uint32 FAkComponentCallbackManager::GetKeyHash(const FOnAkPostEventCallback& Key) +{ + return HashCombine(GetTypeHash(Key.GetUObject()), GetTypeHash(Key.GetFunctionName())); +} + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkComponentCallbackManager.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkComponentCallbackManager.h new file mode 100644 index 0000000..3f87a1c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkComponentCallbackManager.h @@ -0,0 +1,145 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkAudioDevice.h" + + +class IAkUserEventCallbackPackage +{ +public: + /** Copy of the user callback flags, for use in our own callback */ + uint32 uUserFlags; + + uint32 KeyHash; + + bool HasExternalSources = false; + + IAkUserEventCallbackPackage() + : uUserFlags(0) + {} + + IAkUserEventCallbackPackage(uint32 in_Flags, uint32 in_Hash, bool in_HasExternalSources) + : uUserFlags(in_Flags) + , KeyHash(in_Hash) + , HasExternalSources(in_HasExternalSources) + {} + + virtual ~IAkUserEventCallbackPackage() {} + + virtual void HandleAction(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo) = 0; + virtual void CancelCallback() {}; +}; + +class FAkFunctionPtrEventCallbackPackage : public IAkUserEventCallbackPackage +{ +public: + FAkFunctionPtrEventCallbackPackage(AkCallbackFunc CbFunc, void* Cookie, uint32 Flags, uint32 in_Hash, bool in_HasExternalSources) + : IAkUserEventCallbackPackage(Flags, in_Hash, in_HasExternalSources) + , pfnUserCallback(CbFunc) + , pUserCookie(Cookie) + {} + + virtual void HandleAction(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo) override; + virtual void CancelCallback() override; + +private: + /** Copy of the user callback, for use in our own callback */ + AkCallbackFunc pfnUserCallback; + + /** Copy of the user cookie, for use in our own callback */ + void* pUserCookie; + +}; + +class FAkBlueprintDelegateEventCallbackPackage : public IAkUserEventCallbackPackage +{ +public: + FAkBlueprintDelegateEventCallbackPackage(FOnAkPostEventCallback PostEventCallback, uint32 Flags, uint32 in_Hash, bool in_HasExternalSources) + : IAkUserEventCallbackPackage(Flags, in_Hash, in_HasExternalSources) + , BlueprintCallback(PostEventCallback) + {} + + virtual void HandleAction(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo) override; + virtual void CancelCallback() override; + +private: + FOnAkPostEventCallback BlueprintCallback; +}; + +class FAkLatentActionEventCallbackPackage : public IAkUserEventCallbackPackage +{ +public: + FAkLatentActionEventCallbackPackage(FWaitEndOfEventAction* LatentAction, uint32 in_Hash, bool in_HasExternalSources) + : IAkUserEventCallbackPackage(AK_EndOfEvent, in_Hash, in_HasExternalSources) + , EndOfEventLatentAction(LatentAction) + { + LatentActionValidityToken = MakeShared(); + EndOfEventLatentAction->ValidityToken = LatentActionValidityToken; + } + + virtual void HandleAction(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo) override; + +private: + TSharedPtr LatentActionValidityToken; + FWaitEndOfEventAction* EndOfEventLatentAction; +}; + +class FAkComponentCallbackManager +{ +public: + static FAkComponentCallbackManager* GetInstance(); + + static FAkComponentCallbackManager* Instance; + + /** Our own event callback */ + static void AkComponentCallback(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo); + + FAkComponentCallbackManager(); + ~FAkComponentCallbackManager(); + + IAkUserEventCallbackPackage* CreateCallbackPackage(AkCallbackFunc in_cbFunc, void* in_Cookie, uint32 in_Flags, AkGameObjectID in_gameObjID, bool HasExternalSources); + IAkUserEventCallbackPackage* CreateCallbackPackage(FOnAkPostEventCallback BlueprintCallback, uint32 in_Flags, AkGameObjectID in_gameObjID, bool HasExternalSources); + IAkUserEventCallbackPackage* CreateCallbackPackage(FWaitEndOfEventAction* LatentAction, AkGameObjectID in_gameObjID, bool HasExternalSources); + void RemoveCallbackPackage(IAkUserEventCallbackPackage* in_Package, AkGameObjectID in_gameObjID); + + void CancelEventCallback(void* in_Cookie); + void CancelEventCallback(const FOnAkPostEventCallback& in_Delegate); + + void RegisterGameObject(AkGameObjectID in_gameObjID); + void UnregisterGameObject(AkGameObjectID in_gameObjID); + + bool HasActiveEvents(AkGameObjectID in_gameObjID); + +private: + typedef TSet PackageSet; + + void RemovePackageFromSet(PackageSet* in_pPackageSet, IAkUserEventCallbackPackage* in_pPackage, AkGameObjectID in_gameObjID); + + FCriticalSection CriticalSection; + + typedef AkGameObjectIdKeyFuncs PackageSetGameObjectIDKeyFuncs; + TMap GameObjectToPackagesMap; + + // Used for quick lookup in cancel + uint32 inline GetKeyHash(void* Key); + uint32 inline GetKeyHash(const FOnAkPostEventCallback& Key); + + void CancelKeyHash(uint32 HashToCancel); + TMultiMap UserCookieHashToPackageMap; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkComponentHelpers.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkComponentHelpers.cpp new file mode 100644 index 0000000..f56f9ec --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkComponentHelpers.cpp @@ -0,0 +1,212 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkComponentHelpers.h" +#include "Components/PrimitiveComponent.h" +#include "AkAudioDevice.h" +#include "GameFramework/WorldSettings.h" + +namespace AkComponentHelpers +{ + bool HasSimpleCollisionGeometry(const UBodySetup* bodySetup) + { + FKAggregateGeom geometry = bodySetup->AggGeom; + return geometry.BoxElems.Num() > 0 || geometry.ConvexElems.Num() > 0 || geometry.SphereElems.Num() > 0 || geometry.TaperedCapsuleElems.Num() > 0 || geometry.SphylElems.Num() > 0; + } + + bool EncompassesPoint(UPrimitiveComponent& Primitive, FVector Point, float SphereRadius /*= 0.f*/, float* OutDistanceToPoint /*= nullptr*/) + { + bool bUsePhysicsCollision = Primitive.GetOwner() != nullptr; +#ifndef WITH_PHYSX + bUsePhysicsCollision = false; +#endif + float DistanceSqr = 0.0f; + const UBodySetup* bodySetup = Primitive.GetBodySetup(); + if (bodySetup == nullptr || !AkComponentHelpers::HasSimpleCollisionGeometry(bodySetup)) + { + bUsePhysicsCollision = false; + } + if (bUsePhysicsCollision) + { + FVector ClosestPoint; + + if (Primitive.GetSquaredDistanceToCollision(Point, DistanceSqr, ClosestPoint) == false) + { + if (OutDistanceToPoint) + { + *OutDistanceToPoint = -1.f; + } + return false; + } + } + else + { + // Just use simple bounds intersection. This will be less accurate, especially for complex-shaped non-box rooms. + // Since bUseAttachParentBound = true, we can use Bounds here. + DistanceSqr = Primitive.Bounds.GetBox().ComputeSquaredDistanceToPoint(Point); + } + if (OutDistanceToPoint) + { + *OutDistanceToPoint = FMath::Sqrt(DistanceSqr); + } + + return DistanceSqr >= 0.f && DistanceSqr <= FMath::Square(SphereRadius); + } + + float UnrealUnitsPerMeter(const UActorComponent* component) + { + const float defaultWorldToMetersRatio = 100.0f; + + if (component == nullptr) + return defaultWorldToMetersRatio; + + if (component->GetWorld() == nullptr) + return defaultWorldToMetersRatio; + + AWorldSettings* settings = component->GetWorld()->GetWorldSettings(); + if (settings == nullptr) + return defaultWorldToMetersRatio; + + return settings->WorldToMeters; + } + + float UnrealUnitsPerSquaredMeter(const UActorComponent* component) + { + return FMath::Pow(UnrealUnitsPerMeter(component), 2.0f); + } + + float UnrealUnitsPerCubicMeter(const UActorComponent* component) + { + return FMath::Pow(UnrealUnitsPerMeter(component), 3.0f); + } + + bool IsInGameWorld(const UActorComponent* InComponent) + { + UWorld* World = InComponent->GetWorld(); + if (World == nullptr) + return false; + return World->WorldType == EWorldType::Game || World->WorldType == EWorldType::PIE; + } + + AKAUDIO_API FBoxSphereBounds GetPrimitiveBoundsNoRotation(const UPrimitiveComponent& Primitive) + { + FTransform Transform(Primitive.GetComponentTransform()); + Transform.SetRotation(FQuat::Identity); + return Primitive.CalcBounds(Transform); + } + + void GetPrimitiveTransformAndExtent(const UPrimitiveComponent& Primitive, AkWorldTransform& transform, AkExtent& extent) + { + FRotator rotation = Primitive.GetComponentRotation(); + + FVector front = rotation.RotateVector(FVector::ForwardVector); + FVector up = rotation.RotateVector(FVector::UpVector); + + AkVector Front, Up; + FAkAudioDevice::FVectorToAKVector(front, Front); + FAkAudioDevice::FVectorToAKVector(up, Up); + transform.SetOrientation(Front, Up); + + FBoxSphereBounds primitiveBounds = GetPrimitiveBoundsNoRotation(Primitive); + extent = FAkAudioDevice::FVectorToAkExtent(primitiveBounds.BoxExtent); // Potential loss of precision here + AkVector64 Center; + // For uniformly shaped primitives, primitiveBounds.Origin will be the same as the component position. + // For complex meshes and brushes, there will be an offset. + FAkAudioDevice::FVectorToAKVector64(Primitive.Bounds.Origin, Center); + transform.SetPosition(Center); + } + + void GetPrimitiveUpAndFront(const UPrimitiveComponent& Primitive, AkVector& Up, AkVector& Front) + { + FRotator rotation = Primitive.GetComponentRotation(); + + FVector front = rotation.RotateVector(FVector::ForwardVector); + FVector up = rotation.RotateVector(FVector::UpVector); + + FAkAudioDevice::FVectorToAKVector(front, Front); + FAkAudioDevice::FVectorToAKVector(up, Up); + } + + bool DoesMovementRecenterChild(USceneComponent* child, USceneComponent* parent, const FVector& Delta) + { +#if WITH_EDITOR + // Only implement movement when it is to recentre component at local origin + FVector direction; + float length; + FVector relativeDestination = child->GetRelativeLocation() + Delta; + // When movement happens from OnRegister, after reparenting, GetRelativeLocation() can sometimes refer to the old parent, + // because the private SceneComponent::RelativeLocation member has not been correctly updated yet for this frame. + // This causes a local offset to be left applied to the component after it is reparented. + // Therefore, use Parent, which will refer to the current attachment parent and use it to derive the correct relative location. + if (parent != nullptr) + { + relativeDestination = (child->GetComponentLocation() + Delta) - parent->GetComponentLocation(); + } + relativeDestination.ToDirectionAndLength(direction, length); + if (length <= 0.01f) + return true; +#endif //WITH_EDITOR + return false; + } + + void LogAttachmentError(const UActorComponent* child, const UActorComponent* parent, const FString& requiredClassName) + { + FString actorString = FString("NONE"); + if (child->GetOwner() != nullptr) + actorString = child->GetOwner()->GetName(); + FString parentName = parent->GetName(); + FString parentClass = parent->GetClass()->GetName(); + FString childName = child->GetName(); + FString childClass = child->GetClass()->GetName(); + + UE_LOG(LogAkAudio, Error, + TEXT("On actor %s, there is a %s (%s) attached to parent of type %s (%s)."), + *actorString, *childClass, *childName, *parentClass, *parentName); + + UE_LOG(LogAkAudio, Error, TEXT("%s requires to be nested under a component inheriting from %s."), + *childClass, *requiredClassName); + } + + void LogSimpleGeometryWarning(const UPrimitiveComponent* parent, const UActorComponent* child) + { + FString primitiveName = ""; + FString childName = ""; + parent->GetName(primitiveName); + child->GetName(childName); + FString actorName = ""; + AActor* owner = parent->GetOwner(); + if (owner != nullptr) + owner->GetName(actorName); + UE_LOG(LogAkAudio, Warning, + TEXT("Primitive component %s on actor %s has no simple collision geometry.%sChild component %s will use component bounds for containment calculations. This could be less accurate than using simple collision geometry."), + *primitiveName, *actorName, LINE_TERMINATOR, *childName); + } + +#if WITH_EDITOR + bool IsGameWorldBlueprintComponent(const UActorComponent* InComponent) + { + // CreationMethod == EComponentCreationMethod::SimpleConstructionScript means the component was added as part of a blueprint class. + return IsInGameWorld(InComponent) && InComponent->CreationMethod == EComponentCreationMethod::SimpleConstructionScript; + } + + bool ShouldDeferBeginPlay(const UActorComponent* InComponent) + { + bool worldHasBegunPlay = InComponent->GetWorld() && InComponent->GetWorld()->HasBegunPlay(); + return IsGameWorldBlueprintComponent(InComponent) && worldHasBegunPlay; + } +#endif +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkCustomVersion.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkCustomVersion.cpp new file mode 100644 index 0000000..1803777 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkCustomVersion.cpp @@ -0,0 +1,25 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkCustomVersion.h" +#include "AkAudioDevice.h" +#include "Serialization/CustomVersion.h" + +const FGuid FAkCustomVersion::GUID(0xE2717C7E, 0x52F544D3, 0x950C5340, 0xB315035E); + +// Register the custom version with core +FCustomVersionRegistration GRegisterAkCustomVersion(FAkCustomVersion::GUID, FAkCustomVersion::LatestVersion, TEXT("AkAudioVersion")); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkDrawPortalComponent.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkDrawPortalComponent.cpp new file mode 100644 index 0000000..7189776 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkDrawPortalComponent.cpp @@ -0,0 +1,200 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkDrawPortalComponent.cpp: +=============================================================================*/ +#include "AkDrawPortalComponent.h" + +#include "PrimitiveSceneProxy.h" + +#if WITH_EDITOR +#include "DynamicMeshBuilder.h" +#include "AkRoomComponent.h" +#include "AkSpatialAudioDrawUtils.h" +#include "AkSettings.h" +#endif // WITH_EDITOR + +UDrawPortalComponent::UDrawPortalComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +#if WITH_EDITOR +const UAkPortalComponent* UDrawPortalComponent::GetPortalParent() const +{ + return Cast(GetAttachParent()); +} + +void UDrawPortalComponent::DrawPortalOutline(const FSceneView* View, FPrimitiveDrawInterface* PDI, FMeshElementCollector& Collector, int32 ViewIndex) const +{ + const UAkPortalComponent* PortalComponent = GetPortalParent(); + if (IsValid(PortalComponent) && IsValid(PortalComponent->GetPrimitiveParent())) + { + const UPrimitiveComponent* PrimitiveParent = Cast(PortalComponent->GetPrimitiveParent()); + if (PrimitiveParent == nullptr) + return; + // Calculate the unscaled, unrotated box extent of the primitive parent component, at origin. + FVector BoxExtent = PrimitiveParent->CalcBounds(FTransform()).BoxExtent; + FLinearColor FrontDrawColor; + FLinearColor BackDrawColor; + AkSpatialAudioColors::GetPortalColors(PortalComponent, FrontDrawColor, BackDrawColor); + FLinearColor OutlineColor = AkSpatialAudioColors::GetPortalOutlineColor(PortalComponent); + + float Thickness = AkDrawConstants::PortalOutlineThickness; + FTransform T = PrimitiveParent->GetComponentTransform(); + AkDrawBounds DrawBounds(T, BoxExtent); + + /** Draw outline of 'front' (positive X) on portal */ + PDI->DrawLine(DrawBounds.RU(), DrawBounds.FRU(), FrontDrawColor, SDPG_MAX, Thickness); + PDI->DrawLine(DrawBounds.FRU(), DrawBounds.FRD(), FrontDrawColor, SDPG_MAX, Thickness); + PDI->DrawLine(DrawBounds.FRD(), DrawBounds.RD(), FrontDrawColor, SDPG_MAX, Thickness); + PDI->DrawLine(DrawBounds.LU(), DrawBounds.FLU(), FrontDrawColor, SDPG_MAX, Thickness); + PDI->DrawLine(DrawBounds.FLU(), DrawBounds.FLD(), FrontDrawColor, SDPG_MAX, Thickness); + PDI->DrawLine(DrawBounds.FLD(), DrawBounds.LD(), FrontDrawColor, SDPG_MAX, Thickness); + PDI->DrawLine(DrawBounds.FRU(), DrawBounds.FLU(), FrontDrawColor, SDPG_MAX, Thickness); + PDI->DrawLine(DrawBounds.FRD(), DrawBounds.FLD(), FrontDrawColor, SDPG_MAX, Thickness); + /** Draw outline of 'back' (negative X) on portal */ + PDI->DrawLine(DrawBounds.RU(), DrawBounds.BRU(), BackDrawColor, SDPG_MAX, Thickness); + PDI->DrawLine(DrawBounds.BRU(), DrawBounds.BRD(), BackDrawColor, SDPG_MAX, Thickness); + PDI->DrawLine(DrawBounds.BRD(), DrawBounds.RD(), BackDrawColor, SDPG_MAX, Thickness); + PDI->DrawLine(DrawBounds.LU(), DrawBounds.BLU(), BackDrawColor, SDPG_MAX, Thickness); + PDI->DrawLine(DrawBounds.BLU(), DrawBounds.BLD(), BackDrawColor, SDPG_MAX, Thickness); + PDI->DrawLine(DrawBounds.BLD(), DrawBounds.LD(), BackDrawColor, SDPG_MAX, Thickness); + PDI->DrawLine(DrawBounds.BLU(), DrawBounds.BRU(), BackDrawColor, SDPG_MAX, Thickness); + PDI->DrawLine(DrawBounds.BLD(), DrawBounds.BRD(), BackDrawColor, SDPG_MAX, Thickness); + /** Draw outline around centre of portal (YZ plane) */ + PDI->DrawLine(DrawBounds.LU(), DrawBounds.LD(), OutlineColor, SDPG_MAX, Thickness); + PDI->DrawLine(DrawBounds.LD(), DrawBounds.RD(), OutlineColor, SDPG_MAX, Thickness); + PDI->DrawLine(DrawBounds.RD(), DrawBounds.RU(), OutlineColor, SDPG_MAX, Thickness); + PDI->DrawLine(DrawBounds.RU(), DrawBounds.LU(), OutlineColor, SDPG_MAX, Thickness); + + UWorld* world = GetWorld(); + if (world != nullptr) + { + EWorldType::Type worldType = world->WorldType; + if ((!(worldType == EWorldType::Game || worldType == EWorldType::PIE) && PortalComponent->InitialState == AkAcousticPortalState::Closed) || + ((worldType == EWorldType::Game || worldType == EWorldType::PIE) && PortalComponent->GetCurrentState() == AkAcousticPortalState::Closed)) + { + PDI->DrawLine(DrawBounds.FRU(), DrawBounds.BRD(), FrontDrawColor, SDPG_MAX, Thickness); + PDI->DrawLine(DrawBounds.FLD(), DrawBounds.BLU(), BackDrawColor, SDPG_MAX, Thickness); + } + } + + Thickness = AkDrawConstants::PortalRoomConnectionThickness; + + FVector FrontPoint = FVector(BoxExtent.X, 0.0f, 0.0f); + FVector BackPoint = FVector(-BoxExtent.X, 0.0f, 0.0f); + if (PortalComponent->GetFrontRoomComponent() != nullptr && PortalComponent->GetFrontRoomComponent()->GetPrimitiveParent() != nullptr) + { + // Setup front facing vector to test if line from portal to room points 'backwards' (i.e. if it goes back through the portal). In this case, we extend the 'From' point slightly. + FVector Front = PrimitiveParent->GetComponentTransform().TransformVector(FVector(1.0f, 0.0f, 0.0f)); + FVector From = PrimitiveParent->GetComponentTransform().TransformPosition(FrontPoint); + FVector To = PortalComponent->GetFrontRoomComponent()->GetPrimitiveParent()->GetComponentTransform().TransformPosition(FVector(0.0f, 0.0f, 0.0f)); + //PDI->DrawLine(From, To, OutlineColor, SDPG_MAX, Thickness); + FVector ToRoom = To - From; + ToRoom.Normalize(); + PDI->DrawLine(From, To, OutlineColor, SDPG_MAX, Thickness); + } + Thickness = AkDrawConstants::PortalRoomConnectionThickness; + if (PortalComponent->GetBackRoomComponent() != nullptr && PortalComponent->GetBackRoomComponent()->GetPrimitiveParent() != nullptr) + { + // Setup back facing vector to test if line from portal to room points 'backwards' (i.e. if it goes back through the portal). In this case, we extend the 'From' point slightly + FVector Back = PrimitiveParent->GetComponentTransform().TransformVector(FVector(-1.0f, 0.0f, 0.0f)); + FVector From = PrimitiveParent->GetComponentTransform().TransformPosition(BackPoint); + FVector To = PortalComponent->GetBackRoomComponent()->GetPrimitiveParent()->GetComponentTransform().TransformPosition(FVector(0.0f, 0.0f, 0.0f)); + //PDI->DrawLine(From, To, OutlineColor, SDPG_MAX, Thickness); + FVector ToRoom = To - From; + ToRoom.Normalize(); + PDI->DrawLine(From, To, OutlineColor, SDPG_MAX, Thickness); + } + } +} + +/** Represents a portal to the scene manager. + Based on FDrawFrustumSceneProxy (in DrawFrustrumComponent.cpp) +*/ +class FDrawPortalSceneProxy final : public FPrimitiveSceneProxy +{ +public: + /** Based on FDrawFrustumSceneProxy implementation */ + SIZE_T GetTypeHash() const override + { + static size_t UniquePointer; + return reinterpret_cast(&UniquePointer); + } + + /** + * Initialization constructor. + * @param InComponent - game component to draw in the scene + */ + FDrawPortalSceneProxy(const UDrawPortalComponent* InComponent) + : FPrimitiveSceneProxy(InComponent) + , PortalDrawer(InComponent) + {} + + // FPrimitiveSceneProxy interface. + + virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override + { + if (GetDefault()->VisualizeRoomsAndPortals) + { + if (PortalDrawer != nullptr) + { + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) + { + if (VisibilityMap & (1 << ViewIndex)) + { + FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex); + const FSceneView* View = Views[ViewIndex]; + PortalDrawer->DrawPortalOutline(View, PDI, Collector, ViewIndex); + } + } + } + } + } + + virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override + { + FPrimitiveViewRelevance Result; + Result.bDrawRelevance = IsShown(View); + Result.bDynamicRelevance = true; + Result.bStaticRelevance = true; + Result.bEditorNoDepthTestPrimitiveRelevance = true; + return Result; + } + + /** Based on FDrawFrustumSceneProxy implementation */ + virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + GetAllocatedSize()); } + uint32 GetAllocatedSize(void) const { return(FPrimitiveSceneProxy::GetAllocatedSize()); } + +private: + const UDrawPortalComponent* PortalDrawer; +}; + + +FPrimitiveSceneProxy* UDrawPortalComponent::CreateSceneProxy() +{ + return new FDrawPortalSceneProxy(this); +} + +FBoxSphereBounds UDrawPortalComponent::CalcBounds(const FTransform& LocalToWorld) const +{ + return FBoxSphereBounds(LocalToWorld.TransformPosition(FVector::ZeroVector), FVector(AkDrawConstants::CullDepth), AkDrawConstants::CullDepth); +} + +#endif // WITH_EDITOR \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkDrawRoomComponent.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkDrawRoomComponent.cpp new file mode 100644 index 0000000..0ffe544 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkDrawRoomComponent.cpp @@ -0,0 +1,156 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkDrawRoomComponent.cpp: +=============================================================================*/ +#include "AkDrawRoomComponent.h" + +#include "PrimitiveSceneProxy.h" + +#if WITH_EDITOR +#include "AkRoomComponent.h" +#include "AkSettingsPerUser.h" +#include "AkSpatialAudioDrawUtils.h" +#include "DynamicMeshBuilder.h" +#include "SceneManagement.h" +#include "AkSettings.h" +#endif // WITH_EDITOR + +UDrawRoomComponent::UDrawRoomComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +#if WITH_EDITOR +const UAkRoomComponent* UDrawRoomComponent::GetRoomParent() const +{ + return Cast(GetAttachParent()); +} + +void UDrawRoomComponent::DrawRoom(const FSceneView* View, FPrimitiveDrawInterface* PDI, FMeshElementCollector& Collector, int32 ViewIndex) const +{ + const UAkRoomComponent* RoomComponent = GetRoomParent(); + if (IsValid(RoomComponent) && IsValid(RoomComponent->GetPrimitiveParent()) && RoomComponent->bEnable) + { + const UPrimitiveComponent* PrimitiveParent = Cast(RoomComponent->GetPrimitiveParent()); + if (PrimitiveParent == nullptr) + return; + // Calculate the unscaled, unrotated box extent of the primitive parent component, at origin. + FVector BoxExtent = PrimitiveParent->CalcBounds(FTransform()).BoxExtent; + FLinearColor RoomColor = AkSpatialAudioColors::GetRoomColor(); + + FTransform T = PrimitiveParent->GetComponentTransform(); + AkDrawBounds DrawBounds(T, BoxExtent); + + // Calculate the local room axis vectors + FVector RoomAxisForward = DrawBounds.FRU() - DrawBounds.BRU(); + RoomAxisForward.Normalize(); + FVector RoomAxisRight = DrawBounds.RU() - DrawBounds.LU(); + RoomAxisRight.Normalize(); + FVector RoomAxisUp = DrawBounds.RU() - DrawBounds.RD(); + RoomAxisUp.Normalize(); + + // Draw an icon representing the room's orientation. + float Radius = AkDrawConstants::RoomIconRadius; + float IconThickness = AkDrawConstants::RoomIconThickness; + int Sides = AkDrawConstants::RoomIconSides; + + FVector RoomCentre = T.TransformPosition(FVector::ZeroVector); + DrawCircle(PDI, RoomCentre, RoomAxisForward, RoomAxisRight, RoomColor, Radius, Sides, SDPG_MAX, IconThickness); + DrawCircle(PDI, RoomCentre, RoomAxisForward, RoomAxisUp, RoomColor, Radius, Sides, SDPG_MAX, IconThickness); + + float AxisLength = AkDrawConstants::RoomAxisLength; + PDI->DrawLine(RoomCentre, RoomCentre + RoomAxisForward * AxisLength, RoomColor, SDPG_MAX, AkDrawConstants::RoomAxisThickness); + PDI->DrawLine(RoomCentre, RoomCentre + RoomAxisRight * AxisLength, RoomColor, SDPG_MAX, AkDrawConstants::RoomAxisThickness); + PDI->DrawLine(RoomCentre, RoomCentre + RoomAxisUp * AxisLength, RoomColor, SDPG_MAX, AkDrawConstants::RoomAxisThickness); + } +} + +/** Represents a Room to the scene manager. + Based on FDrawFrustumSceneProxy (in DrawFrustrumComponent.cpp) +*/ +class FDrawRoomSceneProxy final : public FPrimitiveSceneProxy +{ +public: + /** Based on FDrawFrustumSceneProxy implementation */ + SIZE_T GetTypeHash() const override + { + static size_t UniquePointer; + return reinterpret_cast(&UniquePointer); + } + + /** + * Initialization constructor. + * @param InComponent - game component to draw in the scene + */ + FDrawRoomSceneProxy(const UDrawRoomComponent* InComponent) + : FPrimitiveSceneProxy(InComponent) + , RoomDrawer(InComponent) + {} + + // FPrimitiveSceneProxy interface. + + virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override + { + if (GetDefault()->VisualizeRoomsAndPortals) + { + if (RoomDrawer != nullptr) + { + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) + { + if (VisibilityMap & (1 << ViewIndex)) + { + FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex); + const FSceneView* View = Views[ViewIndex]; + RoomDrawer->DrawRoom(View, PDI, Collector, ViewIndex); + } + } + } + } + } + + virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override + { + FPrimitiveViewRelevance Result; + Result.bDrawRelevance = IsShown(View); + Result.bDynamicRelevance = true; + Result.bStaticRelevance = true; + Result.bEditorNoDepthTestPrimitiveRelevance = true; + return Result; + } + + /** Based on FDrawFrustumSceneProxy implementation */ + virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + GetAllocatedSize()); } + uint32 GetAllocatedSize(void) const { return(FPrimitiveSceneProxy::GetAllocatedSize()); } + +private: + const UDrawRoomComponent* RoomDrawer; +}; + + +FPrimitiveSceneProxy* UDrawRoomComponent::CreateSceneProxy() +{ + return new FDrawRoomSceneProxy(this); +} + +FBoxSphereBounds UDrawRoomComponent::CalcBounds(const FTransform& LocalToWorld) const +{ + return FBoxSphereBounds(LocalToWorld.TransformPosition(FVector::ZeroVector), FVector(AkDrawConstants::CullDepth), AkDrawConstants::CullDepth); +} + +#endif // WITH_EDITOR \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkEffectShareSet.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkEffectShareSet.cpp new file mode 100644 index 0000000..90b96a8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkEffectShareSet.cpp @@ -0,0 +1,187 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkEffectShareSet.h" +#include "Wwise/WwiseResourceLoader.h" +#include "Wwise/Stats/AkAudio.h" + +#if WITH_EDITORONLY_DATA +#include "AkAudioDevice.h" +#include "Wwise/WwiseProjectDatabase.h" +#include "Wwise/WwiseResourceCooker.h" +#endif + +void UAkEffectShareSet::Serialize(FArchive& Ar) +{ + Super::Serialize(Ar); + + if (HasAnyFlags(RF_ClassDefaultObject)) + { + return; + } +#if !UE_SERVER +#if WITH_EDITORONLY_DATA + if (Ar.IsCooking() && Ar.IsSaving() && !Ar.CookingTarget()->IsServerOnly()) + { + FWwiseLocalizedShareSetCookedData CookedDataToArchive; + if (auto* ResourceCooker = FWwiseResourceCooker::GetForArchive(Ar)) + { + ResourceCooker->PrepareCookedData(CookedDataToArchive, GetValidatedInfo(ShareSetInfo)); + } + CookedDataToArchive.Serialize(Ar); + } +#else + ShareSetCookedData.Serialize(Ar); +#endif +#endif +} + +void UAkEffectShareSet::LoadEffectShareSet() +{ + SCOPED_AKAUDIO_EVENT_2(TEXT("LoadEffectShareSet")); + auto* ResourceLoader = FWwiseResourceLoader::Get(); + if (UNLIKELY(!ResourceLoader)) + { + return; + } + + if (LoadedShareSet) + { + UnloadEffectShareSet(false); + } + +#if WITH_EDITORONLY_DATA + if (IWwiseProjectDatabaseModule::IsInACookingCommandlet()) + { + return; + } + auto* ProjectDatabase = FWwiseProjectDatabase::Get(); + if (!ProjectDatabase || !ProjectDatabase->IsProjectDatabaseParsed()) + { + UE_LOG(LogAkAudio, VeryVerbose, TEXT("UAkEffectShareSet::LoadEffectShareSet: Not loading '%s' because project database is not parsed."), *GetName()) + return; + } + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + return; + } + if (UNLIKELY(!ResourceCooker->PrepareCookedData(ShareSetCookedData, GetValidatedInfo(ShareSetInfo)))) + { + return; + } +#endif + + LoadedShareSet = ResourceLoader->LoadShareSet(ShareSetCookedData); +} + +void UAkEffectShareSet::UnloadEffectShareSet(bool bAsync) +{ + if (LoadedShareSet) + { + auto* ResourceLoader = FWwiseResourceLoader::Get(); + if (UNLIKELY(!ResourceLoader)) + { + return; + } + if (bAsync) + { + FWwiseLoadedShareSetPromise Promise; + Promise.EmplaceValue(MoveTemp(LoadedShareSet)); + ResourceUnload = ResourceLoader->UnloadShareSetAsync(Promise.GetFuture()); + } + else + { + ResourceLoader->UnloadShareSet(MoveTemp(LoadedShareSet)); + } + LoadedShareSet = nullptr; + } +} + +#if WITH_EDITORONLY_DATA +bool UAkEffectShareSet::ObjectIsInSoundBanks() +{ + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkEffectShareSet::GetWwiseRef: ResourceCooker not initialized")); + return false; + } + + auto ProjectDatabase = ResourceCooker->GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkEffectShareSet::GetWwiseRef: ProjectDatabase not initialized")); + return false; + } + + FWwiseObjectInfo* AudioTypeInfo = &ShareSetInfo; + const FWwiseRefPluginShareSet AudioTypeRef = FWwiseDataStructureScopeLock(*ProjectDatabase).GetPluginShareSet( + GetValidatedInfo(ShareSetInfo)); + + return AudioTypeRef.IsValid(); +} + +void UAkEffectShareSet::CookAdditionalFilesOverride(const TCHAR* PackageFilename, const ITargetPlatform* TargetPlatform, + TFunctionRef WriteAdditionalFile) +{ + if (HasAnyFlags(RF_ClassDefaultObject)) + { + return; + } + + FWwiseResourceCooker* ResourceCooker = FWwiseResourceCooker::GetForPlatform(TargetPlatform); + if (!ResourceCooker) + { + return; + } + ResourceCooker->SetSandboxRootPath(PackageFilename); + ResourceCooker->CookShareSet(GetValidatedInfo(ShareSetInfo), WriteAdditionalFile); +} + +void UAkEffectShareSet::FillInfo() +{ + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkEffectShareSet::FillInfo: ResourceCooker not initialized")); + return; + } + + auto ProjectDatabase = ResourceCooker->GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkEffectShareSet::FillInfo: ProjectDatabase not initialized")); + return; + } + + FWwiseObjectInfo* AudioTypeInfo = &ShareSetInfo; + const FWwiseRefPluginShareSet AudioTypeRef = FWwiseDataStructureScopeLock(*ProjectDatabase).GetPluginShareSet( + GetValidatedInfo(ShareSetInfo)); + + if (AudioTypeRef.PluginShareSetName().IsNone() || !AudioTypeRef.PluginShareSetGuid().IsValid() || AudioTypeRef.PluginShareSetId() == AK_INVALID_UNIQUE_ID) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkEffectShareSet::FillInfo: Valid object not found in Project Database")); + return; + } + + AudioTypeInfo->WwiseName = AudioTypeRef.PluginShareSetName(); + AudioTypeInfo->WwiseGuid = AudioTypeRef.PluginShareSetGuid(); + AudioTypeInfo->WwiseShortId = AudioTypeRef.PluginShareSetId(); +} + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkEnvironmentIndex.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkEnvironmentIndex.cpp new file mode 100644 index 0000000..a6cff37 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkEnvironmentIndex.cpp @@ -0,0 +1,78 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkEnvironmentIndex.h" +#include "AkAudioDevice.h" + +void FAkEnvironmentOctreeSemantics::SetElementId(AK_OCTREE_TYPE& OctreeOwner, const FAkEnvironmentOctreeElement& Element, AK_OCTREE_ELEMENT_ID Id) +{ + static_cast(OctreeOwner).ObjectToOctreeId.Add(Element.Component->GetUniqueID(), Id); +} + +void FAkEnvironmentIndex::Add(USceneComponent* EnvironmentToAdd) +{ + UWorld* CurrentWorld = EnvironmentToAdd->GetWorld(); + TUniquePtr& Octree = Map.FindOrAdd(CurrentWorld); + + if (Octree == nullptr) + { + Octree = MakeUnique(); + } + + if (Octree != nullptr) + { + FAkEnvironmentOctreeElement Element(EnvironmentToAdd); + Octree->AddElement(Element); + } +} + +bool FAkEnvironmentIndex::Remove(USceneComponent* EnvironmentToRemove) +{ + UWorld* CurrentWorld = EnvironmentToRemove->GetWorld(); + TUniquePtr* Octree = Map.Find(CurrentWorld); + + if (Octree != nullptr && EnvironmentToRemove != nullptr) + { + AK_OCTREE_ELEMENT_ID* Id = (*Octree)->ObjectToOctreeId.Find(EnvironmentToRemove->GetUniqueID()); + if (Id != nullptr && (*Octree)->IsValidElementId(*Id)) + { + (*Octree)->RemoveElement(*Id); + } + + (*Octree)->ObjectToOctreeId.Remove(EnvironmentToRemove->GetUniqueID()); + return true; + } + + return false; +} + +void FAkEnvironmentIndex::Update(USceneComponent* Environment) +{ + Remove(Environment); + Add(Environment); +} + +void FAkEnvironmentIndex::Clear(const UWorld* World) +{ + Map.Remove(World); +} + +bool FAkEnvironmentIndex::IsEmpty(const UWorld* World) +{ + TUniquePtr* Octree = Map.Find(World); + return Octree == nullptr; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkGameObject.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkGameObject.cpp new file mode 100644 index 0000000..576aa27 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkGameObject.cpp @@ -0,0 +1,275 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkGameObject.cpp: +=============================================================================*/ + +#include "AkGameObject.h" +#include "AkAudioEvent.h" +#include "AkComponentCallbackManager.h" +#include "AkRtpc.h" +#include "Wwise/WwiseExternalSourceManager.h" +#include "Wwise/API/WwiseSoundEngineAPI.h" + +class FPostAssociatedEventAction : public FAkPendingLatentAction +{ +public: + FName ExecutionFunction; + int32 OutputLink = 0; + FWeakObjectPtr CallbackTarget; + int32* PlayingID = nullptr; + TFuture FuturePlayingID; + UAkAudioEvent* AkEvent = nullptr; + bool* bGameObjectStarted= nullptr; + + FPostAssociatedEventAction(const FLatentActionInfo& LatentInfo, int32* PlayingID, UAkAudioEvent* Event, bool* bStarted) + : ExecutionFunction(LatentInfo.ExecutionFunction) + , OutputLink(LatentInfo.Linkage) + , CallbackTarget(LatentInfo.CallbackTarget) + , PlayingID(PlayingID) + , AkEvent(Event) + , bGameObjectStarted(bStarted) + { + } + + virtual void UpdateOperation(FLatentResponse& Response) override + { + bool futureIsReady = FuturePlayingID.IsReady(); + if (futureIsReady) + { + *PlayingID = FuturePlayingID.Get(); + if (bGameObjectStarted!=nullptr) + { + *bGameObjectStarted = true; + } + } + + Response.FinishAndTriggerIf(futureIsReady, ExecutionFunction, OutputLink, CallbackTarget); + } + +#if WITH_EDITOR + virtual FString GetDescription() const override + { + return TEXT("Waiting for posted AkEvent to load media."); + } +#endif +}; + +UAkGameObject::UAkGameObject(const class FObjectInitializer& ObjectInitializer) : +Super(ObjectInitializer) +{ + bEventPosted = false; +} + +int32 UAkGameObject::PostAssociatedAkEvent(int32 CallbackMask, const FOnAkPostEventCallback& PostEventCallback) +{ + return PostAkEvent(AkAudioEvent, CallbackMask, PostEventCallback, EventName); +} + +int32 UAkGameObject::PostAkEvent(UAkAudioEvent* AkEvent, int32 CallbackMask, + const FOnAkPostEventCallback& PostEventCallback, + const FString& InEventName +) +{ + if (LIKELY(IsValid(AkEvent))) + { + return AkEvent->PostOnGameObject(this, PostEventCallback, CallbackMask); + } + + AkPlayingID playingID = AK_INVALID_PLAYING_ID; + + auto AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + playingID = AudioDevice->PostEventOnAkGameObject(AudioDevice->GetShortID(AkEvent, InEventName), this, PostEventCallback, CallbackMask, {}); + } + + return playingID; +} + +AkPlayingID UAkGameObject::PostAkEvent(UAkAudioEvent* AkEvent, AkUInt32 Flags, AkCallbackFunc UserCallback, + void* UserCookie) +{ + if (UNLIKELY(!IsValid(AkEvent))) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to post invalid AkAudioEvent on game object '%s'."), *GetName()); + return AK_INVALID_PLAYING_ID; + } + return AkEvent->PostOnGameObject(this, nullptr, UserCallback, UserCookie, static_cast(Flags), nullptr); +} + +AkPlayingID UAkGameObject::PostAkEventByNameWithDelegate(class UAkAudioEvent * AkEvent, const FString& InEventName, int32 CallbackMask, const FOnAkPostEventCallback& PostEventCallback) +{ + if (AkEvent) + { + return AkEvent->PostOnGameObject(this, &PostEventCallback, nullptr, nullptr, (AkCallbackType)CallbackMask, nullptr); + } + + AkPlayingID playingID = AK_INVALID_PLAYING_ID; + + auto AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + playingID = AudioDevice->PostEventOnAkGameObject(AudioDevice->GetShortID(AkEvent, InEventName), this, PostEventCallback, CallbackMask, {}); + } + + return playingID; +} + +void UAkGameObject::PostAssociatedAkEventAsync(const UObject* WorldContextObject, int32 CallbackMask, const FOnAkPostEventCallback& PostEventCallback, FLatentActionInfo LatentInfo, int32& PlayingID) +{ + AkDeviceAndWorld DeviceAndWorld(WorldContextObject); + FLatentActionManager& LatentActionManager = DeviceAndWorld.CurrentWorld->GetLatentActionManager(); + FPostAssociatedEventAction* NewAction = LatentActionManager.FindExistingAction(LatentInfo.CallbackTarget, LatentInfo.UUID); + if (!NewAction) + { + NewAction = new FPostAssociatedEventAction(LatentInfo, &PlayingID, AkAudioEvent, &bEventPosted); + NewAction->FuturePlayingID = DeviceAndWorld.AkAudioDevice->PostAkAudioEventOnAkGameObjectAsync(AkAudioEvent, this, PostEventCallback, CallbackMask); + LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, NewAction); + } +} + +void UAkGameObject::PostAkEventAsync(const UObject* WorldContextObject, + UAkAudioEvent* AkEvent, + int32& PlayingID, + int32 CallbackMask, + const FOnAkPostEventCallback& PostEventCallback, + FLatentActionInfo LatentInfo +) +{ + AkDeviceAndWorld DeviceAndWorld(WorldContextObject); + FLatentActionManager& LatentActionManager = DeviceAndWorld.CurrentWorld->GetLatentActionManager(); + FPostAssociatedEventAction* NewAction = LatentActionManager.FindExistingAction(LatentInfo.CallbackTarget, LatentInfo.UUID); + if (!NewAction) + { + NewAction = new FPostAssociatedEventAction(LatentInfo, &PlayingID, AkEvent, &bEventPosted); + NewAction->FuturePlayingID = DeviceAndWorld.AkAudioDevice->PostAkAudioEventOnAkGameObjectAsync(AkEvent, this, PostEventCallback, CallbackMask); + LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, NewAction); + } +} + +void UAkGameObject::PostAkEventAsyncByEvent(const UObject* WorldContextObject, + class UAkAudioEvent* AkEvent, + int32 CallbackMask, + const FOnAkPostEventCallback& PostEventCallback, + FLatentActionInfo LatentInfo, + int32& PlayingID +) +{ + AkDeviceAndWorld DeviceAndWorld(WorldContextObject); + FLatentActionManager& LatentActionManager = DeviceAndWorld.CurrentWorld->GetLatentActionManager(); + FPostAssociatedEventAction* NewAction = LatentActionManager.FindExistingAction(LatentInfo.CallbackTarget, LatentInfo.UUID); + if (!NewAction) + { + NewAction = new FPostAssociatedEventAction(LatentInfo, &PlayingID, AkEvent, &bEventPosted); + NewAction->FuturePlayingID = DeviceAndWorld.AkAudioDevice->PostAkAudioEventOnAkGameObjectAsync(AkEvent, this, PostEventCallback, CallbackMask); + LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, NewAction); + } +} + +void UAkGameObject::SetRTPCValue(const UAkRtpc* RTPCValue, float Value, int32 InterpolationTimeMs, FString RTPC) const +{ + if (FAkAudioDevice::Get()) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + if (RTPCValue) + { + SoundEngine->SetRTPCValue(RTPCValue->GetShortID(), Value, GetAkGameObjectID(), InterpolationTimeMs); + } + else + { + SoundEngine->SetRTPCValue(TCHAR_TO_AK(*RTPC), Value, GetAkGameObjectID(), InterpolationTimeMs); + } + } +} + +void UAkGameObject::GetRTPCValue(const UAkRtpc* RTPCValue, ERTPCValueType InputValueType, float& Value, ERTPCValueType& OutputValueType, FString RTPC, int32 PlayingID) const +{ + if (FAkAudioDevice::Get()) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + AK::SoundEngine::Query::RTPCValue_type RTPCType = (AK::SoundEngine::Query::RTPCValue_type)InputValueType; + + if (RTPCValue) + { + SoundEngine->Query->GetRTPCValue(RTPCValue->GetShortID(), GetAkGameObjectID(), PlayingID, Value, RTPCType); + } + else + { + SoundEngine->Query->GetRTPCValue(TCHAR_TO_AK(*RTPC), GetAkGameObjectID(), PlayingID, Value, RTPCType); + } + + OutputValueType = (ERTPCValueType)RTPCType; + } +} + +void UAkGameObject::GetRTPCValue(FString RTPC, int32 PlayingID, ERTPCValueType InputValueType, float& Value, ERTPCValueType& OutputValueType) const +{ + GetRTPCValue(nullptr, InputValueType, Value, OutputValueType, RTPC, PlayingID); +} + +bool UAkGameObject::VerifyEventName(const FString& InEventName) const +{ + const bool IsEventNameEmpty = InEventName.IsEmpty(); + if (IsEventNameEmpty) + { + FString OwnerName = FString(TEXT("")); + FString ObjectName = GetName(); + + const auto owner = GetOwner(); + if (owner) + OwnerName = owner->GetName(); + + UE_LOG(LogAkAudio, Warning, TEXT("[%s.%s] AkGameObject: Attempted to post an empty AkEvent name."), *OwnerName, *ObjectName); + } + + return !IsEventNameEmpty; +} + +bool UAkGameObject::AllowAudioPlayback() const +{ + UWorld* CurrentWorld = GetWorld(); + return (CurrentWorld && CurrentWorld->AllowAudioPlayback() && !IsBeingDestroyed()); +} + +AkGameObjectID UAkGameObject::GetAkGameObjectID() const +{ + return (AkGameObjectID)this; +} + +void UAkGameObject::Stop() +{ + if (HasActiveEvents() && FAkAudioDevice::Get() && IsRegisteredWithWwise) + { + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + SoundEngine->StopAll(GetAkGameObjectID()); + SoundEngine->RenderAudio(); + } +} + +bool UAkGameObject::HasActiveEvents() const +{ + auto CallbackManager = FAkComponentCallbackManager::GetInstance(); + return (CallbackManager != nullptr) && CallbackManager->HasActiveEvents(GetAkGameObjectID()); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkGameplayStatics.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkGameplayStatics.cpp new file mode 100644 index 0000000..a5892fd --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkGameplayStatics.cpp @@ -0,0 +1,1078 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkAudioClasses.cpp: +=============================================================================*/ +#include "AkGameplayStatics.h" + +#include "AkAmbientSound.h" +#include "AkAudioDevice.h" +#include "AkAudioEvent.h" +#include "AkAudioType.h" +#include "AkComponent.h" +#include "AkEffectShareSet.h" +#include "AkRtpc.h" +#include "AkStateValue.h" +#include "AkSwitchValue.h" +#include "AkTrigger.h" +#include "Engine/GameEngine.h" +#include "EngineUtils.h" +#include "AkAcousticPortal.h" +#include "AkAuxBus.h" +#include "Wwise/API/WwiseSoundEngineAPI.h" +#include "Wwise/WwiseExternalSourceManager.h" + +#include "inttypes.h" +#include "WwiseInitBankLoader/WwiseInitBankLoader.h" + + +bool UAkGameplayStatics::m_bSoundEngineRecording = false; + +/*----------------------------------------------------------------------------- + UAkGameplayStatics. +-----------------------------------------------------------------------------*/ + +UAkGameplayStatics::UAkGameplayStatics(const class FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + // Property initialization +} + +class UAkComponent * UAkGameplayStatics::GetAkComponent( class USceneComponent* AttachToComponent, bool& ComponentCreated, FName AttachPointName, FVector Location, EAttachLocation::Type LocationType ) +{ + if ( AttachToComponent == NULL ) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::GetAkComponent: NULL AttachToComponent specified!")); + return NULL; + } + + FAkAudioDevice * AkAudioDevice = FAkAudioDevice::Get(); + if( AkAudioDevice ) + { + return AkAudioDevice->GetAkComponent( AttachToComponent, AttachPointName, &Location, LocationType, ComponentCreated ); + } + + return NULL; +} + +bool UAkGameplayStatics::IsEditor() +{ +#if WITH_EDITOR + return true; +#else + return false; +#endif +} + +bool UAkGameplayStatics::IsGame(UObject* WorldContextObject) +{ + EWorldType::Type WorldType = EWorldType::None; + if (WorldContextObject) + { + UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull); + if(World) + WorldType = World->WorldType; + } + + return WorldType == EWorldType::Game || WorldType == EWorldType::GamePreview || WorldType == EWorldType::PIE; +} + +int32 UAkGameplayStatics::PostEvent(UAkAudioEvent* AkEvent, AActor* Actor, int32 CallbackMask, + const FOnAkPostEventCallback& PostEventCallback, bool bStopWhenAttachedToDestroyed, + FString EventName) +{ + if (LIKELY(IsValid(AkEvent))) + { + return AkEvent->PostOnActor(Actor, PostEventCallback, CallbackMask, bStopWhenAttachedToDestroyed); + } + + AkDeviceAndWorld DeviceAndWorld(Actor); + if (UNLIKELY(!DeviceAndWorld.IsValid())) + { + return AK_INVALID_PLAYING_ID; + } + + AkCallbackType AkCallbackMask = AkCallbackTypeHelpers::GetCallbackMaskFromBlueprintMask(CallbackMask); + AkUInt32 ShortId = DeviceAndWorld.AkAudioDevice->GetShortID(AkEvent, EventName); + return DeviceAndWorld.AkAudioDevice->PostEventOnActor(ShortId, Actor, PostEventCallback, AkCallbackMask, bStopWhenAttachedToDestroyed, {}); +} + +int32 UAkGameplayStatics::PostAndWaitForEndOfEvent(UAkAudioEvent* AkEvent, AActor* Actor, bool bStopWhenAttachedToDestroyed, + FLatentActionInfo LatentInfo) +{ + if (UNLIKELY(!IsValid(AkEvent))) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to post and wait invalid AkAudioEvent on actor '%s'."), IsValid(Actor) ? *Actor->GetName() : TEXT("(invalid)")); + return AK_INVALID_PLAYING_ID; + } + + return AkEvent->PostOnActorAndWait(Actor, bStopWhenAttachedToDestroyed, LatentInfo); +} + +void UAkGameplayStatics::PostAndWaitForEndOfEventAsync( + class UAkAudioEvent* AkEvent, + class AActor* Actor, + int32& PlayingID, + bool bStopWhenAttachedToDestroyed, + FLatentActionInfo LatentInfo + ) +{ + if (!AkEvent) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::PostAndWaitForEndOfEventAsync: No Event specified!")); + PlayingID = AK_INVALID_PLAYING_ID; + return; + } + + if (!Actor) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::PostAndWaitForEndOfEventAsync: NULL Actor specified!")); + PlayingID = AK_INVALID_PLAYING_ID; + return; + } + + AkDeviceAndWorld DeviceAndWorld(Actor); + if (UNLIKELY(!DeviceAndWorld.IsValid())) + { + PlayingID = AK_INVALID_PLAYING_ID; + } + + FLatentActionManager& LatentActionManager = DeviceAndWorld.CurrentWorld->GetLatentActionManager(); + FWaitEndOfEventAsyncAction* NewAction = LatentActionManager.FindExistingAction(LatentInfo.CallbackTarget, LatentInfo.UUID); + if (!NewAction) + { + NewAction = new FWaitEndOfEventAsyncAction(LatentInfo, &PlayingID, AkEvent, bStopWhenAttachedToDestroyed); + LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, NewAction); + NewAction->FuturePlayingID = DeviceAndWorld.AkAudioDevice->PostAkAudioEventWithLatentActionOnActorAsync(AkEvent, Actor, bStopWhenAttachedToDestroyed, NewAction); + } +} + +void UAkGameplayStatics::PostEventByName(const FString& EventName, class AActor* Actor, bool bStopWhenAttachedToDestroyed) +{ + AkDeviceAndWorld DeviceAndWorld(Actor); + if (UNLIKELY(!DeviceAndWorld.IsValid())) + { + return; + } + AkUInt32 ShortId = DeviceAndWorld.AkAudioDevice->GetShortID(nullptr, EventName); + DeviceAndWorld.AkAudioDevice->PostEventOnActor(ShortId, Actor, 0, {}, {}, bStopWhenAttachedToDestroyed); +} + +int32 UAkGameplayStatics::PostEventAtLocation(class UAkAudioEvent* AkEvent, FVector Location, FRotator Orientation, const FString& EventName, UObject* WorldContextObject) +{ + if (LIKELY(IsValid(AkEvent))) + { + return AkEvent->PostAtLocation(Location, Orientation, {}, 0, WorldContextObject); + } + AkDeviceAndWorld DeviceAndWorld(WorldContextObject); + if (UNLIKELY(!DeviceAndWorld.IsValid())) + return AK_INVALID_PLAYING_ID; + + AkUInt32 ShortId = DeviceAndWorld.AkAudioDevice->GetShortID(nullptr, EventName); + return DeviceAndWorld.AkAudioDevice->PostEventAtLocation(EventName, ShortId, Location, Orientation, DeviceAndWorld.CurrentWorld); +} + +void UAkGameplayStatics::PostEventAtLocationByName(const FString& EventName, FVector Location, FRotator Orientation, UObject* WorldContextObject) +{ + AkDeviceAndWorld DeviceAndWorld(WorldContextObject); + if (UNLIKELY(!DeviceAndWorld.IsValid())) + return; + + AkUInt32 ShortId = DeviceAndWorld.AkAudioDevice->GetShortID(nullptr, EventName); + DeviceAndWorld.AkAudioDevice->PostEventAtLocation(EventName, ShortId, Location, Orientation, DeviceAndWorld.CurrentWorld); +} + +UAkComponent* UAkGameplayStatics::SpawnAkComponentAtLocation(UObject* WorldContextObject, class UAkAudioEvent* AkEvent, FVector Location, FRotator Orientation, bool AutoPost, const FString& EventName, bool AutoDestroy /* = true*/) +{ + AkDeviceAndWorld DeviceAndWorld(WorldContextObject); + if (UNLIKELY(!DeviceAndWorld.IsValid())) + { + return nullptr; + } + return DeviceAndWorld.AkAudioDevice->SpawnAkComponentAtLocation(AkEvent, Location, Orientation, AutoPost, EventName, AutoDestroy, DeviceAndWorld.CurrentWorld); +} + +void UAkGameplayStatics::ExecuteActionOnEvent(class UAkAudioEvent* AkEvent, AkActionOnEventType ActionType, class AActor* Actor, int32 TransitionDuration, EAkCurveInterpolation FadeCurve, int32 PlayingID) +{ + if (!AkEvent) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::ExecuteActionOnEvent: No Event specified!")); + return; + } + + AkEvent->ExecuteAction(ActionType, Actor, PlayingID, TransitionDuration, FadeCurve); + +} + +void UAkGameplayStatics::ExecuteActionOnPlayingID(AkActionOnEventType ActionType, int32 PlayingID, int32 TransitionDuration, EAkCurveInterpolation FadeCurve) +{ + if (PlayingID == AK_INVALID_PLAYING_ID) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::ExecuteActionOnPlayingID: Invalid Playing ID!")); + return; + } + + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + AudioDevice->ExecuteActionOnPlayingID(ActionType, PlayingID, TransitionDuration, FadeCurve); + } +} + +void UAkGameplayStatics::SetRTPCValue(const UAkRtpc* RTPCValue, float Value, int32 InterpolationTimeMs, AActor* Actor, FName RTPC) +{ + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + if (RTPCValue) + { + AudioDevice->SetRTPCValue(RTPCValue, Value, InterpolationTimeMs, Actor); + } + else if (RTPC.IsValid()) + { + AudioDevice->SetRTPCValue(*RTPC.ToString(), Value, InterpolationTimeMs, Actor); + } + } +} + +void UAkGameplayStatics::GetRTPCValue(const UAkRtpc* RTPCValue, int32 PlayingID, ERTPCValueType InputValueType, float& Value, ERTPCValueType& OutputValueType, AActor* Actor, FName RTPC) +{ + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + AK::SoundEngine::Query::RTPCValue_type RTPCType = (AK::SoundEngine::Query::RTPCValue_type)InputValueType; + + AkGameObjectID IdToGet = AK_INVALID_GAME_OBJECT; + if (Actor != nullptr) + { + UAkComponent * ComponentToGet = AudioDevice->GetAkComponent(Actor->GetRootComponent(), FName(), NULL, EAttachLocation::KeepRelativeOffset); + IdToGet = ComponentToGet->GetAkGameObjectID(); + } + + if (RTPCValue) + { + AudioDevice->GetRTPCValue(RTPCValue, IdToGet, PlayingID, Value, RTPCType); + } + else if (RTPC.IsValid()) + { + AudioDevice->GetRTPCValue(*RTPC.ToString(), IdToGet, PlayingID, Value, RTPCType); + } + + OutputValueType = (ERTPCValueType)RTPCType; + } +} + +void UAkGameplayStatics::ResetRTPCValue(UAkRtpc const* RTPCValue, int32 InterpolationTimeMs, AActor* Actor, FName RTPC) +{ + + FAkAudioDevice* AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + AkGameObjectID IdToGet = AK_INVALID_GAME_OBJECT; + if (Actor != nullptr) + { + UAkComponent* ComponentToGet = AudioDevice->GetAkComponent(Actor->GetRootComponent(), FName(), NULL, EAttachLocation::KeepRelativeOffset); + IdToGet = ComponentToGet->GetAkGameObjectID(); + } + + if (RTPCValue == NULL && RTPC.IsNone()) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::ResetRTPCValue: No parameter specified!")); + return; + } + + + AKRESULT Result = AK_Success; + + if (RTPCValue) + { + Result = AudioDevice->ResetRTPCValue(RTPCValue, IdToGet, InterpolationTimeMs); + } + else if (RTPC.IsValid()) + { + Result = AudioDevice->ResetRTPCValue(*RTPC.ToString(), IdToGet, InterpolationTimeMs); + } + else + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::ResetRTPCValue: Could not reset RTPC value, valid RTPC value not provided")); + } + + if (Result == AK_IDNotFound) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::ResetRTPCValue: Could not reset RTPC value, RTPC %s not found"), *RTPC.ToString()); + } + else if (Result != AK_Success) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::ResetRTPCValue: Could not reset RTPC value!")); + } + } +} + +void UAkGameplayStatics::SetState(const UAkStateValue* StateValue, FName stateGroup, FName state) +{ + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if( AudioDevice && stateGroup.IsValid() && state.IsValid() ) + { + if (StateValue) + { + AudioDevice->SetState(StateValue); + } + else if (stateGroup.IsValid() && state.IsValid()) + { + AudioDevice->SetState(*stateGroup.ToString(), *state.ToString()); + } + } +} + +void UAkGameplayStatics::PostTrigger(const UAkTrigger* TriggerValue, AActor* Actor, FName Trigger) +{ + if ( Actor == NULL ) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::PostTrigger: NULL Actor specified!")); + return; + } + + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if( AudioDevice) + { + if (TriggerValue) + { + AudioDevice->PostTrigger(TriggerValue, Actor); + } + else if (Trigger.IsValid()) + { + AudioDevice->PostTrigger(*Trigger.ToString(), Actor); + } + } +} + +void UAkGameplayStatics::SetSwitch(const UAkSwitchValue* SwitchValue, AActor* Actor, FName SwitchGroup, FName SwitchState) +{ + if (Actor == NULL) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::SetSwitch: NULL Actor specified!")); + return; + } + + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + if (SwitchValue) + { + AudioDevice->SetSwitch(SwitchValue, Actor); + } + else if (SwitchGroup.IsValid() && SwitchState.IsValid()) + { + AudioDevice->SetSwitch(*SwitchGroup.ToString(), *SwitchState.ToString(), Actor); + } + } +} + +void UAkGameplayStatics::SetMultiplePositions(UAkComponent* GameObjectAkComponent, TArray Positions, + AkMultiPositionType MultiPositionType /*= AkMultiPositionType::MultiPositionType_MultiDirections*/) +{ + if (GameObjectAkComponent == NULL) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::SetMultiplePositions: NULL Component specified!")); + return; + } + + FAkAudioDevice * pAudioDevice = FAkAudioDevice::Get(); + if (pAudioDevice) + { + pAudioDevice->SetMultiplePositions(GameObjectAkComponent, Positions, MultiPositionType); + } +} + +void UAkGameplayStatics::SetMultipleChannelEmitterPositions(UAkComponent* GameObjectAkComponent, + TArray ChannelMasks, + TArray Positions, + AkMultiPositionType MultiPositionType +) +{ + if (GameObjectAkComponent == NULL) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::SetMultipleChannelEmitterPositions: NULL Component specified!")); + return; + } + + FAkAudioDevice * pAudioDevice = FAkAudioDevice::Get(); + if (pAudioDevice) + { + pAudioDevice->SetMultiplePositions(GameObjectAkComponent, ChannelMasks, Positions, MultiPositionType); + } +} + +void UAkGameplayStatics::SetMultipleChannelMaskEmitterPositions(UAkComponent* GameObjectAkComponent, + TArray ChannelMasks, + TArray Positions, + AkMultiPositionType MultiPositionType +) +{ + if (GameObjectAkComponent == NULL) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::SetMultipleChannelMaskEmitterPositions: NULL Component specified!")); + return; + } + + FAkAudioDevice * pAudioDevice = FAkAudioDevice::Get(); + if (pAudioDevice) + { + pAudioDevice->SetMultiplePositions(GameObjectAkComponent, ChannelMasks, Positions, MultiPositionType); + } +} + +void UAkGameplayStatics::UseReverbVolumes(bool inUseReverbVolumes, class AActor* Actor ) +{ + if ( Actor == NULL ) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::UseReverbVolumes: NULL Actor specified!")); + return; + } + + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if( AudioDevice ) + { + UAkComponent * ComponentToSet = AudioDevice->GetAkComponent(Actor->GetRootComponent(), FName(), NULL, EAttachLocation::KeepRelativeOffset); + if( ComponentToSet != NULL ) + { + ComponentToSet->UseReverbVolumes(inUseReverbVolumes); + } + } +} + +void UAkGameplayStatics::UseEarlyReflections(class AActor* Actor, + class UAkAuxBus* AuxBus, + int Order, + float BusSendGain, + float MaxPathLength, + bool SpotReflectors, + const FString& AuxBusName) +{ + // Deprecated +} + +void UAkGameplayStatics::SetReflectionsOrder(int Order, bool RefreshPaths) +{ + if (Order > 4 || Order < 0) + { + Order = FMath::Clamp(Order, 0, 4); + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::SetReflectionsOrder: The order value is invalid. It was clamped to %d"), Order); + } + + FAkAudioDevice* AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + AudioDevice->SetReflectionsOrder(Order, RefreshPaths); + } +} + +void UAkGameplayStatics::SetPortalObstructionAndOcclusion(UAkPortalComponent* PortalComponent, float ObstructionValue, float OcclusionValue) +{ + if (ObstructionValue > 1.f || ObstructionValue < 0.f) + { + ObstructionValue = FMath::Clamp(ObstructionValue, 0.f, 1.f); + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::SetPortalObstructionAndOcclusion: The obstruction value is invalid. It was clamped to %f"), ObstructionValue); + } + + if (OcclusionValue > 1.f || OcclusionValue < 0.f) + { + OcclusionValue = FMath::Clamp(OcclusionValue, 0.f, 1.f); + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::SetPortalObstructionAndOcclusion: The occlusion value is invalid. It was clamped to %f"), OcclusionValue); + } + + FAkAudioDevice* AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + AudioDevice->SetPortalObstructionAndOcclusion(PortalComponent, ObstructionValue, OcclusionValue); + } +} + +void UAkGameplayStatics::SetGameObjectToPortalObstruction(UAkComponent* GameObjectAkComponent, UAkPortalComponent* PortalComponent, float ObstructionValue) +{ + if (ObstructionValue > 1.f || ObstructionValue < 0.f) + { + ObstructionValue = FMath::Clamp(ObstructionValue, 0.f, 1.f); + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::SetGameObjectToPortalObstruction: The obstruction value is invalid. It was clamped to %f"), ObstructionValue); + } + + FAkAudioDevice* AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + AudioDevice->SetGameObjectToPortalObstruction(GameObjectAkComponent, PortalComponent, ObstructionValue); + } +} + +void UAkGameplayStatics::SetPortalToPortalObstruction(UAkPortalComponent* PortalComponent0, UAkPortalComponent* PortalComponent1, float ObstructionValue) +{ + if (ObstructionValue > 1.f || ObstructionValue < 0.f) + { + ObstructionValue = FMath::Clamp(ObstructionValue, 0.f, 1.f); + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::SetGameObjectToPortalObstruction: The obstruction value is invalid. It was clamped to %f"), ObstructionValue); + } + + FAkAudioDevice* AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + AudioDevice->SetPortalToPortalObstruction(PortalComponent0, PortalComponent1, ObstructionValue); + } +} + +void UAkGameplayStatics::SetOutputBusVolume(float BusVolume, class AActor* Actor) +{ + if (Actor == NULL) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::SetOutputBusVolume: NULL Actor specified!")); + return; + } + + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + UAkComponent * ComponentToSet = AudioDevice->GetAkComponent(Actor->GetRootComponent(), FName(), NULL, EAttachLocation::KeepRelativeOffset); + if (ComponentToSet != NULL) + { + ComponentToSet->SetOutputBusVolume(BusVolume); + } + } +} + +void UAkGameplayStatics::SetBusConfig(const FString& BusName, AkChannelConfiguration ChannelConfiguration) +{ + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if (UNLIKELY(!AudioDevice)) + { + return; + } + AkChannelConfig config; + FAkAudioDevice::GetChannelConfig(ChannelConfiguration, config); + AudioDevice->SetBusConfig(BusName, config); +} + +void UAkGameplayStatics::SetPanningRule(PanningRule PanRule) +{ + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if (UNLIKELY(!AudioDevice)) + { + return; + } + + AkPanningRule AkPanRule = (PanRule == PanningRule::PanningRule_Headphones) ? AkPanningRule_Headphones : AkPanningRule_Speakers; + AudioDevice->SetPanningRule(AkPanRule); +} + +void UAkGameplayStatics::AddOutput(const FAkOutputSettings& in_Settings, FAkOutputDeviceID& out_DeviceID, UPARAM(ref) TArray& in_ListenerIDs) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::AddOutput: SoundEngine not initialized, new output will not be added.")); + return; + } + AkUInt32 ShortID = SoundEngine->GetIDFromString(TCHAR_TO_ANSI(*in_Settings.AudioDeviceShareSetName)); + if (UNLIKELY(ShortID == AK_INVALID_UNIQUE_ID)) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::AddOutput: Short ID for %s is invalid, new output will not be added.")); + return; + } + AkOutputSettings OutSettings; + OutSettings.audioDeviceShareset = ShortID; + OutSettings.idDevice = in_Settings.IdDevice; + OutSettings.ePanningRule = (in_Settings.PanRule == PanningRule::PanningRule_Headphones) ? AkPanningRule_Headphones : AkPanningRule_Speakers; + FAkAudioDevice::GetChannelConfig(in_Settings.ChannelConfig, OutSettings.channelConfig); + + AkOutputDeviceID outputDeviceID; + AKRESULT result = AK_Fail; + if (in_ListenerIDs.Num() > 0) + { + result = SoundEngine->AddOutput(OutSettings, &outputDeviceID); + } + else + { + TArray akGameObjectIDArray; + for (int i = 0; i < in_ListenerIDs.Num(); i++) + { + akGameObjectIDArray.Add(in_ListenerIDs[i]->GetAkGameObjectID()); + } + result = SoundEngine->AddOutput(OutSettings, &outputDeviceID, akGameObjectIDArray.GetData(), akGameObjectIDArray.Num()); + } + if (result != AK_Success) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::AddOutput: AddOuput has failed, new output will not be added. AkResult: %s"), result); + } + + out_DeviceID.UInt64Value = outputDeviceID; +} + +void UAkGameplayStatics::RemoveOutput(FAkOutputDeviceID in_OutputDeviceId) +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::RemoveOutput: Could not fetch audio device, output will not be removed.")); + return; + } + + SoundEngine->RemoveOutput(in_OutputDeviceId.UInt64Value); +} + +void UAkGameplayStatics::ReplaceMainOutput(const FAkOutputSettings& MainOutputSettings) +{ + FAkAudioDevice* AudioDevice = FAkAudioDevice::Get(); + if (UNLIKELY(!AudioDevice)) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::ReplaceMainOutput: Could not fetch audio device, main output will not be replaced.")); + return; + } + AkUInt32 ShortID = AudioDevice->GetShortIDFromString(MainOutputSettings.AudioDeviceShareSetName); + if (UNLIKELY(ShortID == AK_INVALID_UNIQUE_ID)) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::ReplaceMainOutput: Short ID for %s is invalid, main output will not be replaced.")); + return; + } + + AkOutputSettings OutSettings; + OutSettings.audioDeviceShareset = ShortID; + OutSettings.idDevice = MainOutputSettings.IdDevice; + OutSettings.ePanningRule = (MainOutputSettings.PanRule == PanningRule::PanningRule_Headphones) ? AkPanningRule_Headphones : AkPanningRule_Speakers; + FAkAudioDevice::GetChannelConfig(MainOutputSettings.ChannelConfig, OutSettings.channelConfig); + AudioDevice->ReplaceMainOutput(OutSettings); +} + +void UAkGameplayStatics::GetSpeakerAngles(TArray& SpeakerAngles, float& HeightAngle, const FString& DeviceShareSet) +{ + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if (UNLIKELY(!AudioDevice)) + { + return; + } + AkOutputDeviceID DeviceID = DeviceShareSet.IsEmpty() ? 0 : AudioDevice->GetOutputID(DeviceShareSet); + AudioDevice->GetSpeakerAngles(SpeakerAngles, HeightAngle, DeviceID); + +} + +void UAkGameplayStatics::SetSpeakerAngles(const TArray& SpeakerAngles, float HeightAngles, const FString& DeviceShareSet) +{ + FAkAudioDevice* AudioDevice = FAkAudioDevice::Get(); + if (UNLIKELY(!AudioDevice)) + { + return; + } + AkOutputDeviceID DeviceID = DeviceShareSet.IsEmpty() ? 0 : AudioDevice->GetOutputID(DeviceShareSet); + AudioDevice->SetSpeakerAngles(SpeakerAngles, HeightAngles, DeviceID); + +} + +void UAkGameplayStatics::SetOcclusionRefreshInterval(float RefreshInterval, class AActor* Actor) +{ + if (Actor == NULL) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::SetOcclusionRefreshInterval: NULL Actor specified!")); + return; + } + + FAkAudioDevice* AudioDevice = FAkAudioDevice::Get(); + if (UNLIKELY(!AudioDevice)) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::SetOcclusionRefreshInterval: Could not retrieve audio device.")); + return; + } + + UAkComponent* ComponentToSet = AudioDevice->GetAkComponent(Actor->GetRootComponent(), FName(), NULL, EAttachLocation::KeepRelativeOffset); + if (ComponentToSet != NULL) + { + ComponentToSet->OcclusionRefreshInterval = RefreshInterval; + } +} + + +void UAkGameplayStatics::StopActor(class AActor* Actor) +{ + if ( Actor == NULL ) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::StopActor: NULL Actor specified!")); + return; + } + + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if (UNLIKELY(!AudioDevice)) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::StopActor: Could not retrieve audio device.")); + return; + } + + AudioDevice->StopGameObject(AudioDevice->GetAkComponent(Actor->GetRootComponent(), FName(), NULL, EAttachLocation::KeepRelativeOffset)); +} + +void UAkGameplayStatics::StopAll() +{ + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if (UNLIKELY(!AudioDevice)) + { + return; + } + + AudioDevice->StopAllSounds(); +} + +void UAkGameplayStatics::CancelEventCallback(const FOnAkPostEventCallback& PostEventCallback) +{ + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if (UNLIKELY(!AudioDevice)) + { + return; + } + AudioDevice->CancelEventCallbackDelegate(PostEventCallback); +} + +void UAkGameplayStatics::StartAllAmbientSounds(UObject* WorldContextObject) +{ + AkDeviceAndWorld DeviceAndWorld(WorldContextObject); + if (UNLIKELY(!DeviceAndWorld.IsValid())) + { + return; + } + + for (FActorIterator It(DeviceAndWorld.CurrentWorld); It; ++It) + { + AAkAmbientSound* pAmbientSound = Cast(*It); + if (pAmbientSound != NULL) + { + UAkComponent* pComponent = pAmbientSound->AkComponent; + if (pComponent && GWorld->Scene == pComponent->GetScene()) + { + pAmbientSound->StartPlaying(); + } + } + } + +} + +void UAkGameplayStatics::StopAllAmbientSounds(UObject* WorldContextObject) +{ + AkDeviceAndWorld DeviceAndWorld(WorldContextObject); + if (UNLIKELY(!DeviceAndWorld.IsValid())) + { + return; + } + + for (FActorIterator It(DeviceAndWorld.CurrentWorld); It; ++It) + { + AAkAmbientSound* pAmbientSound = Cast(*It); + if (pAmbientSound != NULL) + { + UAkComponent* pComponent = pAmbientSound->AkComponent; + if (pComponent && GWorld->Scene == pComponent->GetScene()) + { + pAmbientSound->StopPlaying(); + } + } + } +} + +void UAkGameplayStatics::ClearSoundBanksAndMedia() +{ + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if (UNLIKELY(!AudioDevice)) + { + return; + } + AudioDevice->ClearSoundBanksAndMedia(); +} + +void UAkGameplayStatics::ClearBanks() +{ + ClearSoundBanksAndMedia(); +} + +void UAkGameplayStatics::LoadInitBank() +{ + auto* InitBankLoader = FWwiseInitBankLoader::Get(); + if(UNLIKELY(!InitBankLoader)) + { + UE_LOG(LogAkAudio, Error, TEXT("LoadInitBank: WwiseInitBankLoader is not initialized.")); + return; + } + + InitBankLoader->LoadInitBank(); +} + +void UAkGameplayStatics::UnloadInitBank() +{ + auto* InitBankLoader = FWwiseInitBankLoader::Get(); + if(UNLIKELY(!InitBankLoader)) + { + UE_LOG(LogAkAudio, Error, TEXT("UnloadInitBank: WwiseInitBankLoader is not initialized.")); + return; + } + + InitBankLoader->UnloadInitBank(); +} + +void UAkGameplayStatics::LoadBankByName(const FString& BankName) +{ + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if (UNLIKELY(!AudioDevice)) + { + return; + } + AkBankID BankId; + AudioDevice->LoadBank(BankName, BankId); +} + + +void UAkGameplayStatics::UnloadBankByName(const FString& BankName) +{ + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if (UNLIKELY(!AudioDevice)) + { + return; + } + AudioDevice->UnloadBank(BankName); +} + +void UAkGameplayStatics::StartOutputCapture(const FString& Filename) +{ + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if (UNLIKELY(!AudioDevice)) + { + return; + } + FString name = Filename; + if (!name.EndsWith(".wav")) + { + name += ".wav"; + } + AudioDevice->StartOutputCapture(name); +} + +void UAkGameplayStatics::AddOutputCaptureMarker(const FString& MarkerText) +{ + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if( AudioDevice ) + { + AudioDevice->AddOutputCaptureMarker(MarkerText); + } +} + +void UAkGameplayStatics::StopOutputCapture() +{ + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if( AudioDevice ) + { + AudioDevice->StopOutputCapture(); + } +} + +void UAkGameplayStatics::StartProfilerCapture(const FString& Filename) +{ + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if( AudioDevice ) + { + FString name = Filename; + if( !name.EndsWith(".prof") ) + { + name += ".prof"; + } + AudioDevice->StartProfilerCapture(name); + } +} + +void UAkGameplayStatics::StopProfilerCapture() +{ + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if( AudioDevice ) + { + AudioDevice->StopProfilerCapture(); + } +} + +FString UAkGameplayStatics::GetCurrentAudioCulture() +{ + FAkAudioDevice* AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + return AudioDevice->GetCurrentAudioCulture(); + } + + return FString(); +} + +TArray UAkGameplayStatics::GetAvailableAudioCultures() +{ + FAkAudioDevice* AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + return AudioDevice->GetAvailableAudioCultures(); + } + + return TArray(); +} + +void UAkGameplayStatics::SetCurrentAudioCulture(const FString& AudioCulture, FLatentActionInfo LatentInfo, UObject* WorldContextObject) +{ + AkDeviceAndWorld DeviceAndWorld(WorldContextObject); + FLatentActionManager& LatentActionManager = DeviceAndWorld.CurrentWorld->GetLatentActionManager(); + FSetCurrentAudioCultureAction* NewAction = LatentActionManager.FindExistingAction(LatentInfo.CallbackTarget, LatentInfo.UUID); + if (!NewAction) + { + NewAction = new FSetCurrentAudioCultureAction(LatentInfo); + LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, NewAction); + } + + if (FAkAudioDevice* AudioDevice = FAkAudioDevice::Get()) + { + AudioDevice->SetCurrentAudioCultureAsync(AudioCulture, NewAction); + } + else + { + NewAction->ActionDone = true; + } +} + +void UAkGameplayStatics::SetCurrentAudioCultureAsync(const FString& AudioCulture, const FOnSetCurrentAudioCultureCallback& Completed) +{ + if (FAkAudioDevice* AudioDevice = FAkAudioDevice::Get()) + { + AudioDevice->SetCurrentAudioCultureAsync(AudioCulture, FOnSetCurrentAudioCultureCompleted::CreateLambda([Completed](bool Succeeded) { + Completed.ExecuteIfBound(Succeeded); + })); + } +} + +UObject* UAkGameplayStatics::GetAkAudioTypeUserData(const UAkAudioType* Instance, const UClass* Type) +{ + for (auto entry : Instance->UserData) + { + if (entry && entry->GetClass()->IsChildOf(Type)) + { + return entry; + } + } + + return nullptr; +} + +void UAkGameplayStatics::SetDistanceProbe(AActor* Listener, AActor* DistanceProbe) +{ + if (Listener == nullptr) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::SetDistanceProbe: NULL Listener specified!")); + return; + } + + if (FAkAudioDevice* AudioDevice = FAkAudioDevice::Get()) + { + UAkComponent * ListenerAkComponent = AudioDevice->GetAkComponent(Listener->GetRootComponent(), FName(), NULL, EAttachLocation::KeepRelativeOffset); + + UAkComponent* DistanceProbeAkComponent = + DistanceProbe != nullptr ? + AudioDevice->GetAkComponent(DistanceProbe->GetRootComponent(), FName(), NULL, EAttachLocation::KeepRelativeOffset) : + nullptr; + + AudioDevice->SetDistanceProbe(ListenerAkComponent, DistanceProbeAkComponent); + } +} + +bool UAkGameplayStatics::SetOutputDeviceEffect(const FAkOutputDeviceID InDeviceID, const int32 InEffectIndex, const UAkEffectShareSet* InEffectShareSet) +{ + if(UNLIKELY(!InEffectShareSet)) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::SetOutputDeviceEffect: NULL Effect ShareSet specified!")); + return false; + } + + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return false; + + UE_LOG(LogAkAudio, Verbose, TEXT("UAkGameplayStatics::SetOutputDeviceEffect: DeviceID: %" PRIu64 ", InEffectIndex: %d, EffectShareSet Asset Name: %s, EffectShareSet ShortID: %" PRIu32 "."), + InDeviceID.UInt64Value, InEffectIndex, *InEffectShareSet->GetName(), InEffectShareSet->GetShortID()); + + AKRESULT Result = SoundEngine->SetOutputDeviceEffect(InDeviceID.UInt64Value, InEffectIndex, InEffectShareSet->GetShortID()); + return Result == AK_Success; +} + +bool UAkGameplayStatics::SetBusEffectByName(const FString InBusName, const int32 InEffectIndex, const UAkEffectShareSet* InEffectShareSet) +{ + if(UNLIKELY(!InEffectShareSet)) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::SetBusEffectByName: NULL Effect ShareSet specified!")); + return false; + } + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return false; + + UE_LOG(LogAkAudio, Verbose, TEXT("UAkGameplayStatics::SetBusEffectByName: BusName: %s, InEffectIndex: %d, EffectShareSet Asset Name: %s, EffectShareSet ShortID: %" PRIu32 "."), + *InBusName, InEffectIndex, *InEffectShareSet->GetName(), InEffectShareSet->GetShortID()); + + AKRESULT Result = SoundEngine->SetBusEffect(TCHAR_TO_AK(*InBusName), InEffectIndex, InEffectShareSet->GetShortID()); + return Result == AK_Success; +} + +bool UAkGameplayStatics::SetBusEffectByID(const FAkUniqueID InBusID, const int32 InEffectIndex, const UAkEffectShareSet* InEffectShareSet) +{ + if(UNLIKELY(!InEffectShareSet)) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::SetBusEffectByID: NULL Effect ShareSet specified!")); + return false; + } + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return false; + + UE_LOG(LogAkAudio, Verbose, TEXT("UAkGameplayStatics::SetBusEffectByID: BusID: %" PRIu32 ", InEffectIndex: %d, EffectShareSet Asset Name: %s, EffectShareSet ShortID: %" PRIu32 "."), + InBusID.UInt32Value, InEffectIndex, *InEffectShareSet->GetName(), InEffectShareSet->GetShortID()); + AKRESULT Result = SoundEngine->SetBusEffect(InBusID.UInt32Value, InEffectIndex, InEffectShareSet->GetShortID()); + return Result == AK_Success; +} + +bool UAkGameplayStatics::SetAuxBusEffect(const UAkAuxBus* InAuxBus, const int32 InEffectIndex, const UAkEffectShareSet* InEffectShareSet) +{ + if(UNLIKELY(!InEffectShareSet)) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::SetAuxBusEffect: NULL Effect ShareSet specified!")); + return false; + } + if(UNLIKELY(!InAuxBus)) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::SetAuxBusEffect: NULL Aux Bus specified!")); + return false; + } + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return false; + + UE_LOG(LogAkAudio, Verbose, TEXT("UAkGameplayStatics::SetAuxBusEffect: AuxBus Asset Name: %s, AuxBus Short ID: %" PRIu32 ", InEffectIndex: %d, EffectShareSet Asset Name: %s, EffectShareSet ShortID: %" PRIu32 "."), + *InAuxBus->GetName(), InAuxBus->GetShortID(), InEffectIndex, *InEffectShareSet->GetName(), InEffectShareSet->GetShortID()); + AKRESULT Result = SoundEngine->SetBusEffect(InAuxBus->GetShortID(), InEffectIndex, InEffectShareSet->GetShortID()); + return Result == AK_Success; +} + +bool UAkGameplayStatics::SetActorMixerEffect(const FAkUniqueID InAudioNodeID,const int32 InEffectIndex, const UAkEffectShareSet* InEffectShareSet) +{ + if(UNLIKELY(!InEffectShareSet)) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkGameplayStatics::SetActorMixerEffect: NULL Effect ShareSet specified!")); + return false; + } + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return false; + + UE_LOG(LogAkAudio, Verbose, TEXT("UAkGameplayStatics::SetActorMixerEffect: AudioNodeID: %" PRIu32 ", InEffectIndex: %d, EffectShareSet Asset Name: %s, EffectShareSet ShortID: %" PRIu32 "."), + InAudioNodeID.UInt32Value, InEffectIndex, *InEffectShareSet->GetName(), InEffectShareSet->GetShortID()); + AKRESULT Result = SoundEngine->SetActorMixerEffect(InAudioNodeID.UInt32Value, InEffectIndex, InEffectShareSet->GetShortID()); + return Result == AK_Success; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkGameplayTypes.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkGameplayTypes.cpp new file mode 100644 index 0000000..504aba0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkGameplayTypes.cpp @@ -0,0 +1,414 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkAudioClasses.cpp: +=============================================================================*/ + +#include "AkGameplayTypes.h" + +#include "AkAudioDevice.h" +#include "AkAudioEvent.h" +#include "AkCallbackInfoPool.h" +#include "AkComponent.h" +#include "AkUnrealHelper.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "Engine/GameEngine.h" +#include "EngineUtils.h" +#include "AkCallbackInfoPool.h" +#include "HAL/PlatformString.h" + +UAkCallbackInfo* AkCallbackTypeHelpers::GetBlueprintableCallbackInfo(EAkCallbackType CallbackType, AkCallbackInfo* CallbackInfo) +{ + switch (CallbackType) + { + case EAkCallbackType::EndOfEvent: + return UAkEventCallbackInfo::Create((AkEventCallbackInfo*)CallbackInfo); + case EAkCallbackType::Marker: + return UAkMarkerCallbackInfo::Create((AkMarkerCallbackInfo*)CallbackInfo); + case EAkCallbackType::Duration: + return UAkDurationCallbackInfo::Create((AkDurationCallbackInfo*)CallbackInfo); + case EAkCallbackType::Starvation: + return UAkEventCallbackInfo::Create((AkEventCallbackInfo*)CallbackInfo); + case EAkCallbackType::MusicPlayStarted: + return UAkEventCallbackInfo::Create((AkEventCallbackInfo*)CallbackInfo); + case EAkCallbackType::MusicSyncBeat: + case EAkCallbackType::MusicSyncBar: + case EAkCallbackType::MusicSyncEntry: + case EAkCallbackType::MusicSyncExit: + case EAkCallbackType::MusicSyncGrid: + case EAkCallbackType::MusicSyncUserCue: + case EAkCallbackType::MusicSyncPoint: + return UAkMusicSyncCallbackInfo::Create((AkMusicSyncCallbackInfo*)CallbackInfo); + case EAkCallbackType::MIDIEvent: + return UAkMIDIEventCallbackInfo::Create((AkMIDIEventCallbackInfo*)CallbackInfo); + default: + return nullptr; + } + return nullptr; +} + +AkCallbackInfo* AkCallbackTypeHelpers::CopyWwiseCallbackInfo(AkCallbackType CallbackType, AkCallbackInfo* SourceCallbackInfo) +{ + switch (CallbackType) + { + case AK_EndOfEvent: + case AK_Starvation: + case AK_MusicPlayStarted: + { + AkEventCallbackInfo* CbInfoCopy = (AkEventCallbackInfo*)FMemory::Malloc(sizeof(AkEventCallbackInfo)); + FMemory::Memcpy(CbInfoCopy, SourceCallbackInfo, sizeof(AkEventCallbackInfo)); + return CbInfoCopy; + } + case AK_Marker: + { + const char* SourceLabel = ((AkMarkerCallbackInfo*)SourceCallbackInfo)->strLabel; + int32 LabelSize = SourceLabel ? FPlatformString::Strlen(SourceLabel) + 1 : 0; + AkMarkerCallbackInfo* CbInfoCopy = (AkMarkerCallbackInfo*)FMemory::Malloc(sizeof(AkMarkerCallbackInfo) + LabelSize); + FMemory::Memcpy(CbInfoCopy, SourceCallbackInfo, sizeof(AkMarkerCallbackInfo)); + + if (SourceLabel) + { + CbInfoCopy->strLabel = reinterpret_cast(CbInfoCopy) + sizeof(AkMarkerCallbackInfo); + FPlatformString::Strcpy(const_cast(CbInfoCopy->strLabel), LabelSize - 1, SourceLabel); + } + return CbInfoCopy; + } + case AK_Duration: + { + AkDurationCallbackInfo* CbInfoCopy = (AkDurationCallbackInfo*)FMemory::Malloc(sizeof(AkDurationCallbackInfo)); + FMemory::Memcpy(CbInfoCopy, SourceCallbackInfo, sizeof(AkDurationCallbackInfo)); + return CbInfoCopy; + } + case AK_MusicSyncBeat: + case AK_MusicSyncBar: + case AK_MusicSyncEntry: + case AK_MusicSyncExit: + case AK_MusicSyncGrid: + case AK_MusicSyncUserCue: + case AK_MusicSyncPoint: + { + const char* SourceUserCue = ((AkMusicSyncCallbackInfo*)SourceCallbackInfo)->pszUserCueName; + int32 UserCueSize = SourceUserCue ? FPlatformString::Strlen(SourceUserCue) + 1 : 0; + AkMusicSyncCallbackInfo* CbInfoCopy = (AkMusicSyncCallbackInfo*)FMemory::Malloc(sizeof(AkMusicSyncCallbackInfo) + UserCueSize); + FMemory::Memcpy(CbInfoCopy, SourceCallbackInfo, sizeof(AkMusicSyncCallbackInfo)); + + //SourceUserCue is either null or a non-empty string + if (SourceUserCue) + { + CbInfoCopy->pszUserCueName = reinterpret_cast(CbInfoCopy) + sizeof(AkMusicSyncCallbackInfo); + FPlatformString::Strcpy(const_cast(CbInfoCopy->pszUserCueName), UserCueSize, SourceUserCue); + } + return CbInfoCopy; + } + case AK_MIDIEvent: + { + AkMIDIEventCallbackInfo* CbInfoCopy = (AkMIDIEventCallbackInfo*)FMemory::Malloc(sizeof(AkMIDIEventCallbackInfo)); + FMemory::Memcpy(CbInfoCopy, SourceCallbackInfo, sizeof(AkMIDIEventCallbackInfo)); + return CbInfoCopy; + } + default: + return nullptr; + } + return nullptr; +} + +AkCallbackType AkCallbackTypeHelpers::GetCallbackMaskFromBlueprintMask(int32 BlueprintCallbackType) +{ + return (AkCallbackType)BlueprintCallbackType; +} + +EAkCallbackType AkCallbackTypeHelpers::GetBlueprintCallbackTypeFromAkCallbackType(AkCallbackType CallbackType) +{ + uint32 BitIndex = 0; + uint32 CbType = (uint32)CallbackType >> 1; + while (CbType != 0) + { + CbType >>= 1; + BitIndex++; + } + return (EAkCallbackType)BitIndex; +} + +UAkCallbackInfo::UAkCallbackInfo( class FObjectInitializer const & ObjectInitializer) : + Super(ObjectInitializer) +{} + +UAkCallbackInfo* UAkCallbackInfo::Create(AkGameObjectID GameObjectID) +{ + auto CbInfo = FAkAudioDevice::Get()->GetAkCallbackInfoPool()->Acquire(); + if (CbInfo) + { + CbInfo->AkComponent = UAkComponent::GetAkComponent(GameObjectID); + } + return CbInfo; +} + +void UAkCallbackInfo::Reset() +{ + AkComponent = nullptr; +} + +UAkEventCallbackInfo::UAkEventCallbackInfo(class FObjectInitializer const & ObjectInitializer) : + Super(ObjectInitializer) +{} + +UAkEventCallbackInfo* UAkEventCallbackInfo::Create(AkEventCallbackInfo* AkEventCbInfo) +{ + auto CbInfo = FAkAudioDevice::Get()->GetAkCallbackInfoPool()->Acquire(); + if (CbInfo) + { + CbInfo->AkComponent = UAkComponent::GetAkComponent(AkEventCbInfo->gameObjID); + CbInfo->PlayingID = AkEventCbInfo->playingID; + CbInfo->EventID = AkEventCbInfo->eventID; + } + return CbInfo; +} + +UAkMIDIEventCallbackInfo::UAkMIDIEventCallbackInfo(class FObjectInitializer const & ObjectInitializer) : + Super(ObjectInitializer) +{} + +UAkMIDIEventCallbackInfo* UAkMIDIEventCallbackInfo::Create(AkMIDIEventCallbackInfo* AkMIDIEventCbInfo) +{ + auto CbInfo = FAkAudioDevice::Get()->GetAkCallbackInfoPool()->Acquire(); + if (CbInfo) + { + CbInfo->AkComponent = UAkComponent::GetAkComponent(AkMIDIEventCbInfo->gameObjID); + CbInfo->PlayingID = AkMIDIEventCbInfo->playingID; + CbInfo->EventID = AkMIDIEventCbInfo->eventID; + CbInfo->AkMidiEvent = AkMIDIEventCbInfo->midiEvent; + } + return CbInfo; +} + +UAkMarkerCallbackInfo::UAkMarkerCallbackInfo(class FObjectInitializer const & ObjectInitializer) : + Super(ObjectInitializer) +{} + +UAkMarkerCallbackInfo* UAkMarkerCallbackInfo::Create(AkMarkerCallbackInfo* AkMarkerCbInfo) +{ + auto CbInfo = FAkAudioDevice::Get()->GetAkCallbackInfoPool()->Acquire(); + if (CbInfo) + { + CbInfo->AkComponent = UAkComponent::GetAkComponent(AkMarkerCbInfo->gameObjID); + CbInfo->PlayingID = AkMarkerCbInfo->playingID; + CbInfo->EventID = AkMarkerCbInfo->eventID; + CbInfo->Identifier = AkMarkerCbInfo->uIdentifier; + CbInfo->Position = AkMarkerCbInfo->uPosition; + CbInfo->Label = FString(AkMarkerCbInfo->strLabel); + } + return CbInfo; +} + +UAkDurationCallbackInfo::UAkDurationCallbackInfo(class FObjectInitializer const & ObjectInitializer) : + Super(ObjectInitializer) +{} + +UAkDurationCallbackInfo* UAkDurationCallbackInfo::Create(AkDurationCallbackInfo* AkDurationCbInfo) +{ + auto CbInfo = FAkAudioDevice::Get()->GetAkCallbackInfoPool()->Acquire(); + if (CbInfo) + { + CbInfo->AkComponent = UAkComponent::GetAkComponent(AkDurationCbInfo->gameObjID); + CbInfo->PlayingID = AkDurationCbInfo->playingID; + CbInfo->EventID = AkDurationCbInfo->eventID; + CbInfo->Duration = AkDurationCbInfo->fDuration; + CbInfo->EstimatedDuration = AkDurationCbInfo->fEstimatedDuration; + CbInfo->AudioNodeID = AkDurationCbInfo->audioNodeID; + CbInfo->MediaID = AkDurationCbInfo->mediaID; + CbInfo->bStreaming = AkDurationCbInfo->bStreaming; + } + return CbInfo; +} + +UAkMusicSyncCallbackInfo::UAkMusicSyncCallbackInfo(class FObjectInitializer const & ObjectInitializer) : + Super(ObjectInitializer) +{} + +UAkMusicSyncCallbackInfo* UAkMusicSyncCallbackInfo::Create(AkMusicSyncCallbackInfo* AkMusicCbInfo) +{ + auto CbInfo = FAkAudioDevice::Get()->GetAkCallbackInfoPool()->Acquire(); + if (CbInfo) + { + CbInfo->AkComponent = UAkComponent::GetAkComponent(AkMusicCbInfo->gameObjID); + CbInfo->PlayingID = AkMusicCbInfo->playingID; + CbInfo->SegmentInfo = AkMusicCbInfo->segmentInfo; + CbInfo->MusicSyncType = AkCallbackTypeHelpers::GetBlueprintCallbackTypeFromAkCallbackType(AkMusicCbInfo->musicSyncType); + CbInfo->UserCueName = FString(AkMusicCbInfo->pszUserCueName); + } + return CbInfo; +} + +EAkMidiEventType UAkMIDIEventCallbackInfo::GetType() +{ + return (EAkMidiEventType)AkMidiEvent.byType; +} + +uint8 UAkMIDIEventCallbackInfo::GetChannel() +{ + // Add one here so we report "Artist" channel number (between 1 and 16), instead of reporting the underlying value of 0-F. + return AkMidiEvent.byChan + 1; +} + +bool UAkMIDIEventCallbackInfo::GetGeneric(FAkMidiGeneric& AsGeneric) +{ + AsGeneric = FAkMidiGeneric(AkMidiEvent); + return true; +} + +bool UAkMIDIEventCallbackInfo::GetNoteOn(FAkMidiNoteOnOff& AsNoteOn) +{ + if (GetType() != EAkMidiEventType::AkMidiEventTypeNoteOn) + { + return false; + } + + AsNoteOn = FAkMidiNoteOnOff(AkMidiEvent); + return true; +} + +bool UAkMIDIEventCallbackInfo::GetNoteOff(FAkMidiNoteOnOff& AsNoteOff) +{ + if (GetType() != EAkMidiEventType::AkMidiEventTypeNoteOff) + { + return false; + } + + AsNoteOff = FAkMidiNoteOnOff(AkMidiEvent); + return true; +} + +bool UAkMIDIEventCallbackInfo::GetCc(FAkMidiCc& AsCc) +{ + if (GetType() != EAkMidiEventType::AkMidiEventTypeController) + { + return false; + } + + AsCc = FAkMidiCc(AkMidiEvent); + return true; +} + +bool UAkMIDIEventCallbackInfo::GetPitchBend(FAkMidiPitchBend& AsPitchBend) +{ + if (GetType() != EAkMidiEventType::AkMidiEventTypePitchBend) + { + return false; + } + + AsPitchBend = FAkMidiPitchBend(AkMidiEvent); + return true; +} + +bool UAkMIDIEventCallbackInfo::GetNoteAftertouch(FAkMidiNoteAftertouch& AsNoteAftertouch) +{ + if (GetType() != EAkMidiEventType::AkMidiEventTypeNoteAftertouch) + { + return false; + } + + AsNoteAftertouch = FAkMidiNoteAftertouch(AkMidiEvent); + return true; +} + +bool UAkMIDIEventCallbackInfo::GetChannelAftertouch(FAkMidiChannelAftertouch& AsChannelAftertouch) +{ + if (GetType() != EAkMidiEventType::AkMidiEventTypeChannelAftertouch) + { + return false; + } + + AsChannelAftertouch = FAkMidiChannelAftertouch(AkMidiEvent); + return true; +} + +bool UAkMIDIEventCallbackInfo::GetProgramChange(FAkMidiProgramChange& AsProgramChange) +{ + if (GetType() != EAkMidiEventType::AkMidiEventTypeProgramChange) + { + return false; + } + + AsProgramChange = FAkMidiProgramChange(AkMidiEvent); + return true; +} + +FAkSDKExternalSourceArray::FAkSDKExternalSourceArray(const TArray& BlueprintArray) +{ + for (auto& ExternalSourceInfo : BlueprintArray) + { + AkOSChar* OsCharArray = nullptr; + void* MediaData = nullptr; + AkUInt32 MediaSize = 0; + + if (ExternalSourceInfo.ExternalSourceAsset) + { + UE_LOG(LogAkAudio, Error, TEXT("FAkSDKExternalSourceArray: ExternalSourceAssets are not supported. Please migrate your project and use AkAudioEvent.")); + return; + } + else + { + auto ExternalFileName = ExternalSourceInfo.FileName; + if (FPaths::GetExtension(ExternalFileName).IsEmpty()) + { + ExternalFileName += TEXT(".wem"); + } + OsCharArray = (AkOSChar*)FMemory::Malloc((ExternalFileName.Len() + 1) * sizeof(AkOSChar)); + FPlatformString::Strcpy(OsCharArray, ExternalFileName.Len(), TCHAR_TO_AK(*(ExternalFileName))); + + ExternalSourceArray.Emplace(OsCharArray, FAkAudioDevice::GetShortIDFromString(ExternalSourceInfo.ExternalSrcName), (AkCodecID)ExternalSourceInfo.CodecID); + } + } +} + +FAkSDKExternalSourceArray::~FAkSDKExternalSourceArray() +{ +} + +void FWaitEndOfEventAsyncAction::UpdateOperation(FLatentResponse& Response) +{ + if (FuturePlayingID.IsReady()) + { + *PlayingID = FuturePlayingID.Get(); + if (*PlayingID == AK_INVALID_PLAYING_ID) + { + EventFinished = true; + } + + if (EventFinished) + { + Response.FinishAndTriggerIf(true, ExecutionFunction, OutputLink, CallbackTarget); + } + } +} + +AkDeviceAndWorld::AkDeviceAndWorld(AActor* in_pActor) : + AkAudioDevice(FAkAudioDevice::Get()), + CurrentWorld(in_pActor ? in_pActor->GetWorld() : nullptr) +{ +} + +AkDeviceAndWorld::AkDeviceAndWorld(const UObject* in_pWorldContextObject) : + AkAudioDevice(FAkAudioDevice::Get()), + CurrentWorld(GEngine->GetWorldFromContextObject(in_pWorldContextObject, EGetWorldErrorMode::ReturnNull)) +{} + +bool AkDeviceAndWorld::IsValid() const +{ + return (CurrentWorld && CurrentWorld->AllowAudioPlayback() && AkAudioDevice); +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkGeometryComponent.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkGeometryComponent.cpp new file mode 100644 index 0000000..0343e7e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkGeometryComponent.cpp @@ -0,0 +1,1199 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkGeometryComponent.h" +#include "AkAcousticTexture.h" +#include "AkAudioDevice.h" +#include "AkComponentHelpers.h" +#include "AkReverbDescriptor.h" +#include "AkRoomComponent.h" +#include "AkSettings.h" +#include "AkUEFeatures.h" + +#if AK_USE_PHYSX +#include "PhysXPublic.h" +#endif + +#if AK_USE_CHAOS +#include "ChaosLog.h" +#include "Chaos/CollisionConvexMesh.h" +#include "Chaos/Convex.h" +#endif + +#if WITH_EDITOR +#include "Editor.h" +#endif + +#include "RawIndexBuffer.h" +#include "StaticMeshResources.h" +#include "Components/StaticMeshComponent.h" +#include "Engine/GameEngine.h" +#include "Engine/Polys.h" +#include "Engine/StaticMesh.h" +#include "PhysicsEngine/BodySetup.h" +#include "UObject/Object.h" + +static const float kVertexNear = 0.0001; + +UAkGeometryComponent::UAkGeometryComponent(const class FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer) +{ + // Property initialization + bWantsOnUpdateTransform = true; + + MeshType = AkMeshType::CollisionMesh; + LOD = 0; + CollisionMeshSurfaceOverride.AcousticTexture = nullptr; + CollisionMeshSurfaceOverride.bEnableOcclusionOverride = false; + CollisionMeshSurfaceOverride.OcclusionValue = 1.f; + WeldingThreshold = 0.001; + + bWasAddedByRoom = 0; + bEnableDiffraction = 1; + bEnableDiffractionOnBoundaryEdges = 0; +#if WITH_EDITOR + PrimaryComponentTick.bCanEverTick = true; + bTickInEditor = true; +#endif +} + +void UAkGeometryComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + Super::OnComponentDestroyed(bDestroyingHierarchy); + RemoveGeometry(); + StaticMeshSurfaceOverride.Empty(); +#if WITH_EDITOR + check(!OnMeshMaterialChangedHandle.IsValid()); +#endif +} + +void UAkGeometryComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) +{ + Super::OnUpdateTransform(UpdateTransformFlags, Teleport); + + UpdateGeometry(); + if (ReverbDescriptor != nullptr) + { + DampingEstimationNeedsUpdate = true; + } +} + +bool UAkGeometryComponent::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 UAkGeometryComponent::BeginPlay() +{ + Super::BeginPlay(); + if (!IsBeingDestroyed()) + { +#if WITH_EDITOR + if (AkComponentHelpers::ShouldDeferBeginPlay(this)) + bRequiresDeferredBeginPlay = true; + else + BeginPlayInternal(); +#else + BeginPlayInternal(); +#endif + } +} + +void UAkGeometryComponent::BeginPlayInternal() +{ + + if (Parent == nullptr) + InitializeParent(); + + if (GeometryData.Vertices.Num() == 0) + ConvertMesh(); + + for (int PosIndex = 0; PosIndex < GeometryData.Surfaces.Num(); ++PosIndex) + { + // set geometry surface names and update textures + FString OwnerName; +#if WITH_EDITOR + OwnerName = GetOwner()->GetActorLabel(); +#else + OwnerName = GetOwner()->GetName(); +#endif + GeometryData.Surfaces[PosIndex].Name = OwnerName + GetName() + FString::FromInt(PosIndex); + + UPhysicalMaterial* physMat = GeometryData.ToOverrideAcousticTexture[PosIndex]; + if (physMat) + { + UAkAcousticTexture* acousticTexture = nullptr; + if (GetDefault()->GetAssociatedAcousticTexture(physMat, acousticTexture)) + { + if (acousticTexture) + GeometryData.Surfaces[PosIndex].Texture = acousticTexture->GetShortID(); + } + } + + physMat = GeometryData.ToOverrideOcclusion[PosIndex]; + if (physMat) + { + float occlusionValue = 1.f; + if (GetDefault()->GetAssociatedOcclusionValue(physMat, occlusionValue)) + { + GeometryData.Surfaces[PosIndex].Occlusion = occlusionValue; + } + } + } + + SendGeometry(); + UpdateGeometry(); + DampingEstimationNeedsUpdate = true; +} + +void UAkGeometryComponent::OnRegister() +{ + Super::OnRegister(); + SetRelativeTransform(FTransform::Identity); + InitializeParent(); +#if WITH_EDITOR + OnMeshMaterialChangedHandle = FCoreUObjectDelegates::OnObjectPropertyChanged.AddLambda([this](UObject* Object, FPropertyChangedEvent& PropertyChangedEvent) + { + if (PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(UMeshComponent, OverrideMaterials) && + Parent != nullptr && + Parent == Object && + MeshType == AkMeshType::StaticMesh + ) + { + UpdateStaticMeshOverride(); + } + }); + if (Parent != nullptr) + { + if (MeshType == AkMeshType::StaticMesh) + { + UStaticMeshComponent* MeshParent = Cast(Parent); + if (MeshParent != nullptr) + CalculateSurfaceArea(MeshParent); + } + DampingEstimationNeedsUpdate = true; + } +#endif +} + +void UAkGeometryComponent::OnUnregister() +{ +#if WITH_EDITOR + FCoreUObjectDelegates::OnObjectPropertyChanged.Remove(OnMeshMaterialChangedHandle); + OnMeshMaterialChangedHandle.Reset(); +#endif + Parent = nullptr; + + Super::OnUnregister(); +} + +void UAkGeometryComponent::InitializeParent() +{ + USceneComponent* SceneParent = GetAttachParent(); + if (SceneParent != nullptr) + { + Parent = Cast(SceneParent); + if (!Parent) + { + FString actorString = FString("NONE"); + if (GetOwner() != nullptr) + actorString = GetOwner()->GetName(); + FString parentName = SceneParent->GetName(); + FString parentClass = SceneParent->GetClass()->GetName(); + UE_LOG(LogAkAudio, Error, + TEXT("On actor %s, there is a UAkGeometryComponent (%s) attached to parent of type %s (%s).") + , *actorString, *GetName(), *parentClass, *parentName); + if (MeshType == AkMeshType::StaticMesh) + { + UE_LOG(LogAkAudio, Error, TEXT("When MeshType is set to Static Mesh, UAkGeometryComponent requires to be nested under a component inheriting from UStaticMeshComponent.")); + } + else + { + UE_LOG(LogAkAudio, Error, TEXT("When MeshType is set to Simple Collision, UAkGeometryComponent requires to be nested under a component inheriting from UPrimitiveComponent.")); + } + return; + } + if (MeshType == AkMeshType::StaticMesh) + { + UStaticMeshComponent* MeshParent = Cast(SceneParent); + if (MeshParent != nullptr) + { + bool fillMaterialKeys = true; +#if WITH_EDITOR + // Fill the materials in the StaticMeshSurfaceOverride map only when there is no ongoing transaction (e.g. a user changing a property in the details panel). + if (GetOwner() != nullptr && GetOwner()->CurrentTransactionAnnotation != nullptr) + { + fillMaterialKeys = false; + } +#endif + if (fillMaterialKeys) + { + UpdateMeshAndArchetype(MeshParent); + } + } + else + { + FString actorString = FString("NONE"); + if (GetOwner() != nullptr) + actorString = GetOwner()->GetName(); + FString parentName = SceneParent->GetName(); + FString parentClass = SceneParent->GetClass()->GetName(); + UE_LOG(LogAkAudio, Warning, + TEXT("On actor %s, there is a UAkGeometryComponent (%s) attached to parent of type %s (%s).") + , *actorString, *GetName(), *parentClass, *parentName); + UE_LOG(LogAkAudio, Warning, TEXT("When MeshType is set to Static Mesh, UAkGeometryComponent requires to be nested under a component inheriting from UStaticMeshComponent. Reverting to Simple Collision.")); + MeshType = AkMeshType::CollisionMesh; + + // If we're in the Blueprint editor, update the Archetype object as well. + UWorld* World = GetWorld(); + if (World != nullptr && World->WorldType == EWorldType::EditorPreview + && CreationMethod == EComponentCreationMethod::SimpleConstructionScript) + { + UAkGeometryComponent* Archetype = Cast(GetArchetype()); + if (Archetype != nullptr) + Archetype->MeshType = AkMeshType::CollisionMesh; + } + } + } + } +} + +void UAkGeometryComponent::CalculateSurfaceArea(UStaticMeshComponent* StaticMeshComponent) +{ + SurfaceAreas.Empty(); + + UStaticMesh* mesh = StaticMeshComponent->GetStaticMesh(); +#if UE_4_27_OR_LATER + if (mesh == nullptr || !mesh->GetRenderData()) + return; +#else + if (mesh == nullptr || !mesh->RenderData) + return; +#endif + + const FStaticMeshLODResources& RenderMesh = mesh->GetLODForExport(LOD); + FIndexArrayView RawIndices = RenderMesh.IndexBuffer.GetArrayView(); + + if (RawIndices.Num() == 0) + return; + + const int32 PolygonsCount = RenderMesh.Sections.Num(); + double SurfaceArea = 0.0; + const auto WorldScale = StaticMeshComponent->GetOwner()->ActorToWorld().GetScale3D(); + for (int32 PolygonsIndex = 0; PolygonsIndex < PolygonsCount; ++PolygonsIndex) + { + const FStaticMeshSection& Polygons = RenderMesh.Sections[PolygonsIndex]; + + const uint32 TriangleCount = Polygons.NumTriangles; + for (uint32 TriangleIndex = 0; TriangleIndex < TriangleCount; ++TriangleIndex) + { + const uint32 RawVertIndex0 = RawIndices[Polygons.FirstIndex + ((TriangleIndex * 3) + 0)]; + const uint32 RawVertIndex1 = RawIndices[Polygons.FirstIndex + ((TriangleIndex * 3) + 1)]; + const uint32 RawVertIndex2 = RawIndices[Polygons.FirstIndex + ((TriangleIndex * 3) + 2)]; + + // Scale to world space to ensure proper area + auto ScaledP0 = WorldScale * FVector(RenderMesh.VertexBuffers.PositionVertexBuffer.VertexPosition(RawVertIndex0).GridSnap(WeldingThreshold)); + auto ScaledP1 = WorldScale * FVector(RenderMesh.VertexBuffers.PositionVertexBuffer.VertexPosition(RawVertIndex1).GridSnap(WeldingThreshold)); + auto ScaledP2 = WorldScale * FVector(RenderMesh.VertexBuffers.PositionVertexBuffer.VertexPosition(RawVertIndex2).GridSnap(WeldingThreshold)); + SurfaceArea += FAkReverbDescriptor::TriangleArea(ScaledP0, ScaledP1, ScaledP2); + } + SurfaceAreas.Add(PolygonsIndex, SurfaceArea); + } +} + +bool AddVertsForEdge(const FPositionVertexBuffer& Positions, TArray& UniqueVerts, int32 P0UnrealIdx, int32 P0UniqueIdx, int32 P1UnrealIdx, int32 P1UniqueIdx, TArray< TPair > & VertsOnEdge, float WeldingThreshold) +{ + auto p0 = Positions.VertexPosition(P0UnrealIdx).GridSnap(WeldingThreshold); + auto p1 = Positions.VertexPosition(P1UnrealIdx).GridSnap(WeldingThreshold); + + FUnrealFloatVector Dir; + float Length; + (p1 - p0).ToDirectionAndLength(Dir, Length); + + if (Length <= FLT_MIN) + return false; + + for (int32 i = 0; i < UniqueVerts.Num(); i++) + { + const int32 UnrealVertIdx = UniqueVerts[i]; + auto p = Positions.VertexPosition(UnrealVertIdx).GridSnap(WeldingThreshold); + + float Dot = FUnrealFloatVector::DotProduct(p - p0, Dir); + const float RelLength = Dot / Length; + if (RelLength > kVertexNear && RelLength < 1.f + kVertexNear) + { + FUnrealFloatVector PtOnLine = p0 + Dot * Dir; + FUnrealFloatVector Diff = PtOnLine - p; + const float RelDiff = Diff.GetAbsMax() / Length; + if (RelDiff < kVertexNear) + { + VertsOnEdge.Emplace(i, Dot); + } + } + } + + // VertsOnEdge should contain p1 but not p0 + check(VertsOnEdge.Num() > 0); + + VertsOnEdge.Sort([](const TPair& One, const TPair& Two) + { + return One.Value < Two.Value; + }); + + return true; +} + +void DetermineVertsToWeld(TArray& VertRemap, TArray& UniqueVerts, const FStaticMeshLODResources& RenderMesh, float WeldingThreshold) +{ + const int32 VertexCount = RenderMesh.VertexBuffers.PositionVertexBuffer.GetNumVertices(); + + // Maps unreal verts to reduced list of verts + VertRemap.Empty(VertexCount); + VertRemap.AddUninitialized(VertexCount); + + // List of Unreal Verts to keep + UniqueVerts.Empty(VertexCount); + + // Combine matching verts using hashed search to maintain good performance + TMap HashedVerts; + for (int32 a = 0; a < VertexCount; a++) + { + auto PositionA = RenderMesh.VertexBuffers.PositionVertexBuffer.VertexPosition(a).GridSnap(WeldingThreshold); + const int32* FoundIndex = HashedVerts.Find(PositionA); + if (!FoundIndex) + { + int32 NewIndex = UniqueVerts.Add(a); + VertRemap[a] = NewIndex; + HashedVerts.Add(PositionA, NewIndex); + } + else + { + VertRemap[a] = *FoundIndex; + } + } +} + +void UAkGeometryComponent::ConvertMesh() +{ + if (!(Parent && IsValid(Parent))) + return; + + const UAkSettings* AkSettings = GetDefault(); + + switch (MeshType) + { + case AkMeshType::StaticMesh: + { + UStaticMeshComponent* MeshParent = Cast(Parent); + if (MeshParent != nullptr) + ConvertStaticMesh(MeshParent, AkSettings); + break; + } + case AkMeshType::CollisionMesh: + { + ConvertCollisionMesh(Parent, AkSettings); + break; + } + } +} + +void UAkGeometryComponent::ConvertStaticMesh(UStaticMeshComponent* StaticMeshComponent, const UAkSettings* AkSettings) +{ + UStaticMesh* mesh = StaticMeshComponent->GetStaticMesh(); + if (!(mesh && IsValid(mesh))) + return; + + if (LOD > mesh->GetNumLODs() - 1) + LOD = mesh->GetNumLODs() - 1; + +#if UE_4_27_OR_LATER + if (!mesh->GetRenderData()) + return; +#else + if (!mesh->RenderData) + return; +#endif + + const FStaticMeshLODResources& RenderMesh = mesh->GetLODForExport(LOD); + FIndexArrayView RawIndices = RenderMesh.IndexBuffer.GetArrayView(); + + if (RawIndices.Num() == 0) + return; + + GeometryData.Clear(); + + TArray VertRemap; + TArray UniqueVerts; + + DetermineVertsToWeld(VertRemap, UniqueVerts, RenderMesh, WeldingThreshold); + + for (int PosIndex = 0; PosIndex < UniqueVerts.Num(); ++PosIndex) + { + const int32 UnrealPosIndex = UniqueVerts[PosIndex]; + auto VertexInActorSpace = RenderMesh.VertexBuffers.PositionVertexBuffer.VertexPosition(UnrealPosIndex); + GeometryData.Vertices.Add(FVector(VertexInActorSpace)); + } + + UpdateMeshAndArchetype(StaticMeshComponent); + CalculateSurfaceArea(StaticMeshComponent); + + const int32 PolygonsCount = RenderMesh.Sections.Num(); + for (int32 PolygonsIndex = 0; PolygonsIndex < PolygonsCount; ++PolygonsIndex) + { + const FStaticMeshSection& Polygons = RenderMesh.Sections[PolygonsIndex]; + + FAkAcousticSurface Surface; + UPhysicalMaterial* physMatTexture = nullptr; + UPhysicalMaterial* physMatOcclusion = nullptr; + FAkGeometrySurfaceOverride surfaceOverride; + + UMaterialInterface* Material = StaticMeshComponent->GetMaterial(Polygons.MaterialIndex); + if (Material) + { + UPhysicalMaterial* physicalMaterial = Material->GetPhysicalMaterial(); + + if (StaticMeshSurfaceOverride.Contains(Material)) + surfaceOverride = StaticMeshSurfaceOverride[Material]; + + if (!surfaceOverride.AcousticTexture) + physMatTexture = physicalMaterial; + + if (!surfaceOverride.bEnableOcclusionOverride) + physMatOcclusion = physicalMaterial; + } + + if (surfaceOverride.AcousticTexture) + Surface.Texture = surfaceOverride.AcousticTexture->GetShortID(); + + if (surfaceOverride.bEnableOcclusionOverride) + Surface.Occlusion = surfaceOverride.OcclusionValue; + + GeometryData.Surfaces.Add(Surface); + GeometryData.ToOverrideAcousticTexture.Add(physMatTexture); + GeometryData.ToOverrideOcclusion.Add(physMatOcclusion); + AkSurfIdx surfIdx = (AkSurfIdx)(GeometryData.Surfaces.Num() - 1); + + TArray< TPair > Edge0, Edge1, Edge2; + const uint32 TriangleCount = Polygons.NumTriangles; + for (uint32 TriangleIndex = 0; TriangleIndex < TriangleCount; ++TriangleIndex) + { + uint32 RawVertIndex0 = RawIndices[Polygons.FirstIndex + ((TriangleIndex * 3) + 0)]; + uint32 UniqueVertIndex0 = VertRemap[RawVertIndex0]; + + uint32 RawVertIndex1 = RawIndices[Polygons.FirstIndex + ((TriangleIndex * 3) + 1)]; + uint32 UniqueVertIndex1 = VertRemap[RawVertIndex1]; + + uint32 RawVertIndex2 = RawIndices[Polygons.FirstIndex + ((TriangleIndex * 3) + 2)]; + uint32 UniqueVertIndex2 = VertRemap[RawVertIndex2]; + + Edge0.Empty(8); + bool succeeded = AddVertsForEdge(RenderMesh.VertexBuffers.PositionVertexBuffer, UniqueVerts, RawVertIndex0, UniqueVertIndex0, RawVertIndex1, UniqueVertIndex1, Edge0, WeldingThreshold); + if (!succeeded) + { + UE_LOG(LogAkAudio, Warning, TEXT("%s: UAkGeometryComponent::ConvertStaticMesh Vertex IDs %i and %i are too close resulting in a triangle with an area of 0. The triangle will be skipped."), *GetOwner()->GetName(), RawVertIndex0, RawVertIndex1); + continue; + } + + Edge1.Empty(8); + succeeded = AddVertsForEdge(RenderMesh.VertexBuffers.PositionVertexBuffer, UniqueVerts, RawVertIndex1, UniqueVertIndex1, RawVertIndex2, UniqueVertIndex2, Edge1, WeldingThreshold); + if (!succeeded) + { + UE_LOG(LogAkAudio, Warning, TEXT("%s: UAkGeometryComponent::ConvertStaticMesh Vertex IDs %i and %i are too close resulting in a triangle with an area of 0. The triangle will be skipped."), *GetOwner()->GetName(), RawVertIndex1, RawVertIndex2); + continue; + } + + Edge2.Empty(8); + succeeded = AddVertsForEdge(RenderMesh.VertexBuffers.PositionVertexBuffer, UniqueVerts, RawVertIndex2, UniqueVertIndex2, RawVertIndex0, UniqueVertIndex0, Edge2, WeldingThreshold); + if (!succeeded) + { + UE_LOG(LogAkAudio, Warning, TEXT("%s: UAkGeometryComponent::ConvertStaticMesh Vertex IDs %i and %i are too close resulting in a triangle with an area of 0. The triangle will be skipped."), *GetOwner()->GetName(), RawVertIndex2, RawVertIndex0); + continue; + } + + FAkTriangle triangle; + triangle.Surface = surfIdx; + + bool bDone = false; + do + { + int32 v0, v1, v2; + + if (Edge0.Num() > 1) + { + v1 = Edge0.Pop().Key; + v0 = Edge0.Last().Key; + v2 = Edge1[0].Key; + } + else if (Edge1.Num() > 1) + { + v1 = Edge1.Pop().Key; + v0 = Edge1.Last().Key; + v2 = Edge2[0].Key; + } + else if (Edge2.Num() > 1) + { + v1 = Edge2.Pop().Key; + v0 = Edge2.Last().Key; + v2 = Edge0[0].Key; + } + else + { + v0 = Edge0[0].Key; + v1 = Edge1[0].Key; + v2 = Edge2[0].Key; + bDone = true; + } + + triangle.Point0 = (AkVertIdx)v0; + triangle.Point1 = (AkVertIdx)v1; + triangle.Point2 = (AkVertIdx)v2; + + if (triangle.Point0 != triangle.Point1 && + triangle.Point1 != triangle.Point2 && + triangle.Point2 != triangle.Point0) + GeometryData.Triangles.Add(triangle); + } while (!bDone); + + } + if (SurfaceAreas.Contains(PolygonsIndex)) + surfaceOverride.SetSurfaceArea(SurfaceAreas[PolygonsIndex]); + } +} + +struct VertexIndexByAngle +{ + AkVertIdx Index; + float Angle; +}; + +#if AK_USE_PHYSX +void ConvertConvexMeshToGeometryData(AkSurfIdx surfIdx, const FKConvexElem& convexHull, FAkGeometryData* GeometryData) +{ + physx::PxConvexMesh* convexMesh = convexHull.GetConvexMesh(); + + AkVertIdx initialVertIdx = GeometryData->Vertices.Num(); + if (convexMesh != nullptr) + { + const PxVec3* vertices = convexMesh->getVertices(); + + uint32 numVerts = (uint32)convexMesh->getNbVertices(); + for (uint32 vertIdx = 0; vertIdx < numVerts; ++vertIdx) + { + FVector akvtx; + akvtx.X = vertices[vertIdx].x; + akvtx.Y = vertices[vertIdx].y; + akvtx.Z = vertices[vertIdx].z; + GeometryData->Vertices.Add(akvtx); + } + + const physx::PxU8* indexBuf = convexMesh->getIndexBuffer(); + + uint32 numPolys = (uint32)convexMesh->getNbPolygons(); + for (uint32 polyIdx = 0; polyIdx < numPolys; polyIdx++) + { + PxHullPolygon polyData; + convexMesh->getPolygonData(polyIdx, polyData); + + // order the vertices of the polygon + uint32 numVertsInPoly = (uint32)polyData.mNbVerts; + uint32 vertIdxOffset = (uint32)polyData.mIndexBase; + + TArray orderedIndexes; + // first element is first vertex index + AkVertIdx firstVertIdx = (AkVertIdx)indexBuf[vertIdxOffset]; + orderedIndexes.Add(VertexIndexByAngle{ firstVertIdx, 0 }); + + // get the center of the polygon + FVector center(0, 0, 0); + for (uint32 polyVertIdx = 0; polyVertIdx < numVertsInPoly; ++polyVertIdx) + { + auto vertIdx = indexBuf[vertIdxOffset + polyVertIdx]; + center.X += vertices[vertIdx].x; + center.Y += vertices[vertIdx].y; + center.Z += vertices[vertIdx].z; + } + center.X /= numVertsInPoly; + center.Y /= numVertsInPoly; + center.Z /= numVertsInPoly; + + // get the vector from center to the first vertex + FVector v0; + v0.X = vertices[firstVertIdx].x - center.X; + v0.Y = vertices[firstVertIdx].y - center.Y; + v0.Z = vertices[firstVertIdx].z - center.Z; + v0.Normalize(); + + // get the normal of the plane + FVector n; + n.X = polyData.mPlane[0]; + n.Y = polyData.mPlane[1]; + n.Z = polyData.mPlane[2]; + n.Normalize(); + + // find the angles between v0 and the other vertices of the polygon + for (uint32 polyVertIdx = 1; polyVertIdx < numVertsInPoly; polyVertIdx++) + { + // get the vector from center to the current vertex + AkVertIdx vertIdx = (AkVertIdx)indexBuf[vertIdxOffset + polyVertIdx]; + FVector v1; + v1.X = vertices[vertIdx].x - center.X; + v1.Y = vertices[vertIdx].y - center.Y; + v1.Z = vertices[vertIdx].z - center.Z; + v1.Normalize(); + + // get the angle between v0 and v1 + // to do so, we need the dot product and the determinant respectively proportional to cos and sin of the angle. + // atan2(sin, cos) will give us the angle + float dot = FVector::DotProduct(v0, v1); + // the determinant of two 3D vectors in the same plane can be found with the dot product of the normal with the result of + // a cross product between the vectors + float det = FVector::DotProduct(n, FVector::CrossProduct(v0, v1)); + float angle = (float)atan2(det, dot); + + orderedIndexes.Add(VertexIndexByAngle{ vertIdx, angle }); + } + + orderedIndexes.Sort(); + + // fan triangulation + for (uint32 vertIdx = 1; vertIdx < numVertsInPoly - 1; ++vertIdx) + { + FAkTriangle tri; + tri.Point0 = (AkVertIdx)orderedIndexes[0].Index + initialVertIdx; + tri.Point1 = (AkVertIdx)orderedIndexes[vertIdx].Index + initialVertIdx; + tri.Point2 = (AkVertIdx)orderedIndexes[vertIdx + 1].Index + initialVertIdx; + tri.Surface = surfIdx; + + GeometryData->Triangles.Add(tri); + } + } + } + else + { + // bounding box + GeometryData->AddBox(surfIdx, + convexHull.ElemBox.GetCenter(), + convexHull.ElemBox.GetExtent(), + convexHull.GetTransform().Rotator()); + } +} +#endif + +bool operator<(const VertexIndexByAngle& lhs, const VertexIndexByAngle& rhs) +{ + return lhs.Angle < rhs.Angle; +} + +void UAkGeometryComponent::ConvertCollisionMesh(UPrimitiveComponent* PrimitiveComponent, const UAkSettings* AkSettings) +{ + UBodySetup* bodySetup = PrimitiveComponent->GetBodySetup(); + if (!(bodySetup && IsValid(bodySetup))) + return; + + GeometryData.Clear(); + + FAkAcousticSurface Surface; + UPhysicalMaterial* physicalMaterial = bodySetup->GetPhysMaterial(); + UPhysicalMaterial* physMatTexture = nullptr; + UPhysicalMaterial* physMatOcclusion = nullptr; + FAkGeometrySurfaceOverride surfaceOverride = CollisionMeshSurfaceOverride; + + if (surfaceOverride.AcousticTexture) + Surface.Texture = surfaceOverride.AcousticTexture->GetShortID(); + else + physMatTexture = physicalMaterial; + + if (surfaceOverride.bEnableOcclusionOverride) + Surface.Occlusion = surfaceOverride.OcclusionValue; + else + physMatOcclusion = physicalMaterial; + + GeometryData.ToOverrideAcousticTexture.Add(physMatTexture); + GeometryData.ToOverrideOcclusion.Add(physMatOcclusion); + + GeometryData.Surfaces.Add(Surface); + + AkSurfIdx surfIdx = (AkSurfIdx)(GeometryData.Surfaces.Num() - 1); + + int32 numBoxes = bodySetup->AggGeom.BoxElems.Num(); + for (int32 i = 0; i < numBoxes; i++) + { + FKBoxElem box = bodySetup->AggGeom.BoxElems[i]; + + FVector extent; + extent.X = box.X / 2; + extent.Y = box.Y / 2; + extent.Z = box.Z / 2; + + if ((extent.Z == 0.0f && (extent.X == 0.0f || extent.Y == 0.0f)) + || (extent.Y == 0.0f && (extent.X == 0.0f || extent.Z == 0.0f)) + || (extent.X == 0.0f && (extent.Y == 0.0f || extent.Z == 0.0f))) + { + UE_LOG(LogAkAudio, Warning, TEXT("%s: UAkGeometryComponent::ConvertCollisionMesh: Unable to add box geometry for box index %i as the box contains no triangles. The box will be skipped."), *GetOwner()->GetName(), i); + continue; + } + + GeometryData.AddBox(surfIdx, box.Center, extent, box.Rotation); + } + + const int sides = 16; + + int32 numSpheres = bodySetup->AggGeom.SphereElems.Num(); + for (int32 i = 0; i < numSpheres; i++) + { + FKSphereElem sphere = bodySetup->AggGeom.SphereElems[i]; + GeometryData.AddSphere(surfIdx, sphere.Center, sphere.Radius, sides, sides / 2); + } + + int32 numCapsules = bodySetup->AggGeom.SphylElems.Num(); + for (int32 i = 0; i < numCapsules; i++) + { + FKSphylElem capsule = bodySetup->AggGeom.SphylElems[i]; + + FVector X = capsule.GetTransform().GetUnitAxis(EAxis::X); + FVector Y = capsule.GetTransform().GetUnitAxis(EAxis::Y); + FVector Z = capsule.GetTransform().GetUnitAxis(EAxis::Z); + + GeometryData.AddCapsule(surfIdx, capsule.Center, X, Y, Z, capsule.Radius, capsule.Length / 2, sides / 2); + } + + int32 numConvexElems = bodySetup->AggGeom.ConvexElems.Num(); + for (int32 i = 0; i < numConvexElems; i++) + { + FKConvexElem& convexElem = bodySetup->AggGeom.ConvexElems[i]; + + int32 numVertices = convexElem.VertexData.Num(); + if (numVertices == 0) + continue; + +#if AK_USE_CHAOS + // will compute IndexData if it is empty + convexElem.ComputeChaosConvexIndices(false); +#endif + + int32 numTriangles = convexElem.IndexData.Num() / 3; + if (numTriangles == 0) + { +#if AK_USE_PHYSX + ConvertConvexMeshToGeometryData(surfIdx, convexElem, &GeometryData); +#endif + continue; + } + + int32 vertexOffset = GeometryData.Vertices.Num(); + for (int32 vertIdx = 0; vertIdx < numVertices; ++vertIdx) + { + GeometryData.Vertices.Add(convexElem.GetTransform().TransformPosition(convexElem.VertexData[vertIdx])); + } + for (int32 triIdx = 0; triIdx < numTriangles; ++triIdx) + { + FAkTriangle tri; + tri.Point0 = vertexOffset + convexElem.IndexData[3 * triIdx]; + tri.Point1 = vertexOffset + convexElem.IndexData[3 * triIdx + 1]; + tri.Point2 = vertexOffset + convexElem.IndexData[3 * triIdx + 2]; + tri.Surface = surfIdx; + + GeometryData.Triangles.Add(tri); + } + } +} + +void UAkGeometryComponent::SendGeometry() +{ + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + + if (AkAudioDevice && ShouldSendGeometry()) + { + if (GeometryData.Triangles.Num() > 0 && GeometryData.Vertices.Num() > 0) + { + AkGeometryParams params; + params.NumSurfaces = GeometryData.Surfaces.Num(); + params.NumTriangles = GeometryData.Triangles.Num(); + params.NumVertices = GeometryData.Vertices.Num(); + + TArray Surfaces; + TArray< TSharedPtr< decltype(StringCast(TEXT(""))) > > SurfaceNames; + Surfaces.SetNum(params.NumSurfaces); + SurfaceNames.SetNum(params.NumSurfaces); + + if (params.NumSurfaces) + { + for (int i = 0; i < params.NumSurfaces; ++i) + { + Surfaces[i].transmissionLoss = GeometryData.Surfaces[i].Occlusion; + Surfaces[i].strName = nullptr; + if (!GeometryData.Surfaces[i].Name.IsEmpty()) + { + SurfaceNames[i] = MakeShareable(new decltype(StringCast(TEXT("")))(*GeometryData.Surfaces[i].Name)); + Surfaces[i].strName = SurfaceNames[i].Get()->Get(); + } + Surfaces[i].textureID = GeometryData.Surfaces[i].Texture; + } + } + params.Surfaces = Surfaces.GetData(); + + TUniquePtr Triangles = MakeUnique(params.NumTriangles);// temp triangle buffer + for (int i = 0; i < params.NumTriangles; ++i) + { + Triangles[i].point0 = GeometryData.Triangles[i].Point0; + Triangles[i].point1 = GeometryData.Triangles[i].Point1; + Triangles[i].point2 = GeometryData.Triangles[i].Point2; + Triangles[i].surface = GeometryData.Triangles[i].Surface; + } + params.Triangles = Triangles.Get(); + + TUniquePtr Vertices = MakeUnique(params.NumVertices); // temp vertex buffer + for (int i = 0; i < params.NumVertices; ++i) + { + Vertices[i].X = GeometryData.Vertices[i].X; + Vertices[i].Y = GeometryData.Vertices[i].Y; + Vertices[i].Z = GeometryData.Vertices[i].Z; + } + params.Vertices = Vertices.Get(); + + params.EnableDiffraction = bEnableDiffraction; + params.EnableDiffractionOnBoundaryEdges = bEnableDiffractionOnBoundaryEdges; + params.EnableTriangles = !bWasAddedByRoom; + + SendGeometryToWwise(params); + } + else + { + UE_LOG(LogAkAudio, Warning, TEXT("%s: UAkGeometryComponent::SendGeometry() Geometry Data is empty. Nothing was sent to Spatial Audio."), *GetOwner()->GetName()); + } + } +} + +void UAkGeometryComponent::RemoveGeometry() +{ + RemoveGeometryFromWwise(); +} + +void UAkGeometryComponent::UpdateGeometry() +{ + if (Parent) + { + AkRoomID roomID = AkRoomID(); + + if (AssociatedRoom) + { + UAkRoomComponent* room = Cast(AssociatedRoom->GetComponentByClass(UAkRoomComponent::StaticClass())); + + if (room != nullptr) + roomID = room->GetRoomID(); + } + + SendGeometryInstanceToWwise(Parent->GetComponentRotation(), Parent->GetComponentLocation(), Parent->GetComponentTransform().GetScale3D(), roomID); + } +} + +void UAkGeometryComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + RemoveGeometry(); +} + +float UAkGeometryComponent::GetSurfaceAreaSquaredMeters(const int& surfaceIndex) const +{ + if (SurfaceAreas.Contains(surfaceIndex)) + return SurfaceAreas[surfaceIndex] / AkComponentHelpers::UnrealUnitsPerSquaredMeter(Parent); + return 0.0f; +} + +#if WITH_EDITOR +void UAkGeometryComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + if (AkComponentHelpers::IsGameWorldBlueprintComponent(this) || IsBeingDestroyed()) + return; + if (AssociatedRoom && !Cast(AssociatedRoom->GetComponentByClass(UAkRoomComponent::StaticClass()))) + { + UE_LOG(LogAkAudio, Warning, TEXT("%s: The Associated Room is not of type UAkRoomComponent."), *GetOwner()->GetName()); + } + + const FName memberPropertyName = (PropertyChangedEvent.MemberProperty != nullptr) ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None; + const FName PropertyName = (PropertyChangedEvent.Property != nullptr) ? PropertyChangedEvent.Property->GetFName() : NAME_None; + if (memberPropertyName == GET_MEMBER_NAME_CHECKED(UAkGeometryComponent, MeshType)) + { + if (Parent != nullptr && MeshType == AkMeshType::StaticMesh) + { + UStaticMeshComponent* MeshParent = Cast(Parent); + if (MeshParent == nullptr) + { + FString actorString = FString("NONE"); + if (GetOwner() != nullptr) + actorString = GetOwner()->GetName(); + FString parentName = Parent->GetName(); + FString parentClass = Parent->GetClass()->GetName(); + UE_LOG(LogAkAudio, Warning, + TEXT("On actor %s, there is a UAkGeometryComponent (%s) attached to parent of type %s (%s).") + , *actorString, *GetName(), *parentClass, *parentName); + UE_LOG(LogAkAudio, Warning, TEXT("When MeshType is set to Static Mesh, UAkGeometryComponent requires to be nested under a component inheriting from UStaticMeshComponent. Reverting to Simple Collision.")); + MeshType = AkMeshType::CollisionMesh; + } + else + { + UpdateStaticMeshOverride(); + } + } + UnregisterTextureParamChangeCallbacks(); + RegisterAllTextureParamCallbacks(); + } + else if ( (memberPropertyName == GET_MEMBER_NAME_CHECKED(UAkGeometryComponent, StaticMeshSurfaceOverride) && MeshType == AkMeshType::StaticMesh) + || (memberPropertyName == GET_MEMBER_NAME_CHECKED(UAkGeometryComponent, CollisionMeshSurfaceOverride) && MeshType == AkMeshType::CollisionMesh)) + { + UnregisterTextureParamChangeCallbacks(); + RegisterAllTextureParamCallbacks(); + DampingEstimationNeedsUpdate = true; + } + if (MeshType == AkMeshType::StaticMesh && + memberPropertyName == GET_MEMBER_NAME_CHECKED(UAkGeometryComponent, WeldingThreshold) && + PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet) + { + ConvertMesh(); + SendGeometry(); + UpdateGeometry(); + } + else if (memberPropertyName == GET_MEMBER_NAME_CHECKED(UAkGeometryComponent, bEnableDiffraction) || + memberPropertyName == GET_MEMBER_NAME_CHECKED(UAkGeometryComponent, bEnableDiffractionOnBoundaryEdges)) + { + SendGeometry(); + UpdateGeometry(); + } +} + +void UAkGeometryComponent::PostEditUndo() +{ + OnRefreshDetails.ExecuteIfBound(); + Super::PostEditUndo(); +} + +void UAkGeometryComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + if (bRequiresDeferredBeginPlay) + { + BeginPlayInternal(); + bRequiresDeferredBeginPlay = false; + } + if (MeshType == AkMeshType::StaticMesh) + { + if (IsValid(Parent) && !Parent->IsBeingDestroyed()) + { + UStaticMeshComponent* MeshParent = Cast(Parent); + if (MeshParent != nullptr && StaticMeshSurfaceOverride.Num() != MeshParent->GetNumMaterials()) + UpdateMeshAndArchetype(MeshParent); + } + if (bMeshMaterialChanged) + { + OnRefreshDetails.ExecuteIfBound(); + bMeshMaterialChanged = false; + } + } +} +#endif + +void UAkGeometryComponent::UpdateStaticMeshOverride() +{ + UStaticMeshComponent* MeshParent = Cast(Parent); + if (MeshParent != nullptr) + { + UpdateMeshAndArchetype(MeshParent); + } +} + +void UAkGeometryComponent::UpdateMeshAndArchetype(UStaticMeshComponent* StaticMeshComponent) +{ + _UpdateStaticMeshOverride(StaticMeshComponent); + // If we're in the Blueprint editor, update the Archetype object as well. + // (The archetype object is the object that's edited when properties are changed in the Blueprint editor details inspector. + // This is a separate object from the objects shown in the Blueprint editor viewport.) + UWorld* World = GetWorld(); + if (World != nullptr && World->WorldType == EWorldType::EditorPreview + && CreationMethod == EComponentCreationMethod::SimpleConstructionScript) + { + UAkGeometryComponent* Archetype = Cast(GetArchetype()); + if (Archetype != nullptr) + Archetype->_UpdateStaticMeshOverride(StaticMeshComponent); + } +} + +void UAkGeometryComponent::_UpdateStaticMeshOverride(UStaticMeshComponent* StaticMeshComponent) +{ + auto ToRemove = StaticMeshSurfaceOverride; + + int numMaterials = StaticMeshComponent->GetNumMaterials(); + for (int i = 0; i < numMaterials; i++) + { + UMaterialInterface* material = StaticMeshComponent->GetMaterial(i); + if (StaticMeshSurfaceOverride.Contains(material)) + ToRemove.Remove(material); + else + { + FAkGeometrySurfaceOverride surfaceOverride; + if (PreviousStaticMeshSurfaceOverride.Contains(material)) + surfaceOverride = PreviousStaticMeshSurfaceOverride[material]; + + StaticMeshSurfaceOverride.Add(material, surfaceOverride); + } + } + +#if WITH_EDITORONLY_DATA + if (ToRemove.Num() > 0) + bMeshMaterialChanged = true; +#endif + for (auto& elemToRemove : ToRemove) + StaticMeshSurfaceOverride.Remove(elemToRemove.Key); + + ToRemove.Empty(); + + PreviousStaticMeshSurfaceOverride.Empty(); + PreviousStaticMeshSurfaceOverride = StaticMeshSurfaceOverride; +} + +void UAkGeometryComponent::Serialize(FArchive& Ar) +{ +#if WITH_EDITORONLY_DATA + UWorld* World = GetWorld(); + if (Ar.IsSaving() && World != nullptr && !World->IsGameWorld()) + ConvertMesh(); +#endif + + Super::Serialize(Ar); +} + +void UAkGeometryComponent::GetTexturesAndSurfaceAreas(TArray& textures, TArray& surfaceAreas) const +{ + textures.Empty(); + surfaceAreas.Empty(); + const UAkSettings* AkSettings = GetDefault(); + if (AkSettings != nullptr) + { + if (MeshType == AkMeshType::CollisionMesh) + { + if (CollisionMeshSurfaceOverride.AcousticTexture != nullptr) + { + const FAkAcousticTextureParams* params = AkSettings->GetTextureParams(CollisionMeshSurfaceOverride.AcousticTexture->GetShortID()); + if (params != nullptr) + { + textures.Add(*params); + surfaceAreas.Add(1.0f); // When there is only 1 acoustic texture, surface area magnitude is not important. + } + } + } + else + { + if (StaticMeshSurfaceOverride.Num() > 0) + { + int surfIdx = 0; + float surfaceArea = 0.0f; + for (auto it = StaticMeshSurfaceOverride.CreateConstIterator(); it; ++it) + { + surfaceArea = GetSurfaceAreaSquaredMeters(surfIdx); + FAkGeometrySurfaceOverride surface = it.Value(); + surfaceAreas.Add(surfaceArea); + if (surface.AcousticTexture != nullptr) + { + const FAkAcousticTextureParams* params = AkSettings->GetTextureParams(surface.AcousticTexture->GetShortID()); + if (params != nullptr) + { + textures.Add(*params); + } + } + else + { + textures.Add(FAkAcousticTextureParams()); + } + ++surfIdx; + } + } + } + } +} + + +#if WITH_EDITOR +void UAkGeometryComponent::HandleObjectsReplaced(const TMap& ReplacementMap) +{ + Super::HandleObjectsReplaced(ReplacementMap); + if (ReplacementMap.Contains(Parent)) + { + InitializeParent(); + if (Parent != nullptr) + { + if (MeshType == AkMeshType::StaticMesh) + { + UStaticMeshComponent* MeshParent = Cast(Parent); + if (MeshParent != nullptr) + CalculateSurfaceArea(MeshParent); + } + DampingEstimationNeedsUpdate = true; + } + } +} + +bool UAkGeometryComponent::ContainsTexture(const FGuid& textureID) +{ + if (MeshType == AkMeshType::CollisionMesh) + { + if (CollisionMeshSurfaceOverride.AcousticTexture != nullptr) + return CollisionMeshSurfaceOverride.AcousticTexture->AcousticTextureInfo.WwiseGuid == textureID; + } + else + { + for (auto it = StaticMeshSurfaceOverride.CreateIterator(); it; ++it) + { + if (it.Value().AcousticTexture != nullptr) + { + if (it.Value().AcousticTexture->AcousticTextureInfo.WwiseGuid == textureID) + return true; + } + } + } + return false; +} + +void UAkGeometryComponent::RegisterAllTextureParamCallbacks() +{ + if (MeshType == AkMeshType::CollisionMesh) + { + if (CollisionMeshSurfaceOverride.AcousticTexture != nullptr) + RegisterTextureParamChangeCallback(CollisionMeshSurfaceOverride.AcousticTexture->AcousticTextureInfo.WwiseGuid); + } + else + { + for (auto it = StaticMeshSurfaceOverride.CreateIterator(); it; ++it) + { + if (it.Value().AcousticTexture != nullptr) + { + RegisterTextureParamChangeCallback(it.Value().AcousticTexture->AcousticTextureInfo.WwiseGuid); + } + } + } +} +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkGeometryData.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkGeometryData.cpp new file mode 100644 index 0000000..9653edb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkGeometryData.cpp @@ -0,0 +1,281 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkGeometryData.h" + +#include "PhysicalMaterials/PhysicalMaterial.h" + +void GetBasicBoxGeometryData(TArray& Vertices, TArray& Triangles) +{ + Vertices.Init(FVector(0, 0, 0), 8); + Vertices[0] = FVector(-1, -1, -1); + Vertices[1] = FVector(-1, -1, 1); + Vertices[2] = FVector(-1, 1, -1); + Vertices[3] = FVector(-1, 1, 1); + Vertices[4] = FVector(1, -1, -1); + Vertices[5] = FVector(1, -1, 1); + Vertices[6] = FVector(1, 1, -1); + Vertices[7] = FVector(1, 1, 1); + + Triangles.Init(FAkTriangle(), 12); + Triangles[0] = { 0, 1, 3, AK_INVALID_SURFACE }; + Triangles[1] = { 0, 1, 5, AK_INVALID_SURFACE }; + Triangles[2] = { 0, 2, 3, AK_INVALID_SURFACE }; + Triangles[3] = { 0, 2, 6, AK_INVALID_SURFACE }; + Triangles[4] = { 0, 4, 5, AK_INVALID_SURFACE }; + Triangles[5] = { 0, 4, 6, AK_INVALID_SURFACE }; + Triangles[6] = { 1, 3, 7, AK_INVALID_SURFACE }; + Triangles[7] = { 1, 5, 7, AK_INVALID_SURFACE }; + Triangles[8] = { 2, 3, 7, AK_INVALID_SURFACE }; + Triangles[9] = { 2, 6, 7, AK_INVALID_SURFACE }; + Triangles[10] = { 4, 5, 7, AK_INVALID_SURFACE }; + Triangles[11] = { 4, 6, 7, AK_INVALID_SURFACE }; +} + +void GetBasicXYPlaneGeometryData(TArray& Vertices, TArray& Triangles) +{ + Vertices.Init(FVector(0, 0, 0), 4); + Vertices[0] = FVector(-1, -1, 1); + Vertices[1] = FVector(-1, 1, 1); + Vertices[2] = FVector(1, -1, 1); + Vertices[3] = FVector(1, 1, 1); + + Triangles.Init(FAkTriangle(), 2); + Triangles[0] = { 0, 1, 2, AK_INVALID_SURFACE }; + Triangles[1] = { 1, 3, 2, AK_INVALID_SURFACE }; +} + +void GetBasicXZPlaneGeometryData(TArray& Vertices, TArray& Triangles) +{ + Vertices.Init(FVector(0, 0, 0), 4); + Vertices[0] = FVector(-1, 1, -1); + Vertices[1] = FVector(-1, 1, 1); + Vertices[2] = FVector(1, 1, -1); + Vertices[3] = FVector(1, 1, 1); + + Triangles.Init(FAkTriangle(), 2); + Triangles[0] = { 0, 1, 2, AK_INVALID_SURFACE }; + Triangles[1] = { 1, 3, 2, AK_INVALID_SURFACE }; +} + +void GetBasicYZPlaneGeometryData(TArray& Vertices, TArray& Triangles) +{ + Vertices.Init(FVector(0, 0, 0), 4); + Vertices[0] = FVector(1, -1, -1); + Vertices[1] = FVector(1, -1, 1); + Vertices[2] = FVector(1, 1, -1); + Vertices[3] = FVector(1, 1, 1); + + Triangles.Init(FAkTriangle(), 2); + Triangles[0] = { 0, 1, 2, AK_INVALID_SURFACE }; + Triangles[1] = { 1, 3, 2, AK_INVALID_SURFACE }; +} + +/** Taken from GetOrientedHalfSphereMesh in PrimitiveDrawingUtils.cpp. See original for adding tangents and texture coords. */ +void GenerateHalfSphereVerts(AkSurfIdx surfIdx, const FVector& Center, const FRotator& Orientation, const float Radius, int32 NumSides, int32 NumRings, float StartAngle, float EndAngle, FAkGeometryData& GeometryData) +{ + if (NumSides <= 0 || NumRings <= 0) + return; + // The first/last arc are on top of each other. + int32 numVerts = (NumSides + 1) * (NumRings + 1); + TArray vertices; + vertices.AddDefaulted(numVerts); + TArray triangles; + + int32 BaseVertIndex = GeometryData.Vertices.Num(); + + // Calculate verts for one arc + TArray arcVertices; + for (int32 i = 0; i < NumRings + 1; i++) + { + float angle = StartAngle + ((float)i / NumRings) * (EndAngle - StartAngle); + arcVertices.Add(FVector(0.0f, FMath::Sin(angle) * Radius, FMath::Cos(angle) * Radius) + Center); + } + + // Then rotate this arc NumSides+1 times. + for (int32 s = 0; s < NumSides + 1; s++) + { + FRotator ArcRotator(0, 360.f * (float)s / NumSides, 0); + FRotationMatrix ArcRot(ArcRotator); + + for (int32 v = 0; v < NumRings + 1; v++) + { + int32 VIx = (NumRings + 1) * s + v; + vertices[VIx] = ArcRot.TransformPosition(arcVertices[v]); + } + } + + // Add all of the vertices we generated to the geometry data. + for (int32 vertIdx = 0; vertIdx < numVerts; vertIdx++) + { + GeometryData.Vertices.Add(vertices[vertIdx]); + } + + // If StartAngle is 0, the top-most ring is the top pole, and all sides will start on the same vertex. + const bool sidesStartAtTopPole = StartAngle < PI * 0.1f; + // Similarly, if EndAngle is PI, the bottom-most ring is the bottom pole, and all sides will end on the same vertex. + const bool sidesEndAtBottomPole = EndAngle >= PI * 0.9f; + // Add all of the triangles we generated to the geometry data. + for (uint16 s = 0; s < NumSides; s++) + { + // Add triangles between consecutive sides, from the top-most ring to the bottom. + uint16 side0Start = (s + 0) * (NumRings + 1) + BaseVertIndex; + uint16 side1Start = (s + 1) * (NumRings + 1) + BaseVertIndex; + + uint16 s0 = side0Start; + uint16 s1 = side1Start; + + // s0 and s1 refer to the vertices running up and down the 'sides' of the sphere. + // the line from vertex s0 to vertex s0 + 1 runs down one 'side' + // the line from vertex s0 to vertex s1 runs along one 'ring' (from one 'side' to the other). + // the line from vertex s0 to vertex s1 + 1 runs diagonally down a side and along a ring. + + // Add the initial triangle for this side strip (or triangles, if we're not starting on a pole) + if (sidesStartAtTopPole) + { + GeometryData.Triangles.Add({ s0, uint16(s1 + 1), uint16(s0 + 1), surfIdx }); + } + else + { + GeometryData.Triangles.Add({ s0, s1, uint16(s0 + 1), surfIdx }); + GeometryData.Triangles.Add({ s1, uint16(s1 + 1), uint16(s0 + 1), surfIdx }); + } + + for (uint16 r = 1; r < NumRings - 1; r++) + { + s0 = side0Start + r; + s1 = side1Start + r; + GeometryData.Triangles.Add({ s0, s1, uint16(s0 + 1), surfIdx }); + GeometryData.Triangles.Add({ s1, uint16(s1 + 1), uint16(s0 + 1), surfIdx }); + } + + // Add the final triangle for this side strip (or triangles if we're not ending on a pole) + s0 = side0Start + (NumRings - 1); + s1 = side1Start + (NumRings - 1); + GeometryData.Triangles.Add({ s0, s1, uint16(s0 + 1), surfIdx }); + if (!sidesEndAtBottomPole) + { + GeometryData.Triangles.Add({ s1, uint16(s1 + 1), uint16(s0 + 1), surfIdx }); + } + } +} + +/** Taken from BuildCylinderVerts in PrimitiveDrawingUtils.cpp. See original for adding tangents and texture coords. */ +void GenerateCylinderVerts(AkSurfIdx surfIdx, const FVector& Base, const FVector& XAxis, const FVector& YAxis, const FVector& ZAxis, float Radius, float HalfHeight, uint32 Sides, FAkGeometryData& GeometryData) +{ + const float AngleDelta = 2.0f * PI / Sides; + FVector LastVertex = Base + XAxis * Radius; + + FVector2D TC = FVector2D(0.0f, 0.0f); + float TCStep = 1.0f / Sides; + + FVector TopOffset = HalfHeight * ZAxis; + + int32 BaseVertIndex = GeometryData.Vertices.Num(); + + //Compute vertices for base circle. + for (uint32 SideIndex = 0; SideIndex < Sides; SideIndex++) + { + const FVector Vertex = Base + (XAxis * FMath::Cos(AngleDelta * (SideIndex + 1)) + YAxis * FMath::Sin(AngleDelta * (SideIndex + 1))) * Radius; + FVector Normal = Vertex - Base; + Normal.Normalize(); + + GeometryData.Vertices.Add(Vertex - TopOffset); + + LastVertex = Vertex; + TC.X += TCStep; + } + + LastVertex = Base + XAxis * Radius; + TC = FVector2D(0.0f, 1.0f); + + //Compute vertices for the top circle + for (uint16 SideIndex = 0; SideIndex < Sides; SideIndex++) + { + const FVector Vertex = Base + (XAxis * FMath::Cos(AngleDelta * (SideIndex + 1)) + YAxis * FMath::Sin(AngleDelta * (SideIndex + 1))) * Radius; + FVector Normal = Vertex - Base; + Normal.Normalize(); + + GeometryData.Vertices.Add(Vertex + TopOffset); + + LastVertex = Vertex; + TC.X += TCStep; + } + + //Add sides. + for (uint16 SideIndex = 0; SideIndex < Sides; SideIndex++) + { + uint16 V0 = BaseVertIndex + SideIndex; + uint16 V1 = BaseVertIndex + ((SideIndex + 1) % Sides); + uint16 V2 = V0 + Sides; + uint16 V3 = V1 + Sides; + + GeometryData.Triangles.Add({ V0, V2, V1, surfIdx }); + GeometryData.Triangles.Add({ V2, V3, V1, surfIdx }); + } +} + +void FAkGeometryData::AddBox(AkSurfIdx surfIdx, FVector center, FVector extent, FRotator rotation) +{ + TArray boxVertices; + TArray boxTriangles; + + if (extent.Z == 0.0f) + GetBasicXYPlaneGeometryData(boxVertices, boxTriangles); + else if (extent.Y == 0.0f) + GetBasicXZPlaneGeometryData(boxVertices, boxTriangles); + else if (extent.X == 0.0f) + GetBasicYZPlaneGeometryData(boxVertices, boxTriangles); + else + GetBasicBoxGeometryData(boxVertices, boxTriangles); + + AkVertIdx initialVertIdx = Vertices.Num(); + + // move vertices according to the center and extents + for (AkVertIdx idx = 0; idx < boxVertices.Num(); idx++) + { + FTransform transform(rotation, center, extent); + FVector v = transform.TransformPosition(boxVertices[idx]); + + Vertices.Add(v); + } + + for (AkTriIdx idx = 0; idx < boxTriangles.Num(); idx++) + { + boxTriangles[idx].Point0 += initialVertIdx; + boxTriangles[idx].Point1 += initialVertIdx; + boxTriangles[idx].Point2 += initialVertIdx; + boxTriangles[idx].Surface = surfIdx; + + Triangles.Add(boxTriangles[idx]); + } +} + +void FAkGeometryData::AddSphere(AkSurfIdx surfIdx, const FVector& Center, const float Radius, int32 NumSides, int32 NumRings) +{ + GenerateHalfSphereVerts(surfIdx, Center, FRotator::ZeroRotator, Radius, NumSides, NumRings, 0, PI, *this); +} + +void FAkGeometryData::AddCapsule(AkSurfIdx surfIdx, const FVector& Origin, const FVector& XAxis, const FVector& YAxis, const FVector& ZAxis, float Radius, float HalfHeight, int32 NumSides) +{ + const FVector BottomEnd = Origin - HalfHeight * ZAxis; + const FVector TopEnd = Origin + HalfHeight * ZAxis; + + GenerateHalfSphereVerts(surfIdx, TopEnd, FRotationMatrix::MakeFromXY(XAxis, YAxis).Rotator(), Radius, NumSides, NumSides, 0, PI / 2, *this); + GenerateCylinderVerts(surfIdx, Origin, XAxis, YAxis, ZAxis, Radius, HalfHeight, NumSides, *this); + GenerateHalfSphereVerts(surfIdx, BottomEnd, FRotationMatrix::MakeFromXY(XAxis, YAxis).Rotator(), Radius, NumSides, NumSides, PI / 2, PI, *this); +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkGroupValue.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkGroupValue.cpp new file mode 100644 index 0000000..a6e5b87 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkGroupValue.cpp @@ -0,0 +1,126 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkGroupValue.h" +#include "AkAudioDevice.h" + +#include "Wwise/WwiseResourceLoader.h" + +#include + +void UAkGroupValue::UnloadGroupValue(bool bAsync) +{ + if (LoadedGroupValue) + { + auto* ResourceLoader = FWwiseResourceLoader::Get(); + if (UNLIKELY(!ResourceLoader)) + { + return; + } + if (bAsync) + { + FWwiseLoadedGroupValuePromise Promise; + Promise.EmplaceValue(MoveTemp(LoadedGroupValue)); + ResourceUnload = ResourceLoader->UnloadGroupValueAsync(Promise.GetFuture()); + } + else + { + ResourceLoader->UnloadGroupValue(MoveTemp(LoadedGroupValue)); + } + LoadedGroupValue = nullptr; + } +} + +#if WITH_EDITORONLY_DATA +void UAkGroupValue::MigrateWwiseObjectInfo() +{ + FString GroupName; + FString ValueName; + SplitAssetName( GroupName, ValueName); + if (GroupShortID_DEPRECATED != AK_INVALID_UNIQUE_ID ) + { + GroupValueInfo.GroupShortId = GroupShortID_DEPRECATED; + } + else + { + GroupValueInfo.GroupShortId = FAkAudioDevice::GetShortIDFromString(GroupName); + } + + if (ShortID_DEPRECATED != AK_INVALID_UNIQUE_ID) + { + GroupValueInfo.WwiseShortId = ShortID_DEPRECATED; + } + else + { + GroupValueInfo.WwiseShortId = FAkAudioDevice::GetShortIDFromString(ValueName); + } + if (ID_DEPRECATED.IsValid()) + { + GroupValueInfo.WwiseGuid = ID_DEPRECATED; + } + GroupValueInfo.WwiseName = FName(ValueName); +} + +void UAkGroupValue::ValidateShortID(FWwiseObjectInfo& WwiseInfo) const +{ + if (WwiseInfo.WwiseShortId != AK_INVALID_UNIQUE_ID && WwiseInfo.WwiseShortId != FAkAudioDevice::GetShortIDFromString(WwiseInfo.WwiseName.ToString())) + { + UE_LOG(LogAkAudio, Log, TEXT("UAkGroupValue::ValidateShortID: WwiseShortId does not match ShortID derived from WwiseName. Asset: %s - WwiseName %s - WwiseShortID: %" PRIu32 " instead of %" PRIu32 "."), *GetName(), *WwiseInfo.WwiseName.ToString(), WwiseInfo.WwiseShortId, FAkAudioDevice::GetShortIDFromString(WwiseInfo.WwiseName.ToString())); + } + + if (WwiseInfo.WwiseShortId == AK_INVALID_UNIQUE_ID) + { + if (!WwiseInfo.WwiseName.ToString().IsEmpty()) + { + WwiseInfo.WwiseShortId = FAkAudioDevice::GetShortIDFromString(WwiseInfo.WwiseName.ToString()); + } + else + { + FString GroupName; + FString ValueName; + SplitAssetName( GroupName, ValueName); + UE_LOG(LogAkAudio, Warning, TEXT("UAkGroupValue::ValidateShortID: Using ShortID based on asset name '%s'. Assumed value name is '%s'."), *GetName(), *ValueName); + WwiseInfo.WwiseShortId = FAkAudioDevice::GetShortIDFromString(ValueName); + } + } + + if ( auto WwiseGroupInfo = static_cast(&WwiseInfo)) + { + if (WwiseGroupInfo->GroupShortId == AK_INVALID_UNIQUE_ID) + { + FString GroupName; + FString ValueName; + SplitAssetName(GroupName, ValueName); + UE_LOG(LogAkAudio, Warning, TEXT("UAkGroupValue::ValidateShortID: Using ShortID for group based on asset name '%s'. Assumed group name is '%s'."), *GetName(), *GroupName); + WwiseGroupInfo->GroupShortId = FAkAudioDevice::GetShortIDFromString(GroupName); + } + } +} + +bool UAkGroupValue::SplitAssetName(FString& OutGroupName, FString& OutValueName) const +{ + auto AssetName = GetName(); + if (AssetName.Contains(TEXT("-"))) + { + AssetName.Split(TEXT("-"), &OutGroupName, &OutValueName); + return true; + } + UE_LOG(LogAkAudio, Warning, TEXT("UAkAudioType::GetAssetSplitNames: Couldn't parse group and value names from asset named '%s'."), *GetName()); + return false; +} + +#endif \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkInitBank.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkInitBank.cpp new file mode 100644 index 0000000..ddec25b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkInitBank.cpp @@ -0,0 +1,162 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkInitBank.h" + +#include "Platforms/AkPlatformInfo.h" +#include "Wwise/WwiseResourceLoader.h" +#include "Wwise/Stats/AkAudio.h" + +#if WITH_EDITORONLY_DATA +#include "Wwise/WwiseResourceCooker.h" +#endif + +#if WITH_EDITORONLY_DATA +void UAkInitBank::CookAdditionalFilesOverride(const TCHAR* PackageFilename, const ITargetPlatform* TargetPlatform, + TFunctionRef WriteAdditionalFile) +{ + auto* ResourceCooker = FWwiseResourceCooker::GetForPlatform(TargetPlatform); + if (!ResourceCooker) + { + return; + } + ResourceCooker->SetSandboxRootPath(PackageFilename); + ResourceCooker->CookInitBank(FWwiseObjectInfo::DefaultInitBank, WriteAdditionalFile); +} + +void UAkInitBank::BeginCacheForCookedPlatformData(const ITargetPlatform* TargetPlatform) +{ + auto PlatformID = UAkPlatformInfo::GetSharedPlatformInfo(TargetPlatform->IniPlatformName()); + FWwiseResourceCooker::CreateForPlatform(TargetPlatform, PlatformID, EWwiseExportDebugNameRule::Name); +} +#endif + +void UAkInitBank::Serialize(FArchive& Ar) +{ + bAutoLoad = false; + Super::Serialize(Ar); + + if (HasAnyFlags(RF_ClassDefaultObject)) + { + return; + } + + #if !UE_SERVER + #if WITH_EDITORONLY_DATA + if (Ar.IsCooking() && Ar.IsSaving() && !Ar.CookingTarget()->IsServerOnly()) + { + FWwiseInitBankCookedData CookedDataToArchive; + if (auto* ResourceCooker = FWwiseResourceCooker::GetForArchive(Ar)) + { + ResourceCooker->PrepareCookedData(CookedDataToArchive, FWwiseObjectInfo::DefaultInitBank); + } + CookedDataToArchive.Serialize(Ar); + } + #else + InitBankCookedData.Serialize(Ar); + #endif + #endif +} + +void UAkInitBank::UnloadInitBank(bool bAsync) +{ + if (LoadedInitBank) + { + auto* ResourceLoader = FWwiseResourceLoader::Get(); + if (UNLIKELY(!ResourceLoader)) + { + return; + } + + if (bAsync) + { + FWwiseLoadedInitBankPromise Promise; + Promise.EmplaceValue(MoveTemp(LoadedInitBank)); + ResourceUnload = ResourceLoader->UnloadInitBankAsync(Promise.GetFuture()); + } + else + { + ResourceLoader->UnloadInitBank(MoveTemp(LoadedInitBank)); + } + LoadedInitBank = nullptr; + } +} + +#if WITH_EDITORONLY_DATA +void UAkInitBank::PrepareCookedData() +{ + if (IWwiseProjectDatabaseModule::IsInACookingCommandlet()) + { + return; + } + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + return; + } + if (UNLIKELY(!ResourceCooker->PrepareCookedData(InitBankCookedData, FWwiseObjectInfo::DefaultInitBank))) + { + return; + } +} +#endif + +TArray UAkInitBank::GetLanguages() +{ +#if WITH_EDITORONLY_DATA + PrepareCookedData(); +#endif + + return InitBankCookedData.Language; +} + + +void UAkInitBank::UnloadData(bool bAsync) +{ + UnloadInitBank(bAsync); +} + +void UAkInitBank::LoadInitBank() +{ + SCOPED_AKAUDIO_EVENT_2(TEXT("LoadInitBank")); + auto* ResourceLoader = FWwiseResourceLoader::Get(); + if (UNLIKELY(!ResourceLoader)) + { + return; + } + if (LoadedInitBank) + { + UnloadInitBank(false); + } +#if WITH_EDITORONLY_DATA + PrepareCookedData(); +#endif + LoadedInitBank = ResourceLoader->LoadInitBank(InitBankCookedData); +} + + +#if WITH_EDITORONLY_DATA +void UAkInitBank::MigrateWwiseObjectInfo() +{ + //Do nothing because the DefaultInitBank info is used +} + +FWwiseObjectInfo* UAkInitBank::GetInfoMutable() +{ + return new FWwiseObjectInfo(FWwiseObjectInfo::DefaultInitBank.WwiseGuid, FWwiseObjectInfo::DefaultInitBank.WwiseShortId, FWwiseObjectInfo::DefaultInitBank.WwiseName); +} +#endif \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkJobWorkerScheduler.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkJobWorkerScheduler.cpp new file mode 100644 index 0000000..b7d2075 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkJobWorkerScheduler.cpp @@ -0,0 +1,94 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkJobWorkerScheduler.h" +#include "AkAudioDevice.h" +#include "Wwise/API/WwiseMemoryMgrAPI.h" + +#define AK_DECLARE_JOB_TYPE(__job__, __desc__, __thread__) \ + DECLARE_CYCLE_STAT(TEXT(__desc__), STAT_AkJob##__job__, STATGROUP_Audio); \ + static ENamedThreads::Type kThread##__job__ = __thread__; + +#define AK_DEFINE_JOB_CASE(__job__) \ + case AkJobType_##__job__: \ + jobStatId = GET_STATID(STAT_AkJob##__job__); \ + jobThreadType = kThread##__job__ + +static_assert(AK_NUM_JOB_TYPES == 3, "Update the stat groups and switch cases below for new job types!"); +AK_DECLARE_JOB_TYPE(Generic, "Wwise Generic Job", ENamedThreads::AnyHiPriThreadNormalTask); +AK_DECLARE_JOB_TYPE(AudioProcessing, "Wwise Audio Processing Job", ENamedThreads::AnyHiPriThreadNormalTask); +AK_DECLARE_JOB_TYPE(SpatialAudio, "Wwise Spatial Audio Job", ENamedThreads::AnyHiPriThreadNormalTask); + +static void OnJobWorkerRequest(AkJobWorkerFunc in_fnJobWorker, AkJobType in_jobType, AkUInt32 in_uNumWorkers, void* in_pUserData) +{ + FAkJobWorkerScheduler* pScheduler = static_cast(in_pUserData); + AkUInt32 uMaxExecutionTime = pScheduler->uMaxExecutionTime; + TStatId jobStatId; + ENamedThreads::Type jobThreadType; + switch (in_jobType) + { + AK_DEFINE_JOB_CASE(AudioProcessing); break; + AK_DEFINE_JOB_CASE(SpatialAudio); break; + default: + check(!"Unknown job type."); + // Fall-through + AK_DEFINE_JOB_CASE(Generic); + } + for (int i=0; i < (int)in_uNumWorkers; i++) + { + FFunctionGraphTask::CreateAndDispatchWhenReady([=]() { + in_fnJobWorker(in_jobType, uMaxExecutionTime); + // After completion of the worker function, release any thread-local memory resources + if (auto* MemoryManager = IWwiseMemoryMgrAPI::Get()) + { + MemoryManager->TrimForThread(); + } + }, jobStatId, nullptr, jobThreadType); + } +} + +void FAkJobWorkerScheduler::InstallJobWorkerScheduler(uint32 in_uMaxExecutionTime, uint32 in_uMaxWorkerCount, AkJobMgrSettings & out_settings) +{ + if (!FTaskGraphInterface::Get().IsRunning()) + { + UE_LOG(LogAkAudio, Warning, TEXT("Multi-core rendering was requested, but Task Graph is not running. Multi-core rendering disabled.")); + } + else if (!FPlatformProcess::SupportsMultithreading()) + { + UE_LOG(LogAkAudio, Warning, TEXT("Multi-core rendering was requested, platform does not support multi-threading. Multi-core rendering disabled.")); + } + else + { + check(ENamedThreads::bHasHighPriorityThreads); + uMaxExecutionTime = in_uMaxExecutionTime; + AkUInt32 uNumWorkerThreads = FTaskGraphInterface::Get().GetNumWorkerThreads(); + AkUInt32 uMaxActiveWorkers = FMath::Min(uNumWorkerThreads, in_uMaxWorkerCount); + if (uMaxActiveWorkers > 0) + { + out_settings.fnRequestJobWorker = OnJobWorkerRequest; + out_settings.pClientData = this; + for (int i = 0; i < AK_NUM_JOB_TYPES; i++) + { + out_settings.uMaxActiveWorkers[i] = uMaxActiveWorkers; + } + } + else + { + UE_LOG(LogAkAudio, Warning, TEXT("Multi-core rendering was requested, but Max Num Job Workers is set to 0. Multi-core rendering disabled.")); + } + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkLateReverbComponent.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkLateReverbComponent.cpp new file mode 100644 index 0000000..f20fd0f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkLateReverbComponent.cpp @@ -0,0 +1,903 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkLateReverbComponent.cpp: +=============================================================================*/ + +#include "AkLateReverbComponent.h" +#include "AkCustomVersion.h" +#include "AkSettings.h" +#include "AkComponentHelpers.h" +#include "AkAudioDevice.h" +#include "AkAuxBus.h" +#include "AkRoomComponent.h" +#include "AkSurfaceReflectorSetComponent.h" +#include "AkGeometryComponent.h" +#include "AkAcousticTextureSetComponent.h" +#include "Components/BrushComponent.h" +#include "Model.h" +#include "GameFramework/Volume.h" + +#include "Engine/Canvas.h" +#include "CanvasItem.h" + +#if WITH_EDITOR +#include "Editor.h" +#include "LevelEditorViewport.h" +#include "AkAudioStyle.h" +#include "AkSpatialAudioHelper.h" +#endif + +/*------------------------------------------------------------------------------------ + UAkLateReverbComponent +------------------------------------------------------------------------------------*/ +#if WITH_EDITOR +float UAkLateReverbComponent::TextVisualizerHeightOffset = 80.0f; +#endif + +UAkLateReverbComponent::UAkLateReverbComponent(const class FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer) +{ + Parent = nullptr; + bUseAttachParentBound = true; + + // Property initialization + SendLevel = 1.0f; + FadeRate = 0.5f; + Priority = 1.0f; + + bEnable = true; + bWantsOnUpdateTransform = true; + +#if WITH_EDITOR + if (AkSpatialAudioHelper::GetObjectReplacedEvent()) + { + AkSpatialAudioHelper::GetObjectReplacedEvent()->AddUObject(this, &UAkLateReverbComponent::HandleObjectsReplaced); + } +#endif +} + +void UAkLateReverbComponent::PostLoad() +{ + Super::PostLoad(); + const int32 AkVersion = GetLinkerCustomVersion(FAkCustomVersion::GUID); + + if (AkVersion < FAkCustomVersion::ReverbAuxBusAutoAssignment) + { + AutoAssignAuxBus = false; + AuxBusManual = AuxBus; + } + +#if WITH_EDITOR + RegisterReverbInfoEnabledCallback(); +#endif +} + +void UAkLateReverbComponent::Serialize(FArchive& Ar) +{ + Ar.UsingCustomVersion(FAkCustomVersion::GUID); + Super::Serialize(Ar); +} + +bool UAkLateReverbComponent::HasEffectOnLocation(const FVector& Location) const +{ + // Need to add a small radius, because on the Mac, EncompassesPoint returns false if + // Location is exactly equal to the Volume's location + static float RADIUS = 0.01f; + return LateReverbIsActive() && EncompassesPoint(Location, RADIUS); +} + +uint32 UAkLateReverbComponent::GetAuxBusId() const +{ + return FAkAudioDevice::GetShortID(AuxBus, AuxBusName); +} + +void UAkLateReverbComponent::InitializeParent() +{ + USceneComponent* SceneParent = GetAttachParent(); + if (SceneParent != nullptr) + { + ReverbDescriptor.SetPrimitive(Cast(SceneParent)); + Parent = Cast(SceneParent); + if (Parent) + { + ReverbDescriptor.SetReverbComponent(this); + UBodySetup* bodySetup = Parent->GetBodySetup(); + if (bodySetup == nullptr || !AkComponentHelpers::HasSimpleCollisionGeometry(bodySetup)) + { + if (UBrushComponent* brush = Cast(Parent)) + brush->BuildSimpleBrushCollision(); + else + AkComponentHelpers::LogSimpleGeometryWarning(Parent, this); + } + } + else + { + bEnable = false; + AkComponentHelpers::LogAttachmentError(this, SceneParent, "UPrimitiveComponent"); + return; + } + } + else // will happen when this component gets detached from its parent + { + Parent = nullptr; + ReverbDescriptor.SetPrimitive(nullptr); + bEnable = false; + } +} + +void UAkLateReverbComponent::BeginPlay() +{ + Super::BeginPlay(); + + DecayEstimationNeedsUpdate = true; + PredelayEstimationNeedsUpdate = true; + + UAkRoomComponent* pRoomCmpt = nullptr; + if (Parent) + { + pRoomCmpt = AkComponentHelpers::GetChildComponentOfType(*Parent); + } + + if (!pRoomCmpt || !pRoomCmpt->RoomIsActive()) + { + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (AkAudioDevice && LateReverbIsActive()) + { + AkAudioDevice->IndexLateReverb(this); + } + } +} + +void UAkLateReverbComponent::BeginDestroy() +{ + Super::BeginDestroy(); + if (TextureSetComponent != nullptr) + { + TextureSetComponent->SetReverbDescriptor(nullptr); + } + ReverbDescriptor.SetPrimitive(nullptr); +#if WITH_EDITOR + if (AkSpatialAudioHelper::GetObjectReplacedEvent()) + { + AkSpatialAudioHelper::GetObjectReplacedEvent()->RemoveAll(this); + } +#endif +} + +void UAkLateReverbComponent::OnRegister() +{ + Super::OnRegister(); + SetRelativeTransform(FTransform::Identity); + InitializeParent(); + ParentChanged(); + + // During runtime (non editor), we only want to tick if we'll ever need to update the reverb parameters. + PrimaryComponentTick.bCanEverTick = ReverbDescriptor.RequiresUpdates(); + PrimaryComponentTick.bStartWithTickEnabled = ReverbDescriptor.RequiresUpdates(); +#if WITH_EDITOR + // In editor builds we always want to tick in case the global RTPCs become active, or aux bus assignment is enabled. + bTickInEditor = true; + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.bStartWithTickEnabled = true; +#endif +} + +void UAkLateReverbComponent::ParentChanged() +{ + if (IsValid(Parent)) + { +#if WITH_EDITOR + RegisterAuxBusMapChangedCallback(); + RegisterReverbRTPCChangedCallback(); +#endif + // In the case where a blueprint class has a texture set component and a late reverb component as siblings, We can't know which will be registered first. + // We need to check for the sibling in each OnRegister function and associate the texture set component to the late reverb when they are both registered. + if (UAkSurfaceReflectorSetComponent* surfaceComponent = AkComponentHelpers::GetChildComponentOfType(*Parent)) + { + AssociateAkTextureSetComponent(surfaceComponent); + } + else if (UAkGeometryComponent* geometryComponent = AkComponentHelpers::GetChildComponentOfType(*Parent)) + { + AssociateAkTextureSetComponent(geometryComponent); + } + } + + DecayEstimationNeedsUpdate = true; + PredelayEstimationNeedsUpdate = true; +} + +void UAkLateReverbComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (AkAudioDevice && IsIndexed) + { + AkAudioDevice->UnindexLateReverb(this); + } +} + +void UAkLateReverbComponent::OnUnregister() +{ + Super::OnUnregister(); + +#if WITH_EDITOR + auto* World = GetWorld(); + if(World && World->IsPlayInEditor()) + { + DestroyTextVisualizers(); + } + + UAkSettings* AkSettings = GetMutableDefault(); + if (AkSettings != nullptr) + { + if (AuxBusChangedHandle.IsValid()) + { + AkSettings->OnAuxBusAssignmentMapChanged.Remove(AuxBusChangedHandle); + } + if (RTPCChangedHandle.IsValid()) + { + AkSettings->OnReverbRTPCChanged.Remove(RTPCChangedHandle); + } + } +#endif +} + +bool UAkLateReverbComponent::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 UAkLateReverbComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) +{ + DecayEstimationNeedsUpdate = ReverbDescriptor.ShouldEstimateDecay(); + PredelayEstimationNeedsUpdate = ReverbDescriptor.ShouldEstimatePredelay(); + + if (IsIndexed) + { + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (AkAudioDevice) + AkAudioDevice->ReindexLateReverb(this); + } + +#if WITH_EDITOR + if (TextVisualizerLabels != nullptr) + { + TextVisualizerLabels->SetWorldScale3D(FVector::OneVector); + if (IsValid(Parent)) + { + TextVisualizerLabels->SetWorldLocation(GetTextVisualizersLocation()); + } + } + if (TextVisualizerValues != nullptr) + { + TextVisualizerValues->SetWorldScale3D(FVector::OneVector); + if (IsValid(Parent)) + { + TextVisualizerValues->SetWorldLocation(GetTextVisualizersLocation()); + } + } +#endif +} + +void UAkLateReverbComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + // Update decay and time to first reflection estimations when the size is changed, every PARAM_ESTIMATION_UPDATE_PERIOD seconds. + if (SecondsSinceDecayUpdate < PARAM_ESTIMATION_UPDATE_PERIOD) + { + SecondsSinceDecayUpdate += DeltaTime; + } + if (DecayEstimationNeedsUpdate && SecondsSinceDecayUpdate >= PARAM_ESTIMATION_UPDATE_PERIOD) + { + RecalculateDecay(); + DecayEstimationNeedsUpdate = false; + } + if (SecondsSincePredelayUpdate < PARAM_ESTIMATION_UPDATE_PERIOD) + { + SecondsSincePredelayUpdate += DeltaTime; + } + if (PredelayEstimationNeedsUpdate && SecondsSincePredelayUpdate >= PARAM_ESTIMATION_UPDATE_PERIOD) + { + RecalculatePredelay(); + PredelayEstimationNeedsUpdate = false; + } + +#if WITH_EDITOR + UWorld* World = GetWorld(); + if (World == nullptr) + return; + + if (GCurrentLevelEditingViewportClient != nullptr && World->WorldType == EWorldType::Editor) + { + // Keep the text renderers pointing to the camera. + if (IsValid(TextVisualizerLabels)) + { + FVector PointTo = GCurrentLevelEditingViewportClient->GetViewLocation() - TextVisualizerLabels->GetComponentLocation(); + TextVisualizerLabels->SetWorldRotation(PointTo.Rotation()); + } + if (IsValid(TextVisualizerValues)) + { + FVector PointTo = GCurrentLevelEditingViewportClient->GetViewLocation() - TextVisualizerValues->GetComponentLocation(); + TextVisualizerValues->SetWorldRotation(PointTo.Rotation()); + } + } + if (World->WorldType == EWorldType::Editor || World->WorldType == EWorldType::PIE) + { + // Only show the text renderer for selected actors. + if (GetOwner()->IsSelected() && !WasSelected) + { + WasSelected = true; + UpdateValuesLabels(); + } + if (!GetOwner()->IsSelected() && WasSelected) + { + WasSelected = false; + UpdateValuesLabels(); + } + } + + if (bTextStatusNeedsUpdate) + { + UpdateTextVisualizerStatus(); + bTextStatusNeedsUpdate = false; + } +#endif +} + +void UAkLateReverbComponent::RecalculateDecay() +{ + if (ReverbDescriptor.ShouldEstimateDecay()) + { + ReverbDescriptor.CalculateT60(); + SecondsSinceDecayUpdate = 0.0f; + } +} + +void UAkLateReverbComponent::RecalculatePredelay() +{ + if (ReverbDescriptor.ShouldEstimatePredelay()) + { + ReverbDescriptor.CalculateTimeToFirstReflection(); + SecondsSincePredelayUpdate = 0.0f; + } +} + +#if WITH_EDITOR +void UAkLateReverbComponent::RegisterReverbInfoEnabledCallback() +{ + UAkSettings* AkSettings = GetMutableDefault(); + if (AkSettings == nullptr || ShowReverbInfoChangedHandle.IsValid()) + return; + ShowReverbInfoChangedHandle = AkSettings->OnShowReverbInfoChanged.AddLambda([this, AkSettings]() + { + bTextStatusNeedsUpdate = true; + }); +} + +void UAkLateReverbComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + UAkSettings* AkSettings = GetMutableDefault(); + if (!AkSettings) + return; + AkSettings->OnShowRoomsPortalsChanged.Remove(ShowReverbInfoChangedHandle); + ShowReverbInfoChangedHandle.Reset(); +} + +void UAkLateReverbComponent::InitializeComponent() +{ + Super::InitializeComponent(); + RegisterReverbInfoEnabledCallback(); +} + +void UAkLateReverbComponent::OnComponentCreated() +{ + Super::OnComponentCreated(); + RegisterReverbInfoEnabledCallback(); +} + +void UAkLateReverbComponent::HandleObjectsReplaced(const TMap& ReplacementMap) +{ + if (ReplacementMap.Contains(Parent)) + { + InitializeParent(); + DecayEstimationNeedsUpdate = true; + PredelayEstimationNeedsUpdate = true; + } + if (ReplacementMap.Contains(TextureSetComponent)) + { + if (Parent != nullptr) + { + if (UAkSurfaceReflectorSetComponent* SurfaceComponent = AkComponentHelpers::GetChildComponentOfType(*Parent)) + { + AssociateAkTextureSetComponent(SurfaceComponent); + } + else if (UAkGeometryComponent* GeomComponent = AkComponentHelpers::GetChildComponentOfType(*Parent)) + { + AssociateAkTextureSetComponent(GeomComponent); + } + } + } +} + +void UAkLateReverbComponent::UpdateTextVisualizerStatus() +{ + bool bShowReverbInfo = GetDefault()->bShowReverbInfo; + // The reverb descriptor may or may not require updates depending on which global RTPCs are in use, and whether auto assign aux bus is selected. + // We only want to show the text renderers when the reverb parameter estimation is in use. + if ((!bShowReverbInfo || !ReverbDescriptor.RequiresUpdates()) && TextVisualizersInitialized()) + { + DestroyTextVisualizers(); + } + else if ((bShowReverbInfo && ReverbDescriptor.RequiresUpdates()) && !TextVisualizersInitialized()) + { + InitTextVisualizers(); + DecayEstimationNeedsUpdate = ReverbDescriptor.ShouldEstimateDecay(); + PredelayEstimationNeedsUpdate = ReverbDescriptor.ShouldEstimatePredelay(); + } +} + +bool UAkLateReverbComponent::TextVisualizersInitialized() const +{ + return IsValid(TextVisualizerLabels) && IsValid(TextVisualizerValues); +} + +FText UAkLateReverbComponent::GetValuesLabels() const +{ + // Get a nicely formatted string showing the values of all of the reverb properties in a left-aligned block. + // They will appear adjacent to a right-aligned block showing the property labels. + FString BusName = FString("NONE"); + if (AuxBus != nullptr) + AuxBus->GetName(BusName); + FNumberFormattingOptions NumberFormat; + NumberFormat.MaximumFractionalDigits = 2; + FString volumeString = FText::AsNumber(EnvironmentVolume, &NumberFormat).ToString(); + FString areaString = FText::AsNumber(SurfaceArea, &NumberFormat).ToString(); + + const FString missingRoomString = FString("Requires sibling AkRoom component"); + FString decayString = missingRoomString; + FString reflectionTimeString = missingRoomString; + FString dampingString = missingRoomString; + + if (AutoAssignAuxBus) + { + decayString = FText::AsNumber(EnvironmentDecayEstimate, &NumberFormat).ToString() + " seconds"; + } + + if (Parent != nullptr && AkComponentHelpers::GetChildComponentOfType(*Parent)) + { + decayString = ReverbDescriptor.ShouldEstimateDecay() ? FText::AsNumber(EnvironmentDecayEstimate, &NumberFormat).ToString() + " seconds" : FString("Invalid Late Reverb or Room Primitive Component"); + + reflectionTimeString = FString("Invalid Late Reverb or Room Primitive Component"); + if (ReverbDescriptor.ShouldEstimatePredelay()) + { + reflectionTimeString = FText::AsNumber(TimeToFirstReflection, &NumberFormat).ToString() + " ms"; + if (TimeToFirstReflection > 100.0f) + { + reflectionTimeString = FText::AsNumber(TimeToFirstReflection / 1000.0f, &NumberFormat).ToString() + " s"; + } + } + + dampingString = "No associated geometry component."; + if (TextureSetComponent != nullptr) + { + dampingString = ReverbDescriptor.ShouldEstimateDamping() ? FText::AsNumber(HFDamping, &NumberFormat).ToString() : FString("Invalid Late Reverb or Room Primitive Component"); + } + } + + const UAkSettings* AkSettings = GetDefault(); + if (AkSettings != nullptr) + { + if (!AkSettings->DecayRTPCInUse()) + { + decayString += FString(" (RTPC not in use)"); + } + + if (!AkSettings->PredelayRTPCInUse()) + { + reflectionTimeString += FString(" (RTPC not in use)"); + } + + if (!AkSettings->DampingRTPCInUse()) + { + dampingString += FString(" (RTPC not in use)"); + } + } + + return FText::FromString(FString::Format(TEXT("{1} cubic meters{0}{2} square meters{0}{3}{0}{4}{0}{5}{0}{6}"), + { LINE_TERMINATOR, volumeString, areaString, decayString, BusName, reflectionTimeString, dampingString })); +} + +void UAkLateReverbComponent::InitTextVisualizers() +{ + if (!HasAnyFlags(RF_Transient) && bEnable) + { + if (ReverbDescriptor.RequiresUpdates()) + { + FString OwnerName; +#if WITH_EDITOR + OwnerName = GetOwner()->GetActorLabel(); +#else + OwnerName = GetOwner()->GetName(); +#endif + FString TextVizName = OwnerName + GetName(); + UMaterialInterface* mat = Cast(FAkAudioStyle::GetAkForegroundTextMaterial()); + if (!IsValid(TextVisualizerLabels)) + { + TextVisualizerLabels = NewObject(GetOuter(), *(TextVizName + TEXT("TextLabels"))); + if (IsValid(TextVisualizerLabels)) + { + if (mat != nullptr) + TextVisualizerLabels->SetTextMaterial(mat); + TextVisualizerLabels->SetHorizontalAlignment(EHorizTextAligment::EHTA_Right); + TextVisualizerLabels->RegisterComponentWithWorld(GetWorld()); + TextVisualizerLabels->AttachToComponent(this, FAttachmentTransformRules::KeepWorldTransform); + TextVisualizerLabels->ResetRelativeTransform(); + TextVisualizerLabels->SetWorldScale3D(FVector::OneVector); + if (IsValid(Parent)) + { + TextVisualizerLabels->SetWorldLocation(GetTextVisualizersLocation()); + UWorld* World = TextVisualizerLabels->GetWorld(); + if (World != nullptr && World->WorldType == EWorldType::EditorPreview) + { + TextVisualizerLabels->SetWorldRotation(FVector(100, 0, 0).Rotation()); + } + } + TextVisualizerLabels->bIsEditorOnly = true; + // Creates a right-aligned block of text showing the property labels. + TextVisualizerLabels->SetText(FText::FromString(FString::Format(TEXT("Volume {0}Area {0}Decay {0}AuxBus {0}Time to first reflection {0}HFDamping "), { LINE_TERMINATOR }))); + TextVisualizerLabels->bSelectable = false; + } + } + if (!IsValid(TextVisualizerValues)) + { + TextVisualizerValues = NewObject(GetOuter(), *(TextVizName + TEXT("TextValues"))); + if (IsValid(TextVisualizerValues)) + { + if (mat != nullptr) + TextVisualizerValues->SetTextMaterial(mat); + TextVisualizerValues->SetHorizontalAlignment(EHorizTextAligment::EHTA_Left); + TextVisualizerValues->RegisterComponentWithWorld(GetWorld()); + TextVisualizerValues->AttachToComponent(this, FAttachmentTransformRules::KeepWorldTransform); + TextVisualizerValues->ResetRelativeTransform(); + TextVisualizerValues->SetWorldScale3D(FVector::OneVector); + if (IsValid(Parent)) + { + TextVisualizerValues->SetWorldLocation(GetTextVisualizersLocation()); + UWorld* World = TextVisualizerValues->GetWorld(); + if (World != nullptr && World->WorldType == EWorldType::EditorPreview) + { + TextVisualizerValues->SetWorldRotation(FVector(100, 0, 0).Rotation()); + } + } + TextVisualizerValues->bIsEditorOnly = true; + TextVisualizerValues->bSelectable = false; + UpdateValuesLabels(); + } + } + } + } +} + +void UAkLateReverbComponent::DestroyTextVisualizers() +{ + if (IsValid(TextVisualizerLabels)) + { + TextVisualizerLabels->DestroyComponent(); + TextVisualizerLabels = nullptr; + } + if (IsValid(TextVisualizerValues)) + { + TextVisualizerValues->DestroyComponent(); + TextVisualizerValues = nullptr; + } +} + +void UAkLateReverbComponent::UpdateValuesLabels() +{ + if (!GetDefault()->bShowReverbInfo) + return; + if (!TextVisualizersInitialized()) + InitTextVisualizers(); + if (IsValid(TextVisualizerValues)) + { + TextVisualizerValues->SetText(GetValuesLabels()); + 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) + { + TextVisualizerValues->SetVisibility(visible); + if (IsValid(TextVisualizerLabels)) + TextVisualizerLabels->SetVisibility(visible); + } + } +} +#endif // WITH_EDITOR + +void UAkLateReverbComponent::UpdateRTPCs(const UAkRoomComponent* room) const +{ + // The global RTPCs are set on the room ids. + ReverbDescriptor.UpdateAllRTPCs(room); +} + +void UAkLateReverbComponent::AssociateAkTextureSetComponent(UAkAcousticTextureSetComponent* textureSetComponent) +{ + if (TextureSetComponent != nullptr) + TextureSetComponent->SetReverbDescriptor(nullptr); + TextureSetComponent = textureSetComponent; + TextureSetComponent->SetReverbDescriptor(&ReverbDescriptor); +} + +void UAkLateReverbComponent::UpdateDecayEstimation(float decay, float volume, float surfaceArea) +{ + if (AutoAssignAuxBus) + { + UAkSettings* AkSettings = GetMutableDefault(); + if (AkSettings != nullptr) + { + AkSettings->GetAuxBusForDecayValue(decay, AuxBus); + } + } + +#if WITH_EDITOR + EnvironmentVolume = volume; + SurfaceArea = surfaceArea; + EnvironmentDecayEstimate = decay; + + UpdateValuesLabels(); +#endif // WITH_EDITOR +} + +#if WITH_EDITOR +void UAkLateReverbComponent::UpdateHFDampingEstimation(float hfDamping) +{ + HFDamping = hfDamping; + UpdateValuesLabels(); +} + +void UAkLateReverbComponent::UpdatePredelayEstimation(float predelay) +{ + TimeToFirstReflection = predelay; + UpdateValuesLabels(); +} + +void UAkLateReverbComponent::PreEditChange(FProperty* PropertyAboutToChange) +{ + if (PropertyAboutToChange != nullptr) + { + if (!AutoAssignAuxBus + && (PropertyAboutToChange->NamePrivate == GET_MEMBER_NAME_CHECKED(UAkLateReverbComponent, AutoAssignAuxBus) + || PropertyAboutToChange->NamePrivate == GET_MEMBER_NAME_CHECKED(UAkLateReverbComponent, AuxBus))) + { + AuxBusManual = AuxBus; + } + if (AutoAssignAuxBus + && PropertyAboutToChange->NamePrivate == GET_MEMBER_NAME_CHECKED(UAkLateReverbComponent, AutoAssignAuxBus)) + { + AuxBus = AuxBusManual; + } + } +} + +void UAkLateReverbComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + const FName MemberPropertyName = (PropertyChangedEvent.MemberProperty != nullptr) ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None; + if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkLateReverbComponent, AutoAssignAuxBus)) + { + PrimaryComponentTick.bCanEverTick = AutoAssignAuxBus; + PrimaryComponentTick.bStartWithTickEnabled = AutoAssignAuxBus; + bTickInEditor = AutoAssignAuxBus; + DecayEstimationNeedsUpdate = true; + bTextStatusNeedsUpdate = true; + + if (!AutoAssignAuxBus) + { + AuxBus = AuxBusManual; + } + } + if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkLateReverbComponent, AuxBus)) + { + if (!AutoAssignAuxBus) + { + AuxBusManual = AuxBus; + } + if (AkComponentHelpers::IsInGameWorld(this)) + { + UAkRoomComponent* RoomCmpt = nullptr; + if (Parent) + { + RoomCmpt = AkComponentHelpers::GetChildComponentOfType(*Parent); + } + if (!RoomCmpt || !RoomCmpt->RoomIsActive()) + { + // No room, or inactive room. Update the late reverb in the oct tree. + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (AkAudioDevice && bEnable && IsIndexed) + { + AkAudioDevice->ReindexLateReverb(this); + } + } + else if (RoomCmpt && RoomCmpt->RoomIsActive()) + { + // Late reverb is inside an active room. Update the room such that the reverb aux bus is correctly updated. + RoomCmpt->UpdateSpatialAudioRoom(); + } + } + } + if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkLateReverbComponent, bEnable)) + { + if (AkComponentHelpers::IsInGameWorld(this)) + { + UAkRoomComponent* RoomCmpt = nullptr; + if (Parent) + { + RoomCmpt = AkComponentHelpers::GetChildComponentOfType(*Parent); + } + + if (!RoomCmpt || !RoomCmpt->RoomIsActive()) + { + // No room, or inactive room. Update the late reverb in the oct tree. + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (AkAudioDevice) + { + if (!bEnable && IsIndexed) + { + AkAudioDevice->UnindexLateReverb(this); + } + else if (bEnable && !IsIndexed) + { + AkAudioDevice->IndexLateReverb(this); + } + } + } + else if (RoomCmpt && RoomCmpt->RoomIsActive()) + { + // Late reverb is inside an active room. Update the room such that the reverb aux bus is correctly updated. + RoomCmpt->UpdateSpatialAudioRoom(); + } + } + else if (CreationMethod == EComponentCreationMethod::Instance && bEnable + && GetDefault()->bShowReverbInfo) + { + InitTextVisualizers(); + } + } + if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkLateReverbComponent, SendLevel)) + { + UWorld* World = GetWorld(); + if (World != nullptr && (World->WorldType == EWorldType::Game || World->WorldType == EWorldType::PIE)) + { + UAkRoomComponent* RoomCmpt = nullptr; + if (Parent) + { + RoomCmpt = AkComponentHelpers::GetChildComponentOfType(*Parent); + } + if (!RoomCmpt || !RoomCmpt->RoomIsActive()) + { + // No room, or inactive room. Update the late reverb in the oct tree. + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (AkAudioDevice && bEnable && IsIndexed) + { + AkAudioDevice->ReindexLateReverb(this); + } + } + else if (RoomCmpt&& RoomCmpt->RoomIsActive()) + { + // Late reverb is inside an active room. Update the room such that the reverb send level is correctly updated. + RoomCmpt->UpdateSpatialAudioRoom(); + } + } + } +} + +void UAkLateReverbComponent::OnAttachmentChanged() +{ + Super::OnAttachmentChanged(); + // In other cases of CreationMethod, OnRegister() gets called to initialize the parent + if (CreationMethod == EComponentCreationMethod::Instance) + { + // OnAttachmentChanged can be called when this component is getting detached from its parent + // in this case InitializeParent will disable the component and set the parent and the ReverbDescriptor's primitive to null + InitializeParent(); + ParentChanged(); + } +} + +FVector UAkLateReverbComponent::GetTextVisualizersLocation() +{ + if (!IsValid(Parent)) + return FVector(); + + FBoxSphereBounds bounds = Parent->CalcBounds(Parent->GetComponentTransform()); + return Parent->GetComponentLocation() + FVector(0.0f, 0.0f, bounds.BoxExtent.Z + TextVisualizerHeightOffset); +} + +void UAkLateReverbComponent::RegisterAuxBusMapChangedCallback() +{ + UAkSettings* AkSettings = GetMutableDefault(); + if (AkSettings != nullptr) + { + if (AuxBusChangedHandle.IsValid()) + AkSettings->OnAuxBusAssignmentMapChanged.Remove(AuxBusChangedHandle); + AuxBusChangedHandle = AkSettings->OnAuxBusAssignmentMapChanged.AddLambda([this]() + { + DecayEstimationNeedsUpdate = true; + bTextStatusNeedsUpdate = true; + }); + } +} + +void UAkLateReverbComponent::RegisterReverbRTPCChangedCallback() +{ + UAkSettings* AkSettings = GetMutableDefault(); + if (AkSettings != nullptr) + { + if (RTPCChangedHandle.IsValid()) + AkSettings->OnReverbRTPCChanged.Remove(RTPCChangedHandle); + RTPCChangedHandle = AkSettings->OnReverbRTPCChanged.AddLambda([this]() + { + DecayEstimationNeedsUpdate = true; + PredelayEstimationNeedsUpdate = true; + bTextStatusNeedsUpdate = true; + }); + } +} +#endif + +bool UAkLateReverbComponent::EncompassesPoint(FVector Point, float SphereRadius/*=0.f*/, float* OutDistanceToPoint) const +{ + if (IsValid(Parent)) + { + return AkComponentHelpers::EncompassesPoint(*Parent, Point, SphereRadius, OutDistanceToPoint); + } + FString actorString = FString("NONE"); + if (GetOwner() != nullptr) + { +#if WITH_EDITOR + actorString = GetOwner()->GetActorLabel(); +#else + actorString = GetOwner()->GetName(); +#endif + } + UE_LOG(LogAkAudio, Error, TEXT("UAkLateReverbComponent::EncompassesPoint : Error. In actor %s, AkLateReverbComponent %s has an invalid Parent."), *actorString, *GetName()); + return false; +} + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkReverbDescriptor.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkReverbDescriptor.cpp new file mode 100644 index 0000000..e66c852 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkReverbDescriptor.cpp @@ -0,0 +1,440 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkReverbDescriptor.h" +#include "AkAudioDevice.h" +#include "AkAcousticTextureSetComponent.h" +#include "AkLateReverbComponent.h" +#include "AkRoomComponent.h" +#include "AkComponentHelpers.h" +#include "AkSettings.h" +#include "Wwise/API/WwiseSpatialAudioAPI.h" + +#include "Components/PrimitiveComponent.h" +#include "Rendering/PositionVertexBuffer.h" +#include "Engine/StaticMesh.h" +#include "Components/StaticMeshComponent.h" +#include "Components/ShapeComponent.h" +#include "Components/BrushComponent.h" + +#include "PhysicsEngine/BodySetup.h" +#include "PhysicsEngine/ConvexElem.h" + +#if AK_USE_PHYSX +#include "PhysXIncludes.h" +#endif + +/*============================================================================= + Volume & Area Helpers +=============================================================================*/ + +// FKBoxElem has its own GetVolume function but it is inaccurate. It uses scale.GetMin() rather than using element-wise multiplication. +float BoxVolume(const FKBoxElem& box, const FVector& scale) +{ + return (box.X * scale.X) * (box.Y * scale.Y) * (box.Z * scale.Z); +} +// This is the volume calculated by Unreal in UBodySetup::GetVolume, for box elements. We'll use this to subtract from the total volume, and add the more accurate volume calculated by BoxVolume, above. +float InaccurateBoxVolume(const FKBoxElem& box, const FVector& scale) +{ + float MinScale = scale.GetMin(); + return (box.X * MinScale) * (box.Y * MinScale) * (box.Z * MinScale); +} + +float BoxSurfaceArea(const FKBoxElem& box, const FVector& scale) +{ + return box.X * scale.X * box.Y * scale.Y * 2.0f /* top & bottom */ + + box.X * scale.X * box.Z * scale.Z * 2.0f /* left & right */ + + box.Y * scale.Y * box.Z * scale.Z * 2.0f; /* front & back */ +} + +float SphereSurfaceArea(const FKSphereElem& sphere, const FVector& scale) +{ + return 4.0f * PI * FMath::Pow(sphere.Radius * scale.GetMin(), 2.0f); +} + +float CapsuleSurfaceArea(const FKSphylElem& capsule, const FVector& scale) +{ + const float r = capsule.Radius * FMath::Min(scale.X, scale.Y); + return 2.0f * PI * r * (2.0f * r + capsule.Length * scale.Z); +} + +bool HasSimpleCollisionGeometry(UBodySetup* bodySetup) +{ + FKAggregateGeom geometry = bodySetup->AggGeom; + return geometry.BoxElems.Num() > 0 || geometry.ConvexElems.Num() > 0 || geometry.SphereElems.Num() > 0 || geometry.TaperedCapsuleElems.Num() > 0 || geometry.SphylElems.Num() > 0; + +} + +#if AK_USE_CHAOS +// Copied from BodySetup.cpp +// References: +// http://amp.ece.cmu.edu/Publication/Cha/icip01_Cha.pdf +// http://stackoverflow.com/questions/1406029/how-to-calculate-the-volume-of-a-3d-mesh-object-the-surface-of-which-is-made-up +float FAkReverbDescriptor::SignedVolumeOfTriangle(const FVector& p1, const FVector& p2, const FVector& p3) +{ + return FVector::DotProduct(p1, FVector::CrossProduct(p2, p3)) / 6.0f; +} +#endif + +void UpdateVolumeAndArea(UBodySetup* bodySetup, const FVector& scale, float& volume, float& surfaceArea) +{ + surfaceArea = 0.0f; + // Initially use the Unreal UBodySetup::GetVolume function to calculate volume... +#if UE_5_1_OR_LATER + volume = bodySetup->GetScaledVolume(scale); +#else + volume = bodySetup->GetVolume(scale); +#endif + FKAggregateGeom& geometry = bodySetup->AggGeom; + + for (const FKBoxElem& box : geometry.BoxElems) + { + surfaceArea += BoxSurfaceArea(box, scale); + // ... correct for any FKBoxElem elements in the geometry. + // UBodySetup::GetVolume has an inaccuracy for box elements. It is scaled uniformly by the minimum scale dimension (see FKBoxElem::GetVolume). + // For our purposes we want to scale by each dimension individually. + volume -= InaccurateBoxVolume(box, scale); + volume += BoxVolume(box, scale); + } + for (const FKConvexElem& convexElem : geometry.ConvexElems) + { + FTransform ScaleTransform = FTransform(FQuat::Identity, FVector::ZeroVector, scale); + + int32 numTriangles = convexElem.IndexData.Num() / 3; + for (int32 triIdx = 0; triIdx < numTriangles; ++triIdx) + { + FVector v0 = ScaleTransform.TransformPosition(convexElem.VertexData[convexElem.IndexData[3 * triIdx]]); + FVector v1 = ScaleTransform.TransformPosition(convexElem.VertexData[convexElem.IndexData[3 * triIdx + 1]]); + FVector v2 = ScaleTransform.TransformPosition(convexElem.VertexData[convexElem.IndexData[3 * triIdx + 2]]); + + surfaceArea += FAkReverbDescriptor::TriangleArea(v0, v1, v2); +#if AK_USE_CHAOS && !(UE_5_1_OR_LATER) + // FKConvexElem::GetVolume is not implemented with Chaos before UE 5.1 + volume += FAkReverbDescriptor::SignedVolumeOfTriangle(v0, v1, v2); +#endif + } + } + for (const FKSphereElem& sphere : geometry.SphereElems) + { + surfaceArea += SphereSurfaceArea(sphere, scale); + } + for (const FKSphylElem& capsule : geometry.SphylElems) + { + surfaceArea += CapsuleSurfaceArea(capsule, scale); + } +} + +/*============================================================================= + FAkReverbDescriptor: +=============================================================================*/ +double FAkReverbDescriptor::TriangleArea(const FVector& v1, const FVector& v2, const FVector& v3) +{ +#if UE_5_0_OR_LATER + double Mag = 0.0; +#else + float Mag = 0.0f; +#endif + FVector Dir; + FVector::CrossProduct(v2 - v1, v3 - v1).ToDirectionAndLength(Dir, Mag); + return 0.5 * Mag; +} + +bool FAkReverbDescriptor::ShouldEstimateDecay() const +{ + if (IsValid(ReverbComponent) && ReverbComponent->AutoAssignAuxBus) + return true; + if (!IsValid(Primitive) || AkComponentHelpers::GetChildComponentOfType(*Primitive) == nullptr) + return false; + + return true; +} + +bool FAkReverbDescriptor::ShouldEstimateDamping() const +{ + if (!IsValid(Primitive) || AkComponentHelpers::GetChildComponentOfType(*Primitive) == nullptr) + return false; + + return true; +} + +bool FAkReverbDescriptor::ShouldEstimatePredelay() const +{ + if (!IsValid(Primitive) || AkComponentHelpers::GetChildComponentOfType(*Primitive) == nullptr) + return false; + + return true; +} + +bool FAkReverbDescriptor::RequiresUpdates() const +{ + return ShouldEstimateDecay() || ShouldEstimateDamping() || ShouldEstimatePredelay(); +} + +void FAkReverbDescriptor::SetPrimitive(UPrimitiveComponent* primitive) +{ + Primitive = primitive; +} + +void FAkReverbDescriptor::SetReverbComponent(UAkLateReverbComponent* reverbComp) +{ + ReverbComponent = reverbComp; +} + +void FAkReverbDescriptor::CalculateT60() +{ + if (IsValid(Primitive)) + { + PrimitiveVolume = 0.0f; + PrimitiveSurfaceArea = 0.0f; + T60Decay = 0.0f; + if (Primitive != nullptr) + { + FVector scale = Primitive->GetComponentScale(); + UBodySetup* primitiveBody = Primitive->GetBodySetup(); + if (primitiveBody != nullptr && HasSimpleCollisionGeometry(primitiveBody)) + { + UpdateVolumeAndArea(primitiveBody, scale, PrimitiveVolume, PrimitiveSurfaceArea); + } + else + { + if (UBrushComponent* brush = Cast(Primitive)) + { + brush->BuildSimpleBrushCollision(); + } + else + { + FString PrimitiveName = ""; + Primitive->GetName(PrimitiveName); + FString ActorName = ""; + AActor* owner = Primitive->GetOwner(); + if (owner != nullptr) + owner->GetName(ActorName); + UE_LOG(LogAkAudio, Warning, + TEXT("Primitive component %s on actor %s has no simple collision geometry.%sCalculations for reverb aux bus assignment will use component bounds. This could be less accurate than using simple collision geometry."), + *PrimitiveName, *ActorName, LINE_TERMINATOR); + // only apply scale to local bounds to calculate volume and surface area. + FTransform transform = Primitive->GetComponentTransform(); + transform.SetRotation(FQuat::Identity); + transform.SetLocation(FVector::ZeroVector); + FBoxSphereBounds bounds = Primitive->CalcBounds(transform); + FVector boxDimensions = bounds.BoxExtent * 2.0f; + PrimitiveVolume = boxDimensions.X * boxDimensions.Y * boxDimensions.Z; + PrimitiveSurfaceArea += boxDimensions.X * boxDimensions.Y * 2.0f; + PrimitiveSurfaceArea += boxDimensions.X * boxDimensions.Z * 2.0f; + PrimitiveSurfaceArea += boxDimensions.Y * boxDimensions.Z * 2.0f; + } + } + + PrimitiveVolume = FMath::Abs(PrimitiveVolume) / AkComponentHelpers::UnrealUnitsPerCubicMeter(Primitive); + PrimitiveSurfaceArea /= AkComponentHelpers::UnrealUnitsPerSquaredMeter(Primitive); + + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (SpatialAudio && PrimitiveVolume > 0.0f && PrimitiveSurfaceArea > 0.0f) + { + float absorption = 0.5f; + UAkSettings* AkSettings = GetMutableDefault(); + if (AkSettings != nullptr) + absorption = AkSettings->GlobalDecayAbsorption; + //calcuate t60 using the Sabine equation + SpatialAudio->ReverbEstimation->EstimateT60Decay(PrimitiveVolume, PrimitiveSurfaceArea, absorption, T60Decay); + } + } + } +#if WITH_EDITOR + if (IsValid(ReverbComponent)) + ReverbComponent->UpdateDecayEstimation(T60Decay, PrimitiveVolume, PrimitiveSurfaceArea); +#endif + UpdateDecayRTPC(); +} + +void FAkReverbDescriptor::CalculateTimeToFirstReflection() +{ + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (SpatialAudio && IsValid(Primitive)) + { + FTransform transform = Primitive->GetComponentTransform(); + transform.SetRotation(FQuat::Identity); + transform.SetLocation(FVector::ZeroVector); + FBoxSphereBounds bounds = Primitive->CalcBounds(transform); + AkVector extentMeters = FAkAudioDevice::FVectorToAKVector(bounds.BoxExtent / AkComponentHelpers::UnrealUnitsPerMeter(Primitive)); + SpatialAudio->ReverbEstimation->EstimateTimeToFirstReflection(extentMeters, TimeToFirstReflection); + } +#if WITH_EDITOR + if (IsValid(ReverbComponent)) + ReverbComponent->UpdatePredelayEstimation(TimeToFirstReflection); +#endif + UpdatePredelaytRTPC(); +} + +void FAkReverbDescriptor::CalculateHFDamping(const UAkAcousticTextureSetComponent* acousticTextureSetComponent) +{ + HFDamping = 0.0f; + + if (IsValid(Primitive)) + { + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + const UAkSettings* AkSettings = GetDefault(); + if (SpatialAudio && AkSettings) + { + TArray texturesParams; + TArray surfaceAreas; + acousticTextureSetComponent->GetTexturesAndSurfaceAreas(texturesParams, surfaceAreas); + + if (texturesParams.Num() == 0) + { + HFDamping = 0.0f; + } + else + { + bool areAbsorptionValuesZero = true; + bool areSurfaceAreasZero = true; + int idx = 0; + + TArray textures; + for (const FAkAcousticTextureParams& params : texturesParams) + { + AkAcousticTexture texture; + texture.fAbsorptionLow = params.AbsorptionLow(); + texture.fAbsorptionMidLow = params.AbsorptionMidLow(); + texture.fAbsorptionMidHigh = params.AbsorptionMidHigh(); + texture.fAbsorptionHigh = params.AbsorptionHigh(); + textures.Add(texture); + + if (texture.fAbsorptionLow != 0 || + texture.fAbsorptionMidLow != 0 || + texture.fAbsorptionMidHigh != 0 || + texture.fAbsorptionHigh != 0) + { + areAbsorptionValuesZero = false; + } + if (surfaceAreas[idx] != 0) + { + areSurfaceAreasZero = false; + } + idx++; + } + + if (areAbsorptionValuesZero || areSurfaceAreasZero) + { + HFDamping = 0.0f; + } + else + { + SpatialAudio->ReverbEstimation->EstimateHFDamping(&textures[0], &surfaceAreas[0], textures.Num(), HFDamping); + } + } + } + } +#if WITH_EDITOR + if (IsValid(ReverbComponent)) + ReverbComponent->UpdateHFDampingEstimation(HFDamping); +#endif + UpdateDampingRTPC(); +} + +bool FAkReverbDescriptor::GetRTPCRoom(UAkRoomComponent*& room) const +{ + if (!IsValid(Primitive)) + return false; + + room = AkComponentHelpers::GetChildComponentOfType(*Primitive); + if (!CanSetRTPCOnRoom(room)) + { + room = nullptr; + } + + return room != nullptr; +} + +bool FAkReverbDescriptor::CanSetRTPCOnRoom(const UAkRoomComponent* room) const +{ + if (FAkAudioDevice::Get() == nullptr + || room == nullptr + || !room->HasBeenRegisteredWithWwise() + || room->GetWorld() == nullptr + || (room->GetWorld()->WorldType != EWorldType::Game && room->GetWorld()->WorldType != EWorldType::PIE)) + { + return false; + } + return true; +} + +void FAkReverbDescriptor::UpdateDecayRTPC() const +{ + UAkRoomComponent* room = nullptr; + if (GetRTPCRoom(room)) + { + const UAkSettings* AkSettings = GetDefault(); + if (AkSettings != nullptr && AkSettings->DecayRTPCInUse()) + { + room->SetRTPCValue(AkSettings->DecayEstimateRTPC.LoadSynchronous(), T60Decay, 0, AkSettings->DecayEstimateName); + } + } +} + +void FAkReverbDescriptor::UpdateDampingRTPC() const +{ + UAkRoomComponent* room = nullptr; + if (GetRTPCRoom(room)) + { + const UAkSettings* AkSettings = GetDefault(); + if (AkSettings != nullptr && AkSettings->DampingRTPCInUse()) + { + room->SetRTPCValue(AkSettings->HFDampingRTPC.LoadSynchronous(), HFDamping, 0, *AkSettings->HFDampingName); + } + } +} + +void FAkReverbDescriptor::UpdatePredelaytRTPC() const +{ + UAkRoomComponent* room = nullptr; + if (GetRTPCRoom(room)) + { + const UAkSettings* AkSettings = GetDefault(); + if (AkSettings != nullptr && AkSettings->PredelayRTPCInUse()) + { + room->SetRTPCValue(AkSettings->TimeToFirstReflectionRTPC.LoadSynchronous(), TimeToFirstReflection, 0, *AkSettings->TimeToFirstReflectionName); + } + } +} + +void FAkReverbDescriptor::UpdateAllRTPCs(const UAkRoomComponent* room) const +{ + AKASSERT(room != nullptr); + + if (CanSetRTPCOnRoom(room)) + { + const UAkSettings* AkSettings = GetDefault(); + if (AkSettings != nullptr && AkSettings->ReverbRTPCsInUse()) + { + if (AkSettings->DecayRTPCInUse()) + { + room->SetRTPCValue(AkSettings->DecayEstimateRTPC.LoadSynchronous(), T60Decay, 0, AkSettings->DecayEstimateName); + } + + if (AkSettings->DampingRTPCInUse()) + { + room->SetRTPCValue(AkSettings->HFDampingRTPC.LoadSynchronous(), HFDamping, 0, *AkSettings->HFDampingName); + } + + if (AkSettings->PredelayRTPCInUse()) + { + room->SetRTPCValue(AkSettings->TimeToFirstReflectionRTPC.LoadSynchronous(), TimeToFirstReflection, 0, *AkSettings->TimeToFirstReflectionName); + } + } + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkReverbVolume.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkReverbVolume.cpp new file mode 100644 index 0000000..2577d3e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkReverbVolume.cpp @@ -0,0 +1,80 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkReverbVolume.cpp: +=============================================================================*/ + +#include "AkReverbVolume.h" +#include "AkAudioDevice.h" +#include "AkLateReverbComponent.h" +#include "Components/BrushComponent.h" +#include "Model.h" +#include "AkCustomVersion.h" + +/*------------------------------------------------------------------------------------ + AAkReverbVolume +------------------------------------------------------------------------------------*/ + +AAkReverbVolume::AAkReverbVolume(const class FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer) +{ + // Property initialization + UBrushComponent* BrushComp = GetBrushComponent(); + if (BrushComp) + { + BrushComp->SetGenerateOverlapEvents(false); + BrushComp->SetCollisionEnabled(ECollisionEnabled::QueryOnly); + BrushComp->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic); + BrushComp->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); + } + + bColored = true; + BrushColor = FColor(0, 255, 255, 255); + + bEnabled_DEPRECATED = true; + SendLevel_DEPRECATED = 1.0f; + FadeRate_DEPRECATED = 0.5f; + Priority_DEPRECATED = 1.0f; + + static const FName LateReverbName = TEXT("LateReverb"); + LateReverbComponent = ObjectInitializer.CreateDefaultSubobject(this, LateReverbName); + LateReverbComponent->SetupAttachment(BrushComp); +} + +void AAkReverbVolume::Serialize(FArchive& Ar) +{ + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FAkCustomVersion::GUID); +} + +void AAkReverbVolume::PostLoad() +{ + Super::PostLoad(); + const int32 AkVersion = GetLinkerCustomVersion(FAkCustomVersion::GUID); + + if (LateReverbComponent && AkVersion < FAkCustomVersion::AddedSpatialAudio) + { + LateReverbComponent->bEnable = bEnabled_DEPRECATED; + LateReverbComponent->AuxBus = AuxBus_DEPRECATED; + LateReverbComponent->AuxBusName = AuxBusName_DEPRECATED; + LateReverbComponent->SendLevel = SendLevel_DEPRECATED; + LateReverbComponent->FadeRate = FadeRate_DEPRECATED; + LateReverbComponent->Priority = Priority_DEPRECATED; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkRoomComponent.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkRoomComponent.cpp new file mode 100644 index 0000000..262be79 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkRoomComponent.cpp @@ -0,0 +1,627 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkRoomComponent.cpp: +=============================================================================*/ + +#include "AkRoomComponent.h" +#include "AkComponentHelpers.h" +#include "AkAcousticPortal.h" +#include "AkAudioDevice.h" +#include "AkGeometryComponent.h" +#include "AkLateReverbComponent.h" +#include "AkSurfaceReflectorSetComponent.h" +#include "Components/BrushComponent.h" +#include "GameFramework/Volume.h" +#include "Model.h" +#include "EngineUtils.h" +#include "AkAudioEvent.h" +#include "AkSettings.h" +#include "Wwise/API/WwiseSpatialAudioAPI.h" +#if WITH_EDITOR +#include "AkDrawRoomComponent.h" +#include "AkSpatialAudioHelper.h" +#endif + +#define MOVEMENT_STOP_TIMEOUT 0.1f + +/*------------------------------------------------------------------------------------ + UAkRoomComponent +------------------------------------------------------------------------------------*/ + +UAkRoomComponent::UAkRoomComponent(const class FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer) +{ + Parent = NULL; + + WallOcclusion = 1.0f; + + bEnable = true; + bUseAttachParentBound = true; + AutoPost = false; + + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.bStartWithTickEnabled = true; + bTickInEditor = true; +#if WITH_EDITOR + if (AkSpatialAudioHelper::GetObjectReplacedEvent()) + { + AkSpatialAudioHelper::GetObjectReplacedEvent()->AddUObject(this, &UAkRoomComponent::HandleObjectsReplaced); + } + bWantsOnUpdateTransform = true; + bWantsInitializeComponent = true; +#else + bWantsOnUpdateTransform = bDynamic; +#endif +} + +FName UAkRoomComponent::GetName() const +{ + return Parent->GetFName(); +} + +bool UAkRoomComponent::HasEffectOnLocation(const FVector& Location) const +{ + // Need to add a small radius, because on the Mac, EncompassesPoint returns false if + // Location is exactly equal to the Volume's location + static float RADIUS = 0.01f; + return RoomIsActive() && EncompassesPoint(Location, RADIUS); +} + +bool UAkRoomComponent::RoomIsActive() const +{ + return IsValid(Parent) && bEnable && !IsRunningCommandlet(); +} + +void UAkRoomComponent::OnRegister() +{ + Super::OnRegister(); + SetRelativeTransform(FTransform::Identity); + InitializeParent(); + // We want to add / update the room both in BeginPlay and OnRegister. BeginPlay for aux bus and reverb level assignment, OnRegister for portal room assignment and visualization + if (!IsRegisteredWithWwise) + AddSpatialAudioRoom(); + else + UpdateSpatialAudioRoom(); + +#if WITH_EDITOR + if (GetDefault()->VisualizeRoomsAndPortals) + { + InitializeDrawComponent(); + } +#endif +} + +void UAkRoomComponent::OnUnregister() +{ + Super::OnUnregister(); + RemoveSpatialAudioRoom(); +} + +#if WITH_EDITOR +void UAkRoomComponent::OnComponentCreated() +{ + Super::OnComponentCreated(); + RegisterVisEnabledCallback(); +} + +void UAkRoomComponent::InitializeComponent() +{ + Super::InitializeComponent(); + RegisterVisEnabledCallback(); +} + +void UAkRoomComponent::PostLoad() +{ + Super::PostLoad(); + RegisterVisEnabledCallback(); +} + + +void UAkRoomComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + UAkSettings* AkSettings = GetMutableDefault(); + AkSettings->OnShowRoomsPortalsChanged.Remove(ShowRoomsChangedHandle); + ShowRoomsChangedHandle.Reset(); + DestroyDrawComponent(); +} +#endif // WITH_EDITOR + +void UAkRoomComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction * ThisTickFunction) +{ +#if WITH_EDITOR + if (bRequiresDeferredBeginPlay) + { + BeginPlayInternal(); + bRequiresDeferredBeginPlay = false; + } +#endif + + // In PIE, only update in tick if bDynamic is true (simulate the behaviour in the no-editor game build). + bool bUpdate = true; +#if WITH_EDITOR + if (AkComponentHelpers::IsInGameWorld(this)) + bUpdate = bDynamic; +#endif + if (bUpdate) + { + if (Moving) + { + SecondsSinceMovement += DeltaTime; + if (SecondsSinceMovement >= MOVEMENT_STOP_TIMEOUT) + { + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (AkAudioDevice != nullptr) + { + AkAudioDevice->ReindexRoom(this); + AkAudioDevice->PortalsNeedRoomUpdate(GetWorld()); + } + Moving = false; + } + } + if ((bEnable && !IsRegisteredWithWwise) || (!bEnable && IsRegisteredWithWwise)) + { + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (AkAudioDevice != nullptr) + { + if (IsRegisteredWithWwise) + RemoveSpatialAudioRoom(); + else + AddSpatialAudioRoom(); + } + } + } +} + +#if WITH_EDITOR +void UAkRoomComponent::BeginDestroy() +{ + Super::BeginDestroy(); + if (AkSpatialAudioHelper::GetObjectReplacedEvent()) + { + AkSpatialAudioHelper::GetObjectReplacedEvent()->RemoveAll(this); + } +} + +void UAkRoomComponent::HandleObjectsReplaced(const TMap& ReplacementMap) +{ + if (ReplacementMap.Contains(Parent)) + { + InitializeParent(); + if (!IsRegisteredWithWwise) + AddSpatialAudioRoom(); + else + UpdateSpatialAudioRoom(); + } + if (ReplacementMap.Contains(GeometryComponent)) + { + GeometryComponent = AkComponentHelpers::GetChildComponentOfType(*Parent); + if (GeometryComponent == nullptr || GeometryComponent->HasAnyFlags(RF_Transient) || GeometryComponent->IsBeingDestroyed()) + { + GeometryComponent = NewObject(Parent, TEXT("GeometryComponent")); + UAkGeometryComponent* GeomComp = Cast(GeometryComponent); + GeomComp->MeshType = AkMeshType::CollisionMesh; + GeomComp->bWasAddedByRoom = true; + GeometryComponent->AttachToComponent(Parent, FAttachmentTransformRules::KeepRelativeTransform); + GeometryComponent->RegisterComponent(); + + if (!RoomIsActive()) + GeomComp->RemoveGeometry(); + } + SendGeometry(); + UpdateSpatialAudioRoom(); + } +} + +void UAkRoomComponent::RegisterVisEnabledCallback() +{ + if (!ShowRoomsChangedHandle.IsValid()) + { + UAkSettings* AkSettings = GetMutableDefault(); + ShowRoomsChangedHandle = AkSettings->OnShowRoomsPortalsChanged.AddLambda([this, AkSettings]() + { + if (AkSettings->VisualizeRoomsAndPortals) + { + InitializeDrawComponent(); + } + else + { + DestroyDrawComponent(); + } + }); + } +} + +void UAkRoomComponent::InitializeDrawComponent() +{ + if (AActor* Owner = GetOwner()) + { + if (DrawRoomComponent == nullptr) + { + DrawRoomComponent = NewObject(Owner, NAME_None, RF_Transactional | RF_TextExportTransient); + DrawRoomComponent->SetupAttachment(this); + DrawRoomComponent->SetIsVisualizationComponent(true); + DrawRoomComponent->CreationMethod = CreationMethod; + DrawRoomComponent->RegisterComponentWithWorld(GetWorld()); + DrawRoomComponent->MarkRenderStateDirty(); + } + } +} + +void UAkRoomComponent::DestroyDrawComponent() +{ + if (DrawRoomComponent != nullptr) + { + DrawRoomComponent->DestroyComponent(); + DrawRoomComponent = nullptr; + } +} +#endif + +void UAkRoomComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) +{ + Moving = true; + SecondsSinceMovement = 0.0f; +} + +bool UAkRoomComponent::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 UAkRoomComponent::InitializeParent() +{ + USceneComponent* SceneParent = GetAttachParent(); + if (SceneParent != nullptr) + { + Parent = Cast(SceneParent); + if (!Parent) + { + bEnable = false; + AkComponentHelpers::LogAttachmentError(this, SceneParent, "UPrimitiveComponent"); + return; + } + + UBodySetup* bodySetup = Parent->GetBodySetup(); + if (bodySetup == nullptr || !AkComponentHelpers::HasSimpleCollisionGeometry(bodySetup)) + { + if (UBrushComponent* brush = Cast(Parent)) + brush->BuildSimpleBrushCollision(); + else + AkComponentHelpers::LogSimpleGeometryWarning(Parent, this); + } + } +} + +FString UAkRoomComponent::GetRoomName() +{ + FString nameStr = UObject::GetName(); + + AActor* roomOwner = GetOwner(); + if (roomOwner != nullptr) + { +#if WITH_EDITOR + nameStr = roomOwner->GetActorLabel(); +#else + nameStr = roomOwner->GetName(); +#endif + if (Parent != nullptr) + { + TInlineComponentArray RoomComponents; + roomOwner->GetComponents(RoomComponents); + if (RoomComponents.Num() > 1) + nameStr.Append(FString("_").Append(Parent->GetName())); + } + } + + return nameStr; +} + +void UAkRoomComponent::GetRoomParams(AkRoomParams& outParams) +{ + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (!AkAudioDevice) + return; + + if (IsValid(Parent)) + { + AkComponentHelpers::GetPrimitiveUpAndFront(*Parent, outParams.Up, outParams.Front); + } + + outParams.TransmissionLoss = WallOcclusion; + + UAkLateReverbComponent* ReverbComp = GetReverbComponent(); + if (ReverbComp && ReverbComp->bEnable) + { + if (UNLIKELY(!ReverbComp->AuxBus && ReverbComp->AuxBusName.IsEmpty())) + { + outParams.ReverbAuxBus = AK_INVALID_AUX_ID; + } + else + { + outParams.ReverbAuxBus = ReverbComp->GetAuxBusId(); + } + outParams.ReverbLevel = ReverbComp->SendLevel; + } + + if (GeometryComponent != nullptr) + outParams.GeometryInstanceID = GeometryComponent->GetGeometrySetID(); + + outParams.RoomGameObj_AuxSendLevelToSelf = AuxSendLevel; + outParams.RoomGameObj_KeepRegistered = AkAudioEvent == NULL && EventName.IsEmpty() ? false : true; + const UAkSettings* AkSettings = GetDefault(); + if (AkSettings != nullptr && AkSettings->ReverbRTPCsInUse()) + outParams.RoomGameObj_KeepRegistered = true; +} + +UPrimitiveComponent* UAkRoomComponent::GetPrimitiveParent() const +{ + return Parent; +} + +void UAkRoomComponent::AddSpatialAudioRoom() +{ + if (RoomIsActive()) + { + SendGeometry(); + + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + IWwiseSpatialAudioAPI* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (AkAudioDevice && SpatialAudio) + { + AkRoomParams Params; + GetRoomParams(Params); + AkAudioDevice->AddRoom(this, Params); + IsRegisteredWithWwise = true; + if (GetOwner() != nullptr && IsRegisteredWithWwise && (GetWorld()->WorldType == EWorldType::Game || GetWorld()->WorldType == EWorldType::PIE)) + { + UAkLateReverbComponent* pRvbComp = GetReverbComponent(); + if (pRvbComp != nullptr) + pRvbComp->UpdateRTPCs(this); + } + } + } +} + +void UAkRoomComponent::UpdateSpatialAudioRoom() +{ + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + IWwiseSpatialAudioAPI* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + if (RoomIsActive() && AkAudioDevice && SpatialAudio && IsRegisteredWithWwise) + { + AkRoomParams Params; + GetRoomParams(Params); + AkAudioDevice->UpdateRoom(this, Params); + if (GetOwner() != nullptr && (GetWorld()->WorldType == EWorldType::Game || GetWorld()->WorldType == EWorldType::PIE)) + { + UAkLateReverbComponent* pRvbComp = GetReverbComponent(); + if (pRvbComp != nullptr) + pRvbComp->UpdateRTPCs(this); + } + } +} + +void UAkRoomComponent::RemoveSpatialAudioRoom() +{ + if (Parent && !IsRunningCommandlet()) + { + RemoveGeometry(); + + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (AkAudioDevice) + { + if (GetOwner() != nullptr && (GetWorld()->WorldType == EWorldType::Game || GetWorld()->WorldType == EWorldType::PIE)) + { + // stop all sounds posted on the room + Stop(); + } + AkAudioDevice->RemoveRoom(this); + IsRegisteredWithWwise = false; + } + } +} + +int32 UAkRoomComponent::PostAssociatedAkEvent(int32 CallbackMask, const FOnAkPostEventCallback& PostEventCallback) +{ + AkPlayingID playingID = AK_INVALID_PLAYING_ID; + + if (!HasActiveEvents()) + playingID = PostAkEvent(AkAudioEvent, CallbackMask, PostEventCallback, EventName); + + return playingID; +} + +AkPlayingID UAkRoomComponent::PostAkEventByNameWithDelegate( + UAkAudioEvent* AkEvent, + const FString& in_EventName, + int32 CallbackMask, const FOnAkPostEventCallback& PostEventCallback) +{ + AkPlayingID PlayingID = AK_INVALID_PLAYING_ID; + + auto AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + const AkUInt32 ShortID = AudioDevice->GetShortID(AkEvent, in_EventName); + PlayingID = AudioDevice->PostEventOnAkGameObject(ShortID, this, PostEventCallback, CallbackMask); + } + + return PlayingID; +} + +void UAkRoomComponent::BeginPlay() +{ + Super::BeginPlay(); + +#if WITH_EDITOR + // If we're PIE, or somehow otherwise in a game world in editor, simulate the bDynamic behaviour. + if (AkComponentHelpers::IsInGameWorld(this)) + { + bWantsOnUpdateTransform = bDynamic; + } + if (AkComponentHelpers::ShouldDeferBeginPlay(this)) + bRequiresDeferredBeginPlay = true; + else + BeginPlayInternal(); +#else + BeginPlayInternal(); + PrimaryComponentTick.bCanEverTick = bDynamic; + PrimaryComponentTick.bStartWithTickEnabled = bDynamic; +#endif +} + +void UAkRoomComponent::BeginPlayInternal() +{ + GeometryComponent = AkComponentHelpers::GetChildComponentOfType(*Parent); + if (GeometryComponent == nullptr || GeometryComponent->HasAnyFlags(RF_Transient) || GeometryComponent->IsBeingDestroyed()) + { + static const FName GeometryComponentName = TEXT("GeometryComponent"); + GeometryComponent = NewObject(Parent, GeometryComponentName); + UAkGeometryComponent* geom = Cast(GeometryComponent); + geom->MeshType = AkMeshType::CollisionMesh; + geom->bWasAddedByRoom = true; + GeometryComponent->AttachToComponent(Parent, FAttachmentTransformRules::KeepRelativeTransform); + GeometryComponent->RegisterComponent(); + + if (!RoomIsActive()) + geom->RemoveGeometry(); + } + + // We want to add / update the room both in BeginPlay and OnRegister. BeginPlay for aux bus and reverb level assignment, OnRegister for portal room assignment and visualization + if (!IsRegisteredWithWwise) + { + AddSpatialAudioRoom(); + } + else + { + SendGeometry(); + UpdateSpatialAudioRoom(); + } + + if (AutoPost) + { + if (!HasActiveEvents()) + PostAssociatedAkEvent(0, FOnAkPostEventCallback()); + } +} + +void UAkRoomComponent::EndPlay(EEndPlayReason::Type EndPlayReason) +{ + if (bEventPosted) + { + Stop(); + } + + Super::EndPlay(EndPlayReason); +} + +void UAkRoomComponent::SetGeometryComponent(UAkAcousticTextureSetComponent* textureSetComponent) +{ + if (GeometryComponent != nullptr) + { + RemoveGeometry(); + } + GeometryComponent = textureSetComponent; + if (RoomIsActive()) + { + SendGeometry(); + UpdateSpatialAudioRoom(); + } +} + +#if WITH_EDITOR +void UAkRoomComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + //Call add again to update the room parameters, if it has already been added. + if (IsRegisteredWithWwise) + UpdateSpatialAudioRoom(); +} +#endif + +bool UAkRoomComponent::EncompassesPoint(FVector Point, float SphereRadius/*=0.f*/, float* OutDistanceToPoint/*=nullptr*/) const +{ + if (IsValid(Parent)) + { + return AkComponentHelpers::EncompassesPoint(*Parent, Point, SphereRadius, OutDistanceToPoint); + } + FString actorString = FString("NONE"); + if (GetOwner() != nullptr) + actorString = GetOwner()->GetName(); + UE_LOG(LogAkAudio, Error, TEXT("UAkRoomComponent::EncompassesPoint : Error. In actor %s, AkRoomComponent %s has an invalid Parent."), *actorString, *UObject::GetName()); + return false; +} + +void UAkRoomComponent::SendGeometry() +{ + if (GeometryComponent) + { + UAkGeometryComponent* GeometryComp = Cast(GeometryComponent); + if (GeometryComp && GeometryComp->bWasAddedByRoom) + { + if (!GeometryComp->GetGeometryHasBeenSent()) + GeometryComp->SendGeometry(); + if (!GeometryComp->GetGeometryInstanceHasBeenSent()) + GeometryComp->UpdateGeometry(); + } + UAkSurfaceReflectorSetComponent* SurfaceReflector = Cast(GeometryComponent); + if (SurfaceReflector && !SurfaceReflector->bEnableSurfaceReflectors) + { + if (!SurfaceReflector->GetGeometryHasBeenSent()) + SurfaceReflector->SendSurfaceReflectorSet(); + if (!SurfaceReflector->GetGeometryInstanceHasBeenSent()) + SurfaceReflector->UpdateSurfaceReflectorSet(); + } + } +} + +void UAkRoomComponent::RemoveGeometry() +{ + if (IsValid(GeometryComponent)) + { + UAkGeometryComponent* GeometryComp = Cast(GeometryComponent); + if (GeometryComp && GeometryComp->bWasAddedByRoom) + { + GeometryComp->RemoveGeometry(); + } + UAkSurfaceReflectorSetComponent* SurfaceReflector = Cast(GeometryComponent); + if (SurfaceReflector && !SurfaceReflector->bEnableSurfaceReflectors) + { + SurfaceReflector->RemoveSurfaceReflectorSet(); + } + } +} + +UAkLateReverbComponent* UAkRoomComponent::GetReverbComponent() +{ + UAkLateReverbComponent* pRvbComp = nullptr; + if (Parent != nullptr) + { + pRvbComp = AkComponentHelpers::GetChildComponentOfType(*Parent); + } + return pRvbComp; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkRtpc.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkRtpc.cpp new file mode 100644 index 0000000..6465c79 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkRtpc.cpp @@ -0,0 +1,126 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkRtpc.h" + +#if WITH_EDITORONLY_DATA +#include "Wwise/WwiseProjectDatabase.h" +#include "Wwise/WwiseResourceCooker.h" +#include "AkAudioDevice.h" +#endif + + +void UAkRtpc::Serialize(FArchive& Ar) +{ + Super::Serialize(Ar); + + if (HasAnyFlags(RF_ClassDefaultObject)) + { + return; + } +#if !UE_SERVER +#if WITH_EDITORONLY_DATA + if (Ar.IsCooking() && Ar.IsSaving() && !Ar.CookingTarget()->IsServerOnly()) + { + FWwiseGameParameterCookedData CookedDataToArchive; + if (auto* ResourceCooker = FWwiseResourceCooker::GetForArchive(Ar)) + { + ResourceCooker->PrepareCookedData(CookedDataToArchive, GetValidatedInfo(RtpcInfo)); + } + CookedDataToArchive.Serialize(Ar); + } +#else + GameParameterCookedData.Serialize(Ar); +#endif +#endif +} + +#if WITH_EDITORONLY_DATA +void UAkRtpc::GetGameParameterCookedData() +{ + SCOPED_AKAUDIO_EVENT_2(TEXT("GetGameParameterCookedData")); + if (IWwiseProjectDatabaseModule::IsInACookingCommandlet()) + { + return; + } + auto* ProjectDatabase = FWwiseProjectDatabase::Get(); + if (!ProjectDatabase || !ProjectDatabase->IsProjectDatabaseParsed()) + { + UE_LOG(LogAkAudio, VeryVerbose, TEXT("UAkRtpc::GetGameParameterCookedData: Not loading '%s' because project database is not parsed."), *GetName()) + return; + } + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + return; + } + ResourceCooker->PrepareCookedData(GameParameterCookedData, GetValidatedInfo(RtpcInfo)); +} + +void UAkRtpc::FillInfo() +{ + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkRtpc::FillInfo: ResourceCooker not initialized")); + return; + } + + auto ProjectDatabase = ResourceCooker->GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkRtpc::FillInfo: ProjectDatabase not initialized")); + return; + } + FWwiseObjectInfo* AudioTypeInfo = &RtpcInfo; + const FWwiseRefGameParameter GameParameterRef = FWwiseDataStructureScopeLock(*ProjectDatabase).GetGameParameter( + GetValidatedInfo(RtpcInfo)); + + if (GameParameterRef.GameParameterName().IsNone() || !GameParameterRef.GameParameterGuid().IsValid() || GameParameterRef.GameParameterId() == AK_INVALID_UNIQUE_ID) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkRtpc::FillInfo: Valid object not found in Project Database")); + return; + } + + AudioTypeInfo->WwiseName = GameParameterRef.GameParameterName(); + AudioTypeInfo->WwiseGuid = GameParameterRef.GameParameterGuid(); + AudioTypeInfo->WwiseShortId = GameParameterRef.GameParameterId(); +} + +bool UAkRtpc::ObjectIsInSoundBanks() +{ + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkRtpc::GetWwiseRef: ResourceCooker not initialized")); + return false; + } + + auto ProjectDatabase = ResourceCooker->GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkRtpc::GetWwiseRef: ProjectDatabase not initialized")); + return false; + } + + FWwiseObjectInfo* AudioTypeInfo = &RtpcInfo; + const FWwiseRefGameParameter GameParameterRef = FWwiseDataStructureScopeLock(*ProjectDatabase).GetGameParameter( + GetValidatedInfo(RtpcInfo)); + + return GameParameterRef.IsValid(); +} +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSettings.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSettings.cpp new file mode 100644 index 0000000..4faae43 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSettings.cpp @@ -0,0 +1,1520 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkSettings.h" + +#include "AkAcousticTexture.h" +#include "AkAuxBus.h" +#include "AkAudioDevice.h" +#include "AkAudioEvent.h" +#include "AkAudioModule.h" +#include "AkSettingsPerUser.h" +#include "AkUnrealHelper.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "AssetRegistry/AssetData.h" +#include "Framework/Docking/TabManager.h" +#include "Framework/Notifications/NotificationManager.h" +#include "StringMatchAlgos/Array2D.h" +#include "StringMatchAlgos/StringMatching.h" +#include "UObject/UnrealType.h" +#include "Widgets/Notifications/SNotificationList.h" + +#if WITH_EDITOR +#include "AkAudioStyle.h" +#include "AssetTools/Public/AssetToolsModule.h" +#if UE_5_0_OR_LATER +#include "HAL/PlatformFileManager.h" +#else +#include "HAL/PlatformFilemanager.h" +#endif +#include "Misc/ConfigCacheIni.h" +#include "Misc/FileHelper.h" +#include "Misc/MessageDialog.h" +#include "Platforms/AkUEPlatform.h" +#include "Settings/ProjectPackagingSettings.h" +#include "SettingsEditor/Public/ISettingsEditorModule.h" +#include "ISourceControlModule.h" +#include "SourceControlHelpers.h" +#include "AkUnrealEditorHelper.h" + +#if AK_SUPPORT_WAAPI +#include "AkWaapiClient.h" +#include "AkWaapiUtils.h" +#include "Async/Async.h" + +bool WAAPIGetTextureParams(FGuid textureID, FAkAcousticTextureParams& params) +{ + auto waapiClient = FAkWaapiClient::Get(); + if (waapiClient != nullptr) + { + /* Construct the relevant WAAPI json fields. */ + TArray> fromID; + fromID.Add(MakeShareable(new FJsonValueString(textureID.ToString(EGuidFormats::DigitsWithHyphensInBraces)))); + + TSharedRef getArgsJson = FAkWaapiClient::CreateWAAPIGetArgumentJson(FAkWaapiClient::WAAPIGetFromOption::ID, fromID); + + TSharedRef options = MakeShareable(new FJsonObject()); + TArray> StructJsonArray; + StructJsonArray.Add(MakeShareable(new FJsonValueString("id"))); + TArray absorptionStrings{ "@AbsorptionLow", "@AbsorptionMidLow", "@AbsorptionMidHigh", "@AbsorptionHigh" }; + for (int i = 0; i < absorptionStrings.Num(); ++i) + StructJsonArray.Add(MakeShareable(new FJsonValueString(absorptionStrings[i]))); + + options->SetArrayField(FAkWaapiClient::WAAPIStrings::RETURN, StructJsonArray); + + TSharedPtr outJsonResult; + if (waapiClient->Call(ak::wwise::core::object::get, getArgsJson, options, outJsonResult, 500, false)) + { + /* Get absorption values from WAAPI return json. */ + TArray> returnJson = outJsonResult->GetArrayField(FAkWaapiClient::WAAPIStrings::RETURN); + if (returnJson.Num() > 0) + { + auto jsonObj = returnJson[0]->AsObject(); + if (jsonObj != nullptr) + { + TSharedPtr absorptionObject = nullptr; + for (int i = 0; i < absorptionStrings.Num(); ++i) + { + params.AbsorptionValues[i] = (float)(jsonObj->GetNumberField(absorptionStrings[i])) / 100.0f; + } + return true; + } + } + } + } + return false; +} + +bool WAAPIGetObjectColorIndex(FGuid textureID, int& index) +{ + auto waapiClient = FAkWaapiClient::Get(); + if (waapiClient != nullptr) + { + /* Construct the relevant WAAPI json fields. */ + TArray> fromID; + fromID.Add(MakeShareable(new FJsonValueString(textureID.ToString(EGuidFormats::DigitsWithHyphensInBraces)))); + TSharedRef getArgsJson = FAkWaapiClient::CreateWAAPIGetArgumentJson(FAkWaapiClient::WAAPIGetFromOption::ID, fromID); + + TSharedRef options = MakeShareable(new FJsonObject()); + TArray> StructJsonArray; + StructJsonArray.Add(MakeShareable(new FJsonValueString("id"))); + StructJsonArray.Add(MakeShareable(new FJsonValueString("@Color"))); + + options->SetArrayField(FAkWaapiClient::WAAPIStrings::RETURN, StructJsonArray); + + TSharedPtr outJsonResult; + if (waapiClient->Call(ak::wwise::core::object::get, getArgsJson, options, outJsonResult, 500, false)) + { + /* Get absorption values from WAAPI return json. */ + TArray> returnJson = outJsonResult->GetArrayField(FAkWaapiClient::WAAPIStrings::RETURN); + if (returnJson.Num() > 0) + { + auto jsonObj = returnJson[0]->AsObject(); + if (jsonObj != nullptr) + { + index = (int)(jsonObj->GetNumberField("@Color")); + return true; + } + } + } + } + return false; +} + +bool WAAPIGetObjectOverrideColor(FGuid textureID) +{ + auto waapiClient = FAkWaapiClient::Get(); + if (waapiClient != nullptr) + { + /* Construct the relevant WAAPI json fields. */ + TArray> fromID; + fromID.Add(MakeShareable(new FJsonValueString(textureID.ToString(EGuidFormats::DigitsWithHyphensInBraces)))); + TSharedRef getArgsJson = FAkWaapiClient::CreateWAAPIGetArgumentJson(FAkWaapiClient::WAAPIGetFromOption::ID, fromID); + + TSharedRef options = MakeShareable(new FJsonObject()); + TArray> StructJsonArray; + StructJsonArray.Add(MakeShareable(new FJsonValueString("id"))); + StructJsonArray.Add(MakeShareable(new FJsonValueString("@OverrideColor"))); + + options->SetArrayField(FAkWaapiClient::WAAPIStrings::RETURN, StructJsonArray); + + TSharedPtr outJsonResult; + if (waapiClient->Call(ak::wwise::core::object::get, getArgsJson, options, outJsonResult, 500, false)) + { + /* Get absorption values from WAAPI return json. */ + TArray> returnJson = outJsonResult->GetArrayField(FAkWaapiClient::WAAPIStrings::RETURN); + if (returnJson.Num() > 0) + { + auto jsonObj = returnJson[0]->AsObject(); + if (jsonObj != nullptr) + { + return jsonObj->GetBoolField("@OverrideColor"); + } + } + } + } + return false; +} +#endif // AK_SUPPORT_WAAPI +#endif // WITH_EDITOR + +#define LOCTEXT_NAMESPACE "AkSettings" + +////////////////////////////////////////////////////////////////////////// +// UAkSettings + +namespace AkSettings_Helper +{ +#if WITH_EDITOR + void MigrateMultiCoreRendering(bool EnableMultiCoreRendering, const FString& PlatformName) + { + FString SettingsClassName = FString::Format(TEXT("Ak{0}InitializationSettings"), { *PlatformName }); +#if UE_5_1_OR_LATER + auto* SettingsClass = UClass::TryFindTypeSlow(*SettingsClassName); +#else + auto* SettingsClass = FindObject(ANY_PACKAGE, *SettingsClassName); +#endif + if (!SettingsClass) + { + return; + } + + auto* MigrationFunction = SettingsClass->FindFunctionByName(TEXT("MigrateMultiCoreRendering")); + auto* Settings = SettingsClass->GetDefaultObject(); + if (!MigrationFunction || !Settings) + { + return; + } + + Settings->ProcessEvent(MigrationFunction, &EnableMultiCoreRendering); + + AkUnrealEditorHelper::SaveConfigFile(Settings); + } +#endif + + void MatchAcousticTextureNamesToPhysMaterialNames( + const TArray& PhysicalMaterials, + const TArray& AcousticTextures, + TArray& assignments) + { + uint32 NumPhysMat = (uint32)PhysicalMaterials.Num(); + uint32 NumAcousticTex = (uint32)AcousticTextures.Num(); + + // Create a scores matrix + Array2D scores(NumPhysMat, NumAcousticTex, 0); + + for (uint32 i = 0; i < NumPhysMat; ++i) + { + TArray perfectObjectMatches; + perfectObjectMatches.Init(false, NumAcousticTex); + + if (PhysicalMaterials[i].GetAsset()) + { + FString physMaterialName = PhysicalMaterials[i].GetAsset()->GetName(); + + if (physMaterialName.Len() == 0) + continue; + + for (uint32 j = 0; j < NumAcousticTex; ++j) + { + // Skip objects for which we already found a perfect match + if (perfectObjectMatches[j] == true) + continue; + + if (AcousticTextures[j].GetAsset()) + { + FString acousticTextureName = AcousticTextures[j].GetAsset()->GetName(); + + if (acousticTextureName.Len() == 0) + continue; + + // Calculate longest common substring length + float lcs = LCS::GetLCSScore( + physMaterialName.ToLower(), + acousticTextureName.ToLower()); + + scores(i, j) = lcs; + + if (FMath::IsNearlyEqual(lcs, 1.f)) + { + assignments[i] = j; + perfectObjectMatches[j] = true; + break; + } + } + } + } + } + + for (uint32 i = 0; i < NumPhysMat; ++i) + { + if (assignments[i] == -1) + { + float bestScore = 0.f; + int32 matchedIdx = -1; + for (uint32 j = 0; j < NumAcousticTex; ++j) + { + if (scores(i, j) > bestScore) + { + bestScore = scores(i, j); + matchedIdx = j; + } + } + if (bestScore >= 0.2f) + assignments[i] = matchedIdx; + } + } + } +} + +FString UAkSettings::DefaultSoundDataFolder = TEXT("WwiseAudio"); + +#if WITH_EDITOR +float UAkSettings::MinimumDecayKeyDistance = 0.01f; +#endif + +UAkSettings::UAkSettings(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + WwiseSoundDataFolder.Path = DefaultSoundDataFolder; + + GlobalDecayAbsorption = 0.5f; + +#if WITH_EDITOR + AssetRegistryModule = &FModuleManager::LoadModuleChecked("AssetRegistry"); + + //register to asset modification delegates + auto& AssetRegistry = AssetRegistryModule->Get(); + AssetRegistry.OnAssetAdded().AddUObject(this, &UAkSettings::OnAssetAdded); + AssetRegistry.OnAssetRemoved().AddUObject(this, &UAkSettings::OnAssetRemoved); + VisualizeRoomsAndPortals = false; + bShowReverbInfo = true; +#endif // WITH_EDITOR +} + +UAkSettings::~UAkSettings() +{ +#if WITH_EDITOR +#if AK_SUPPORT_WAAPI + FAkWaapiClient* waapiClient = FAkWaapiClient::Get(); + if (waapiClient != nullptr) + { + if (WaapiProjectLoadedHandle.IsValid()) + { + waapiClient->OnProjectLoaded.Remove(WaapiProjectLoadedHandle); + WaapiProjectLoadedHandle.Reset(); + } + if (WaapiConnectionLostHandle.IsValid()) + { + waapiClient->OnConnectionLost.Remove(WaapiConnectionLostHandle); + WaapiConnectionLostHandle.Reset(); + } + ClearWaapiTextureCallbacks(); + } +#endif +#endif +} + +ECollisionChannel UAkSettings::ConvertFitToGeomCollisionChannel(EAkCollisionChannel CollisionChannel) +{ + if (CollisionChannel != EAkCollisionChannel::EAKCC_UseIntegrationSettingsDefault) + return (ECollisionChannel)CollisionChannel; + + const UAkSettings* AkSettings = GetDefault(); + + if (AkSettings) + return AkSettings->DefaultFitToGeometryCollisionChannel; + + return ECollisionChannel::ECC_WorldStatic; +} + +ECollisionChannel UAkSettings::ConvertOcclusionCollisionChannel(EAkCollisionChannel CollisionChannel) +{ + if (CollisionChannel != EAkCollisionChannel::EAKCC_UseIntegrationSettingsDefault) + return (ECollisionChannel)CollisionChannel; + + const UAkSettings* AkSettings = GetDefault(); + + if (AkSettings) + return AkSettings->DefaultOcclusionCollisionChannel; + + return ECollisionChannel::ECC_WorldStatic; +} + +void UAkSettings::PostInitProperties() +{ + Super::PostInitProperties(); + +#if WITH_EDITOR + UAkSettingsPerUser* AkSettingsPerUser = GetMutableDefault(); + + if (AkSettingsPerUser) + { + bool didChanges = false; + + if (!WwiseWindowsInstallationPath_DEPRECATED.Path.IsEmpty()) + { + AkSettingsPerUser->WwiseWindowsInstallationPath = WwiseWindowsInstallationPath_DEPRECATED; + WwiseWindowsInstallationPath_DEPRECATED.Path.Reset(); + didChanges = true; + } + + if (!WwiseMacInstallationPath_DEPRECATED.FilePath.IsEmpty()) + { + AkSettingsPerUser->WwiseMacInstallationPath = WwiseMacInstallationPath_DEPRECATED; + WwiseMacInstallationPath_DEPRECATED.FilePath.Reset(); + didChanges = true; + } + + if (bAutoConnectToWAAPI_DEPRECATED) + { + AkSettingsPerUser->bAutoConnectToWAAPI = true; + bAutoConnectToWAAPI_DEPRECATED = false; + didChanges = true; + } + + if (didChanges) + { + AkUnrealEditorHelper::SaveConfigFile(this); + AkSettingsPerUser->SaveConfig(); + } + } + + if (!MigratedEnableMultiCoreRendering) + { + MigratedEnableMultiCoreRendering = true; + + for (const auto& PlatformName : AkUnrealPlatformHelper::GetAllSupportedWwisePlatforms()) + { + AkSettings_Helper::MigrateMultiCoreRendering(bEnableMultiCoreRendering_DEPRECATED, *PlatformName); + } + } + + PreviousDecayAuxBusMap = EnvironmentDecayAuxBusMap; +#endif // WITH_EDITOR +} + +#if WITH_EDITOR +void UAkSettings::PreEditChange(FProperty* PropertyAboutToChange) +{ + PreviousWwiseProjectPath = WwiseProjectPath.FilePath; + PreviousWwiseGeneratedSoundBankFolder = GeneratedSoundBanksFolder.Path; +} + +bool UAkSettings::UpdateGeneratedSoundBanksPath(FString Path) +{ + PreviousWwiseGeneratedSoundBankFolder = GeneratedSoundBanksFolder.Path; + GeneratedSoundBanksFolder.Path = Path; + return UpdateGeneratedSoundBanksPath(); +} + +bool UAkSettings::GeneratedSoundBanksPathExists() const +{ + return FPaths::DirectoryExists(AkUnrealHelper::GetSoundBankDirectory()); +} + +bool UAkSettings::AreSoundBanksGenerated() const +{ + return FPaths::FileExists(FPaths::Combine(AkUnrealHelper::GetSoundBankDirectory(), TEXT("ProjectInfo.json"))); +} + +void UAkSettings::RefreshAcousticTextureParams() const +{ + for (auto const& texture : AcousticTextureParamsMap) + { + OnTextureParamsChanged.Broadcast(texture.Key); + } +} + +bool UAkSettings::UpdateGeneratedSoundBanksPath() +{ + bool bPathChanged = AkUnrealEditorHelper::SanitizeFolderPathAndMakeRelativeToContentDir( + GeneratedSoundBanksFolder.Path, PreviousWwiseGeneratedSoundBankFolder, + FText::FromString("Please enter a valid directory path")); + + if (bPathChanged) + { + OnGeneratedSoundBanksPathChanged.Broadcast(); + } + else + { + UE_LOG(LogAkAudio, Log, TEXT("AkSettings: The given GeneratedSoundBanks folder was the same as the previous one.")); + } + return bPathChanged; +} + +void UAkSettings::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + const FName PropertyName = (PropertyChangedEvent.Property != nullptr) ? PropertyChangedEvent.Property->GetFName() : NAME_None; + const FName MemberPropertyName = (PropertyChangedEvent.MemberProperty != nullptr) ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None; + ISettingsEditorModule& SettingsEditorModule = FModuleManager::GetModuleChecked("SettingsEditor"); + + if ( PropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, MaxSimultaneousReverbVolumes)) + { + MaxSimultaneousReverbVolumes = FMath::Clamp( MaxSimultaneousReverbVolumes, 0, AK_MAX_AUX_PER_OBJ ); + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if( AkAudioDevice ) + { + AkAudioDevice->SetMaxAuxBus(MaxSimultaneousReverbVolumes); + } + } + else if (PropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, AudioRouting)) + { + OnAudioRoutingUpdate(); + } + + else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, WwiseProjectPath)) + { + SanitizeProjectPath(WwiseProjectPath.FilePath, PreviousWwiseProjectPath, FText::FromString("Please enter a valid Wwise project")); + } + else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, AkGeometryMap)) + { + if (PropertyName == GET_MEMBER_NAME_CHECKED(FAkGeometrySurfacePropertiesToMap, AcousticTexture)) + { + for (auto& elem : AkGeometryMap) + { + PhysicalMaterialAcousticTextureMap[elem.Key.LoadSynchronous()] = elem.Value.AcousticTexture.LoadSynchronous(); + } + } + else if (PropertyName == GET_MEMBER_NAME_CHECKED(FAkGeometrySurfacePropertiesToMap, OcclusionValue)) + { + for (auto& elem : AkGeometryMap) + { + PhysicalMaterialOcclusionMap[elem.Key.LoadSynchronous()] = elem.Value.OcclusionValue; + } + } + } + else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, VisualizeRoomsAndPortals)) + { + OnShowRoomsPortalsChanged.Broadcast(); + } + else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, bShowReverbInfo)) + { + OnShowReverbInfoChanged.Broadcast(); + } + else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, EnvironmentDecayAuxBusMap)) + { + if (PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive) + { + DecayAuxBusMapChanged(); + OnAuxBusAssignmentMapChanged.Broadcast(); + } + } + else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, GlobalDecayAbsorption)) + { + OnAuxBusAssignmentMapChanged.Broadcast(); + } + else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, DefaultReverbAuxBus)) + { + OnAuxBusAssignmentMapChanged.Broadcast(); + } + else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, HFDampingName) + || MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, DecayEstimateName) + || MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, TimeToFirstReflectionName) + || MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, HFDampingRTPC) + || MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, DecayEstimateRTPC) + || MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, TimeToFirstReflectionRTPC)) + { + OnReverbRTPCChanged.Broadcast(); + } + else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, WwiseStagingDirectory)) + { + FAkAudioModule::AkAudioModuleInstance->UpdateWwiseResourceLoaderSettings(); + } + else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, GeneratedSoundBanksFolder)) + { + UpdateGeneratedSoundBanksPath(); + } + + Super::PostEditChangeProperty(PropertyChangedEvent); +} + +void UAkSettings::ToggleVisualizeRoomsAndPortals() +{ + VisualizeRoomsAndPortals = !VisualizeRoomsAndPortals; + OnShowRoomsPortalsChanged.Broadcast(); +} + +void UAkSettings::ToggleShowReverbInfo() +{ + bShowReverbInfo = !bShowReverbInfo; + OnShowReverbInfoChanged.Broadcast(); +} + +void UAkSettings::FillAkGeometryMap( + const TArray& PhysicalMaterialAssets, + const TArray& AcousticTextureAssets) +{ + TArray assignments; + assignments.Init(-1, PhysicalMaterialAssets.Num()); + + AkSettings_Helper::MatchAcousticTextureNamesToPhysMaterialNames(PhysicalMaterialAssets, AcousticTextureAssets, assignments); + + for (int i = 0; i < PhysicalMaterialAssets.Num(); i++) + { + auto physicalMaterial = Cast(PhysicalMaterialAssets[i].GetAsset()); + if (!PhysicalMaterialAcousticTextureMap.Contains(physicalMaterial)) + { + if (assignments[i] != -1) + { + int32 acousticTextureIdx = assignments[i]; + auto acousticTexture = Cast(AcousticTextureAssets[acousticTextureIdx].GetAsset()); + PhysicalMaterialAcousticTextureMap.Add(physicalMaterial, acousticTexture); + } + else + { + PhysicalMaterialAcousticTextureMap.Add(physicalMaterial); + } + } + else + { + if (assignments[i] != -1) + { + if (!PhysicalMaterialAcousticTextureMap[physicalMaterial]) + { + int32 acousticTextureIdx = assignments[i]; + auto acousticTexture = Cast(AcousticTextureAssets[acousticTextureIdx].GetAsset()); + PhysicalMaterialAcousticTextureMap[physicalMaterial] = acousticTexture; + } + } + } + + if (!PhysicalMaterialOcclusionMap.Contains(physicalMaterial)) + PhysicalMaterialOcclusionMap.Add(physicalMaterial, 1.f); + } + + UpdateAkGeometryMap(); +} + +void UAkSettings::UpdateAkGeometryMap() +{ + decltype(AkGeometryMap) UpdatedGeometryMap; + for (const auto& AcousticTextureTuple : PhysicalMaterialAcousticTextureMap) + { + const auto* PhysicalMaterial(AcousticTextureTuple.Key); + const auto* AcousticTexture(AcousticTextureTuple.Value); + const auto* OcclusionPtr = PhysicalMaterialOcclusionMap.Find(PhysicalMaterial); + if (UNLIKELY(!OcclusionPtr)) + { + UE_LOG(LogAkAudio, Warning, TEXT("UpdateAkGeometryMap: Could not find Occlusion of Physical Material %s with Acoustic Texture %s"), + PhysicalMaterial ? *PhysicalMaterial->GetFName().ToString() : TEXT("[nullptr]"), + AcousticTexture ? *AcousticTexture->GetFName().ToString() : TEXT("[nullptr]")); + continue; + } + + FAkGeometrySurfacePropertiesToMap SurfaceProperties; + SurfaceProperties.AcousticTexture = AcousticTexture; + SurfaceProperties.OcclusionValue = *OcclusionPtr; + UpdatedGeometryMap.Emplace(PhysicalMaterial, MoveTemp(SurfaceProperties)); + } + + UpdatedGeometryMap.KeySort([](const auto& Lhs, const auto& Rhs) { + if (UNLIKELY(!Lhs.IsValid() || !Rhs.IsValid())) + { + return !Lhs.IsValid(); + } + return Lhs->GetPathName().Compare(Rhs->GetPathName()) < 0; + }); + + if (!UpdatedGeometryMap.OrderIndependentCompareEqual(AkGeometryMap)) + { + UE_LOG(LogAkAudio, Verbose, TEXT("UpdateAkGeometryMap: Updating changed AkGeometryMap")); + AkGeometryMap = UpdatedGeometryMap; + AkUnrealEditorHelper::SaveConfigFile(this); + } + else + { + UE_LOG(LogAkAudio, VeryVerbose, TEXT("UpdateAkGeometryMap: AkGeometryMap is unchanged. Skip updating.")); + } +} + +void UAkSettings::InitAkGeometryMap() +{ + PhysicalMaterialAcousticTextureMap.Empty(); + PhysicalMaterialOcclusionMap.Empty(); + + // copy everything from the ini file + for (auto& elem : AkGeometryMap) + { + auto physMat = elem.Key.LoadSynchronous(); + auto surfaceProps = elem.Value; + PhysicalMaterialAcousticTextureMap.Add(physMat, surfaceProps.AcousticTexture.LoadSynchronous()); + PhysicalMaterialOcclusionMap.Add(physMat, surfaceProps.OcclusionValue); + } + bAkGeometryMapInitialized = true; + + // Obtain the 2 list of children we want to match + TArray PhysicalMaterials, AcousticTextures; +#if UE_5_1_OR_LATER + AssetRegistryModule->Get().GetAssetsByClass(UPhysicalMaterial::StaticClass()->GetClassPathName(), PhysicalMaterials); + AssetRegistryModule->Get().GetAssetsByClass(UAkAcousticTexture::StaticClass()->GetClassPathName(), AcousticTextures); +#else + AssetRegistryModule->Get().GetAssetsByClass(UPhysicalMaterial::StaticClass()->GetFName(), PhysicalMaterials); + AssetRegistryModule->Get().GetAssetsByClass(UAkAcousticTexture::StaticClass()->GetFName(), AcousticTextures); +#endif + + FillAkGeometryMap(PhysicalMaterials, AcousticTextures); +} + +void UAkSettings::ClearAkRoomDecayAuxBusMap() +{ + EnvironmentDecayAuxBusMap.Empty(); + PreviousDecayAuxBusMap = EnvironmentDecayAuxBusMap; +} + +void UAkSettings::InsertDecayKeyValue(const float& decayKey) +{ + if (decayKey < 0.0f) + { + UE_LOG(LogAkAudio, Warning, TEXT("AkSettings: Reverb Assignment Map: Decay key values must be positive.")); + return; + } + // Refuse key value if it is too close to an existing key value (within MinimumDecayKeyDistance). + TArray decayKeys; + EnvironmentDecayAuxBusMap.GetKeys(decayKeys); + for (int i = 0; i < decayKeys.Num(); ++i) + { + if (FMath::Abs(decayKeys[i] - decayKey) < MinimumDecayKeyDistance) + { + UE_LOG(LogAkAudio, Warning, TEXT("AkSettings: Reverb Assignment Map: New decay key too close to existing key. Must be +- %f from any existing key."), MinimumDecayKeyDistance); + return; + } + // Keys are reverse sorted. If the new key value is larger enough than the current index, we have found a valid insert space. + // (There will be no other keys larger than the current index to check against). + if (decayKey - decayKeys[i] > MinimumDecayKeyDistance) + break; + } + EnvironmentDecayAuxBusMap.Add(decayKey, nullptr); + SortDecayKeys(); +} + +void UAkSettings::SetAcousticTextureParams(const FGuid& textureID, const FAkAcousticTextureParams& params) +{ + if (AcousticTextureParamsMap.Contains(textureID)) + AcousticTextureParamsMap[textureID] = params; + else + AcousticTextureParamsMap.Add(textureID, params); + +#if AK_SUPPORT_WAAPI + RegisterWaapiTextureCallback(textureID); +#endif +} + +void UAkSettings::ClearTextureParamsMap() +{ + AcousticTextureParamsMap.Empty(); + +#if AK_SUPPORT_WAAPI + ClearWaapiTextureCallbacks(); +#endif +} + +#if AK_SUPPORT_WAAPI +void UAkSettings::WaapiProjectLoaded() +{ + TArray keys; + AcousticTextureParamsMap.GetKeys(keys); + for (auto key : keys) + { + UpdateTextureParams(key); + UpdateTextureColor(key); + RegisterWaapiTextureCallback(key); + } +} + +void UAkSettings::WaapiDisconnected() +{ + ClearWaapiTextureCallbacks(); +} + +void UAkSettings::RegisterWaapiTextureCallback(const FGuid& textureID) +{ + FAkWaapiClient* waapiClient = FAkWaapiClient::Get(); + if (waapiClient != nullptr && waapiClient->IsConnected()) + { + auto absorptionCallback = WampEventCallback::CreateLambda([this](uint64_t id, TSharedPtr jsonObject) + { + const TSharedPtr itemObj = jsonObject->GetObjectField(WwiseWaapiHelper::OBJECT); + if (itemObj != nullptr) + { + const FString itemIdString = itemObj->GetStringField(WwiseWaapiHelper::ID); + FGuid itemID = FGuid::NewGuid(); + FGuid::ParseExact(itemIdString, EGuidFormats::DigitsWithHyphensInBraces, itemID); + if (AcousticTextureParamsMap.Find(itemID) != nullptr) + { + AsyncTask(ENamedThreads::GameThread, [this, itemID] + { + UpdateTextureParams(itemID); + }); + } + } + }); + + + TSharedRef options = MakeShareable(new FJsonObject()); + options->SetStringField(WwiseWaapiHelper::OBJECT, textureID.ToString(EGuidFormats::DigitsWithHyphensInBraces)); + + TArray absorptionStrings{ "AbsorptionLow", "AbsorptionMidLow", "AbsorptionMidHigh", "AbsorptionHigh" }; + TSharedPtr jsonResult; + TSharedPtr unsubscribeResult; + bool unsubscribeNeeded = WaapiTextureSubscriptions.Find(textureID) != nullptr; + TArray subscriptionIDs{ 0,0,0,0 }; + for (int i = 0; i < absorptionStrings.Num(); ++i) + { + options->SetStringField(WwiseWaapiHelper::PROPERTY, absorptionStrings[i]); + if (unsubscribeNeeded) + { + waapiClient->Unsubscribe(WaapiTextureSubscriptions[textureID][i], unsubscribeResult); + } + if (!waapiClient->Subscribe(ak::wwise::core::object::propertyChanged, options, absorptionCallback, subscriptionIDs[i], jsonResult)) + { + UE_LOG(LogAkAudio, Warning, TEXT("AkSettings: WAAPI: Acoustic texture propertyChanged subscription failed.")); + } + } + WaapiTextureSubscriptions.Add(textureID, subscriptionIDs); + + + auto colorCallback = WampEventCallback::CreateLambda([this](uint64_t id, TSharedPtr jsonObject) + { + const TSharedPtr itemObj = jsonObject->GetObjectField(WwiseWaapiHelper::OBJECT); + if (itemObj != nullptr) + { + const FString itemIdString = itemObj->GetStringField(WwiseWaapiHelper::ID); + FGuid itemID = FGuid::NewGuid(); + FGuid::ParseExact(itemIdString, EGuidFormats::DigitsWithHyphensInBraces, itemID); + if (AcousticTextureParamsMap.Find(itemID) != nullptr) + { + AsyncTask(ENamedThreads::GameThread, [this, itemID] + { + UpdateTextureColor(itemID); + }); + } + } + }); + + options = MakeShareable(new FJsonObject()); + options->SetStringField(WwiseWaapiHelper::OBJECT, textureID.ToString(EGuidFormats::DigitsWithHyphensInBraces)); + unsubscribeNeeded = WaapiTextureColorSubscriptions.Find(textureID) != nullptr; + uint64 subscriptionID = 0; + options->SetStringField(WwiseWaapiHelper::PROPERTY, "Color"); + if (unsubscribeNeeded) + { + waapiClient->Unsubscribe(WaapiTextureColorSubscriptions[textureID], unsubscribeResult); + } + if (!waapiClient->Subscribe(ak::wwise::core::object::propertyChanged, options, colorCallback, subscriptionID, jsonResult)) + { + UE_LOG(LogAkAudio, Warning, TEXT("AkSettings: WAAPI: Acoustic texture Color propertyChanged subscription failed.")); + } + WaapiTextureColorSubscriptions.Add(textureID, subscriptionID); + + unsubscribeNeeded = WaapiTextureColorOverrideSubscriptions.Find(textureID) != nullptr; + subscriptionID = 0; + options->SetStringField(WwiseWaapiHelper::PROPERTY, "OverrideColor"); + if (unsubscribeNeeded) + { + waapiClient->Unsubscribe(WaapiTextureColorOverrideSubscriptions[textureID], unsubscribeResult); + } + if (!waapiClient->Subscribe(ak::wwise::core::object::propertyChanged, options, colorCallback, subscriptionID, jsonResult)) + { + UE_LOG(LogAkAudio, Warning, TEXT("AkSettings: WAAPI: Acoustic texture OverrideColor propertyChanged subscription failed.")); + } + WaapiTextureColorOverrideSubscriptions.Add(textureID, subscriptionID); + } +} + +void UAkSettings::UnregisterWaapiTextureCallback(const FGuid& textureID) +{ + FAkWaapiClient* waapiClient = FAkWaapiClient::Get(); + if (waapiClient != nullptr && waapiClient->IsConnected()) + { + if (WaapiTextureSubscriptions.Find(textureID) != nullptr) + { + TSharedPtr unsubscribeResult; + for (int i = 0; i < WaapiTextureSubscriptions[textureID].Num(); ++i) + waapiClient->Unsubscribe(WaapiTextureSubscriptions[textureID][i], unsubscribeResult); + WaapiTextureSubscriptions.Remove(textureID); + } + if (WaapiTextureColorSubscriptions.Find(textureID) != nullptr) + { + TSharedPtr unsubscribeResult; + waapiClient->Unsubscribe(WaapiTextureColorSubscriptions[textureID], unsubscribeResult); + WaapiTextureColorSubscriptions.Remove(textureID); + } + if (WaapiTextureColorOverrideSubscriptions.Find(textureID) != nullptr) + { + TSharedPtr unsubscribeResult; + waapiClient->Unsubscribe(WaapiTextureColorOverrideSubscriptions[textureID], unsubscribeResult); + WaapiTextureColorOverrideSubscriptions.Remove(textureID); + } + } +} + +void UAkSettings::ClearWaapiTextureCallbacks() +{ + FAkWaapiClient* waapiClient = FAkWaapiClient::Get(); + if (waapiClient != nullptr && waapiClient->IsConnected()) + { + for (auto it = WaapiTextureSubscriptions.CreateIterator(); it; ++it) + { + TSharedPtr unsubscribeResult; + for (int i = 0; i < it.Value().Num(); ++i) + waapiClient->Unsubscribe(it.Value()[i], unsubscribeResult); + } + for (auto it = WaapiTextureColorSubscriptions.CreateIterator(); it; ++it) + { + TSharedPtr unsubscribeResult; + waapiClient->Unsubscribe(it.Value(), unsubscribeResult); + } + for (auto it = WaapiTextureColorOverrideSubscriptions.CreateIterator(); it; ++it) + { + TSharedPtr unsubscribeResult; + waapiClient->Unsubscribe(it.Value(), unsubscribeResult); + } + WaapiTextureSubscriptions.Empty(); + WaapiTextureColorSubscriptions.Empty(); + WaapiTextureColorOverrideSubscriptions.Empty(); + } +} + +void UAkSettings::UpdateTextureParams(const FGuid& textureID) +{ + WAAPIGetTextureParams(textureID, AcousticTextureParamsMap[textureID]); + OnTextureParamsChanged.Broadcast(textureID); +} + +void UAkSettings::UpdateTextureColor(const FGuid& textureID) +{ + if (!WAAPIGetObjectOverrideColor(textureID)) + { + SetTextureColor(textureID, -1); + return; + } + + int colorIndex = 0; + if (WAAPIGetObjectColorIndex(textureID, colorIndex)) + { + SetTextureColor(textureID, colorIndex); + } +} + +void UAkSettings::SetTextureColor(FGuid textureID, int colorIndex) +{ + TArray AcousticTextures; +#if UE_5_1_OR_LATER + AssetRegistryModule->Get().GetAssetsByClass(UAkAcousticTexture::StaticClass()->GetClassPathName(), AcousticTextures); +#else + AssetRegistryModule->Get().GetAssetsByClass(UAkAcousticTexture::StaticClass()->GetFName(), AcousticTextures); +#endif + + FLinearColor color = FAkAudioStyle::GetWwiseObjectColor(colorIndex); + for (FAssetData& textureAsset : AcousticTextures) + { + if (UAkAcousticTexture* texture = Cast(textureAsset.GetAsset())) + { + if (texture->AcousticTextureInfo.WwiseGuid == textureID && texture->EditColor != color) + { + texture->Modify(); + texture->EditColor = color; + break; + } + } + } +} + +#endif // AK_SUPPORT_WAAPI + +void UAkSettings::DecayAuxBusMapChanged() +{ + // If a key has been moved beyond its neighbours, restrict it between neighbouring key values + + // Removal - nothing to restrict + if (PreviousDecayAuxBusMap.Num() > EnvironmentDecayAuxBusMap.Num()) + { + PreviousDecayAuxBusMap = EnvironmentDecayAuxBusMap; + return; + } + // Addition - when the insert button is used, restrictions will already be handled. + // If the stock Unreal '+' button is used, a 0-nullptr entry will be added at the end of the map. In this case, + // remove it and insert it using the InsertDecayKeyValue so it can be properly restricted and placed. + if (PreviousDecayAuxBusMap.Num() < EnvironmentDecayAuxBusMap.Num()) + { + if (EnvironmentDecayAuxBusMap.Num() > 1) + { + TArray keys; + EnvironmentDecayAuxBusMap.GetKeys(keys); + if (keys.Last() == 0.0f && EnvironmentDecayAuxBusMap[0.0f] == nullptr) + { + EnvironmentDecayAuxBusMap.Remove(0.0f); + InsertDecayKeyValue(0.0f); + } + } + PreviousDecayAuxBusMap = EnvironmentDecayAuxBusMap; + return; + } + + // Find key that has changed + int changedKeyIndex = -1; + const int numKeys = PreviousDecayAuxBusMap.Num(); + TArray previousKeys; + TArray newKeys; + PreviousDecayAuxBusMap.GetKeys(previousKeys); + EnvironmentDecayAuxBusMap.GetKeys(newKeys); + for (int i = 0; i < numKeys; ++i) + { + if (!FMath::IsNearlyEqual(previousKeys[i], newKeys[i], 1.0e-06F)) // Floating point property values have a tendancy to gradually wander in UE. + { + changedKeyIndex = i; + break; + } + } + // If no key values have changed, an aux bus has been changed. Nothing to restrict. + if (changedKeyIndex == -1) + return; + // check key value + float newKeyValue = newKeys[changedKeyIndex]; + float restrictedKeyValue = newKeyValue; + FString restrictionInfoString; + // Keys are sorted in reverse order such that they are ordered vertically from smallest to largest, in the UI. + // So, check newKeyValue <= newKeys[changedKeyIndex + 1] and newKeyValue >= newKeys[changedKeyIndex - 1]. + const bool changedKeyIsSmallest = changedKeyIndex == numKeys - 1; + float lowerLimit = changedKeyIsSmallest ? 0.0f : newKeys[changedKeyIndex + 1]; + if (newKeyValue <= lowerLimit) + { + restrictedKeyValue = changedKeyIsSmallest ? 0.0f : lowerLimit + MinimumDecayKeyDistance; + restrictionInfoString = FString("Decay key value limited by next lowest value."); + } + else if (changedKeyIndex > 0 && newKeyValue >= newKeys[changedKeyIndex - 1]) + { + restrictedKeyValue = newKeys[changedKeyIndex - 1] - MinimumDecayKeyDistance; + restrictionInfoString = FString("Decay key value limited by next highest value."); + } + // If key value needs to be restricted, remove and replace the entry in the map. + if (restrictedKeyValue != newKeyValue) + { + FAkAudioStyle::DisplayEditorMessage(FText::FromString(restrictionInfoString)); + TSoftObjectPtr auxBusToMove = EnvironmentDecayAuxBusMap[newKeyValue]; + EnvironmentDecayAuxBusMap.Remove(newKeyValue); + EnvironmentDecayAuxBusMap.Add(restrictedKeyValue, auxBusToMove); + SortDecayKeys(); + } + else // No restriction to apply, but still keep track of the new key values. + { + PreviousDecayAuxBusMap = EnvironmentDecayAuxBusMap; + } +} + +void UAkSettings::SortDecayKeys() +{ + // high to low decay ('large' to 'small' environment structure) + EnvironmentDecayAuxBusMap.KeySort([](const float& left, const float& right) {return left > right; }); + PreviousDecayAuxBusMap = EnvironmentDecayAuxBusMap; +} + +void UAkSettings::OnAssetAdded(const FAssetData& NewAssetData) +{ + if (!bAkGeometryMapInitialized) + return; + +#if UE_5_1_OR_LATER + if (NewAssetData.AssetClassPath == UPhysicalMaterial::StaticClass()->GetClassPathName()) +#else + if (NewAssetData.AssetClass == UPhysicalMaterial::StaticClass()->GetFName()) +#endif + { + if (auto physicalMaterial = Cast(NewAssetData.GetAsset())) + { + TArray PhysicalMaterials, AcousticTextures; + PhysicalMaterials.Add(NewAssetData); +#if UE_5_1_OR_LATER + AssetRegistryModule->Get().GetAssetsByClass(UAkAcousticTexture::StaticClass()->GetClassPathName(), AcousticTextures); +#else + AssetRegistryModule->Get().GetAssetsByClass(UAkAcousticTexture::StaticClass()->GetFName(), AcousticTextures); +#endif + + FillAkGeometryMap(PhysicalMaterials, AcousticTextures); + } + } +#if UE_5_1_OR_LATER + else if (NewAssetData.AssetClassPath == UAkAcousticTexture::StaticClass()->GetClassPathName()) +#else + else if (NewAssetData.AssetClass == UAkAcousticTexture::StaticClass()->GetFName()) +#endif + { + if (auto acousticTexture = Cast(NewAssetData.GetAsset())) + { + TArray PhysicalMaterials, AcousticTextures; +#if UE_5_1_OR_LATER + AssetRegistryModule->Get().GetAssetsByClass(UPhysicalMaterial::StaticClass()->GetClassPathName(), PhysicalMaterials); +#else + AssetRegistryModule->Get().GetAssetsByClass(UPhysicalMaterial::StaticClass()->GetFName(), PhysicalMaterials); +#endif + AcousticTextures.Add(NewAssetData); + + FillAkGeometryMap(PhysicalMaterials, AcousticTextures); + FAkAcousticTextureParams params; + bool paramsExist = AcousticTextureParamsMap.Contains(acousticTexture->AcousticTextureInfo.WwiseGuid); + if (paramsExist) + { + params = *AcousticTextureParamsMap.Find(acousticTexture->AcousticTextureInfo.WwiseGuid); + params.shortID = acousticTexture->AcousticTextureInfo.WwiseShortId; + } +#if AK_SUPPORT_WAAPI + bool paramsSet = WAAPIGetTextureParams(acousticTexture->AcousticTextureInfo.WwiseGuid, params); + if (paramsSet && !paramsExist) + AcousticTextureParamsMap.Add(acousticTexture->AcousticTextureInfo.WwiseGuid, params); + RegisterWaapiTextureCallback(acousticTexture->AcousticTextureInfo.WwiseGuid); + int colorIndex = -1; + if (WAAPIGetObjectColorIndex(acousticTexture->AcousticTextureInfo.WwiseGuid, colorIndex)) + { + acousticTexture->EditColor = FAkAudioStyle::GetWwiseObjectColor(colorIndex); + } +#endif + } + } +} + +void UAkSettings::OnAssetRemoved(const struct FAssetData& AssetData) +{ +#if UE_5_1_OR_LATER + if (AssetData.AssetClassPath == UPhysicalMaterial::StaticClass()->GetClassPathName()) +#else + if (AssetData.AssetClass == UPhysicalMaterial::StaticClass()->GetFName()) +#endif + { + if (auto physicalMaterial = Cast(AssetData.GetAsset())) + { + PhysicalMaterialAcousticTextureMap.Remove(physicalMaterial); + PhysicalMaterialOcclusionMap.Remove(physicalMaterial); + UpdateAkGeometryMap(); + } + } +#if UE_5_1_OR_LATER + else if(AssetData.AssetClassPath == UAkAcousticTexture::StaticClass()->GetClassPathName()) +#else + else if(AssetData.AssetClass == UAkAcousticTexture::StaticClass()->GetFName()) +#endif + { + if(auto acousticTexture = Cast(AssetData.GetAsset())) + { + AcousticTextureParamsMap.Remove(acousticTexture->AcousticTextureInfo.WwiseGuid); +#if AK_SUPPORT_WAAPI + UnregisterWaapiTextureCallback(acousticTexture->AcousticTextureInfo.WwiseGuid); +#endif + } + } +} + +#if AK_SUPPORT_WAAPI +void UAkSettings::InitWaapiSync() +{ + FAkWaapiClient* waapiClient = FAkWaapiClient::Get(); + if (waapiClient != nullptr) + { + if (waapiClient->IsProjectLoaded()) + WaapiProjectLoaded(); + WaapiProjectLoadedHandle = waapiClient->OnProjectLoaded.AddLambda([this]() + { + WaapiProjectLoaded(); + }); + WaapiConnectionLostHandle = waapiClient->OnConnectionLost.AddLambda([this]() + { + WaapiDisconnected(); + }); + } +} +#endif + +void UAkSettings::EnsurePluginContentIsInAlwaysCook() const +{ + UProjectPackagingSettings* PackagingSettings = GetMutableDefault(); + + bool packageSettingsNeedUpdate = false; + + TArray PathsToCheck = { TEXT("/Wwise/WwiseTree"), TEXT("/Wwise/WwiseTypes") }; + + for (auto pathToCheck : PathsToCheck) + { + if (!PackagingSettings->DirectoriesToAlwaysCook.ContainsByPredicate([pathToCheck](FDirectoryPath PathInArray) { return PathInArray.Path == pathToCheck; })) + { + FDirectoryPath newPath; + newPath.Path = pathToCheck; + + PackagingSettings->DirectoriesToAlwaysCook.Add(newPath); + packageSettingsNeedUpdate = true; + } + } + + if (packageSettingsNeedUpdate) + { + AkUnrealEditorHelper::SaveConfigFile(PackagingSettings); + } +} + +void UAkSettings::RemoveSoundDataFromAlwaysCook(const FString& SoundDataPath) +{ + bool changed = false; + + UProjectPackagingSettings* PackagingSettings = GetMutableDefault(); + + for (int32 i = PackagingSettings->DirectoriesToAlwaysCook.Num() - 1; i >= 0; --i) + { + if (PackagingSettings->DirectoriesToAlwaysCook[i].Path == SoundDataPath) + { + PackagingSettings->DirectoriesToAlwaysCook.RemoveAt(i); + changed = true; + break; + } + } + + if (changed) + { + AkUnrealEditorHelper::SaveConfigFile(PackagingSettings); + } +} + +void UAkSettings::SanitizeProjectPath(FString& Path, const FString& PreviousPath, const FText& DialogMessage) +{ + AkUnrealHelper::TrimPath(Path); + + FString TempPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForWrite(*Path); + + FText FailReason; + if (!FPaths::ValidatePath(TempPath, &FailReason)) + { + if (EAppReturnType::Ok == FMessageDialog::Open(EAppMsgType::Ok, FailReason)) + { + Path = PreviousPath; + return; + } + } + + auto ProjectDirectory = AkUnrealHelper::GetProjectDirectory(); + if (!FPaths::FileExists(TempPath)) + { + // Path might be a valid one (relative to game) entered manually. Check that. + TempPath = FPaths::ConvertRelativePathToFull(ProjectDirectory, Path); + + if (!FPaths::FileExists(TempPath)) + { + if (EAppReturnType::Ok == FMessageDialog::Open(EAppMsgType::Ok, DialogMessage)) + { + Path = PreviousPath; + return; + } + } + } + + // Make the path relative to the game dir + FPaths::MakePathRelativeTo(TempPath, *ProjectDirectory); + Path = TempPath; + + if (Path != PreviousPath) + { +#if UE_4_26_OR_LATER + auto WwiseBrowserTab = FGlobalTabmanager::Get()->TryInvokeTab(FName("WwiseBrowser")); +#else + TSharedRef WwiseBrowserTab = FGlobalTabmanager::Get()->InvokeTab(FName("WwiseBrowser")); +#endif + bRequestRefresh = true; + } +} + +void UAkSettings::OnAudioRoutingUpdate() +{ + // Calculate what is expected + bool bExpectedCustom = false; + bool bExpectedSeparate = false; + bool bExpectedUsingAudioMixer = false; + bool bExpectedAudioModuleOverride = true; + bool bExpectedWwiseSoundEngineEnabled = true; + bool bExpectedWwiseAudioLinkEnabled = false; + bool bExpectedAkAudioMixerEnabled = false; + FString ExpectedAudioDeviceModuleName; + FString ExpectedAudioMixerModuleName; + switch (AudioRouting) + { + case EAkUnrealAudioRouting::Custom: + UE_LOG(LogAkAudio, VeryVerbose, TEXT("OnAudioRoutingUpdate: Setting for Custom")); + bExpectedCustom = true; + break; + + case EAkUnrealAudioRouting::Separate: + UE_LOG(LogAkAudio, VeryVerbose, TEXT("OnAudioRoutingUpdate: Setting for Separate")); + bExpectedSeparate = true; + bExpectedUsingAudioMixer = true; + bExpectedAudioModuleOverride = false; + break; + + case EAkUnrealAudioRouting::EnableWwiseOnly: + UE_LOG(LogAkAudio, VeryVerbose, TEXT("OnAudioRoutingUpdate: Setting for DisableUnreal")); + bExpectedUsingAudioMixer = false; + ExpectedAudioDeviceModuleName = TEXT(""); + ExpectedAudioMixerModuleName = TEXT(""); + break; + + case EAkUnrealAudioRouting::EnableUnrealOnly: + UE_LOG(LogAkAudio, VeryVerbose, TEXT("OnAudioRoutingUpdate: Setting for DisableWwise")); + bExpectedSeparate = true; + bExpectedUsingAudioMixer = true; + bExpectedAudioModuleOverride = false; + bExpectedWwiseSoundEngineEnabled = false; + break; + + case EAkUnrealAudioRouting::AudioMixer: + UE_LOG(LogAkAudio, VeryVerbose, TEXT("OnAudioRoutingUpdate: Setting for AudioMixer")); + bExpectedUsingAudioMixer = true; + bExpectedAkAudioMixerEnabled = true; + ExpectedAudioDeviceModuleName = TEXT("AkAudioMixer"); + ExpectedAudioMixerModuleName = TEXT("AkAudioMixer"); + break; + + case EAkUnrealAudioRouting::AudioLink: + UE_LOG(LogAkAudio, VeryVerbose, TEXT("OnAudioRoutingUpdate: Setting for AudioLink")); + bExpectedSeparate = true; + bExpectedUsingAudioMixer = true; + bExpectedWwiseAudioLinkEnabled = true; + bExpectedAudioModuleOverride = false; + break; + + default: + UE_LOG(LogAkAudio, Warning, TEXT("OnAudioRoutingUpdate: Unknown AudioRouting")); + return; + } + + // + // Actually update the files + // + + UE_LOG(LogAkAudio, Verbose, TEXT("OnAudioRoutingUpdate: Updating system settings.")); + + { + bWwiseSoundEngineEnabled = bExpectedWwiseSoundEngineEnabled; + UE_LOG(LogAkAudio, Log, TEXT("OnAudioRoutingUpdate: Wwise SoundEngine Enabled: %s"), bExpectedWwiseSoundEngineEnabled ? TEXT("true") : TEXT("false")); + + bWwiseAudioLinkEnabled = bExpectedWwiseAudioLinkEnabled; + UE_LOG(LogAkAudio, Log, TEXT("OnAudioRoutingUpdate: Wwise AudioLink Enabled: %s"), bExpectedWwiseAudioLinkEnabled ? TEXT("true") : TEXT("false")); + + bAkAudioMixerEnabled = bExpectedAkAudioMixerEnabled; + UE_LOG(LogAkAudio, Log, TEXT("OnAudioRoutingUpdate: Wwise AudioMixer Enabled: %s"), bExpectedAkAudioMixerEnabled ? TEXT("true") : TEXT("false")); +#if UE_5_0_OR_LATER + TryUpdateDefaultConfigFile(); +#else + UpdateDefaultConfigFile(); +#endif + } + + TArray IniPlatformNames; + +#if UE_5_0_OR_LATER + for (const auto& PlatformInfo : FDataDrivenPlatformInfoRegistry::GetAllPlatformInfos()) + { + if (!PlatformInfo.Value.bIsFakePlatform) + { + IniPlatformNames.Add(PlatformInfo.Value.IniPlatformName.ToString()); + } + } +#else + for (const auto& Platform : GetTargetPlatformManagerRef().GetTargetPlatforms()) + { + IniPlatformNames.Add(Platform->IniPlatformName()); + } +#endif + for (const auto& IniPlatformName : IniPlatformNames) + { + const auto RelativePlatformEnginePath = FString::Printf(TEXT("%s/%sEngine.ini"), *IniPlatformName, *IniPlatformName); + auto PlatformEnginePath = FString::Printf(TEXT("%s%s"), *FPaths::SourceConfigDir(), *RelativePlatformEnginePath); + +#if UE_5_1_OR_LATER + PlatformEnginePath = FConfigCacheIni::NormalizeConfigIniPath(PlatformEnginePath); +#else + FPaths::RemoveDuplicateSlashes(PlatformEnginePath); + PlatformEnginePath = FPaths::CreateStandardFilename(PlatformEnginePath); +#endif + + const FString FullPlatformEnginePath = FPaths::ConvertRelativePathToFull(PlatformEnginePath); + + if (FPlatformFileManager::Get().GetPlatformFile().FileExists(*FullPlatformEnginePath)) + { + FText ErrorMessage; + + if (ISourceControlModule::Get().IsEnabled()) + { + if (SourceControlHelpers::CheckoutOrMarkForAdd(FullPlatformEnginePath, FText::FromString(FullPlatformEnginePath), NULL, ErrorMessage)) + { + ErrorMessage = FText(); + } + } + else if (!FPlatformFileManager::Get().GetPlatformFile().SetReadOnly(*FullPlatformEnginePath, false)) + { + ErrorMessage = FText::Format(LOCTEXT("FailedToMakeWritable", "Could not make {0} writable."), FText::FromString(FullPlatformEnginePath)); + } + + if (!ErrorMessage.IsEmpty()) + { + FNotificationInfo Info(ErrorMessage); + Info.ExpireDuration = 3.0; + + FSlateNotificationManager::Get().AddNotification(Info); + continue; + } + } + + if (bExpectedUsingAudioMixer) + { + UE_LOG(LogAkAudio, Log, TEXT("%s: Removing UseAudioMixer override"), *RelativePlatformEnginePath); + GConfig->RemoveKey(TEXT("Audio"), TEXT("UseAudioMixer"), PlatformEnginePath); + } + else + { + UE_LOG(LogAkAudio, Log, TEXT("%s: Updating UseAudioMixer to: %s"), *RelativePlatformEnginePath, bExpectedUsingAudioMixer ? TEXT("true") : TEXT("false")); + GConfig->SetBool(TEXT("Audio"), TEXT("UseAudioMixer"), bExpectedUsingAudioMixer, PlatformEnginePath); + } + + if (bExpectedAudioModuleOverride) + { + UE_LOG(LogAkAudio, Log, TEXT("%s: Updating AudioDeviceModuleName: %s"), *RelativePlatformEnginePath, ExpectedAudioDeviceModuleName.IsEmpty() ? TEXT("[empty]") : *ExpectedAudioDeviceModuleName); + UE_LOG(LogAkAudio, Log, TEXT("%s: Updating AudioMixerModuleName: %s"), *RelativePlatformEnginePath, ExpectedAudioMixerModuleName.IsEmpty() ? TEXT("[empty]") : *ExpectedAudioMixerModuleName); + GConfig->SetString(TEXT("Audio"), TEXT("AudioDeviceModuleName"), *ExpectedAudioDeviceModuleName, PlatformEnginePath); + GConfig->SetString(TEXT("Audio"), TEXT("AudioMixerModuleName"), *ExpectedAudioMixerModuleName, PlatformEnginePath); + } + else + { + UE_LOG(LogAkAudio, Log, TEXT("%s: Removing AudioDeviceModuleName override"), *RelativePlatformEnginePath); + UE_LOG(LogAkAudio, Log, TEXT("%s: Removing AudioMixerModuleName override"), *RelativePlatformEnginePath); + GConfig->RemoveKey(TEXT("Audio"), TEXT("AudioDeviceModuleName"), PlatformEnginePath); + GConfig->RemoveKey(TEXT("Audio"), TEXT("AudioMixerModuleName"), PlatformEnginePath); + } + + GConfig->Flush(false, PlatformEnginePath); + } +} + +void UAkSettings::RemoveSoundDataFromAlwaysStageAsUFS(const FString& SoundDataPath) +{ + bool changed = false; + + UProjectPackagingSettings* PackagingSettings = GetMutableDefault(); + + for (int32 i = PackagingSettings->DirectoriesToAlwaysStageAsUFS.Num() - 1; i >= 0; --i) + { + if (PackagingSettings->DirectoriesToAlwaysStageAsUFS[i].Path == SoundDataPath) + { + PackagingSettings->DirectoriesToAlwaysStageAsUFS.RemoveAt(i); + changed = true; + break; + } + } + + if (changed) + { + AkUnrealEditorHelper::SaveConfigFile(PackagingSettings); + } +} + +#endif // WITH_EDITOR + +const FAkAcousticTextureParams* UAkSettings::GetTextureParams(const uint32& shortID) const +{ + for (auto it = AcousticTextureParamsMap.CreateConstIterator(); it; ++it) + { + if (it.Value().shortID == shortID) + return AcousticTextureParamsMap.Find(it.Key()); + } + return nullptr; +} + +bool UAkSettings::ReverbRTPCsInUse() const +{ + return DecayRTPCInUse() || DampingRTPCInUse() || PredelayRTPCInUse(); +} + +bool UAkSettings::DecayRTPCInUse() const +{ + const bool validPath = !DecayEstimateRTPC.ToSoftObjectPath().ToString().IsEmpty(); + return validPath || !DecayEstimateName.IsEmpty(); +} + +bool UAkSettings::DampingRTPCInUse() const +{ + const bool validPath = !HFDampingRTPC.ToSoftObjectPath().ToString().IsEmpty(); + return validPath || !HFDampingName.IsEmpty(); +} + +bool UAkSettings::PredelayRTPCInUse() const +{ + const bool validPath = !TimeToFirstReflectionRTPC.ToSoftObjectPath().ToString().IsEmpty(); + return validPath || !TimeToFirstReflectionName.IsEmpty(); +} + +bool UAkSettings::GetAssociatedAcousticTexture(const UPhysicalMaterial* physMaterial, UAkAcousticTexture*& acousticTexture) const +{ + TSoftObjectPtr physMatPtr(physMaterial); + auto props = AkGeometryMap.Find(physMatPtr); + + if (!props) + return false; + + TSoftObjectPtr texturePtr = props->AcousticTexture; + acousticTexture = texturePtr.LoadSynchronous(); + return true; +} + +bool UAkSettings::GetAssociatedOcclusionValue(const UPhysicalMaterial* physMaterial, float& occlusionValue) const +{ + TSoftObjectPtr physMatPtr(physMaterial); + auto props = AkGeometryMap.Find(physMatPtr); + + if (!props) + return false; + + occlusionValue = props->OcclusionValue; + return true; +} + +void UAkSettings::GetAuxBusForDecayValue(float decay, UAkAuxBus*& auxBus) +{ + TArray decayKeys; + EnvironmentDecayAuxBusMap.GetKeys(decayKeys); + decayKeys.Sort(); + int numKeys = decayKeys.Num(); + if (numKeys > 0) + { + int i = numKeys - 1; + if (decay > decayKeys[i]) + { + auxBus = DefaultReverbAuxBus.LoadSynchronous(); + return; + } + while (i > 0 && decay <= decayKeys[i - 1]) + { + --i; + } + TSoftObjectPtr auxBusPtr = EnvironmentDecayAuxBusMap[decayKeys[i]]; + auxBus = auxBusPtr.LoadSynchronous(); + } + else + { + auxBus = DefaultReverbAuxBus.LoadSynchronous(); + } +} + +void UAkSettings::GetAudioInputEvent(UAkAudioEvent*& OutInputEvent) +{ + OutInputEvent = AudioInputEvent.LoadSynchronous(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSettingsPerUser.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSettingsPerUser.cpp new file mode 100644 index 0000000..4102bbd --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSettingsPerUser.cpp @@ -0,0 +1,87 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkSettingsPerUser.h" + +#include "AkAudioDevice.h" +#include "Misc/Paths.h" +#include "AkUnrealHelper.h" + +#if WITH_EDITOR +#include "AkUnrealEditorHelper.h" +#include "SettingsEditor/Public/ISettingsEditorModule.h" +#endif +////////////////////////////////////////////////////////////////////////// +// UAkSettingsPerUser + +UAkSettingsPerUser::UAkSettingsPerUser(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +#if WITH_EDITOR + WwiseWindowsInstallationPath.Path = FPlatformMisc::GetEnvironmentVariable(TEXT("WWISEROOT")); +#endif +} + +#if WITH_EDITOR +void UAkSettingsPerUser::PreEditChange(FProperty* PropertyAboutToChange) +{ + PreviousWwiseWindowsInstallationPath = WwiseWindowsInstallationPath.Path; + PreviousWwiseMacInstallationPath = WwiseMacInstallationPath.FilePath; + PreviousGeneratedSoundBanksFolder = GeneratedSoundBanksFolderUserOverride.Path; +} + +void UAkSettingsPerUser::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + const FName PropertyName = (PropertyChangedEvent.Property != nullptr) ? PropertyChangedEvent.Property->GetFName() : NAME_None; + const FName MemberPropertyName = (PropertyChangedEvent.Property != nullptr) ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None; + + if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettingsPerUser, WwiseWindowsInstallationPath)) + { + AkUnrealEditorHelper::SanitizePath(WwiseWindowsInstallationPath.Path, PreviousWwiseWindowsInstallationPath, FText::FromString("Please enter a valid Wwise Installation path")); + } + else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettingsPerUser, WwiseMacInstallationPath)) + { + AkUnrealEditorHelper::SanitizePath(WwiseMacInstallationPath.FilePath, PreviousWwiseMacInstallationPath, FText::FromString("Please enter a valid Wwise Authoring Mac executable path")); + } + else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettingsPerUser, bAutoConnectToWAAPI)) + { + OnAutoConnectToWaapiChanged.Broadcast(); + } + else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettingsPerUser, WaapiTranslatorTimeout)) + { + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (AkAudioDevice) + { + AkAudioDevice->SetLocalOutput(); + } + } + else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettingsPerUser, GeneratedSoundBanksFolderUserOverride)) + { + bool bPathChanged = AkUnrealEditorHelper::SanitizeFolderPathAndMakeRelativeToContentDir( + GeneratedSoundBanksFolderUserOverride.Path, PreviousGeneratedSoundBanksFolder, + FText::FromString("Please enter a valid directory path")); + + if (bPathChanged) + { + OnGeneratedSoundBanksPathChanged.Broadcast(); + } + OnGeneratedSoundBanksPathChanged.Broadcast(); + } + + Super::PostEditChangeProperty(PropertyChangedEvent); +} +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSpatialAudioDrawUtils.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSpatialAudioDrawUtils.cpp new file mode 100644 index 0000000..6a529a8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSpatialAudioDrawUtils.cpp @@ -0,0 +1,148 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkSpatialAudioDrawUtils.cpp: +=============================================================================*/ +#include "AkSpatialAudioDrawUtils.h" + +#if WITH_EDITOR + +#include "AkUEFeatures.h" +#include "AkAcousticPortal.h" +#include "AkSpatialAudioVolume.h" +#include "AkSurfaceReflectorSetComponent.h" + +AkDrawBounds::AkDrawBounds(const FTransform& T, const FVector& Extent) : Transform(T), BoxExtent(Extent) {} + +FVector AkDrawBounds::FRU() const { return Transform.TransformPosition(FVector(BoxExtent)); } +FVector AkDrawBounds::BLD() const { return Transform.TransformPosition(FVector(-BoxExtent)); } +FVector AkDrawBounds::FLD() const { return Transform.TransformPosition(FVector(BoxExtent.X, -BoxExtent.Y, -BoxExtent.Z)); } +FVector AkDrawBounds::BRU() const { return Transform.TransformPosition(FVector(-BoxExtent.X, BoxExtent.Y, BoxExtent.Z)); } +FVector AkDrawBounds::FLU() const { return Transform.TransformPosition(FVector(BoxExtent.X, -BoxExtent.Y, BoxExtent.Z)); } +FVector AkDrawBounds::BLU() const { return Transform.TransformPosition(FVector(-BoxExtent.X, -BoxExtent.Y, BoxExtent.Z)); } +FVector AkDrawBounds::FRD() const { return Transform.TransformPosition(FVector(BoxExtent.X, BoxExtent.Y, -BoxExtent.Z)); } +FVector AkDrawBounds::BRD() const { return Transform.TransformPosition(FVector(-BoxExtent.X, BoxExtent.Y, -BoxExtent.Z)); } +FVector AkDrawBounds::RU() const { return Transform.TransformPosition(FVector(0.0, BoxExtent.Y, BoxExtent.Z)); } +FVector AkDrawBounds::LU() const { return Transform.TransformPosition(FVector(0.0, -BoxExtent.Y, BoxExtent.Z)); } +FVector AkDrawBounds::RD() const { return Transform.TransformPosition(FVector(0.0, BoxExtent.Y, -BoxExtent.Z)); } +FVector AkDrawBounds::LD() const { return Transform.TransformPosition(FVector(0.0, -BoxExtent.Y, -BoxExtent.Z)); } + +namespace AkSpatialAudioColors +{ + float kAlphaValue = 0.35f; + float kDraggingAlphaValue = 0.05f; + + void GetPortalColors(const UAkPortalComponent* Portal, FLinearColor& FrontColor, FLinearColor& BackColor) + { + FLinearColor ConnectedColor = FAkAppStyle::Get().GetSlateColor("SelectionColor").GetSpecifiedColor(); + FrontColor = ConnectedColor; + BackColor = ConnectedColor; + if (!Portal->PortalPlacementValid()) + { + FLinearColor ErrorColor = FLinearColor::Red; + FrontColor = ErrorColor; + BackColor = ErrorColor; + } + else if (Portal->GetFrontRoomComponent() == nullptr) + { + FLinearColor DisconnectedColor = FLinearColor::Gray; + FrontColor = DisconnectedColor; + } + else if (Portal->GetBackRoomComponent() == nullptr) + { + FLinearColor DisconnectedColor = FLinearColor::Gray; + BackColor = DisconnectedColor; + } + FrontColor.A = kAlphaValue; + BackColor.A = kAlphaValue; + } + + FLinearColor GetPortalOutlineColor(const UAkPortalComponent* Portal) + { + FLinearColor OutlineColor = FAkAppStyle::Get().GetSlateColor("SelectionColor").GetSpecifiedColor(); + if (false == Portal->PortalPlacementValid()) + { + OutlineColor = FLinearColor::Red; + OutlineColor.A = 0.85f; + } + return OutlineColor; + } + + FLinearColor GetRoomColor() + { + return FAkAppStyle::Get().GetSlateColor("SelectionColor").GetSpecifiedColor(); + } + + FLinearColor GetRadialEmitterOutlineColor() + { + return FAkAppStyle::Get().GetSlateColor("SelectionColor").GetSpecifiedColor(); + } + + FLinearColor GetRadialEmitterColor() + { + return GetRadialEmitterOutlineColor(); + } + + float GetRadialEmitterInnerOpacity() { return kAlphaValue; } + + float GetRadialEmitterOuterOpacity() { return kAlphaValue * 0.15f; } + + FLinearColor GetSurfaceReflectorColor(const UAkSurfaceReflectorSetComponent* SurfaceReflectorSet, int NodeIdx, bool IsDragging) + { + const FLinearColor DefaultColor(FColor(0x4280AF)); + + FLinearColor Color = FLinearColor::Gray; + + FAkSurfacePoly AcousticProperties = SurfaceReflectorSet->AcousticPolys[NodeIdx]; + if (AcousticProperties.EnableSurface) + { + Color = DefaultColor; + + if (AcousticProperties.Texture != nullptr) + { + Color = AcousticProperties.Texture->EditColor; + } + } + + Color.A = IsDragging ? kDraggingAlphaValue : kAlphaValue; + + return Color; + } + + FLinearColor GetSpatialAudioVolumeOutlineColor() + { + return FAkAppStyle::Get().GetSlateColor("SelectionColor").GetSpecifiedColor(); + } + + FLinearColor GetBadFitSpatialAudioVolumeOutlineColor() + { + return FLinearColor::Red; + } + + FLinearColor GetDiffractionEdgeColor() + { + return FLinearColor(FColor(0x09558F)); + } + + FLinearColor GetBoundaryDiffractionEdgeColor() + { + return FLinearColor(FColor(0x480D97)); + } +} + +#endif // WITH_EDITOR diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSpatialAudioHelper.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSpatialAudioHelper.cpp new file mode 100644 index 0000000..bbb6b8d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSpatialAudioHelper.cpp @@ -0,0 +1,63 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkSpatialAudioHelper.h" + +#include "AkSpatialAudioVolume.h" +#include "AkAcousticPortal.h" + +namespace AkSpatialAudioHelper +{ + AActor* GetActorFromHitResult(const FHitResult& HitResult) + { + AActor* HitActor = nullptr; +#if UE_5_0_OR_LATER + HitActor = HitResult.HitObjectHandle.FetchActor(); +#else + HitActor = HitResult.Actor.Get(); +#endif + + return HitActor; + } + + bool IsAkSpatialAudioActorClass(const AActor* Actor) + { + if (Actor == nullptr) + return false; + + return + Actor->GetClass() == AAkSpatialAudioVolume::StaticClass() || + Actor->GetClass() == AAkAcousticPortal::StaticClass(); + } + +#if WITH_EDITOR + UEditorEngine::FObjectsReplacedEvent* GetObjectReplacedEvent() + { +#if UE_5_0_OR_LATER + return &FCoreUObjectDelegates::OnObjectsReplaced; +#else + if (GEditor) + { + return &GEditor->OnObjectsReplaced(); + } + + return nullptr; +#endif + } +#endif + +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSpatialAudioVolume.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSpatialAudioVolume.cpp new file mode 100644 index 0000000..98692d3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSpatialAudioVolume.cpp @@ -0,0 +1,856 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkSpatialAudioVolume.cpp: +=============================================================================*/ + +#include "AkSpatialAudioVolume.h" +#include "AkSpatialAudioHelper.h" +#include "AkAudioDevice.h" +#include "AkLateReverbComponent.h" +#include "AkRoomComponent.h" +#include "AkSurfaceReflectorSetComponent.h" +#include "Components/BrushComponent.h" +#include "Model.h" +#include "Engine/BrushBuilder.h" + +#include "AkAcousticPortal.h" +#include "AkSettings.h" + +// Geometric Tools +#if WITH_EDITOR +#include "AkAudioStyle.h" +#include "Mathematics/Math.h" +#include "Mathematics/UIntegerAP32.h" +#include "Mathematics/BSRational.h" +#include "Mathematics/MinimumVolumeBox3.h" +#if UE_5_1_OR_LATER +#include "Misc/TransactionObjectEvent.h" +#endif +#endif + +static const float kScaleEpsilon = 0.001; +static const float kConvexHullEpsilon = 0.001; +static const FName NAME_SAV_Fit = TEXT("AkSpatialAudioVolumeRaycast"); + +#if WITH_EDITOR +bool IntersectPlanes(FVector n0, float d0, FVector n1, float d1, FVector n2, float d2, FVector &p) +{ + FVector u = FVector::CrossProduct(n1, n2); + float denom = FVector::DotProduct(n0, u); + if (std::abs(denom) < 0.1) + return false; // Planes do not intersect in a point + + p = (d0*u + FVector::CrossProduct(n0, (d2 * n1) - (d1 * n2))) / denom; + return true; +} + +WwiseGTE::Vector3< float > ToGTEVector(FVector& In) +{ + WwiseGTE::Vector3< float > Out; + Out[0] = In.X; + Out[1] = In.Y; + Out[2] = In.Z; + return Out; +} + +FVector ToFVector(WwiseGTE::Vector3< float >& In) +{ + return FVector(In[0], In[1], In[2]); +} +#endif + +/*------------------------------------------------------------------------------------ + AAkSpatialAudioVolume +------------------------------------------------------------------------------------*/ + +AAkSpatialAudioVolume::AAkSpatialAudioVolume(const class FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer) +{ + // Property initialization + UBrushComponent* BrushComp = GetBrushComponent(); + if (BrushComp) + { + BrushComp->SetGenerateOverlapEvents(false); + BrushComp->SetCollisionEnabled(ECollisionEnabled::QueryOnly); + BrushComp->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic); + BrushComp->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); + } + + static const FName SurfReflSetName = TEXT("SurfaceReflector"); + SurfaceReflectorSet = ObjectInitializer.CreateDefaultSubobject(this, SurfReflSetName); + SurfaceReflectorSet->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + + static const FName LateReverbame = TEXT("LateReverb"); + LateReverb = ObjectInitializer.CreateDefaultSubobject(this, LateReverbame); + LateReverb->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + + static const FName RoomName = TEXT("Room"); + Room = ObjectInitializer.CreateDefaultSubobject(this, RoomName); + Room->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + + bColored = true; + BrushColor = FColor(109, 187, 255, 255); + +#if WITH_EDITOR + CollisionChannel = EAkCollisionChannel::EAKCC_UseIntegrationSettingsDefault; + + PrimaryActorTick.bCanEverTick = true; + PrimaryActorTick.bStartWithTickEnabled = true; + +#endif +} + +#if WITH_EDITOR +ECollisionChannel AAkSpatialAudioVolume::GetCollisionChannel() +{ + return UAkSettings::ConvertFitToGeomCollisionChannel(CollisionChannel.GetValue()); +} + +void AAkSpatialAudioVolume::FitRaycast() +{ + UWorld* World = GEngine->GetWorldFromContextObjectChecked(this); + if (!World) + return; + + TArray hits; + hits.Reserve(kNumRaycasts); + + const float kRayLength = 10000000.f; + + FCollisionQueryParams CollisionParams(NAME_SAV_Fit, true, this); + CollisionParams.bReturnPhysicalMaterial = true; + + FVector RaycastOrigin = GetActorLocation(); + + float Offset = 2.f / kNumRaycasts; + float Increment = PI * (3.f - sqrtf(5.f)); + + for (int i = 0; i < kNumRaycasts; ++i) + { + float y = ((i * Offset) - 1) + (Offset / 2); + float r = sqrtf(1.f - powf(y, 2.f)); + + float phi = ((i + 1) % kNumRaycasts) * Increment; + + float x = cosf(phi) * r; + float z = sinf(phi) * r; + + FVector to = RaycastOrigin + FVector(x, y, z) * kRayLength; + + TArray< FHitResult > OutHits; + OutHits.Empty(); + World->LineTraceMultiByObjectType(OutHits, RaycastOrigin, to, (int)GetCollisionChannel(), CollisionParams); + + for (auto& res : OutHits) + { + AActor* HitActor = AkSpatialAudioHelper::GetActorFromHitResult(res); + if (HitActor != nullptr) + { + UAkPortalComponent* PortalComponent = (UAkPortalComponent*)HitActor->FindComponentByClass(UAkPortalComponent::StaticClass()); + if (PortalComponent != nullptr) + { + // We hit a portal. The portals are a good reference point for the SAV, but we need to extend the ray to the center of the portal + FVector PortalNorm = PortalComponent->GetComponentTransform().GetRotation().RotateVector(FVector(0.f, 1.f, 0.f)); + FVector PortalPos = PortalComponent->GetComponentLocation(); + float d = FVector::DotProduct(PortalPos, PortalNorm); + FVector ab = to - res.ImpactPoint; + float t = (d - FVector::DotProduct(PortalNorm, res.ImpactPoint)) / FVector::DotProduct(PortalNorm, ab); + + if (t >= 0.f && t < 1.0f) + { + FVector ImpactPointOnPoralPlane = res.ImpactPoint + t * ab; + FHitResult ModifiedHitResult = res; + ModifiedHitResult.ImpactPoint = ImpactPointOnPoralPlane; + ModifiedHitResult.ImpactNormal = PortalNorm; + if (FVector::DotProduct(PortalNorm, res.ImpactNormal) < 0.f) + ModifiedHitResult.ImpactNormal = -PortalNorm; + hits.Emplace(ModifiedHitResult); + break; + } + } + + if (!res.bStartPenetrating && + HitActor->GetClass() == AAkSpatialAudioVolume::StaticClass()) + { + hits.Emplace(res); + break; + } + } + + if (res.IsValidBlockingHit() ) + { + hits.Emplace(res); + break; + } + } + } + + auto SortPredicate = [](FHitResult& A, FHitResult& B){ return A.Distance < B.Distance; }; + + Algo::Sort(hits, SortPredicate); + + FitPoints.Empty(); + FitPoints.Reserve(hits.Num()); + + FitMaterials.Empty(); + FitMaterials.Reserve(hits.Num()); + + FitNormals.Empty(); + FitNormals.Reserve(hits.Num()); + + for (int i = 0; i < hits.Num(); ++i) + { + FitPoints.Emplace(hits[i].ImpactPoint); + FitNormals.Emplace(hits[i].ImpactNormal); + FitMaterials.Emplace(hits[i].PhysMaterial); + } +} + +void AAkSpatialAudioVolume::PostRebuildBrush() +{ + UnregisterAllComponents(true); + RegisterAllComponents(); + + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (AkAudioDevice != nullptr) + AkAudioDevice->PortalsNeedRoomUpdate(GetWorld()); + + if (SurfaceReflectorSet != nullptr) + { + SurfaceReflectorSet->UpdatePolys(true); + SurfaceReflectorSet->UpdateSurfaceReflectorSet(); + } + + ClearTextComponents(); +} + +void AAkSpatialAudioVolume::ClearTextComponents() +{ + for (int i = 0; i < PreviewTextureNameComponents.Num(); ++i) + { + UTextRenderComponent* textComp = PreviewTextureNameComponents[i]; + if (textComp != nullptr) + { + textComp->DestroyComponent(); + } + } + PreviewTextureNameComponents.Empty(); +} + +void AAkSpatialAudioVolume::UpdatePreviewTextComponents(TArray positions) +{ + ClearTextComponents(); + int index = 0; + UMaterialInterface* mat = Cast(FAkAudioStyle::GetAkForegroundTextMaterial()); + ensure(positions.Num() == PreviewPolys.Num()); + for (FAkSurfacePoly& Poly : PreviewPolys) + { + FString VizName = GetName() + TEXT("PolyPreviewText ") + FString::FromInt(index); + if (Poly.EnableSurface) + { + int32 idx = PreviewTextureNameComponents.Add(NewObject(GetOuter(), *VizName)); + UTextRenderComponent* textComp = PreviewTextureNameComponents[idx]; + if (textComp != nullptr) + { + if (mat != nullptr) + textComp->SetTextMaterial(mat); + textComp->RegisterComponentWithWorld(GetWorld()); + textComp->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepWorldTransform); + textComp->bIsEditorOnly = true; + textComp->bSelectable = false; + textComp->bAlwaysRenderAsText = true; + textComp->SetHorizontalAlignment(EHTA_Center); + textComp->SetVerticalAlignment(EVRTA_TextCenter); + bool displayOcclusion = true; + if (SurfaceReflectorSet != nullptr) + displayOcclusion = SurfaceReflectorSet->bEnableSurfaceReflectors; + textComp->SetText(Poly.GetPolyText(displayOcclusion)); + textComp->SetWorldLocation(positions[index]); + } + } + else + { + PreviewTextureNameComponents.Add(nullptr); + } + ++index; + } +} + +void AAkSpatialAudioVolume::UpdatePreviewPolys(TArray, int>> materialVotes) +{ + PreviewPolys.Empty(); + PreviewPolys.AddDefaulted(materialVotes.Num()); + for (int i = 0; i < materialVotes.Num(); ++i) + { + PreviewPolys[i].EnableSurface = false; + TMap, int>& votes = materialVotes[i]; + // Tally the votes + if (votes.Num() > 0) + { + auto MaxVotes = *votes.begin(); + auto it = votes.begin(); + ++it; + + while (it != votes.end()) + { + if (it->Value > MaxVotes.Value) + MaxVotes = *it; + + ++it; + } + // Use the material with the max number of points. + if (MaxVotes.Key.IsValid()) + { + GetDefault()->GetAssociatedAcousticTexture(MaxVotes.Key.Get(), PreviewPolys[i].Texture); + GetDefault()->GetAssociatedOcclusionValue(MaxVotes.Key.Get(), PreviewPolys[i].Occlusion); + PreviewPolys[i].EnableSurface = true; + } + } + } +} + +void AddOrIncrementMaterialVote(TMap, int>& votes, TWeakObjectPtr& material) +{ + int* Count = votes.Find(material); + if (Count == nullptr) + { + Count = &votes.Add(material); + *Count = 0; + } + if (Count != nullptr) + { + *Count += 1; + }; +} + +void AAkSpatialAudioVolume::FitBox(bool bPreviewOnly) +{ + ClearTextComponents(); + LongestEdgeLength = 0.0f; + + if (FitPoints.Num() * FilterHitPoints == 0) + { + FitFailed = true; + return; + } + + static const float kExtent = 100.f; + + using MinimumVolumeBox3 = WwiseGTE::MinimumVolumeBox3>; + using OBB = WwiseGTE::OrientedBox3 < float >; + using Vector3 = WwiseGTE::Vector3< float >; + + PreviewOutline.Empty(); + const float kNormalAgreement = 0.866f; // ~30 degrees + if (Shape == EAkFitToGeometryMode::OrientedBox || + Shape == EAkFitToGeometryMode::AlignedBox ) + { + FTransform XF = GetActorTransform(); + + if (Shape == EAkFitToGeometryMode::OrientedBox) //allow rotation. + { + TArray Points; + Points.Reserve(FitPoints.Num()); + + for (int i = 0; i < FitPoints.Num() && i < FitPoints.Num()*FilterHitPoints; ++i) + { + Points.Emplace(ToGTEVector(FitPoints[i])); + } + + MinimumVolumeBox3 mvb(std::min(8U, std::thread::hardware_concurrency()-1), true); + OBB obb = mvb(Points.Num(), &Points[0], kConvexHullEpsilon); + + FVector Location(obb.center[0], obb.center[1], obb.center[2]); + FVector Front(obb.axis[1][0], obb.axis[1][1], obb.axis[1][2]); + FVector Top(obb.axis[2][0], obb.axis[2][1], obb.axis[2][2]); + FQuat Rotation = FRotationMatrix::MakeFromYZ(Front, Top).ToQuat(); + FVector Scale(obb.extent[0], obb.extent[1], obb.extent[2]); + + if (Scale.X > ::kScaleEpsilon && + Scale.Y > ::kScaleEpsilon && + Scale.Z > ::kScaleEpsilon) + { + LongestEdgeLength = Scale.GetAbsMax(); + // Compensate for the standard AAkSpatialAudioVolume, based on a cube brush with verts at [+/-]100 X,Y,Z. + Scale /= kExtent; + + XF.SetLocation(Location); + XF.SetRotation(Rotation); + XF.SetScale3D(Scale); + } + + TArray textPositions = TArray(); + textPositions.AddDefaulted(6); // Box face planes + // The material votes for each plane. + TArray, int>> MaterialVotes; + MaterialVotes.AddDefaulted(6); + for (int boxAxis = 0; boxAxis < 3; ++boxAxis) + { + int faceIndex = boxAxis * 2; + int oppositeFaceIndex = faceIndex + 1; + FVector axis(obb.axis[boxAxis][0], obb.axis[boxAxis][1], obb.axis[boxAxis][2]); + FVector faceCenter = Location + axis * obb.extent[boxAxis]; + FVector oppositeFaceCenter = Location - axis * obb.extent[boxAxis]; + textPositions[faceIndex] = faceCenter; + textPositions[oppositeFaceIndex] = oppositeFaceCenter; + for (int i = 0; i < FitPoints.Num() && i < FitPoints.Num() * FilterHitPoints; ++i) + { + // The sign on the axis is flipped in the comparisons because the normals face in towards the cuboid... + if (FVector::DotProduct(FitNormals[i], -axis) >= kNormalAgreement && FMath::IsNearlyZero(FVector::DotProduct(FitPoints[i] - faceCenter, axis), 0.01f)) + { + TMap, int>& votes = MaterialVotes[faceIndex]; + AddOrIncrementMaterialVote(votes, FitMaterials[i]); + } + if (FVector::DotProduct(FitNormals[i], axis) >= kNormalAgreement && FMath::IsNearlyZero(FVector::DotProduct(FitPoints[i] - oppositeFaceCenter, -axis), 0.01f)) + { + TMap, int>& votes = MaterialVotes[oppositeFaceIndex]; + AddOrIncrementMaterialVote(votes, FitMaterials[i]); + } + } + } + if (bPreviewOnly) + { + UpdatePreviewPolys(MaterialVotes); + UpdatePreviewTextComponents(textPositions); + } + } + else if (Shape == EAkFitToGeometryMode::AlignedBox) + { + USceneComponent* RC = GetRootComponent(); + if (RC) + { + FRotator Rotation = RC->GetComponentRotation(); + FVector Min(FLT_MAX, FLT_MAX, FLT_MAX); + FVector Max(-FLT_MAX, -FLT_MAX, -FLT_MAX); + + for (int i = 0; i < FitPoints.Num() && i < FitPoints.Num()*FilterHitPoints; ++i) + { + FVector PtWorld(FitPoints[i][0], FitPoints[i][1], FitPoints[i][2]); + FVector PtLocal = Rotation.UnrotateVector(PtWorld); + Min = Min.ComponentMin(PtLocal); + Max = Max.ComponentMax(PtLocal); + } + + FVector Scale = Max - Min; + if (Scale.X > ::kScaleEpsilon && + Scale.Y > ::kScaleEpsilon && + Scale.Z > ::kScaleEpsilon) + { + LongestEdgeLength = Scale.GetAbsMax(); + FVector Location = Rotation.RotateVector((Min + Max) / 2.f); + XF.SetLocation(Location); + XF.SetScale3D(Scale / (2.f*kExtent)); + } + + FVector centre(XF.GetLocation()); + FVector extent(Scale / 2.0f); + TArray textPositions = // Box face planes + { + centre + FVector(extent.X, 0.0f, 0.0f), centre - FVector(extent.X, 0.0f, 0.0f), + centre + FVector(0.0f, extent.Y, 0.0f), centre - FVector(0.0f, extent.Y, 0.0f), + centre + FVector(0.0f, 0.0f, extent.Z), centre - FVector(0.0f, 0.0f, extent.Z) + }; + TArray faceNormals = + { + FVector(1, 0, 0), FVector(-1, 0, 0), FVector(0, 1, 0), FVector(0, -1, 0), FVector(0, 0, 1), FVector(0, 0, -1) + }; + // The material votes for each plane. + TArray, int>> materialVotes; + materialVotes.AddDefaulted(6); + for (int axisIndex = 0; axisIndex < textPositions.Num(); ++axisIndex) + { + for (int i = 0; i < FitPoints.Num() && i < FitPoints.Num() * FilterHitPoints; ++i) + { + if (FVector::DotProduct(-FitNormals[i], faceNormals[axisIndex]) >= kNormalAgreement && FMath::IsNearlyZero(FVector::DotProduct(FitPoints[i] - textPositions[axisIndex], faceNormals[axisIndex]), 0.01f)) + { + TMap, int>& votes = materialVotes[axisIndex]; + AddOrIncrementMaterialVote(votes, FitMaterials[i]); + } + } + } + if (bPreviewOnly) + { + UpdatePreviewPolys(materialVotes); + UpdatePreviewTextComponents(textPositions); + } + } + } + + if (!bPreviewOnly && BrushBuilder) + { + BrushBuilder->BeginBrush(true, this->GetFName()); + + const FTransform& ActorTransform = GetActorTransform(); + + for (int32 i = -1; i < 2; i += 2) + for (int32 j = -1; j < 2; j += 2) + for (int32 k = -1; k < 2; k += 2) + BrushBuilder->Vertexv(ActorTransform.InverseTransformPosition( XF.TransformPosition(FVector(i*kExtent, j*kExtent, k*kExtent)) )); + + BrushBuilder->Poly4i(+1, 0, 1, 3, 2); + BrushBuilder->Poly4i(+1, 2, 3, 7, 6); + BrushBuilder->Poly4i(+1, 6, 7, 5, 4); + BrushBuilder->Poly4i(+1, 4, 5, 1, 0); + BrushBuilder->Poly4i(+1, 3, 1, 5, 7); + BrushBuilder->Poly4i(+1, 0, 2, 6, 4); + + BrushBuilder->EndBrush(GetWorld(), this); + + SetNeedRebuild(GetLevel()); + GEditor->RebuildAlteredBSP(); + + PostRebuildBrush(); + } + + + PreviewOutline.Emplace(TPair(XF.TransformPosition(FVector(+kExtent, +kExtent, -kExtent)), XF.TransformPosition(FVector(+kExtent, +kExtent, +kExtent)))); + PreviewOutline.Emplace(TPair(XF.TransformPosition(FVector(+kExtent, -kExtent, -kExtent)), XF.TransformPosition(FVector(+kExtent, -kExtent, +kExtent)))); + PreviewOutline.Emplace(TPair(XF.TransformPosition(FVector(-kExtent, +kExtent, -kExtent)), XF.TransformPosition(FVector(-kExtent, +kExtent, +kExtent)))); + PreviewOutline.Emplace(TPair(XF.TransformPosition(FVector(-kExtent, -kExtent, -kExtent)), XF.TransformPosition(FVector(-kExtent, -kExtent, +kExtent)))); + + PreviewOutline.Emplace(TPair(XF.TransformPosition(FVector(-kExtent, -kExtent, -kExtent)), XF.TransformPosition(FVector(-kExtent, +kExtent, -kExtent)))); + PreviewOutline.Emplace(TPair(XF.TransformPosition(FVector(+kExtent, -kExtent, -kExtent)), XF.TransformPosition(FVector(+kExtent, +kExtent, -kExtent)))); + PreviewOutline.Emplace(TPair(XF.TransformPosition(FVector(-kExtent, -kExtent, -kExtent)), XF.TransformPosition(FVector(+kExtent, -kExtent, -kExtent)))); + PreviewOutline.Emplace(TPair(XF.TransformPosition(FVector(-kExtent, +kExtent, -kExtent)), XF.TransformPosition(FVector(+kExtent, +kExtent, -kExtent)))); + + PreviewOutline.Emplace(TPair(XF.TransformPosition(FVector(-kExtent, -kExtent, +kExtent)), XF.TransformPosition(FVector(-kExtent, +kExtent, +kExtent)))); + PreviewOutline.Emplace(TPair(XF.TransformPosition(FVector(+kExtent, -kExtent, +kExtent)), XF.TransformPosition(FVector(+kExtent, +kExtent, +kExtent)))); + PreviewOutline.Emplace(TPair(XF.TransformPosition(FVector(-kExtent, -kExtent, +kExtent)), XF.TransformPosition(FVector(+kExtent, -kExtent, +kExtent)))); + PreviewOutline.Emplace(TPair(XF.TransformPosition(FVector(-kExtent, +kExtent, +kExtent)), XF.TransformPosition(FVector(+kExtent, +kExtent, +kExtent)))); + + } + else if (Shape == EAkFitToGeometryMode::ConvexPolyhedron) + { + static const int kMaxAllowedPointsBehindPlane = 1; // Allows for some leniency - we cant expect geometry to be completely convex. + static const float kDotEpsilon = 0.1f; // To determine if points are infront/behind a given plane. + static const float kDotThreshold = 0.866f; //~ 30 degrees, enough for a polygonal cross section with 12 sides. Used for comparing normals. + + using ConvexHull3 = ::WwiseGTE::ConvexHull3>; + using ETManifoldMesh = ::WwiseGTE::ETManifoldMesh; + + FVector Origin = GetActorLocation(); + + TArray Points; + Points.Reserve(FitPoints.Num()); + + TArray Normals; + // The material votes for each plane. + TArray, int>> MaterialVotes; + TArray Ds; + + for (int i = 0; i < FitPoints.Num()*FilterHitPoints; ++i) + { + // If the room is convex, every other point should be in front of the plane defined by the hit point and normal. + int PointsBehindPlane = 0; + for (int j = 0; j < FitPoints.Num()*FilterHitPoints; ++j) + { + if (i != j) + { + FVector ToPt = FitPoints[j] - FitPoints[i]; + ToPt.Normalize(); + float Proj = FVector::DotProduct(ToPt, FitNormals[i]); + if (Proj < -kDotEpsilon) + PointsBehindPlane++; + } + + } + + // But in practice we will allow 1 point to be behind. + if (PointsBehindPlane <= kMaxAllowedPointsBehindPlane) + { + // Calculate a representative plane for this point to be used in the convex hull algorithm. + float d = FVector::DotProduct(Origin - FitPoints[i], FitNormals[i]); + FVector HullPoint = Origin - FitNormals[i] * d; + FVector& Normal = FitNormals[i]; + TWeakObjectPtr& Material = FitMaterials[i]; + bool Found = false; + + for (int n = 0; n < Normals.Num(); ++n) + { + // Check to see if a plane with the same normal has already been found. + if (FVector::DotProduct(Normals[n], Normal) > kDotThreshold) + { + // If so, take the one with the largest d value. + Found = true; + if (Ds[n] < d) + { + Normals[n] = Normal; + Ds[n] = d; + Points[n] = ToGTEVector(HullPoint); + } + TMap, int>& votes = MaterialVotes[n]; + AddOrIncrementMaterialVote(votes, Material); + } + } + + if (!Found) + { + MaterialVotes.Add(TMap, int>()); + TMap, int>& votes = MaterialVotes.Last(); + AddOrIncrementMaterialVote(votes, Material); + Normals.Add(Normal); + Ds.Add(d); + Points.Emplace(ToGTEVector(HullPoint)); + } + } + } + + if (Points.Num() < 4) + { + FitFailed = true; + return; + } + + // Build a convex hull with the planes found from the raycasts. + ConvexHull3 ConvexHull; + if (ConvexHull(Points.Num(), &Points[0], kConvexHullEpsilon)) + { + ETManifoldMesh RoughMesh = ConvexHull.GetHullMesh(); + + //At this point the polyhedron mesh is missing 'corners'. Iterate through the triangles, checking the normals of the vertices. + TArray MeshPoints; + MeshPoints.Reserve(FitPoints.Num()); + + ETManifoldMesh::TMap RoughTriangles = RoughMesh.GetTriangles(); + for (auto& Triangle : RoughTriangles) + { + auto& Indexes = Triangle.second->V; + + FVector& n0 = Normals[Indexes[0]]; + FVector& n1 = Normals[Indexes[1]]; + FVector& n2 = Normals[Indexes[2]]; + + FVector p0 = ToFVector(Points[Indexes[0]]); + FVector p1 = ToFVector(Points[Indexes[1]]); + FVector p2 = ToFVector(Points[Indexes[2]]); + + float d0 = FVector::DotProduct(p0, n0); + float d1 = FVector::DotProduct(p1, n1); + float d2 = FVector::DotProduct(p2, n2); + + FVector CornerPoint; + if (IntersectPlanes(n0, d0, n1, d1, n2, d2, CornerPoint)) + { + // Punch out the corner + MeshPoints.Emplace(ToGTEVector(CornerPoint)); + } + else + { + FitFailed = true; + return; + } + } + + // Now generate a convex hull with the new corner points. + ConvexHull3 ConvexPolyhedron; + if (ConvexPolyhedron(MeshPoints.Num(), &MeshPoints[0], kConvexHullEpsilon)) + { + ETManifoldMesh Mesh = ConvexPolyhedron.GetHullMesh(); + + // Build a new brush with the polyhedron mesh. + if (!bPreviewOnly && + BrushBuilder) + { + BrushBuilder->BeginBrush(true, this->GetFName()); + + FVector Location = GetActorLocation(); + + for (int p = 0; p < ConvexPolyhedron.GetNumPoints(); ++p) + { + const Vector3& Vert = ConvexPolyhedron.GetPoints()[p]; + BrushBuilder->Vertex3f( Vert[0] - Location.X, + Vert[1] - Location.Y, + Vert[2] - Location.Z); + } + + + ETManifoldMesh::TMap Triangles = Mesh.GetTriangles(); + + for (auto& Triangle : Triangles) + { + auto& Indexes = Triangle.second->V; + BrushBuilder->Poly3i(+1, Indexes[0], Indexes[1], Indexes[2]); + } + + BrushBuilder->EndBrush(GetWorld(), this); + + auto* RC = GetRootComponent(); + if (RC) + { + RC->SetWorldRotation(FQuat::Identity); + RC->SetWorldScale3D(FVector::OneVector); + } + + SetNeedRebuild(GetLevel()); + GEditor->RebuildAlteredBSP(); + + PostRebuildBrush(); + } + + // Add the edges to the preview outline - skipping the edges that are 'flat'. + ETManifoldMesh::EMap Edges = Mesh.GetEdges(); + for (auto& E : Edges) + { + auto& Edge = *E.second; + auto T0 = Edge.T[0].lock(); + auto T1 = Edge.T[1].lock(); + if (T0 != nullptr && T1 != nullptr) + { + FVector N0 = FVector::CrossProduct(ToFVector(MeshPoints[T0->V[1]]) - ToFVector(MeshPoints[T0->V[0]]), ToFVector(MeshPoints[T0->V[2]]) - ToFVector(MeshPoints[T0->V[0]])); + FVector N1 = FVector::CrossProduct(ToFVector(MeshPoints[T1->V[1]]) - ToFVector(MeshPoints[T1->V[0]]), ToFVector(MeshPoints[T1->V[2]]) - ToFVector(MeshPoints[T1->V[0]])); + N0.Normalize(); + N1.Normalize(); + if (FVector::DotProduct(N0, N1) < kDotThreshold) + { + PreviewOutline.Add(TPair(ToFVector(MeshPoints[Edge.V[0]]), ToFVector(MeshPoints[Edge.V[1]]))); + float edgeLength = (PreviewOutline.Last().Value - PreviewOutline.Last().Key).Size(); + if (edgeLength > LongestEdgeLength) + LongestEdgeLength = edgeLength; + } + } + } + } + } + + if (bPreviewOnly) + { + UpdatePreviewPolys(MaterialVotes); + TArray textPositions = TArray(); + textPositions.AddDefaulted(Points.Num()); + for (int posIndex = 0; posIndex < Points.Num(); ++posIndex) + { + textPositions[posIndex] = ToFVector(Points[posIndex]); + } + UpdatePreviewTextComponents(textPositions); + } + } + else + { + check(false); + } + + // Map physics materials to surfaces. + if ((!bPreviewOnly) && SurfaceReflectorSet) + { + SurfaceReflectorSet->AssignAcousticTexturesFromSamples(FitPoints, FitNormals, FitMaterials, std::min(FitPoints.Num(), (int32)(FitPoints.Num()*FilterHitPoints))); + } + + FitFailed = false; +} + +bool AAkSpatialAudioVolume::ShouldTickIfViewportsOnly() const +{ + return bBrushNeedsRebuild || ((GetBounds() != PreviousBounds) && PreviousBounds.SphereRadius != 0.0f); +} + +void AAkSpatialAudioVolume::Tick(float DeltaSeconds) +{ + if (ShouldTickIfViewportsOnly()) + { + SetNeedRebuild(GetLevel()); + GEditor->RebuildAlteredBSP(); + PostRebuildBrush(); + PreviousBounds = GetBounds(); + bBrushNeedsRebuild = false; + } +} + +void AAkSpatialAudioVolume::PostTransacted(const FTransactionObjectEvent& TransactionEvent) +{ + Super::PostTransacted(TransactionEvent); + const TArray& ChangedProperties = TransactionEvent.GetChangedProperties(); + if (TransactionEvent.GetEventType() == ETransactionObjectEventType::UndoRedo && ChangedProperties.Contains(FName("FitToGeometry"))) + { + bBrushNeedsRebuild = true; + } +} + +void AAkSpatialAudioVolume::PostEditMove(bool bFinished) +{ + Super::PostEditMove(bFinished); + + IsDragging = !bFinished; + + if (FitToGeometry) + { + FitRaycast(); + + if (bFinished && Shape == EAkFitToGeometryMode::AlignedBox) + { + USceneComponent* RC = GetRootComponent(); + if (RC) + SavedRotation = RC->GetComponentRotation(); + } + + FitBox(!bFinished); + } +} + +void AAkSpatialAudioVolume::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + IsDragging = PropertyChangedEvent.ChangeType == EPropertyChangeType::Interactive; + + if (PropertyChangedEvent.Property) + { + if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(AAkSpatialAudioVolume, FitToGeometry)) + { + if (FitToGeometry) + { + FitRaycast(); + FitBox(); + } + else + { + FitPoints.Empty(); + } + } + + if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(AAkSpatialAudioVolume, FilterHitPoints)) + { + if (FitToGeometry) // only fit box continuously on value set for performance reasons. + { + FitBox(PropertyChangedEvent.ChangeType != EPropertyChangeType::ValueSet); + } + } + + if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(AAkSpatialAudioVolume, Shape)) + { + USceneComponent* RC = GetRootComponent(); + if (RC) + { + if (Shape != EAkFitToGeometryMode::AlignedBox) + { + // We just disabled 'ConstrainRotation'. Save the old rotation. + SavedRotation = RC->GetComponentRotation(); + } + else + { + // We just enabled 'ConstrainRotation'. Restore the old rotation. + RC->SetWorldRotation(SavedRotation); + } + } + + FitBox(); + } + } +} + +#endif // WITH_EDITOR \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSpotReflector.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSpotReflector.cpp new file mode 100644 index 0000000..6c6f6e4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSpotReflector.cpp @@ -0,0 +1,190 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkSpotReflector.h" +#include "AkAudioDevice.h" +#include "AkComponent.h" +#include "AkAuxBus.h" +#include "AkRoomComponent.h" +#include "Engine/Texture2D.h" +#include "Components/BillboardComponent.h" + +AAkSpotReflector::WorldToSpotReflectorsMap AAkSpotReflector::sWorldToSpotReflectors; + +// Sets default values +AAkSpotReflector::AAkSpotReflector(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , AcousticTexture(NULL) + , DistanceScalingFactor(2.f) + , Level(1.f) +{ + static const FName ComponentName = TEXT("SpotReclectorRootComponent"); + RootComponent = ObjectInitializer.CreateDefaultSubobject(this, ComponentName); + +#if WITH_EDITORONLY_DATA + static const FName SpriteComponentName = TEXT("Sprite"); + SpriteComponent = CreateEditorOnlyDefaultSubobject(SpriteComponentName); + if (SpriteComponent) + { + SpriteComponent->SetSprite(LoadObject(NULL, TEXT("/Wwise/S_AkSpotReflector.S_AkSpotReflector"))); + SpriteComponent->SetRelativeScale3D(FVector(0.5f, 0.5f, 0.5f)); + SpriteComponent->SetupAttachment(RootComponent); + } +#endif + + // AActor properties + SetHidden(true); + SetCanBeDamaged(false); +} + +void AAkSpotReflector::PostInitializeComponents() +{ + Super::PostInitializeComponents(); + + AddToWorld(); +} + +void AAkSpotReflector::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + RemoveFromWorld(); +} + +void AAkSpotReflector::AddToWorld() +{ + UWorld* world = GetWorld(); + if (world) + { + SpotReflectorSet& SRSet = sWorldToSpotReflectors.FindOrAdd(world); + SRSet.Add(this); + } +} + +void AAkSpotReflector::RemoveFromWorld() +{ + UWorld* world = GetWorld(); + if (world) + { + SpotReflectorSet* pSRSet = sWorldToSpotReflectors.Find(world); + if (pSRSet) + { + pSRSet->Remove(this); + + if (pSRSet->Num() == 0) + { + sWorldToSpotReflectors.Remove(world); + } + } + } +} + +AkImageSourceID AAkSpotReflector::GetImageSourceID() const +{ + return (AkImageSourceID)(uint64)this; +} + +AkAuxBusID AAkSpotReflector::GetAuxBusID() const +{ + if (EarlyReflectionAuxBus || !EarlyReflectionAuxBusName.IsEmpty()) + { + return FAkAudioDevice::GetShortID(EarlyReflectionAuxBus, EarlyReflectionAuxBusName); + } + else + { + // No early reflection aux bus is set. The one assigned in the Wwise Authoring Tool will be used instead. + // Skipping call to FAkAudioDevice::GetShortID() to avoid warning. + return AK_INVALID_UNIQUE_ID; + } +} + +void AAkSpotReflector::SetImageSource(UAkComponent* AkComponent) +{ + FAkAudioDevice* pDev = FAkAudioDevice::Get(); + if (!pDev) + return; + + const auto& RootTransform = RootComponent->GetComponentTransform(); + + if (SameRoomOnly) + { + AkRoomID roomID; + if (EnableRoomOverride) + { + if (RoomOverride != nullptr) + { + UAkRoomComponent* room = Cast(RoomOverride->GetComponentByClass(UAkRoomComponent::StaticClass())); + if (room != nullptr) + roomID = room->GetRoomID(); + } + } + else + { + TArray AkRooms = pDev->FindRoomComponentsAtLocation(RootTransform.GetTranslation(), GetWorld()); + if (AkRooms.Num() > 0) + roomID = AkRooms[0]->GetRoomID(); + } + + if (roomID != AkComponent->GetSpatialAudioRoom()) + return; + } + + AkImageSourceSettings sourceInfo = AkImageSourceSettings( + FAkAudioDevice::FVectorToAKVector64(RootTransform.GetTranslation()), + DistanceScalingFactor, Level); + + if (AcousticTexture) + { + sourceInfo.SetOneTexture(AcousticTexture->GetShortID()); + } + + pDev->SetImageSource(this, sourceInfo, GetAuxBusID(), AkComponent); +} + +void AAkSpotReflector::UpdateSpotReflectors(UAkComponent* AkComponent) +{ + FAkAudioDevice* pDev = FAkAudioDevice::Get(); + if (pDev) + { + pDev->ClearImageSources(AK_INVALID_AUX_ID, AkComponent); + + if (AkComponent->EnableSpotReflectors) + { + UWorld* world = AkComponent->GetWorld(); + if (world) + { + SpotReflectorSet* pSRSet = sWorldToSpotReflectors.Find(world); + if (pSRSet) + { + for (auto It = pSRSet->CreateIterator(); It; ++It) + { + (*It)->SetImageSource(AkComponent); + } + } + } + } + } +} + +const FString AAkSpotReflector::GetSpotReflectorName() const +{ +#if WITH_EDITOR + return GetActorLabel(); +#else + return GetName(); +#endif +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkStateValue.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkStateValue.cpp new file mode 100644 index 0000000..6673bb9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkStateValue.cpp @@ -0,0 +1,180 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkStateValue.h" + +#include "Wwise/WwiseResourceLoader.h" +#include "Wwise/Stats/AkAudio.h" + +#if WITH_EDITORONLY_DATA +#include "Wwise/WwiseProjectDatabase.h" +#include "Wwise/WwiseResourceCooker.h" +#include "AkAudioDevice.h" +#endif + +void UAkStateValue::LoadGroupValue() +{ + SCOPED_AKAUDIO_EVENT_2(TEXT("UAkStateValue::LoadGroupValue")); + auto* ResourceLoader = FWwiseResourceLoader::Get(); + if (UNLIKELY(!ResourceLoader)) + { + return; + } + + if (LoadedGroupValue) + { + UnloadGroupValue(false); + } + +#if WITH_EDITORONLY_DATA + if (IWwiseProjectDatabaseModule::IsInACookingCommandlet()) + { + return; + } + auto* ProjectDatabase = FWwiseProjectDatabase::Get(); + if (!ProjectDatabase || !ProjectDatabase->IsProjectDatabaseParsed()) + { + UE_LOG(LogAkAudio, VeryVerbose, TEXT("UAkStateValue::LoadGroupValue: Not loading '%s' because project database is not parsed."), *GetName()) + return; + } + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + return; + } + if (UNLIKELY(!ResourceCooker->PrepareCookedData(GroupValueCookedData, GetValidatedInfo(GroupValueInfo), EWwiseGroupType::State))) + { + return; + } +#endif + LoadedGroupValue = ResourceLoader->LoadGroupValue(GroupValueCookedData); +} + +void UAkStateValue::Serialize(FArchive& Ar) +{ + Super::Serialize(Ar); + + if (HasAnyFlags(RF_ClassDefaultObject)) + { + return; + } +#if !UE_SERVER +#if WITH_EDITORONLY_DATA + if (Ar.IsCooking() && Ar.IsSaving() && !Ar.CookingTarget()->IsServerOnly()) + { + FWwiseGroupValueCookedData CookedDataToArchive; + if (auto* ResourceCooker = FWwiseResourceCooker::GetForArchive(Ar)) + { + ResourceCooker->PrepareCookedData(CookedDataToArchive, GetValidatedInfo(GroupValueInfo), EWwiseGroupType::State); + } + CookedDataToArchive.Serialize(Ar); + } +#else + GroupValueCookedData.Serialize(Ar); +#endif +#endif +} + +#if WITH_EDITORONLY_DATA +void UAkStateValue::FillInfo() +{ + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkStateValue::FillInfo: ResourceCooker not initialized")); + return; + } + + auto ProjectDatabase = ResourceCooker->GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkStateValue::FillInfo: ProjectDatabase not initialized")); + return; + } + + FWwiseGroupValueInfo* AudioTypeInfo = static_cast(GetInfoMutable()); + FWwiseRefState RefState = FWwiseDataStructureScopeLock(*ProjectDatabase).GetState( + GetValidatedInfo(*AudioTypeInfo)); + + if (RefState.StateName().IsNone() || !RefState.StateGuid().IsValid() || RefState.StateId() == AK_INVALID_UNIQUE_ID) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkStateValue::FillInfo: Valid object not found in Project Database")); + return; + } + + AudioTypeInfo->WwiseName = RefState.StateName(); + AudioTypeInfo->WwiseGuid = RefState.StateGuid(); + AudioTypeInfo->WwiseShortId = RefState.StateId(); + AudioTypeInfo->GroupShortId = RefState.StateGroupId(); +} + +void UAkStateValue::FillInfo(const FWwiseAnyRef& CurrentWwiseRef) +{ + FWwiseGroupValueInfo* AudioTypeInfo = static_cast(GetInfoMutable()); + + AudioTypeInfo->WwiseName = CurrentWwiseRef.GetName(); + AudioTypeInfo->WwiseGuid = CurrentWwiseRef.GetGuid(); + AudioTypeInfo->WwiseShortId = CurrentWwiseRef.GetId(); + AudioTypeInfo->GroupShortId = CurrentWwiseRef.GetGroupId(); +} + +FName UAkStateValue::GetWwiseGroupName() +{ + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkStateValue::FillInfo: ResourceCooker not initialized")); + return {}; + } + + auto ProjectDatabase = ResourceCooker->GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkStateValue::FillInfo: ProjectDatabase not initialized")); + return {}; + } + + FWwiseGroupValueInfo* AudioTypeInfo = static_cast(GetInfoMutable()); + FWwiseRefState RefState = FWwiseDataStructureScopeLock(*ProjectDatabase).GetState( + GetValidatedInfo(*AudioTypeInfo)); + + return RefState.StateGroupName(); +} + +bool UAkStateValue::ObjectIsInSoundBanks() +{ + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkStateValue::GetWwiseRef: ResourceCooker not initialized")); + return false; + } + + auto ProjectDatabase = ResourceCooker->GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkStateValue::GetWwiseRef: ProjectDatabase not initialized")); + return false; + } + + FWwiseGroupValueInfo* AudioTypeInfo = static_cast(GetInfoMutable()); + FWwiseRefState RefState = FWwiseDataStructureScopeLock(*ProjectDatabase).GetState( + GetValidatedInfo(*AudioTypeInfo)); + + return RefState.IsValid(); +} +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSubmixInputComponent.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSubmixInputComponent.cpp new file mode 100644 index 0000000..83888ac --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSubmixInputComponent.cpp @@ -0,0 +1,162 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= +AkSubmixInputComponent.cpp: +=============================================================================*/ + +#include "AkSubmixInputComponent.h" +#include "AkAudioDevice.h" +#include "AudioMixerDevice.h" + +#include + +UAkSubmixInputComponent::UAkSubmixInputComponent(const class FObjectInitializer& ObjectInitializer) : + UAkAudioInputComponent(ObjectInitializer) +{} + +Audio::FMixerDevice* UAkSubmixInputComponent::GetAudioMixerDevice() +{ + UWorld* ThisWorld = GetWorld(); + if (!ThisWorld || !ThisWorld->bAllowAudioPlayback || ThisWorld->GetNetMode() == NM_DedicatedServer) + { + return nullptr; + } + + if (FAudioDevice* AudioDevice = ThisWorld->GetAudioDevice().GetAudioDevice()) + { + if (!AudioDevice->IsAudioMixerEnabled()) + { + return nullptr; + } + else + { + return static_cast(AudioDevice); + } + } + return nullptr; +} + +int32 UAkSubmixInputComponent::PostAssociatedAudioInputEvent() +{ + if (PlayingID == AK_INVALID_PLAYING_ID) + { + Audio::FMixerDevice* AudioMixerDevice = GetAudioMixerDevice(); + if (AudioMixerDevice) + { + NumChannels = AudioMixerDevice->GetNumDeviceChannels(); + SampleRate = AudioMixerDevice->GetDeviceSampleRate(); + BufferLength = AudioMixerDevice->GetBufferLength(); + SampleBuffer.SetCapacity(2 * BufferLength * NumChannels); + PoppedSamples.Empty(); + PoppedSamples.AddUninitialized(BufferLength * NumChannels); + AudioMixerDevice->RegisterSubmixBufferListener(this, SubmixToRecord); + } + else + { + UE_LOG(LogAkAudio, Warning, TEXT("AkSubmixInputComponent::PostAssociatedAudioInputEvent (%s): No Audio Mixer Device available, AkSubMixInputComponent cannot work."), *GetOwner()->GetName()); + return PlayingID; + } + + PlayingID = Super::PostAssociatedAudioInputEvent(); + } + else + { + UE_LOG(LogAkAudio, Log, TEXT("AkSubmixInputComponent (%s): Event was already posted."), *GetOwner()->GetName()); + } + return PlayingID; +} + +void UAkSubmixInputComponent::Stop() +{ + Audio::FMixerDevice* AudioMixerDevice = GetAudioMixerDevice(); + if (AudioMixerDevice) + { + AudioMixerDevice->UnregisterSubmixBufferListener(this, SubmixToRecord); + } + Super::Stop(); + PlayingID = AK_INVALID_PLAYING_ID; +} + +bool UAkSubmixInputComponent::FillSamplesBuffer(uint32 InNumChannels, uint32 InNumSamples, float** InOutBufferToFill) +{ + check(InNumChannels == NumChannels); + if (SampleBuffer.Num() >= (InNumChannels * InNumSamples)) + { + auto NumPopped = SampleBuffer.Pop(PoppedSamples.GetData(), InNumChannels * InNumSamples); + if (NumPopped == InNumChannels * InNumSamples) + { + for (uint32 Channel = 0; Channel < InNumChannels; Channel++) + { + for (uint32 Sample = 0; Sample < InNumSamples; Sample++) + { + InOutBufferToFill[Channel][Sample] = PoppedSamples[((NumChannels * Sample) + Channel)]; + } + } + + return true; + } + } + + for (uint32 Channel = 0; Channel < InNumChannels; Channel++) + { + FMemory::Memset(InOutBufferToFill[Channel], 0, InNumSamples * sizeof(float)); + } + return true; +} + +void UAkSubmixInputComponent::GetChannelConfig(AkAudioFormat& AudioFormat) +{ + Audio::FMixerDevice* AudioMixerDevice = GetAudioMixerDevice(); + if (!AudioMixerDevice) + { + UE_LOG(LogAkAudio, Error, TEXT("AkSubmixInputComponent::GetChannelConfig (%s): No Audio Mixer Device available, AkSubMixInputComponent cannot work."), *GetOwner()->GetName()); + return; + } + + AudioFormat.uSampleRate = SampleRate; + switch (NumChannels) + { + case 8: + AudioFormat.channelConfig.SetStandard(AK_SPEAKER_SETUP_7POINT1); + break; + case 6: + AudioFormat.channelConfig.SetStandard(AK_SPEAKER_SETUP_5POINT1); + break; + case 2: + AudioFormat.channelConfig.SetStandard(AK_SPEAKER_SETUP_STEREO); + break; + case 1: + AudioFormat.channelConfig.SetStandard(AK_SPEAKER_SETUP_MONO); + break; + default: + UE_LOG(LogAkAudio, Error, TEXT("AkSubmixInputComponent::GetChannelConfig (%s): Unknown number of channels (%" PRIu32 ")"), *GetOwner()->GetName(), NumChannels); + } +} + +void UAkSubmixInputComponent::OnNewSubmixBuffer( + const USoundSubmix* InOwningSubmix, + float* InAudioData, + int32 InNumSamples, + int32 InNumChannels, + const int32 InSampleRate, + double InAudioClock) +{ + check(InNumChannels == NumChannels); + check(InSampleRate == SampleRate); + SampleBuffer.Push(InAudioData, InNumSamples); +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSurfaceReflectorSetComponent.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSurfaceReflectorSetComponent.cpp new file mode 100644 index 0000000..a9356c8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSurfaceReflectorSetComponent.cpp @@ -0,0 +1,1370 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkSurfaceReflectorSetComponent.h" + +#include "AkAudioDevice.h" +#include "AkComponentHelpers.h" +#include "AkReverbDescriptor.h" +#include "AkRoomComponent.h" +#include "AkSettings.h" +#include "AkSpatialAudioVolume.h" +#include "AkUEFeatures.h" +#include "Components/BrushComponent.h" + +#include "EngineUtils.h" +#include "Model.h" +#include "GameFramework/Volume.h" +#include "UObject/Object.h" + +#if WITH_EDITOR +#include "AkAudioStyle.h" + +#include "Editor.h" +#include "EditorGeometry.h" +#include "EditorModeManager.h" +#include "EditorModes.h" +#include "EditorModeTools.h" +#include "GeometryEdMode.h" +#include "LevelEditorViewport.h" +#include "Kismet/KismetMathLibrary.h" +#endif + +#if WITH_EDITOR + +struct FFacePlane +{ + FFacePlane(FVector InOrigin, FVector InUp, FVector InRight) + : Origin(InOrigin) + , Up(InUp) + , Right(InRight) + {} + + /* Transform a point in world space to coordinates on the plane. */ + FVector2D WorldToPlaneCoordinates(FVector WorldPoint) const + { + float X = FVector::DotProduct((WorldPoint - Origin), Right); + float Y = FVector::DotProduct((WorldPoint - Origin), Up); + return FVector2D(X, Y); + } + + /* Transform an edge in world space to an edge defined by plane coordinates. */ + void WorldToPlaneEdge(FVector WorldStart, FVector WorldEnd, FVector2D& PlaneStart, FVector2D& PlaneEnd, float ExtendAmount = 0.0f) const + { + PlaneStart = WorldToPlaneCoordinates(WorldStart); + PlaneEnd = WorldToPlaneCoordinates(WorldEnd); + if (ExtendAmount > 0.0f) + { + PlaneStart += (PlaneStart - PlaneEnd).GetSafeNormal() * ExtendAmount; + PlaneEnd += (PlaneEnd - PlaneStart).GetSafeNormal() * ExtendAmount; + } + } + + FVector Origin = FVector::ZeroVector; + FVector Up = FVector::UpVector; + FVector Right = FVector::RightVector; +}; + +namespace TextAlignmentHelpers +{ + /* Get the four edges of the text render component in world space. */ + TArray GetTextEdges(const UTextRenderComponent& TextComp, const FVector& TextBottomLeft, const FFacePlane& FacePlane, const float scale) + { + TArray Edges; + FVector LocalTextSize = TextComp.GetTextLocalSize(); + const float TextSizeVer = LocalTextSize.Z * scale; + const float TextSizeHor = LocalTextSize.Y * scale; + const FVector BottomRight = TextBottomLeft + TextSizeHor * FacePlane.Right; + const FVector TopLeft = TextBottomLeft + TextSizeVer * FacePlane.Up; + const FVector TopRight = TopLeft + TextSizeHor * FacePlane.Right; + Edges.Add(FAkSurfaceEdgeInfo(TextBottomLeft, BottomRight)); + Edges.Add(FAkSurfaceEdgeInfo(TopLeft, TopRight)); + Edges.Add(FAkSurfaceEdgeInfo(TextBottomLeft, TopLeft)); + Edges.Add(FAkSurfaceEdgeInfo(BottomRight, TopRight)); + return Edges; + } + + FVector From2D(const FVector2D& Vec2D) + { + return FVector(Vec2D.X, Vec2D.Y, 0.0f); + } + + /* Query where BrushEdge bisects TextEdge. BisectionRatio indicates the length ratio at which TextEdge is intersected by BrushEdge. + This is used to determine the scaling factor for the text visualizers on each face. */ + void GetTextEdgeBisection(const FAkSurfaceEdgeInfo& TextEdge, const FAkSurfaceEdgeInfo& BrushEdge, const FFacePlane& Plane, float& BisectionRatio) + { + BisectionRatio = 0.0f; + const float Tolerance = 0.1f; + FVector2D TextEdgeStart2D, TextEdgeEnd2D, BrushEdgeStart2D, BrushEdgeEnd2D; + Plane.WorldToPlaneEdge(TextEdge.V0(), TextEdge.V1(), TextEdgeStart2D, TextEdgeEnd2D); + Plane.WorldToPlaneEdge(BrushEdge.V0(), BrushEdge.V1(), BrushEdgeStart2D, BrushEdgeEnd2D, 2.0f); + FVector Intersection = FVector::ZeroVector; + // If TextEdge and BrushEdge are almost parallel, record no intersection. + if (FMath::Abs((FVector2D::DotProduct((BrushEdgeEnd2D - BrushEdgeStart2D).GetSafeNormal(), (TextEdgeEnd2D - TextEdgeStart2D).GetSafeNormal()))) >= 0.95f) + return; + + if (FMath::SegmentIntersection2D(From2D(TextEdgeStart2D), From2D(TextEdgeEnd2D), From2D(BrushEdgeStart2D), From2D(BrushEdgeEnd2D), Intersection)) + { + // Ignore intersection with the first vertex of TextEdge + if (Intersection.Equals(From2D(TextEdgeStart2D), Tolerance)) + return; + + BisectionRatio = (Intersection - From2D(TextEdgeStart2D)).Size() / (TextEdgeEnd2D - TextEdgeStart2D).Size(); + } + } + + /* Use Tangent and Normal to determine the up vector. Uses the viewport client orientation to align up with camera. */ + FVector DetermineUpVector(const FVector& Tangent, FVector& Normal, const FVector& CamToCentre) + { + if (GCurrentLevelEditingViewportClient != nullptr) + { + if (FVector::DotProduct(CamToCentre, Normal) < 0.0f) + { + Normal *= -1.0f; + } + } + + FVector Up = -FVector::CrossProduct(Tangent, Normal); + Up.Normalize(); + + if (GCurrentLevelEditingViewportClient != nullptr) + { + FVector CamUp = UKismetMathLibrary::GetUpVector(GCurrentLevelEditingViewportClient->GetViewRotation()); + if (FVector::DotProduct(Up, CamUp) < 0.0f) + { + Up *= -1.0f; + } + } + + return Up; + } + + /* Set the text alignment for TextComp according to where the AlginmentEdge is in relation to the MidPoint. */ + void SetTextAlignment(UTextRenderComponent* TextComp, const FAkSurfaceEdgeInfo& AlignmentEdge, const FVector& MidPoint) + { + TextComp->SetHorizontalAlignment(EHTA_Left); + TextComp->SetVerticalAlignment(EVRTA_TextBottom); + const FVector& AnchorVertex = AlignmentEdge.V0(); + // 'Probe' slightly up and right of the AnchorVertex to determine how the text should be aligned vertically and horizontally. + FVector TextWorldUp = TextComp->GetUpVector(); + FVector Probe = AnchorVertex + TextWorldUp; + if ((MidPoint - Probe).Size() > (MidPoint - AnchorVertex).Size()) + TextComp->SetVerticalAlignment(EVRTA_TextTop); + // Since the normal faces out of the face, we actually probe to the "left" of the anchor point (hence the subtract instead of add). + Probe = AnchorVertex - TextComp->GetRightVector(); + if ((Probe - AlignmentEdge.V1()).Size() > (AnchorVertex - AlignmentEdge.V1()).Size()) + TextComp->SetHorizontalAlignment(EHTA_Right); + } + + float GetDistanceScaling(UTextRenderComponent* TextComp) + { + float DistanceScaling = 1.0f; + if (GCurrentLevelEditingViewportClient != nullptr) + { + DistanceScaling = (GCurrentLevelEditingViewportClient->GetViewLocation() - TextComp->GetComponentLocation()).Size(); + } + if (DistanceScaling > 1.0f) + { + // empirically derived through experimentation + DistanceScaling = DistanceScaling / 800.0f; + } + return DistanceScaling; + } +} + +#endif + +UAkSurfaceReflectorSetComponent::UAkSurfaceReflectorSetComponent(const class FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer) +{ + // Property initialization + bWantsOnUpdateTransform = true; + bEnableSurfaceReflectors = true; + bEnableDiffraction = true; + bEnableDiffractionOnBoundaryEdges = false; + +#if WITH_EDITOR + PropertyChangedHandle = FCoreUObjectDelegates::OnObjectPropertyChanged.AddUObject(this, &UAkSurfaceReflectorSetComponent::OnPropertyChanged); + PrimaryComponentTick.bCanEverTick = true; + bTickInEditor = true; + WasSelected = false; +#endif +} + +void UAkSurfaceReflectorSetComponent::BeginDestroy() +{ + Super::BeginDestroy(); +#if WITH_EDITOR + FCoreUObjectDelegates::OnObjectPropertyChanged.Remove(PropertyChangedHandle); +#endif +} + +void UAkSurfaceReflectorSetComponent::OnRegister() +{ + Super::OnRegister(); + InitializeParentBrush(); + SendSurfaceReflectorSet(); + UpdateSurfaceReflectorSet(); +} + +void UAkSurfaceReflectorSetComponent::InitializeParentBrush(bool fromTick /* = false */) +{ + AVolume* Parent = Cast(GetOwner()); + if (Parent) + { + ParentBrush = Parent->Brush; +#if WITH_EDITOR + // For runtime viewports, delay the UpdatePolys call until post-registration in tick. + // When editing geometry in the editor, the brush vertex data can sometimes be out of date at this point, causing the + // EdgeMap to be incorrect, which results in the visualizer drawing incorrect edges. + SchedulePolysUpdate(); +#endif + } + else + { + bEnableSurfaceReflectors = false; + ParentBrush = nullptr; + if (!fromTick) + { + FString actorString = FString("NONE"); + FString actorClass = FString("NONE"); + if (GetOwner() != nullptr) + { + actorString = GetOwner()->GetName(); + UClass* ownerClass = GetOwner()->GetClass(); + if (ownerClass != nullptr) + actorClass = ownerClass->GetName(); + } + UE_LOG(LogAkAudio, Error, TEXT("UAkSurfaceReflectorSetComponent::InitializeParentBrush : Error. Actor %s, of type %s, has an AkSurfaceReflectorSetComponent attached (%s)."), *actorString, *actorClass, *UObject::GetName()); + UE_LOG(LogAkAudio, Error, TEXT("UAkSurfaceReflectorSetComponent requires to be attached to an actor inheriting from AVolume.")); + } + } +} + +void UAkSurfaceReflectorSetComponent::OnUnregister() +{ +#if WITH_EDITOR + if (!HasAnyFlags(RF_Transient)) + { + DestroyTextVisualizers(); + } +#endif + RemoveSurfaceReflectorSet(); + Super::OnUnregister(); +} + +void UAkSurfaceReflectorSetComponent::UpdateAcousticProperties(TArray in_acousticSurfacePoly) +{ + AcousticPolys = in_acousticSurfacePoly; + + if (ReverbDescriptor != nullptr && ParentBrush != nullptr) + { + ComputeAcousticPolySurfaceArea(); + DampingEstimationNeedsUpdate = true; + } +} + +void UAkSurfaceReflectorSetComponent::ComputeAcousticPolySurfaceArea() +{ + int32 NumFaces = AcousticPolys.Num(); + int32 NumBrushFaces = ParentBrush->Nodes.Num(); + + if (NumBrushFaces > NumFaces) + { + AcousticPolys.AddDefaulted(NumBrushFaces - NumFaces); + } + else if (NumBrushFaces < NumFaces) + { + AcousticPolys.RemoveAt(NumBrushFaces, NumFaces - NumBrushFaces); + } + + const auto WorldScale = GetOwner()->ActorToWorld().GetScale3D(); + for (int32 NodeIdx = 0; NodeIdx < ParentBrush->Nodes.Num(); ++NodeIdx) + { + if (ParentBrush->Nodes[NodeIdx].NumVertices > 2) + { + TArray ScaledVertices; + + const int32 VertStartIndex = ParentBrush->Nodes[NodeIdx].iVertPool; + + FVector CentroidPosition = FVector::ZeroVector; + for (int32 VertIdx = 0; VertIdx < ParentBrush->Nodes[NodeIdx].NumVertices; ++VertIdx) + { + auto Vert = FVector(ParentBrush->Points[ParentBrush->Verts[VertStartIndex + VertIdx].pVertex]) * WorldScale; + CentroidPosition += Vert; + ScaledVertices.Emplace(Vert); + } + CentroidPosition /= (float)ParentBrush->Nodes[NodeIdx].NumVertices; + double Area = 0.0f; + for (int vIndex = 0; vIndex < ScaledVertices.Num() - 1; ++vIndex) + { + Area += FAkReverbDescriptor::TriangleArea(CentroidPosition, ScaledVertices[vIndex], ScaledVertices[vIndex + 1]); + } + Area += FAkReverbDescriptor::TriangleArea(CentroidPosition, ScaledVertices[ScaledVertices.Num() - 1], ScaledVertices[0]); + AcousticPolys[NodeIdx].SetSurfaceArea(Area); + } + } +} + +#if WITH_EDITOR + +TSet UAkSurfaceReflectorSetComponent::GetSelectedFaceIndices() const +{ + TSet selectedFaceIndices; + + // Determine if we are in geometry edit mode. + if (GLevelEditorModeTools().IsModeActive(FEditorModeID(TEXT("EM_Geometry")))) + { + // If we are in geometry mode, go through the list of geometry objects + // and find our current brush and update its source data as it might have changed + // in RecomputePoly + if (ABrush* ownerBrush = Cast(GetOwner())) + { + FEdModeGeometry* GeomMode = (FEdModeGeometry*)GLevelEditorModeTools().GetActiveMode(FEditorModeID(TEXT("EM_Geometry"))); + FEdModeGeometry::TGeomObjectIterator GeomModeIt = GeomMode->GeomObjectItor(); + const float tolerance = 0.001f; + for (; GeomModeIt; ++GeomModeIt) + { + FGeomObjectPtr Object = *GeomModeIt; + if (Object->GetActualBrush() == ownerBrush) + { + // selectedGeometry is a list of selected geometry elements. They can be vertices, edges, or polys + TArray selectedGeometry = Object->SelectionOrder; + for (FGeomBase* selection : selectedGeometry) + { + if (!selection->IsVertex()) + { + // There is no way to distinguish an edge from a poly, and we are unable to downcast. + // Check the normal and mid point against the normal and mid point of each face in our model. + // If we find the corresponding face, add its index to the selectedFaceIndices list. + for (int32 NodeIdx = 0; NodeIdx < ParentBrush->Nodes.Num() && NodeIdx < AcousticPolys.Num(); ++NodeIdx) + { + if (FMath::IsNearlyEqual((selection->GetNormal() - FPlane(ParentBrush->Nodes[NodeIdx].Plane)).Size(), 0.0f, tolerance) + && FMath::IsNearlyEqual((selection->GetMid() - AcousticPolys[NodeIdx].MidPoint).Size(), 0.0f, tolerance)) + { + selectedFaceIndices.Add(NodeIdx); + break; + } + } + } + } + break; + } + } + } + } + + return selectedFaceIndices; +} + +void UAkSurfaceReflectorSetComponent::CacheAcousticProperties() +{ + ensure(PreviousPolys.Num() == AcousticPolys.Num()); + for (int FaceIndex = 0; FaceIndex < PreviousPolys.Num(); ++FaceIndex) + { + PreviousPolys[FaceIndex].Texture = AcousticPolys[FaceIndex].Texture; + PreviousPolys[FaceIndex].Occlusion = AcousticPolys[FaceIndex].Occlusion; + PreviousPolys[FaceIndex].EnableSurface = AcousticPolys[FaceIndex].EnableSurface; + } +} + +void UAkSurfaceReflectorSetComponent::CacheLocalSpaceSurfaceGeometry() +{ + PreviousPolys = AcousticPolys; +} + +bool UAkSurfaceReflectorSetComponent::TexturesDiffer() const +{ + if (AcousticPolys.Num() == 0) + return false; + + UAkAcousticTexture* texture = AcousticPolys[0].Texture; + for (const FAkSurfacePoly& Poly : AcousticPolys) + { + if (Poly.Texture != texture) + return true; + } + + return false; +} + +void UAkSurfaceReflectorSetComponent::OnPropertyChanged(UObject* ObjectBeingModified, FPropertyChangedEvent& PropertyChangedEvent) +{ + bool changeAffectsThis = false; + if (AActor* actor = Cast(ObjectBeingModified)) + { + if (actor == GetOwner()) + { + changeAffectsThis = true; + } + } + if (USceneComponent* component = Cast(ObjectBeingModified)) + { + if (component->GetOwner() == GetOwner()) + { + changeAffectsThis = true; + } + } + if (changeAffectsThis) + { + // The start of a UI interaction will trigger a EPropertyChangeType::Interactive. + // This will be followed by a EPropertyChangeType::ValueSet at the end of the interaction. + if (PropertyChangedEvent.ChangeType == EPropertyChangeType::Interactive) + { + UserInteractionInProgress = true; + } + if (UserInteractionInProgress && PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet) + { + UserInteractionInProgress = false; + } + } +} + +void UAkSurfaceReflectorSetComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + InitializeParentBrush(); + + if (ParentBrush != nullptr) + { + UpdatePolys(); + } + + if (AssociatedRoom && !Cast(AssociatedRoom->GetComponentByClass(UAkRoomComponent::StaticClass()))) + { + UE_LOG(LogAkAudio, Warning, TEXT("%s: The Surface Reflector Set's Associated Room is not of type UAkRoomComponent."), *GetOwner()->GetName()); + } + + const FName MemberPropertyName = (PropertyChangedEvent.MemberProperty != nullptr) ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None; + if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSurfaceReflectorSetComponent, AcousticPolys)) + { + RegisterAllTextureParamCallbacks(); + DampingEstimationNeedsUpdate = true; + } +} + +void UAkSurfaceReflectorSetComponent::PostEditUndo() +{ + OnRefreshDetails.ExecuteIfBound(); + Super::PostEditUndo(); + // After the undo operation has finished, re-populate the PreviousPolys array to the current state. + CacheLocalSpaceSurfaceGeometry(); +} + +void UAkSurfaceReflectorSetComponent::PreEditUndo() +{ + // Empty the previous polys such that any surface properties are correctly reverted. + PreviousPolys.Empty(); + Super::PreEditUndo(); +} + +FText UAkSurfaceReflectorSetComponent::GetPolyText(int32 PolyIdx) const +{ + if (PolyIdx >= AcousticPolys.Num()) + return FText(); + + return AcousticPolys[PolyIdx].GetPolyText(bEnableSurfaceReflectors); +} + +void UAkSurfaceReflectorSetComponent::UpdateText(bool Visible) +{ + bool bReallyVisible = GetWorld() && GetWorld()->WorldType == EWorldType::Editor && Visible; + if (GetOwner() != nullptr) + { + const AAkSpatialAudioVolume* SpatialAudioVolume = Cast(GetOwner()); + if (SpatialAudioVolume != nullptr && SpatialAudioVolume->FitToGeometry) + bReallyVisible &= (!SpatialAudioVolume->IsDragging || SpatialAudioVolume->FitFailed); + } + for (int32 i = 0; i < TextVisualizers.Num(); i++) + { + if (TextVisualizers[i]) + { + TextVisualizers[i]->SetText(GetPolyText(i)); + TextVisualizers[i]->SetVisibility(bReallyVisible); + } + } + UpdateTextPositions(); +} + +void UAkSurfaceReflectorSetComponent::SurfacePropertiesChanged() +{ + CacheAcousticProperties(); + + UpdateText(GetOwner() && GetOwner()->IsSelected()); + RegisterAllTextureParamCallbacks(); + DampingEstimationNeedsUpdate = true; +} + +void UAkSurfaceReflectorSetComponent::DestroyTextVisualizers() +{ + for (int32 i = 0; i < TextVisualizers.Num(); i++) + { + if(TextVisualizers[i]) + TextVisualizers[i]->DestroyComponent(); + } + + TextVisualizers.Empty(); +} + +void UAkSurfaceReflectorSetComponent::SchedulePolysUpdate() +{ + if (GetWorld()->ShouldTick()) + { + PolysNeedUpdate = true; + } + else + { + if (ParentBrush && ParentBrush->Nodes.Num() != AcousticPolys.Num()) + { + UpdatePolys(); + } + } +} + +void UAkSurfaceReflectorSetComponent::UpdatePolys(bool bPreserveTextures /*= false*/) +{ + if (!ParentBrush || HasAnyFlags(RF_Transient) || UserInteractionInProgress) + { + return; + } + + const bool shouldRefreshDetails = AcousticPolys.Num() != ParentBrush->Nodes.Num(); + + ComputeAcousticPolySurfaceArea(); + + // Always recreate all text visualizers as indexes may have changed. + DestroyTextVisualizers(); + UMaterialInterface* mat = Cast(FAkAudioStyle::GetAkForegroundTextMaterial()); + + for (int32 i = 0; i < AcousticPolys.Num(); i++) + { + FString VizName = GetOwner()->GetName() + GetName() + TEXT("TextViz ") + FString::FromInt(i); + if (AcousticPolys[i].EnableSurface) + { + int32 idx = TextVisualizers.Add(NewObject(GetOuter(), *VizName)); + if (TextVisualizers[idx]) + { + if (mat != nullptr) + TextVisualizers[idx]->SetTextMaterial(mat); + TextVisualizers[idx]->RegisterComponentWithWorld(GetWorld()); + TextVisualizers[idx]->AttachToComponent(this, FAttachmentTransformRules::KeepWorldTransform); + TextVisualizers[idx]->bIsEditorOnly = true; + TextVisualizers[idx]->bSelectable = false; + TextVisualizers[idx]->bAlwaysRenderAsText = true; + TextVisualizers[idx]->SetHorizontalAlignment(EHTA_Center); + TextVisualizers[idx]->SetVerticalAlignment(EVRTA_TextCenter); + } + } + else + { + TextVisualizers.Add(nullptr); + } + } + + UpdateEdgeMap(!bPreserveTextures); + UpdateText(GetOwner() && GetOwner()->IsSelected()); + RegisterAllTextureParamCallbacks(); + DampingEstimationNeedsUpdate = true; + PolysNeedUpdate = false; + if (shouldRefreshDetails) + OnRefreshDetails.ExecuteIfBound(); +} + +FUnrealFloatVector GetModelCenter(const UModel& Model) +{ + FUnrealFloatVector Center(0.f); + uint32 Count = 0; + for (int32 NodeIndex = 0; NodeIndex < Model.Nodes.Num(); NodeIndex++) + { + const FBspNode& Node = Model.Nodes[NodeIndex]; + const uint32 NumVerts = (Node.NodeFlags & PF_TwoSided) ? Node.NumVertices / 2 : Node.NumVertices; + for (uint32 VertexIndex = 0; VertexIndex < NumVerts; VertexIndex++) + { + const FVert& Vert = Model.Verts[Node.iVertPool + VertexIndex]; + const auto& Position = Model.Points[Vert.pVertex]; + Center += Position; + Count++; + } + } + + if (Count > 0) + { + Center /= Count; + } + + return Center; +} + +void UAkSurfaceReflectorSetComponent::SortFaceEdges(int FaceIndex) +{ + FAkSurfacePoly& Face = AcousticPolys[FaceIndex]; + + if (Face.Edges.Num() < 2) + return; + + for (int SortedPosition = 1; SortedPosition < Face.Edges.Num(); ++SortedPosition) + { + const FVector& PreviousV1 = Face.Edges[SortedPosition - 1].V1; + for (int IndexToSwap = SortedPosition; IndexToSwap < Face.Edges.Num(); ++IndexToSwap) + { + const FVector& CurrentV0 = Face.Edges[IndexToSwap].V0; + const FVector& CurrentV1 = Face.Edges[IndexToSwap].V1; + if (PreviousV1.Equals(CurrentV0, AkSurfaceReflectorUtils::EQUALITY_THRESHOLD) + || PreviousV1.Equals(CurrentV1, AkSurfaceReflectorUtils::EQUALITY_THRESHOLD)) + { + Face.Edges.Swap(SortedPosition, IndexToSwap); + if (PreviousV1.Equals(CurrentV1, AkSurfaceReflectorUtils::EQUALITY_THRESHOLD)) + { + Face.Edges[SortedPosition].Invert(); + } + break; + } + } + } +} + +void UAkSurfaceReflectorSetComponent::UpdateFaceNormals(int FaceIndex) +{ + FAkSurfacePoly& Face = AcousticPolys[FaceIndex]; + + FVector E0 = Face.Edges[0].GetUnitVector(); + FVector E1 = Face.Edges[1].GetUnitVector(); + const float AlmostParallel = 0.99f; + if (FVector::DotProduct(E0, E1) >= AlmostParallel) + return; + Face.Normal = FVector::CrossProduct(E0, E1); + Face.Normal.Normalize(); + if (ParentBrush != nullptr) + { + const FVector BrushCentre = FVector(GetModelCenter(*ParentBrush)); + FVector VToCentre = BrushCentre - Face.Edges[0].V0; + VToCentre.Normalize(); + if (FVector::DotProduct(VToCentre, Face.Normal) > 0.0f) + { + // normal is pointing in wrong direction. Flip the ends of each Edge. + for (int EdgeIndex = 0; EdgeIndex < Face.Edges.Num(); ++EdgeIndex) + { + Face.Edges[EdgeIndex].Invert(); + } + Face.Normal *= -1.0f; + Face.Normal.Normalize(); + } + } +} + +void UAkSurfaceReflectorSetComponent::UpdateEdgeMap(bool bUpdateTextures) +{ + EdgeMap.Empty(); + const AAkSpatialAudioVolume* SpatialAudioVolume = Cast(GetOwner()); + if (ParentBrush != nullptr && SpatialAudioVolume != nullptr) + { + for (int32 NodeIdx = 0; NodeIdx < ParentBrush->Nodes.Num() && NodeIdx < AcousticPolys.Num(); ++NodeIdx) + { + AcousticPolys[NodeIdx].ClearEdgeInfo(); + FVector PolyMidPoint(0, 0, 0); + + int32 NumVertices = ParentBrush->Nodes[NodeIdx].NumVertices; + // from the unreal doc: If the node has zero vertices, it's only used for splitting and doesn't contain a polygon (this happens in the editor). + if (NumVertices == 0) + { + AcousticPolys[NodeIdx].MidPoint = PolyMidPoint; + continue; + } + + FVector PolyWorldSpaceNormal(0, 0, 0); + FAkSurfaceEdgeInfo EdgeInfo; + FUnrealFloatPlane& Plane = ParentBrush->Nodes[NodeIdx].Plane; + EdgeInfo.Normal = FVector(Plane.X, Plane.Y, Plane.Z); + EdgeInfo.IsEnabled = AcousticPolys[NodeIdx].EnableSurface; + int32 VertStartIndex = ParentBrush->Nodes[NodeIdx].iVertPool; + FVert BrushVert0 = ParentBrush->Verts[VertStartIndex]; + FVert BrushVert1 = ParentBrush->Verts[VertStartIndex + 1]; + + // Add edges to map for edges visualization + for (int32 Idx0 = NumVertices - 1, Idx1 = 0; + Idx1 < NumVertices; + Idx0 = Idx1, Idx1++) + { + BrushVert0 = ParentBrush->Verts[VertStartIndex + Idx0]; + BrushVert1 = ParentBrush->Verts[VertStartIndex + Idx1]; + + PolyMidPoint += FVector(ParentBrush->Points[BrushVert1.pVertex]); + + EdgeInfo.SetV0(FVector(ParentBrush->Points[BrushVert0.pVertex])); + EdgeInfo.SetV1(FVector(ParentBrush->Points[BrushVert1.pVertex])); + + int64 EdgeHash = EdgeInfo.GetHash(); + FAkSurfaceEdgeInfo* Found = EdgeMap.Find(EdgeHash); + if (Found) + { + const float DP = FVector::DotProduct(Found->Normal, EdgeInfo.Normal); + if (FMath::IsNearlyEqual(DP, 1.0f, 0.00001f)) + Found->IsFlat = true; + else + Found->IsBoundary = !(Found->IsEnabled && EdgeInfo.IsEnabled); + + AcousticPolys[NodeIdx].Edges.Add(EdgeInfo.EdgeVerts); + Found->IsEnabled |= EdgeInfo.IsEnabled; + } + else + { + EdgeMap.Add(EdgeHash, EdgeInfo); + AcousticPolys[NodeIdx].Edges.Add(EdgeInfo.EdgeVerts); + } + } + PolyMidPoint /= (float)NumVertices; + AcousticPolys[NodeIdx].MidPoint = PolyMidPoint; + SortFaceEdges(NodeIdx); + // Non-uniform scaling of dimensions will skew the normals stored in the brush, so we need to recaluclate them here + // taking scaling into account. + UpdateFaceNormals(NodeIdx); + } + if (bUpdateTextures) + EdgeMapChanged(); + } + CacheLocalSpaceSurfaceGeometry(); +} + +void UAkSurfaceReflectorSetComponent::EdgeMapChanged() +{ + if (PreviousPolys.Num() <= 0) + return; + for (int FaceIndex = 0; FaceIndex < AcousticPolys.Num(); ++FaceIndex) + { + FAkSurfacePoly& Face = AcousticPolys[FaceIndex]; + + if (Face.Edges.Num() == 0) + continue; + + Face.Texture = nullptr; + Face.Occlusion = 0.0f; + Face.EnableSurface = true; + FVector ComponentNormal = Face.Normal; + ComponentNormal.Normalize(); + const float Thresh = AkSurfaceReflectorUtils::EQUALITY_THRESHOLD; + for (int OtherFaceIndex = 0; OtherFaceIndex < PreviousPolys.Num(); ++OtherFaceIndex) + { + FAkSurfacePoly& PreviousFace = PreviousPolys[OtherFaceIndex]; + if (!ComponentNormal.Equals(PreviousFace.Normal, Thresh)) + continue; + int NumSharedEdges = 0; + int EdgeIndex = 0; + while (NumSharedEdges < 2 && EdgeIndex < Face.Edges.Num()) + { + FAkSurfaceEdgeVerts Edge = Face.Edges[EdgeIndex]; + for (int OtherEdgeIndex = 0; OtherEdgeIndex < PreviousFace.Edges.Num(); ++OtherEdgeIndex) + { + const FAkSurfaceEdgeVerts& PreviousEdge = PreviousFace.Edges[OtherEdgeIndex]; + const float EdgeDP = FVector::DotProduct(Edge.GetUnitVector(), PreviousEdge.GetUnitVector()); + if (FMath::IsNearlyEqual(EdgeDP, 1.0f, Thresh) && (FAkSurfaceEdgeVerts::EdgesShareVertex(Edge, PreviousEdge))) + { + ++NumSharedEdges; + break; + } + } + ++EdgeIndex; + } + if (NumSharedEdges >= 2) + { + Face.Texture = PreviousFace.Texture; + Face.Occlusion = PreviousFace.Occlusion; + Face.EnableSurface = PreviousFace.EnableSurface; + break; + } + } + } +} + +int UAkSurfaceReflectorSetComponent::ChooseAlignmentEdge(int FaceIndex) const +{ + int EdgeChoice = 0; + if (GCurrentLevelEditingViewportClient != nullptr + && ParentBrush != nullptr + && FaceIndex < TextVisualizers.Num() + && FaceIndex < ParentBrush->Nodes.Num() + && FaceIndex < AcousticPolys.Num()) + { + FVector CamUp = UKismetMathLibrary::GetUpVector(GCurrentLevelEditingViewportClient->GetViewRotation()); + UTextRenderComponent* TextComp = TextVisualizers[FaceIndex]; + FVector Normal = AcousticPolys[FaceIndex].Normal; + Normal.Normalize(); + const FAkSurfacePoly& Face = AcousticPolys[FaceIndex]; + FVector AlignmentV0 = FVector::ZeroVector; + FVector AlignmentV1 = FVector::ZeroVector; + for (int EdgeIndex = 0; EdgeIndex < Face.Edges.Num(); ++EdgeIndex) + { + AlignmentV0 = Face.Edges[EdgeIndex].V0; + AlignmentV1 = Face.Edges[EdgeIndex].V1; + FVector Tangent = AlignmentV1 - AlignmentV0; + Tangent.Normalize(); + FVector Up = -FVector::CrossProduct(Tangent, Normal); + Up.Normalize(); + float DP = FMath::Abs(FVector::DotProduct(CamUp, Up)); + const float DPDiffThresh = 0.1f; + if (DP > AcousticPolys[FaceIndex].OptimalEdgeDP && FMath::Abs(DP - AcousticPolys[FaceIndex].OptimalEdgeDP) > DPDiffThresh) + { + AcousticPolys[FaceIndex].OptimalEdgeDP = DP; + EdgeChoice = EdgeIndex; + } + } + } + return EdgeChoice; +} + +FVector UAkSurfaceReflectorSetComponent::GetTextAnchorPosition(int FaceIndex, const FAkSurfaceEdgeInfo& AlignmentEdge, int AlignmentEdgeIndex) const +{ + UTextRenderComponent* TextComp = TextVisualizers[FaceIndex]; + TextComp->SetWorldLocation(AlignmentEdge.V0()); + const FAkSurfacePoly& Face = AcousticPolys[FaceIndex]; + // Find the edge that connectes to the first vertex of the AlignmentEdge + int ConnectedEdgeIndex = (AlignmentEdgeIndex - 1) < 0 ? Face.Edges.Num() - 1 : AlignmentEdgeIndex - 1; + const FAkSurfaceEdgeVerts* ConnectedEdge = &Face.Edges[ConnectedEdgeIndex]; + if (!ConnectedEdge->V1.Equals(AlignmentEdge.V0(), AkSurfaceReflectorUtils::EQUALITY_THRESHOLD)) + { + ConnectedEdgeIndex = (AlignmentEdgeIndex + 1) % Face.Edges.Num(); + ConnectedEdge = &Face.Edges[ConnectedEdgeIndex]; + if (!ConnectedEdge->V1.Equals(AlignmentEdge.V0(), AkSurfaceReflectorUtils::EQUALITY_THRESHOLD)) + return AlignmentEdge.V0(); + } + const FVector NormedConnectedEdge = -ConnectedEdge->GetUnitVector(); + const FVector AlignmentEdgeUnitV = (AlignmentEdge.V1() - AlignmentEdge.V0()).GetSafeNormal(); + const float AlignmentCornerDP = FVector::DotProduct(NormedConnectedEdge, AlignmentEdgeUnitV); + FVector Shift(0.0f); + if (AlignmentCornerDP > 0.0f) + { + float ProjCornerV = FMath::Abs(FVector::DotProduct(NormedConnectedEdge, AlignmentEdgeUnitV)); + float MaxWidth = (AlignmentEdge.V1() - AlignmentEdge.V0()).Size() / 2.0f; + Shift = ProjCornerV * MaxWidth * AlignmentEdgeUnitV; + } + return AlignmentEdge.V0() + Shift; +} + +void UAkSurfaceReflectorSetComponent::SetTextScale(UTextRenderComponent* TextComp, int FaceIndex, int AlignmentEdgeIndex, const FVector& TextAnchorPosition, const FFacePlane& FacePlane, const FTransform& AttachTransform) const +{ + const FAkSurfacePoly& Face = AcousticPolys[FaceIndex]; + if (Face.Edges.Num() == 0) + return; + + float Scale = TextAlignmentHelpers::GetDistanceScaling(TextComp); + bool IntersectionFound = true; + // Look for intersections between the edges of the text visualizer and the edges of the face. + // Scale down by the smallest amount until no more intersections are found. + while (IntersectionFound) + { + TArray TextEdges = TextAlignmentHelpers::GetTextEdges(*TextComp, TextAnchorPosition, FacePlane, Scale); + float ScaleDown = 0.0f; + float BisectionRatio; + for (const FAkSurfaceEdgeInfo& TextEdge : TextEdges) + { + for (int EdgeIndex = 0; EdgeIndex < Face.Edges.Num(); ++EdgeIndex) + { + if (EdgeIndex != AlignmentEdgeIndex) + { + FVector BrushEdgeV0 = AttachTransform.TransformPosition(Face.Edges[EdgeIndex].V0); + FVector BrushEdgeV1 = AttachTransform.TransformPosition(Face.Edges[EdgeIndex].V1); + + FAkSurfaceEdgeInfo brushEdge(BrushEdgeV0, BrushEdgeV1); + FVector intersection = FVector::ZeroVector; + + TextAlignmentHelpers::GetTextEdgeBisection(TextEdge, brushEdge, FacePlane, BisectionRatio); + if (BisectionRatio > ScaleDown && BisectionRatio < 1.0f) + ScaleDown = BisectionRatio; + } + } + } + if (ScaleDown > 0.0f) + Scale *= ScaleDown; + else + IntersectionFound = false; + } + TextComp->SetWorldScale3D(FVector(FMath::Max(1.0f, Scale))); +} + +void UAkSurfaceReflectorSetComponent::AlignTextWithEdge(int FaceIndex) const +{ + if (GCurrentLevelEditingViewportClient != nullptr + && ParentBrush != nullptr + && FaceIndex < TextVisualizers.Num() + && FaceIndex < ParentBrush->Nodes.Num() + && FaceIndex < AcousticPolys.Num()) + { + USceneComponent* ParentComp = GetAttachParent(); + if (ParentComp != nullptr) + { + UTextRenderComponent* TextComp = TextVisualizers[FaceIndex]; + + if (TextComp != nullptr) + { + const FAkSurfacePoly& Face = AcousticPolys[FaceIndex]; + if (Face.Edges.Num() == 0) + return; + + const FTransform& AttachTransform = ParentComp->GetComponentTransform(); + const FRotator& Rotation = ParentComp->GetComponentRotation(); + + int EdgeIndex = ChooseAlignmentEdge(FaceIndex); + + FVector TextAnchorPosition = AttachTransform.TransformPosition(GetTextAnchorPosition(FaceIndex, FAkSurfaceEdgeInfo(Face.Edges[EdgeIndex].V0, Face.Edges[EdgeIndex].V1), EdgeIndex)); + TextComp->SetWorldLocation(TextAnchorPosition); + + FVector AlignmentV0 = AttachTransform.TransformPosition(Face.Edges[EdgeIndex].V0); + FVector AlignmentV1 = AttachTransform.TransformPosition(Face.Edges[EdgeIndex].V1); + FVector MidPoint = AttachTransform.TransformPosition(Face.MidPoint); + + FVector Normal = Rotation.RotateVector(Face.Normal); + Normal.Normalize(); + + FVector Edge = AlignmentV1 - AlignmentV0; + FVector Tangent = Edge; + Tangent.Normalize(); + + FVector CamToCentre = GCurrentLevelEditingViewportClient->GetViewLocation() - AlignmentV0 + Edge * 0.5f; + FVector Up = TextAlignmentHelpers::DetermineUpVector(Tangent, Normal, CamToCentre); + TextComp->SetWorldRotation(UKismetMathLibrary::MakeRotFromXZ(Normal, Up)); + + TextAlignmentHelpers::SetTextAlignment(TextComp, FAkSurfaceEdgeInfo(AlignmentV0, AlignmentV1), MidPoint); + + FFacePlane FacePlane(AlignmentV0, TextComp->VerticalAlignment == EVRTA_TextBottom ? Up : -Up, Edge.GetSafeNormal()); + SetTextScale(TextComp, FaceIndex, EdgeIndex, TextAnchorPosition, FacePlane, AttachTransform); + } + } + } +} + +void UAkSurfaceReflectorSetComponent::UpdateTextPositions() const +{ + const AAkSpatialAudioVolume* SpatialAudioVolume = Cast(GetOwner()); + if (ParentBrush != nullptr && SpatialAudioVolume != nullptr) + { + // For each text visualizer, find an appropriate edge on the face and align that visualizer with the edge. + for (int32 NodeIdx = 0; NodeIdx < ParentBrush->Nodes.Num() && NodeIdx < TextVisualizers.Num(); ++NodeIdx) + { + UTextRenderComponent* TextComp = TextVisualizers[NodeIdx]; + if (TextComp != nullptr) + { + AlignTextWithEdge(NodeIdx); + } + } + } +} + +void UAkSurfaceReflectorSetComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + if (!ParentBrush) + { + InitializeParentBrush(true); + } + + const bool bNumBrushNodesChanged = (ParentBrush && ParentBrush->Nodes.Num() != AcousticPolys.Num()); + if (PolysNeedUpdate || bNumBrushNodesChanged) + { + const bool bPreserveTextures = !bNumBrushNodesChanged; + UpdatePolys(bPreserveTextures); + } + + if (GetOwner()->IsSelected() && !WasSelected) + { + WasSelected = true; + UpdateText(true); + } + + if (!GetOwner()->IsSelected() && WasSelected) + { + WasSelected = false; + UpdateText(false); + } +} +#endif // WITH_EDITOR + +bool UAkSurfaceReflectorSetComponent::MoveComponentImpl( + const FVector& Delta, + const FQuat& NewRotation, + bool bSweep, + FHitResult* Hit, + EMoveComponentFlags MoveFlags, + ETeleportType Teleport) +{ + if (USceneComponent* Parent = GetAttachParent()) + if (AkComponentHelpers::DoesMovementRecenterChild(this, Parent, Delta)) + Super::MoveComponentImpl(Delta, NewRotation, bSweep, Hit, MoveFlags, Teleport); + + return false; +} + +void UAkSurfaceReflectorSetComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) +{ + Super::OnUpdateTransform(UpdateTransformFlags, Teleport); + UpdateSurfaceReflectorSet(); +#if WITH_EDITOR + UpdateEdgeMap(false); + UpdateText(GetOwner() && GetOwner()->IsSelected()); +#endif +} + +void UAkSurfaceReflectorSetComponent::GetTexturesAndSurfaceAreas(TArray& textures, TArray& surfaceAreas) const +{ + const UAkSettings* AkSettings = GetDefault(); + if (AkSettings != nullptr) + { + if (AcousticPolys.Num() > 0) + { + for (const FAkSurfacePoly& Poly : AcousticPolys) + { + if (Poly.Texture && Poly.EnableSurface) + { + surfaceAreas.Add(Poly.GetSurfaceArea() / AkComponentHelpers::UnrealUnitsPerSquaredMeter(this)); + const FAkAcousticTextureParams* params = AkSettings->GetTextureParams(Poly.Texture->GetShortID()); + if (params != nullptr) + { + textures.Add(*params); + } + else + { + textures.Add(FAkAcousticTextureParams()); + } + } + } + } + } +} + +bool UAkSurfaceReflectorSetComponent::ShouldSendGeometry() const +{ + if (GetAttachParent() == nullptr || ParentBrush == nullptr) + return false; + UAkRoomComponent* siblingRoom = AkComponentHelpers::GetChildComponentOfType(*GetAttachParent()); + if (!bEnableSurfaceReflectors && !(siblingRoom && siblingRoom->bEnable)) + return false; + + return UAkAcousticTextureSetComponent::ShouldSendGeometry(); +} + +void UAkSurfaceReflectorSetComponent::SendSurfaceReflectorSet() +{ + if (GetWorld() && GetWorld()->bIsTearingDown) + return; + + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + + if (AkAudioDevice && ShouldSendGeometry()) + { + TArray VertsToSend; + TArray SurfacesToSend; + TArray TrianglesToSend; + TArray< TSharedPtr< decltype(StringCast(TEXT(""))) > > SurfaceNames; + + FString ParentName; +#if WITH_EDITOR + ParentName = GetOwner()->GetActorLabel(); +#else + ParentName = GetOwner()->GetName(); +#endif + + + // Some clarifications: + // - All of the brush's vertices are held in the UModel->Verts array (elements of type FVert) + // - FVert contains pVertex, which points to the UModel->Points array (actual coords of the point in actor space) + // - Polygons are represented by the UModel->Nodes array (elements of type FBspNode). + // - FBspNode contains iVertPool, which represents the index in the UModel->Verts at which the node's verts start + // - FBspNode contains NumVertices, the number of vertices that compose this node. + // + // For more insight on how all of these tie together, look at UModel::BuildVertexBuffers(). + + // A mapping from the unreal vertex index to the wwise vertex index. + TArray UnrealToWwiseIndex; + UnrealToWwiseIndex.Init(-1, ParentBrush->Points.Num()); + + // A function to add unique vertices to the VertsToSend array. + // UnrealToWwiseIndex keeps track of added vertices to avoid duplicates. + // This function ensures that we only include vertices that are actually referenced by triangles. + auto AddVertex = [&UnrealToWwiseIndex,&VertsToSend,this](int32 UnrealIdx) + { + int32 wwiseIdx = UnrealToWwiseIndex[UnrealIdx]; + if (wwiseIdx == -1) + { + wwiseIdx = VertsToSend.Num(); + UnrealToWwiseIndex[UnrealIdx] = wwiseIdx; + + const auto& VertexInActorSpace = ParentBrush->Points[UnrealIdx]; + AkVertex akvtx; + akvtx.X = VertexInActorSpace.X; + akvtx.Y = VertexInActorSpace.Y; + akvtx.Z = VertexInActorSpace.Z; + VertsToSend.Add(akvtx); + + } + check(wwiseIdx < (AkVertIdx)-1); + return (AkVertIdx)wwiseIdx; + }; + + for (int32 NodeIdx = 0; NodeIdx < ParentBrush->Nodes.Num(); ++NodeIdx) + { + if (AcousticPolys.Num() > NodeIdx) + { + FAkSurfacePoly AcousticSurface = AcousticPolys[NodeIdx]; + if (ParentBrush->Nodes[NodeIdx].NumVertices > 2 && (AcousticSurface.EnableSurface || !bEnableSurfaceReflectors)) + { + FString TriangleName; + if (AcousticSurface.Texture != nullptr) + { + TriangleName = ParentName + GetName() + FString(TEXT("_")) + AcousticSurface.Texture->GetName() + FString::FromInt(NodeIdx); + } + else + { + TriangleName = ParentName + GetName() + FString(TEXT("_")) + FString::FromInt(NodeIdx); + } + SurfaceNames.Add(MakeShareable(new decltype(StringCast(TEXT("")))(*TriangleName))); + + AkAcousticSurface NewSurface; + NewSurface.textureID = AcousticSurface.Texture != nullptr ? FAkAudioDevice::Get()->GetShortIDFromString(AcousticSurface.Texture->GetName()) : 0; + NewSurface.transmissionLoss = AcousticSurface.Occlusion; + NewSurface.strName = SurfaceNames.Last()->Get(); + SurfacesToSend.Add(NewSurface); + + int32 VertStartIndex = ParentBrush->Nodes[NodeIdx].iVertPool; + + const FVert* Vert0 = &ParentBrush->Verts[VertStartIndex + 0]; + const FVert* Vert1 = &ParentBrush->Verts[VertStartIndex + 1]; + + for (int32 VertexIdx = 2; VertexIdx < ParentBrush->Nodes[NodeIdx].NumVertices; ++VertexIdx) + { + const FVert* Vert2 = &ParentBrush->Verts[VertStartIndex + VertexIdx]; + + AkTriangle NewTriangle; + NewTriangle.point0 = AddVertex(Vert0->pVertex); + NewTriangle.point1 = AddVertex(Vert1->pVertex); + NewTriangle.point2 = AddVertex(Vert2->pVertex); + NewTriangle.surface = (AkSurfIdx)(SurfacesToSend.Num()-1); + TrianglesToSend.Add(NewTriangle); + + Vert1 = Vert2; + } + } + } + } + + if (TrianglesToSend.Num() > 0 && VertsToSend.Num() > 0) + { + AkGeometryParams params; + params.NumSurfaces = SurfacesToSend.Num(); + params.NumTriangles = TrianglesToSend.Num(); + params.NumVertices = VertsToSend.Num(); + params.Surfaces = SurfacesToSend.GetData(); + params.Triangles = TrianglesToSend.GetData(); + params.Vertices = VertsToSend.GetData(); + params.EnableDiffraction = bEnableDiffraction; + params.EnableDiffractionOnBoundaryEdges = bEnableDiffractionOnBoundaryEdges; + params.EnableTriangles = bEnableSurfaceReflectors; + + SendGeometryToWwise(params); + } + } +} + +void UAkSurfaceReflectorSetComponent::RemoveSurfaceReflectorSet() +{ + RemoveGeometryFromWwise(); +} + +void UAkSurfaceReflectorSetComponent::UpdateSurfaceReflectorSet() +{ + AkRoomID roomID = AkRoomID(); + if (AssociatedRoom) + { + UAkRoomComponent* room = Cast(AssociatedRoom->GetComponentByClass(UAkRoomComponent::StaticClass())); + + if (room != nullptr) + roomID = room->GetRoomID(); + } + + SendGeometryInstanceToWwise(GetOwner()->ActorToWorld().Rotator(), GetOwner()->GetActorLocation(), GetOwner()->ActorToWorld().GetScale3D(), roomID); + + if (ReverbDescriptor != nullptr && ParentBrush != nullptr) + { + ComputeAcousticPolySurfaceArea(); + DampingEstimationNeedsUpdate = true; + } +} + +#if WITH_EDITOR +void UAkSurfaceReflectorSetComponent::HandleObjectsReplaced(const TMap& ReplacementMap) +{ + Super::HandleObjectsReplaced(ReplacementMap); + if (ReplacementMap.Contains(ParentBrush)) + { + InitializeParentBrush(); + UpdateSurfaceReflectorSet(); + } +} + +bool UAkSurfaceReflectorSetComponent::ContainsTexture(const FGuid& textureID) +{ + for (const FAkSurfacePoly& Poly : AcousticPolys) + if (Poly.Texture != nullptr && Poly.Texture->AcousticTextureInfo.WwiseGuid == textureID) + return true; + return false; +} + +void UAkSurfaceReflectorSetComponent::RegisterAllTextureParamCallbacks() +{ + for (const FAkSurfacePoly& Poly : AcousticPolys) + if (Poly.Texture != nullptr && TextureDelegateHandles.Find(Poly.Texture->AcousticTextureInfo.WwiseGuid) == nullptr) + RegisterTextureParamChangeCallback(Poly.Texture->AcousticTextureInfo.WwiseGuid); +} + +TWeakObjectPtr AssignPolygonTexturesFromSamples(const TArray& Vertices, const TArray& Points, const TArray& Normals, const TArray< TWeakObjectPtr >& Materials, int Num) +{ + const float kNormalAgreement = 0.866f; // ~30 degrees + + TMap, int> Votes; + + const FVector* Vert0 = &Vertices[0]; + const FVector* Vert1 = &Vertices[1]; + + for (int32 VertexIdx = 2; VertexIdx < Vertices.Num(); ++VertexIdx) + { + const FVector* Vert2 = &Vertices[VertexIdx]; + + FVector e0 = *Vert1 - *Vert0; + FVector e1 = *Vert2 - *Vert0; + + float d00 = FVector::DotProduct(e0, e0); + float d01 = FVector::DotProduct(e0, e1); + float d10 = FVector::DotProduct(e1, e0); + float d11 = FVector::DotProduct(e1, e1); + float denom = d00 * d11 - d01 * d01; + + // n defined such that the normal faces inwards. + FVector n = FVector::CrossProduct(e1, e0); + n.Normalize(); + + for (int i = 0; i < Num; ++i) + { + const FVector& pt = Points[i]; + const FVector& norm = Normals[i]; + + // We want some amount of agreement between the hit normal and the triangle normal. + if (FVector::DotProduct(n, norm) < kNormalAgreement) + continue; + + // Init tally to 0 if the normal points the right way. + int Tally = 0; + + //project point on to triangle. + float proj = FVector::DotProduct(n, pt - *Vert0); + + FVector pt_proj = pt - proj * n; + FVector vToPt = pt_proj - *Vert0; + + float d20 = FVector::DotProduct(vToPt, e0); + float d21 = FVector::DotProduct(vToPt, e1); + + // convert to barycentric coords to see if the point projects into the triangle. + float u = (d00 * d21 - d01 * d20) / denom; + if (u > 0.f && u < 1.f) + { + float v = (d11 * d20 - d01 * d21) / denom; + if (v > 0.f && v < 1.f) + { + if (u + v < 1.f) + { + // Assign another point to the surface if the point projects into the triangle + Tally++; + } + } + } + + int* Count = Votes.Find(Materials[i]); + if (Count == nullptr) + { + Count = &Votes.Add(Materials[i]); + *Count = 0; + } + + if (Count != nullptr) + { + *Count += Tally; + } + } + + Vert1 = Vert2; + } + + // Tally the votes + if (Votes.Num() > 0) + { + auto MaxVotes = *Votes.begin(); + auto it = Votes.begin(); + ++it; + + while (it != Votes.end()) + { + if (it->Value > MaxVotes.Value) + MaxVotes = *it; + + ++it; + } + + // Return the material with the max number of points. + return MaxVotes.Key; + } + + return nullptr; +} + +void UAkSurfaceReflectorSetComponent::AssignAcousticTexturesFromSamples(const TArray& Points, const TArray& Normals, const TArray< TWeakObjectPtr >& Materials, int Num) +{ + check(Points.Num() == Materials.Num()); + + FTransform ToWorld = GetOwner()->ActorToWorld(); + + for (int32 NodeIdx = 0; NodeIdx < ParentBrush->Nodes.Num(); ++NodeIdx) + { + AcousticPolys[NodeIdx].EnableSurface = false; + + if (ParentBrush->Nodes[NodeIdx].NumVertices > 2) + { + TArray WorldVertices; + + const int32 VertStartIndex = ParentBrush->Nodes[NodeIdx].iVertPool; + + for (int32 VertIdx = 0; VertIdx < ParentBrush->Nodes[NodeIdx].NumVertices; ++VertIdx) + { + WorldVertices.Emplace(ToWorld.TransformPosition(FVector(ParentBrush->Points[ParentBrush->Verts[VertStartIndex + VertIdx].pVertex]))); + } + + TWeakObjectPtr Material = AssignPolygonTexturesFromSamples(WorldVertices, Points, Normals, Materials, Num); + + if (Material.IsValid()) + { + GetDefault()->GetAssociatedAcousticTexture(Material.Get(), AcousticPolys[NodeIdx].Texture); + GetDefault()->GetAssociatedOcclusionValue(Material.Get(), AcousticPolys[NodeIdx].Occlusion); + AcousticPolys[NodeIdx].EnableSurface = true; + } + } + if (AcousticPolys[NodeIdx].Texture != nullptr) + RegisterTextureParamChangeCallback(AcousticPolys[NodeIdx].Texture->AcousticTextureInfo.WwiseGuid); + } + + OnRefreshDetails.ExecuteIfBound(); + // Update text visualizers. + SchedulePolysUpdate(); +} + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSurfaceReflectorSetUtils.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSurfaceReflectorSetUtils.cpp new file mode 100644 index 0000000..c6fbb6c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSurfaceReflectorSetUtils.cpp @@ -0,0 +1,75 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkSurfaceReflectorSetUtils.h" + +#if WITH_EDITOR + +FText FAkSurfacePoly::GetPolyText(bool includeOcclusion) const +{ + if (!EnableSurface) + return FText(); + + FString textureName = FString("None"); + + if (Texture) + textureName = Texture->GetName(); + + if (!includeOcclusion) + return FText::FromString(FString::Format(TEXT("{0}"), { textureName })); + + FNumberFormattingOptions NumberFormat; + NumberFormat.MaximumFractionalDigits = 2; + NumberFormat.MinimumFractionalDigits = 1; + FString transmissionLossValueString = FText::AsNumber(Occlusion, &NumberFormat).ToString(); + return FText::FromString(FString::Format(TEXT("{0}{1}{2}"), { textureName, LINE_TERMINATOR, transmissionLossValueString })); +} + +void FAkSurfacePoly::ClearEdgeInfo() +{ + Edges.Empty(); + OptimalEdgeDP = 0.0f; +} + +#endif + +bool FAkSurfaceEdgeVerts::EdgesShareVertex(const FAkSurfaceEdgeVerts& Edge1, const FAkSurfaceEdgeVerts& Edge2) +{ + return FMath::IsNearlyEqual(FVector::Dist(Edge1.V0, Edge2.V0), 0.0f, AkSurfaceReflectorUtils::EQUALITY_THRESHOLD) + || FMath::IsNearlyEqual(FVector::Dist(Edge1.V1, Edge2.V1), 0.0f, AkSurfaceReflectorUtils::EQUALITY_THRESHOLD); +} + +FAkSurfaceEdgeVerts FAkSurfaceEdgeVerts::GetTransformedEdge(const FTransform& Transform) const +{ + return FAkSurfaceEdgeVerts(Transform.TransformPositionNoScale(V0), Transform.TransformPositionNoScale(V1)); +} + +void FAkSurfaceEdgeVerts::TransformEdge(const FTransform& Transform) +{ + V0 = Transform.TransformPositionNoScale(V0); + V1 = Transform.TransformPositionNoScale(V1); +} + +void FAkSurfaceEdgeVerts::Invert() +{ + FVector Temp = V0; + V0 = V1; + V1 = Temp; +} + +FAkSurfaceEdgeInfo::FAkSurfaceEdgeInfo() {} +FAkSurfaceEdgeInfo::FAkSurfaceEdgeInfo(FVector InV0, FVector InV1) : EdgeVerts(InV0, InV1) {} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSwitchValue.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSwitchValue.cpp new file mode 100644 index 0000000..50fea5b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkSwitchValue.cpp @@ -0,0 +1,190 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkSwitchValue.h" +#include "Wwise/Stats/AkAudio.h" + +#include "Wwise/WwiseResourceLoader.h" + +#if WITH_EDITORONLY_DATA +#include "Wwise/WwiseProjectDatabase.h" +#include "Wwise/WwiseResourceCooker.h" +#include "AkAudioDevice.h" +#endif + +void UAkSwitchValue::LoadGroupValue() +{ + SCOPED_AKAUDIO_EVENT_2(TEXT("UAkSwitchValue::LoadGroupValue")); + auto* ResourceLoader = FWwiseResourceLoader::Get(); + if (UNLIKELY(!ResourceLoader)) + { + return; + } + + if (LoadedGroupValue) + { + UnloadGroupValue(false); + } + +#if WITH_EDITORONLY_DATA + if (IWwiseProjectDatabaseModule::IsInACookingCommandlet()) + { + return; + } + auto* ProjectDatabase = FWwiseProjectDatabase::Get(); + if (!ProjectDatabase || !ProjectDatabase->IsProjectDatabaseParsed()) + { + UE_LOG(LogAkAudio, VeryVerbose, TEXT("UAkSwitchValue::LoadGroupValue: Not loading '%s' because project database is not parsed."), *GetName()) + return; + } + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + return; + } + if (UNLIKELY(!ResourceCooker->PrepareCookedData(GroupValueCookedData, GetValidatedInfo(GroupValueInfo), EWwiseGroupType::Switch))) + { + return; + } +#endif + LoadedGroupValue = ResourceLoader->LoadGroupValue(GroupValueCookedData); +} + +void UAkSwitchValue::Serialize(FArchive& Ar) +{ + Super::Serialize(Ar); + + if (HasAnyFlags(RF_ClassDefaultObject)) + { + return; + } + +#if !UE_SERVER +#if WITH_EDITORONLY_DATA + if (Ar.IsCooking() && Ar.IsSaving() && !Ar.CookingTarget()->IsServerOnly()) + { + FWwiseGroupValueCookedData CookedDataToArchive; + if (auto* ResourceCooker = FWwiseResourceCooker::GetForArchive(Ar)) + { + ResourceCooker->PrepareCookedData(CookedDataToArchive, GetValidatedInfo(GroupValueInfo), EWwiseGroupType::Switch); + } + CookedDataToArchive.Serialize(Ar); + } +#else + GroupValueCookedData.Serialize(Ar); +#endif +#endif +} + +#if WITH_EDITORONLY_DATA +void UAkSwitchValue::FillInfo() +{ + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkSwitchValue::FillInfo: ResourceCooker not initialized")); + return; + } + + auto ProjectDatabase = ResourceCooker->GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkSwitchValue::FillInfo: ProjectDatabase not initialized")); + return; + } + + FWwiseGroupValueInfo* AudioTypeInfo = static_cast(GetInfoMutable()); + FWwiseRefSwitch RefSwitch = FWwiseDataStructureScopeLock(*ProjectDatabase).GetSwitch( + GetValidatedInfo(*AudioTypeInfo)); + + if (RefSwitch.SwitchName().IsNone() || !RefSwitch.SwitchGuid().IsValid() || RefSwitch.SwitchId() == AK_INVALID_UNIQUE_ID) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkSwitchValue::FillInfo: Valid object not found in Project Database")); + return; + } + + FWwiseAnyRef Ref = FWwiseAnyRef::Create(RefSwitch); + + AudioTypeInfo->WwiseName = RefSwitch.SwitchName(); + AudioTypeInfo->WwiseGuid = RefSwitch.SwitchGuid(); + AudioTypeInfo->WwiseShortId = RefSwitch.SwitchId(); + AudioTypeInfo->GroupShortId = RefSwitch.SwitchGroupId(); +} + +void UAkSwitchValue::FillInfo(const FWwiseAnyRef& CurrentWwiseRef) +{ + FWwiseGroupValueInfo* AudioTypeInfo = static_cast(GetInfoMutable()); + + const FWwiseMetadataSwitch* MetadataSwitch = CurrentWwiseRef.GetSwitch(); + + AudioTypeInfo->WwiseName = MetadataSwitch->Name; + AudioTypeInfo->WwiseGuid = MetadataSwitch->GUID; + AudioTypeInfo->WwiseShortId = MetadataSwitch->Id; + AudioTypeInfo->GroupShortId = CurrentWwiseRef.GetGroupId(); +} + +bool UAkSwitchValue::ObjectIsInSoundBanks() +{ + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkSwitchValue::GetWwiseRef: ResourceCooker not initialized")); + return false; + } + + auto ProjectDatabase = ResourceCooker->GetProjectDatabase(); + + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkSwitchValue::GetWwiseRef: ProjectDatabase not initialized")); + return false; + } + + FWwiseGroupValueInfo* AudioTypeInfo = static_cast(GetInfoMutable()); + FWwiseRefSwitch RefSwitch = FWwiseDataStructureScopeLock(*ProjectDatabase).GetSwitch( + GetValidatedInfo(*AudioTypeInfo)); + + return RefSwitch.IsValid(); +} + +FName UAkSwitchValue::GetWwiseGroupName() +{ + + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkSwitchValue::GetWwiseRef: ResourceCooker not initialized")); + return {}; + } + + auto ProjectDatabase = ResourceCooker->GetProjectDatabase(); + + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkSwitchValue::GetWwiseRef: ProjectDatabase not initialized")); + return {}; + } + + FWwiseGroupValueInfo* AudioTypeInfo = static_cast(GetInfoMutable()); + FWwiseRefSwitch RefSwitch = FWwiseDataStructureScopeLock(*ProjectDatabase).GetSwitch( + GetValidatedInfo(*AudioTypeInfo)); + + return RefSwitch.SwitchGroupName(); + +} +#endif + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkTrigger.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkTrigger.cpp new file mode 100644 index 0000000..d23379a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkTrigger.cpp @@ -0,0 +1,136 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkTrigger.h" +#include "Wwise/Stats/AkAudio.h" + +#if WITH_EDITORONLY_DATA +#include "Wwise/WwiseProjectDatabase.h" +#include "Wwise/WwiseResourceCooker.h" +#include "AkAudioDevice.h" +#endif + +void UAkTrigger::Serialize(FArchive& Ar) +{ + Super::Serialize(Ar); + + if (HasAnyFlags(RF_ClassDefaultObject)) + { + return; + } + +#if !UE_SERVER +#if WITH_EDITORONLY_DATA + if (Ar.IsCooking() && Ar.IsSaving() && !Ar.CookingTarget()->IsServerOnly()) + { + FWwiseTriggerCookedData CookedDataToArchive; + if (auto* ResourceCooker = FWwiseResourceCooker::GetForArchive(Ar)) + { + ResourceCooker->PrepareCookedData(CookedDataToArchive, GetValidatedInfo(TriggerInfo)); + } + CookedDataToArchive.Serialize(Ar); + } +#else + TriggerCookedData.Serialize(Ar); +#endif +#endif +} + +#if WITH_EDITOR +void UAkTrigger::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + GetTriggerCookedData(); +} +#endif + +#if WITH_EDITORONLY_DATA +void UAkTrigger::FillInfo() +{ + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkTrigger::FillInfo: ResourceCooker not initialized")); + return; + } + + auto ProjectDatabase = ResourceCooker->GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkTrigger::FillInfo: ProjectDatabase not initialized")); + return; + } + + FWwiseObjectInfo* AudioTypeInfo = &TriggerInfo; + FWwiseRefTrigger TriggerRef = FWwiseDataStructureScopeLock(*ProjectDatabase).GetTrigger( + GetValidatedInfo(TriggerInfo)); + + if (TriggerRef.TriggerName().IsNone() || !TriggerRef.TriggerGuid().IsValid() || TriggerRef.TriggerId() == AK_INVALID_UNIQUE_ID) + { + UE_LOG(LogAkAudio, Warning, TEXT("UAkTrigger::FillInfo: Valid object not found in Project Database")); + return; + } + + AudioTypeInfo->WwiseName = TriggerRef.TriggerName(); + AudioTypeInfo->WwiseGuid = TriggerRef.TriggerGuid(); + AudioTypeInfo->WwiseShortId = TriggerRef.TriggerId(); +} + +bool UAkTrigger::ObjectIsInSoundBanks() +{ + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkTrigger::GetWwiseRef: ResourceCooker not initialized")); + return false; + } + + auto ProjectDatabase = ResourceCooker->GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAkAudio, Error, TEXT("UAkTrigger::GetWwiseRef: ProjectDatabase not initialized")); + return false; + } + + FWwiseObjectInfo* AudioTypeInfo = &TriggerInfo; + FWwiseRefTrigger TriggerRef = FWwiseDataStructureScopeLock(*ProjectDatabase).GetTrigger( + GetValidatedInfo(TriggerInfo)); + + return TriggerRef.IsValid(); +} + +void UAkTrigger::GetTriggerCookedData() +{ + SCOPED_AKAUDIO_EVENT_2(TEXT("GetTriggerCookedData")); + if (IWwiseProjectDatabaseModule::IsInACookingCommandlet()) + { + return; + } + auto* ProjectDatabase = FWwiseProjectDatabase::Get(); + if (!ProjectDatabase || !ProjectDatabase->IsProjectDatabaseParsed()) + { + UE_LOG(LogAkAudio, VeryVerbose, TEXT("UAkTrigger::GetTriggerCookedData: Not loading '%s' because project database is not parsed."), *GetName()) + return; + } + auto* ResourceCooker = FWwiseResourceCooker::GetDefault(); + if (UNLIKELY(!ResourceCooker)) + { + return; + } + ResourceCooker->PrepareCookedData(TriggerCookedData, GetValidatedInfo(TriggerInfo)); +} +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkUnrealEditorHelper.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkUnrealEditorHelper.cpp new file mode 100644 index 0000000..4085063 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkUnrealEditorHelper.cpp @@ -0,0 +1,208 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkUnrealEditorHelper.h" + +#if WITH_EDITOR + +#include "Interfaces/IPluginManager.h" +#include "HAL/FileManager.h" +#include "Misc/App.h" +#include "Misc/MessageDialog.h" +#include "Misc/Paths.h" +#include "ISourceControlModule.h" +#include "SSettingsEditorCheckoutNotice.h" + +#include "AkSettings.h" +#include "AkUnrealHelper.h" +#include "Wwise/Stats/AkAudio.h" + +#if UE_5_0_OR_LATER +#include "HAL/PlatformFileManager.h" +#else +#include "HAL/PlatformFilemanager.h" +#endif + +#define LOCTEXT_NAMESPACE "AkAudio" +namespace AkUnrealEditorHelper +{ + + const TCHAR* LocalizedFolderName = TEXT("Localized"); + + + void SanitizePath(FString& Path, const FString& PreviousPath, const FText& DialogMessage) + { + AkUnrealHelper::TrimPath(Path); + + FText FailReason; + if (!FPaths::ValidatePath(Path, &FailReason)) + { + if (FApp::CanEverRender()) + { + FMessageDialog::Open(EAppMsgType::Ok, FailReason); + } + else + { + UE_LOG(LogAkAudio, Error, TEXT("%s"), *FailReason.ToString()); + } + Path = PreviousPath; + return; + } + + const FString AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*Path); + if (!FPaths::DirectoryExists(AbsolutePath)) + { + if (FApp::CanEverRender()) + { + FMessageDialog::Open(EAppMsgType::Ok, DialogMessage); + } + else + { + UE_LOG(LogAkAudio, Error, TEXT("%s"), *DialogMessage.ToString()); + } + Path = PreviousPath; + return; + } + } + + bool SanitizeFolderPathAndMakeRelativeToContentDir(FString& Path, const FString& PreviousPath, const FText& DialogMessage) + { + AkUnrealHelper::TrimPath(Path); + + FString TempPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForWrite(*Path); + FText FailReason; + if (!FPaths::ValidatePath(TempPath, &FailReason)) + { + if (FApp::CanEverRender()) + { + FMessageDialog::Open(EAppMsgType::Ok, FailReason); + } + else + { + UE_LOG(LogAkAudio, Error, TEXT("%s"), *FailReason.ToString()); + } + Path = PreviousPath; + return false; + } + + auto ContentDirectory = AkUnrealHelper::GetContentDirectory(); + if (!FPaths::FileExists(TempPath)) + { + // Path might be a valid one (relative to game) entered manually. Check that. + TempPath = FPaths::ConvertRelativePathToFull(ContentDirectory, Path); + + if (!FPaths::DirectoryExists(TempPath)) + { + if (FApp::CanEverRender()) + { + if (EAppReturnType::Ok == FMessageDialog::Open(EAppMsgType::Ok, DialogMessage)) + { + Path = PreviousPath; + return false; + } + } + else + { + // Allow setting not yet existing paths when running in headless mode (e.g. migration) + UE_LOG(LogAkAudio, Warning, TEXT("Path '%s' does not exist."), *Path); + } + } + } + + // Make the path relative to the game dir + FPaths::MakePathRelativeTo(TempPath, *ContentDirectory); + Path = TempPath; + + if (Path != PreviousPath) + { + + return true; + } + return false; + } + + bool SaveConfigFile(UObject* ConfigObject) + { + const FString ConfigFilename = ConfigObject->GetDefaultConfigFilename(); + if(ISourceControlModule::Get().IsEnabled()) + { + if (!SettingsHelpers::IsCheckedOut(ConfigFilename, true)) + { + if (!SettingsHelpers::CheckOutOrAddFile(ConfigFilename, true)) + { + return false; + } + } + } + +#if UE_5_0_OR_LATER + return ConfigObject->TryUpdateDefaultConfigFile(); +#else + ConfigObject->UpdateDefaultConfigFile(); + return true; +#endif + } + + FString GetLegacySoundBankDirectory() + { + if (const UAkSettings* AkSettings = GetDefault()) + { + return FPaths::Combine(AkUnrealHelper::GetContentDirectory(), AkSettings->WwiseSoundDataFolder.Path); + } + else + { + return FPaths::Combine(AkUnrealHelper::GetContentDirectory(), UAkSettings::DefaultSoundDataFolder); + } + } + + FString GetContentDirectory() + { + return FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir()); + } + + void DeleteLegacySoundBanks() + { + const TArray ExtensionsToDelete = { "bnk", "wem", "json", "txt", "xml" }; + bool SuccessfulDelete = true; + for (auto& Extension : ExtensionsToDelete) + { + TArray FoundFiles; + FPlatformFileManager::Get().GetPlatformFile().FindFilesRecursively(FoundFiles, *AkUnrealHelper::GetSoundBankDirectory(), *Extension); + FPlatformFileManager::Get().GetPlatformFile().FindFilesRecursively(FoundFiles, *GetLegacySoundBankDirectory(), *Extension); + TSet FoundFilesSet(FoundFiles); + for (auto& File : FoundFilesSet) + { + SuccessfulDelete |= FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*File); + } + } + + if (!SuccessfulDelete) + { + if (!FApp::CanEverRender()) + { + UE_LOG(LogAkAudio, Warning, TEXT("Unable to delete legacy SoundBank files. Please ensure to manually delete them after migration is complete.")); + } + else + { + FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("CannotDeleteOldBanks", "Unable to delete legacy SoundBank files. Please ensure to manually delete them after migration is complete.")); + } + } + } +} +#undef LOCTEXT_NAMESPACE + +#endif // WITH_EDITOR \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiBlueprints/AkWaapiCalls.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiBlueprints/AkWaapiCalls.cpp new file mode 100644 index 0000000..463e0d9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiBlueprints/AkWaapiCalls.cpp @@ -0,0 +1,141 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkWaapiCalls.cpp: +=============================================================================*/ + +#include "AkWaapiBlueprints/AkWaapiCalls.h" +#include "AkAudioDevice.h" +#include "AkWaapiBlueprints/AkWaapiUriCustomization.h" +#include "AkWaapiBlueprints/AkWaapiFieldNamesCustomization.h" +#include "Async/Async.h" +#include "Core/Public/Modules/ModuleManager.h" + +/*----------------------------------------------------------------------------- + AkWaapiCalls. +-----------------------------------------------------------------------------*/ + + +UAkWaapiCalls::UAkWaapiCalls(const class FObjectInitializer& ObjectInitializer) +: Super(ObjectInitializer) +{ +#if WITH_EDITOR + // Property initialization + FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); + PropertyModule.RegisterCustomPropertyTypeLayout("AkWaapiUri", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FAkWaapiUriCustomization::MakeInstance)); + PropertyModule.RegisterCustomPropertyTypeLayout("AkWaapiFieldNames", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FAkWaapiFieldNamesCustomization::MakeInstance)); + PropertyModule.NotifyCustomizationModuleChanged(); +#endif +} + +void UAkWaapiCalls::SetSubscriptionID(const FAkWaapiSubscriptionId& Subscription, int id) +{ + Subscription.SubscriptionId = (uint64_t)id; +} + +int UAkWaapiCalls::GetSubscriptionID(const FAkWaapiSubscriptionId& Subscription) +{ + return Subscription.SubscriptionId; +} + +bool UAkWaapiCalls::RegisterWaapiProjectLoadedCallback(const FOnWaapiProjectLoaded& Callback) +{ + if (auto waapiClient = FAkWaapiClient::Get()) + { + waapiClient->OnProjectLoaded.AddLambda([Callback]() { Callback.ExecuteIfBound(); }); + return true; + } + return false; +} + +bool UAkWaapiCalls::RegisterWaapiConnectionLostCallback(const FOnWaapiConnectionLost& Callback) +{ + if (auto waapiClient = FAkWaapiClient::Get()) + { + waapiClient->OnConnectionLost.AddLambda([Callback]() + { + Callback.ExecuteIfBound(); + }); + return true; + } + return false; +} + +FAKWaapiJsonObject UAkWaapiCalls::CallWaapi(const FAkWaapiUri& WaapiUri, const FAKWaapiJsonObject& WaapiArgs, const FAKWaapiJsonObject& WaapiOptions) +{ + FAKWaapiJsonObject outJsonResult = FAKWaapiJsonObject(); + // Connect to Wwise Authoring on localhost. + if (auto waapiClient = FAkWaapiClient::Get()) + { + // Request data from Wwise using WAAPI + if (!waapiClient->Call(TCHAR_TO_ANSI(*WaapiUri.Uri), WaapiArgs.WaapiJsonObj.ToSharedRef(), WaapiOptions.WaapiJsonObj.ToSharedRef(), outJsonResult.WaapiJsonObj)) + { + UE_LOG(LogAkAudio, Log, TEXT("Call Failed")); + } + } + else + { + UE_LOG(LogAkAudio, Log, TEXT("Unable to connect to localhost")); + } + return outJsonResult; +} + +FAKWaapiJsonObject UAkWaapiCalls::SubscribeToWaapi(const FAkWaapiUri& WaapiUri, const FAKWaapiJsonObject& WaapiOptions, const FOnEventCallback& CallBack, FAkWaapiSubscriptionId& SubscriptionId, bool& SubscriptionDone) +{ + FAKWaapiJsonObject outJsonResult = FAKWaapiJsonObject(); + + // Connect to Wwise Authoring on localhost. + if (auto waapiClient = FAkWaapiClient::Get()) + { + auto wampEventCallback = WampEventCallback::CreateLambda([CallBack](uint64_t id, TSharedPtr in_UEJsonObject) + { + AsyncTask(ENamedThreads::GameThread,[CallBack,id,in_UEJsonObject] + { + FAKWaapiJsonObject outWaapiObj = FAKWaapiJsonObject(); + outWaapiObj.WaapiJsonObj = in_UEJsonObject; + CallBack.ExecuteIfBound(id, outWaapiObj); + }); + }); + + // Subscribe to action notifications. + SubscriptionDone = waapiClient->Subscribe(TCHAR_TO_ANSI(*WaapiUri.Uri), WaapiOptions.WaapiJsonObj.ToSharedRef(), wampEventCallback, SubscriptionId.SubscriptionId, outJsonResult.WaapiJsonObj); + } + return outJsonResult; +} + +FAKWaapiJsonObject UAkWaapiCalls::Unsubscribe(const FAkWaapiSubscriptionId& SubscriptionId, bool& UnsubscriptionDone) +{ + FAKWaapiJsonObject outJsonResult = FAKWaapiJsonObject(); + // Connect to Wwise Authoring on localhost. + if (auto waapiClient = FAkWaapiClient::Get()) + { + // Subscribe to action notifications. + UnsubscriptionDone = waapiClient->Unsubscribe(SubscriptionId.SubscriptionId, outJsonResult.WaapiJsonObj); + } + return outJsonResult; +} + +FString UAkWaapiCalls::Conv_FAkWaapiSubscriptionIdToString(const FAkWaapiSubscriptionId& INAkWaapiSubscriptionId) +{ + return FString::Printf(TEXT("%u"), INAkWaapiSubscriptionId.SubscriptionId); +} + +FText UAkWaapiCalls::Conv_FAkWaapiSubscriptionIdToText(const FAkWaapiSubscriptionId& INAkWaapiSubscriptionId) +{ + return FText::FromString(*Conv_FAkWaapiSubscriptionIdToString(INAkWaapiSubscriptionId)); +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiBlueprints/AkWaapiFieldNames.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiBlueprints/AkWaapiFieldNames.cpp new file mode 100644 index 0000000..45bd377 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiBlueprints/AkWaapiFieldNames.cpp @@ -0,0 +1,254 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ +AkWaapiFieldNames.cpp +------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------ +includes. +------------------------------------------------------------------------------------*/ +#include "AkWaapiBlueprints/AkWaapiFieldNames.h" +#include "AkAudioDevice.h" +#include "Widgets/Input/SSearchBox.h" +#include "Misc/ScopedSlowTask.h" +#include "Framework/Application/SlateApplication.h" +#include "AkAudioStyle.h" + +/*------------------------------------------------------------------------------------ +Defines +------------------------------------------------------------------------------------*/ +#define LOCTEXT_NAMESPACE "AkAudio" + +/*------------------------------------------------------------------------------------ +Statics and Globals +------------------------------------------------------------------------------------*/ +namespace SAkWaapiFieldNames_Helpers +{ + static const FString FullFieldNamesList[] = + { + TEXT("id"), + TEXT("return"), + TEXT("path"), + TEXT("filePath"), + TEXT("from"), + TEXT("name"), + TEXT("type"), + TEXT("children"), + TEXT("childrenCount"), + TEXT("ancestors"), + TEXT("descendants"), + TEXT("workunit:type"), + TEXT("Folder"), + TEXT("PhysicalFolder"), + TEXT("search"), + TEXT("parent"), + TEXT("select"), + TEXT("transform"), + TEXT("object"), + TEXT("objects"), + TEXT("value"), + TEXT("command"), + TEXT("transport"), + TEXT("action"), + TEXT("play"), + TEXT("stop"), + TEXT("stopped"), + TEXT("displayName"), + TEXT("Delete Items"), + TEXT("Drag Drop Items"), + TEXT("Undo"), + TEXT("Redo"), + TEXT("state"), + TEXT("ofType"), + TEXT("Project"), + TEXT("property"), + TEXT("Volume"), + TEXT("newName"), + TEXT("oldName"), + TEXT("new"), + TEXT("classId"), + TEXT("FindInProjectExplorerSyncGroup1") + }; + + enum { FullFieldNamesListSize = sizeof(FullFieldNamesList) / sizeof(*FullFieldNamesList) }; +} +/*------------------------------------------------------------------------------------ +Helpers +------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------ +USAkWaapiFieldNamesConv +------------------------------------------------------------------------------------*/ +USAkWaapiFieldNamesConv::USAkWaapiFieldNamesConv(const class FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + // Property initialization +} + +FString USAkWaapiFieldNamesConv::Conv_FAkWaapiFieldNamesToString(const FAkWaapiFieldNames& INAkWaapiFieldNames) +{ + return INAkWaapiFieldNames.FieldName; +} + +FText USAkWaapiFieldNamesConv::Conv_FAkWaapiFieldNamesToText(const FAkWaapiFieldNames& INAkWaapiFieldNames) +{ + return FText::FromString(*INAkWaapiFieldNames.FieldName); +} + +/*------------------------------------------------------------------------------------ +SAkWaapiFieldNames +------------------------------------------------------------------------------------*/ +SAkWaapiFieldNames::SAkWaapiFieldNames() +{} + +SAkWaapiFieldNames::~SAkWaapiFieldNames() +{} + +void SAkWaapiFieldNames::Construct(const FArguments& InArgs) +{ + OnDragDetected = InArgs._OnDragDetected; + OnSelectionChanged = InArgs._OnSelectionChanged; + + if (InArgs._FocusSearchBoxWhenOpened) + { + RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &SAkWaapiFieldNames::SetFocusPostConstruct)); + } + + SearchBoxFilter = MakeShareable(new StringFilter(StringFilter::FItemToStringArray::CreateSP(this, &SAkWaapiFieldNames::PopulateSearchStrings))); + SearchBoxFilter->OnChanged().AddSP(this, &SAkWaapiFieldNames::FilterUpdated); + + ChildSlot + [ + SNew(SBorder) + .Padding(4) + .BorderImage(FAkAudioStyle::GetBrush("AudiokineticTools.GroupBorder")) + [ + SNew(SVerticalBox) + // Search + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 1, 0, 3) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + InArgs._SearchContent.Widget + ] + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SAssignNew(SearchBoxPtr, SSearchBox) + .HintText(LOCTEXT("FieldNameSearchHint", "Search a Field Name")) + .ToolTipText(LOCTEXT("FieldNameSearchTooltip", "Type here to search for a Field Name")) + .OnTextChanged(SearchBoxFilter.Get(), &StringFilter::SetRawFilterText) + .SelectAllTextWhenFocused(false) + .DelayChangeNotificationsWhileTyping(true) + ] + ] + // Tree + + SVerticalBox::Slot() + .FillHeight(1.f) + [ + SAssignNew(ListViewPtr, SListView< TSharedPtr >) + .ListItemsSource(&FieldNamesList) + .OnGenerateRow(this, &SAkWaapiFieldNames::GenerateRow) + .ItemHeight(18) + .SelectionMode(InArgs._SelectionMode) + .OnSelectionChanged(this, &SAkWaapiFieldNames::ListSelectionChanged) + .ClearSelectionOnClick(false) + ] + ] + ]; + + for (const auto& FieldName : SAkWaapiFieldNames_Helpers::FullFieldNamesList) + { + FieldNamesList.Add(MakeShareable(new FString(FieldName))); + } + FieldNamesList.Sort([](TSharedPtr< FString > Firststr, TSharedPtr< FString > Secondstr) { return *Firststr.Get() < *Secondstr.Get(); }); + ListViewPtr->RequestListRefresh(); +} + +TSharedRef SAkWaapiFieldNames::GenerateRow(TSharedPtr in_FieldName, const TSharedRef& OwnerTable) +{ + check(in_FieldName.IsValid()); + + TSharedPtr NewRow = SNew(STableRow< TSharedPtr >, OwnerTable) + [ + SNew(STextBlock) + .Text(FText::FromString(*in_FieldName.Get())) + .HighlightText(SearchBoxFilter.Get(), &StringFilter::GetRawFilterText) + ]; + return NewRow.ToSharedRef(); +} + +void SAkWaapiFieldNames::PopulateSearchStrings(const FString& in_FieldName, OUT TArray< FString >& OutSearchStrings) const +{ + OutSearchStrings.Add(in_FieldName); +} + +void SAkWaapiFieldNames::FilterUpdated() +{ + FScopedSlowTask SlowTask(2.f, LOCTEXT("AK_PopulatingPicker", "Populating FieldName Picker...")); + SlowTask.MakeDialog(); + + FieldNamesList.Empty(SAkWaapiFieldNames_Helpers::FullFieldNamesListSize); + + FString FilterString = SearchBoxFilter->GetRawFilterText().ToString(); + if (FilterString.IsEmpty()) + { + for (const auto& FieldName : SAkWaapiFieldNames_Helpers::FullFieldNamesList) + { + FieldNamesList.Add(MakeShareable(new FString(FieldName))); + } + } + else + { + for (const auto& FieldName : SAkWaapiFieldNames_Helpers::FullFieldNamesList) + { + if (FieldName.Contains(FilterString)) + { + FieldNamesList.Add(MakeShareable(new FString(FieldName))); + } + } + } + FieldNamesList.Sort([](TSharedPtr< FString > Firststr, TSharedPtr< FString > Secondstr) { return *Firststr.Get() < *Secondstr.Get(); }); + ListViewPtr->RequestListRefresh(); +} + +void SAkWaapiFieldNames::ListSelectionChanged(TSharedPtr< FString > in_FieldName, ESelectInfo::Type /*SelectInfo*/) +{ + if (OnSelectionChanged.IsBound()) + OnSelectionChanged.Execute(in_FieldName, ESelectInfo::OnMouseClick); +} + +const TArray> SAkWaapiFieldNames::GetSelectedFieldNames() const +{ + return ListViewPtr->GetSelectedItems(); +} + +EActiveTimerReturnType SAkWaapiFieldNames::SetFocusPostConstruct(double InCurrentTime, float InDeltaTime) +{ + FWidgetPath WidgetToFocusPath; + FSlateApplication::Get().GeneratePathToWidgetUnchecked(SearchBoxPtr.ToSharedRef(), WidgetToFocusPath); + FSlateApplication::Get().SetKeyboardFocus(WidgetToFocusPath, EFocusCause::SetDirectly); + + return EActiveTimerReturnType::Stop; +} +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiBlueprints/AkWaapiFieldNamesCustomization.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiBlueprints/AkWaapiFieldNamesCustomization.cpp new file mode 100644 index 0000000..e72fbb7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiBlueprints/AkWaapiFieldNamesCustomization.cpp @@ -0,0 +1,126 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#if WITH_EDITOR +#include "AkWaapiBlueprints/AkWaapiFieldNamesCustomization.h" +#include "AkAudioDevice.h" + +#include "Framework/Application/SlateApplication.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SButton.h" +#include "DetailWidgetRow.h" +#include "AkWaapiBlueprints/AkWaapiFieldNames.h" +#include "Components/SlateWrapperTypes.h" +#include "AkAudioStyle.h" + +#if UE_5_0_OR_LATER +#include "Framework/Docking/TabManager.h" +#endif + +#define LOCTEXT_NAMESPACE "AkWaapiFieldNamesCustomization" + +TSharedRef FAkWaapiFieldNamesCustomization::MakeInstance() +{ + return MakeShareable(new FAkWaapiFieldNamesCustomization()); +} + +void FAkWaapiFieldNamesCustomization::CustomizeHeader(TSharedRef StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + FieldNameHandle = StructPropertyHandle->GetChildHandle("FieldName"); + + if (FieldNameHandle.IsValid()) + { + TSharedPtr PickerWidget = nullptr; + + PickerWidget = SAssignNew(PickerButton, SButton) + .ButtonStyle(FAkAudioStyle::Get(), "AudiokineticTools.HoverHintOnly") + .ToolTipText(LOCTEXT("WwiseFieldNameToolTipText", "Choose A Field Name")) + .OnClicked(FOnClicked::CreateSP(this, &FAkWaapiFieldNamesCustomization::OnPickContent, FieldNameHandle.ToSharedRef())) + .ContentPadding(2.0f) + .ForegroundColor(FSlateColor::UseForeground()) + .IsFocusable(false) + [ + SNew(SImage) + .Image(FAkAudioStyle::GetBrush("AudiokineticTools.Button_EllipsisIcon")) + .ColorAndOpacity(FSlateColor::UseForeground()) + ]; + + HeaderRow.ValueContent() + .MinDesiredWidth(125.0f) + .MaxDesiredWidth(600.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + [ + FieldNameHandle->CreatePropertyValueWidget() + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(FMargin(4.0f, 0.0f, 0.0f, 0.0f)) + .VAlign(VAlign_Center) + [ + PickerWidget.ToSharedRef() + ] + ] + .NameContent() + [ + StructPropertyHandle->CreatePropertyNameWidget() + ]; + } +} + +void FAkWaapiFieldNamesCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ +} + +FReply FAkWaapiFieldNamesCustomization::OnPickContent(TSharedRef PropertyHandle) +{ + Window = SNew(SWindow) + .Title(LOCTEXT("FieldNamePickerWindowTitle", "Choose A Field Name")) + .SizingRule(ESizingRule::UserSized) + .AutoCenter(EAutoCenter::PreferredWorkArea) + .ClientSize(FVector2D(350, 400)); + + Window->SetContent( + SNew(SBorder) + [ + SNew(SAkWaapiFieldNames) + .FocusSearchBoxWhenOpened(true) + .SelectionMode(ESelectionMode::Single) + .OnSelectionChanged(this, &FAkWaapiFieldNamesCustomization::FieldNameSelectionChanged) + ] + ); + + TSharedPtr RootWindow = FGlobalTabmanager::Get()->GetRootWindow(); + FSlateApplication::Get().AddWindowAsNativeChild(Window.ToSharedRef(), RootWindow.ToSharedRef()); + return FReply::Handled(); +} + +void FAkWaapiFieldNamesCustomization::FieldNameSelectionChanged(TSharedPtr< FString > in_FieldName, ESelectInfo::Type SelectInfo) +{ + if (in_FieldName.IsValid()) + { + FieldNameHandle->SetValue(*in_FieldName.Get()); + Window->RequestDestroyWindow(); + } +} + +#undef LOCTEXT_NAMESPACE + +#endif//WITH_EDITOR diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiBlueprints/AkWaapiJsonManager.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiBlueprints/AkWaapiJsonManager.cpp new file mode 100644 index 0000000..2937d7e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiBlueprints/AkWaapiJsonManager.cpp @@ -0,0 +1,185 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkAudioClasses.cpp: +=============================================================================*/ + +#include "AkWaapiBlueprints/AkWaapiJsonManager.h" +#include "AkAudioDevice.h" +#include "EngineUtils.h" +#include "Model.h" +#include "UObject/UObjectIterator.h" +#include "Engine/GameEngine.h" +#include "AkWaapiClient.h" + + +/*----------------------------------------------------------------------------- + AkWaapiJsonManager. +-----------------------------------------------------------------------------*/ + +UAkWaapiJsonManager::UAkWaapiJsonManager(const class FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + // Property initialization +} + +FAKWaapiJsonObject UAkWaapiJsonManager::SetStringField(const FAkWaapiFieldNames& FieldName, const FString& FieldValue, FAKWaapiJsonObject target) +{ + if (!FieldName.FieldName.IsEmpty() && !FieldValue.IsEmpty() && target.WaapiJsonObj.IsValid()) + { + target.WaapiJsonObj->SetStringField(FieldName.FieldName, FieldValue); + } + return target; +} + +FAKWaapiJsonObject UAkWaapiJsonManager::SetBoolField(const FAkWaapiFieldNames& FieldName, bool FieldValue, FAKWaapiJsonObject target) +{ + if (!FieldName.FieldName.IsEmpty() && target.WaapiJsonObj.IsValid()) + { + target.WaapiJsonObj->SetBoolField(FieldName.FieldName, FieldValue); + } + return target; +} + +FAKWaapiJsonObject UAkWaapiJsonManager::SetNumberField(const FAkWaapiFieldNames& FieldName, float FieldValue, FAKWaapiJsonObject target) +{ + if (!FieldName.FieldName.IsEmpty() && target.WaapiJsonObj.IsValid()) + { + target.WaapiJsonObj->SetNumberField(FieldName.FieldName, FieldValue); + } + return target; +} + +FAKWaapiJsonObject UAkWaapiJsonManager::SetObjectField(const FAkWaapiFieldNames& FieldName, FAKWaapiJsonObject FieldValue, FAKWaapiJsonObject target) +{ + if (!FieldName.FieldName.IsEmpty() && FieldValue.WaapiJsonObj.IsValid() && target.WaapiJsonObj.IsValid()) + { + target.WaapiJsonObj->SetObjectField(FieldName.FieldName, FieldValue.WaapiJsonObj); + } + return target; +} + +FAKWaapiJsonObject UAkWaapiJsonManager::SetArrayStringFields(const FAkWaapiFieldNames& FieldName, const TArray< FString >& FieldStringValues, FAKWaapiJsonObject target) +{ + if (!FieldName.FieldName.IsEmpty() && FieldStringValues.Num() && target.WaapiJsonObj.IsValid()) + { + TArray> JsonArray; + for (FString Field : FieldStringValues) + { + JsonArray.Add(MakeShareable(new FJsonValueString(Field))); + } + target.WaapiJsonObj->SetArrayField(FieldName.FieldName, JsonArray); + } + return target; +} + +FAKWaapiJsonObject UAkWaapiJsonManager::SetArrayObjectFields(const FAkWaapiFieldNames& FieldName, const TArray< FAKWaapiJsonObject >& FieldObjectValues, FAKWaapiJsonObject target) +{ + if (!FieldName.FieldName.IsEmpty() && FieldObjectValues.Num() && target.WaapiJsonObj.IsValid()) + { + TArray> JsonArray; + for (FAKWaapiJsonObject Field : FieldObjectValues) + { + if (Field.WaapiJsonObj.IsValid()) + { + JsonArray.Add(MakeShareable(new FJsonValueObject(Field.WaapiJsonObj))); + } + } + target.WaapiJsonObj->SetArrayField(FieldName.FieldName, JsonArray); + } + return target; +} + +FString UAkWaapiJsonManager::GetStringField(const FAkWaapiFieldNames& FieldName, FAKWaapiJsonObject target) +{ + FString result; + if (!FieldName.FieldName.IsEmpty() && target.WaapiJsonObj.IsValid()) + { + target.WaapiJsonObj->TryGetStringField(FieldName.FieldName, result); + } + return result; +} + +bool UAkWaapiJsonManager::GetBoolField(const FAkWaapiFieldNames& FieldName, FAKWaapiJsonObject target) +{ + bool result = true; + if (!FieldName.FieldName.IsEmpty() && target.WaapiJsonObj.IsValid()) + { + target.WaapiJsonObj->TryGetBoolField(FieldName.FieldName, result); + } + return result; +} + +float UAkWaapiJsonManager::GetNumberField(const FAkWaapiFieldNames& FieldName, FAKWaapiJsonObject target) +{ + double result = TNumericLimits::Min(); + if (!FieldName.FieldName.IsEmpty() && target.WaapiJsonObj.IsValid()) + { + target.WaapiJsonObj->TryGetNumberField(FieldName.FieldName, result); + } + return result; +} + +int32 UAkWaapiJsonManager::GetIntegerField(const FAkWaapiFieldNames& FieldName, FAKWaapiJsonObject target) +{ + int32 result = INT32_MIN; + if (!FieldName.FieldName.IsEmpty() && target.WaapiJsonObj.IsValid()) + { + target.WaapiJsonObj->TryGetNumberField(FieldName.FieldName, result); + } + return result; +} + +FAKWaapiJsonObject UAkWaapiJsonManager::GetObjectField(const FAkWaapiFieldNames& FieldName, FAKWaapiJsonObject target) +{ + FAKWaapiJsonObject outResult = FAKWaapiJsonObject(); + const TSharedPtr *outJsonResult = nullptr; + if (!FieldName.FieldName.IsEmpty() && target.WaapiJsonObj.IsValid()) + { + outResult.WaapiJsonObj = target.WaapiJsonObj->GetObjectField(FieldName.FieldName); + } + return outResult; +} + +const TArray UAkWaapiJsonManager::GetArrayField(const FAkWaapiFieldNames& FieldName, FAKWaapiJsonObject target) +{ + TArray WaapiJsonArrayResult; + if (!FieldName.FieldName.IsEmpty() && target.WaapiJsonObj.IsValid()) + { + TArray> JsonArray = target.WaapiJsonObj->GetArrayField(FieldName.FieldName); + for (TSharedPtr JObj : JsonArray) + { + FAKWaapiJsonObject WaapiJsonObj = FAKWaapiJsonObject(); + WaapiJsonObj.WaapiJsonObj = JObj->AsObject(); + WaapiJsonArrayResult.Add(WaapiJsonObj); + } + } + return WaapiJsonArrayResult; +} + +FString UAkWaapiJsonManager::Conv_FAKWaapiJsonObjectToString(FAKWaapiJsonObject INAKWaapiJsonObject) +{ + FString ResultAsString; + FAkWaapiClient::JsonObjectToString(INAKWaapiJsonObject.WaapiJsonObj.ToSharedRef(), ResultAsString); + return ResultAsString; +} + +FText UAkWaapiJsonManager::Conv_FAKWaapiJsonObjectToText(FAKWaapiJsonObject INAKWaapiJsonObject) +{ + return FText::FromString(*Conv_FAKWaapiJsonObjectToString(INAKWaapiJsonObject)); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiBlueprints/AkWaapiUri.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiBlueprints/AkWaapiUri.cpp new file mode 100644 index 0000000..93a3709 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiBlueprints/AkWaapiUri.cpp @@ -0,0 +1,208 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ +AkWaapiUri.cpp +------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------ +includes. +------------------------------------------------------------------------------------*/ +#include "AkWaapiBlueprints/AkWaapiUri.h" +#include "AkAudioDevice.h" +#include "Widgets/Input/SSearchBox.h" +#include "Misc/ScopedSlowTask.h" +#include "Framework/Application/SlateApplication.h" +#include "AkAudioStyle.h" + +/*------------------------------------------------------------------------------------ +Defines +------------------------------------------------------------------------------------*/ +#define LOCTEXT_NAMESPACE "AkAudio" + +/*------------------------------------------------------------------------------------ +Statics and Globals +------------------------------------------------------------------------------------*/ +namespace SAkWaapiUri_Helpers +{ +#include "AkWaapiUriList.inc" +} +/*------------------------------------------------------------------------------------ +Helpers +------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------ +UAkWaapiUriConv +------------------------------------------------------------------------------------*/ +UAkWaapiUriConv::UAkWaapiUriConv(const class FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + // Property initialization +} + +FString UAkWaapiUriConv::Conv_FAkWaapiUriToString(const FAkWaapiUri& INAkWaapiUri) +{ + return INAkWaapiUri.Uri; +} + +FText UAkWaapiUriConv::Conv_FAkWaapiUriToText(const FAkWaapiUri& INAkWaapiUri) +{ + return FText::FromString(*INAkWaapiUri.Uri); +} + +/*------------------------------------------------------------------------------------ +SAkWaapiUri +------------------------------------------------------------------------------------*/ +SAkWaapiUri::SAkWaapiUri() +{} + +SAkWaapiUri::~SAkWaapiUri() +{} + +void SAkWaapiUri::Construct(const FArguments& InArgs) +{ + OnDragDetected = InArgs._OnDragDetected; + OnSelectionChanged = InArgs._OnSelectionChanged; + + if (InArgs._FocusSearchBoxWhenOpened) + { + RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &SAkWaapiUri::SetFocusPostConstruct)); + } + + SearchBoxFilter = MakeShareable(new StringFilter(StringFilter::FItemToStringArray::CreateSP(this, &SAkWaapiUri::PopulateSearchStrings))); + SearchBoxFilter->OnChanged().AddSP(this, &SAkWaapiUri::FilterUpdated); + + ChildSlot + [ + SNew(SBorder) + .Padding(4) + .BorderImage(FAkAudioStyle::GetBrush("AudiokineticTools.GroupBorder")) + [ + SNew(SVerticalBox) + // Search + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 1, 0, 3) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + InArgs._SearchContent.Widget + ] + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SAssignNew(SearchBoxPtr, SSearchBox) + .HintText(LOCTEXT("UriSearchHint", "Search a Uri")) + .ToolTipText(LOCTEXT("UriSearchTooltip", "Type here to search for a Uri")) + .OnTextChanged(SearchBoxFilter.Get(), &StringFilter::SetRawFilterText) + .SelectAllTextWhenFocused(false) + .DelayChangeNotificationsWhileTyping(true) + ] + ] + // Tree + + SVerticalBox::Slot() + .FillHeight(1.f) + [ + SAssignNew(ListViewPtr, SListView< TSharedPtr >) + .ListItemsSource(&UriList) + .OnGenerateRow(this, &SAkWaapiUri::GenerateRow) + .ItemHeight(18) + .SelectionMode(InArgs._SelectionMode) + .OnSelectionChanged(this, &SAkWaapiUri::ListSelectionChanged) + .ClearSelectionOnClick(false) + ] + ] + ]; + + for (const auto& Uri : SAkWaapiUri_Helpers::FullUriList) + { + UriList.Add(MakeShareable(new FString(Uri))); + } + UriList.Sort([](TSharedPtr< FString > Firststr, TSharedPtr< FString > Secondstr) { return *Firststr.Get() < *Secondstr.Get(); }); + ListViewPtr->RequestListRefresh(); +} + +TSharedRef SAkWaapiUri::GenerateRow(TSharedPtr in_Uri, const TSharedRef& OwnerTable) +{ + check(in_Uri.IsValid()); + + TSharedPtr NewRow = SNew(STableRow< TSharedPtr >, OwnerTable) + [ + SNew(STextBlock) + .Text(FText::FromString(*in_Uri.Get())) + .HighlightText(SearchBoxFilter.Get(), &StringFilter::GetRawFilterText) + ]; + return NewRow.ToSharedRef(); +} + +void SAkWaapiUri::PopulateSearchStrings(const FString& in_Uri, OUT TArray< FString >& OutSearchStrings) const +{ + OutSearchStrings.Add(in_Uri); +} + +void SAkWaapiUri::FilterUpdated() +{ + FScopedSlowTask SlowTask(2.f, LOCTEXT("AK_PopulatingPicker", "Populating Uri Picker...")); + SlowTask.MakeDialog(); + + UriList.Empty(SAkWaapiUri_Helpers::FullUriListSize); + + FString FilterString = SearchBoxFilter->GetRawFilterText().ToString(); + if (FilterString.IsEmpty()) + { + for (const auto& Uri : SAkWaapiUri_Helpers::FullUriList) + { + UriList.Add(MakeShareable(new FString(Uri))); + } + } + else + { + for (const auto& Uri : SAkWaapiUri_Helpers::FullUriList) + { + if (Uri.Contains(FilterString)) + { + UriList.Add(MakeShareable(new FString(Uri))); + } + } + } + UriList.Sort([](TSharedPtr< FString > Firststr, TSharedPtr< FString > Secondstr) { return *Firststr.Get() < *Secondstr.Get(); }); + ListViewPtr->RequestListRefresh(); +} + +void SAkWaapiUri::ListSelectionChanged(TSharedPtr< FString > in_Uri, ESelectInfo::Type /*SelectInfo*/) +{ + if (OnSelectionChanged.IsBound()) + OnSelectionChanged.Execute(in_Uri, ESelectInfo::OnMouseClick); +} + +const TArray> SAkWaapiUri::GetSelectedUri() const +{ + return ListViewPtr->GetSelectedItems(); +} + +EActiveTimerReturnType SAkWaapiUri::SetFocusPostConstruct(double InCurrentTime, float InDeltaTime) +{ + FWidgetPath WidgetToFocusPath; + FSlateApplication::Get().GeneratePathToWidgetUnchecked(SearchBoxPtr.ToSharedRef(), WidgetToFocusPath); + FSlateApplication::Get().SetKeyboardFocus(WidgetToFocusPath, EFocusCause::SetDirectly); + + return EActiveTimerReturnType::Stop; +} +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiBlueprints/AkWaapiUriCustomization.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiBlueprints/AkWaapiUriCustomization.cpp new file mode 100644 index 0000000..81d82de --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiBlueprints/AkWaapiUriCustomization.cpp @@ -0,0 +1,123 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#if WITH_EDITOR +#include "AkWaapiBlueprints/AkWaapiUriCustomization.h" +#include "AkAudioDevice.h" +#include "Framework/Application/SlateApplication.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SButton.h" +#include "DetailWidgetRow.h" +#include "AkWaapiBlueprints/AkWaapiUri.h" +#include "AkAudioStyle.h" + +#if UE_5_0_OR_LATER +#include "Framework/Docking/TabManager.h" +#endif + +#define LOCTEXT_NAMESPACE "AkWaapiUriCustomization" + +TSharedRef FAkWaapiUriCustomization::MakeInstance() +{ + return MakeShareable(new FAkWaapiUriCustomization()); +} + +void FAkWaapiUriCustomization::CustomizeHeader(TSharedRef StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + UriHandle = StructPropertyHandle->GetChildHandle("Uri"); + + if (UriHandle.IsValid()) + { + TSharedPtr PickerWidget = nullptr; + + PickerWidget = SAssignNew(PickerButton, SButton) + .ButtonStyle(FAkAudioStyle::Get(), "AudiokineticTools.HoverHintOnly") + .ToolTipText(LOCTEXT("WwiseUriToolTipText", "Choose A Uri")) + .OnClicked(FOnClicked::CreateSP(this, &FAkWaapiUriCustomization::OnPickContent, UriHandle.ToSharedRef())) + .ContentPadding(2.0f) + .ForegroundColor(FSlateColor::UseForeground()) + .IsFocusable(false) + [ + SNew(SImage) + .Image(FAkAudioStyle::GetBrush("AudiokineticTools.Button_EllipsisIcon")) + .ColorAndOpacity(FSlateColor::UseForeground()) + ]; + + HeaderRow.ValueContent() + .MinDesiredWidth(125.0f) + .MaxDesiredWidth(600.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + [ + UriHandle->CreatePropertyValueWidget() + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(FMargin(4.0f, 0.0f, 0.0f, 0.0f)) + .VAlign(VAlign_Center) + [ + PickerWidget.ToSharedRef() + ] + ] + .NameContent() + [ + StructPropertyHandle->CreatePropertyNameWidget() + ]; + } +} + +void FAkWaapiUriCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ +} + +FReply FAkWaapiUriCustomization::OnPickContent(TSharedRef PropertyHandle) +{ + Window = SNew(SWindow) + .Title(LOCTEXT("UriPickerWindowTitle", "Choose A Uri")) + .SizingRule(ESizingRule::UserSized) + .AutoCenter(EAutoCenter::PreferredWorkArea) + .ClientSize(FVector2D(350, 400)); + + Window->SetContent( + SNew(SBorder) + [ + SNew(SAkWaapiUri) + .FocusSearchBoxWhenOpened(true) + .SelectionMode(ESelectionMode::Single) + .OnSelectionChanged(this, &FAkWaapiUriCustomization::UriSelectionChanged) + ] + ); + + TSharedPtr RootWindow = FGlobalTabmanager::Get()->GetRootWindow(); + FSlateApplication::Get().AddWindowAsNativeChild(Window.ToSharedRef(), RootWindow.ToSharedRef()); + return FReply::Handled(); +} + +void FAkWaapiUriCustomization::UriSelectionChanged(TSharedPtr< FString > in_Uri, ESelectInfo::Type SelectInfo) +{ + if (in_Uri.IsValid()) + { + UriHandle->SetValue(*in_Uri.Get()); + Window->RequestDestroyWindow(); + } +} +#undef LOCTEXT_NAMESPACE + +#endif//WITH_EDITOR \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiBlueprints/AkWaapiUriList.inc b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiBlueprints/AkWaapiUriList.inc new file mode 100644 index 0000000..76b3593 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiBlueprints/AkWaapiUriList.inc @@ -0,0 +1,278 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +static const FString FullUriList[] = { + // Retrieve global Wwise information. + TEXT("ak.wwise.core.getInfo"), + // Bring Wwise main window to foreground. Refer to SetForegroundWindow and AllowSetForegroundWindow on MSDN for more information on the restrictions. Refer to ak.wwise.core.getInfo to obtain the Wwise process ID for AllowSetForegroundWindow. + TEXT("ak.wwise.ui.bringToForeground"), + // Asynchronously post an Event to the sound engine (by event ID). See AK::SoundEngine::PostEvent. + TEXT("ak.soundengine.postEvent"), + // Executes an action on all nodes that are referenced in the specified event in a Play action. See AK::SoundEngine::ExecuteActionOnEvent. + TEXT("ak.soundengine.executeActionOnEvent"), + // Register a game object. Registering a game object twice does nothing. Unregistering it once unregisters it no matter how many times it has been registered. See AK::SoundEngine::RegisterGameObj. + TEXT("ak.soundengine.registerGameObj"), + // Stops the current content, associated to the specified playing ID, from playing. See AK::SoundEngine::StopPlayingID. + TEXT("ak.soundengine.stopPlayingID"), + // Stop playing the current content associated to the specified game object ID. If no game object is specified, all sounds are stopped. See AK::SoundEngine::StopAll. + TEXT("ak.soundengine.stopAll"), + // Display a message in the Profiler's Capture Log view. + TEXT("ak.soundengine.postMsgMonitor"), + // Set a game object's obstruction and occlusion levels. This function is used to affect how an object should be heard by a specific listener. See AK::SoundEngine::SetObjectObstructionAndOcclusion. + TEXT("ak.soundengine.setObjectObstructionAndOcclusion"), + // Set the output bus volume (direct) to be used for the specified game object. See AK::SoundEngine::SetGameObjectOutputBusVolume. + TEXT("ak.soundengine.setGameObjectOutputBusVolume"), + // Sets the Auxiliary Busses to route the specified game object. See AK::SoundEngine::SetGameObjectAuxSendValues. + TEXT("ak.soundengine.setGameObjectAuxSendValues"), + // Posts the specified Trigger. See AK::SoundEngine::PostTrigger. + TEXT("ak.soundengine.postTrigger"), + // Sets the State of a Switch Group. See AK::SoundEngine::SetSwitch. + TEXT("ak.soundengine.setSwitch"), + // Sets the State of a State Group. See AK::SoundEngine::SetState. + TEXT("ak.soundengine.setState"), + // Resets the value of a real-time parameter control to its default value, as specified in the Wwise project. See AK::SoundEngine::ResetRTPCValue. + TEXT("ak.soundengine.resetRTPCValue"), + // Sets the value of a real-time parameter control. See AK::SoundEngine::SetRTPCValue. + TEXT("ak.soundengine.setRTPCValue"), + // Sets a listener's spatialization parameters. This lets you define listener-specific volume offsets for each audio channel. See AK::SoundEngine::SetListenerSpatialization. + TEXT("ak.soundengine.setListenerSpatialization"), + // Sets multiple positions for a single game object. Setting multiple positions for a single game object is a way to simulate multiple emission sources while using the resources of only one voice. This can be used to simulate wall openings, area sounds, or multiple objects emitting the same sound in the same area. See AK::SoundEngine::SetMultiplePositions. + TEXT("ak.soundengine.setMultiplePositions"), + // Sets the position of a game object. See AK::SoundEngine::SetPosition. + TEXT("ak.soundengine.setPosition"), + // Sets the scaling factor of a game object. You can modify the attenuation computations on this game object to simulate sounds with a larger or smaller affected areas. See AK::SoundEngine::SetScalingFactor. + TEXT("ak.soundengine.setScalingFactor"), + // Sets the default active listeners for all subsequent game objects that are registered. See AK::SoundEngine::SetDefaultListeners. + TEXT("ak.soundengine.setDefaultListeners"), + // Sets a single game object's active listeners. By default, all new game objects have no listeners active, but this behavior can be overridden with SetDefaultListeners(). Inactive listeners are not computed. See AK::SoundEngine::SetListeners. + TEXT("ak.soundengine.setListeners"), + // Seeks inside all playing objects that are referenced in Play Actions of the specified Event. See AK::SoundEngine::SeekOnEvent. + TEXT("ak.soundengine.seekOnEvent"), + // Unregisters a game object. Registering a game object twice does nothing. Unregistering it once unregisters it no matter how many times it has been registered. Unregistering a game object while it is in use is allowed, but the control over the parameters of this game object is lost. For example, say a sound associated with this game object is a 3D moving sound. It stops moving when the game object is unregistered, and there is no way to regain control over the game object. See AK::SoundEngine::UnregisterGameObj. + TEXT("ak.soundengine.unregisterGameObj"), + // Retrieves the list of topics to which a client can subscribe. + TEXT("ak.wwise.waapi.getTopics"), + // Retrieves the list of functions. + TEXT("ak.wwise.waapi.getFunctions"), + // Retrieves the JSON schema of a Waapi URI. + TEXT("ak.wwise.waapi.getSchema"), + // Opens a project, specified by path. Please refer to \ref ak_wwise_core_project_loaded for further explanations on how to be notified when the operation has completed. + TEXT("ak.wwise.ui.project.open"), + // Closes the current project. + TEXT("ak.wwise.ui.project.close"), + // Saves the current project. + TEXT("ak.wwise.core.project.save"), + // Renames an object. + TEXT("ak.wwise.core.object.setName"), + // Sets an object's reference value. Refer to \ref wobjects_index for more information on the references available on each object type. + TEXT("ak.wwise.core.object.setReference"), + // Sets a property value of an object for a specific platform. Refer to \ref wobjects_index for more information on the properties available on each object type. Refer to \ref ak_wwise_core_object_setreference to set a reference to an object. + TEXT("ak.wwise.core.object.setProperty"), + // Sets the randomizer values of a property of an object for a specific platform. Refer to \ref wobjects_index for more information on the properties available on each object type. + TEXT("ak.wwise.core.object.setRandomizer"), + // Sets the object's notes. + TEXT("ak.wwise.core.object.setNotes"), + // Executes a command. Some commands can take a list of objects as parameters. Refer to \ref globalcommandsids for the available commands. + TEXT("ak.wwise.ui.commands.execute"), + // Gets the list of commands. + TEXT("ak.wwise.ui.commands.getCommands"), + // Retrieves the list of objects currently selected by the user in the active view. + TEXT("ak.wwise.ui.getSelectedObjects"), + // Gets the specified attenuation curve for a given attenuation object. + TEXT("ak.wwise.core.object.getAttenuationCurve"), + // Sets the specified attenuation curve for a given attenuation object. + TEXT("ak.wwise.core.object.setAttenuationCurve"), + // Assigns a Switch Container's child to a Switch. This is the equivalent of doing a drag&drop of the child to a state in the Assigned Objects view. The child is always added at the end for each state. + TEXT("ak.wwise.core.switchContainer.addAssignment"), + // Removes an assignment between a Switch Container's child and a State. + TEXT("ak.wwise.core.switchContainer.removeAssignment"), + // Returns the list of assignments between a Switch Container's children and states. + TEXT("ak.wwise.core.switchContainer.getAssignments"), + // Creates an object of type 'type', as a child of 'parent'. Refer to \ref waapi_import for more information about creating objects. Also refer to \ref ak_wwise_core_audio_import to import audio files to Wwise. + TEXT("ak.wwise.core.object.create"), + // Moves an object to the given parent. Returns the moved object. + TEXT("ak.wwise.core.object.move"), + // Copies an object to the given parent. + TEXT("ak.wwise.core.object.copy"), + // Deletes the specified object. + TEXT("ak.wwise.core.object.delete"), + // Performs a query and returns specified data for each object in query result. Refer to \ref waapi_query for more information. + TEXT("ak.wwise.core.object.get"), + // Creates Wwise objects and imports audio files. This function uses the same importation processor available through the Tab Delimited import in the Audio File Importer. The function returns an array of all objects created, replaced or re-used. Use the options to specify how the objects are returned. For more information, refer to \ref waapi_import. + TEXT("ak.wwise.core.audio.import"), + // Scripted object creation and audio file import from a tab-delimited file. + TEXT("ak.wwise.core.audio.importTabDelimited"), + // Connects the Wwise Authoring application to a Wwise Sound Engine running executable. The host must be running code with communication enabled. If only "host" is provided, Wwise connects to the first Sound Engine instance found. To distinguish between different instances, you can also provide the name of the application to connect to. + TEXT("ak.wwise.core.remote.connect"), + // Disconnects the Wwise Authoring application from a connected Wwise Sound Engine running executable. + TEXT("ak.wwise.core.remote.disconnect"), + // Retrieves all consoles available for connecting Wwise Authoring to a Sound Engine instance. + TEXT("ak.wwise.core.remote.getAvailableConsoles"), + // Retrieves the connection status. + TEXT("ak.wwise.core.remote.getConnectionStatus"), + // Begins an undo group. Make sure to call \ref ak_wwise_core_undo_endgroup exactly once for every ak.wwise.core.beginUndoGroup call you make. Calls to ak.wwise.core.undo.beginGroup can be nested. + TEXT("ak.wwise.core.undo.beginGroup"), + // Cancels the last undo group. Please note that this does not revert the operations made since the last \ref ak_wwise_core_undo_begingroup call. + TEXT("ak.wwise.core.undo.cancelGroup"), + // Ends the last undo group. + TEXT("ak.wwise.core.undo.endGroup"), + // Retrieves the list of all object types registered in Wwise's object model. This function returns the equivalent of \ref wobjects_index . + // \deprecated in favor of ak.wwise.core.object.getTypes + TEXT("ak.wwise.core.plugin.getList"), + // Retrieves the list of all object types registered in Wwise's object model. This function returns the equivalent of \ref wobjects_index . + TEXT("ak.wwise.core.object.getTypes"), + // Retrieves information about an object property. + // \deprecated in favor of ak.wwise.core.object.getPropertyInfo + TEXT("ak.wwise.core.plugin.getProperty"), + // Retrieves information about an object property. + TEXT("ak.wwise.core.object.getPropertyInfo"), + // Retrieves the list of property and reference names for an object. + // \deprecated in favor of ak.wwise.core.object.getPropertyAndReferenceNames + TEXT("ak.wwise.core.plugin.getProperties"), + // Retrieves the list of property and reference names for an object. + // \deprecated in favor of ak.wwise.core.object.getPropertyAndReferenceNames + TEXT("ak.wwise.core.object.getPropertyNames"), + // Retrieves the list of property and reference names for an object. + TEXT("ak.wwise.core.object.getPropertyAndReferenceNames"), + // Returns true if a property is enabled based on the values of the properties it depends on. + TEXT("ak.wwise.core.object.isPropertyEnabled"), + // Enables debug assertions. Every call to enableAsserts with 'false' increments the ref count. Calling with true decrements the ref count. This is only available with Debug builds. + TEXT("ak.wwise.debug.enableAsserts"), + // Private use only. + TEXT("ak.wwise.debug.testAssert"), + // Private use only. + TEXT("ak.wwise.debug.testCrash"), + // Enables or disables the automation mode for Wwise. This reduces the potential interruptions caused by message boxes and dialogs. For instance, enabling the automation mode silently accepts: project migration, project load log, EULA acceptance, project licence display and generic message boxes. + TEXT("ak.wwise.debug.enableAutomationMode"), + // Captures a part of the Wwise UI relative to a view. + TEXT("ak.wwise.ui.captureScreen"), + // Retrieves a SoundBank's inclusion list. + TEXT("ak.wwise.core.soundbank.getInclusions"), + // Modifies a SoundBank's inclusion list. The 'operation' argument determines how the 'inclusions' argument modifies the SoundBank's inclusion list; 'inclusions' may be added to / removed from / replace the SoundBank's inclusion list. + TEXT("ak.wwise.core.soundbank.setInclusions"), + // Generate a list of SoundBank in memory without writing them to disk, subscribe to ak.wwise.core.soundbank.generated to get SoundBank structure info and the bank data as base64. + TEXT("ak.wwise.core.soundbank.generate"), + // Migrate and save the project. + TEXT("ak.wwise.cli.migrate"), + // Imports a tab-delimited file to create and modify different object hierarchies. The project is automatically migrated (if required). It is also automatically saved following the import. + TEXT("ak.wwise.cli.tabDelimitedImport"), + // Starts a command-line Wwise Authoring API server, to which client applications, using the Wwise Authoring API, can connect. + TEXT("ak.wwise.cli.waapiServer"), + // Creates a blank new project. The project must not already exist. If the folder does not exist, it is created. + TEXT("ak.wwise.cli.createNewProject"), + // Dump the objects model of a project as a JSON file. + TEXT("ak.wwise.cli.dumpObjects"), + // Adds a new platform to a project. The platform must not already exist. + TEXT("ak.wwise.cli.addNewPlatform"), + // External Source conversion. While External Source conversion is also triggered by SoundBank generation, this operation can be used to process sources not contained in the Wwise Project. + TEXT("ak.wwise.cli.convertExternalSource"), + // SoundBank generation. SoundBank generation is performed according to the settings stored in the project. Custom user settings are ignored when SoundBank generation is launched from the command line. However, some of these settings can be overridden from the command line. + TEXT("ak.wwise.cli.generateSoundbank"), + // Moves the project's media IDs from its work units (.wwu) to a single file, .mediaid. This command will force a save of all the project's work units. + TEXT("ak.wwise.cli.moveMediaIdsToSingleFile"), + // Moves the project's media IDs from a single xml file .mediaid to its work units (.wwu). This command will force a save of all the project's work units. + TEXT("ak.wwise.cli.moveMediaIdsToWorkUnits"), + // Loads the project and updates the contents of .mediaid, if it exists. + TEXT("ak.wwise.cli.updateMediaIdsInSingleFile"), + // Retrieves the latest log for a specific channel. Refer to \ref ak_wwise_core_log_itemadded to be notified when a item is added to the log. + TEXT("ak.wwise.core.log.get"), + // Creates a transport object for the given Wwise object. The return transport object can be used to play, stop, pause and resume the Wwise object via the other transport functions. + TEXT("ak.wwise.core.transport.create"), + // Destroys the given transport object. + TEXT("ak.wwise.core.transport.destroy"), + // Gets the state of the given transport object. + TEXT("ak.wwise.core.transport.getState"), + // Returns the list of transport objects. + TEXT("ak.wwise.core.transport.getList"), + // Executes an action on the given transport object, or all transport objects if none is specified. + TEXT("ak.wwise.core.transport.executeAction"), + // Gets the min/max peak pairs, in the given region of an audio source, as a collection of binary strings (one per channel). The strings are base-64 encoded, 16-bit signed int arrays, with min and max values being interleaved. If getCrossChannelPeaks is true, only one binary string represents the peaks across all channels globally. + TEXT("ak.wwise.core.audioSourcePeaks.getMinMaxPeaksInRegion"), + // Gets the min/max peak pairs in the entire trimmed region of an audio source, for each channel, as an array of binary strings (one per channel). The strings are base-64 encoded, 16-bit signed int arrays, with min and max values being interleaved. If getCrossChannelPeaks is true, there is only one binary string representing peaks across all channels globally. + TEXT("ak.wwise.core.audioSourcePeaks.getMinMaxPeaksInTrimmedRegion"), + // Registers an array of add-on commands. Registered commands remain until the Wwise process is terminated. Refer to \ref defining_custom_commands for more information about registering commands. Also refer to \ref ak_wwise_ui_commands_executed. + TEXT("ak.wwise.ui.commands.register"), + // Unregisters an array of add-on UI commands. + TEXT("ak.wwise.ui.commands.unregister"), + // Retrieves the voices at a specific profiler capture time. + TEXT("ak.wwise.core.profiler.getVoices"), + // Retrieves active RTPCs at a specific profiler capture time. + TEXT("ak.wwise.core.profiler.getRTPCs"), + // Retrieves the busses at a specific profiler capture time. + TEXT("ak.wwise.core.profiler.getBusses"), + // Retrieves all parameters affecting voice volume, highpass and lowpass for a voice path, resolved from pipeline IDs. + TEXT("ak.wwise.core.profiler.getVoiceContributions"), + // Returns the current time of the specified profiler cursor, in milliseconds. + TEXT("ak.wwise.core.profiler.getCursorTime"), + // Starts the profiler capture and returns the time at the beginning of the capture, in milliseconds. + TEXT("ak.wwise.core.profiler.startCapture"), + // Stops the profiler capture and returns the time at the end of the capture, in milliseconds. + TEXT("ak.wwise.core.profiler.stopCapture"), + // Sent at the end of an import operation. + TEXT("ak.wwise.core.audio.imported"), + // Sent when an object reference is changed. + TEXT("ak.wwise.core.object.referenceChanged"), + // Sent when an assignment is added to a Switch Container. + TEXT("ak.wwise.core.switchContainer.assignmentAdded"), + // Sent when an assignment is removed from a Switch Container. + TEXT("ak.wwise.core.switchContainer.assignmentRemoved"), + // Sent when an object is renamed. Publishes the renamed object. + TEXT("ak.wwise.core.object.nameChanged"), + // Sent when the object's notes are changed. + TEXT("ak.wwise.core.object.notesChanged"), + // Sent when an object is created. + TEXT("ak.wwise.core.object.created"), + // Sent prior to an object's deletion. + TEXT("ak.wwise.core.object.preDeleted"), + // Sent following an object's deletion. + TEXT("ak.wwise.core.object.postDeleted"), + // Sent when an object is added as a child to another object. + TEXT("ak.wwise.core.object.childAdded"), + // Sent when an object is removed from the children of another object. + TEXT("ak.wwise.core.object.childRemoved"), + // Sent when one or many curves are changed. + TEXT("ak.wwise.core.object.curveChanged"), + // Sent when an attenuation curve is changed. + TEXT("ak.wwise.core.object.attenuationCurveChanged"), + // Sent when an attenuation curve's link/unlink is changed. + TEXT("ak.wwise.core.object.attenuationCurveLinkChanged"), + // Sent when the watched property of an object changes. + TEXT("ak.wwise.core.object.propertyChanged"), + // Sent when a single SoundBank is generated. This could be sent multiple times during SoundBank generation, for every SoundBank generated and for every platform. To generate SoundBanks, refer to \ref ak_wwise_ui_commands_execute with one of the SoundBank generation commands. Refer to \ref globalcommandsids for the list of commands. + TEXT("ak.wwise.core.soundbank.generated"), + // Sent when all soundbanks are generated. + TEXT("ak.wwise.core.soundbank.generationDone"), + // Sent when an item is added to the log. This could be used to retrieve items added to the SoundBank generation log. To retrieve the complete log, refer to \ref ak_wwise_core_log_get. + TEXT("ak.wwise.core.log.itemAdded"), + // Sent when the selection changes in the project. + TEXT("ak.wwise.ui.selectionChanged"), + // Sent when the project has been successfully loaded. + TEXT("ak.wwise.core.project.loaded"), + // Sent when the project begins closing. + TEXT("ak.wwise.core.project.preClosed"), + // Sent when the after the project is completely closed. + TEXT("ak.wwise.core.project.postClosed"), + // Sent when the project has been saved. + TEXT("ak.wwise.core.project.saved"), + // Sent when the transport's state has changed. + TEXT("ak.wwise.core.transport.stateChanged"), + // Sent when an assert has failed. This is only available in Debug builds. + TEXT("ak.wwise.debug.assertFailed"), + // Sent when a command is executed. The objects for which the command is executed are sent in the publication. + TEXT("ak.wwise.ui.commands.executed"), +}; +enum { FullUriListSize = sizeof(FullUriList) / sizeof(*FullUriList) }; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiClient.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiClient.cpp new file mode 100644 index 0000000..353edb2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiClient.cpp @@ -0,0 +1,1222 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkWaapiClient.cpp: Audiokinetic WAAPI interface object. + + Unreal is RHS with Y and Z swapped (or technically LHS with flipped axis) + +=============================================================================*/ + +/*------------------------------------------------------------------------------------ + Audio includes. +------------------------------------------------------------------------------------*/ +#include "AkWaapiClient.h" +#include "AkAudioDevice.h" +#include "AkSettings.h" +#include "AkSettingsPerUser.h" +#include "AkUnrealHelper.h" +#include "Serialization/JsonSerializer.h" +#include "Async/Async.h" +#include "Misc/ScopeLock.h" +#include "Misc/CoreDelegates.h" +#include "Wwise/API/WAAPI.h" + +#if AK_SUPPORT_WAAPI + +#if PLATFORM_WINDOWS +// Problem with the Windows API as a whole: it uses noexcept straight up, whether exceptions are used or not, generating a warning that Unreal then converts to an error. +#if _MSC_VER >= 1910 +#pragma warning (disable: 4577) +#endif // _MSC_VER >= 1910 +#endif // #if PLATFORM_WINDOWS + +typedef AK::WwiseAuthoringAPI::JsonProvider JsonProvider; +#endif + +/*------------------------------------------------------------------------------------ +Statics and Globals +------------------------------------------------------------------------------------*/ +FAkWaapiClient* g_AkWaapiClient = nullptr; + +/*------------------------------------------------------------------------------------ +FAkWaapiClientConnectionHandler +------------------------------------------------------------------------------------*/ + +FAkWaapiClientConnectionHandler::FAkWaapiClientConnectionHandler(FAkWaapiClient& in_Client) : m_Client(in_Client) +{ + WaitEvent = FPlatformProcess::GetSynchEventFromPool(true); +} + +FAkWaapiClientConnectionHandler::~FAkWaapiClientConnectionHandler() +{ + FPlatformProcess::ReturnSynchEventToPool(WaitEvent); + WaitEvent = nullptr; +} + +void FAkWaapiClientConnectionHandler::RegisterAutoConnectChangedCallback() +{ +#if WITH_EDITOR + FScopeLock Lock(&AkSettingsSection); + if (auto AkSettingsPerUser = GetDefault()) + { + AutoConnectChangedHandle = AkSettingsPerUser->OnAutoConnectToWaapiChanged.AddLambda([this, AkSettingsPerUser]() + { + ResetReconnectionDelay(); + if (AkSettingsPerUser->bAutoConnectToWAAPI) + WaitEvent->Trigger(); + else + { + m_Client.BroadcastConnectionLost(); + } + }); + } +#endif +} + +void FAkWaapiClientConnectionHandler::Wake() +{ + WaitEvent->Trigger(); +} + +/* FRunnable interface */ +bool FAkWaapiClientConnectionHandler::Init() +{ + return true; +} + +uint32 FAkWaapiClientConnectionHandler::Run() +{ +#if AK_SUPPORT_WAAPI + AKASSERT(!IsInGameThread()); + while (!ThreadShouldExit) + { + if (!m_Client.IsProjectLoaded()) + { + /** Check if we should attempt to reconnect according to the Wwise Plugin Settings. */ + bool bReconnect = !m_Client.IsDisconnecting() && !m_Client.AppIsExiting(); + { + FScopeLock Lock(&AkSettingsSection); + if (auto AkSettingsPerUser = GetDefault()) + { + bReconnect = AkSettingsPerUser->bAutoConnectToWAAPI; + } + } + /** If we previously had a connection (and we're not exiting), broadcast connection lost.*/ + if (hadConnection && !m_Client.AppIsExiting()) + { + if (bReconnect) + UE_LOG(LogAkAudio, Warning, TEXT("Lost connection to WAAPI client. Attempting reconnection ...")); + hadConnection = false; + AsyncTask(ENamedThreads::GameThread, [this]() + { + m_Client.BroadcastConnectionLost(); + }); + } + /** If we should reconnect, attempt a reconnection and, if successful, call the client's connection established function on the game thread. + * Otherwise, print a failed connection log. + */ + if (bReconnect) + { + if (AttemptReconnect()) + { + hadConnection = true; + m_Client.SetConnectionClosing(false); + ResetReconnectionDelay(); + AsyncTask(ENamedThreads::GameThread, [this]() + { + m_Client.ConnectionEstablished(); + }); + } + else + { + if (LogOutputCount.GetValue() < 7) + { + UE_LOG(LogAkAudio, Warning, TEXT("Failed to connect to WAAPI client on local host. Trying again in %i seconds."), ReconnectDelay.GetValue()); + LogOutputCount.Increment(); + } + } + /** Delay the next reconnection attempt according to the ReconnectDelay value. */ + const int iCurrentDelay = ReconnectDelay.GetValue(); + if (iCurrentDelay < m_iMaxReconnectDelay) + ReconnectDelay.Set(iCurrentDelay * 2); + WaitEvent->Wait(iCurrentDelay * 1000); + WaitEvent->Reset(); + } + else + { + /** We shouldn't attempt reconnection, so wait until the auto-reconnect option is changed. */ + WaitEvent->Wait(); + WaitEvent->Reset(); + } + + } + else /** We're already connected. Check connection status. */ + { + TSharedRef args = MakeShareable(new FJsonObject()); + TSharedRef options = MakeShareable(new FJsonObject()); + TSharedPtr result = MakeShareable(new FJsonObject()); + m_Client.Call(ak::wwise::core::getInfo, args, options, result, 500, true); + WaitEvent->Wait(ConnectionMonitorDelay.GetValue() * 1000); + WaitEvent->Reset(); + } + + } +#endif + return 0; +} + +void FAkWaapiClientConnectionHandler::ResetReconnectionDelay() +{ + bool bReconnect = true; + { + FScopeLock Lock(&AkSettingsSection); + if (auto AkSettingsPerUser = GetDefault()) + { + bReconnect = AkSettingsPerUser->bAutoConnectToWAAPI; + } + } + if (bReconnect && !m_Client.AppIsExiting() && !m_Client.IsDisconnecting()) + { + ReconnectDelay.Set(2); + LogOutputCount.Set(0); + } +} + +void FAkWaapiClientConnectionHandler::Stop() +{ + ThreadShouldExit = true; +} + +void FAkWaapiClientConnectionHandler::Exit() +{ + ThreadShouldExit = true; +} + +bool FAkWaapiClientConnectionHandler::AttemptReconnect() +{ +#if AK_SUPPORT_WAAPI + if (m_Client.AttemptConnection()) + { + UE_LOG(LogAkAudio, Log, TEXT("Successfully connected to Wwise Authoring on localhost.")); + return true; + } +#endif + return false; +} + +/*------------------------------------------------------------------------------------ +Helpers +------------------------------------------------------------------------------------*/ +struct FAkWaapiClientImpl +{ + void Init(FAkWaapiClient& in_Client) + { +#if AK_SUPPORT_WAAPI + auto* WAAPI = IWAAPI::Get(); + if (UNLIKELY(!WAAPI)) + { + return; + } + m_Client = WAAPI->NewClient(); + if (UNLIKELY(!m_Client)) + { + return; + } + + m_pConnectionHandler = MakeShareable(new FAkWaapiClientConnectionHandler(in_Client)); + FString ThreadName(FString::Printf(TEXT("WAAPIClientConnectionThread%i"), ThreadCounter.Increment())); + m_pReconnectionThread = MakeShareable(FRunnableThread::Create(m_pConnectionHandler.Get(), + *ThreadName, 0, + EThreadPriority::TPri_BelowNormal)); + + m_pConnectionHandler->RegisterAutoConnectChangedCallback(); +#endif + } + + ~FAkWaapiClientImpl() + { +#if AK_SUPPORT_WAAPI + if (m_pConnectionHandler.IsValid()) + { + m_pConnectionHandler->Exit(); + m_pConnectionHandler->Wake(); + } + + if (m_pReconnectionThread.IsValid()) + { + if (!m_pReconnectionThread->Kill(true)) + { + UE_LOG(LogAkAudio, Error, TEXT("WAAPI Connection Thread Failed to Exit!")); + } + } + + delete m_Client; m_Client = nullptr; +#endif + } + +#if AK_SUPPORT_WAAPI + /** Map containing id keys and WampEventCallback values. */ + TMap m_wampEventCallbackMap; + IWAAPI::Client* m_Client = nullptr; + /** A non-0 value indicates that UE is exiting. */ + FThreadSafeCounter AppExitingCounter = 0; + FThreadSafeCounter ThreadCounter; + /** Thread on which the WAAPI connection is monitored. */ + TSharedPtr m_pReconnectionThread; + /** The connection to WAAPI is monitored by this connection handler. + * It tries to reconnect when connection is lost, and continuously polls WAAPI for the connection status when WAAPI is connected. + * This behaviour can be disabled in AkSettings using the AutoConnectToWaapi boolean option. + */ + TSharedPtr m_pConnectionHandler; + FCriticalSection ClientSection; + + /** Flag indicating whether the correct project has been loaded (it's "correct" if it matches the Project Path in AkSettings.) */ + FThreadSafeBool bProjectLoaded = false; + + /** Flag indicating if the connection is being killed and shouldn't be restarted. */ + FThreadSafeBool bIsConnectionClosing = false; +#endif +}; + +bool FAkWaapiClient::JsonObjectToString(const TSharedRef& in_jsonObject, FString& ou_jsonObjectString) +{ + TSharedRef> JsonWriterArgs = TJsonWriterFactory<>::Create(&ou_jsonObjectString); + auto result = FJsonSerializer::Serialize(in_jsonObject, JsonWriterArgs); + if (!result) + { + UE_LOG(LogAkAudio, Log, TEXT("Unable to get a string representation of the Json Object.")); + } + JsonWriterArgs->Close(); + return result; +} + +#if AK_SUPPORT_WAAPI +void WampEventCallbacks(const uint64_t& in_subscriptionId, const JsonProvider& in_rJson) +{ + if (g_AkWaapiClient == nullptr) + return; + + auto wampEventCallbacks = g_AkWaapiClient->GetWampEventCallback(in_subscriptionId); + if (!wampEventCallbacks) + return; + + auto* WAAPI = IWAAPI::Get(); + if (UNLIKELY(!WAAPI)) + { + return; + } + + TSharedPtr ueJsonObject; + + TSharedRef> Reader = TJsonReaderFactory<>::Create(UTF8_TO_TCHAR(WAAPI->GetJsonString(in_rJson).c_str())); + if (!FJsonSerializer::Deserialize(Reader, ueJsonObject) || !ueJsonObject.IsValid()) + { + UE_LOG(LogAkAudio, Log, TEXT("Unable to deserialize a JSON object from the string : %s"), UTF8_TO_TCHAR(WAAPI->GetJsonString(in_rJson).c_str())); + return; + } + + wampEventCallbacks->Execute(in_subscriptionId, ueJsonObject); +} +#endif + + +/*------------------------------------------------------------------------------------ +Implementation. +------------------------------------------------------------------------------------*/ + +FAkWaapiClient::~FAkWaapiClient() +{ + delete m_Impl; +} + +void FAkWaapiClient::Initialize() +{ +#if AK_SUPPORT_WAAPI + if (!g_AkWaapiClient) + { + g_AkWaapiClient = new FAkWaapiClient(); + if(g_AkWaapiClient) + { + g_AkWaapiClient->m_Impl->Init(*g_AkWaapiClient); + } + FCoreDelegates::OnPreExit.AddLambda([] + { + if (g_AkWaapiClient != nullptr) + { + g_AkWaapiClient->m_Impl->AppExitingCounter.Increment(); + TArray aSubscriptionIDs; + g_AkWaapiClient->m_Impl->m_wampEventCallbackMap.GetKeys(aSubscriptionIDs); + TSharedPtr jsonResult = MakeShareable(new FJsonObject()); + for (auto iSubscriptionID : aSubscriptionIDs) + { + g_AkWaapiClient->Unsubscribe(iSubscriptionID, jsonResult); + } + } + DeleteInstance(); + }); + } +#endif +} + +/** Returns the singleton instance of FAkWaapiClient. Be sure to call FAkWaapiClient::Initialize() first (i.e. during Module startup). */ +FAkWaapiClient* FAkWaapiClient::Get() +{ + return g_AkWaapiClient; +} + +bool FAkWaapiClient::AppIsExiting() +{ +#if AK_SUPPORT_WAAPI + return m_Impl->AppExitingCounter.GetValue() != 0; +#else + return false; +#endif +} + +void FAkWaapiClient::SetConnectionClosing(bool isClosing) +{ +#if AK_SUPPORT_WAAPI + m_Impl->bIsConnectionClosing = isClosing; +#endif +} + +void FAkWaapiClient::DeleteInstance() +{ +#if AK_SUPPORT_WAAPI + if (g_AkWaapiClient == nullptr) + return; + + g_AkWaapiClient->OnClientBeginDestroy.Broadcast(); + g_AkWaapiClient->m_Impl->bIsConnectionClosing = true; + if (g_AkWaapiClient->m_Impl->m_Client) + { + g_AkWaapiClient->m_Impl->m_Client->Disconnect(); + } + delete g_AkWaapiClient; + g_AkWaapiClient = nullptr; +#endif +} + +bool FAkWaapiClient::IsDisconnecting() +{ +#if AK_SUPPORT_WAAPI + return m_Impl->bIsConnectionClosing; +#else + return false; +#endif +} + +WampEventCallback* FAkWaapiClient::GetWampEventCallback(const uint64_t& in_subscriptionId) +{ +#if AK_SUPPORT_WAAPI + return m_Impl->m_wampEventCallbackMap.Find(in_subscriptionId); +#else + return nullptr; +#endif +} + +bool FAkWaapiClient::IsProjectLoaded() +{ +#if AK_SUPPORT_WAAPI + if (g_AkWaapiClient == nullptr) + return false; + + if (!g_AkWaapiClient->IsConnected()) + { + return false; + } + + return g_AkWaapiClient->m_Impl->bProjectLoaded; +#else + return false; +#endif +} + +/** This is called when the reconnection handler successfully connects to WAAPI. +* We check if the correct project is loaded on a background thread. If it is, we broadcast OnProjectLoaded. +* We also subscribe to ak::wwise::core::project::loaded in order to check the project whenever one is loaded. +* If an incorrect project is loaded we broadcast OnConnectionLost. +*/ +void FAkWaapiClient::ConnectionEstablished() +{ +#if AK_SUPPORT_WAAPI + ensure(IsInGameThread()); + if (g_AkWaapiClient == nullptr) + return; + + // Broadcast OnProjectLoaded. If we are here, it means connection was successful, and that the correct project is + // loaded and available + OnProjectLoaded.Broadcast(); + + //We also subscribe to ak::wwise::core::project::loaded in order to check the project whenever one is loaded. + auto projectLoadedCallback = WampEventCallback::CreateLambda([this](uint64_t id, TSharedPtr in_UEJsonObject) + { + AsyncTask(ENamedThreads::GameThread, [this, id, in_UEJsonObject]() + { + m_Impl->bProjectLoaded = CheckProjectLoaded(); + if (m_Impl->bProjectLoaded) + { + OnProjectLoaded.Broadcast(); + } + else if (!AppIsExiting())//If an incorrect project is loaded we broadcast OnConnectionLost + { + BroadcastConnectionLost(); + } + }); + }); + + TSharedPtr projectLoadedSubscriptionResult = MakeShareable(new FJsonObject()); + TSharedRef projectLoadedOptions = MakeShareable(new FJsonObject()); + uint64_t projectLoadedSubscriptionID; + g_AkWaapiClient->Subscribe(ak::wwise::core::project::loaded, projectLoadedOptions, projectLoadedCallback, projectLoadedSubscriptionID, projectLoadedSubscriptionResult); + + //And we need to subscribe to ak::wwise::core::project::postClosed such that we are able to re-connect to WAAPI (for example if Wwise is closed then opened again). + auto projectClosedCallback = WampEventCallback::CreateLambda([this](uint64_t id, TSharedPtr in_UEJsonObject) + { + AsyncTask(ENamedThreads::GameThread, [this]() + { + BroadcastConnectionLost(); + }); + }); + TSharedPtr projectClosedSubscriptionResult = MakeShareable(new FJsonObject()); + TSharedRef projectClosedOptions = MakeShareable(new FJsonObject()); + uint64_t projectClosedSubscriptionID; + g_AkWaapiClient->Subscribe(ak::wwise::core::project::postClosed, projectClosedOptions, projectClosedCallback, projectClosedSubscriptionID, projectClosedSubscriptionResult); +#endif +} + +/** Returns the path of the Wwise project as defined in AkSettings (WWise Plugin Settings). */ +bool FAkWaapiClient::GetProjectPath(TSharedPtr& inOutJsonResult, FString& ProjectPath) +{ +#if AK_SUPPORT_WAAPI + TArray> inFromItems; + inFromItems.Add(MakeShareable(new FJsonValueString("Project"))); + const bool bSuccess = WAAPIGet(WAAPIGetFromOption::OF_TYPE, inFromItems, (AkInt64)WAAPIGetReturnOptionFlag::FILEPATH, + inOutJsonResult, WAAPIGetTransformOption::NONE, TArray>(), true); + if (bSuccess) + { + if (inOutJsonResult->HasField(FAkWaapiClient::WAAPIStrings::RETURN)) + { + TArray> returnJson = inOutJsonResult->GetArrayField(FAkWaapiClient::WAAPIStrings::RETURN); + if (returnJson.Num() > 0) + ProjectPath = returnJson[0]->AsObject()->GetStringField(FAkWaapiClient::WAAPIStrings::FILEPATH); + } + } + return bSuccess; +#else + return true; +#endif +} + +// WAAPI can become temporarily unavailable from time to time, this should be taken into account in the design. +// Please note that you shouldn't use this kind of design unless you know your call is accurate. If you do this +// and the error is caused by an argument problem, you might end up with an infinite retry loop. +FString GetAndWaitForCurrentProject(float RetrySleepTimeSeconds) +{ + FString ProjectPath; + static int RetryCount = 5; + while (g_AkWaapiClient->IsConnected()) + { + TSharedPtr JsonResult = MakeShareable(new FJsonObject()); + if (g_AkWaapiClient->GetProjectPath(JsonResult, ProjectPath)) + break; + + if (RetrySleepTimeSeconds > 0.0f) + { + if (--RetryCount == 0) + { + RetryCount = 5; + return {}; + } + + // Avoid flooding with requests while WAAPI is unavailable. + FPlatformProcess::Sleep(RetrySleepTimeSeconds); + } + } +#if PLATFORM_MAC + if(ProjectPath.StartsWith(TEXT("Y:"))) + { + ProjectPath.ReplaceInline(TEXT("Y:"), *FPlatformMisc::GetEnvironmentVariable(TEXT("HOME"))); + } + if(ProjectPath.StartsWith(TEXT("Z:"))) + { + ProjectPath.ReplaceInline(TEXT("Z:"), *FPlatformMisc::GetEnvironmentVariable(TEXT("ROOT"))); + } + ProjectPath.ReplaceInline(TEXT("\\"), TEXT("/")); +#endif + return ProjectPath; +} + +/** Checks if the currently loaded Wwise project matches the project path set in AkSettings (Wwise plugin settings). +* NOTE: This function will block while Wwise has a modal window open. It should not be called on the Game thread. +*/ +bool FAkWaapiClient::CheckProjectLoaded() +{ +#if AK_SUPPORT_WAAPI + if (g_AkWaapiClient == nullptr) + return false; + + if (!g_AkWaapiClient->IsConnected()) + { + g_AkWaapiClient->m_Impl->m_pConnectionHandler->ResetReconnectionDelay(); + return false; + } + + if (const UAkSettings* AkSettings = GetDefault()) + { + auto ProjectPathString = AkUnrealHelper::GetWwiseProjectPath(); + + FString sCurrentlyLoadedProjectPath = GetAndWaitForCurrentProject(1.0f); + if (FPaths::IsSamePath(sCurrentlyLoadedProjectPath, ProjectPathString)) + { + return true; + } + } +#endif + return false; +} + +void FAkWaapiClient::BroadcastConnectionLost() +{ +#if AK_SUPPORT_WAAPI + m_Impl->bProjectLoaded = false; + OnConnectionLost.Broadcast(); +#endif +} + +bool FAkWaapiClient::IsConnected() +{ +#if AK_SUPPORT_WAAPI + if (UNLIKELY(!g_AkWaapiClient->m_Impl->m_Client)) + { + return false; + } + return m_Impl->m_Client->IsConnected(); +#else + return false; +#endif +} + + +bool FAkWaapiClient::AttemptConnection() +{ + bIsWrongProjectLoaded = false; + bool bConnected = false; +#if AK_SUPPORT_WAAPI + if (UNLIKELY(!g_AkWaapiClient->m_Impl->m_Client)) + { + bConnected = false; + } + else if (const UAkSettingsPerUser* AkSettingsPerUser = GetDefault()) + { + bConnected = m_Impl->m_Client->Connect(TCHAR_TO_UTF8(*AkSettingsPerUser->WaapiIPAddress), AkSettingsPerUser->WaapiPort); + } + else + { + bConnected = m_Impl->m_Client->Connect(WAAPI_LOCAL_HOST_IP_STRING, WAAPI_PORT); + } + + if (bConnected) + { + bool bProjectLoaded = CheckProjectLoaded(); + if (!bProjectLoaded) + { + // We successfully connected, but the wrong project is open (or getting the project timed out). Disconnect. + // We will attemps reconnection later. + bIsWrongProjectLoaded = true; + m_Impl->m_Client->Disconnect(); + bConnected = false; + } + m_Impl->bProjectLoaded = bProjectLoaded; + } +#endif + return bConnected; +} + +bool FAkWaapiClient::Subscribe(const char* in_uri, const FString& in_options, WampEventCallback in_callback, + uint64& out_subscriptionId, FString& out_result, int in_iTimeoutMs /*= 500*/) +{ + bool eResult = false; +#if AK_SUPPORT_WAAPI + std::string out_resultString(""); + if (IsConnected()) + { + if (!m_Impl->bIsConnectionClosing) + { + // Call for the AK WAAPI method using string params. + if (LIKELY(g_AkWaapiClient->m_Impl->m_Client)) + { + FScopeLock Lock(&m_Impl->ClientSection); + eResult = m_Impl->m_Client->Subscribe(in_uri, TCHAR_TO_UTF8(*in_options), &WampEventCallbacks, out_subscriptionId, out_resultString, in_iTimeoutMs); + } + if (eResult) + { + m_Impl->m_wampEventCallbackMap.Add(out_subscriptionId, in_callback); + } + else + { + UE_LOG(LogAkAudio, Log, TEXT("Subscription failed: %s"), *FString(UTF8_TO_TCHAR(out_resultString.c_str()))); + } + out_result = FString(UTF8_TO_TCHAR(out_resultString.c_str())); + } + } + else + { + if (m_Impl && m_Impl->m_pConnectionHandler) + { + m_Impl->m_pConnectionHandler->ResetReconnectionDelay(); + } + } +#endif + return eResult; +} + +bool FAkWaapiClient::Subscribe(const char* in_uri, const TSharedRef& in_options, WampEventCallback in_callback, + uint64& out_subscriptionId, TSharedPtr& out_result, int in_iTimeoutMs /*= 500*/) +{ + bool eResult = false; +#if AK_SUPPORT_WAAPI + FString in_optionsString = TEXT(""); + + // Retrieve the options data string from the Json object. + JsonObjectToString(in_options, in_optionsString); + + FString out_resultString(TEXT("")); + // Call for the AK WAAPI method using string params. + eResult = Subscribe(in_uri, in_optionsString, in_callback, out_subscriptionId, out_resultString, in_iTimeoutMs); + + if (!eResult) + { + // Deserialize a JSON object from the string. + TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(out_resultString); + + if ((!FJsonSerializer::Deserialize(Reader, out_result) || !out_result.IsValid()) && IsConnected()) + { + UE_LOG(LogAkAudio, Log, TEXT("Subscribe: Output result -> unable to deserialize the Json object from the string : %s"), *out_resultString); + } + } +#endif + return eResult; +} + +bool FAkWaapiClient::Unsubscribe(const uint64_t& in_subscriptionId, FString& out_result, int in_iTimeoutMs /*= 500*/, bool in_bSilenceLog /*= false*/) +{ + bool eResult = false; +#if AK_SUPPORT_WAAPI + if (IsConnected()) + { + if (!m_Impl->bIsConnectionClosing) + { + std::string out_resultString(""); + // Call the AK WAAPI method. + if (LIKELY(g_AkWaapiClient->m_Impl->m_Client)) + { + FScopeLock Lock(&m_Impl->ClientSection); + eResult = m_Impl->m_Client->Unsubscribe(in_subscriptionId, out_resultString, in_iTimeoutMs); + } + if (eResult) + { + if (m_Impl->m_wampEventCallbackMap.Contains(in_subscriptionId)) + m_Impl->m_wampEventCallbackMap.Remove(in_subscriptionId); + } + else if (!in_bSilenceLog) + { + UE_LOG(LogAkAudio, Log, TEXT("Unsubscription failed: %s"), *FString(UTF8_TO_TCHAR(out_resultString.c_str()))); + } + out_result = FString(UTF8_TO_TCHAR(out_resultString.c_str())); + } + } + else + { + if (m_Impl && m_Impl->m_pConnectionHandler) + { + m_Impl->m_pConnectionHandler->ResetReconnectionDelay(); + } + } +#endif + return eResult; +} + +bool FAkWaapiClient::Unsubscribe(const uint64_t& in_subscriptionId, TSharedPtr& out_result, int in_iTimeoutMs /*= 500*/, bool in_bSilenceLog /*= false*/) +{ + bool eResult = false; +#if AK_SUPPORT_WAAPI + if (IsConnected()) + { + FString out_resultString(TEXT("")); + // Call the AK WAAPI method. + eResult = Unsubscribe(in_subscriptionId, out_resultString, in_iTimeoutMs, in_bSilenceLog); + + if (!eResult) + { + // Deserialize a JSON object from the string + TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(out_resultString); + + if ((!FJsonSerializer::Deserialize(Reader, out_result) || !out_result.IsValid()) && IsConnected()) + { + UE_LOG(LogAkAudio, Log, TEXT("Unsubscribe: Output result -> unable to deserialize the Json object from the string : %s"), *out_resultString); + } + } + } + else + { + if (m_Impl && m_Impl->m_pConnectionHandler) + { + m_Impl->m_pConnectionHandler->ResetReconnectionDelay(); + } + } +#endif + return eResult; +} + +bool FAkWaapiClient::RemoveWampEventCallback(const uint64_t in_subscriptionId) +{ +#if AK_SUPPORT_WAAPI + if (m_Impl->m_wampEventCallbackMap.Contains(in_subscriptionId)) + { + m_Impl->m_wampEventCallbackMap.Remove(in_subscriptionId); + return true; + } +#endif + + return false; +} + +bool FAkWaapiClient::Call(const char* in_uri, const FString& in_args, const FString& in_options, FString& out_result, int in_iTimeoutMs /*= 500*/, bool silenceLog /* = false*/) +{ + bool eResult = false; +#if AK_SUPPORT_WAAPI + if (IsConnected()) + { + if (!m_Impl->bIsConnectionClosing) + { + std::string out_resultString(""); + // Call the AK WAAPI method. + if (LIKELY(g_AkWaapiClient->m_Impl->m_Client)) + { + FScopeLock Lock(&m_Impl->ClientSection); + eResult = m_Impl->m_Client->Call(in_uri, TCHAR_TO_UTF8(*in_args), TCHAR_TO_UTF8(*in_options), out_resultString, in_iTimeoutMs); + } + if (!eResult && !silenceLog) + { + UE_LOG(LogAkAudio, Log, TEXT("Call failed: %s"), *FString(UTF8_TO_TCHAR(out_resultString.c_str()))); + } + out_result = FString(UTF8_TO_TCHAR(out_resultString.c_str())); + } + } + else + { + if (m_Impl && m_Impl->m_pConnectionHandler) + { + m_Impl->m_pConnectionHandler->ResetReconnectionDelay(); + } + } +#endif + return eResult; +} + +bool FAkWaapiClient::Call(const char* in_uri, const TSharedRef& in_args, const TSharedRef& in_options, + TSharedPtr& out_result, int in_iTimeoutMs /*= 500*/, bool silenceLog /*= false*/) +{ + bool eResult = false; +#if AK_SUPPORT_WAAPI + FString in_argsString = TEXT(""); + FString in_optionsString = TEXT(""); + + // Make sure the arguments are valid Json data. + JsonObjectToString(in_args, in_argsString); + + // Make sure the options are valid Json data. + JsonObjectToString(in_options, in_optionsString); + + FString out_resultString(TEXT("")); + // Call the AK WAAPI method. + eResult = Call(in_uri, in_argsString, in_optionsString, out_resultString, in_iTimeoutMs, silenceLog); + + // Deserialize a JSON object from the string. + TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(out_resultString); + + if (!FJsonSerializer::Deserialize(Reader, out_result) || !out_result.IsValid()) + { + if (!silenceLog && IsConnected()) + { + UE_LOG(LogAkAudio, Log, TEXT("Output result -> unable to deserialize a JSON object from the string : %s"), *out_resultString); + } + } +#endif + return eResult; +} + +bool FAkWaapiClient::Call(const char* inUri, const TArray& Values, + TSharedPtr& outJsonResult) +{ + TSharedRef Args = MakeShareable(new FJsonObject()); + + for (const auto& Value : Values) + { + Args->SetStringField(Value.KeyArg, Value.ValueArg); + } + + // Construct the options Json object; + TSharedRef Options = MakeShareable(new FJsonObject()); + // Request infos/changes in Waapi Picker using WAAPI + if (Call(inUri, Args, Options, outJsonResult)) + { + return true; + } + UE_LOG(LogAkAudio, Log, TEXT("Call %hs failed."), inUri); + return false; +} + +FAkWaapiClient::FAkWaapiClient() + : m_Impl(new FAkWaapiClientImpl) +{ +} + +/** Sets in_outParentGUID to the object ID of a parent of object in_objectGUID of type in_strType. */ +void FAkWaapiClient::GetParentOfType(FGuid in_objectGUID, FGuid& in_outParentGUID, FString in_strType) +{ +#if AK_SUPPORT_WAAPI + if (g_AkWaapiClient == nullptr) + return; + + /* Construct the relevant WAAPI json fields. */ + AkInt64 returnFlags = (AkInt64)WAAPIGetReturnOptionFlag::ID | + (AkInt64)WAAPIGetReturnOptionFlag::TYPE; + + TArray> fromID; + fromID.Add(MakeShareable(new FJsonValueString(in_objectGUID.ToString(EGuidFormats::DigitsWithHyphensInBraces)))); + + TSharedPtr outJsonResult; + if (!WAAPIGet(WAAPIGetFromOption::ID, fromID, returnFlags, outJsonResult)) + return; + + if (!outJsonResult->HasField(WAAPIStrings::RETURN)) + return; + + TArray> returnJson = outJsonResult->GetArrayField(WAAPIStrings::RETURN); + if (returnJson.Num() <= 0) + return; + + FString objectType; + { + TSharedPtr typeObject = returnJson[0]->AsObject(); + if (typeObject->HasField(WAAPIStrings::TYPE)) + objectType = typeObject->GetStringField(WAAPIStrings::TYPE); + } + in_outParentGUID = in_objectGUID; + + if (objectType.Equals(in_strType, ESearchCase::IgnoreCase)) + return; + + TSharedPtr select = MakeShareable(new FJsonObject()); + TArray> selectJsonArray; + selectJsonArray.Add(MakeShareable(new FJsonValueString(WAAPIStrings::PARENT))); + select->SetArrayField(WAAPIStrings::SELECT, selectJsonArray); + TArray> transform; + transform.Add(MakeShareable(new FJsonValueObject(select))); + + while (!objectType.Equals(in_strType, ESearchCase::IgnoreCase)) + { + fromID.Empty(); + fromID.Add(MakeShareable(new FJsonValueString(in_outParentGUID.ToString(EGuidFormats::DigitsWithHyphensInBraces)))); + outJsonResult = MakeShareable(new FJsonObject()); + if (!WAAPIGet(WAAPIGetFromOption::ID, fromID, returnFlags, outJsonResult, WAAPIGetTransformOption::SELECT, selectJsonArray)) + break; + + if (!outJsonResult->HasField(WAAPIStrings::RETURN)) + continue; + + TArray> aReturnJson = outJsonResult->GetArrayField(WAAPIStrings::RETURN); + TSharedPtr returnObject = aReturnJson[0]->AsObject(); + if (returnObject->HasField(WAAPIStrings::TYPE)) + objectType = returnObject->GetStringField(WAAPIStrings::TYPE); + if (returnObject->HasField(WAAPIStrings::ID)) + FGuid::Parse(returnObject->GetStringField(WAAPIStrings::ID), in_outParentGUID); + } +#endif +} + +bool FAkWaapiClient::IsProjectDirty() +{ +#if AK_SUPPORT_WAAPI + /* Ensure that Initialize() has been called! */ + AKASSERT(g_AkWaapiClient != nullptr); + + TArray> inFromItems; + AkInt64 iReturnOptions = (uint64_t)WAAPIGetReturnOptionFlag::WORKUNIT_IS_DIRTY; + TSharedPtr pJsonResult = MakeShareable(new FJsonObject()); + inFromItems.Add(MakeShareable(new FJsonValueString("WorkUnit"))); + WAAPIGet(WAAPIGetFromOption::OF_TYPE, inFromItems, iReturnOptions, pJsonResult); + if (pJsonResult->HasField(WAAPIStrings::RETURN)) + { + TArray> returnJson = pJsonResult->GetArrayField(WAAPIStrings::RETURN); + FString workunitDirty = GetReturnOptionString(WAAPIGetReturnOptionFlag::WORKUNIT_IS_DIRTY); + for (auto json : returnJson) + { + if (json->AsObject()->HasField(workunitDirty) && json->AsObject()->GetBoolField(workunitDirty)) + return true; + } + } +#endif + return false; +} + +/** +* WAAPI Structures +*/ + +FString FAkWaapiClient::GetFromOptionString(WAAPIGetFromOption from) +{ + switch (from) + { + case WAAPIGetFromOption::ID: return "id"; + case WAAPIGetFromOption::SEARCH: return "search"; + case WAAPIGetFromOption::PATH: return "path"; + case WAAPIGetFromOption::OF_TYPE: return "ofType"; + case WAAPIGetFromOption::QUERY: return "query"; + default: AKASSERT(false && "From option unhandled"); return ""; + } +} + +FString FAkWaapiClient::GetTransformOptionString(WAAPIGetTransformOption transform) +{ + switch (transform) + { + case WAAPIGetTransformOption::SELECT: return "select"; + case WAAPIGetTransformOption::RANGE: return "range"; + case WAAPIGetTransformOption::WHERE: return "where"; + case WAAPIGetTransformOption::NONE: return ""; + default: AKASSERT(false && "Transform option unhandled"); return ""; + } +} + +FAkWaapiClient::WAAPIGetReturnOptionFlag FAkWaapiClient::GetReturnOptionFlagValue(int in_iFlagIndex) +{ + return (WAAPIGetReturnOptionFlag)(AkInt64)pow(2, in_iFlagIndex); +} + +FString FAkWaapiClient::GetReturnOptionString(WAAPIGetReturnOptionFlag returnOption) +{ + switch (returnOption) + { + case WAAPIGetReturnOptionFlag::ID: return "id"; + case WAAPIGetReturnOptionFlag::NAME: return "name"; + case WAAPIGetReturnOptionFlag::NOTES: return "notes"; + case WAAPIGetReturnOptionFlag::TYPE: return "type"; + case WAAPIGetReturnOptionFlag::PATH: return "path"; + case WAAPIGetReturnOptionFlag::PARENT: return "parent"; + case WAAPIGetReturnOptionFlag::OWNER: return "owner"; + case WAAPIGetReturnOptionFlag::IS_PLAYABLE: return "isPlayable"; + case WAAPIGetReturnOptionFlag::SHORT_ID: return "shortId"; + case WAAPIGetReturnOptionFlag::CATEGORY: return "category"; + case WAAPIGetReturnOptionFlag::FILEPATH: return "filePath"; + case WAAPIGetReturnOptionFlag::WORKUNIT: return "workunit"; + case WAAPIGetReturnOptionFlag::CHILDREN_COUNT: return "childrenCount"; + case WAAPIGetReturnOptionFlag::MUSIC_TRANSITION_ROOT: return "music:transitionRoot"; + case WAAPIGetReturnOptionFlag::MUSIC_PLAYLIST_ROOT: return "music:playlistRoot"; + case WAAPIGetReturnOptionFlag::SOUND_ORIGINAL_WAV_FILE_PATH: return "sound:originalWavFilePath"; + case WAAPIGetReturnOptionFlag::SOUND_CONVERTED_WEM_FILE_PATH: return "sound:convertedWemFilePath"; + case WAAPIGetReturnOptionFlag::SOUNDBANK_BANK_FILE_PATH: return "soundbank:bnkFilePath"; + case WAAPIGetReturnOptionFlag::AUDIO_SOURCE_PLAYBACK_DURATION: return "audioSource:playbackDuration"; + case WAAPIGetReturnOptionFlag::AUDIO_SOURCE_MAX_DURATION_SOURCE: return "audioSource:maxDurationSource"; + case WAAPIGetReturnOptionFlag::AUDIO_SOURCE_TRIM_VALUES: return "audioSource:trimValues"; + case WAAPIGetReturnOptionFlag::WORKUNIT_IS_DEFAULT: return "workunit:isDefault"; + case WAAPIGetReturnOptionFlag::WORKUNIT_TYPE: return "workunit:type"; + case WAAPIGetReturnOptionFlag::WORKUNIT_IS_DIRTY: return "workunit:isDirty"; + default: AKASSERT(false && "Return option unhandled"); return ""; + } +} +/** +* JSon Helpers +*/ +TSharedRef FAkWaapiClient::CreateWAAPIGetArgumentJson(WAAPIGetFromOption in_FromOption, TArray> in_FromItems, + WAAPIGetTransformOption in_TransformOption /*= WAAPIGetTransformOption::NONE*/, + TArray> in_TransformItems /*= TArray>()*/) +{ + TSharedRef args = MakeShareable(new FJsonObject()); + TSharedPtr from = MakeShareable(new FJsonObject()); + from->SetArrayField(GetFromOptionString(in_FromOption), in_FromItems); + args->SetObjectField(FAkWaapiClient::WAAPIStrings::FROM, from); + if (in_TransformOption != WAAPIGetTransformOption::NONE && in_TransformItems.Num() > 0) + { + TArray> transformArgArray; + TSharedPtr transformObjectArg = MakeShareable(new FJsonObject()); + transformObjectArg->SetArrayField(GetTransformOptionString(in_TransformOption), in_TransformItems); + transformArgArray.Add(MakeShareable(new FJsonValueObject(transformObjectArg))); + args->SetArrayField(FAkWaapiClient::WAAPIStrings::TRANSFORM, transformArgArray); + } + return args; +} + +TSharedRef FAkWaapiClient::CreateWAAPIGetReturnOptionsJson(AkInt64 ReturnOptions) +{ + TSharedRef options = MakeShareable(new FJsonObject()); + TArray> StructJsonArray; + for (int bitIndex = 0; bitIndex < (int)WAAPIGetReturnOptionFlag::NUM_FLAGS; ++bitIndex) + { + WAAPIGetReturnOptionFlag returnOption = GetReturnOptionFlagValue(bitIndex); + if ((ReturnOptions & (AkInt64)returnOption) != 0) + { + StructJsonArray.Add(MakeShareable(new FJsonValueString(GetReturnOptionString(returnOption)))); + } + } + + options->SetArrayField(FAkWaapiClient::WAAPIStrings::RETURN, StructJsonArray); + return options; +} + +/** +* WAAPI Helpers +*/ + +bool FAkWaapiClient::WAAPIGet(WAAPIGetFromOption inFromField, + TArray> inFromItems, + AkInt64 inReturnOptionsFlags, + TSharedPtr& outJsonResult, + WAAPIGetTransformOption inTransformField /*= WAAPIGetTransformOption::NONE*/, + TArray> inTransformItems /*= TArray>()*/, + bool in_bSilenceLog /*= false*/) +{ +#if AK_SUPPORT_WAAPI + TSharedRef getArgsJson = CreateWAAPIGetArgumentJson(inFromField, inFromItems, inTransformField, inTransformItems); + TSharedRef returnOptionsJson = CreateWAAPIGetReturnOptionsJson(inReturnOptionsFlags); + + if (g_AkWaapiClient != nullptr && g_AkWaapiClient->IsConnected()) + { + if (g_AkWaapiClient->Call(ak::wwise::core::object::get, getArgsJson, returnOptionsJson, outJsonResult, 500, in_bSilenceLog)) + return true; + else if (!in_bSilenceLog) + UE_LOG(LogAkAudio, Log, TEXT("Call to ak.wwise.core.object.get Failed")); + } +#endif + return false; +} + +bool FAkWaapiClient::GetGUIDForObjectOfTypeWithName(FGuid& io_GUID, const FString& in_sTypeName, const FString& in_sName) +{ +#if AK_SUPPORT_WAAPI + TArray> nameArray; + nameArray.Add(MakeShareable(new FJsonValueString(in_sName))); + TSharedPtr outJsonResult = MakeShareable(new FJsonObject()); + AkInt64 returnOptionFlags = (AkInt64)WAAPIGetReturnOptionFlag::ID | (AkInt64)WAAPIGetReturnOptionFlag::NAME | (AkInt64)WAAPIGetReturnOptionFlag::TYPE; + if (WAAPIGet(WAAPIGetFromOption::SEARCH, nameArray, returnOptionFlags, outJsonResult)) + { + if (outJsonResult->HasField(WAAPIStrings::RETURN)) + { + TArray> returnJson = outJsonResult->GetArrayField(WAAPIStrings::RETURN); + for (auto json : returnJson) + { + auto jsonObj = json->AsObject(); + auto name = jsonObj->GetStringField(WAAPIStrings::NAME); + auto typeName = jsonObj->GetStringField(WAAPIStrings::TYPE); + if (name == in_sName && typeName.Equals(in_sTypeName, ESearchCase::IgnoreCase)) + { + auto iD = jsonObj->GetStringField(WAAPIStrings::ID); + FGuid::Parse(iD, io_GUID); + return true; + } + } + } + } +#endif + return false; +} + +void FAkWaapiClient::SaveProject() +{ +#if AK_SUPPORT_WAAPI + TSharedRef argsJson = MakeShareable(new FJsonObject()); + TSharedRef optionsJson = MakeShareable(new FJsonObject()); + TSharedPtr outputJson = MakeShareable(new FJsonObject()); + if (g_AkWaapiClient->IsConnected()) + { + g_AkWaapiClient->Call(ak::wwise::core::project::save, argsJson, optionsJson, outputJson); + } +#endif +} + +const FString FAkWaapiClient::WAAPIStrings::BACK_SLASH = TEXT("\\"); +const FString FAkWaapiClient::WAAPIStrings::ID = TEXT("id"); +const FString FAkWaapiClient::WAAPIStrings::RETURN = TEXT("return"); +const FString FAkWaapiClient::WAAPIStrings::PATH = TEXT("path"); +const FString FAkWaapiClient::WAAPIStrings::FILEPATH = TEXT("filePath"); +const FString FAkWaapiClient::WAAPIStrings::FROM = TEXT("from"); +const FString FAkWaapiClient::WAAPIStrings::NAME = TEXT("name"); +const FString FAkWaapiClient::WAAPIStrings::TYPE = TEXT("type"); +const FString FAkWaapiClient::WAAPIStrings::CHILDREN = TEXT("children"); +const FString FAkWaapiClient::WAAPIStrings::CHILDREN_COUNT = TEXT("childrenCount"); +const FString FAkWaapiClient::WAAPIStrings::ANCESTORS = TEXT("ancestors"); +const FString FAkWaapiClient::WAAPIStrings::DESCENDANTS = TEXT("descendants"); +const FString FAkWaapiClient::WAAPIStrings::WOKUNIT_TYPE = TEXT("workunit:type"); +const FString FAkWaapiClient::WAAPIStrings::FOLDER = TEXT("Folder"); +const FString FAkWaapiClient::WAAPIStrings::PHYSICAL_FOLDER = TEXT("PhysicalFolder"); +const FString FAkWaapiClient::WAAPIStrings::SEARCH = TEXT("search"); +const FString FAkWaapiClient::WAAPIStrings::PARENT = TEXT("parent"); +const FString FAkWaapiClient::WAAPIStrings::SELECT = TEXT("select"); +const FString FAkWaapiClient::WAAPIStrings::TRANSFORM = TEXT("transform"); +const FString FAkWaapiClient::WAAPIStrings::OBJECT = TEXT("object"); +const FString FAkWaapiClient::WAAPIStrings::OBJECTS = TEXT("objects"); +const FString FAkWaapiClient::WAAPIStrings::VALUE = TEXT("value"); +const FString FAkWaapiClient::WAAPIStrings::COMMAND = TEXT("command"); +const FString FAkWaapiClient::WAAPIStrings::TRANSPORT = TEXT("transport"); +const FString FAkWaapiClient::WAAPIStrings::ACTION = TEXT("action"); +const FString FAkWaapiClient::WAAPIStrings::PLAY = TEXT("play"); +const FString FAkWaapiClient::WAAPIStrings::STOP = TEXT("stop"); +const FString FAkWaapiClient::WAAPIStrings::STOPPED = TEXT("stopped"); +const FString FAkWaapiClient::WAAPIStrings::DISPLAY_NAME = TEXT("displayName"); +const FString FAkWaapiClient::WAAPIStrings::DELETE_ITEMS = TEXT("Delete Items"); +const FString FAkWaapiClient::WAAPIStrings::DRAG_DROP_ITEMS = TEXT("Drag Drop Items"); +const FString FAkWaapiClient::WAAPIStrings::UNDO = TEXT("Undo"); +const FString FAkWaapiClient::WAAPIStrings::REDO = TEXT("Redo"); +const FString FAkWaapiClient::WAAPIStrings::STATE = TEXT("state"); +const FString FAkWaapiClient::WAAPIStrings::OF_TYPE = TEXT("ofType"); +const FString FAkWaapiClient::WAAPIStrings::PROJECT = TEXT("Project"); +const FString FAkWaapiClient::WAAPIStrings::PROPERTY = TEXT("property"); +const FString FAkWaapiClient::WAAPIStrings::VOLUME = TEXT("Volume"); +const FString FAkWaapiClient::WAAPIStrings::FIND_IN_PROJECT_EXPLORER = TEXT("FindInProjectExplorerSelectionChannel1"); +const FString FAkWaapiClient::WAAPIStrings::TRIMMED_DURATION = TEXT("trimmedDuration"); + +const FString FAkWaapiClient::WwiseTypeStrings::SOUND = TEXT("Sound"); +const FString FAkWaapiClient::WwiseTypeStrings::WORKUNIT = TEXT("WorkUnit"); + +const FString FAkWaapiClient::AudioPeaksStrings::Args::OBJECT = TEXT("object"); +const FString FAkWaapiClient::AudioPeaksStrings::Args::NUM_PEAKS = TEXT("numPeaks"); +const FString FAkWaapiClient::AudioPeaksStrings::Args::TIME_FROM = TEXT("timeFrom"); +const FString FAkWaapiClient::AudioPeaksStrings::Args::TIME_TO = TEXT("timeTo"); +const FString FAkWaapiClient::AudioPeaksStrings::Args::CROSS_CHANNEL_PEAKS = TEXT("getCrossChannelPeaks"); +const FString FAkWaapiClient::AudioPeaksStrings::Results::PEAKS_BINARY = TEXT("peaksBinaryStrings"); +const FString FAkWaapiClient::AudioPeaksStrings::Results::MAX_ABS_VALUE = TEXT("maxAbsValue"); +const FString FAkWaapiClient::AudioPeaksStrings::Results::PEAKS_ARRAY_LENGTH = TEXT("peaksArrayLength"); +const FString FAkWaapiClient::AudioPeaksStrings::Results::PEAKS_DATA_SIZE = TEXT("peaksDataSize"); + +const FString FAkWaapiClient::PropertyChangedStrings::RequiredOptions::OBJECT = TEXT("object"); +const FString FAkWaapiClient::PropertyChangedStrings::RequiredOptions::PROPERTY = TEXT("property"); +const FString FAkWaapiClient::PropertyChangedStrings::OptionalOptions::RETURN = TEXT("return"); +const FString FAkWaapiClient::PropertyChangedStrings::OptionalOptions::PLATFORM = TEXT("platform"); + +const FString FAkWaapiClient::AudioSourceProperties::TRIM_END = TEXT("TrimEnd"); +const FString FAkWaapiClient::AudioSourceProperties::TRIM_BEGIN = TEXT("TrimBegin"); + +const FString FAkWaapiClient::PlaybackDurationStrings::MIN = TEXT("playbackDurationMin"); +const FString FAkWaapiClient::PlaybackDurationStrings::MAX = TEXT("playbackDurationMax"); +const FString FAkWaapiClient::PlaybackDurationStrings::TYPE = TEXT("playbackDurationType"); + +const FString FAkWaapiClient::TrimValuesStrings::TRIM_BEGIN = TEXT("trimBegin"); +const FString FAkWaapiClient::TrimValuesStrings::TRIM_END = TEXT("trimEnd"); +// end \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiSlate/Widgets/Input/AkSSlider.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiSlate/Widgets/Input/AkSSlider.cpp new file mode 100644 index 0000000..34f06ad --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiSlate/Widgets/Input/AkSSlider.cpp @@ -0,0 +1,322 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkWaapiSlate/Widgets/Input/AkSSlider.h" +#include "AkAudioDevice.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Input/SSlider.h" +#include "Widgets/Text/STextBlock.h" +#include "WaapiPicker/SWaapiPicker.h" +#include "AkWaapiUMG/Components/WwiseUmgDragDropOp.h" +#include "AkWaapiUMG/Components/WwisePropertyDragDropOp.h" +#include "AkWaapiUtils.h" +#include "Widgets/Input/SNumericEntryBox.h" + +/*------------------------------------------------------------------------------------ +Defines +------------------------------------------------------------------------------------*/ + +#define LOCTEXT_NAMESPACE "AkWaapiUMG" + +/*------------------------------------------------------------------------------------ +Helpers +------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------ +Implementation +------------------------------------------------------------------------------------*/ + +void AkSSlider::Construct(const AkSSlider::FArguments& InDeclaration) +{ + check(InDeclaration._Style); + + OnDropDelegate = InDeclaration._OnDrop; + + this->ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(this, &AkSSlider::GetAkSliderItemControlled) + ] + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(this, &AkSSlider::GetAkSliderProperty) + ] + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + SNew(SNumericEntryBox) + .Value(this, &AkSSlider::GetAkSliderValue) + .MinValue(this, &AkSSlider::GetAkSliderRangeMin) + .MinSliderValue(this, &AkSSlider::GetAkSliderRangeMin) + .MaxValue(this, &AkSSlider::GetAkSliderRangeMax) + .MaxSliderValue(this, &AkSSlider::GetAkSliderRangeMax) + .OnValueCommitted(this, &AkSSlider::OnValueCommitted) + + ] + ] + + SVerticalBox::Slot() + [ + SAssignNew(AkScrubberSSlider, SSlider) + .OnValueChanged(this, &AkSSlider::HandleAkSliderValueChanged) + .Style(InDeclaration._Style) + ] + ]; +} + +TSharedPtr AkSSlider::GetAkSilder() const +{ + return AkScrubberSSlider; +} + +TOptional AkSSlider::GetAkSliderValue() const +{ + if (AkScrubberSSlider.IsValid()) + return AkScrubberSSlider->GetValue()*(UIMaxValue - UIMinValue) + UIMinValue; + return 0.0f; +} + +FText AkSSlider::GetAkSliderProperty() const +{ + return FText::FromString(TEXT("Property : ") + AkSliderItemProperty); +} + +FText AkSSlider::GetAkSliderItemControlled() const +{ + return FText::FromString(TEXT("Item : ") + ItemControlled); +} + +void AkSSlider::HandleAkSliderValueChanged(float NewValue) +{ + if (AkSliderItemId.IsEmpty() || AkSliderItemProperty.IsEmpty()) + { + UE_LOG(LogAkAudio, Log, TEXT("No item or property to control")); + return; + } + // Construct the arguments Json object" + TSharedRef args = MakeShareable(new FJsonObject()); + { + args->SetStringField(WwiseWaapiHelper::OBJECT, AkSliderItemId); + args->SetStringField(WwiseWaapiHelper::PROPERTY, AkSliderItemProperty); + + args->SetNumberField(WwiseWaapiHelper::VALUE, NewValue*(UIMaxValue - UIMinValue) + UIMinValue); // e.i. This gives us a range of volume from [-96 to 12] for the volume property + } + + // Construct the options Json object; + TSharedRef options = MakeShareable(new FJsonObject()); + + // Connect to Wwise Authoring on localhost. + FAkWaapiClient* waapiClient = FAkWaapiClient::Get(); + if (waapiClient == nullptr) + { + return; + } +#if AK_SUPPORT_WAAPI + TSharedPtr outJsonResult; + // Request data from Wwise using WAAPI + if (!waapiClient->Call(ak::wwise::core::object::setProperty, args, options, outJsonResult)) + { + UE_LOG(LogAkAudio, Log, TEXT("Failed to set property %s on AKSSlider"), *AkSliderItemProperty); + } +#endif +} + +FReply AkSSlider::OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) +{ + FReply HandledState = FReply::Unhandled(); + TSharedPtr Operation = DragDropEvent.GetOperation(); + if (Operation.IsValid()) + { + if (Operation->IsOfType()) + { + const auto& AssetDragDropOp = StaticCastSharedPtr(Operation); + if (AssetDragDropOp.IsValid()) + { + const auto& WwiseAssets = AssetDragDropOp->GetWiseItems(); + if (WwiseAssets.Num() && WwiseAssets[0].IsValid()) + { + SetAkSliderItemId(WwiseAssets[0]->ItemId.ToString(EGuidFormats::DigitsWithHyphensInBraces)); + } + if (OnDropDelegate.IsBound()) + { + HandledState = OnDropDelegate.Execute(MyGeometry, DragDropEvent); + } + + if (!HandledState.IsEventHandled()) + { + if (WwiseAssets.Num() && WwiseAssets[0].IsValid()) + { + HandledState = FReply::Handled(); + } + } + } + } + else if (Operation->IsOfType()) + { + const auto& AssetDragDropOp = StaticCastSharedPtr(Operation); + if (AssetDragDropOp.IsValid()) + { + const auto& WwiseAssets = AssetDragDropOp->GetWiseProperties(); + if (WwiseAssets.Num() && WwiseAssets[0].IsValid()) + { + SetAkSliderItemProperty(*WwiseAssets[0].Get()); + } + if (OnDropDelegate.IsBound()) + { + HandledState = OnDropDelegate.Execute(MyGeometry, DragDropEvent); + } + + if (!HandledState.IsEventHandled()) + { + if (WwiseAssets.Num() && WwiseAssets[0].IsValid()) + { + HandledState = FReply::Handled(); + } + } + } + } + } + return HandledState; +} + +void AkSSlider::SetAkSliderItemProperty(const FString& ItemProperty) +{ + if (!ItemProperty.IsEmpty() && (AkSliderItemProperty != ItemProperty)) + { + AkSliderItemProperty = ItemProperty; + UpdateRange(); + } +} + +const FString& AkSSlider::GetAkSliderItemProperty() const +{ + return AkSliderItemProperty; +} + +void AkSSlider::SetAkSliderItemId(const FString& ItemId) +{ + if (!ItemId.IsEmpty() && (AkSliderItemId != ItemId)) + { + AkSliderItemId = ItemId; + TSharedPtr getResult; + FString itemName = TEXT(""); + if (SWaapiPicker::CallWaapiGetInfoFrom(WwiseWaapiHelper::ID, AkSliderItemId, getResult, {})) + { + // Recover the information from the Json object getResult and use it to get the item name. + TArray> StructJsonArray = getResult->GetArrayField(WwiseWaapiHelper::RETURN); + if (StructJsonArray.Num() > 0) + { + const TSharedPtr& ItemInfoObj = StructJsonArray[0]->AsObject(); + ItemControlled = ItemInfoObj->GetStringField(WwiseWaapiHelper::NAME); + } + } + UpdateRange(); + } +} + +inline void AkSSlider::UpdateRange() +{ + if(AkSliderItemId.IsEmpty()) + return; + + // Construct the arguments Json object. + TSharedRef args = MakeShareable(new FJsonObject()); + args->SetStringField(WwiseWaapiHelper::OBJECT, AkSliderItemId); + args->SetStringField(WwiseWaapiHelper::PROPERTY, AkSliderItemProperty); + + // Construct the options Json object. + TSharedRef options = MakeShareable(new FJsonObject()); + + TSharedPtr outJsonResult; + // Connect to Wwise Authoring on localhost. + FAkWaapiClient* waapiClient = FAkWaapiClient::Get(); + if (waapiClient) + { +#if AK_SUPPORT_WAAPI + // Request data from Wwise using WAAPI + if (waapiClient->Call(ak::wwise::core::object::getPropertyInfo, args, options, outJsonResult)) + { + // Recover the information from the Json object outJsonResult and use it to get the property state. + if (outJsonResult->HasField(WwiseWaapiHelper::UI)) + { + TSharedPtr uiLimit = outJsonResult->GetObjectField(WwiseWaapiHelper::UI)->GetObjectField(WwiseWaapiHelper::VALUE); + UIMinValue = uiLimit->GetNumberField(WwiseWaapiHelper::MIN); + UIMaxValue = uiLimit->GetNumberField(WwiseWaapiHelper::MAX); + } + } +#endif + } +} + +const FString& AkSSlider::GetAkSliderItemId() const +{ + return AkSliderItemId; +} + +void AkSSlider::SetAkSliderRangeMax(const double in_NewValue) +{ + UIMaxValue = in_NewValue; +} + + +void AkSSlider::SetAkSliderRangeMin(const double in_NewValue) +{ + UIMinValue = in_NewValue; +} + + + +TOptional AkSSlider::GetAkSliderRangeMin() const +{ + return UIMinValue; +} +TOptional AkSSlider::GetAkSliderRangeMax() const +{ + return UIMaxValue; +} + +void AkSSlider::OnValueCommitted(double InNewValue, ETextCommit::Type Commit) +{ + if (AkSliderItemProperty.IsEmpty() || InNewValue > UIMaxValue || InNewValue < UIMinValue) + { + return; + } + + const float newValue = (InNewValue - UIMinValue) / (UIMaxValue - UIMinValue); + if (AkScrubberSSlider.IsValid()) + AkScrubberSSlider->SetValue(newValue); + HandleAkSliderValueChanged(newValue); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkBoolPropertyToControlCustomization.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkBoolPropertyToControlCustomization.cpp new file mode 100644 index 0000000..7a0b747 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkBoolPropertyToControlCustomization.cpp @@ -0,0 +1,126 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#if WITH_EDITOR + +#include "AkWaapiUMG/Components/AkBoolPropertyToControlCustomization.h" +#include "AkAudioDevice.h" +#include "Framework/Application/SlateApplication.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SButton.h" +#include "DetailWidgetRow.h" +#include "AkWaapiUMG/Components/SAkItemBoolProperties.h" +#include "AkAudioStyle.h" + +#if UE_5_0_OR_LATER +#include "Framework/Docking/TabManager.h" +#endif + +#define LOCTEXT_NAMESPACE "AkPropertyToControlCustomization" + +TSharedRef FAkBoolPropertyToControlCustomization::MakeInstance() +{ + return MakeShareable(new FAkBoolPropertyToControlCustomization()); +} + +void FAkBoolPropertyToControlCustomization::CustomizeHeader( TSharedRef StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils ) +{ + ItemPropertyHandle = StructPropertyHandle->GetChildHandle("ItemProperty"); + + if(ItemPropertyHandle.IsValid()) + { + TSharedPtr PickerWidget = nullptr; + + PickerWidget = SAssignNew(PickerButton, SButton) + .ButtonStyle(FAkAudioStyle::Get(), "AudiokineticTools.HoverHintOnly" ) + .ToolTipText( LOCTEXT( "WwisePropertyToolTipText", "Choose a property") ) + .OnClicked(FOnClicked::CreateSP(this, &FAkBoolPropertyToControlCustomization::OnPickContent, ItemPropertyHandle.ToSharedRef())) + .ContentPadding(2.0f) + .ForegroundColor( FSlateColor::UseForeground() ) + .IsFocusable(false) + [ + SNew(SImage) + .Image(FAkAudioStyle::GetBrush("AudiokineticTools.Button_EllipsisIcon")) + .ColorAndOpacity(FSlateColor::UseForeground()) + ]; + + HeaderRow.ValueContent() + .MinDesiredWidth(125.0f) + .MaxDesiredWidth(600.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + [ + ItemPropertyHandle->CreatePropertyValueWidget() + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(FMargin(4.0f, 0.0f, 0.0f, 0.0f)) + .VAlign(VAlign_Center) + [ + PickerWidget.ToSharedRef() + ] + + ] + .NameContent() + [ + StructPropertyHandle->CreatePropertyNameWidget() + ]; + + } +} + +void FAkBoolPropertyToControlCustomization::CustomizeChildren( TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils ) +{ +} + +FReply FAkBoolPropertyToControlCustomization::OnPickContent(TSharedRef PropertyHandle) +{ + Window = SNew(SWindow) + .Title(LOCTEXT("PropertyPickerWindowTitle", "Choose A Property")) + .SizingRule(ESizingRule::UserSized) + .AutoCenter(EAutoCenter::PreferredWorkArea) + .ClientSize(FVector2D(350, 400)); + + Window->SetContent( + SNew(SBorder) + [ + SNew(SAkItemBoolProperties) + .FocusSearchBoxWhenOpened(true) + .SelectionMode(ESelectionMode::Single) + .OnSelectionChanged(this, &FAkBoolPropertyToControlCustomization::PropertySelectionChanged) + ] + ); + + TSharedPtr RootWindow = FGlobalTabmanager::Get()->GetRootWindow(); + FSlateApplication::Get().AddWindowAsNativeChild(Window.ToSharedRef(), RootWindow.ToSharedRef()); + return FReply::Handled(); +} + +void FAkBoolPropertyToControlCustomization::PropertySelectionChanged(TSharedPtr< FString > ItemProperty, ESelectInfo::Type SelectInfo) +{ + if (ItemProperty.IsValid()) + { + ItemPropertyHandle->SetValue(*ItemProperty.Get()); + Window->RequestDestroyWindow(); + } +} +#undef LOCTEXT_NAMESPACE + +#endif//WITH_EDITOR \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkCheckBox.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkCheckBox.cpp new file mode 100644 index 0000000..91a99e5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkCheckBox.cpp @@ -0,0 +1,496 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkWaapiUMG/Components/AkCheckBox.h" +#include "AkAudioDevice.h" +#include "Widgets/SNullWidget.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/Input/SCheckBox.h" +#include "AkWaapiUMG/Components/AkBoolPropertyToControlCustomization.h" +#include "AkWaapiUMG/Components/WwiseBoolPropertyDragDropOp.h" +#include "AkWaapiUtils.h" +#include "AkWaapiBlueprints/AkWaapiCalls.h" +#include "Async/Async.h" +#include "AkWaapiUMG/Components/WwiseUmgDragDropOp.h" +#include "WaapiPicker/SWaapiPicker.h" + +#if WITH_EDITOR +#include "Modules/ModuleManager.h" +#include "PropertyEditorModule.h" +#endif + +#define LOCTEXT_NAMESPACE "AkWaapiUMG" + +//////////////////////////////////////////////// +// SCheckBoxDropHandler +//////////////////////////////////////////////// + +/** Drag-drop zone for adding a Wwise item or a bool property to the CheckBox */ +class SCheckBoxDropHandler : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SCheckBoxDropHandler) {} + SLATE_DEFAULT_SLOT(FArguments, Content) + SLATE_EVENT(FOnDrop, OnDrop) + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs) + { + OnDropDelegate = InArgs._OnDrop; + + this->ChildSlot + [ + SNew(SBorder) + [ + InArgs._Content.Widget + ] + ]; + } + + FReply OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override + { + if (OnDropDelegate.IsBound()) + { + return OnDropDelegate.Execute(MyGeometry, DragDropEvent); + } + return FReply::Handled(); + } + +private: + FOnDrop OnDropDelegate; +}; + +///////////////////////////////////////////////////// +// UAkCheckBox + +UAkCheckBox::UAkCheckBox(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + SCheckBox::FArguments SlateDefaults; + WidgetStyle = *SlateDefaults._Style; + + CheckedState = ECheckBoxState::Unchecked; + + HorizontalAlignment = SlateDefaults._HAlign; + + IsFocusable = true; +} + +void UAkCheckBox::BeginDestroy() +{ + if (SubscriptionIdNameChanged != 0) + { + bool isUnsubscribed; + UAkWaapiCalls::Unsubscribe(FAkWaapiSubscriptionId(SubscriptionIdNameChanged), isUnsubscribed); + if (isUnsubscribed) + SubscriptionIdNameChanged = 0; + } + if (SubscriptionId != 0) + { + bool isUnsubscribed; + UAkWaapiCalls::Unsubscribe(FAkWaapiSubscriptionId(SubscriptionId), isUnsubscribed); + if (isUnsubscribed) + SubscriptionId = 0; + } + + Super::BeginDestroy(); +} + +void UAkCheckBox::ReleaseSlateResources(bool bReleaseChildren) +{ + Super::ReleaseSlateResources(bReleaseChildren); + + MyCheckbox.Reset(); +} + +TSharedRef UAkCheckBox::RebuildWidget() +{ +#if WITH_EDITOR + FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); + PropertyModule.RegisterCustomPropertyTypeLayout("AkBoolPropertyToControl", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FAkBoolPropertyToControlCustomization::MakeInstance)); + PropertyModule.NotifyCustomizationModuleChanged(); +#endif//WITH_EDITOR + + MyCheckbox = SNew(SCheckBox) + .OnCheckStateChanged(BIND_UOBJECT_DELEGATE(FOnCheckStateChanged, SlateOnCheckStateChangedCallback)) + .Style(&WidgetStyle) + .HAlign(HorizontalAlignment) + .IsFocusable(IsFocusable) + ; + + if (GetChildrenCount() > 0) + { + MyCheckbox->SetContent(GetContentSlot()->Content ? GetContentSlot()->Content->TakeWidget() : SNullWidget::NullWidget); + } + + return + SNew(SCheckBoxDropHandler) + .OnDrop(FOnDrop::CreateUObject(this, &UAkCheckBox::OnDropHandler)) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(FMargin(3.0f, 3.0f)) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + MyCheckbox.ToSharedRef() + ] + + SHorizontalBox::Slot() + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(5.0f, 0.0f, 0.0f, 0.0f) + .HAlign(HAlign_Left) + [ + SNew(STextBlock) + .Text(TAttribute::Create(TAttribute::FGetter::CreateUObject(this, &UAkCheckBox::GetAkItemControlled))) + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(5.0f, 0.0f, 0.0f, 0.0f) + .HAlign(HAlign_Left) + [ + SNew(STextBlock) + .Text(TAttribute::Create(TAttribute::FGetter::CreateUObject(this, &UAkCheckBox::GetAkBoolProperty))) + ] + ] + ]; +} + +void UAkCheckBox::SynchronizeProperties() +{ + Super::SynchronizeProperties(); + + MyCheckbox->SetStyle(&WidgetStyle); + MyCheckbox->SetIsChecked(PROPERTY_BINDING(ECheckBoxState, CheckedState) ); + + ItemToControl.ItemPath = ItemToControl.ItemPicked.ItemPath; + + FGuid ItemId; + if (!ItemToControl.ItemPicked.ItemId.IsEmpty()) + { + if(FGuid::ParseExact(ItemToControl.ItemPicked.ItemId, EGuidFormats::DigitsWithHyphensInBraces, ItemId)) + { + SetAkItemId(ItemId); + } + } + SetAkBoolProperty(ThePropertyToControl.ItemProperty); +} + +void UAkCheckBox::SetAkItemControlled(const FString& Item) +{ + ItemControlled = Item; +} + +FText UAkCheckBox::GetAkItemControlled() +{ + return FText::FromString(TEXT("Item : ") + ItemControlled); +} + +void UAkCheckBox::SetAkItemId(const FGuid& ItemId) +{ + if (ItemId.IsValid()) + { + IdItemToControl = ItemId.ToString(EGuidFormats::DigitsWithHyphensInBraces); + + TSharedPtr getResult; + if (!SWaapiPicker::CallWaapiGetInfoFrom(WwiseWaapiHelper::ID, IdItemToControl, getResult, {})) + { + return; + } + TArray> StructJsonArray = getResult->GetArrayField(WwiseWaapiHelper::RETURN); + if (StructJsonArray.Num() > 0) + { + const TSharedPtr& ItemInfoObj = StructJsonArray[0]->AsObject(); + SetAkItemControlled(ItemInfoObj->GetStringField(WwiseWaapiHelper::NAME)); + } +#if AK_SUPPORT_WAAPI + /** UnSubscribe to object renamed to be notified from Wwise using WAAPI, so we can maintain the name of the item controlled up to date dynamically. */ + // Connect to Wwise Authoring on localhost. + if (SubscriptionIdNameChanged != 0) + { + bool isUnsubscribed; + UAkWaapiCalls::Unsubscribe(FAkWaapiSubscriptionId(SubscriptionIdNameChanged), isUnsubscribed); + if (isUnsubscribed) + SubscriptionIdNameChanged = 0; + } + TSharedPtr outJsonResult; + /** Subscribe to object renamed-created-deleted and removed to be notified from Wwise using WAAPI, so we can maintain the Wise picker up to date dynamically. */ + // Connect to Wwise Authoring on localhost. + FAkWaapiClient* waapiClient = FAkWaapiClient::Get(); + if (waapiClient) + { + auto wampEventCallback = WampEventCallback::CreateLambda([this](uint64_t id, TSharedPtr in_UEJsonObject) + { + AsyncTask(ENamedThreads::GameThread, [this, in_UEJsonObject] + { + SetAkItemControlled(in_UEJsonObject->GetStringField(WwiseWaapiHelper::NEW_NAME)); + }); + }); + // Construct the options Json object : Getting parent infos. + TSharedRef in_options = MakeShareable(new FJsonObject()); + // Subscribe to object renamed-created-deleted and removed notifications. + waapiClient->Subscribe(ak::wwise::core::object::nameChanged, in_options, wampEventCallback, SubscriptionIdNameChanged, outJsonResult); + } + SynchronizePropertyWithWwise(); +#endif + } +} + +const FGuid UAkCheckBox::GetAkItemId() const +{ + FGuid ItemId; + if (!IdItemToControl.IsEmpty()) + { + FGuid::ParseExact(IdItemToControl, EGuidFormats::DigitsWithHyphensInBraces, ItemId); + } + return ItemId; +} + +void UAkCheckBox::SetAkBoolProperty(const FString& BoolProperty) +{ + BoolPropertyToControl = BoolProperty; + SynchronizePropertyWithWwise(); +} + +const FString UAkCheckBox::GetAkProperty() const +{ + return BoolPropertyToControl; +} + +FText UAkCheckBox::GetAkBoolProperty() const +{ + return FText::FromString(TEXT("Property : ") + GetAkProperty()); +} + +void UAkCheckBox::OnSlotAdded(UPanelSlot* InSlot) +{ + // Add the child to the live slot if it already exists + if ( MyCheckbox.IsValid() ) + { + MyCheckbox->SetContent(InSlot->Content ? InSlot->Content->TakeWidget() : SNullWidget::NullWidget); + } +} + +void UAkCheckBox::SynchronizePropertyWithWwise() +{ + if (!IdItemToControl.IsEmpty() && !BoolPropertyToControl.IsEmpty()) + { + TSharedPtr ItemInfoResult; + if (CallWappiGetPropertySate(IdItemToControl, BoolPropertyToControl, ItemInfoResult)) + { + bool result = false; + + if (ItemInfoResult.Get()->TryGetBoolField(WwiseWaapiHelper::AT + BoolPropertyToControl, result)) + { + SetIsChecked(result); + } + } + + if (SubscriptionId != 0) + { + bool isUnsubscribed; + UAkWaapiCalls::Unsubscribe(FAkWaapiSubscriptionId(SubscriptionId), isUnsubscribed); + if (isUnsubscribed) + SubscriptionId = 0; + } + TSharedPtr outJsonResult; + + auto wampEventCallback = WampEventCallback::CreateLambda([this](uint64_t id, TSharedPtr in_UEJsonObject) + { + bool result = false; + if (in_UEJsonObject->TryGetBoolField(WwiseWaapiHelper::NEW, result)) + { + SetIsChecked(result); + } + }); + SubscribeToPropertyStateChange(IdItemToControl, BoolPropertyToControl, wampEventCallback, SubscriptionId, outJsonResult); + } +} + +void UAkCheckBox::OnSlotRemoved(UPanelSlot* InSlot) +{ + // Remove the widget from the live slot if it exists. + if ( MyCheckbox.IsValid() ) + { + MyCheckbox->SetContent(SNullWidget::NullWidget); + } +} + +bool UAkCheckBox::IsPressed() const +{ + if ( MyCheckbox.IsValid() ) + { + return MyCheckbox->IsPressed(); + } + + return false; +} + +bool UAkCheckBox::IsChecked() const +{ + if ( MyCheckbox.IsValid() ) + { + return MyCheckbox->IsChecked(); + } + + return ( CheckedState == ECheckBoxState::Checked ); +} + +ECheckBoxState UAkCheckBox::GetCheckedState() const +{ + if ( MyCheckbox.IsValid() ) + { + return MyCheckbox->GetCheckedState(); + } + + return CheckedState; +} + +void UAkCheckBox::SetIsChecked(bool InIsChecked) +{ + CheckedState = InIsChecked ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + if ( MyCheckbox.IsValid() ) + { + MyCheckbox->SetIsChecked(PROPERTY_BINDING(ECheckBoxState, CheckedState)); + } +} + +void UAkCheckBox::SetCheckedState(ECheckBoxState InCheckedState) +{ + CheckedState = InCheckedState; + if ( MyCheckbox.IsValid() ) + { + MyCheckbox->SetIsChecked(PROPERTY_BINDING(ECheckBoxState, CheckedState)); + } +} + +void UAkCheckBox::SlateOnCheckStateChangedCallback(ECheckBoxState NewState) +{ + CheckedState = NewState; + + if (ItemControlled.IsEmpty() || BoolPropertyToControl.IsEmpty() || IdItemToControl.IsEmpty()) + { + UE_LOG(LogAkAudio, Log, TEXT("No item or property to control")); + return; + } + // Construct the arguments Json object : setting configs + TSharedRef args = MakeShareable(new FJsonObject()); + { + args->SetStringField(WwiseWaapiHelper::OBJECT, IdItemToControl); + args->SetStringField(WwiseWaapiHelper::PROPERTY, BoolPropertyToControl); + args->SetBoolField(WwiseWaapiHelper::VALUE, IsChecked()); // this gives us a range of volume from [-96 to 12] + } + + // Construct the options Json object; + TSharedRef options = MakeShareable(new FJsonObject()); + + // Connect to Wwise Authoring on localhost. + FAkWaapiClient* waapiClient = FAkWaapiClient::Get(); + if (waapiClient) + { +#if AK_SUPPORT_WAAPI + TSharedPtr outJsonResult; + // Request data from Wwise using WAAPI + if (waapiClient->Call(ak::wwise::core::object::setProperty, args, options, outJsonResult)) + { + } + else + { + UE_LOG(LogAkAudio, Log, TEXT("Call Failed")); + return; + } +#endif + } + else + { + UE_LOG(LogAkAudio, Log, TEXT("Unable to connect to localhost")); + return; + } + + // Choosing to treat Undetermined as Checked + const bool bWantsToBeChecked = NewState != ECheckBoxState::Unchecked; + AkOnCheckStateChanged.Broadcast(bWantsToBeChecked); +} + +FReply UAkCheckBox::OnDropHandler(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) +{ + FReply HandledState = FReply::Unhandled(); + TSharedPtr Operation = DragDropEvent.GetOperation(); + if (Operation.IsValid()) + { + if (Operation->IsOfType()) + { + const auto& AssetDragDropOp = StaticCastSharedPtr(Operation); + if (AssetDragDropOp.IsValid()) + { + const auto& WwiseAssets = AssetDragDropOp->GetWiseItems(); + if (WwiseAssets.Num() && WwiseAssets[0].IsValid()) + { + if (OnItemDropped.IsBound()) + { + OnItemDropped.Broadcast(WwiseAssets[0]->ItemId); + } + else + { + SetAkItemId(WwiseAssets[0]->ItemId); + } + HandledState = FReply::Handled(); + } + } + } + else if (Operation->IsOfType()) + { + const auto& AssetDragDropOp = StaticCastSharedPtr(Operation); + if (AssetDragDropOp.IsValid()) + { + const auto& WwiseAssets = AssetDragDropOp->GetWiseProperties(); + if (WwiseAssets.Num() && WwiseAssets[0].IsValid()) + { + if (OnPropertyDropped.IsBound()) + { + OnPropertyDropped.Broadcast(*WwiseAssets[0].Get()); + } + else + { + SetAkBoolProperty(*WwiseAssets[0].Get()); + } + HandledState = FReply::Handled(); + } + } + } + } + return HandledState; +} + + +#if WITH_EDITOR + +const FText UAkCheckBox::GetPaletteCategory() +{ + return LOCTEXT("Wwise", "Wwise"); +} + +#endif + +///////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkItemBoolProperties.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkItemBoolProperties.cpp new file mode 100644 index 0000000..be9d468 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkItemBoolProperties.cpp @@ -0,0 +1,166 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ +includes. +------------------------------------------------------------------------------------*/ + +#include "AkWaapiUMG/Components/AkItemBoolProperties.h" +#include "AkAudioDevice.h" +#include "Widgets/Input/SButton.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "AkWaapiUMG/Components/WwiseBoolPropertyDragDropOp.h" + +/*------------------------------------------------------------------------------------ +Defines +------------------------------------------------------------------------------------*/ +#define LOCTEXT_NAMESPACE "AkWaapiUMG" + +/*------------------------------------------------------------------------------------ +UAkItemBoolPropertiesConv +------------------------------------------------------------------------------------*/ + +UAkItemBoolPropertiesConv::UAkItemBoolPropertiesConv(const class FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + // Property initialization +} + +FString UAkItemBoolPropertiesConv::Conv_FAkBoolPropertyToControlToString(const FAkBoolPropertyToControl& INAkBoolPropertyToControl) +{ + return INAkBoolPropertyToControl.ItemProperty; +} + +FText UAkItemBoolPropertiesConv::Conv_FAkBoolPropertyToControlToText(const FAkBoolPropertyToControl& INAkBoolPropertyToControl) +{ + return FText::FromString(*INAkBoolPropertyToControl.ItemProperty); +} +/*------------------------------------------------------------------------------------ +Implementation +------------------------------------------------------------------------------------*/ +///////////////////////////////////////////////////// +// UAkItemBoolProperties + +UAkItemBoolProperties::UAkItemBoolProperties(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{} + +TSharedRef UAkItemBoolProperties::RebuildWidget() +{ + bool autoFocus = GetWorld()->WorldType != EWorldType::Game && + GetWorld()->WorldType != EWorldType::PIE && + GetWorld()->WorldType != EWorldType::GamePreview; + return + SNew(SBorder) + .BorderImage(FAkAudioStyle::GetBrush("AudiokineticTools.GroupBorder")) + .Padding(FMargin(0.0f, 3.0f)) + .BorderBackgroundColor(FLinearColor(.6, .6, .6, 1.0f)) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(FMargin(10.0f, 0.0f, 0.0f, 0.0f)) + .AutoWidth() + .VAlign(VAlign_Center) + [ + SAssignNew(PropertyTextBlock, STextBlock) + .IsEnabled(true) + .ToolTipText(LOCTEXT("editable_Tooltip", "Property Name")) + .MinDesiredWidth(300.f) + .Text(FText::FromString(TEXT("Choose a Boolean property"))) + ] + ] + + SVerticalBox::Slot() + [ + SAssignNew(WwiseProperties, SAkItemBoolProperties) + .FocusSearchBoxWhenOpened(autoFocus) + .SelectionMode(ESelectionMode::Single) + .OnSelectionChanged(FOnSelectionChanged::CreateUObject(this, &UAkItemBoolProperties::PropertySelectionChanged)) + .OnDragDetected(FOnDragDetected::CreateUObject(this, &UAkItemBoolProperties::HandleOnDragDetected)) + ] + ]; +} + +void UAkItemBoolProperties::PropertySelectionChanged(TSharedPtr< FString > PropertySelected, ESelectInfo::Type SelectInfo) +{ + if (PropertySelected.IsValid()) + { + const auto& PropertySelectedString = *PropertySelected.Get(); + PropertyTextBlock->SetText(FText::FromString(PropertySelectedString)); + + if (OnSelectionChanged.IsBound()) + { + OnSelectionChanged.Broadcast(PropertySelectedString); + } + } +} + +FReply UAkItemBoolProperties::HandleOnDragDetected(const FGeometry& Geometry, const FPointerEvent& MouseEvent) +{ + if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton) && WwiseProperties.IsValid()) + { + const TArray>& ProperySelected = WwiseProperties->GetSelectedProperties(); + if (ProperySelected.Num() == 1) + { + if(OnPropertyDragged.IsBound()) + { + OnPropertyDragged.Broadcast(*ProperySelected[0].Get()); + } + return FReply::Handled().BeginDragDrop(FWwiseBoolPropertyDragDropOp::New(ProperySelected)); + } + } + return FReply::Unhandled(); +} + +FString UAkItemBoolProperties::GetSelectedProperty() const +{ + const TArray> selectedList = WwiseProperties->GetSelectedProperties(); + return selectedList.Num()? *selectedList[0] : TEXT(""); +} + +FString UAkItemBoolProperties::GetSearchText() const +{ + return WwiseProperties->GetSearchText(); +} + +void UAkItemBoolProperties::SetSearchText(const FString& newText) +{ + WwiseProperties->SetSearchText(newText); +} + +void UAkItemBoolProperties::ReleaseSlateResources(bool bReleaseChildren) +{ + Super::ReleaseSlateResources(bReleaseChildren); + + PropertyTextBlock.Reset(); + WwiseProperties.Reset(); +} + +#if WITH_EDITOR +const FText UAkItemBoolProperties::GetPaletteCategory() +{ + return LOCTEXT("Wwise", "Wwise"); +} + +#endif +// +///////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkItemProperties.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkItemProperties.cpp new file mode 100644 index 0000000..f90d1e1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkItemProperties.cpp @@ -0,0 +1,172 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ +includes. +------------------------------------------------------------------------------------*/ + +#include "AkWaapiUMG/Components/AkItemProperties.h" +#include "AkAudioDevice.h" +#include "Widgets/Input/SButton.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "AkWaapiUMG/Components/WwisePropertyDragDropOp.h" +#include "AkWaapiSlate/Widgets/Input/AkSSlider.h" +#include "AkAudioStyle.h" + +/*------------------------------------------------------------------------------------ +Defines +------------------------------------------------------------------------------------*/ +#define LOCTEXT_NAMESPACE "AkWaapiUMG" + +/*------------------------------------------------------------------------------------ +UAkItemPropertiesConv +------------------------------------------------------------------------------------*/ + +UAkItemPropertiesConv::UAkItemPropertiesConv(const class FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + // Property initialization +} + +FString UAkItemPropertiesConv::Conv_FAkPropertyToControlToString(const FAkPropertyToControl& INAkPropertyToControl) +{ + return INAkPropertyToControl.ItemProperty; +} + +FText UAkItemPropertiesConv::Conv_FAkPropertyToControlToText(const FAkPropertyToControl& INAkPropertyToControl) +{ + return FText::FromString(*INAkPropertyToControl.ItemProperty); +} + +/*------------------------------------------------------------------------------------ +Implementation +------------------------------------------------------------------------------------*/ +///////////////////////////////////////////////////// +// UAkItemProperties + +UAkItemProperties::UAkItemProperties(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + AkSSlider::FArguments Defaults; +} + +TSharedRef UAkItemProperties::RebuildWidget() +{ + bool autoFocus = GetWorld()->WorldType != EWorldType::Game && + GetWorld()->WorldType != EWorldType::PIE && + GetWorld()->WorldType != EWorldType::GamePreview; + return + SNew(SBorder) + .BorderImage(FAkAudioStyle::GetBrush("AudiokineticTools.GroupBorder")) + .Padding(FMargin(0.0f, 3.0f)) + .BorderBackgroundColor(FLinearColor(.6, .6, .6, 1.0f)) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(FMargin(10.0f, 0.0f, 0.0f, 0.0f)) + .AutoWidth() + .VAlign(VAlign_Center) + [ + SAssignNew(PropertyTextBlock, STextBlock) + .IsEnabled(true) + .ToolTipText(LOCTEXT("editable_Tooltip", "Property Name")) + .MinDesiredWidth(300.f) + .Text(FText::FromString(TEXT("Choose a property"))) + ] + ] + + SVerticalBox::Slot() + [ + SAssignNew(WwiseProperties, SAkItemProperties) + .FocusSearchBoxWhenOpened(autoFocus) + .SelectionMode(ESelectionMode::Single) + .OnSelectionChanged(FOnSelectionChanged::CreateUObject(this, &UAkItemProperties::PropertySelectionChanged)) + .OnDragDetected(FOnDragDetected::CreateUObject(this, &UAkItemProperties::HandleOnDragDetected)) + ] + ]; +} + +void UAkItemProperties::PropertySelectionChanged(TSharedPtr< FString > PropertySelected, ESelectInfo::Type SelectInfo) +{ + if (PropertySelected.IsValid()) + { + const auto& PropertyString = *PropertySelected.Get(); + PropertyTextBlock->SetText(FText::FromString(PropertyString)); + + if (OnSelectionChanged.IsBound()) + { + OnSelectionChanged.Broadcast(PropertyString); + } + } +} + +FReply UAkItemProperties::HandleOnDragDetected(const FGeometry& Geometry, const FPointerEvent& MouseEvent) +{ + if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton) && WwiseProperties.IsValid()) + { + const TArray>& ProperySelected = WwiseProperties->GetSelectedProperties(); + if (ProperySelected.Num() == 1) + { + if(OnPropertyDragged.IsBound()) + { + OnPropertyDragged.Broadcast(*ProperySelected[0].Get()); + } + TSharedRef Operation = FWwisePropertyDragDropOp::New(ProperySelected); + return FReply::Handled().BeginDragDrop(Operation); + } + } + return FReply::Unhandled(); +} + +FString UAkItemProperties::GetSelectedProperty() const +{ + return *WwiseProperties->GetSelectedProperties()[0]; +} + +FString UAkItemProperties::GetSearchText() const +{ + return WwiseProperties->GetSearchText(); +} + +void UAkItemProperties::SetSearchText(const FString& newText) +{ + WwiseProperties->SetSearchText(newText); +} + +void UAkItemProperties::ReleaseSlateResources(bool bReleaseChildren) +{ + Super::ReleaseSlateResources(bReleaseChildren); + + PropertyTextBlock.Reset(); + WwiseProperties.Reset(); +} + +#if WITH_EDITOR + +const FText UAkItemProperties::GetPaletteCategory() +{ + return LOCTEXT("Wwise", "Wwise"); +} + +#endif +// +///////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkPropertyToControlCustomization.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkPropertyToControlCustomization.cpp new file mode 100644 index 0000000..c74396b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkPropertyToControlCustomization.cpp @@ -0,0 +1,126 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#if WITH_EDITOR + +#include "AkWaapiUMG/Components/AkPropertyToControlCustomization.h" +#include "AkAudioDevice.h" +#include "Framework/Application/SlateApplication.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SButton.h" +#include "DetailWidgetRow.h" +#include "AkWaapiUMG/Components/SAkItemProperties.h" +#include "AkAudioStyle.h" + +#if UE_5_0_OR_LATER +#include "Framework/Docking/TabManager.h" +#endif + +#define LOCTEXT_NAMESPACE "AkPropertyToControlCustomization" + +TSharedRef FAkPropertyToControlCustomization::MakeInstance() +{ + return MakeShareable(new FAkPropertyToControlCustomization()); +} + +void FAkPropertyToControlCustomization::CustomizeHeader( TSharedRef StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils ) +{ + ItemPropertyHandle = StructPropertyHandle->GetChildHandle("ItemProperty"); + + if(ItemPropertyHandle.IsValid()) + { + TSharedPtr PickerWidget = nullptr; + + PickerWidget = SAssignNew(PickerButton, SButton) + .ButtonStyle(FAkAudioStyle::Get(), "AudiokineticTools.HoverHintOnly" ) + .ToolTipText( LOCTEXT( "WwisePropertyToolTipText", "Choose a property") ) + .OnClicked(FOnClicked::CreateSP(this, &FAkPropertyToControlCustomization::OnPickContent, ItemPropertyHandle.ToSharedRef())) + .ContentPadding(2.0f) + .ForegroundColor( FSlateColor::UseForeground() ) + .IsFocusable(false) + [ + SNew(SImage) + .Image(FAkAudioStyle::GetBrush("AudiokineticTools.Button_EllipsisIcon")) + .ColorAndOpacity(FSlateColor::UseForeground()) + ]; + + HeaderRow.ValueContent() + .MinDesiredWidth(125.0f) + .MaxDesiredWidth(600.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + [ + ItemPropertyHandle->CreatePropertyValueWidget() + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(FMargin(4.0f, 0.0f, 0.0f, 0.0f)) + .VAlign(VAlign_Center) + [ + PickerWidget.ToSharedRef() + ] + + ] + .NameContent() + [ + StructPropertyHandle->CreatePropertyNameWidget() + ]; + + } +} + +void FAkPropertyToControlCustomization::CustomizeChildren( TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils ) +{ +} + +FReply FAkPropertyToControlCustomization::OnPickContent(TSharedRef PropertyHandle) +{ + Window = SNew(SWindow) + .Title(LOCTEXT("PropertyPickerWindowTitle", "Choose A Property")) + .SizingRule(ESizingRule::UserSized) + .AutoCenter(EAutoCenter::PreferredWorkArea) + .ClientSize(FVector2D(350, 400)); + + Window->SetContent( + SNew(SBorder) + [ + SNew(SAkItemProperties) + .FocusSearchBoxWhenOpened(true) + .SelectionMode(ESelectionMode::Single) + .OnSelectionChanged(this, &FAkPropertyToControlCustomization::PropertySelectionChanged) + ] + ); + + TSharedPtr RootWindow = FGlobalTabmanager::Get()->GetRootWindow(); + FSlateApplication::Get().AddWindowAsNativeChild(Window.ToSharedRef(), RootWindow.ToSharedRef()); + return FReply::Handled(); +} + +void FAkPropertyToControlCustomization::PropertySelectionChanged(TSharedPtr< FString > ItemProperty, ESelectInfo::Type SelectInfo) +{ + if (ItemProperty.IsValid()) + { + ItemPropertyHandle->SetValue(*ItemProperty.Get()); + Window->RequestDestroyWindow(); + } +} +#undef LOCTEXT_NAMESPACE + +#endif//WITH_EDITOR diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkSlider.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkSlider.cpp new file mode 100644 index 0000000..c8323de --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkSlider.cpp @@ -0,0 +1,408 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ +includes. +------------------------------------------------------------------------------------*/ + +#include "AkWaapiUMG/Components/AkSlider.h" +#include "AkAudioDevice.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "AkWaapiSlate/Widgets/Input/AkSSlider.h" +#include "UMGStyle.h" +#include "AkWaapiUMG/Components/AkWwiseObjectDetailsStructCustomization.h" +#include "AkWaapiUMG/Components/AkPropertyToControlCustomization.h" +#include "AkWaapiUMG/Components/WwiseUmgDragDropOp.h" +#include "AkWaapiUMG/Components/WwisePropertyDragDropOp.h" +#include "AkWaapiUtils.h" +#include "AkWaapiBlueprints/AkWaapiCalls.h" +#include "Widgets/Input/SSlider.h" +#include "Async/Async.h" + +#if WITH_EDITOR +#include "Editor.h" +#include "Modules/ModuleManager.h" +#include "PropertyEditorModule.h" +#endif + +/*------------------------------------------------------------------------------------ +Defines +------------------------------------------------------------------------------------*/ +#define LOCTEXT_NAMESPACE "AkWaapiUMG" + +/*------------------------------------------------------------------------------------ +Helpers +------------------------------------------------------------------------------------*/ +static inline const float ComputeValue(const float inValue, const float inMaxValue, const float inMinValue) +{ + return (inValue - inMinValue) / (inMaxValue - inMinValue); +} +/*------------------------------------------------------------------------------------ +Implementation +------------------------------------------------------------------------------------*/ +///////////////////////////////////////////////////// +// UAkSlider + +UAkSlider::UAkSlider(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + Orientation = EOrientation::Orient_Horizontal; + SliderBarColor = FLinearColor::White; + SliderHandleColor = FLinearColor::White; + StepSize = 0.01f; + AkSSlider::FArguments Defaults; + WidgetStyle = *Defaults._Style; + IsFocusable = true; + SubscriptionId = 0; +} + +void UAkSlider::BeginDestroy() +{ + UnsubscribePropertyChangedCallback(); + Super::BeginDestroy(); +} + + +TSharedRef UAkSlider::RebuildWidget() +{ +#if WITH_EDITOR + FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); + PropertyModule.RegisterCustomPropertyTypeLayout("AkWwiseObjectDetails", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FAkWwiseObjectDetailsStructCustomization::MakeInstance)); + PropertyModule.RegisterCustomPropertyTypeLayout("AkPropertyToControl", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FAkPropertyToControlCustomization::MakeInstance)); + PropertyModule.NotifyCustomizationModuleChanged(); +#endif//WITH_EDITOR + + MyAkSlider = SNew(AkSSlider) + .Style(&WidgetStyle) + .IsFocusable(IsFocusable) + .OnValueChanged(BIND_UOBJECT_DELEGATE(FOnFloatValueChanged, HandleOnValueChanged)) + .OnDrop(FOnDrop::CreateUObject(this, &UAkSlider::HandleDropped)); + + SynchronizePropertyWithWwise(); + +#if WITH_EDITOR + if (!IsRunningGame()) + { + return CreateDesignerOutline(MyAkSlider.ToSharedRef()); + } +#endif + return MyAkSlider.ToSharedRef(); +} + +void UAkSlider::SynchronizeProperties() +{ + Super::SynchronizeProperties(); + + TAttribute ValueBinding = PROPERTY_BINDING(float, Value); + + if (MyAkSlider.IsValid()) + { + TSharedPtr MySlider = MyAkSlider->GetAkSilder(); + + MySlider->SetOrientation(Orientation); + MySlider->SetSliderBarColor(SliderBarColor); + MySlider->SetSliderHandleColor(SliderHandleColor); + MySlider->SetValue(ValueBinding); + MySlider->SetLocked(Locked); + MySlider->SetIndentHandle(IndentHandle); + MySlider->SetStepSize(StepSize); + MyAkSlider->SetAkSliderRangeMin(minValue); + MyAkSlider->SetAkSliderRangeMax(maxValue); + + ItemToControl.ItemPath = ItemToControl.ItemPicked.ItemPath; + bool shouldSynchronize = false; + if (!ThePropertyToControl.ItemProperty.IsEmpty() && (ThePropertyToControl.ItemProperty != MyAkSlider->GetAkSliderItemProperty())) + { + MyAkSlider->SetAkSliderItemProperty(ThePropertyToControl.ItemProperty); + shouldSynchronize = true; + } + if (!ItemToControl.ItemPicked.ItemId.IsEmpty() && (ItemToControl.ItemPicked.ItemId != MyAkSlider->GetAkSliderItemId())) + { + MyAkSlider->SetAkSliderItemId(ItemToControl.ItemPicked.ItemId); + shouldSynchronize = true; + } + if (shouldSynchronize) + SynchronizePropertyWithWwise(); + } +} + +void UAkSlider::ReleaseSlateResources(bool bReleaseChildren) +{ + Super::ReleaseSlateResources(bReleaseChildren); + UnsubscribePropertyChangedCallback(); + MyAkSlider.Reset(); +} + +void UAkSlider::HandleOnValueChanged(float InValue) +{ + OnValueChanged.Broadcast(InValue); +} + +float UAkSlider::GetValue() const +{ + if (MyAkSlider.IsValid() ) + { + TSharedPtr MySlider = MyAkSlider->GetAkSilder(); + if (MySlider.IsValid()) + { + return MySlider->GetValue(); + } + } + return Value; +} + +void UAkSlider::SetValue(float InValue) +{ + Value = InValue; + if ( MyAkSlider.IsValid() ) + { + TSharedPtr MySlider = MyAkSlider->GetAkSilder(); + if (MySlider.IsValid()) + { + MySlider->SetValue(InValue); + } + } +} + +void UAkSlider::SetIndentHandle(bool InIndentHandle) +{ + IndentHandle = InIndentHandle; + if ( MyAkSlider.IsValid() ) + { + TSharedPtr MySlider = MyAkSlider->GetAkSilder(); + if (MySlider.IsValid()) + { + MySlider->SetIndentHandle(InIndentHandle); + } + } +} + +void UAkSlider::SetLocked(bool InLocked) +{ + Locked = InLocked; + if ( MyAkSlider.IsValid() ) + { + TSharedPtr MySlider = MyAkSlider->GetAkSilder(); + if (MySlider.IsValid()) + { + MySlider->SetLocked(InLocked); + } + } +} + +void UAkSlider::SetStepSize(float InValue) +{ + StepSize = InValue; + if (MyAkSlider.IsValid()) + { + TSharedPtr MySlider = MyAkSlider->GetAkSilder(); + if (MySlider.IsValid()) + { + MySlider->SetStepSize(InValue); + } + } +} + +void UAkSlider::SetSliderHandleColor(FLinearColor InValue) +{ + SliderHandleColor = InValue; + if (MyAkSlider.IsValid()) + { + TSharedPtr MySlider = MyAkSlider->GetAkSilder(); + if (MySlider.IsValid()) + { + MySlider->SetSliderHandleColor(InValue); + } + } +} + +void UAkSlider::SetSliderBarColor(FLinearColor InValue) +{ + SliderBarColor = InValue; + if (MyAkSlider.IsValid()) + { + TSharedPtr MySlider = MyAkSlider->GetAkSilder(); + if (MySlider.IsValid()) + { + MySlider->SetSliderBarColor(InValue); + } + } +} + +void UAkSlider::SetAkSliderItemId(const FGuid& ItemId) +{ + if (ItemId.IsValid()) + { + MyAkSlider->SetAkSliderItemId(ItemId.ToString(EGuidFormats::DigitsWithHyphensInBraces)); + SynchronizePropertyWithWwise(); + } +} + +const FGuid UAkSlider::GetAkSliderItemId() const +{ + FGuid ItemId = FGuid::NewGuid(); + FGuid::ParseExact(MyAkSlider->GetAkSliderItemId(), EGuidFormats::DigitsWithHyphensInBraces, ItemId); + return ItemId; +} + +void UAkSlider::SetAkSliderItemProperty(const FString& ItemProperty) +{ + if (!ItemProperty.IsEmpty()) + { + MyAkSlider->SetAkSliderItemProperty(ItemProperty); + SynchronizePropertyWithWwise(); + } +} + +const FString UAkSlider::GetAkSliderItemProperty() const +{ + return MyAkSlider->GetAkSliderItemProperty(); +} + +void UAkSlider::SynchronizePropertyWithWwise() +{ + if (MyAkSlider.IsValid()) + { + const FString& ItemId = MyAkSlider->GetAkSliderItemId(); + const FString& ItemProperty = MyAkSlider->GetAkSliderItemProperty(); + + if (!ItemId.IsEmpty() && !ItemProperty.IsEmpty()) + { + TSharedPtr ItemInfoResult; + if (CallWappiGetPropertySate(ItemId, ItemProperty, ItemInfoResult) && MyAkSlider.IsValid()) + { + double PropertyValue = 0.0; + if (ItemInfoResult->TryGetNumberField(WwiseWaapiHelper::AT + ItemProperty, PropertyValue)) + { + double maxRange = MyAkSlider->GetAkSliderRangeMax().GetValue(); + double minRange = MyAkSlider->GetAkSliderRangeMin().GetValue(); + if (PropertyValue > maxRange) + { + MyAkSlider->SetAkSliderRangeMax(PropertyValue); + maxRange = PropertyValue; + } + if (PropertyValue < minRange) + { + MyAkSlider->SetAkSliderRangeMin(PropertyValue); + minRange = PropertyValue; + } + const float newValue = ComputeValue(PropertyValue, maxRange, minRange); + SetValue(newValue); + } + } + + if (SubscriptionId != 0) + { + bool isUnsubscribed; + UAkWaapiCalls::Unsubscribe(FAkWaapiSubscriptionId(SubscriptionId), isUnsubscribed); + } + TSharedPtr outJsonResult; + + auto wampEventCallback = WampEventCallback::CreateLambda([this](uint64_t id, TSharedPtr in_UEJsonObject) + { + AsyncTask(ENamedThreads::GameThread, [this, id, in_UEJsonObject] + { + if (MyAkSlider.IsValid()) + { + double PropertyValue = 0.0; + if (in_UEJsonObject->TryGetNumberField(WwiseWaapiHelper::NEW, PropertyValue)) + { + double maxRange = MyAkSlider->GetAkSliderRangeMax().GetValue(); + double minRange = MyAkSlider->GetAkSliderRangeMin().GetValue(); + if (PropertyValue > maxRange) + { + MyAkSlider->SetAkSliderRangeMax(PropertyValue); + maxRange = PropertyValue; + } + else if (PropertyValue < minRange) + { + MyAkSlider->SetAkSliderRangeMin(PropertyValue); + minRange = PropertyValue; + } + const float newValue = ComputeValue(PropertyValue, maxRange, minRange); + SetValue(newValue); + } + } + }); + }); + SubscribeToPropertyStateChange(ItemId, ItemProperty, wampEventCallback, SubscriptionId, outJsonResult); + } + } +} + +void UAkSlider::UnsubscribePropertyChangedCallback() +{ + if (SubscriptionId != 0) + { + bool isUnsubscribed; + UAkWaapiCalls::Unsubscribe(FAkWaapiSubscriptionId(SubscriptionId), isUnsubscribed); + if (isUnsubscribed) + SubscriptionId = 0; + } +} + +FReply UAkSlider::HandleDropped(const FGeometry& DropZoneGeometry, const FDragDropEvent& DragDropEvent) +{ + TSharedPtr Operation = DragDropEvent.GetOperation(); + if (Operation.IsValid()) + { + if (OnItemDropped.IsBound() && Operation->IsOfType()) + { + const auto& AssetDragDropOp = StaticCastSharedPtr(Operation); + if (AssetDragDropOp.IsValid()) + { + const TArray>& WwiseAssets = AssetDragDropOp->GetWiseItems(); + if (WwiseAssets.Num() && WwiseAssets[0].IsValid()) + { + OnItemDropped.Broadcast(WwiseAssets[0]->ItemId); + SynchronizePropertyWithWwise(); + return FReply::Handled(); + } + } + } + else if (OnPropertyDropped.IsBound() && Operation->IsOfType()) + { + const auto& AssetDragDropOp = StaticCastSharedPtr(Operation); + if (AssetDragDropOp.IsValid()) + { + const TArray>& WwiseAssets = AssetDragDropOp->GetWiseProperties(); + if (WwiseAssets.Num() && WwiseAssets[0].IsValid()) + { + OnPropertyDropped.Broadcast(*WwiseAssets[0].Get()); + SynchronizePropertyWithWwise(); + return FReply::Handled(); + } + } + } + + SynchronizePropertyWithWwise(); + } + return FReply::Unhandled(); +} + +#if WITH_EDITOR + +const FText UAkSlider::GetPaletteCategory() +{ + return LOCTEXT("Wwise", "Wwise"); +} + +#endif + +///////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkWwiseObjectDetailsStructCustomization.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkWwiseObjectDetailsStructCustomization.cpp new file mode 100644 index 0000000..a417560 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkWwiseObjectDetailsStructCustomization.cpp @@ -0,0 +1,129 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#if WITH_EDITOR + +#include "AkWaapiUMG/Components/AkWwiseObjectDetailsStructCustomization.h" +#include "AkAudioDevice.h" +#include "Framework/Application/SlateApplication.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SButton.h" +#include "DetailWidgetRow.h" +#include "AkAudioStyle.h" + +#if UE_5_0_OR_LATER +#include "Framework/Docking/TabManager.h" +#endif + +#define LOCTEXT_NAMESPACE "AkWwiseObjectDetailsStructCustomization" + +TSharedRef FAkWwiseObjectDetailsStructCustomization::MakeInstance() +{ + return MakeShareable(new FAkWwiseObjectDetailsStructCustomization()); +} + +void FAkWwiseObjectDetailsStructCustomization::CustomizeHeader( TSharedRef StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils ) +{ + ItemNameProperty = StructPropertyHandle->GetChildHandle("ItemName"); + ItemPathProperty = StructPropertyHandle->GetChildHandle("ItemPath"); + ItemIdProperty = StructPropertyHandle->GetChildHandle("ItemId"); + + if(ItemNameProperty.IsValid()) + { + TSharedPtr PickerWidget = nullptr; + + PickerWidget = SAssignNew(PickerButton, SButton) + .ButtonStyle(FAkAudioStyle::Get(), "AudiokineticTools.HoverHintOnly" ) + .ToolTipText( LOCTEXT( "WwiseItemToolTipText", "Choose a Wwise Item") ) + .OnClicked(FOnClicked::CreateSP(this, &FAkWwiseObjectDetailsStructCustomization::OnPickContent, ItemNameProperty.ToSharedRef())) + .ContentPadding(2.0f) + .ForegroundColor( FSlateColor::UseForeground() ) + .IsFocusable(false) + [ + SNew(SImage) + .Image(FAkAudioStyle::GetBrush("AudiokineticTools.Button_EllipsisIcon")) + .ColorAndOpacity(FSlateColor::UseForeground()) + ]; + + HeaderRow.ValueContent() + .MinDesiredWidth(125.0f) + .MaxDesiredWidth(600.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + [ + ItemNameProperty->CreatePropertyValueWidget() + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(FMargin(4.0f, 0.0f, 0.0f, 0.0f)) + .VAlign(VAlign_Center) + [ + PickerWidget.ToSharedRef() + ] + + ] + .NameContent() + [ + StructPropertyHandle->CreatePropertyNameWidget() + ]; + + } +} + +void FAkWwiseObjectDetailsStructCustomization::CustomizeChildren( TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils ) +{ +} + +FReply FAkWwiseObjectDetailsStructCustomization::OnPickContent(TSharedRef PropertyHandle) +{ + TSharedRef Window = SNew(SWindow) + .Title(LOCTEXT("PropertyPickerWindowTitle", "Choose A Wwise Item")) + .SizingRule(ESizingRule::UserSized) + .AutoCenter(EAutoCenter::PreferredWorkArea) + .ClientSize(FVector2D(350, 400)); + + Window->SetContent( + SNew(SBorder) + [ + SAssignNew(WaapiPicker, SWaapiPicker) + .FocusSearchBoxWhenOpened(true) + .SelectionMode(ESelectionMode::Single) + .OnSelectionChanged(this, &FAkWwiseObjectDetailsStructCustomization::TreeSelectionChanged) + ] + ); + + TSharedPtr RootWindow = FGlobalTabmanager::Get()->GetRootWindow(); + FSlateApplication::Get().AddWindowAsNativeChild(Window, RootWindow.ToSharedRef()); + return FReply::Handled(); +} + +void FAkWwiseObjectDetailsStructCustomization::TreeSelectionChanged(TSharedPtr< FWwiseTreeItem > TreeItem, ESelectInfo::Type SelectInfo) +{ + if (TreeItem.IsValid()) + { + ItemNameProperty->SetValue(TreeItem->DisplayName); + ItemIdProperty->SetValue(TreeItem->ItemId.ToString(EGuidFormats::DigitsWithHyphensInBraces)); + ItemPathProperty->SetValue(TreeItem->FolderPath); + } +} +#undef LOCTEXT_NAMESPACE + +#endif//WITH_EDITOR + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkWwiseTree.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkWwiseTree.cpp new file mode 100644 index 0000000..b846be4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkWwiseTree.cpp @@ -0,0 +1,164 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ +includes. +------------------------------------------------------------------------------------*/ + +#include "AkWaapiUMG/Components/AkWwiseTree.h" +#include "AkAudioDevice.h" +#include "Widgets/Input/SButton.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "AkWaapiUMG/Components/WwiseUmgDragDropOp.h" +#include "AkWaapiSlate/Widgets/Input/AkSSlider.h" +/*------------------------------------------------------------------------------------ +Defines +------------------------------------------------------------------------------------*/ +#define LOCTEXT_NAMESPACE "AkWaapiUMG" + +/*------------------------------------------------------------------------------------ +Implementation +------------------------------------------------------------------------------------*/ +///////////////////////////////////////////////////// +// UAkWwiseTree + +UAkWwiseTree::UAkWwiseTree(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +TSharedRef UAkWwiseTree::RebuildWidget() +{ + bool autoFocus = GetWorld()->WorldType != EWorldType::Game && + GetWorld()->WorldType != EWorldType::PIE && + GetWorld()->WorldType != EWorldType::GamePreview; + return + SNew(SBorder) + .BorderImage(FAkAudioStyle::GetBrush("AudiokineticTools.GroupBorder")) + .Padding(FMargin(0.0f, 3.0f)) + .BorderBackgroundColor(FLinearColor(.6, .6, .6, 1.0f)) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(FMargin(10.0f, 0.0f, 0.0f, 0.0f)) + .AutoWidth() + .VAlign(VAlign_Center) + [ + SAssignNew(ItemTextBlock, STextBlock) + .IsEnabled(true) + .ToolTipText(LOCTEXT("editable_Tooltip", "Item Name")) + .MinDesiredWidth(300.f) + .Text(FText::FromString(TEXT("Choose an item from the Wwise tree"))) + ] + ] + + SVerticalBox::Slot() + [ + SAssignNew(WaapiPicker, SWaapiPicker) + .RestrictContextMenu(true) + .FocusSearchBoxWhenOpened(autoFocus) + .SelectionMode(ESelectionMode::Single) + .OnSelectionChanged(FOnSelectionChanged::CreateUObject(this, &UAkWwiseTree::TreeSelectionChanged)) + .OnDragDetected(FOnDragDetected::CreateUObject(this, &UAkWwiseTree::HandleOnDragDetected)) + ] + ]; +} + +void UAkWwiseTree::SynchronizeProperties() +{ + Super::SynchronizeProperties(); + +} + +void UAkWwiseTree::ReleaseSlateResources(bool bReleaseChildren) +{ + Super::ReleaseSlateResources(bReleaseChildren); + + WaapiPicker.Reset(); +} + +void UAkWwiseTree::TreeSelectionChanged(TSharedPtr< FWwiseTreeItem > TreeItem, ESelectInfo::Type SelectInfo) +{ + if (TreeItem.IsValid()) + { + ItemTextBlock->SetText(FText::FromString(TreeItem->DisplayName)); + + if (OnSelectionChanged.IsBound()) + { + OnSelectionChanged.Broadcast(TreeItem->ItemId); + } + } +} + +FReply UAkWwiseTree::HandleOnDragDetected(const FGeometry& Geometry, const FPointerEvent& MouseEvent) +{ + if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton) && WaapiPicker.IsValid()) + { + const TArray>& ItemsSelected = WaapiPicker->GetSelectedItems(); + if (ItemsSelected.Num() == 1) + { + if(OnItemDragged.IsBound()) + { + OnItemDragged.Broadcast(ItemsSelected[0]->ItemId, ItemsSelected[0]->DisplayName); + } + return FReply::Handled().BeginDragDrop(FWwiseUmgDragDropOp::New(ItemsSelected)); + } + } + return FReply::Unhandled(); +} + +FAkWwiseObjectDetails UAkWwiseTree::GetSelectedItem() const +{ + const TArray> selectedItems = WaapiPicker->GetSelectedItems(); + + FAkWwiseObjectDetails itemObjectDetails = FAkWwiseObjectDetails(); + if (selectedItems.Num()) + { + TSharedPtr itemSelected = selectedItems[0]; + itemObjectDetails.ItemId = itemSelected->ItemId.ToString(EGuidFormats::DigitsWithHyphensInBraces); + itemObjectDetails.ItemName = itemSelected->DisplayName; + itemObjectDetails.ItemPath = itemSelected->FolderPath; + } + return itemObjectDetails; +} + +FString UAkWwiseTree::GetSearchText() const +{ + return WaapiPicker->GetSearchText(); +} + +void UAkWwiseTree::SetSearchText(const FString& newText) +{ + WaapiPicker->SetSearchText(newText); +} + + +#if WITH_EDITOR + +const FText UAkWwiseTree::GetPaletteCategory() +{ + return LOCTEXT("Wwise", "Wwise"); +} + +#endif +// +///////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkWwiseTreeSelector.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkWwiseTreeSelector.cpp new file mode 100644 index 0000000..a671e86 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/AkWwiseTreeSelector.cpp @@ -0,0 +1,175 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ +includes. +------------------------------------------------------------------------------------*/ + +#include "AkWaapiUMG/Components/AkWwiseTreeSelector.h" +#include "AkAudioDevice.h" +#include "Widgets/Input/SButton.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Framework/Docking/TabManager.h" +#include "Framework/Application/SlateApplication.h" +#include "AkWaapiUMG/Components/WwiseUmgDragDropOp.h" +#include "AkWaapiSlate/Widgets/Input/AkSSlider.h" + +/*------------------------------------------------------------------------------------ +Defines +------------------------------------------------------------------------------------*/ +#define LOCTEXT_NAMESPACE "AkWaapiUMG" + +/*------------------------------------------------------------------------------------ +Implementation +------------------------------------------------------------------------------------*/ +///////////////////////////////////////////////////// +// UAkWwiseTreeSelector + +UAkWwiseTreeSelector::UAkWwiseTreeSelector(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + AkSSlider::FArguments Defaults; +} + +TSharedRef UAkWwiseTreeSelector::RebuildWidget() +{ + TSharedPtr PickerWidget = nullptr; + + PickerWidget = SAssignNew(PickerButton, SButton) + .ButtonStyle(FAkAudioStyle::Get(), "AudiokineticTools.HoverHintOnly") + .ToolTipText(LOCTEXT("WwiseItemToolTipText", "Choose a Wwise Item")) + .OnClicked(FOnClicked::CreateUObject(this, &UAkWwiseTreeSelector::HandleButtonClicked)) + .ContentPadding(2.0f) + .ForegroundColor(FSlateColor::UseForeground()) + .IsFocusable(true) + [ + SNew(SImage) + .Image(FAkAudioStyle::GetBrush("AudiokineticTools.Button_EllipsisIcon")) + .ColorAndOpacity(FSlateColor::UseForeground()) + ]; + + return + SNew(SBorder) + .BorderImage(FAkAudioStyle::GetBrush("AudiokineticTools.GroupBorder")) + .Padding(FMargin(0.0f, 3.0f)) + .BorderBackgroundColor(FLinearColor(.6, .6, .6, 1.0f)) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(FMargin(10.0f, 0.0f, 0.0f, 0.0f)) + .AutoWidth() + .VAlign(VAlign_Center) + [ + SAssignNew(ItemTextBlock, STextBlock) + .IsEnabled(true) + .ToolTipText(LOCTEXT("editable_Tooltip", "Item Name")) + .MinDesiredWidth(300.f) + .Text(FText::FromString(TEXT("Choose an item from the Wwise tree"))) + ] + + SHorizontalBox::Slot() + .Padding(FMargin(0.0f, 0.0f, 10.0f, 0.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Right) + [ + PickerWidget.ToSharedRef() + ] + ]; +} + +FReply UAkWwiseTreeSelector::HandleButtonClicked() +{ + TSharedRef Window = SNew(SWindow) + .Title(LOCTEXT("PropertyPickerWindowTitle", "Choose A Wwise Item")) + .SizingRule(ESizingRule::UserSized) + .AutoCenter(EAutoCenter::PreferredWorkArea) + .ClientSize(FVector2D(350, 400)); + + Window->SetContent( + SNew(SBorder) + [ + SAssignNew(WaapiPicker, SWaapiPicker) + .RestrictContextMenu(true) + .FocusSearchBoxWhenOpened(true) + .SelectionMode(ESelectionMode::Single) + .OnSelectionChanged(FOnSelectionChanged::CreateUObject(this, &UAkWwiseTreeSelector::TreeSelectionChanged)) + .OnDragDetected(FOnDragDetected::CreateUObject(this, &UAkWwiseTreeSelector::HandleOnDragDetected)) + ] + ); +#if WITH_EDITOR + TSharedPtr RootWindow = FGlobalTabmanager::Get()->GetRootWindow(); + FSlateApplication::Get().AddWindowAsNativeChild(Window, RootWindow.ToSharedRef()); +#endif//WITH_EDITOR + return FReply::Handled(); +} + +void UAkWwiseTreeSelector::SynchronizeProperties() +{ + Super::SynchronizeProperties(); +} + +void UAkWwiseTreeSelector::ReleaseSlateResources(bool bReleaseChildren) +{ + Super::ReleaseSlateResources(bReleaseChildren); + + ItemTextBlock.Reset(); + WaapiPicker.Reset(); + PickerButton.Reset(); + PickerMenu.Reset(); +} + +void UAkWwiseTreeSelector::TreeSelectionChanged(TSharedPtr< FWwiseTreeItem > TreeItem, ESelectInfo::Type SelectInfo) +{ + if (TreeItem.IsValid()) + { + ItemTextBlock->SetText(FText::FromString(TreeItem->DisplayName)); + + if (OnSelectionChanged.IsBound()) + { + OnSelectionChanged.Broadcast(TreeItem->ItemId); + } + } +} + +FReply UAkWwiseTreeSelector::HandleOnDragDetected(const FGeometry& Geometry, const FPointerEvent& MouseEvent) +{ + if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton) && WaapiPicker.IsValid()) + { + const TArray>& ItemsSelected = WaapiPicker->GetSelectedItems(); + if (ItemsSelected.Num() == 1) + { + if (OnItemDragged.IsBound()) + { + OnItemDragged.Broadcast(ItemsSelected[0]->ItemId, ItemsSelected[0]->DisplayName); + } + return FReply::Handled().BeginDragDrop(FWwiseUmgDragDropOp::New(ItemsSelected)); + } + } + return FReply::Unhandled(); +} + +#if WITH_EDITOR + +const FText UAkWwiseTreeSelector::GetPaletteCategory() +{ + return LOCTEXT("Wwise", "Wwise"); +} + +#endif +// +///////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/SAkItemBoolProperties.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/SAkItemBoolProperties.cpp new file mode 100644 index 0000000..32ff760 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/SAkItemBoolProperties.cpp @@ -0,0 +1,268 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ + SAkItemBoolProperties.cpp +------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------ + includes. +------------------------------------------------------------------------------------*/ +#include "AkWaapiUMG/Components/SAkItemBoolProperties.h" +#include "AkAudioDevice.h" +#include "Widgets/Input/SSearchBox.h" +#include "Widgets/Views/SListView.h" +#include "Misc/ScopedSlowTask.h" +#include "AkAudioStyle.h" +#include "Framework/Application/SlateApplication.h" + +/*------------------------------------------------------------------------------------ +Defines +------------------------------------------------------------------------------------*/ +#define LOCTEXT_NAMESPACE "AkAudio" + +/*------------------------------------------------------------------------------------ +Statics and Globals +------------------------------------------------------------------------------------*/ +namespace SAkItemBoolProperties_Helpers +{ + static const FString FullBoolPropertiesList[] = + { + TEXT("BypassEffect"), + TEXT("BypassEffect0"), + TEXT("BypassEffect1"), + TEXT("BypassEffect2"), + TEXT("BypassEffect3"), +#if AK_WWISESDK_VERSION_MAJOR <= 2017 + TEXT("DynamicPositioning"), +#endif +#if AK_WWISESDK_VERSION_MAJOR >= 2019 + TEXT("EnableAttenuation"), +#endif + TEXT("EnableLoudnessNormalization"), + TEXT("EnableMidiNoteTracking"), +#if AK_WWISESDK_VERSION_MAJOR <= 2017 + TEXT("EnablePanner"), + TEXT("EnablePositioning"), + TEXT("FollowListenerOrientation"), +#endif + TEXT("HdrEnableEnvelope"), +#if AK_WWISESDK_VERSION_MAJOR >= 2018 + TEXT("HoldEmitterPositionOrientation"), + TEXT("HoldListenerOrientation"), +#endif + TEXT("IgnoreParentMaxSoundInstance"), + TEXT("Inclusion"), + TEXT("IsLoopingEnabled"), + TEXT("IsLoopingInfinite"), + TEXT("IsNonCachable"), + TEXT("IsStreamingEnabled"), + TEXT("IsZeroLantency"), +#if AK_WWISESDK_VERSION_MAJOR >= 2018 + TEXT("ListenerRelativeRouting"), +#endif + TEXT("MidiBreakOnNoteOff"), + TEXT("OverrideAnalysis"), + TEXT("OverrideConversion"), + TEXT("OverrideEffect"), + TEXT("OverrideGameAuxSends"), + TEXT("OverrideHdrEnvelope"), + TEXT("OverrideMidiEventsBehavior"), + TEXT("OverrideMidiNoteTracking"), + TEXT("OverrideOutput"), + TEXT("OverridePositioning"), + TEXT("OverridePriority"), + TEXT("OverrideUserAuxSends"), + TEXT("OverrideVirtualVoice"), + TEXT("PriorityDistanceFactor"), + TEXT("RenderEffect0"), + TEXT("RenderEffect1"), + TEXT("RenderEffect2"), + TEXT("RenderEffect3"), +#if AK_WWISESDK_VERSION_MAJOR <= 2017 + TEXT("Spatialization"), +#endif + TEXT("UseGameAuxSends"), + TEXT("UseMaxSoundPerInstance") + }; + + enum { FullBoolPropertiesListSize = sizeof(FullBoolPropertiesList) / sizeof(*FullBoolPropertiesList) }; +} +/*------------------------------------------------------------------------------------ +Helpers +------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------ +SAkItemBoolProperties +------------------------------------------------------------------------------------*/ +SAkItemBoolProperties::SAkItemBoolProperties() +{} + +SAkItemBoolProperties::~SAkItemBoolProperties() +{} + +void SAkItemBoolProperties::Construct(const FArguments& InArgs) +{ + OnDragDetected = InArgs._OnDragDetected; + OnSelectionChanged = InArgs._OnSelectionChanged; + + if (InArgs._FocusSearchBoxWhenOpened) + { + RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &SAkItemBoolProperties::SetFocusPostConstruct)); + } + + SearchBoxFilter = MakeShareable( new StringFilter( StringFilter::FItemToStringArray::CreateSP( this, &SAkItemBoolProperties::PopulateSearchStrings ) ) ); + SearchBoxFilter->OnChanged().AddSP( this, &SAkItemBoolProperties::FilterUpdated ); + + ChildSlot + [ + SNew(SBorder) + .Padding(4) + .BorderImage(FAkAudioStyle::GetBrush("AudiokineticTools.GroupBorder")) + [ + SNew(SVerticalBox) + // Search + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 1, 0, 3) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + InArgs._SearchContent.Widget + ] + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SAssignNew(SearchBoxPtr,SSearchBox) + .HintText( LOCTEXT( "PropertiesSearchHint", "Search an item property" ) ) + .ToolTipText(LOCTEXT("PropertiesSearchTooltip", "Type here to search for a property")) + .OnTextChanged(SearchBoxFilter.Get(), &StringFilter::SetRawFilterText) + .SelectAllTextWhenFocused(false) + .DelayChangeNotificationsWhileTyping(true) + ] + ] + // Tree + +SVerticalBox::Slot() + .FillHeight(1.f) + [ + SAssignNew(ListViewPtr, SListView< TSharedPtr >) + .ListItemsSource(&PropertiesList) + .OnGenerateRow( this, &SAkItemBoolProperties::GenerateRow ) + .ItemHeight(18) + .SelectionMode(InArgs._SelectionMode) + .OnSelectionChanged(this, &SAkItemBoolProperties::ListSelectionChanged) + .ClearSelectionOnClick(false) + ] + ] + ]; + + for (const auto& Property : SAkItemBoolProperties_Helpers::FullBoolPropertiesList) + { + PropertiesList.Add(MakeShareable(new FString(Property))); + } + ListViewPtr->RequestListRefresh(); +} + +TSharedRef SAkItemBoolProperties::GenerateRow( TSharedPtr ItemProperty, const TSharedRef& OwnerTable ) +{ + check(ItemProperty.IsValid()); + + TSharedPtr NewRow = SNew(STableRow< TSharedPtr >, OwnerTable) + .OnDragDetected(this, &SAkItemBoolProperties::HandleOnDragDetected) + [ + SNew(STextBlock) + .Text(FText::FromString(*ItemProperty.Get())) + .HighlightText(SearchBoxFilter.Get(), &StringFilter::GetRawFilterText) + ]; + return NewRow.ToSharedRef(); +} + +FReply SAkItemBoolProperties::HandleOnDragDetected(const FGeometry& Geometry, const FPointerEvent& MouseEvent) +{ + // Refresh the contents + if(OnDragDetected.IsBound()) + return OnDragDetected.Execute(Geometry,MouseEvent); + return FReply::Unhandled(); +} + +void SAkItemBoolProperties::PopulateSearchStrings( const FString& PropertyName, OUT TArray< FString >& OutSearchStrings ) const +{ + OutSearchStrings.Add( PropertyName ); +} + +void SAkItemBoolProperties::FilterUpdated() +{ + FScopedSlowTask SlowTask(2.f, LOCTEXT("AK_PopulatingPicker", "Populating Properties Picker...")); + SlowTask.MakeDialog(); + + PropertiesList.Empty(SAkItemBoolProperties_Helpers::FullBoolPropertiesListSize); + + FString FilterString = SearchBoxFilter->GetRawFilterText().ToString(); + if (FilterString.IsEmpty()) + { + for (const auto& Property : SAkItemBoolProperties_Helpers::FullBoolPropertiesList) + { + PropertiesList.Add(MakeShareable(new FString(Property))); + } + } + else + { + for (const auto& Property : SAkItemBoolProperties_Helpers::FullBoolPropertiesList) + { + if (Property.Contains(FilterString)) + { + PropertiesList.Add(MakeShareable(new FString(Property))); + } + } + } + + ListViewPtr->RequestListRefresh(); +} + +void SAkItemBoolProperties::ListSelectionChanged( TSharedPtr< FString > ItemProperty, ESelectInfo::Type /*SelectInfo*/ ) +{ + if (OnSelectionChanged.IsBound()) + OnSelectionChanged.Execute(ItemProperty, ESelectInfo::OnMouseClick); +} + +const TArray> SAkItemBoolProperties::GetSelectedProperties() const +{ + return ListViewPtr->GetSelectedItems(); +} + +const FString SAkItemBoolProperties::GetSearchText() const +{ + return SearchBoxFilter->GetRawFilterText().ToString(); +} + +const void SAkItemBoolProperties::SetSearchText(const FString& newText) +{ + SearchBoxPtr->SetText(FText::FromString(newText)); +} + +EActiveTimerReturnType SAkItemBoolProperties::SetFocusPostConstruct(double InCurrentTime, float InDeltaTime) +{ + FWidgetPath WidgetToFocusPath; + FSlateApplication::Get().GeneratePathToWidgetUnchecked(SearchBoxPtr.ToSharedRef(), WidgetToFocusPath); + FSlateApplication::Get().SetKeyboardFocus(WidgetToFocusPath, EFocusCause::SetDirectly); + + return EActiveTimerReturnType::Stop; +} +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/SAkItemProperties.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/SAkItemProperties.cpp new file mode 100644 index 0000000..10c30b3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUMG/Components/SAkItemProperties.cpp @@ -0,0 +1,263 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ + SAkItemProperties.cpp +------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------ + includes. +------------------------------------------------------------------------------------*/ +#include "AkWaapiUMG/Components/SAkItemProperties.h" +#include "AkAudioDevice.h" +#include "Widgets/Input/SSearchBox.h" +#include "Layout/WidgetPath.h" +#include "Misc/ScopedSlowTask.h" +#include "AkAudioStyle.h" +#include "Framework/Application/SlateApplication.h" + +/*------------------------------------------------------------------------------------ +Defines +------------------------------------------------------------------------------------*/ +#define LOCTEXT_NAMESPACE "AkAudio" + +/*------------------------------------------------------------------------------------ +Statics and Globals +------------------------------------------------------------------------------------*/ +namespace SAkItemProperties_Helpers +{ + static const FString FullPropertiesList[] = + { +#if AK_WWISESDK_VERSION_MAJOR >= 2018 + TEXT("CenterPercentage"), +#endif +#if AK_WWISESDK_VERSION_MAJOR <= 2017 + TEXT("DivergenceCenter"), +#endif + TEXT("GameAuxSendHPF"), + TEXT("GameAuxSendLPF"), + TEXT("GameAuxSendVolume"), + TEXT("HdrActiveRange"), + TEXT("HdrEnvelopeSensitivity"), + TEXT("Highpass"), + TEXT("InitialDelay"), + TEXT("LoopCount"), + TEXT("Lowpass"), + TEXT("MakeUpGain"), + TEXT("MaxSoundPerInstance"), + TEXT("MidiChannelFilter"), + TEXT("MidiKeyFilterMax"), + TEXT("MidiKeyFilterMin"), + TEXT("MidiTrackingRootNote"), + TEXT("MidiTransposition"), + TEXT("MidiVelocityFilterMax"), + TEXT("MidiVelocityFilterMin"), + TEXT("MidiVelocityOffset"), +#if AK_WWISESDK_VERSION_MAJOR <= 2017 + TEXT("MotionLowpass"), + TEXT("MotionVolume"), +#endif + TEXT("OutputBusHighpass"), + TEXT("OutputBusLowpass"), + TEXT("OutputBusVolume"), + TEXT("Pitch"), + TEXT("PreFetchLength"), + TEXT("Priority"), + TEXT("PriorityDistanceOffset"), +#if AK_WWISESDK_VERSION_MAJOR >= 2018 + TEXT("SpeakerPanning3DSpatializationMix"), +#endif + TEXT("UserAuxSendVolume0"), + TEXT("UserAuxSendVolume1"), + TEXT("UserAuxSendVolume2"), + TEXT("UserAuxSendVolume3"), + TEXT("Volume"), + TEXT("Weight") + }; + + enum { FullPropertiesListSize = sizeof(FullPropertiesList) / sizeof(*FullPropertiesList) }; +} +/*------------------------------------------------------------------------------------ +Helpers +------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------ +Implementation +------------------------------------------------------------------------------------*/ +SAkItemProperties::SAkItemProperties() +{} + +SAkItemProperties::~SAkItemProperties() +{} + +void SAkItemProperties::Construct(const FArguments& InArgs) +{ + OnDragDetected = InArgs._OnDragDetected; + OnSelectionChanged = InArgs._OnSelectionChanged; + + if (InArgs._FocusSearchBoxWhenOpened) + { + RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &SAkItemProperties::SetFocusPostConstruct)); + } + + SearchBoxFilter = MakeShareable( new StringFilter( StringFilter::FItemToStringArray::CreateSP( this, &SAkItemProperties::PopulateSearchStrings ) ) ); + SearchBoxFilter->OnChanged().AddSP( this, &SAkItemProperties::FilterUpdated ); + + ChildSlot + [ + SNew(SBorder) + .Padding(4) + .BorderImage(FAkAudioStyle::GetBrush("AudiokineticTools.GroupBorder")) + [ + SNew(SVerticalBox) + // Search + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 1, 0, 3) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + InArgs._SearchContent.Widget + ] + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SAssignNew(SearchBoxPtr,SSearchBox) + .HintText( LOCTEXT( "PropertiesSearchHint", "Search an item property" ) ) + .ToolTipText(LOCTEXT("PropertiesSearchTooltip", "Type here to search for a property")) + .OnTextChanged( this, &SAkItemProperties::OnSearchBoxChanged ) + .SelectAllTextWhenFocused(false) + .DelayChangeNotificationsWhileTyping(true) + ] + ] + // Tree + +SVerticalBox::Slot() + .FillHeight(1.f) + [ + SAssignNew(ListViewPtr, SListView< TSharedPtr >) + .ListItemsSource(&PropertiesList) + .OnGenerateRow( this, &SAkItemProperties::GenerateRow ) + .ItemHeight(18) + .SelectionMode(InArgs._SelectionMode) + .OnSelectionChanged(this, &SAkItemProperties::ListSelectionChanged) + .ClearSelectionOnClick(false) + ] + ] + ]; + + for (const auto& Property : SAkItemProperties_Helpers::FullPropertiesList) + { + PropertiesList.Add(MakeShareable(new FString(Property))); + } + + ListViewPtr->RequestListRefresh(); +} + +TSharedRef SAkItemProperties::GenerateRow( TSharedPtr ItemProperty, const TSharedRef& OwnerTable ) +{ + check(ItemProperty.IsValid()); + + TSharedPtr NewRow = SNew(STableRow< TSharedPtr >, OwnerTable) + .OnDragDetected(this, &SAkItemProperties::HandleOnDragDetected) + [ + SNew(STextBlock) + .Text(FText::FromString(*ItemProperty.Get())) + .HighlightText(SearchBoxFilter.Get(), &StringFilter::GetRawFilterText) + ]; + return NewRow.ToSharedRef(); +} + +FReply SAkItemProperties::HandleOnDragDetected(const FGeometry& Geometry, const FPointerEvent& MouseEvent) +{ + // Refresh the contents + if(OnDragDetected.IsBound()) + return OnDragDetected.Execute(Geometry,MouseEvent); + return FReply::Unhandled(); +} + +void SAkItemProperties::PopulateSearchStrings( const FString& PropertyName, OUT TArray< FString >& OutSearchStrings ) const +{ + OutSearchStrings.Add( PropertyName ); +} + +void SAkItemProperties::OnSearchBoxChanged( const FText& InSearchText ) +{ + SearchBoxFilter->SetRawFilterText( InSearchText ); +} + +void SAkItemProperties::FilterUpdated() +{ + FScopedSlowTask SlowTask(2.f, LOCTEXT("AK_PopulatingPicker", "Populating Properties Picker...")); + SlowTask.MakeDialog(); + + PropertiesList.Empty(SAkItemProperties_Helpers::FullPropertiesListSize); + + const FString& FilterString = SearchBoxFilter->GetRawFilterText().ToString(); + if (FilterString.IsEmpty()) + { + for (const auto& Property : SAkItemProperties_Helpers::FullPropertiesList) + { + PropertiesList.Add(MakeShareable(new FString(Property))); + } + } + else + { + for (const auto& Property : SAkItemProperties_Helpers::FullPropertiesList) + { + if (Property.Contains(FilterString)) + { + PropertiesList.Add(MakeShareable(new FString(Property))); + } + } + } + + ListViewPtr->RequestListRefresh(); +} + +void SAkItemProperties::ListSelectionChanged( TSharedPtr< FString > ItemProperty, ESelectInfo::Type /*SelectInfo*/ ) +{ + if (OnSelectionChanged.IsBound()) + OnSelectionChanged.Execute(ItemProperty, ESelectInfo::OnMouseClick); +} + +const TArray> SAkItemProperties::GetSelectedProperties() const +{ + return ListViewPtr->GetSelectedItems(); +} + +const FString SAkItemProperties::GetSearchText() const +{ + return SearchBoxFilter->GetRawFilterText().ToString(); +} + +const void SAkItemProperties::SetSearchText(const FString& newText) +{ + SearchBoxPtr->SetText(FText::FromString(newText)); +} + +EActiveTimerReturnType SAkItemProperties::SetFocusPostConstruct(double InCurrentTime, float InDeltaTime) +{ + FWidgetPath WidgetToFocusPath; + FSlateApplication::Get().GeneratePathToWidgetUnchecked(SearchBoxPtr.ToSharedRef(), WidgetToFocusPath); + FSlateApplication::Get().SetKeyboardFocus(WidgetToFocusPath, EFocusCause::SetDirectly); + + return EActiveTimerReturnType::Stop; +} +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUtils.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUtils.cpp new file mode 100644 index 0000000..d35d0aa --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AkWaapiUtils.cpp @@ -0,0 +1,167 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ + includes. +------------------------------------------------------------------------------------*/ +#include "AkWaapiUtils.h" +#include "AkAudioDevice.h" +#include "AkWaapiClient.h" + +/*------------------------------------------------------------------------------------ +Defines +------------------------------------------------------------------------------------*/ +#define LOCTEXT_NAMESPACE "AkAudio" + +/*------------------------------------------------------------------------------------ +Statics and Globals +------------------------------------------------------------------------------------*/ +const FString WwiseWaapiHelper::ACTION = TEXT("action"); +const FString WwiseWaapiHelper::ANCESTORS = TEXT("ancestors"); +const FString WwiseWaapiHelper::AT = TEXT("@"); +const FString WwiseWaapiHelper::AUX_BUSSES = TEXT("auxBusses"); +const FString WwiseWaapiHelper::BACK_SLASH = TEXT("\\"); +const FString WwiseWaapiHelper::BANK_DATA = TEXT("bankData"); +const FString WwiseWaapiHelper::BANK_INFO = TEXT("bankInfo"); +const FString WwiseWaapiHelper::CHILD = TEXT("child"); +const FString WwiseWaapiHelper::CHILDREN = TEXT("children"); +const FString WwiseWaapiHelper::CHILDREN_COUNT = TEXT("childrenCount"); +const FString WwiseWaapiHelper::CLASSID = TEXT("classId"); +const FString WwiseWaapiHelper::COMMAND = TEXT("command"); +const FString WwiseWaapiHelper::DATA = TEXT("data"); +const FString WwiseWaapiHelper::DELETE_ITEMS = TEXT("Delete Items"); +const FString WwiseWaapiHelper::DESCENDANTS = TEXT("descendants"); +const FString WwiseWaapiHelper::DISPLAY_NAME = TEXT("displayName"); +const FString WwiseWaapiHelper::DRAG_DROP_ITEMS = TEXT("Drag Drop Items"); +const FString WwiseWaapiHelper::EVENT = TEXT("event"); +const FString WwiseWaapiHelper::EVENTS = TEXT("events"); +const FString WwiseWaapiHelper::FILEPATH = TEXT("filePath"); +const FString WwiseWaapiHelper::FILTER = TEXT("filter"); +const FString WwiseWaapiHelper::FIND_IN_PROJECT_EXPLORER = TEXT("FindInProjectExplorerSelectionChannel1"); +const FString WwiseWaapiHelper::FOLDER = TEXT("Folder"); +const FString WwiseWaapiHelper::FROM = TEXT("from"); +const FString WwiseWaapiHelper::ID = TEXT("id"); +const FString WwiseWaapiHelper::INCLUSIONS = TEXT("inclusions"); +const FString WwiseWaapiHelper::INFO_FILE = TEXT("infoFile"); +const FString WwiseWaapiHelper::IS_CONNECTED = TEXT("isConnected"); +const FString WwiseWaapiHelper::LANGUAGE = TEXT("language"); +const FString WwiseWaapiHelper::LANGUAGES = TEXT("languages"); +const FString WwiseWaapiHelper::MAX = TEXT("max"); +const FString WwiseWaapiHelper::MAX_RADIUS_ATTENUATION = TEXT("audioSource:maxRadiusAttenuation"); +const FString WwiseWaapiHelper::MESSSAGE = TEXT("message"); +const FString WwiseWaapiHelper::MIN = TEXT("min"); +const FString WwiseWaapiHelper::NAME = TEXT("name"); +const FString WwiseWaapiHelper::NAMECONTAINS = TEXT("name:contains"); +const FString WwiseWaapiHelper::NEW = TEXT("new"); +const FString WwiseWaapiHelper::NEW_NAME = TEXT("newName"); +const FString WwiseWaapiHelper::NOTES = TEXT("notes"); +const FString WwiseWaapiHelper::OBJECT = TEXT("object"); +const FString WwiseWaapiHelper::OBJECTS = TEXT("objects"); +const FString WwiseWaapiHelper::OF_TYPE = TEXT("ofType"); +const FString WwiseWaapiHelper::OLD_NAME = TEXT("oldName"); +const FString WwiseWaapiHelper::ON_NAME_CONFLICT = TEXT("onNameConflict"); +const FString WwiseWaapiHelper::OPERATION = TEXT("operation"); +const FString WwiseWaapiHelper::PARENT = TEXT("parent"); +const FString WwiseWaapiHelper::PATH = TEXT("path"); +const FString WwiseWaapiHelper::PHYSICAL_FOLDER = TEXT("PhysicalFolder"); +const FString WwiseWaapiHelper::PLATFORM = TEXT("platform"); +const FString WwiseWaapiHelper::PLATFORMS = TEXT("platforms"); +const FString WwiseWaapiHelper::PLAY = TEXT("play"); +const FString WwiseWaapiHelper::PLAYING = TEXT("playing"); +const FString WwiseWaapiHelper::PLAYSTOP = TEXT("playStop"); +const FString WwiseWaapiHelper::PLUGININFO_OPTIONS = TEXT("pluginInfo"); +const FString WwiseWaapiHelper::PLUGININFO_RESPONSE = TEXT("PluginInfo"); +const FString WwiseWaapiHelper::PROJECT = TEXT("Project"); +const FString WwiseWaapiHelper::PROPERTY = TEXT("property"); +const FString WwiseWaapiHelper::RADIUS = TEXT("radius"); +const FString WwiseWaapiHelper::RANGE = TEXT("range"); +const FString WwiseWaapiHelper::REBUILD = TEXT("rebuild"); +const FString WwiseWaapiHelper::REBUILD_INIT_BANK = TEXT("rebuildInitBank"); +const FString WwiseWaapiHelper::REDO = TEXT("Redo"); +const FString WwiseWaapiHelper::RENAME = TEXT("rename"); +const FString WwiseWaapiHelper::RESTRICTION = TEXT("restriction"); +const FString WwiseWaapiHelper::RETURN = TEXT("return"); +const FString WwiseWaapiHelper::SEARCH = TEXT("search"); +const FString WwiseWaapiHelper::SELECT = TEXT("select"); +const FString WwiseWaapiHelper::SIZE = TEXT("size"); +const FString WwiseWaapiHelper::SKIP_LANGUAGES = TEXT("skipLanguages"); +const FString WwiseWaapiHelper::SOUNDBANK_TYPE = TEXT("SoundBank"); +const FString WwiseWaapiHelper::SOUNDBANK_FIELD = TEXT("soundbank"); +const FString WwiseWaapiHelper::SOUNDBANKS = TEXT("soundbanks"); +const FString WwiseWaapiHelper::STATE = TEXT("state"); +const FString WwiseWaapiHelper::STOP = TEXT("stop"); +const FString WwiseWaapiHelper::STOPPED = TEXT("stopped"); +const FString WwiseWaapiHelper::STRUCTURE = TEXT("structure"); +const FString WwiseWaapiHelper::TRANSFORM = TEXT("transform"); +const FString WwiseWaapiHelper::TRANSPORT = TEXT("transport"); +const FString WwiseWaapiHelper::TYPE = TEXT("type"); +const FString WwiseWaapiHelper::UI = TEXT("ui"); +const FString WwiseWaapiHelper::UNDO = TEXT("Undo"); +const FString WwiseWaapiHelper::VALUE = TEXT("value"); +const FString WwiseWaapiHelper::VOLUME = TEXT("Volume"); +const FString WwiseWaapiHelper::WHERE = TEXT("where"); +const FString WwiseWaapiHelper::WORKUNIT_TYPE = TEXT("workunit:type"); +const FString WwiseWaapiHelper::WRITE_TO_DISK = TEXT("writeToDisk"); + + +/*------------------------------------------------------------------------------------ + Methods +------------------------------------------------------------------------------------*/ + +bool CallWappiGetPropertySate(const FString& ItemID, const FString& ItemProperty, TSharedPtr& ItemInfoResult) +{ + auto* waapiClient = FAkWaapiClient::Get(); + if (!waapiClient) + return false; + + TSharedRef args = MakeShareable(new FJsonObject()); + { + TSharedPtr from = MakeShareable(new FJsonObject()); + from->SetArrayField(WwiseWaapiHelper::ID, TArray> { MakeShareable(new FJsonValueString(ItemID)) }); + args->SetObjectField(WwiseWaapiHelper::FROM, from); + } + + TSharedRef options = MakeShareable(new FJsonObject()); + options->SetArrayField(WwiseWaapiHelper::RETURN, TArray> { MakeShareable(new FJsonValueString(WwiseWaapiHelper::AT + ItemProperty)) }); + +#if AK_SUPPORT_WAAPI + TSharedPtr outJsonResult; + if (!waapiClient->Call(ak::wwise::core::object::get, args, options, outJsonResult)) + return false; + + ItemInfoResult = outJsonResult->GetArrayField(WwiseWaapiHelper::RETURN)[0]->AsObject(); +#endif + return true; +} + +bool SubscribeToPropertyStateChange(const FString& ItemID, const FString& ItemProperty, WampEventCallback CallBack, uint64& SubscriptionId, TSharedPtr& outJsonResult) +{ + auto* waapiClient = FAkWaapiClient::Get(); + if (!waapiClient) + return false; + +#if AK_SUPPORT_WAAPI + TSharedRef options = MakeShareable(new FJsonObject()); + options->SetStringField(WwiseWaapiHelper::OBJECT, ItemID); + options->SetStringField(WwiseWaapiHelper::PROPERTY, ItemProperty); + + return waapiClient->Subscribe(ak::wwise::core::object::propertyChanged, options, CallBack, SubscriptionId, outJsonResult); +#endif + return false; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AssetManagement/WwiseProjectInfo.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AssetManagement/WwiseProjectInfo.cpp new file mode 100644 index 0000000..73583aa --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/AssetManagement/WwiseProjectInfo.cpp @@ -0,0 +1,230 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AssetManagement/WwiseProjectInfo.h" + +#include "AkAudioDevice.h" +#include "AkSettings.h" +#include "Misc/Paths.h" +#include "XmlFile.h" +#include "Internationalization/Regex.h" +#include "Misc/FileHelper.h" + +void WwiseProjectInfo::Parse() +{ + SupportedPlatforms.Empty(); + SupportedLanguages.Empty(); + DefaultLanguage.Empty(); + CacheDirectory.Empty(); + + bProjectInfoParsed = false; + FString ProjectPath = GetProjectPath(); + if (ProjectPath.Len() > 0) + { + FText errorMessage; + int32 errorLineNumber; + FString ProjectFileString; + + if (!FFileHelper::LoadFileToString(ProjectFileString, *ProjectPath)) + { +// UE_LOG(LogAkAudio, Error, TEXT("Could not read the Wwise project file '<%s>'."), *ProjectPath); + return; + } + + ParseCacheDirectory(*ProjectFileString); + SanitizeProjectFileString(ProjectFileString); + bProjectInfoParsed = FFastXml::ParseXmlFile(this, nullptr, ProjectFileString.GetCharArray().GetData(), nullptr, false, false, errorMessage, errorLineNumber); + + if (DefaultLanguage.Len() == 0 || DefaultLanguage == TEXT("")) + { + DefaultLanguage = TEXT("English(US)"); + } + } +} + +void WwiseProjectInfo::ParseCacheDirectory(const FString ProjectFileString) +{ + FString PatternString(TEXT("([\\s\\S]*?)")); + FRegexPattern Pattern(PatternString); + FRegexMatcher Matcher(Pattern, ProjectFileString); + if (Matcher.FindNext()) + { + CacheDirectory = Matcher.GetCaptureGroup(1); + CacheDirectory.RemoveFromStart(TEXT("")); + } + else if (CacheDirectory.IsEmpty()) + { + CacheDirectory = TEXT(".cache"); + UE_LOG(LogAkAudio, Log, TEXT("Could not parse the Cache directory from the Wwise project file. Using default value : .cache")); + } + + if (FPaths::IsRelative(CacheDirectory)) + { + CacheDirectory = FPaths::ConvertRelativePathToFull(FPaths::GetPath(GetProjectPath()), CacheDirectory); + } +} + +void WwiseProjectInfo::SanitizeProjectFileString(FString& InOutProjectFileString) +{ + FString PatternString(TEXT("<\\!\\[CDATA[\\s\\S]*?>")); + FRegexPattern Pattern(PatternString); + FRegexMatcher Matcher(Pattern, InOutProjectFileString); + while (Matcher.FindNext()) + { + InOutProjectFileString = InOutProjectFileString.Replace(*Matcher.GetCaptureGroup(0), TEXT("")); + } +} + +bool WwiseProjectInfo::ProcessAttribute(const TCHAR* AttributeName, const TCHAR* AttributeValue) +{ + if (bInsidePlatformElement) + { + if (FCString::Strcmp(AttributeName, TEXT("Name")) == 0) + { + CurrentPlatformInfo.Name = AttributeValue; + } + else if (FCString::Strcmp(AttributeName, TEXT("ID")) == 0) + { + FGuid::ParseExact(AttributeValue, EGuidFormats::DigitsWithHyphensInBraces, CurrentPlatformInfo.ID); + } + } + + if (bInsideLanguageElement) + { + if (FCString::Strcmp(AttributeName, TEXT("Name")) == 0) + { + if (FCString::Strcmp(AttributeValue, TEXT("External")) == 0 + || FCString::Strcmp(AttributeValue, TEXT("Mixed")) == 0 + || FCString::Strcmp(AttributeValue, TEXT("SFX")) == 0) + { + bInsideLanguageElement = false; + } + else + { + if (CurrentLanguageInfo.Name.IsEmpty()) + { + CurrentLanguageInfo.Name = AttributeValue; + + AK::FNVHash32 hash; + FTCHARToUTF8 utf8(*CurrentLanguageInfo.Name.ToLower()); + CurrentLanguageInfo.ShortID = hash.Compute(utf8.Get(), utf8.Length()); + } + } + } + else if (FCString::Strcmp(AttributeName, TEXT("ID")) == 0) + { + FGuid::ParseExact(AttributeValue, EGuidFormats::DigitsWithHyphensInBraces, CurrentLanguageInfo.ID); + } + } + + if (bInsidePropertyElement + && FCString::Strcmp(AttributeName, TEXT("Name")) == 0 + && FCString::Strcmp(AttributeValue, TEXT("DefaultLanguage")) == 0 + ) + { + bInsideDefaultLanguage = true; + } + + if (bInsideDefaultLanguage && FCString::Strcmp(AttributeName, TEXT("Value")) == 0) + { + DefaultLanguage = AttributeValue; + bInsideDefaultLanguage = false; + } + + return true; +} + +bool WwiseProjectInfo::ProcessClose(const TCHAR* Element) +{ + if (bInsidePlatformElement && FCString::Strcmp(Element, TEXT("Platform")) == 0) + { + SupportedPlatforms.Add(CurrentPlatformInfo); + bInsidePlatformElement = false; + } + if (bInsideLanguageElement && FCString::Strcmp(Element, TEXT("Language")) == 0) + { + SupportedLanguages.Add(CurrentLanguageInfo); + bInsideLanguageElement = false; + } + if (bInsidePropertyElement && FCString::Strcmp(Element, TEXT("Property")) == 0) + { + bInsidePropertyElement = false; + } + return true; +} + +bool WwiseProjectInfo::ProcessComment(const TCHAR* Comment) +{ + return true; +} + +bool WwiseProjectInfo::ProcessElement(const TCHAR* ElementName, const TCHAR* ElementData, int32 XmlFileLineNumber) +{ + if (FCString::Strcmp(ElementName, TEXT("Platform")) == 0) + { + bInsidePlatformElement = true; + + // Clear CurrentPlatformInfo + new (&CurrentPlatformInfo) FWwisePlatformInfo(); + } + else if (FCString::Strcmp(ElementName, TEXT("Language")) == 0) + { + bInsideLanguageElement = true; + + // Clear CurrentLanguageInfo + new (&CurrentLanguageInfo) FWwiseLanguageInfo(); + } + else if (FCString::Strcmp(ElementName, TEXT("Property")) == 0) + { + bInsidePropertyElement = true; + } + + if (bInsideDefaultLanguage && FCString::Strcmp(ElementName, TEXT("Value")) == 0) + { + // Only use this as a backup + if (DefaultLanguage.IsEmpty()) + { + DefaultLanguage = ElementData; + } + bInsideDefaultLanguage = false; + } + + return true; +} + +bool WwiseProjectInfo::ProcessXmlDeclaration(const TCHAR* ElementData, int32 XmlFileLineNumber) +{ + return true; +} + +FString WwiseProjectInfo::GetProjectPath() const +{ + FString ProjectPath; + + if (const UAkSettings* Settings = GetDefault()) + { + ProjectPath = Settings->WwiseProjectPath.FilePath; + + if (FPaths::IsRelative(ProjectPath)) + { + ProjectPath = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir(), ProjectPath); + } + } + + return ProjectPath; +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/BlueprintNodes/PostEventAsync.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/BlueprintNodes/PostEventAsync.cpp new file mode 100644 index 0000000..ca5fcb2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/BlueprintNodes/PostEventAsync.cpp @@ -0,0 +1,82 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "BlueprintNodes/PostEventAsync.h" + +#include "AkGameplayTypes.h" +#include "AkAudioEvent.h" +#include "Engine/Public/TimerManager.h" + +UPostEventAsync* UPostEventAsync::PostEventAsync( + const UObject* WorldContextObject, + UAkAudioEvent* AkEvent, + AActor* Actor, + int32 CallbackMask, + const FOnAkPostEventCallback& PostEventCallback, + bool bStopWhenAttachedToDestroyed +) +{ + UPostEventAsync* newNode = NewObject(); + newNode->WorldContextObject = WorldContextObject; + newNode->AkEvent = AkEvent; + newNode->Actor = Actor; + newNode->CallbackMask = CallbackMask; + newNode->PostEventCallback = PostEventCallback; + newNode->bStopWhenAttachedToDestroyed = bStopWhenAttachedToDestroyed; + return newNode; +} + +void UPostEventAsync::Activate() +{ + if (AkEvent == nullptr) + { + UE_LOG(LogAkAudio, Warning, TEXT("PostEventAsync: No Event specified!")); + Completed.Broadcast(AK_INVALID_PLAYING_ID); + return; + } + + if (Actor == nullptr) + { + UE_LOG(LogAkAudio, Warning, TEXT("PostEventAsync: NULL Actor specified!")); + Completed.Broadcast(AK_INVALID_PLAYING_ID); + return; + } + + AkDeviceAndWorld DeviceAndWorld(Actor); + if (DeviceAndWorld.IsValid()) + { + AkCallbackType AkCallbackMask = AkCallbackTypeHelpers::GetCallbackMaskFromBlueprintMask(CallbackMask); + PlayingIDFuture = DeviceAndWorld.AkAudioDevice->PostAkAudioEventOnActorAsync(AkEvent, Actor, PostEventCallback, AkCallbackMask); + + WorldContextObject->GetWorld()->GetTimerManager().SetTimer(Timer, this, &UPostEventAsync::PollPostEventFuture, 1.f / 60.f, true); + } + else + { + Completed.Broadcast(AK_INVALID_PLAYING_ID); + } +} + +void UPostEventAsync::PollPostEventFuture() +{ + if (PlayingIDFuture.IsReady()) + { + AkPlayingID PlayingID = PlayingIDFuture.Get(); + WorldContextObject->GetWorld()->GetTimerManager().ClearTimer(Timer); + Timer.Invalidate(); + Completed.Broadcast(PlayingID); + } +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/BlueprintNodes/PostEventAtLocationAsync.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/BlueprintNodes/PostEventAtLocationAsync.cpp new file mode 100644 index 0000000..ef1e267 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/BlueprintNodes/PostEventAtLocationAsync.cpp @@ -0,0 +1,63 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "BlueprintNodes/PostEventAtLocationAsync.h" + +#include "AkGameplayTypes.h" +#include "Engine/Public/TimerManager.h" + +UPostEventAtLocationAsync* UPostEventAtLocationAsync::PostEventAtLocationAsync(const UObject* WorldContextObject, UAkAudioEvent* AkEvent, FVector Location, FRotator Orientation) +{ + UPostEventAtLocationAsync* newNode = NewObject(); + newNode->WorldContextObject = WorldContextObject; + newNode->AkEvent = AkEvent; + newNode->Location = Location; + newNode->Orientation = Orientation; + return newNode; +} + +void UPostEventAtLocationAsync::Activate() +{ + if (AkEvent == nullptr) + { + UE_LOG(LogAkAudio, Warning, TEXT("PostEventAtLocationAsync: No Event specified!")); + Completed.Broadcast(AK_INVALID_PLAYING_ID); + return; + } + + AkDeviceAndWorld DeviceAndWorld(WorldContextObject); + if (DeviceAndWorld.IsValid()) + { + playingIDFuture = DeviceAndWorld.AkAudioDevice->PostAkAudioEventAtLocationAsync(AkEvent, Location, Orientation, DeviceAndWorld.CurrentWorld); + + WorldContextObject->GetWorld()->GetTimerManager().SetTimer(Timer, this, &UPostEventAtLocationAsync::PollPostEventFuture, 1.f / 60.f, true); + } + else + { + Completed.Broadcast(AK_INVALID_PLAYING_ID); + } +} + +void UPostEventAtLocationAsync::PollPostEventFuture() +{ + if (playingIDFuture.IsReady()) + { + WorldContextObject->GetWorld()->GetTimerManager().ClearTimer(Timer); + Timer.Invalidate(); + Completed.Broadcast(playingIDFuture.Get()); + } +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/InitializationSettings/AkAudioSession.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/InitializationSettings/AkAudioSession.cpp new file mode 100644 index 0000000..bbe505f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/InitializationSettings/AkAudioSession.cpp @@ -0,0 +1,29 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "InitializationSettings/AkAudioSession.h" +#include "InitializationSettings/AkInitializationSettings.h" + +void FAkAudioSession::FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const +{ +#if PLATFORM_IOS + InitializationStructure.PlatformInitSettings.audioSession.eCategory = (AkAudioSessionCategory)AudioSessionCategory; + InitializationStructure.PlatformInitSettings.audioSession.eCategoryOptions = (AkAudioSessionCategoryOptions)AudioSessionCategoryOptions; + InitializationStructure.PlatformInitSettings.audioSession.eMode = (AkAudioSessionMode)AudioSessionMode; +#endif +} + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/InitializationSettings/AkInitializationSettings.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/InitializationSettings/AkInitializationSettings.cpp new file mode 100644 index 0000000..48f5606 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/InitializationSettings/AkInitializationSettings.cpp @@ -0,0 +1,552 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "InitializationSettings/AkInitializationSettings.h" + +#include "Platforms/AkUEPlatform.h" +#include "HAL/PlatformMemory.h" +#include "Misc/App.h" +#include "ProfilingDebugging/CpuProfilerTrace.h" +#include "ProfilingDebugging/MiscTrace.h" + +#include "AkAudioDevice.h" +#include "Wwise/WwiseIOHook.h" +#include "Wwise/API/WwiseCommAPI.h" +#include "Wwise/API/WwiseMemoryMgrAPI.h" +#include "Wwise/API/WwiseMonitorAPI.h" +#include "Wwise/API/WwiseMusicEngineAPI.h" +#include "Wwise/API/WwiseSoundEngineAPI.h" +#include "Wwise/API/WwiseSpatialAudioAPI.h" +#include "Wwise/API/WwiseStreamMgrAPI.h" +#include "Wwise/WwiseGlobalCallbacks.h" + +namespace AkInitializationSettings_Helpers +{ + enum { IsLoggingInitialization = true }; + + void AssertHook(const char* expression, const char* fileName, int lineNumber) + { + check(expression); + check(fileName); + const FString Expression(expression); + const FString FileName(fileName); + UE_LOG(LogAkAudio, Error, TEXT("AKASSERT: %s. File: %s, line: %d"), *Expression, *FileName, lineNumber); + } + + FAkInitializationStructure::MemoryAllocFunction AllocFunction = nullptr; + FAkInitializationStructure::MemoryFreeFunction FreeFunction = nullptr; + + void* AkMemAllocVM(size_t size, size_t* /*extra*/) + { + return AllocFunction(size); + } + + void AkMemFreeVM(void* address, size_t /*size*/, size_t /*extra*/, size_t release) + { + if (release) + { + FreeFunction(address, release); + } + } + + void AkProfilerPushTimer(AkPluginID in_uPluginID, const char* in_pszZoneName) + { + if (!in_pszZoneName) + { + in_pszZoneName = "(Unknown)"; + } + +#if CPUPROFILERTRACE_ENABLED + FCpuProfilerTrace::OutputBeginDynamicEvent(in_pszZoneName); +#endif +#if PLATFORM_IMPLEMENTS_BeginNamedEventStatic + FPlatformMisc::BeginNamedEventStatic(WwiseNamedEvents::Color1, in_pszZoneName); +#else + FPlatformMisc::BeginNamedEvent(WwiseNamedEvents::Color1, in_pszZoneName); +#endif + } + + void AkProfilerPopTimer() + { + FPlatformMisc::EndNamedEvent(); +#if CPUPROFILERTRACE_ENABLED + FCpuProfilerTrace::OutputEndEvent(); +#endif + } + + void AkProfilerPostMarker(AkPluginID in_uPluginID, const char* in_pszMarkerName) + { + // Filter out audioFrameBoundary bookmarks, because those occur too frequently + if (in_uPluginID != AKMAKECLASSID(AkPluginTypeNone, AKCOMPANYID_AUDIOKINETIC, AK::ProfilingID::AudioFrameBoundary)) + { + TRACE_BOOKMARK(TEXT("AK Marker: %s"), in_pszMarkerName); + } + } +} + + +////////////////////////////////////////////////////////////////////////// +// FAkInitializationStructure + +FAkInitializationStructure::FAkInitializationStructure() +{ + auto* Comm = IWwiseCommAPI::Get(); + auto* MemoryMgr = IWwiseMemoryMgrAPI::Get(); + auto* MusicEngine = IWwiseMusicEngineAPI::Get(); + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + auto* StreamMgr = IWwiseStreamMgrAPI::Get(); + if (UNLIKELY(!Comm || !MemoryMgr || !MusicEngine || !SoundEngine || !StreamMgr)) return; + + MemoryMgr->GetDefaultSettings(MemSettings); + + StreamMgr->GetDefaultSettings(StreamManagerSettings); + + StreamMgr->GetDefaultDeviceSettings(DeviceSettings); + DeviceSettings.uSchedulerTypeFlags = AK_SCHEDULER_DEFERRED_LINED_UP; + DeviceSettings.uMaxConcurrentIO = AK_UNREAL_MAX_CONCURRENT_IO; + + SoundEngine->GetDefaultInitSettings(InitSettings); + InitSettings.pfnAssertHook = AkInitializationSettings_Helpers::AssertHook; + InitSettings.eFloorPlane = AkFloorPlane_XY; + InitSettings.fGameUnitsToMeters = 100.f; + InitSettings.fnProfilerPushTimer = AkInitializationSettings_Helpers::AkProfilerPushTimer; + InitSettings.fnProfilerPopTimer = AkInitializationSettings_Helpers::AkProfilerPopTimer; + InitSettings.fnProfilerPostMarker = AkInitializationSettings_Helpers::AkProfilerPostMarker; + + SoundEngine->GetDefaultPlatformInitSettings(PlatformInitSettings); + + MusicEngine->GetDefaultInitSettings(MusicSettings); + +#if AK_ENABLE_COMMUNICATION + Comm->GetDefaultInitSettings(CommSettings); +#endif +} + +FAkInitializationStructure::~FAkInitializationStructure() +{ + delete[] InitSettings.szPluginDLLPath; +} + +void FAkInitializationStructure::SetPluginDllPath(const FString& PlatformArchitecture) +{ + if (PlatformArchitecture.IsEmpty()) + { + InitSettings.szPluginDLLPath = nullptr; + return; + } + + auto Path = FAkPlatform::GetDSPPluginsDirectory(PlatformArchitecture); + auto Length = Path.Len() + 1; + AkOSChar* PluginDllPath = new AkOSChar[Length]; + AKPLATFORM::SafeStrCpy(PluginDllPath, TCHAR_TO_AK(*Path), Length); + InitSettings.szPluginDLLPath = PluginDllPath; +} + +void FAkInitializationStructure::SetupLLMAllocFunctions(MemoryAllocFunction alloc, MemoryFreeFunction free, bool UseMemTracker) +{ + ensure(alloc == nullptr && free == nullptr || alloc != nullptr && free != nullptr); + + AkInitializationSettings_Helpers::AllocFunction = alloc; + AkInitializationSettings_Helpers::FreeFunction = free; + +#if ENABLE_LOW_LEVEL_MEM_TRACKER + if (UseMemTracker) + { + int32 OutAlignment = 0; + FPlatformMemory::GetLLMAllocFunctions(AkInitializationSettings_Helpers::AllocFunction, AkInitializationSettings_Helpers::FreeFunction, OutAlignment); + } +#endif + + if (!AkInitializationSettings_Helpers::AllocFunction || !AkInitializationSettings_Helpers::FreeFunction) + return; + + MemSettings.pfAllocVM = AkInitializationSettings_Helpers::AkMemAllocVM; + MemSettings.pfFreeVM = AkInitializationSettings_Helpers::AkMemFreeVM; +} + + +////////////////////////////////////////////////////////////////////////// +// FAkMainOutputSettings + +void FAkMainOutputSettings::FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const +{ + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) return; + + auto& OutputSettings = InitializationStructure.InitSettings.settingsMainOutput; + + auto ShareSetID = !AudioDeviceShareSet.IsEmpty() ? SoundEngine->GetIDFromString(TCHAR_TO_ANSI(*AudioDeviceShareSet)) : AK_INVALID_UNIQUE_ID; + OutputSettings.audioDeviceShareset = ShareSetID; + + switch (ChannelConfigType) + { + case EAkChannelConfigType::Anonymous: + OutputSettings.channelConfig.SetAnonymous(NumberOfChannels); + break; + + case EAkChannelConfigType::Standard: + OutputSettings.channelConfig.SetStandard(ChannelMask); + break; + + case EAkChannelConfigType::Ambisonic: + OutputSettings.channelConfig.SetAmbisonic(NumberOfChannels); + break; + } + + OutputSettings.ePanningRule = (AkPanningRule)PanningRule; + OutputSettings.idDevice = DeviceID; +} + + +////////////////////////////////////////////////////////////////////////// +// FAkSpatialAudioSettings + +void FAkSpatialAudioSettings::FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const +{ + auto& SpatialAudioInitSettings = InitializationStructure.SpatialAudioInitSettings; + SpatialAudioInitSettings.uMaxSoundPropagationDepth = MaxSoundPropagationDepth; + SpatialAudioInitSettings.fMovementThreshold = MovementThreshold; + SpatialAudioInitSettings.uNumberOfPrimaryRays = NumberOfPrimaryRays; + SpatialAudioInitSettings.uMaxReflectionOrder = ReflectionOrder; + SpatialAudioInitSettings.uMaxDiffractionOrder = DiffractionOrder; + SpatialAudioInitSettings.uDiffractionOnReflectionsOrder = DiffractionOnReflectionsOrder; + SpatialAudioInitSettings.fMaxPathLength = MaximumPathLength; + SpatialAudioInitSettings.fCPULimitPercentage = CPULimitPercentage; + SpatialAudioInitSettings.uLoadBalancingSpread = LoadBalancingSpread; + SpatialAudioInitSettings.bEnableGeometricDiffractionAndTransmission = EnableGeometricDiffractionAndTransmission; + SpatialAudioInitSettings.bCalcEmitterVirtualPosition = CalcEmitterVirtualPosition; +} + + +////////////////////////////////////////////////////////////////////////// +// FAkCommunicationSettings + +void FAkCommunicationSettings::FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const +{ +#ifndef AK_OPTIMIZED + auto& CommSettings = InitializationStructure.CommSettings; + CommSettings.ports.uDiscoveryBroadcast = DiscoveryBroadcastPort; + CommSettings.ports.uCommand = CommandPort; + + const FString GameName = GetCommsNetworkName(); + FCStringAnsi::Strcpy(CommSettings.szAppNetworkName, AK_COMM_SETTINGS_MAX_STRING_SIZE, TCHAR_TO_ANSI(*GameName)); +#endif // AK_OPTIMIZED +} + +FString FAkCommunicationSettings::GetCommsNetworkName() const +{ + FString CommsNetworkName = NetworkName; + + if (CommsNetworkName.IsEmpty() && FApp::HasProjectName()) + { + CommsNetworkName = FApp::GetProjectName(); + } + +#if WITH_EDITORONLY_DATA + if (!CommsNetworkName.IsEmpty() && !IsRunningGame()) + { + CommsNetworkName += TEXT(" (Editor)"); + } +#endif + + return CommsNetworkName; +} + + +////////////////////////////////////////////////////////////////////////// +// FAkCommunicationSettingsWithSystemInitialization + +void FAkCommunicationSettingsWithSystemInitialization::FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const +{ + Super::FillInitializationStructure(InitializationStructure); + +#if AK_ENABLE_COMMUNICATION + InitializationStructure.CommSettings.bInitSystemLib = InitializeSystemComms; +#endif +} + +void FAkCommunicationSettingsWithCommSelection::FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const +{ + Super::FillInitializationStructure(InitializationStructure); + +#if AK_ENABLE_COMMUNICATION + InitializationStructure.CommSettings.commSystem = (AkCommSettings::AkCommSystem)CommunicationSystem; +#endif +} + +////////////////////////////////////////////////////////////////////////// +// FAkCommonInitializationSettings + +void FAkCommonInitializationSettings::FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const +{ + auto& InitSettings = InitializationStructure.InitSettings; + InitSettings.uMaxNumPaths = MaximumNumberOfPositioningPaths; + InitSettings.uCommandQueueSize = CommandQueueSize; + InitSettings.uNumSamplesPerFrame = SamplesPerFrame; + + MainOutputSettings.FillInitializationStructure(InitializationStructure); + + auto& PlatformInitSettings = InitializationStructure.PlatformInitSettings; + PlatformInitSettings.uNumRefillsInVoice = NumberOfRefillsInVoice; + + SpatialAudioSettings.FillInitializationStructure(InitializationStructure); + + InitializationStructure.MusicSettings.fStreamingLookAheadRatio = StreamingLookAheadRatio; +} + + +////////////////////////////////////////////////////////////////////////// +// FAkAdvancedInitializationSettings + +void FAkAdvancedInitializationSettings::FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const +{ + auto& DeviceSettings = InitializationStructure.DeviceSettings; + DeviceSettings.uIOMemorySize = IO_MemorySize; + DeviceSettings.uGranularity = IO_Granularity == 0 ? (32 * 1024) : IO_Granularity; + DeviceSettings.fTargetAutoStmBufferLength = TargetAutoStreamBufferLength; + DeviceSettings.bUseStreamCache = UseStreamCache; + DeviceSettings.uMaxCachePinnedBytes = MaximumPinnedBytesInCache; + + auto& InitSettings = InitializationStructure.InitSettings; + InitSettings.bEnableGameSyncPreparation = EnableGameSyncPreparation; + InitSettings.uContinuousPlaybackLookAhead = ContinuousPlaybackLookAhead; + InitSettings.uMonitorQueuePoolSize = MonitorQueuePoolSize; + InitSettings.uMaxHardwareTimeoutMs = MaximumHardwareTimeoutMs; + InitSettings.bDebugOutOfRangeCheckEnabled = DebugOutOfRangeCheckEnabled; + InitSettings.fDebugOutOfRangeLimit = DebugOutOfRangeLimit; +} + + +////////////////////////////////////////////////////////////////////////// +// FAkAdvancedInitializationSettingsWithMultiCoreRendering + +void FAkAdvancedInitializationSettingsWithMultiCoreRendering::FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const +{ + Super::FillInitializationStructure(InitializationStructure); + + if (EnableMultiCoreRendering) + { + FAkAudioDevice* pDevice = FAkAudioDevice::Get(); + check(pDevice != nullptr); + + FAkJobWorkerScheduler* pScheduler = pDevice->GetAkJobWorkerScheduler(); + check(pScheduler != nullptr); + + auto& InitSettings = InitializationStructure.InitSettings; + pScheduler->InstallJobWorkerScheduler(JobWorkerMaxExecutionTimeUSec, MaxNumJobWorkers, InitSettings.settingsJobManager); + } +} + +static void UELocalOutputFunc( + AK::Monitor::ErrorCode in_eErrorCode, + const AkOSChar* in_pszError, + AK::Monitor::ErrorLevel in_eErrorLevel, + AkPlayingID in_playingID, + AkGameObjectID in_gameObjID) +{ + if (!IsRunningCommandlet()) + { + FString AkError(in_pszError); + + if (in_eErrorLevel == AK::Monitor::ErrorLevel_Message) + { + UE_LOG(LogWwiseMonitor, Log, TEXT("%s"), *AkError); + } + +#if !UE_BUILD_SHIPPING + else if (FPlatformMisc::IsDebuggerPresent() && AkError == TEXT("Voice Starvation")) + { + UE_LOG(LogWwiseMonitor, Log, TEXT("%s [Debugger])"), *AkError); + } +#endif + + else + { +#if UE_EDITOR + UE_LOG(LogWwiseMonitor, Warning, TEXT("%s"), *AkError); +#else + UE_LOG(LogWwiseMonitor, Error, TEXT("%s"), *AkError); +#endif + } + } +} + +namespace FAkSoundEngineInitialization +{ + bool Initialize(FWwiseIOHook* IOHook) + { + if (!IOHook) + { + UE_LOG(LogAkAudio, Error, TEXT("IOHook is null.")); + return false; + } + + const UAkInitializationSettings* InitializationSettings = FAkPlatform::GetInitializationSettings(); + if (InitializationSettings == nullptr) + { + UE_LOG(LogAkAudio, Error, TEXT("InitializationSettings could not be found.")); + return false; + } + + FAkInitializationStructure InitializationStructure; + InitializationSettings->FillInitializationStructure(InitializationStructure); + + UE_CLOG(AkInitializationSettings_Helpers::IsLoggingInitialization, LogAkAudio, Verbose, TEXT("Initializing Platform")); + FAkPlatform::PreInitialize(InitializationStructure); + + auto* Comm = IWwiseCommAPI::Get(); + auto* MemoryMgr = IWwiseMemoryMgrAPI::Get(); + auto* Monitor = IWwiseMonitorAPI::Get(); + auto* MusicEngine = IWwiseMusicEngineAPI::Get(); + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); + auto* StreamMgr = IWwiseStreamMgrAPI::Get(); + + // Enable AK error redirection to UE log. + if (LIKELY(Monitor)) + { + UE_CLOG(AkInitializationSettings_Helpers::IsLoggingInitialization, LogAkAudio, Verbose, TEXT("Initializing Monitor's Output")); + Monitor->SetLocalOutput(AK::Monitor::ErrorLevel_All, UELocalOutputFunc); + } + + UE_CLOG(AkInitializationSettings_Helpers::IsLoggingInitialization, LogAkAudio, Verbose, TEXT("Initializing Memory Manager")); + if (UNLIKELY(!MemoryMgr) || MemoryMgr->Init(&InitializationStructure.MemSettings) != AK_Success) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to initialize AK::MemoryMgr.")); + return false; + } + + UE_CLOG(AkInitializationSettings_Helpers::IsLoggingInitialization, LogAkAudio, Verbose, TEXT("Initializing Global Callbacks")); + auto* GlobalCallbacks = FWwiseGlobalCallbacks::Get(); + if (UNLIKELY(!GlobalCallbacks) || !GlobalCallbacks->Initialize()) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to initialize Global Callbacks.")); + return false; + } + + UE_CLOG(AkInitializationSettings_Helpers::IsLoggingInitialization, LogAkAudio, Verbose, TEXT("Initializing Stream Manager")); + if (UNLIKELY(!StreamMgr) || !StreamMgr->Create(InitializationStructure.StreamManagerSettings)) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to initialize AK::StreamMgr.")); + return false; + } + + UE_CLOG(AkInitializationSettings_Helpers::IsLoggingInitialization, LogAkAudio, Verbose, TEXT("Initializing IOHook")); + if (!IOHook->Init(InitializationStructure.DeviceSettings)) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to initialize IOHook.")); + return false; + } + + if (AkInitializationSettings_Helpers::IsLoggingInitialization && InitializationStructure.InitSettings.szPluginDLLPath) + { + FString DllPath(InitializationStructure.InitSettings.szPluginDLLPath); + UE_LOG(LogAkAudio, Log, TEXT("Wwise plug-in DLL path: %s"), *DllPath); + } + + UE_CLOG(AkInitializationSettings_Helpers::IsLoggingInitialization, LogAkAudio, Verbose, TEXT("Initializing Sound Engine")); + if (UNLIKELY(!SoundEngine) || SoundEngine->Init(&InitializationStructure.InitSettings, &InitializationStructure.PlatformInitSettings) != AK_Success) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to initialize AK::SoundEngine.")); + return false; + } + + UE_CLOG(AkInitializationSettings_Helpers::IsLoggingInitialization, LogAkAudio, Verbose, TEXT("Initializing Music Engine")); + if (UNLIKELY(!MusicEngine) || MusicEngine->Init(&InitializationStructure.MusicSettings) != AK_Success) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to initialize AK::MusicEngine.")); + return false; + } + + UE_CLOG(AkInitializationSettings_Helpers::IsLoggingInitialization, LogAkAudio, Verbose, TEXT("Initializing Spatial Audio")); + if (UNLIKELY(!SpatialAudio) || SpatialAudio->Init(InitializationStructure.SpatialAudioInitSettings) != AK_Success) + { + UE_LOG(LogAkAudio, Error, TEXT("Failed to initialize AK::SpatialAudio.")); + return false; + } + +#if AK_ENABLE_COMMUNICATION + UE_CLOG(AkInitializationSettings_Helpers::IsLoggingInitialization, LogAkAudio, Verbose, TEXT("Initializing Communication")); + if (UNLIKELY(!Comm) || Comm->Init(InitializationStructure.CommSettings) != AK_Success) + { + UE_LOG(LogAkAudio, Warning, TEXT("Could not initialize Wwise communication.")); + } + else + { + UE_CLOG(AkInitializationSettings_Helpers::IsLoggingInitialization, LogAkAudio, Log, TEXT("Wwise remote connection application name: %s"), ANSI_TO_TCHAR(InitializationStructure.CommSettings.szAppNetworkName)); + } +#endif + + return true; + } + + void Finalize(FWwiseIOHook* IOHook) + { + auto* Comm = IWwiseCommAPI::Get(); + auto* MemoryMgr = IWwiseMemoryMgrAPI::Get(); + auto* Monitor = IWwiseMonitorAPI::Get(); + auto* MusicEngine = IWwiseMusicEngineAPI::Get(); + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + auto* StreamMgr = IWwiseStreamMgrAPI::GetAkStreamMgr(); + +#if AK_ENABLE_COMMUNICATION + if (LIKELY(Comm)) + { + UE_CLOG(AkInitializationSettings_Helpers::IsLoggingInitialization, LogAkAudio, Verbose, TEXT("Terminating Communication")); + Comm->Term(); + } +#endif + + // Note: No Spatial Audio Term + + if (LIKELY(MusicEngine)) + { + UE_CLOG(AkInitializationSettings_Helpers::IsLoggingInitialization, LogAkAudio, Verbose, TEXT("Terminating Music Engine")); + MusicEngine->Term(); + } + + if (LIKELY(SoundEngine && SoundEngine->IsInitialized())) + { + UE_CLOG(AkInitializationSettings_Helpers::IsLoggingInitialization, LogAkAudio, Verbose, TEXT("Terminating Sound Engine")); + SoundEngine->Term(); + } + + if (LIKELY(IOHook)) + { + UE_CLOG(AkInitializationSettings_Helpers::IsLoggingInitialization, LogAkAudio, Verbose, TEXT("Terminating IOHook")); + IOHook->Term(); + } + + if (LIKELY(StreamMgr)) + { + UE_CLOG(AkInitializationSettings_Helpers::IsLoggingInitialization, LogAkAudio, Verbose, TEXT("Terminating Stream Manager")); + StreamMgr->Destroy(); + } + + if (LIKELY(MemoryMgr && MemoryMgr->IsInitialized())) + { + UE_CLOG(AkInitializationSettings_Helpers::IsLoggingInitialization, LogAkAudio, Verbose, TEXT("Terminating Memory Manager")); + MemoryMgr->Term(); + } + + if (LIKELY(Monitor)) + { + UE_CLOG(AkInitializationSettings_Helpers::IsLoggingInitialization, LogAkAudio, Verbose, TEXT("Resetting Monitor's Output")); + Monitor->SetLocalOutput(0, nullptr); + } + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioEventSection.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioEventSection.cpp new file mode 100644 index 0000000..939cfc2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioEventSection.cpp @@ -0,0 +1,694 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "MovieSceneAkAudioEventSection.h" +#include "AkAudioDevice.h" +#include "AkAudioEvent.h" +#include "AkWaapiClient.h" +#include "MovieSceneAkAudioEventTrack.h" + +#include "Misc/Base64.h" +#include "MovieScene.h" +#include "MovieSceneCommonHelpers.h" +#include "MovieSceneSection.h" +#include "MovieSceneAkAudioEventTemplate.h" +#include "Async/Async.h" + +/** Default values and helper used by UMovieSceneAkAudioEventSection when querying event information. */ +namespace AkAudioEventSectionHelper +{ + // durations are in seconds + const float DefaultDurationForEventSpecifiedByName = 0.5f; + const float MinimumDuration = 0.05f; + const float MaximumDuration = 720000.f; + + int32 GetMaxDurationForFrameRate(FFrameRate FrameRate) + { + float Duration = (FrameRate.Denominator * MaximumDuration) / FrameRate.Numerator; + return static_cast(FMath::FloorToFloat(Duration)); + } + + FFloatRange GetDuration(UAkAudioEvent* Event, FFrameRate FrameRate) + { + if (Event == nullptr) + { + return FFloatRange(DefaultDurationForEventSpecifiedByName); + } + + if (Event->IsInfinite) + { + return FFloatRange(FMath::Min((float)GetMaxDurationForFrameRate(FrameRate), MaximumDuration)); + } + + return FFloatRange(Event->MinimumDuration, TRangeBound::Inclusive(FMath::Clamp(Event->MaximumDuration, MinimumDuration, MaximumDuration))); + } +} + +//=================================================================================================== +// Initialization / Destruction +//=================================================================================================== + +void UMovieSceneAkAudioEventSection::Initialize() +{ +#if WITH_EDITOR + if ((Event != nullptr || EventName != "") && InitState != AkEventSectionState::EUnrecognized) + { + UpdateAudioSourceInfo(); + SubscribeToEventChildAddedRemoved(); + + FAkWaapiClient* pWaapiClient = FAkWaapiClient::Get(); + if (pWaapiClient != nullptr) + { + if (InitState == AkEventSectionState::EUninitialized && iChildAddedInitializeSubscriptionID == 0 && pWaapiClient->IsConnected()) + { + UE_LOG(LogAkAudio, Warning, + TEXT("Failed to initialize Section for Event: %s"), + Event == nullptr ? *EventName : *(Event->GetName())); + + //=========================================================== + // Callback + //=========================================================== + auto updateAudioSourceInfoCallback = WampEventCallback::CreateLambda([this](uint64_t id, TSharedPtr in_UEJsonObject) + { + iCallbackCounter.Increment(); + AsyncTask(ENamedThreads::GameThread, [this, in_UEJsonObject]() + { + FScopeLock Lock(&WAAPISection); + Initialize(); + if (InitState == AkEventSectionState::EInitialized) + { + RequiresUpdate = true; + } + iCallbackCounter.Decrement(); + }); + }); +#if AK_SUPPORT_WAAPI + const auto addedUri = ak::wwise::core::object::childAdded; + TSharedRef emptyOptions = MakeShareable(new FJsonObject()); + TSharedPtr subscriptionResult = MakeShareable(new FJsonObject()); + //=========================================================== + // Child Added + //=========================================================== + pWaapiClient->Subscribe(addedUri, emptyOptions, updateAudioSourceInfoCallback, iChildAddedInitializeSubscriptionID, subscriptionResult); +#endif + } + } + } +#endif //WITH_EDITOR +} + +void UMovieSceneAkAudioEventSection::BeginDestroy() +{ +#if WITH_EDITOR + /* Wait for WAAPI callbacks to complete */ + while (iCallbackCounter.GetValue() > 0) {} + + UnsubscribeAllWAAPICallbacks(); +#endif + + if (auto* TrackerPtr = EventTracker.Get()) + { + if (TrackerPtr->IsPlaying()) + { + FAkAudioDevice::Get()->CancelEventCallbackCookie(EventTracker.Get()); + } + } + Super::BeginDestroy(); +} + +#if WITH_EDITOR +void UMovieSceneAkAudioEventSection::PostEditChangeProperty(struct FPropertyChangedEvent& e) +{ + FName PropertyName = (e.Property != nullptr) ? e.Property->GetFName() : NAME_None; + if (PropertyName == GET_MEMBER_NAME_CHECKED(UMovieSceneAkAudioEventSection, Event) + || PropertyName == GET_MEMBER_NAME_CHECKED(UMovieSceneAkAudioEventSection, EventName)) + { + UpdateAkEventInfo(); + } + Super::PostEditChangeProperty(e); +} + +//=================================================================================================== +// WAAPI Subscriptions +//=================================================================================================== + +/** Registers a call to update the audio source info when a child is added or removed from in_sParentID */ +void UMovieSceneAkAudioEventSection::SubscribeToChildAddedRemoved(FString in_sParentID, uint64& in_iAddedSubID, uint64& in_iRemovedSubID) +{ + FAkWaapiClient* pWaapiClient = FAkWaapiClient::Get(); + if (pWaapiClient != nullptr) + { + UnsubscribeWAAPICallback(in_iAddedSubID); + UnsubscribeWAAPICallback(in_iRemovedSubID); + //=========================================================== + // Callback + //=========================================================== + auto updateAudioSourceInfoCallback = WampEventCallback::CreateLambda([this, in_sParentID](uint64_t id, TSharedPtr in_UEJsonObject) + { + iCallbackCounter.Increment(); + AsyncTask(ENamedThreads::GameThread, [this, in_sParentID, in_UEJsonObject]() + { + auto parentIDString = in_UEJsonObject->GetObjectField(FAkWaapiClient::WAAPIStrings::PARENT)->GetStringField(FAkWaapiClient::WAAPIStrings::ID); + if (parentIDString.Equals(in_sParentID, ESearchCase::IgnoreCase)) + { + FScopeLock Lock(&WAAPISection); + UpdateAudioSourceInfo(); + RequiresUpdate = true; + } + iCallbackCounter.Decrement(); + }); + }); + +#if AK_SUPPORT_WAAPI + const auto addedUri = ak::wwise::core::object::childAdded; + const auto removedUri = ak::wwise::core::object::childRemoved; + TSharedRef emptyOptions = MakeShareable(new FJsonObject()); + TSharedPtr subscriptionResult = MakeShareable(new FJsonObject()); + //=========================================================== + // Child Added + //=========================================================== + pWaapiClient->Subscribe(addedUri, emptyOptions, updateAudioSourceInfoCallback, in_iAddedSubID, subscriptionResult); + //=========================================================== + // Child Removed + //=========================================================== + subscriptionResult = MakeShareable(new FJsonObject()); + pWaapiClient->Subscribe(removedUri, emptyOptions, updateAudioSourceInfoCallback, in_iRemovedSubID, subscriptionResult); +#endif // AK_SUPPORT_WAAPI + } +} + +void UMovieSceneAkAudioEventSection::SubscribeToTrimChanges() +{ + UnsubscribeWAAPICallback(iTrimBeginSubscriptionID); + UnsubscribeWAAPICallback(iTrimEndSubscriptionID); + //=========================================================== + // Subscribe to TrimBegin and TrimEnd changed events for + // new event details. + //=========================================================== + auto pWaapiClient = FAkWaapiClient::Get(); + if (pWaapiClient != nullptr && pWaapiClient->IsConnected() && AudioSourceInfoIsValid()) + { + auto updateTrimCallback = WampEventCallback::CreateLambda([this](uint64_t id, TSharedPtr in_UEJsonObject) + { + iCallbackCounter.Increment(); + AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this, id, in_UEJsonObject]() + { + FScopeLock Lock(&WAAPISection); + UpdateTrimData(); + CheckForWorkunitChanges(true); + RequiresUpdate = true; + iCallbackCounter.Decrement(); + }); + }); + + //=========================================================== + // TrimEnd + //=========================================================== + TSharedPtr trimEndSubscriptionResult = MakeShareable(new FJsonObject()); + TSharedRef trimEndChangedOptions = MakeShareable(new FJsonObject()); + { + trimEndChangedOptions->SetStringField(FAkWaapiClient::PropertyChangedStrings::RequiredOptions::OBJECT, MaxDurationSourceID); + trimEndChangedOptions->SetStringField(FAkWaapiClient::PropertyChangedStrings::RequiredOptions::PROPERTY, FAkWaapiClient::AudioSourceProperties::TRIM_END); + } +#if AK_SUPPORT_WAAPI + pWaapiClient->Subscribe(ak::wwise::core::object::propertyChanged, trimEndChangedOptions, updateTrimCallback, iTrimEndSubscriptionID, trimEndSubscriptionResult); +#endif + //=========================================================== + // TrimBegin + //=========================================================== + TSharedPtr trimBeginSubscriptionResult = MakeShareable(new FJsonObject()); + TSharedRef trimBeginChangedOptions = MakeShareable(new FJsonObject()); + { + trimBeginChangedOptions->SetStringField(FAkWaapiClient::PropertyChangedStrings::RequiredOptions::OBJECT, MaxDurationSourceID); + trimBeginChangedOptions->SetStringField(FAkWaapiClient::PropertyChangedStrings::RequiredOptions::PROPERTY, FAkWaapiClient::AudioSourceProperties::TRIM_BEGIN); + } +#if AK_SUPPORT_WAAPI + pWaapiClient->Subscribe(ak::wwise::core::object::propertyChanged, trimBeginChangedOptions, updateTrimCallback, iTrimBeginSubscriptionID, trimBeginSubscriptionResult); +#endif + } +} + +void UMovieSceneAkAudioEventSection::SubscribeToEventChildAddedRemoved() +{ + UnsubscribeWAAPICallback(iChildAddedSubscriptionID); + UnsubscribeWAAPICallback(iChildRemovedSubscriptionID); + auto sParentID = GetEventWwiseGUID().ToString(EGuidFormats::DigitsWithHyphensInBraces); + SubscribeToChildAddedRemoved(sParentID, iChildAddedSubscriptionID, iChildRemovedSubscriptionID); + SubscribeToTrimChanges(); +} + +/** Subscribes to child added and child removed for each of the action targets in the Wwise event that this section triggers. */ +void UMovieSceneAkAudioEventSection::SubscribeToEventChildren() +{ + auto pWaapiClient = FAkWaapiClient::Get(); + if (pWaapiClient != nullptr) + { + for (int i = 0; i < EventActionSubscriptionIDs.Num(); ++i) + { + UnsubscribeWAAPICallback(EventActionSubscriptionIDs[i]); + } + + EventActionSubscriptionIDs.Empty(); + + /* Construct the relevant WAAPI json fields. */ + TArray> fromID; + fromID.Add(MakeShareable(new FJsonValueString(GetEventWwiseGUID().ToString(EGuidFormats::DigitsWithHyphensInBraces)))); + AkInt64 returnFlags = (AkInt64)FAkWaapiClient::WAAPIGetReturnOptionFlag::ID | (AkInt64)FAkWaapiClient::WAAPIGetReturnOptionFlag::TYPE; + + TSharedPtr select = MakeShareable(new FJsonObject()); + TArray> selectJsonArray; + selectJsonArray.Add(MakeShareable(new FJsonValueString(FAkWaapiClient::WAAPIStrings::DESCENDANTS))); + select->SetArrayField(FAkWaapiClient::WAAPIStrings::SELECT, selectJsonArray); + TArray> transform; + transform.Add(MakeShareable(new FJsonValueObject(select))); + + TSharedPtr outJsonResult; + if (FAkWaapiClient::WAAPIGet(FAkWaapiClient::WAAPIGetFromOption::ID, fromID, returnFlags, outJsonResult, FAkWaapiClient::WAAPIGetTransformOption::SELECT, selectJsonArray)) + { + if (outJsonResult->HasField(FAkWaapiClient::WAAPIStrings::RETURN)) + { + TArray> returnJson = outJsonResult->GetArrayField(FAkWaapiClient::WAAPIStrings::RETURN); + for (int descendant = 0; descendant < returnJson.Num(); ++descendant) + { + auto pJsonObj = returnJson[descendant]->AsObject(); + auto descendantID = pJsonObj->GetStringField("id"); + auto descendantType = pJsonObj->GetStringField("type"); + + if (descendantType.Equals("Action", ESearchCase::IgnoreCase)) + { + /** When we find a child of type action, get the target property */ + fromID.Empty(); + fromID.Add(MakeShareable(new FJsonValueString(descendantID))); + outJsonResult = MakeShareable(new FJsonObject()); + TSharedRef args = MakeShareable(new FJsonObject()); + TSharedPtr from = MakeShareable(new FJsonObject()); + from->SetArrayField(FAkWaapiClient::GetFromOptionString(FAkWaapiClient::WAAPIGetFromOption::ID), fromID); + args->SetObjectField(FAkWaapiClient::WAAPIStrings::FROM, from); + TSharedRef options = MakeShareable(new FJsonObject()); + TArray> StructJsonArray; + StructJsonArray.Add(MakeShareable(new FJsonValueString("@Target"))); + options->SetArrayField(FAkWaapiClient::WAAPIStrings::RETURN, StructJsonArray); + +#if AK_SUPPORT_WAAPI + if (pWaapiClient->Call(ak::wwise::core::object::get, args, options, outJsonResult, 500)) + { + if (outJsonResult->HasField(FAkWaapiClient::WAAPIStrings::RETURN)) + { + TArray> descendantReturnJson = outJsonResult->GetArrayField(FAkWaapiClient::WAAPIStrings::RETURN); + auto pDescendantJsonObj = descendantReturnJson[0]->AsObject(); + if (pDescendantJsonObj->HasField("@Target")) + { + /** Subscribe to child added and child removed for the target object. */ + auto targetObj = pDescendantJsonObj->GetObjectField("@Target"); + auto targetID = targetObj->GetStringField("id"); + + uint64 addedSubID = 0; + uint64 removedSubID = 0; + + SubscribeToChildAddedRemoved(targetID, addedSubID, removedSubID); + + if (addedSubID != 0) + EventActionSubscriptionIDs.Add(addedSubID); + if (removedSubID != 0) + EventActionSubscriptionIDs.Add(removedSubID); + } + } + } +#endif + } + } + } + } + } +} + +void UMovieSceneAkAudioEventSection::UnsubscribeWAAPICallback(uint64& in_iSubID) +{ + if (in_iSubID != 0) + { + FScopeLock Lock(&WAAPISection); + TSharedPtr unsubscribeResult = MakeShareable(new FJsonObject()); + FAkWaapiClient* pWaapiClient = FAkWaapiClient::Get(); + if (pWaapiClient != nullptr) + pWaapiClient->Unsubscribe(in_iSubID, unsubscribeResult, 500, true); + in_iSubID = 0; + } +} + +void UMovieSceneAkAudioEventSection::UnsubscribeAllWAAPICallbacks() +{ + UnsubscribeWAAPICallback(iChildAddedInitializeSubscriptionID); + UnsubscribeWAAPICallback(iTrimBeginSubscriptionID); + UnsubscribeWAAPICallback(iTrimEndSubscriptionID); + UnsubscribeWAAPICallback(iChildAddedSubscriptionID); + UnsubscribeWAAPICallback(iChildRemovedSubscriptionID); + + for (int i = 0; i < EventActionSubscriptionIDs.Num(); ++i) + { + UnsubscribeWAAPICallback(EventActionSubscriptionIDs[i]); + } + + EventActionSubscriptionIDs.Empty(); +} + +void UMovieSceneAkAudioEventSection::WAAPIGetPeaks(const char* in_uri, + TSharedRef in_getPeaksArgs, + TSharedRef in_getPeaksOptions, + TSharedPtr in_getPeaksResults) +{ + auto WaapiClient = FAkWaapiClient::Get(); + if (WaapiClient != nullptr) + { + if (WaapiClient->Call(in_uri, in_getPeaksArgs, in_getPeaksOptions, in_getPeaksResults, 2000)) + { + int numPeaksReturned = (int)in_getPeaksResults->GetNumberField(FAkWaapiClient::AudioPeaksStrings::Results::PEAKS_ARRAY_LENGTH); + if (numPeaksReturned > 0) + { + /* Decode the peaks data binary string from WAAPI to the AudioSourcePeaks array. */ + auto peaksBinaryString = in_getPeaksResults->GetArrayField(FAkWaapiClient::AudioPeaksStrings::Results::PEAKS_BINARY)[0]->AsString(); + TArray peaksData; + FBase64::Decode(peaksBinaryString, peaksData); + int16* peaks = reinterpret_cast(peaksData.GetData()); + double maxAbsPeakValue = (double)in_getPeaksResults->GetNumberField(FAkWaapiClient::AudioPeaksStrings::Results::MAX_ABS_VALUE); + + AudioSourcePeaks.Empty(); + for (int p = 0; p < numPeaksReturned * 2; ++p) + { + int16 peakValue = peaks[p]; + AudioSourcePeaks.Add(peakValue / maxAbsPeakValue); + } + } + } + else + { + UE_LOG(LogAkAudio, Warning, TEXT("Failed to get audio source peak data from WAAPI")); + } + } +} + +//=================================================================================================== +//=================================================================================================== + +#endif //WITH_EDITOR + +//=================================================================================================== +// Getters +//=================================================================================================== + +bool UMovieSceneAkAudioEventSection::EventShouldStopAtSectionEnd() const { return StopAtSectionEnd; } + +#if WITH_EDITOR +FGuid UMovieSceneAkAudioEventSection::GetEventWwiseGUID() const +{ + if (Event) + { + return Event->GetWwiseGuid(); + } + else + { + UE_LOG(LogAkAudio, Log, TEXT("UMovieSceneAkAudioEventSection: Using deprecated event name to find GUID. Please set the \"Event\" property to a valid AkAudioEvent asset.")); + FGuid EventGUID; + FAkWaapiClient::GetGUIDForObjectOfTypeWithName(EventGUID, "Event", EventName); + return EventGUID; + } +} + +FString UMovieSceneAkAudioEventSection::GetEventWwiseName() const { return Event->GetWwiseName().ToString(); } + +const TArray& UMovieSceneAkAudioEventSection::GetAudioSourcePeaks() const { return AudioSourcePeaks; } + +/** Returns the number of min max magnitude pairs in the current peaks array. */ +const int UMovieSceneAkAudioEventSection::GetNumMinMaxPairs() const { return AudioSourcePeaks.Num() / 2; } + +const float UMovieSceneAkAudioEventSection::GetMaxSourceDuration() const { return MaxSourceDuration; } +#endif + +/** Returns the minimum and maximum durations for the specified Event or EventName. This uses the generated XML data, not WAAPI. */ +int32 UMovieSceneAkAudioEventSection::GetMaxEventDuration() const +{ + FFrameRate FrameRate = GetTypedOuter()->GetTickResolution(); + auto MaxDuration = AkAudioEventSectionHelper::GetDuration(Event, FrameRate).GetUpperBoundValue(); + return FrameRate.AsFrameNumber(MaxDuration).Value; +} + +float UMovieSceneAkAudioEventSection::GetStartTime() const +{ + FFrameRate FrameRate = GetTypedOuter()->GetTickResolution(); + return (float)FrameRate.AsSeconds(GetRange().GetLowerBoundValue()); +} + +float UMovieSceneAkAudioEventSection::GetEndTime() const +{ + FFrameRate FrameRate = GetTypedOuter()->GetTickResolution(); + return (float)FrameRate.AsSeconds(GetRange().GetUpperBoundValue()); +} + +FFloatRange UMovieSceneAkAudioEventSection::GetEventDuration() const +{ + FFrameRate FrameRate = GetTypedOuter()->GetTickResolution(); + return AkAudioEventSectionHelper::GetDuration(Event, FrameRate); +} + +#if !UE_4_26_OR_LATER +FMovieSceneEvalTemplatePtr UMovieSceneAkAudioEventSection::GenerateTemplate() const +{ + return FMovieSceneAkAudioEventTemplate(this); +} +#endif + +#if WITH_EDITOR +/** Associate a new AK audio event with this section. Also updates section time and audio source info. */ +bool UMovieSceneAkAudioEventSection::SetEvent(UAkAudioEvent* AudioEvent, const FString& Name) +{ + bool dataLoaded = true; + // Update the event details. + if (AudioEvent != nullptr) + { + Event = AudioEvent; + EventName = Name; + } + else + { + EventName = Name; + } + return UpdateAkEventInfo(); +} + +bool UMovieSceneAkAudioEventSection::UpdateAkEventInfo() +{ + UpdateAudioSourceInfo(); + if (Event && Event->MaximumDuration != FLT_MAX && Event->MinimumDuration != FLT_MAX) + { + MatchSectionLengthToEventLength(); + + SubscribeToEventChildAddedRemoved(); + return true; + } + else if (Event) + { + UE_LOG(LogAkAudio, Error, TEXT("%s Event doesn't have a defined duration."), *Event->GetFName().ToString()); + } + return false; +} +#endif + +#if WITH_EDITOR +void UMovieSceneAkAudioEventSection::MatchSectionLengthToEventLength() +{ + SetRange(TRange(GetRange().GetLowerBoundValue(), GetRange().GetLowerBoundValue() + GetMaxEventDuration())); +} + +/** Get the audio peaks data (min max magnitude pairs) for the current MaxDurationSourceID, using WAAPI. + * @param in_iNumPeaks - The number of peaks required. + */ +void UMovieSceneAkAudioEventSection::UpdateAudioSourcePeaks(int in_iNumPeaks) +{ + AKASSERT(in_iNumPeaks > 0); + /* Construct the relevant WAAPI json fields */ + TSharedRef getPeaksArgs = MakeShareable(new FJsonObject()); + TSharedRef getPeaksOptions = MakeShareable(new FJsonObject()); + TSharedPtr getPeaksResult = MakeShareable(new FJsonObject()); + getPeaksArgs->SetStringField(FAkWaapiClient::AudioPeaksStrings::Args::OBJECT, MaxDurationSourceID); + getPeaksArgs->SetNumberField(FAkWaapiClient::AudioPeaksStrings::Args::NUM_PEAKS, in_iNumPeaks); + getPeaksArgs->SetBoolField(FAkWaapiClient::AudioPeaksStrings::Args::CROSS_CHANNEL_PEAKS, true); +#if AK_SUPPORT_WAAPI + WAAPIGetPeaks(ak::wwise::core::audioSourcePeaks::getMinMaxPeaksInTrimmedRegion, getPeaksArgs, getPeaksOptions, getPeaksResult); +#endif +} + +/** Get the audio peaks data (min max magnitude pairs) for the current MaxDurationSourceID, using WAAPI. +* @param in_iNumPeaks - The number of peaks required. +* @param in_dtimeFrom - The start time of the time period for which peaks are required +* @param in_dTimeTo - The end time of the time period for which peaks are required. +*/ +void UMovieSceneAkAudioEventSection::UpdateAudioSourcePeaks(int in_iNumPeaks, double in_dTimeFrom, double in_dTimeTo) +{ + AKASSERT(in_iNumPeaks > 0); + /* Construct the relevant WAAPI json fields */ + TSharedRef getPeaksArgs = MakeShareable(new FJsonObject()); + TSharedRef getPeaksOptions = MakeShareable(new FJsonObject()); + TSharedPtr getPeaksResult = MakeShareable(new FJsonObject()); + getPeaksArgs->SetStringField(FAkWaapiClient::AudioPeaksStrings::Args::OBJECT, MaxDurationSourceID); + getPeaksArgs->SetNumberField(FAkWaapiClient::AudioPeaksStrings::Args::NUM_PEAKS, in_iNumPeaks); + getPeaksArgs->SetNumberField(FAkWaapiClient::AudioPeaksStrings::Args::TIME_FROM, in_dTimeFrom); + getPeaksArgs->SetNumberField(FAkWaapiClient::AudioPeaksStrings::Args::TIME_TO, in_dTimeTo); + getPeaksArgs->SetBoolField(FAkWaapiClient::AudioPeaksStrings::Args::CROSS_CHANNEL_PEAKS, true); +#if AK_SUPPORT_WAAPI + WAAPIGetPeaks(ak::wwise::core::audioSourcePeaks::getMinMaxPeaksInRegion, getPeaksArgs, getPeaksOptions, getPeaksResult); +#endif +} + +/** Update the trim data for the longest audio source used by the Wwise event that this section triggers. */ +void UMovieSceneAkAudioEventSection::UpdateTrimData() +{ + AKASSERT(MaxDurationSourceID != ""); + + AkInt64 returnFlag = (AkInt64)FAkWaapiClient::WAAPIGetReturnOptionFlag::AUDIO_SOURCE_TRIM_VALUES; + TArray> fromAudioSourceID; + fromAudioSourceID.Add(MakeShareable(new FJsonValueString(MaxDurationSourceID))); + TSharedPtr trimJsonResult; + if (FAkWaapiClient::WAAPIGet(FAkWaapiClient::WAAPIGetFromOption::ID, fromAudioSourceID, returnFlag, trimJsonResult)) + { + TArray> trimReturnJson = trimJsonResult->GetArrayField(FAkWaapiClient::WAAPIStrings::RETURN); + auto trimJsonObj = trimReturnJson[0]->AsObject(); + auto trimValuesString = FAkWaapiClient::GetReturnOptionString(FAkWaapiClient::WAAPIGetReturnOptionFlag::AUDIO_SOURCE_TRIM_VALUES); + auto trimValuesJson = trimJsonObj->GetObjectField(trimValuesString); + + /* We only keep track of the TrimBegin value, as the waveform length is calculated using the duration value. */ + TrimBegin = trimValuesJson->GetNumberField(FAkWaapiClient::TrimValuesStrings::TRIM_BEGIN); + + auto TrimEnd = trimValuesJson->GetNumberField(FAkWaapiClient::TrimValuesStrings::TRIM_END); + MaxSourceDuration = TrimEnd - TrimBegin; + } +} + +/** Use WAAPI to update the MaxDurationSourceID and MaxSourceDuration. */ +void UMovieSceneAkAudioEventSection::UpdateAudioSourceInfo() +{ + UE_LOG(LogAkAudio, Verbose, + TEXT("UMovieSceneAkAudioEventSection::UpdateAudioSourceInfo: Updating section %s source info (Event %s)"), + Event == nullptr ? *EventName : *(Event->GetName()), + *GetName()); + + // Invalidate all audio source info data. + InvalidateAudioSourceInfo(); + TrimBegin = 0.0f; + + EventTracker->IsDirty = false; + + // Update the event state in case it had previously been set to EUnrecognised + InitState = AkEventSectionState::EUninitialized; + + TArray> fromID; + fromID.Add(MakeShareable(new FJsonValueString(GetEventWwiseGUID().ToString(EGuidFormats::DigitsWithHyphensInBraces)))); + AkInt64 returnFlags = (AkInt64)FAkWaapiClient::WAAPIGetReturnOptionFlag::AUDIO_SOURCE_MAX_DURATION_SOURCE | + (AkInt64)FAkWaapiClient::WAAPIGetReturnOptionFlag::AUDIO_SOURCE_PLAYBACK_DURATION; + + TSharedPtr outJsonResult; + if (FAkWaapiClient::WAAPIGet(FAkWaapiClient::WAAPIGetFromOption::ID, fromID, returnFlags, outJsonResult)) + { + /* Update MaxDurationSourceID and MaxSourceDuration from WAAPI return json. */ + TArray> returnJson = outJsonResult->GetArrayField(FAkWaapiClient::WAAPIStrings::RETURN); + + auto jsonObj = returnJson[0]->AsObject(); + auto maxDurationSourceString = FAkWaapiClient::GetReturnOptionString(FAkWaapiClient::WAAPIGetReturnOptionFlag::AUDIO_SOURCE_MAX_DURATION_SOURCE); + auto maxDurationSourceJson = jsonObj->GetObjectField(maxDurationSourceString); + auto newMaxDurationSourceID = maxDurationSourceJson->GetStringField(FAkWaapiClient::WAAPIStrings::ID); + MaxDurationSourceID = newMaxDurationSourceID; + MaxSourceDuration = maxDurationSourceJson->GetNumberField(FAkWaapiClient::WAAPIStrings::TRIMMED_DURATION); + + UpdateTrimData(); + SubscribeToTrimChanges(); + SubscribeToEventChildren(); + InitState = AkEventSectionState::EInitialized; + UnsubscribeWAAPICallback(iChildAddedInitializeSubscriptionID); + UE_LOG(LogAkAudio, Verbose, + TEXT("UMovieSceneAkAudioEventSection::UpdateAudioSourceInfo: Updated section %s source info (Event %s)"), + Event == nullptr ? *EventName : *(Event->GetName()), + *GetName()); + } + else + { + InitState = AkEventSectionState::EUnrecognized; + UE_LOG(LogAkAudio, Verbose, + TEXT("UMovieSceneAkAudioEventSection::UpdateAudioSourceInfo: Failed to update section %s source info (Event %s)"), + Event == nullptr ? *EventName : *(Event->GetName()), + *GetName()); + } + + CheckForWorkunitChanges(true); + RequiresUpdate = true; +} + +void UMovieSceneAkAudioEventSection::InvalidateAudioSourceInfo() +{ + AudioSourcePeaks.Empty(); + MaxSourceDuration = -1.0f; + MaxDurationSourceID = ""; +} + +bool UMovieSceneAkAudioEventSection::AudioSourceInfoIsValid() const +{ + return MaxDurationSourceID != "" && MaxSourceDuration != -1.0f; +} + +void UMovieSceneAkAudioEventSection::CheckForWorkunitChanges(bool in_bNotifyTrack) +{ + /* Check if either of the workunits that contain the event or the longest audio source are dirty. + */ + bool bIsDirty = false; + bIsDirty |= CheckWorkunitChangesForID(GetEventWwiseGUID()); + if (!MaxDurationSourceID.IsEmpty()) + { + FGuid maxDurationID; + FGuid::Parse(MaxDurationSourceID, maxDurationID); + bIsDirty |= CheckWorkunitChangesForID(maxDurationID); + } + EventTracker->IsDirty = bIsDirty; + if (bIsDirty && in_bNotifyTrack) + { + if (UMovieSceneAkAudioEventTrack* OwnerTrack = Cast(GetOuter())) + { + OwnerTrack->WorkUnitChangesDetectedFromSection(this); + } + } +} + +bool UMovieSceneAkAudioEventSection::CheckWorkunitChangesForID(FGuid in_objectGUID) +{ + FGuid workUnitGUID; + FAkWaapiClient::GetParentOfType(in_objectGUID, workUnitGUID, FAkWaapiClient::WwiseTypeStrings::WORKUNIT); + TArray> fromID; + fromID.Add(MakeShareable(new FJsonValueString(workUnitGUID.ToString(EGuidFormats::DigitsWithHyphensInBraces)))); + AkInt64 returnFlags = (AkInt64)FAkWaapiClient::WAAPIGetReturnOptionFlag::TYPE | + (AkInt64)FAkWaapiClient::WAAPIGetReturnOptionFlag::WORKUNIT_IS_DIRTY; + TSharedPtr outJsonResult; + if (FAkWaapiClient::WAAPIGet(FAkWaapiClient::WAAPIGetFromOption::ID, fromID, returnFlags, outJsonResult)) + { + TArray> returnJson = outJsonResult->GetArrayField(FAkWaapiClient::WAAPIStrings::RETURN); + auto jsonObj = returnJson[0]->AsObject(); + auto typeString = FAkWaapiClient::GetReturnOptionString(FAkWaapiClient::WAAPIGetReturnOptionFlag::TYPE); + auto objType = jsonObj->GetStringField(typeString); + AKASSERT(objType.Equals("workunit", ESearchCase::IgnoreCase)); + auto workunitDirtyString = FAkWaapiClient::GetReturnOptionString(FAkWaapiClient::WAAPIGetReturnOptionFlag::WORKUNIT_IS_DIRTY); + auto workUnitIsDirty = jsonObj->GetBoolField(workunitDirtyString); + return workUnitIsDirty; + } + return false; +} + +#endif // WITH_EDITOR diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioEventTemplate.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioEventTemplate.cpp new file mode 100644 index 0000000..9c30260 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioEventTemplate.cpp @@ -0,0 +1,325 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "MovieSceneAkAudioEventTemplate.h" +#include "AkAudioDevice.h" + +#include "MovieSceneAkAudioEventSection.h" + +#include "MovieSceneExecutionToken.h" +#include "IMovieScenePlayer.h" +#include "Engine/World.h" + +/** Defines the behaviour of an AKAdioEventSection within UE sequencer. */ +struct FMovieSceneAkAudioEventSectionData +{ + FMovieSceneAkAudioEventSectionData(const UMovieSceneAkAudioEventSection& InSection) + : EventTracker(InSection.EventTracker) + { + EventTracker->bStopAtSectionEnd = InSection.EventShouldStopAtSectionEnd(); + EventTracker->EventName = InSection.GetEventName(); + EventTracker->Event = InSection.GetEvent(); + EventTracker->ClipStartTime = InSection.GetStartTime(); + EventTracker->ClipEndTime = InSection.GetEndTime(); + EventTracker->EventDuration = InSection.GetEventDuration(); + EventTracker->EmptyPlayingIDs(); + EventTracker->EmptyScheduledStops(); + RetriggerEvent = InSection.ShouldRetriggerEvent(); + } + + float inline GetTimeInSeconds(const FMovieSceneContext& Context) + { + return (float)Context.GetFrameRate().AsSeconds(Context.GetTime()); + } + + void Update(const FMovieSceneContext& Context, const FMovieSceneEvaluationOperand& Operand, IMovieScenePlayer& Player, FAkAudioDevice* AudioDevice) + { + ensure(AudioDevice != nullptr); + + + switch (Player.GetPlaybackStatus()) + { + case EMovieScenePlayerStatus::Paused: + case EMovieScenePlayerStatus::Stopped: + { + ResetTracker(AudioDevice); + break; + } + case EMovieScenePlayerStatus::Playing: + { + /* We use a slight hack to support looping in the UE sequencer, by checking If our current time is <= the previous event start time.*/ + const float CurrentTime = GetTimeInSeconds(Context); + bool bSequencerHasLooped = CurrentTime <= EventTracker->PreviousPlayingTime; + /* If the section is played and no Wwise event has been triggered */ + if ((Context.GetDirection() == EPlayDirection::Forwards && !EventTracker->IsPlaying()) || bSequencerHasLooped) + { + /* If the sequencer has looped, we want to kill any currently playing events */ + if (EventTracker->IsPlaying() && bSequencerHasLooped) + WwiseEventTriggering::StopAllPlayingIDs(AudioDevice, *EventTracker); + + /* If the section has a valid object binding */ + if (Operand.ObjectBindingID.IsValid()) + { + /* If the Wwise event hasn't been previously triggered */ + if (EventTracker->PreviousEventStartTime == -1.0f || bSequencerHasLooped) + ObjectBindingPlay(AudioDevice, Player.FindBoundObjects(Operand), Context); + else if (RetriggerEvent) + ObjectBindingRetrigger(AudioDevice, Player.FindBoundObjects(Operand), Context); + } + else /* Otherwwise play or re-trigger the Wwise event on a dummy object. */ + { + if (EventTracker->PreviousEventStartTime == -1.0f || bSequencerHasLooped) + MasterPlay(AudioDevice, Context); + else if (RetriggerEvent) + MasterRetrigger(AudioDevice, Context); + } + EventTracker->PreviousPlayingTime = CurrentTime; + } + break; + } + case EMovieScenePlayerStatus::Scrubbing: + case EMovieScenePlayerStatus::Stepping: + case EMovieScenePlayerStatus::Jumping: + { + if (Operand.ObjectBindingID.IsValid()) + { + ObjectBindingScrub(AudioDevice, Player.FindBoundObjects(Operand), Context); + } + else + { + MasterScrub(AudioDevice, Context); + } + break; + } + } + } + + void SectionBeingDestroyed(FAkAudioDevice* AudioDevice) + { + AudioDevice->CancelEventCallbackCookie(EventTracker.Get()); + ResetTracker(AudioDevice); + } + + void ResetTracker(FAkAudioDevice* AudioDevice) + { + if (EventTracker->bStopAtSectionEnd) + WwiseEventTriggering::StopAllPlayingIDs(AudioDevice, *EventTracker); + + EventTracker->PreviousEventStartTime = -1.0f; + EventTracker->PreviousPlayingTime = -1.0f; + } + + TSharedPtr GetEventTracker() { return EventTracker; } + +private: + /** Empty previous retriggered events, play the Wwise event, and seek to the current time. */ + void ObjectBindingPlay(FAkAudioDevice* AudioDevice, TArrayView> BoundObjects, const FMovieSceneContext& Context) + { + if (EventTracker.IsValid() && EventShouldPlay(Context)) + { + const float CurrentTime = GetTimeInSeconds(Context); + for (auto ObjectPtr : BoundObjects) + { + auto Object = ObjectPtr.Get(); + AkPlayingID PlayingID = WwiseEventTriggering::PostEvent(Object, AudioDevice, *EventTracker, CurrentTime); + WwiseEventTriggering::SeekOnEvent(Object, AudioDevice, GetProportionalTime(Context), *EventTracker, PlayingID); + EventTracker->PreviousEventStartTime = CurrentTime; + } + } + } + + /** Play the Wwise event, store the event start time in the event tracker, and jump to the current time. */ + void ObjectBindingRetrigger(FAkAudioDevice* AudioDevice, TArrayView> BoundObjects, const FMovieSceneContext& Context) + { + if (EventTracker.IsValid() && EventShouldPlay(Context)) + { + const float CurrentTime = GetTimeInSeconds(Context); + for (auto ObjectPtr : BoundObjects) + { + auto Object = ObjectPtr.Get(); + AkPlayingID PlayingID = WwiseEventTriggering::PostEvent(Object, AudioDevice, *EventTracker, CurrentTime); + EventTracker->PreviousEventStartTime = CurrentTime; + WwiseEventTriggering::SeekOnEvent(Object, AudioDevice, GetProportionalTime(Context), *EventTracker, PlayingID); + } + } + } + + /** Empty previous retriggered events, play the Wwise event, and seek to the current time. */ + void MasterPlay(FAkAudioDevice* AudioDevice, const FMovieSceneContext& Context) + { + if (EventTracker.IsValid() && EventShouldPlay(Context)) + { + EventTracker->PreviousEventStartTime = EventTracker->ClipStartTime; + const float CurrentTime = GetTimeInSeconds(Context); + AkPlayingID PlayingID = WwiseEventTriggering::PostEventOnDummyObject(AudioDevice, *EventTracker, CurrentTime); + WwiseEventTriggering::SeekOnEventWithDummyObject(AudioDevice, GetProportionalTime(Context), *EventTracker, PlayingID); + EventTracker->PreviousEventStartTime = CurrentTime; + } + } + + /** Play the Wwise event, store the event start time in the event tracker, and jump to the current time. */ + void MasterRetrigger(FAkAudioDevice* AudioDevice, const FMovieSceneContext& Context) + { + if (EventTracker.IsValid() && EventShouldPlay(Context)) + { + const float CurrentTime = GetTimeInSeconds(Context); + AkPlayingID PlayingID = WwiseEventTriggering::PostEventOnDummyObject(AudioDevice, *EventTracker, CurrentTime); + EventTracker->PreviousEventStartTime = CurrentTime; + WwiseEventTriggering::SeekOnEventWithDummyObject(AudioDevice, GetProportionalTime(Context), *EventTracker, PlayingID); + } + } + + void ObjectBindingScrub(FAkAudioDevice* AudioDevice, TArrayView> BoundObjects, const FMovieSceneContext& Context) + { + if (EventTracker.IsValid() && EventShouldPlay(Context)) + { + auto ProportionalTime = GetProportionalTime(Context); + const float CurrentTime = GetTimeInSeconds(Context); + for (auto ObjectPtr : BoundObjects) + { + auto Object = ObjectPtr.Get(); + + if (!EventTracker->IsPlaying()) + { + WwiseEventTriggering::TriggerScrubSnippet(Object, AudioDevice, *EventTracker); + EventTracker->PreviousEventStartTime = -1.0f; + } + else if (!EventTracker->HasScheduledStop()) + { + WwiseEventTriggering::ScheduleStopEventsForCurrentlyPlayingIDs(AudioDevice, *EventTracker); + } + WwiseEventTriggering::SeekOnEvent(Object, AudioDevice, ProportionalTime, *EventTracker); + } + } + } + + void MasterScrub(FAkAudioDevice* AudioDevice, const FMovieSceneContext& Context) + { + if (EventTracker.IsValid() && EventShouldPlay(Context)) + { + const float CurrentTime = GetTimeInSeconds(Context); + if (!EventTracker->IsPlaying()) + { + WwiseEventTriggering::TriggerScrubSnippetOnDummyObject(AudioDevice, *EventTracker); + } + else if (!EventTracker->HasScheduledStop()) + { + WwiseEventTriggering::ScheduleStopEventsForCurrentlyPlayingIDs(AudioDevice, *EventTracker); + } + EventTracker->PreviousEventStartTime = -1.0f; + WwiseEventTriggering::SeekOnEventWithDummyObject(AudioDevice, GetProportionalTime(Context), *EventTracker); + } + } + + auto GetMaxDuration() const + { + const auto& DurationRange = EventTracker->EventDuration; + auto MaxDuration = DurationRange.GetUpperBoundValue(); + if (!DurationRange.IsDegenerate() || MaxDuration == 0.0f) + MaxDuration = EventTracker->CurrentDurationEstimation == -1.0f ? EventTracker->GetClipDuration() : EventTracker->CurrentDurationEstimation; + + return MaxDuration; + } + + /** Checks whether the current time is less than the maximum estimated duration OR if the event is set to retrigger. */ + bool EventShouldPlay(const FMovieSceneContext& Context) + { + const double PreviousStartTime = EventTracker->PreviousEventStartTime == -1.0f ? EventTracker->ClipStartTime + : EventTracker->PreviousEventStartTime; + const double CurrentTime = GetTimeInSeconds(Context) - PreviousStartTime; + return CurrentTime < GetMaxDuration() || RetriggerEvent; + } + + /** Returns the current time as a proportion of the maximum duration (0 - 1) */ + AkReal32 GetProportionalTime(const FMovieSceneContext& Context) + { + if (EventTracker.IsValid()) + { + auto MaxDuration = GetMaxDuration(); + if (MaxDuration > 0.0f) + { + const double PreviousStartTime = EventTracker->PreviousEventStartTime == -1.0f ? EventTracker->ClipStartTime : EventTracker->PreviousEventStartTime; + const double CurrentTime = GetTimeInSeconds(Context) - PreviousStartTime; + return (float)fmod(CurrentTime, (double)MaxDuration) / MaxDuration; + } + } + + return 0.0f; + } + + TSharedPtr EventTracker; + bool RetriggerEvent = false; +}; + +struct FAkAudioEventEvaluationData : IPersistentEvaluationData +{ + TSharedPtr SectionData; +}; + +struct FAkAudioEventExecutionToken : IMovieSceneExecutionToken +{ + virtual void Execute(const FMovieSceneContext& Context, const FMovieSceneEvaluationOperand& Operand, + FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player) override + { + auto AudioDevice = FAkAudioDevice::Get(); + if (!AudioDevice) + return; + + auto persistentData = PersistentData.GetSectionData(); + TSharedPtr SectionData = persistentData.SectionData; + if (SectionData.IsValid()) + SectionData->Update(Context, Operand, Player, AudioDevice); + } +}; + + +FMovieSceneAkAudioEventTemplate::FMovieSceneAkAudioEventTemplate(const UMovieSceneAkAudioEventSection* InSection) + : Section(InSection) +{} + +void FMovieSceneAkAudioEventTemplate::Evaluate(const FMovieSceneEvaluationOperand& Operand, const FMovieSceneContext& Context, + const FPersistentEvaluationData& PersistentData, FMovieSceneExecutionTokens& ExecutionTokens) const +{ + auto AudioDevice = FAkAudioDevice::Get(); + if (!AudioDevice) + return; + + ExecutionTokens.Add(FAkAudioEventExecutionToken()); +} + +void FMovieSceneAkAudioEventTemplate::Setup(FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player) const +{ + auto AudioDevice = FAkAudioDevice::Get(); + if (!AudioDevice) + return; + + if (Section) + PersistentData.AddSectionData().SectionData = MakeShareable(new FMovieSceneAkAudioEventSectionData(*Section)); +} + +void FMovieSceneAkAudioEventTemplate::TearDown(FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player) const +{ + auto AudioDevice = FAkAudioDevice::Get(); + if (!AudioDevice) + return; + + TSharedPtr SectionData = PersistentData.GetSectionData().SectionData; + if (SectionData.IsValid()) + { + SectionData->SectionBeingDestroyed(AudioDevice); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioEventTemplate.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioEventTemplate.h new file mode 100644 index 0000000..c0f15af --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioEventTemplate.h @@ -0,0 +1,48 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Evaluation/MovieSceneEvalTemplate.h" + +#include "MovieSceneAkAudioEventTemplate.generated.h" + +class UMovieSceneAkAudioEventSection; + +USTRUCT() +struct AKAUDIO_API FMovieSceneAkAudioEventTemplate + : public FMovieSceneEvalTemplate +{ + GENERATED_BODY() + + FMovieSceneAkAudioEventTemplate() {} + + FMovieSceneAkAudioEventTemplate(const UMovieSceneAkAudioEventSection* InSection); + + virtual void Evaluate(const FMovieSceneEvaluationOperand& Operand, const FMovieSceneContext& Context, const FPersistentEvaluationData& PersistentData, FMovieSceneExecutionTokens& ExecutionTokens) const override; + + virtual UScriptStruct& GetScriptStructImpl() const override { return *StaticStruct(); } + + virtual void Setup(FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player) const override; + virtual void TearDown(FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player) const override; + virtual void SetupOverrides() override { EnableOverrides(RequiresSetupFlag | RequiresTearDownFlag); } + + UPROPERTY() + const UMovieSceneAkAudioEventSection* Section = nullptr; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioEventTrack.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioEventTrack.cpp new file mode 100644 index 0000000..f7bd304 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioEventTrack.cpp @@ -0,0 +1,88 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "MovieSceneAkAudioEventTrack.h" +#include "AkAudioDevice.h" + +#include "IMovieScenePlayer.h" +#include "MovieScene.h" +#include "MovieSceneAkAudioEventSection.h" +#include "MovieSceneAkAudioEventTemplate.h" + +FMovieSceneEvalTemplatePtr UMovieSceneAkAudioEventTrack::CreateTemplateForSection(const UMovieSceneSection& InSection) const +{ +#if UE_4_26_OR_LATER + return FMovieSceneAkAudioEventTemplate(CastChecked(&InSection)); +#else + return InSection.GenerateTemplate(); +#endif +} + +UMovieSceneSection* UMovieSceneAkAudioEventTrack::CreateNewSection() +{ + return NewObject(this, UMovieSceneAkAudioEventSection::StaticClass(), NAME_None, RF_Transactional); +} + +#if WITH_EDITOR +bool UMovieSceneAkAudioEventTrack::AddNewEvent(FFrameNumber Time, UAkAudioEvent* Event, const FString& EventName) +{ + UMovieSceneAkAudioEventSection* NewSection = NewObject(this); + ensure(NewSection); + + bool eventSet = NewSection->SetEvent(Event, EventName); + if (eventSet) + { + const auto Duration = NewSection->GetMaxEventDuration(); + NewSection->InitialPlacement(GetAllSections(), Time, Duration, SupportsMultipleRows()); + AddSection(*NewSection); + } + return true; +} + +void UMovieSceneAkAudioEventTrack::WorkUnitChangesDetectedFromSection(UMovieSceneAkAudioEventSection* in_pSection) +{ + for (auto Section : Sections) + { + if (UMovieSceneAkAudioEventSection* AkSection = Cast(Section)) + { + if (AkSection != in_pSection) + { + AkSection->CheckForWorkunitChanges(); + } + } + } +} +#endif + +#if WITH_EDITORONLY_DATA +FText UMovieSceneAkAudioEventTrack::GetDisplayName() const +{ + return NSLOCTEXT("MovieSceneAkAudioEventTrack", "TrackName", "AkAudioEvents"); +} +#endif + +FName UMovieSceneAkAudioEventTrack::GetTrackName() const +{ + static FName TrackName("AkAudioEvents"); + return TrackName; +} + +bool UMovieSceneAkAudioEventTrack::SupportsType(TSubclassOf SectionClass) const +{ + return SectionClass == UMovieSceneAkAudioEventSection::StaticClass(); +} + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioRTPCSection.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioRTPCSection.cpp new file mode 100644 index 0000000..b5c56e9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioRTPCSection.cpp @@ -0,0 +1,143 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "MovieSceneAkAudioRTPCSection.h" +#include "AkAudioDevice.h" +#include "AkCustomVersion.h" +#include "MovieSceneAkAudioRTPCTemplate.h" + +#include "Channels/MovieSceneChannelProxy.h" +#include "Channels/MovieSceneChannelEditorData.h" +#include "MovieScene.h" + +UMovieSceneAkAudioRTPCSection::UMovieSceneAkAudioRTPCSection(const FObjectInitializer& Init) + : Super(Init) +{ + FMovieSceneChannelProxyData Channels; + +#if WITH_EDITOR + FMovieSceneChannelMetaData Metadata; + Metadata.SetIdentifiers("RTPC", NSLOCTEXT("MovieSceneAkAudioRTPCSectionEditorData", "RTPC", "RTPC")); + Channels.Add(RTPCChannel, Metadata, TMovieSceneExternalValue()); +#else + Channels.Add(RTPCChannel); +#endif + + // Populate the channel proxy - if any of our channels were ever reallocated, we'd need to repopulate the proxy, + // but since ours are all value member types, we only need to populate in the constructor + ChannelProxy = MakeShared(MoveTemp(Channels)); +} + +#if !UE_4_26_OR_LATER +FMovieSceneEvalTemplatePtr UMovieSceneAkAudioRTPCSection::GenerateTemplate() const +{ + return FMovieSceneAkAudioRTPCTemplate(*this); +} +#endif + +float UMovieSceneAkAudioRTPCSection::GetStartTime() const +{ + FFrameRate FrameRate = GetTypedOuter()->GetTickResolution(); + return (float)FrameRate.AsSeconds(GetRange().GetLowerBoundValue()); +} + +float UMovieSceneAkAudioRTPCSection::GetEndTime() const +{ + FFrameRate FrameRate = GetTypedOuter()->GetTickResolution(); + return (float)FrameRate.AsSeconds(GetRange().GetUpperBoundValue()); +} + +void UMovieSceneAkAudioRTPCSection::PostLoad() +{ + Super::PostLoad(); + const int32 AkVersion = GetLinkerCustomVersion(FAkCustomVersion::GUID); + + if (AkVersion < FAkCustomVersion::NewRTPCTrackDataContainer) + { + + if (FloatCurve.GetDefaultValue() != MAX_flt) + { + RTPCChannel.SetDefault(FloatCurve.GetDefaultValue()); + } + + RTPCChannel.PreInfinityExtrap = FloatCurve.PreInfinityExtrap; + RTPCChannel.PostInfinityExtrap = FloatCurve.PostInfinityExtrap; + + TArray Times; + TArray Values; + Times.Reserve(FloatCurve.GetNumKeys()); + Values.Reserve(FloatCurve.GetNumKeys()); + + const FFrameRate LegacyFrameRate = GetLegacyConversionFrameRate(); + const float Interval = LegacyFrameRate.AsInterval(); + + int32 Index = 0; + for (auto It = FloatCurve.GetKeyIterator(); It; ++It) + { + const FRichCurveKey& Key = *It; + + FFrameNumber KeyTime = UpgradeLegacyMovieSceneTime(nullptr, LegacyFrameRate, It->Time); + + FMovieSceneFloatValue NewValue; + NewValue.Value = Key.Value; + NewValue.InterpMode = Key.InterpMode; + NewValue.TangentMode = Key.TangentMode; + NewValue.Tangent.ArriveTangent = Key.ArriveTangent * Interval; + NewValue.Tangent.LeaveTangent = Key.LeaveTangent * Interval; + ConvertInsertAndSort(Index++, KeyTime, NewValue, Times, Values); + } + + RTPCChannel.Set(Times, Values); + return; + } + + FloatChannelSerializationHelper.ToFloatChannel(RTPCChannel); +} + +void UMovieSceneAkAudioRTPCSection::Serialize(FArchive& Ar) +{ + FloatChannelSerializationHelper = RTPCChannel; + Ar.UsingCustomVersion(FAkCustomVersion::GUID); + Super::Serialize(Ar); +} + +#if WITH_EDITOR +void UMovieSceneAkAudioRTPCSection::PreEditChange(FProperty* PropertyAboutToChange) +{ + PreviousName = Name; +} + +void UMovieSceneAkAudioRTPCSection::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + const FName PropertyName = (PropertyChangedEvent.Property != nullptr) ? PropertyChangedEvent.Property->GetFName() : NAME_None; + + if (PropertyName == GET_MEMBER_NAME_CHECKED(UMovieSceneAkAudioRTPCSection, Name)) + { + if (!IsRTPCNameValid()) + { + Name = PreviousName; + } + } + + Super::PostEditChangeProperty(PropertyChangedEvent); +} + +bool UMovieSceneAkAudioRTPCSection::IsRTPCNameValid() +{ + return !Name.IsEmpty(); +} +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioRTPCTemplate.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioRTPCTemplate.cpp new file mode 100644 index 0000000..7e92642 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioRTPCTemplate.cpp @@ -0,0 +1,99 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "MovieSceneAkAudioRTPCTemplate.h" +#include "AkAudioDevice.h" +#include "MovieSceneAkAudioRTPCSection.h" +#include "MovieSceneExecutionToken.h" +#include "IMovieScenePlayer.h" + + +FMovieSceneAkAudioRTPCSectionData::FMovieSceneAkAudioRTPCSectionData(const UMovieSceneAkAudioRTPCSection& Section) + : RTPCName(Section.GetRTPCName()) + , RTPCChannel(Section.GetChannel()) +{ +} + + +struct FAkAudioRTPCEvaluationData : IPersistentEvaluationData +{ + TSharedPtr SectionData; +}; + + +struct FAkAudioRTPCExecutionToken : IMovieSceneExecutionToken +{ + virtual void Execute(const FMovieSceneContext& Context, const FMovieSceneEvaluationOperand& Operand, FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player) override + { + auto AudioDevice = FAkAudioDevice::Get(); + if (!AudioDevice) + { + return; + } + + TSharedPtr SectionData = PersistentData.GetSectionData().SectionData; + if (SectionData.IsValid()) + { + auto RTPCNameString = *(SectionData->RTPCName); + float Value; + SectionData->RTPCChannel.Evaluate(Context.GetTime(), Value); + + if (Operand.ObjectBindingID.IsValid()) + { // Object binding audio track + for (auto ObjectPtr : Player.FindBoundObjects(Operand)) + { + auto Object = ObjectPtr.Get(); + if (Object) + { + auto Actor = CastChecked(Object); + if (Actor) + { + AudioDevice->SetRTPCValue(RTPCNameString, Value, 0, Actor); + } + } + } + } + else + { // Master audio track + AudioDevice->SetRTPCValue(RTPCNameString, Value, 0, nullptr); + } + } + } +}; + + +FMovieSceneAkAudioRTPCTemplate::FMovieSceneAkAudioRTPCTemplate(const UMovieSceneAkAudioRTPCSection& InSection) + : Section(&InSection) +{ + if (!Section->GetRTPCName().Len()) + { + Section = nullptr; + } +} + +void FMovieSceneAkAudioRTPCTemplate::Evaluate(const FMovieSceneEvaluationOperand& Operand, const FMovieSceneContext& Context, const FPersistentEvaluationData& PersistentData, FMovieSceneExecutionTokens& ExecutionTokens) const +{ + ExecutionTokens.Add(FAkAudioRTPCExecutionToken()); +} + +void FMovieSceneAkAudioRTPCTemplate::Setup(FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player) const +{ + if (Section != nullptr) + { + PersistentData.AddSectionData().SectionData = MakeShareable(new FMovieSceneAkAudioRTPCSectionData(*Section)); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioRTPCTemplate.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioRTPCTemplate.h new file mode 100644 index 0000000..f7d98c0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioRTPCTemplate.h @@ -0,0 +1,60 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Evaluation/MovieSceneEvalTemplate.h" +#include "Channels/MovieSceneFloatChannel.h" +#include "MovieSceneAkAudioRTPCTemplate.generated.h" + + +class UMovieSceneAkAudioRTPCSection; + +struct FMovieSceneAkAudioRTPCSectionData +{ + FMovieSceneAkAudioRTPCSectionData() {} + + FMovieSceneAkAudioRTPCSectionData(const UMovieSceneAkAudioRTPCSection& Section); + + FString RTPCName; + + FMovieSceneFloatChannel RTPCChannel; +}; + + +USTRUCT() +struct AKAUDIO_API FMovieSceneAkAudioRTPCTemplate + : public FMovieSceneEvalTemplate +{ + GENERATED_BODY() + + FMovieSceneAkAudioRTPCTemplate() {} + + FMovieSceneAkAudioRTPCTemplate(const UMovieSceneAkAudioRTPCSection& InSection); + + virtual void Evaluate(const FMovieSceneEvaluationOperand& Operand, const FMovieSceneContext& Context, const FPersistentEvaluationData& PersistentData, FMovieSceneExecutionTokens& ExecutionTokens) const override; + + virtual UScriptStruct& GetScriptStructImpl() const override { return *StaticStruct(); } + + virtual void Setup(FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player) const override; + virtual void SetupOverrides() override { EnableOverrides(RequiresSetupFlag); } + + UPROPERTY() + const UMovieSceneAkAudioRTPCSection* Section = nullptr; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioRTPCTrack.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioRTPCTrack.cpp new file mode 100644 index 0000000..790f922 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/MovieSceneAkAudioRTPCTrack.cpp @@ -0,0 +1,72 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "MovieSceneAkAudioRTPCTrack.h" +#include "AkAudioDevice.h" + +#include "IMovieScenePlayer.h" +#include "MovieSceneCommonHelpers.h" + +#include "MovieSceneAkAudioRTPCSection.h" + +#include "MovieSceneAkAudioRTPCTemplate.h" + +FMovieSceneEvalTemplatePtr UMovieSceneAkAudioRTPCTrack::CreateTemplateForSection(const UMovieSceneSection& InSection) const +{ +#if UE_4_26_OR_LATER + return FMovieSceneAkAudioRTPCTemplate(*CastChecked(&InSection)); +#else + return InSection.GenerateTemplate(); +#endif +} + +UMovieSceneSection* UMovieSceneAkAudioRTPCTrack::CreateNewSection() +{ + return NewObject(this, UMovieSceneAkAudioRTPCSection::StaticClass(), NAME_None, RF_Transactional); +} + +#if WITH_EDITORONLY_DATA +FText UMovieSceneAkAudioRTPCTrack::GetDisplayName() const +{ + auto AllSections = GetAllSections(); + FString DisplayName("AkAudioRTPC"); + if (AllSections.Num() > 0) + { + UMovieSceneAkAudioRTPCSection* RTPCSection = CastChecked(AllSections[0]); + DisplayName += TEXT(" - ") + RTPCSection->GetRTPCName(); + } + + if (AllSections.Num() > 1) + { + DisplayName += TEXT(" and more."); + } + + return FText::FromString(DisplayName); +} +#endif + +FName UMovieSceneAkAudioRTPCTrack::GetTrackName() const +{ + const auto Section = CastChecked(MovieSceneHelpers::FindNearestSectionAtTime(Sections, 0)); + return (Section != nullptr) ? FName(*Section->GetRTPCName()) : FName(NAME_None); +} + +bool UMovieSceneAkAudioRTPCTrack::SupportsType(TSubclassOf SectionClass) const +{ + return SectionClass == UMovieSceneAkAudioRTPCSection::StaticClass(); +} + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/ObstructionAndOcclusionService/AkComponentObstructionAndOcclusionService.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/ObstructionAndOcclusionService/AkComponentObstructionAndOcclusionService.cpp new file mode 100644 index 0000000..0e8c1a9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/ObstructionAndOcclusionService/AkComponentObstructionAndOcclusionService.cpp @@ -0,0 +1,51 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= +IAkObstructionAndOcclusionService.cpp: +=============================================================================*/ + +#include "ObstructionAndOcclusionService/AkComponentObstructionAndOcclusionService.h" +#include "ObstructionAndOcclusionService/AkObstructionAndOcclusionService.h" +#include "AkAudioDevice.h" +#include "AkComponent.h" +#include "Engine/World.h" + +void AkComponentObstructionAndOcclusionService::Init(UAkComponent* in_akComponent, float in_refreshInterval) +{ + _Init(in_akComponent->GetWorld(), in_refreshInterval); + AssociatedComponent = in_akComponent; +} + +void AkComponentObstructionAndOcclusionService::SetObstructionAndOcclusion(AkGameObjectID ListenerId, float Value) +{ + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (AkAudioDevice) + { + AkGameObjectID gameObjId = AssociatedComponent->GetAkGameObjectID(); + bool bUsingRooms = AkAudioDevice->UsingSpatialAudioRooms(AssociatedComponent->GetWorld()); + + if (bUsingRooms) + { + AkAudioDevice->SetObjectObstructionAndOcclusion(gameObjId, ListenerId, Value, 0.0f); + } + else // if (!bUsingRooms) + { + AkAudioDevice->SetObjectObstructionAndOcclusion(gameObjId, ListenerId, 0.0f, Value); + } + } +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/ObstructionAndOcclusionService/AkObstructionAndOcclusionService.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/ObstructionAndOcclusionService/AkObstructionAndOcclusionService.cpp new file mode 100644 index 0000000..d43656c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/ObstructionAndOcclusionService/AkObstructionAndOcclusionService.cpp @@ -0,0 +1,452 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= +AkObstructionAndOcclusionService.cpp: +=============================================================================*/ + +#include "ObstructionAndOcclusionService/AkObstructionAndOcclusionService.h" +#include "AkAudioDevice.h" +#include "AkComponent.h" +#include "AkSpatialAudioHelper.h" +#include "AkAcousticPortal.h" +#include "Engine/World.h" +#include "Engine/Engine.h" +#include "Components/PrimitiveComponent.h" +#include "Async/Async.h" +#include "GameFramework/PlayerController.h" +#include "GameFramework/Pawn.h" + + +#define AK_DEBUG_OCCLUSION_PRINT 0 +#if AK_DEBUG_OCCLUSION_PRINT +static int framecounter = 0; +#endif + +#define AK_DEBUG_OCCLUSION 0 +#if AK_DEBUG_OCCLUSION +#include "DrawDebugHelpers.h" +#endif + + + +FAkListenerObstructionAndOcclusion::FAkListenerObstructionAndOcclusion(float in_TargetValue, float in_CurrentValue) + : CurrentValue(in_CurrentValue) + , TargetValue(in_TargetValue) + , Rate(0.0f) +{} + +void FAkListenerObstructionAndOcclusion::SetTarget(float in_TargetValue) +{ + TargetValue = FMath::Clamp(in_TargetValue, 0.0f, 1.0f); + + const float UAkComponent_OCCLUSION_FADE_RATE = 2.0f; // from 0.0 to 1.0 in 0.5 seconds + Rate = FMath::Sign(TargetValue - CurrentValue) * UAkComponent_OCCLUSION_FADE_RATE; +} + +bool FAkListenerObstructionAndOcclusion::Update(float DeltaTime) +{ + auto OldValue = CurrentValue; + if (OldValue != TargetValue) + { + const auto NewValue = OldValue + Rate * DeltaTime; + if (OldValue > TargetValue) + CurrentValue = FMath::Clamp(NewValue, TargetValue, OldValue); + else + CurrentValue = FMath::Clamp(NewValue, OldValue, TargetValue); + + AKASSERT(CurrentValue >= 0.f && CurrentValue <= 1.f); + return true; + } + + return false; +} + +bool FAkListenerObstructionAndOcclusion::ReachedTarget() +{ + return CurrentValue == TargetValue; +} + +//===================================================================================== +// FAkListenerObstructionAndOcclusionPair +//===================================================================================== + +FAkListenerObstructionAndOcclusionPair::FAkListenerObstructionAndOcclusionPair() +{ + SourceRayCollisions.AddZeroed(NUM_BOUNDING_BOX_TRACE_POINTS); + ListenerRayCollisions.AddZeroed(NUM_BOUNDING_BOX_TRACE_POINTS); + + SourceTraceHandles.AddDefaulted(NUM_BOUNDING_BOX_TRACE_POINTS); + ListenerTraceHandles.AddDefaulted(NUM_BOUNDING_BOX_TRACE_POINTS); +} + +bool FAkListenerObstructionAndOcclusionPair::Update(float DeltaTime) +{ + if (CurrentCollisionCount != GetCollisionCount()) + { + CurrentCollisionCount = GetCollisionCount(); + const float ratio = (float)CurrentCollisionCount / NUM_BOUNDING_BOX_TRACE_POINTS; + Occ.SetTarget(ratio); + Obs.SetTarget(ratio); + } + const bool bObsChanged = Obs.Update(DeltaTime); + const bool bOccChanged = Occ.Update(DeltaTime); + return bObsChanged || bOccChanged; +} + +void FAkListenerObstructionAndOcclusionPair::Reset() +{ + for (int i = 0; i < NUM_BOUNDING_BOX_TRACE_POINTS; ++i) + { + SourceRayCollisions[i] = ListenerRayCollisions[i] = false; + } +} + +bool FAkListenerObstructionAndOcclusionPair::ReachedTarget() +{ + return Obs.ReachedTarget() && Occ.ReachedTarget(); +} + +void FAkListenerObstructionAndOcclusionPair::AsyncTraceFromSource(const FVector& SourcePosition, const FVector& EndPosition, int BoundingBoxPointIndex, ECollisionChannel CollisionChannel, UWorld* World, const FCollisionQueryParams& CollisionParams) +{ + ensure(BoundingBoxPointIndex < NUM_BOUNDING_BOX_TRACE_POINTS); + // Check that we're not stacking another async trace on top of one that hasn't completed yet. + if (!World->IsTraceHandleValid(SourceTraceHandles[BoundingBoxPointIndex], false)) + { + SourceTraceHandles[BoundingBoxPointIndex] = World->AsyncLineTraceByChannel(EAsyncTraceType::Single, SourcePosition, EndPosition, CollisionChannel, CollisionParams); + } +} +void FAkListenerObstructionAndOcclusionPair::AsyncTraceFromListener(const FVector& ListenerPosition, const FVector& EndPosition, int BoundingBoxPointIndex, ECollisionChannel CollisionChannel, UWorld* World, const FCollisionQueryParams& CollisionParams) +{ + ensure(BoundingBoxPointIndex < NUM_BOUNDING_BOX_TRACE_POINTS); + // Check that we're not stacking another async trace on top of one that hasn't completed yet. + if (!World->IsTraceHandleValid(ListenerTraceHandles[BoundingBoxPointIndex], false)) + { + ListenerTraceHandles[BoundingBoxPointIndex] = World->AsyncLineTraceByChannel(EAsyncTraceType::Single, ListenerPosition, EndPosition, CollisionChannel, CollisionParams); + } +} + +int FAkListenerObstructionAndOcclusionPair::GetCollisionCount() +{ + int CollisionCount = 0; + for (int i = 0; i < NUM_BOUNDING_BOX_TRACE_POINTS; ++i) + { + CollisionCount += (SourceRayCollisions[i] || ListenerRayCollisions[i]) ? 1 : 0; + } + return CollisionCount; +} + +void FAkListenerObstructionAndOcclusionPair::CheckTraceResults(UWorld* World) +{ + CheckListenerTraceHandles(World); + CheckSourceTraceHandles(World); +} + +void FAkListenerObstructionAndOcclusionPair::CheckListenerTraceHandles(UWorld* World) +{ + for (int BoundingBoxPointIndex = 0; BoundingBoxPointIndex < NUM_BOUNDING_BOX_TRACE_POINTS; ++BoundingBoxPointIndex) + { + if (ListenerTraceHandles[BoundingBoxPointIndex]._Data.FrameNumber != 0) + { + FTraceDatum OutData; + if (World->QueryTraceData(ListenerTraceHandles[BoundingBoxPointIndex], OutData)) + { + ListenerTraceHandles[BoundingBoxPointIndex]._Data.FrameNumber = 0; + ListenerRayCollisions[BoundingBoxPointIndex] = OutData.OutHits.Num() > 0; + } + } + } +} + +void FAkListenerObstructionAndOcclusionPair::CheckSourceTraceHandles(UWorld* World) +{ + for (int BoundingBoxPointIndex = 0; BoundingBoxPointIndex < NUM_BOUNDING_BOX_TRACE_POINTS; ++BoundingBoxPointIndex) + { + if (SourceTraceHandles[BoundingBoxPointIndex]._Data.FrameNumber != 0) + { + FTraceDatum OutData; + if (World->QueryTraceData(SourceTraceHandles[BoundingBoxPointIndex], OutData)) + { + SourceTraceHandles[BoundingBoxPointIndex]._Data.FrameNumber = 0; + SourceRayCollisions[BoundingBoxPointIndex] = OutData.OutHits.Num() > 0; + } + } + } +} + +//===================================================================================== +// AkObstructionAndOcclusionService +//===================================================================================== + +void AkObstructionAndOcclusionService::_Init(UWorld* in_world, float in_refreshInterval) +{ + if (in_refreshInterval > 0 && in_world != nullptr) + LastObstructionAndOcclusionRefresh = in_world->GetTimeSeconds() + FMath::RandRange(0.0f, in_refreshInterval); + else + LastObstructionAndOcclusionRefresh = -1; + +} + +void AkObstructionAndOcclusionService::RefreshObstructionAndOcclusion(const UAkComponentSet& in_Listeners, const FVector& SourcePosition, const AActor* Actor, AkRoomID RoomID, ECollisionChannel in_collisionChannel, const float DeltaTime, float OcclusionRefreshInterval) +{ + auto AudioDevice = FAkAudioDevice::Get(); + + // Fade the active occlusions + bool StillClearingObsOcc = false; + for (auto It = ListenerInfoMap.CreateIterator(); It; ++It) + { + AkGameObjectID Listener = It->Key; + + if (in_Listeners.Find((UAkComponent*)Listener) == nullptr) + { + It.RemoveCurrent(); + continue; + } + + FAkListenerObstructionAndOcclusionPair& ObsOccPair = It->Value; + ObsOccPair.CheckTraceResults(Actor->GetWorld()); + if (ObsOccPair.Update(DeltaTime) && AudioDevice) + { + SetObstructionAndOcclusion(Listener, ObsOccPair.Obs.CurrentValue); + } + + if (ClearingObstructionAndOcclusion) + { + StillClearingObsOcc |= !ObsOccPair.ReachedTarget(); + } + } + + if (ClearingObstructionAndOcclusion) + { + ClearingObstructionAndOcclusion = StillClearingObsOcc; + return; + } + + // Compute occlusion only when needed. + // Have to have "LastObstructionAndOcclusionRefresh == -1" because GetWorld() might return nullptr in UAkComponent's constructor, + // preventing us from initializing it to something smart. + const UWorld* CurrentWorld = Actor ? Actor->GetWorld() : nullptr; + if (CurrentWorld) + { + float CurrentTime = CurrentWorld->GetTimeSeconds(); + if (CurrentTime < LastObstructionAndOcclusionRefresh && LastObstructionAndOcclusionRefresh - CurrentTime > OcclusionRefreshInterval) + { + // Occlusion refresh interval was made shorter since the last refresh, we need to re-distribute the next random calculation + LastObstructionAndOcclusionRefresh = CurrentTime + FMath::RandRange(0.0f, OcclusionRefreshInterval); + } + + if (LastObstructionAndOcclusionRefresh == -1 || (CurrentTime - LastObstructionAndOcclusionRefresh) >= OcclusionRefreshInterval) + { + LastObstructionAndOcclusionRefresh = CurrentTime; + for (auto& Listener : in_Listeners) + { + auto& MapEntry = ListenerInfoMap.FindOrAdd(Listener->GetAkGameObjectID()); + MapEntry.Position = Listener->GetPosition(); + } + CalculateObstructionAndOcclusionValues(in_Listeners, SourcePosition, Actor, RoomID, in_collisionChannel); + } + } +} + +void AkObstructionAndOcclusionService::CalculateObstructionAndOcclusionValues(const UAkComponentSet& in_Listeners, const FVector& SourcePosition, const AActor* Actor, AkRoomID RoomID, ECollisionChannel in_collisionChannel, bool bAsync /* = true */) +{ + auto CurrentWorld = Actor->GetWorld(); + if (!CurrentWorld) + return; + + static const FName NAME_SoundOcclusion = TEXT("SoundOcclusion"); + FCollisionQueryParams CollisionParams(NAME_SoundOcclusion, true, Actor); + auto PlayerController = GEngine->GetFirstLocalPlayerController(CurrentWorld); + if (PlayerController) + CollisionParams.AddIgnoredActor(PlayerController->GetPawn()); + + for (auto& Listener : in_Listeners) + { + if (RoomID != Listener->GetSpatialAudioRoom()) + continue; + + auto MapEntry = ListenerInfoMap.Find(Listener->GetAkGameObjectID()); + if (MapEntry == nullptr) + continue; + + const FVector ListenerPosition = MapEntry->Position; + + FHitResult OutHit; + const bool bNowOccluded = CurrentWorld->LineTraceSingleByChannel(OutHit, SourcePosition, ListenerPosition, in_collisionChannel, CollisionParams); + + if (bNowOccluded) + { + FBox BoundingBox; + AActor* HitActor = AkSpatialAudioHelper::GetActorFromHitResult(OutHit); + if (HitActor) + { + BoundingBox = HitActor->GetComponentsBoundingBox(); + } + else if (OutHit.Component.IsValid()) + { + BoundingBox = OutHit.Component->Bounds.GetBox(); + } + // Translate the impact point to the bounding box of the obstacle + const FVector Points[] = + { + FVector(OutHit.ImpactPoint.X, BoundingBox.Min.Y, BoundingBox.Min.Z), + FVector(OutHit.ImpactPoint.X, BoundingBox.Min.Y, BoundingBox.Max.Z), + FVector(OutHit.ImpactPoint.X, BoundingBox.Max.Y, BoundingBox.Min.Z), + FVector(OutHit.ImpactPoint.X, BoundingBox.Max.Y, BoundingBox.Max.Z), + + FVector(BoundingBox.Min.X, OutHit.ImpactPoint.Y, BoundingBox.Min.Z), + FVector(BoundingBox.Min.X, OutHit.ImpactPoint.Y, BoundingBox.Max.Z), + FVector(BoundingBox.Max.X, OutHit.ImpactPoint.Y, BoundingBox.Min.Z), + FVector(BoundingBox.Max.X, OutHit.ImpactPoint.Y, BoundingBox.Max.Z), + + FVector(BoundingBox.Min.X, BoundingBox.Min.Y, OutHit.ImpactPoint.Z), + FVector(BoundingBox.Min.X, BoundingBox.Max.Y, OutHit.ImpactPoint.Z), + FVector(BoundingBox.Max.X, BoundingBox.Min.Y, OutHit.ImpactPoint.Z), + FVector(BoundingBox.Max.X, BoundingBox.Max.Y, OutHit.ImpactPoint.Z) + }; + + if (bAsync) + { + for (int PointIndex = 0; PointIndex < NUM_BOUNDING_BOX_TRACE_POINTS; ++PointIndex) + { + auto Point = Points[PointIndex]; + MapEntry->AsyncTraceFromListener(ListenerPosition, Point, PointIndex, in_collisionChannel, CurrentWorld, CollisionParams); + MapEntry->AsyncTraceFromSource(SourcePosition, Point, PointIndex, in_collisionChannel, CurrentWorld, CollisionParams); + } + } + else + { + // Compute the number of "second order paths" that are also obstructed. This will allow us to approximate + // "how obstructed" the source is. + int32 NumObstructedPaths = 0; + for (const auto& Point : Points) + { + if (CurrentWorld->LineTraceSingleByChannel(OutHit, ListenerPosition, Point, in_collisionChannel, CollisionParams) || + CurrentWorld->LineTraceSingleByChannel(OutHit, SourcePosition, Point, in_collisionChannel, CollisionParams)) + ++NumObstructedPaths; + } + // Modulate occlusion by blocked secondary paths. + const float ratio = (float)NumObstructedPaths / NUM_BOUNDING_BOX_TRACE_POINTS; + MapEntry->Occ.SetTarget(ratio); + MapEntry->Obs.SetTarget(ratio); + } + +#if AK_DEBUG_OCCLUSION + check(IsInGameThread()); + // Draw bounding box and "second order paths" + //UE_LOG(LogAkAudio, Log, TEXT("Target Occlusion level: %f"), ListenerOcclusionInfo[ListenerIdx].TargetValue); + FlushPersistentDebugLines(CurrentWorld); + FlushDebugStrings(CurrentWorld); + DrawDebugBox(CurrentWorld, BoundingBox.GetCenter(), BoundingBox.GetExtent(), FColor::White, false, 4); + DrawDebugPoint(CurrentWorld, ListenerPosition, 10.0f, FColor(0, 255, 0), false, 4); + DrawDebugPoint(CurrentWorld, SourcePosition, 10.0f, FColor(0, 255, 0), false, 4); + DrawDebugPoint(CurrentWorld, OutHit.ImpactPoint, 10.0f, FColor(0, 255, 0), false, 4); + + for (int32 i = 0; i < NUM_BOUNDING_BOX_TRACE_POINTS; i++) + { + DrawDebugPoint(CurrentWorld, Points[i], 10.0f, FColor(255, 255, 0), false, 4); + DrawDebugString(CurrentWorld, Points[i], FString::Printf(TEXT("%d"), i), nullptr, FColor::White, 4); + DrawDebugLine(CurrentWorld, Points[i], ListenerPosition, FColor::Cyan, false, 4); + DrawDebugLine(CurrentWorld, Points[i], SourcePosition, FColor::Cyan, false, 4); + } + FColor LineColor = FColor::MakeRedToGreenColorFromScalar(1.0f - MapEntry->Occ.TargetValue); + DrawDebugLine(CurrentWorld, ListenerPosition, SourcePosition, LineColor, false, 4); +#endif // AK_DEBUG_OCCLUSION + } + else + { + MapEntry->Occ.SetTarget(0.0f); + MapEntry->Obs.SetTarget(0.0f); + MapEntry->Reset(); + } + } +} + +void AkObstructionAndOcclusionService::SetObstructionAndOcclusion(const UAkComponentSet& in_Listeners, AkRoomID RoomID) +{ + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (!AkAudioDevice) + return; + + for (auto& Listener : in_Listeners) + { + if (RoomID != Listener->GetSpatialAudioRoom()) + continue; + + auto MapEntry = ListenerInfoMap.Find(Listener->GetAkGameObjectID()); + + if (MapEntry == nullptr) + continue; + + MapEntry->Occ.CurrentValue = MapEntry->Occ.TargetValue; + SetObstructionAndOcclusion(Listener->GetAkGameObjectID(), MapEntry->Obs.CurrentValue/*, Occlusion.CurrentValue*/); + } +} + +void AkObstructionAndOcclusionService::ClearOcclusionValues() +{ + ClearingObstructionAndOcclusion = false; + + for (auto& ListenerPack : ListenerInfoMap) + { + FAkListenerObstructionAndOcclusionPair& Pair = ListenerPack.Value; + Pair.Occ.SetTarget(0.0f); + Pair.Obs.SetTarget(0.0f); + ClearingObstructionAndOcclusion |= !Pair.ReachedTarget(); + } +} + +void AkObstructionAndOcclusionService::Tick(const UAkComponentSet& in_Listeners, const FVector& SourcePosition, const AActor* Actor, AkRoomID RoomID, ECollisionChannel in_collisionChannel, float DeltaTime, float OcclusionRefreshInterval) +{ + // Check Occlusion/Obstruction, if enabled + if (OcclusionRefreshInterval > 0.0f || ClearingObstructionAndOcclusion) + { + RefreshObstructionAndOcclusion(in_Listeners, SourcePosition, Actor, RoomID, in_collisionChannel, DeltaTime, OcclusionRefreshInterval); + } + else if (OcclusionRefreshInterval != PreviousRefreshInterval) + { + // Reset the occlusion obstruction pairs so that the occlusion is correctly recalculated. + for (auto& ListenerPack : ListenerInfoMap) + { + FAkListenerObstructionAndOcclusionPair& Pair = ListenerPack.Value; + Pair.Reset(); + } + if (OcclusionRefreshInterval <= 0.0f) + ClearOcclusionValues(); + } + PreviousRefreshInterval = OcclusionRefreshInterval; +} + +void AkObstructionAndOcclusionService::UpdateObstructionAndOcclusion(const UAkComponentSet& in_Listeners, const FVector& SourcePosition, const AActor* Actor, AkRoomID RoomID, ECollisionChannel in_collisionChannel, float OcclusionRefreshInterval) +{ + if ((OcclusionRefreshInterval > 0.f || ClearingObstructionAndOcclusion) && Actor) + { + for (auto& Listener : in_Listeners) + { + auto& MapEntry = ListenerInfoMap.FindOrAdd(Listener->GetAkGameObjectID()); + MapEntry.Position = Listener->GetPosition(); + } + CalculateObstructionAndOcclusionValues(in_Listeners, SourcePosition, Actor, RoomID, in_collisionChannel, false); + for (auto& ListenerPair : ListenerInfoMap) + { + ListenerPair.Value.Obs.CurrentValue = ListenerPair.Value.Obs.TargetValue; + ListenerPair.Value.Occ.CurrentValue = ListenerPair.Value.Occ.TargetValue; + } + SetObstructionAndOcclusion(in_Listeners, RoomID); + } +} + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/ObstructionAndOcclusionService/AkPortalObstructionAndOcclusionService.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/ObstructionAndOcclusionService/AkPortalObstructionAndOcclusionService.cpp new file mode 100644 index 0000000..abd19f4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/ObstructionAndOcclusionService/AkPortalObstructionAndOcclusionService.cpp @@ -0,0 +1,43 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= +AkObstructionAndOcclusionService.cpp: +=============================================================================*/ + +#include "ObstructionAndOcclusionService/AkPortalObstructionAndOcclusionService.h" +#include "ObstructionAndOcclusionService/AkObstructionAndOcclusionService.h" +#include "AkAudioDevice.h" +#include "AkAcousticPortal.h" +#include "Engine/World.h" +#include "Engine/Engine.h" +#include "Components/PrimitiveComponent.h" + + +void AkPortalObstructionAndOcclusionService::Init(UAkPortalComponent* in_portal, float in_refreshInterval) +{ + _Init(in_portal->GetWorld(), in_refreshInterval); + AssociatedPortal = in_portal; +} +void AkPortalObstructionAndOcclusionService::SetObstructionAndOcclusion(AkGameObjectID ListenerId, float Value) +{ + FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); + if (AkAudioDevice) + { + AkAudioDevice->SetPortalObstructionAndOcclusion(AssociatedPortal, Value, 0.0f); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatformBase.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatformBase.cpp new file mode 100644 index 0000000..7879352 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatformBase.cpp @@ -0,0 +1,42 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Platforms/AkPlatformBase.h" +#include "AkAudioDevice.h" +#include "Interfaces/IPluginManager.h" +#include "Misc/Paths.h" +#include "AkUnrealHelper.h" + +FString FAkPlatformBase::GetWwisePluginDirectory() +{ + return FPaths::ConvertRelativePathToFull(IPluginManager::Get().FindPlugin(TEXT("Wwise"))->GetBaseDir()); +} + +FString FAkPlatformBase::GetDSPPluginsDirectory(const FString& PlatformArchitecture) +{ +#ifdef AK_CONFIGURATION + auto* Configuration = AK_CONFIGURATION; +#elif UE_BUILD_SHIPPING + auto* Configuration = "Release"; +#elif UE_BUILD_DEBUG + auto* Configuration = "Debug"; +#else + auto* Configuration = "Profile"; +#endif + + return AkUnrealHelper::GetThirdPartyDirectory() / PlatformArchitecture / Configuration / "bin" / ""; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_Android/AkAndroidInitializationSettings.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_Android/AkAndroidInitializationSettings.cpp new file mode 100644 index 0000000..0822c5d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_Android/AkAndroidInitializationSettings.cpp @@ -0,0 +1,69 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Platforms/AkPlatform_Android/AkAndroidInitializationSettings.h" +#include "AkAudioDevice.h" + +#if PLATFORM_ANDROID +#include "Android/AndroidApplication.h" +#endif + + +////////////////////////////////////////////////////////////////////////// +// FAkAndroidAdvancedInitializationSettings + +void FAkAndroidAdvancedInitializationSettings::FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const +{ + Super::FillInitializationStructure(InitializationStructure); + +#if PLATFORM_ANDROID + InitializationStructure.PlatformInitSettings.eAudioAPI = static_cast(AudioAPI); + InitializationStructure.PlatformInitSettings.bRoundFrameSizeToHWSize = RoundFrameSizeToHardwareSize; +#endif +} + + +////////////////////////////////////////////////////////////////////////// +// UAkAndroidInitializationSettings + +UAkAndroidInitializationSettings::UAkAndroidInitializationSettings(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + CommonSettings.MainOutputSettings.PanningRule = EAkPanningRule::Headphones; + CommonSettings.MainOutputSettings.ChannelConfigType = EAkChannelConfigType::Standard; + CommonSettings.MainOutputSettings.ChannelMask = AK_SPEAKER_SETUP_STEREO; +} + +void UAkAndroidInitializationSettings::FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const +{ + InitializationStructure.SetupLLMAllocFunctions(); + + CommonSettings.FillInitializationStructure(InitializationStructure); + CommunicationSettings.FillInitializationStructure(InitializationStructure); + AdvancedSettings.FillInitializationStructure(InitializationStructure); + +#if PLATFORM_ANDROID + InitializationStructure.PlatformInitSettings.uSampleRate = CommonSettings.SampleRate; + InitializationStructure.PlatformInitSettings.jActivity = FAndroidApplication::GetGameActivityThis(); + +#if USE_ANDROID_JNI + // GJavaVM is defined only if USE_ANDROID_JNI=1 + extern JavaVM* GJavaVM; + InitializationStructure.PlatformInitSettings.pJavaVM = GJavaVM; +#endif // USE_ANDROID_JNI +#endif // PLATFORM_ANDROID +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_Hololens/AkHololensInitializationSettings.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_Hololens/AkHololensInitializationSettings.cpp new file mode 100644 index 0000000..da65e52 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_Hololens/AkHololensInitializationSettings.cpp @@ -0,0 +1,77 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Platforms/AkPlatform_Hololens/AkHololensInitializationSettings.h" +#include "AkAudioDevice.h" +#if PLATFORM_HOLOLENS +#include "Runtime/HeadMountedDisplay/Public/IHeadMountedDisplayModule.h" +#endif + +#include "Wwise/API/WwisePlatformAPI.h" + +////////////////////////////////////////////////////////////////////////// +// FAkHololensAdvancedInitializationSettings + +void FAkHololensAdvancedInitializationSettings::FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const +{ + Super::FillInitializationStructure(InitializationStructure); + +#if PLATFORM_HOLOLENS + auto Platform = IWwisePlatformAPI::Get(); + if (UNLIKELY(!Platform)) + { + return; + } + + if (UseHeadMountedDisplayAudioDevice && IHeadMountedDisplayModule::IsAvailable()) + { + FString AudioOutputDevice = IHeadMountedDisplayModule::Get().GetAudioOutputDevice(); + if (!AudioOutputDevice.IsEmpty()) + InitializationStructure.InitSettings.settingsMainOutput.idDevice = Platform->GetDeviceIDFromName((wchar_t*)*AudioOutputDevice); + } +#endif // PLATFORM_HOLOLENS +} + + +////////////////////////////////////////////////////////////////////////// +// UAkHololensInitializationSettings + +UAkHololensInitializationSettings::UAkHololensInitializationSettings(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void UAkHololensInitializationSettings::FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const +{ + +#ifdef AK_HOLOLENS_VS_VERSION + constexpr auto PlatformArchitecture = "UWP_ARM64_" AK_HOLOLENS_VS_VERSION; +#else + constexpr auto PlatformArchitecture = "UWP_ARM64_vc160"; +#endif + + InitializationStructure.SetPluginDllPath(PlatformArchitecture); + InitializationStructure.SetupLLMAllocFunctions(); + + CommonSettings.FillInitializationStructure(InitializationStructure); + CommunicationSettings.FillInitializationStructure(InitializationStructure); + AdvancedSettings.FillInitializationStructure(InitializationStructure); + +#if PLATFORM_HOLOLENS + InitializationStructure.PlatformInitSettings.uSampleRate = CommonSettings.SampleRate; +#endif +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_Linux/AkLinuxInitializationSettings.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_Linux/AkLinuxInitializationSettings.cpp new file mode 100644 index 0000000..1a4acea --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_Linux/AkLinuxInitializationSettings.cpp @@ -0,0 +1,46 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Platforms/AkPlatform_Linux/AkLinuxInitializationSettings.h" +#include "AkAudioDevice.h" + +////////////////////////////////////////////////////////////////////////// +// UAkLinuxInitializationSettings + +UAkLinuxInitializationSettings::UAkLinuxInitializationSettings(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void UAkLinuxInitializationSettings::FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const +{ +#if PLATFORM_64BITS + InitializationStructure.SetPluginDllPath("Linux_x64"); +#else + ensure(!"The Wwise Unreal Engine integration does not support 32-bit Linux distributions."); +#endif + + InitializationStructure.SetupLLMAllocFunctions(); + + CommonSettings.FillInitializationStructure(InitializationStructure); + CommunicationSettings.FillInitializationStructure(InitializationStructure); + AdvancedSettings.FillInitializationStructure(InitializationStructure); + +#if PLATFORM_LINUX + InitializationStructure.PlatformInitSettings.uSampleRate = CommonSettings.SampleRate; +#endif +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_Linux/AkLinuxPlatform.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_Linux/AkLinuxPlatform.cpp new file mode 100644 index 0000000..72fb497 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_Linux/AkLinuxPlatform.cpp @@ -0,0 +1,29 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#if defined(PLATFORM_LINUX) && PLATFORM_LINUX + +#include "Platforms/AkPlatform_Linux/AkLinuxPlatform.h" +#include "AkUnrealHelper.h" +#include "Misc/Paths.h" + +FString FAkLinuxPlatform::GetDSPPluginsDirectory(const FString& PlatformArchitecture) +{ + return AkUnrealHelper::GetThirdPartyDirectory() / PlatformArchitecture / "Release" / "bin" / ""; +} + +#endif \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_Mac/AkMacInitializationSettings.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_Mac/AkMacInitializationSettings.cpp new file mode 100644 index 0000000..4ff7c0b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_Mac/AkMacInitializationSettings.cpp @@ -0,0 +1,43 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Platforms/AkPlatform_Mac/AkMacInitializationSettings.h" +#include "AkAudioDevice.h" + +////////////////////////////////////////////////////////////////////////// +// UAkMacInitializationSettings + +UAkMacInitializationSettings::UAkMacInitializationSettings(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void UAkMacInitializationSettings::FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const +{ + InitializationStructure.SetPluginDllPath("Mac"); + InitializationStructure.SetupLLMAllocFunctions(); + + CommonSettings.FillInitializationStructure(InitializationStructure); + CommunicationSettings.FillInitializationStructure(InitializationStructure); + AdvancedSettings.FillInitializationStructure(InitializationStructure); + +#if PLATFORM_MAC + InitializationStructure.PlatformInitSettings.uSampleRate = CommonSettings.SampleRate; + // From FRunnableThreadMac + InitializationStructure.DeviceSettings.threadProperties.uStackSize = 4 * 1024 * 1024; +#endif +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_Windows/AkWindowsInitializationSettings.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_Windows/AkWindowsInitializationSettings.cpp new file mode 100644 index 0000000..49dbf1c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_Windows/AkWindowsInitializationSettings.cpp @@ -0,0 +1,83 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Platforms/AkPlatform_Windows/AkWindowsInitializationSettings.h" +#include "AkAudioDevice.h" +#include "Runtime/HeadMountedDisplay/Public/IHeadMountedDisplayModule.h" + +#include "Wwise/API/WwisePlatformAPI.h" + +////////////////////////////////////////////////////////////////////////// +// FAkWindowsAdvancedInitializationSettings + +void FAkWindowsAdvancedInitializationSettings::FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const +{ + Super::FillInitializationStructure(InitializationStructure); + +#if PLATFORM_WINDOWS + auto Platform = IWwisePlatformAPI::Get(); + if (UNLIKELY(!Platform)) + { + return; + } + + if (UseHeadMountedDisplayAudioDevice && IHeadMountedDisplayModule::IsAvailable()) + { + FString AudioOutputDevice = IHeadMountedDisplayModule::Get().GetAudioOutputDevice(); + if (!AudioOutputDevice.IsEmpty()) + InitializationStructure.InitSettings.settingsMainOutput.idDevice = Platform->GetDeviceIDFromName((wchar_t*)*AudioOutputDevice); + } + InitializationStructure.PlatformInitSettings.uMaxSystemAudioObjects = MaxSystemAudioObjects; +#endif // PLATFORM_WINDOWS +} + + +////////////////////////////////////////////////////////////////////////// +// UAkWindowsInitializationSettings + +UAkWindowsInitializationSettings::UAkWindowsInitializationSettings(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void UAkWindowsInitializationSettings::FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const +{ +#if PLATFORM_64BITS + #define AK_WINDOWS_ARCHITECTURE "x64_" +#else + #define AK_WINDOWS_ARCHITECTURE "Win32_" +#endif + +#ifdef AK_WINDOWS_VS_VERSION + constexpr auto PlatformArchitecture = AK_WINDOWS_ARCHITECTURE AK_WINDOWS_VS_VERSION; +#else + constexpr auto PlatformArchitecture = AK_WINDOWS_ARCHITECTURE "vc160"; +#endif + +#undef AK_WINDOWS_ARCHITECTURE + + InitializationStructure.SetPluginDllPath(PlatformArchitecture); + InitializationStructure.SetupLLMAllocFunctions(); + + CommonSettings.FillInitializationStructure(InitializationStructure); + CommunicationSettings.FillInitializationStructure(InitializationStructure); + AdvancedSettings.FillInitializationStructure(InitializationStructure); + +#if PLATFORM_WINDOWS + InitializationStructure.PlatformInitSettings.uSampleRate = CommonSettings.SampleRate; +#endif +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_iOS/AkIOSInitializationSettings.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_iOS/AkIOSInitializationSettings.cpp new file mode 100644 index 0000000..4aeabb4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_iOS/AkIOSInitializationSettings.cpp @@ -0,0 +1,47 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Platforms/AkPlatform_iOS/AkIOSInitializationSettings.h" +#include "AkAudioDevice.h" +#include "InitializationSettings/AkAudioSession.h" + +////////////////////////////////////////////////////////////////////////// +// UAkIOSInitializationSettings + +UAkIOSInitializationSettings::UAkIOSInitializationSettings(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + CommonSettings.MainOutputSettings.PanningRule = EAkPanningRule::Headphones; + CommonSettings.MainOutputSettings.ChannelConfigType = EAkChannelConfigType::Standard; + CommonSettings.MainOutputSettings.ChannelMask = AK_SPEAKER_SETUP_STEREO; +} + +void UAkIOSInitializationSettings::FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const +{ + InitializationStructure.SetupLLMAllocFunctions(); + + CommonSettings.FillInitializationStructure(InitializationStructure); + AudioSession.FillInitializationStructure(InitializationStructure); + CommunicationSettings.FillInitializationStructure(InitializationStructure); + AdvancedSettings.FillInitializationStructure(InitializationStructure); + +#if PLATFORM_IOS && !PLATFORM_TVOS + InitializationStructure.PlatformInitSettings.uSampleRate = CommonSettings.SampleRate; + // From FRunnableThreadApple + InitializationStructure.DeviceSettings.threadProperties.uStackSize = 256 * 1024; +#endif +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_tvOS/AkTVOSInitializationSettings.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_tvOS/AkTVOSInitializationSettings.cpp new file mode 100644 index 0000000..ddcb2d0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkPlatform_tvOS/AkTVOSInitializationSettings.cpp @@ -0,0 +1,47 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Platforms/AkPlatform_tvOS/AkTVOSInitializationSettings.h" +#include "AkAudioDevice.h" +#include "InitializationSettings/AkAudioSession.h" + +////////////////////////////////////////////////////////////////////////// +// UAkTVOSInitializationSettings + +UAkTVOSInitializationSettings::UAkTVOSInitializationSettings(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + CommonSettings.MainOutputSettings.PanningRule = EAkPanningRule::Headphones; + CommonSettings.MainOutputSettings.ChannelConfigType = EAkChannelConfigType::Standard; + CommonSettings.MainOutputSettings.ChannelMask = AK_SPEAKER_SETUP_STEREO; +} + +void UAkTVOSInitializationSettings::FillInitializationStructure(FAkInitializationStructure& InitializationStructure) const +{ + InitializationStructure.SetupLLMAllocFunctions(); + + CommonSettings.FillInitializationStructure(InitializationStructure); + AudioSession.FillInitializationStructure(InitializationStructure); + CommunicationSettings.FillInitializationStructure(InitializationStructure); + AdvancedSettings.FillInitializationStructure(InitializationStructure); + +#if PLATFORM_TVOS + InitializationStructure.PlatformInitSettings.uSampleRate = CommonSettings.SampleRate; + // From FRunnableThreadApple + InitializationStructure.DeviceSettings.threadProperties.uStackSize = 256 * 1024; +#endif +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkUEPlatform.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkUEPlatform.cpp new file mode 100644 index 0000000..da832e0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Platforms/AkUEPlatform.cpp @@ -0,0 +1,117 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Platforms/AkUEPlatform.h" +#include "AkAudioDevice.h" +#include "Interfaces/IProjectManager.h" +#include "ProjectDescriptor.h" + +#if WITH_EDITOR +#include "PlatformInfo.h" +#include "Platforms/AkPlatformInfo.h" +TMap UAkPlatformInfo::UnrealNameToPlatformInfo; + +TMap UAkPlatformInfo::UnrealTargetNameToSharedPlatformId; + +TSet AkUnrealPlatformHelper::GetAllSupportedUnrealPlatforms() +{ + TSet SupportedPlatforms; +#if UE_5_0_OR_LATER + for (const PlatformInfo::FTargetPlatformInfo* TargetPlatformInfo : PlatformInfo::GetPlatformInfoArray()) + { + auto Info = *TargetPlatformInfo; + FName PlatformInfoName = Info.Name; + FString VanillaName = Info.VanillaInfo->Name.ToString(); +#else + for (const PlatformInfo::FPlatformInfo& Info : PlatformInfo::GetPlatformInfoArray()) + { + FName PlatformInfoName = Info.PlatformInfoName; + FString VanillaName = Info.VanillaPlatformName.ToString(); +#endif + bool bIsGame = Info.PlatformType == EBuildTargetType::Game; + if (Info.IsVanilla() && bIsGame && (PlatformInfoName != TEXT("AllDesktop"))) + { + VanillaName.RemoveFromEnd(TEXT("NoEditor")); + SupportedPlatforms.Add(VanillaName); + } + } + + return SupportedPlatforms; + +} + +TSet AkUnrealPlatformHelper::GetAllSupportedUnrealPlatformsForProject() +{ + TSet SupportedPlatforms = GetAllSupportedUnrealPlatforms(); + IProjectManager& ProjectManager = IProjectManager::Get(); + auto* CurrentProject = ProjectManager.GetCurrentProject(); + if (CurrentProject && CurrentProject->TargetPlatforms.Num() > 0) + { + auto& TargetPlatforms = CurrentProject->TargetPlatforms; + TSet AvailablePlatforms; + for (const auto& TargetPlatform : TargetPlatforms) + { + FString PlatformName = TargetPlatform.ToString(); + PlatformName.RemoveFromEnd(TEXT("NoEditor")); + AvailablePlatforms.Add(PlatformName); + } + + auto Intersection = SupportedPlatforms.Intersect(AvailablePlatforms); + if (Intersection.Num() > 0) + { + SupportedPlatforms = Intersection; + } + } + + return SupportedPlatforms; +} + +TArray> AkUnrealPlatformHelper::GetAllSupportedWwisePlatforms(bool ProjectScope /* = false */) +{ + TArray> WwisePlatforms; + + TSet TemporaryWwisePlatformNames; + auto UnrealPlatforms = ProjectScope ? GetAllSupportedUnrealPlatformsForProject() : GetAllSupportedUnrealPlatforms(); + for (const auto& AvailablePlatform : UnrealPlatforms) + { + FString SettingsClassName = FString::Format(TEXT("Ak{0}InitializationSettings"), { *AvailablePlatform }); +#if UE_5_1_OR_LATER + SettingsClassName = "/Script/ AkAudio." + SettingsClassName; + if (UClass::TryFindTypeSlow(*SettingsClassName)) +#else + if (FindObject(ANY_PACKAGE, *SettingsClassName)) +#endif + { + TemporaryWwisePlatformNames.Add(AvailablePlatform); + } + } + + TemporaryWwisePlatformNames.Sort([](const FString& L, const FString& R) { return L.Compare(R) < 0; }); + + for (const auto& WwisePlatformName : TemporaryWwisePlatformNames) + { + WwisePlatforms.Add(TSharedPtr(new FString(WwisePlatformName))); + } + + return WwisePlatforms; +} + +bool AkUnrealPlatformHelper::IsEditorPlatform(FString Platform) +{ + return Platform == "Windows" || Platform == "Mac" || Platform == "Linux"; +} +#endif // WITH_EDITOR diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/StringMatchAlgos/Array2D.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/StringMatchAlgos/Array2D.h new file mode 100644 index 0000000..7cb7203 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/StringMatchAlgos/Array2D.h @@ -0,0 +1,68 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + Array2D.h: +=============================================================================*/ + +#pragma once + +template +class Array2D : public TArray +{ +public: + typedef int32 size_type; + typedef T reference; + + Array2D(size_type in_sizeX, size_type in_sizeY) + : TArray() + , m_sizeX(in_sizeX) + , m_sizeY(in_sizeY) + { + TArray::Init(0, in_sizeX * in_sizeY); + } + Array2D(size_type in_sizeX, size_type in_sizeY, T in_defaultValue) + : TArray() + , m_sizeX(in_sizeX) + , m_sizeY(in_sizeY) + { + TArray::Init(in_defaultValue, in_sizeX * in_sizeY); + } + + inline reference& operator() (size_type in_x, size_type in_y) + { + return At(in_x, in_y); + } + + inline const reference& operator() (size_type in_x, size_type in_y) const + { + return At(in_x, in_y); + } + + inline reference& At(size_type in_x, size_type in_y) + { + return TArray::GetData()[in_y*m_sizeX + in_x]; + } + + inline const reference& At(size_type in_x, size_type in_y) const + { + return TArray::GetData()[in_y*m_sizeX + in_x]; + } + + size_type m_sizeX; + size_type m_sizeY; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/StringMatchAlgos/StringMatching.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/StringMatchAlgos/StringMatching.cpp new file mode 100644 index 0000000..552510c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/StringMatchAlgos/StringMatching.cpp @@ -0,0 +1,40 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + StringMatching.cpp: +=============================================================================*/ +#include "StringMatching.h" +#include "Array2D.h" + +float LCS::GetLCSScore(const FString& a, const FString& b) +{ + StringComparer compare(a, b); + compare.lcs(); + + float score = (float)(compare.a.Len() + compare.b.Len()) / (a.Len() + b.Len()); + + float subscore; + if (compare.a.Len() < compare.b.Len()) + subscore = (float)compare.a.Len() / a.Len(); + else + subscore = (float)compare.b.Len() / b.Len(); + + score = (1.f - score) * (1.f - subscore); + + return score; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/StringMatchAlgos/StringMatching.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/StringMatchAlgos/StringMatching.h new file mode 100644 index 0000000..2de0fbb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/StringMatchAlgos/StringMatching.h @@ -0,0 +1,85 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + StringMatching.h: +=============================================================================*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Array2D.h" + +// Longest Common Substring +namespace LCS +{ + struct StringComparer + { + StringComparer(FString in_a, FString in_b) + { + a = in_a; + b = in_b; + } + + void lcs() + { + int32 m = a.Len(); + int32 n = b.Len(); + + Array2D suffixLengths(m + 1, n + 1, 0); + int32 result = 0; + int32 aIdx = 0; + int32 bIdx = 0; + + for (int32 i = 0; i <= m; i++) + { + for (int32 j = 0; j <= n; j++) + { + if (i == 0 || j == 0) + suffixLengths(i, j) = 0; + else if (a[i - 1] == b[j - 1]) + { + suffixLengths(i, j) = suffixLengths(i - 1, j - 1) + 1; + if (suffixLengths(i, j) > result) + { + result = suffixLengths(i, j); + aIdx = i; + bIdx = j; + } + } + else + suffixLengths(i, j) = 0; + } + } + + if (result > 2) + { + a.RemoveAt(aIdx - result, result); + b.RemoveAt(bIdx - result, result); + + lcs(); + } + } + + FString a; + FString b; + }; + + // Obtain the length of the longest common sub-string between two strings + // https://en.wikipedia.org/wiki/Longest_common_substring_problem + float GetLCSScore(const FString& a, const FString& b); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/WaapiPicker/SWaapiPicker.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/WaapiPicker/SWaapiPicker.cpp new file mode 100644 index 0000000..dc41d50 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/WaapiPicker/SWaapiPicker.cpp @@ -0,0 +1,1998 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ + SWaapiPicker.cpp +------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------ + includes. +------------------------------------------------------------------------------------*/ +#include "WaapiPicker/SWaapiPicker.h" +#include "WaapiPicker/SWaapiPickerRow.h" +#include "WaapiPicker/WaapiPickerViewCommands.h" +#include "AkWaapiUtils.h" +#include "AkAudioStyle.h" +#include "AkSettings.h" +#include "AkSettingsPerUser.h" +#include "AkAudioDevice.h" +#include "AkUnrealHelper.h" + +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SHyperlink.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SSearchBox.h" +#include "Widgets/Layout/SSpacer.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/Text/SInlineEditableTextBlock.h" +#include "Framework/Application/SlateApplication.h" +#include "Framework/Commands/UICommandList.h" +#include "Framework/Commands/GenericCommands.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Misc/ScopedSlowTask.h" +#include "HAL/PlatformProcess.h" +#include "Async/Async.h" + +#if WITH_EDITOR +#include "DesktopPlatformModule.h" +#include "Editor/UnrealEd/Public/EditorDirectories.h" +#include "IDesktopPlatform.h" +#endif + +/*------------------------------------------------------------------------------------ +Defines +------------------------------------------------------------------------------------*/ +#define LOCTEXT_NAMESPACE "AkAudio" + +DECLARE_CYCLE_STAT(TEXT("WaapiPicker - ConstructTree"), STAT_WaapiPickerConstructTree, STATGROUP_Audio); +DECLARE_CYCLE_STAT(TEXT("WaapiPicker - TreeExpansionChanged"), STAT_WaapiPickerTreeExpansionChanged, STATGROUP_Audio); + +/*------------------------------------------------------------------------------------ +Statics and Globals +------------------------------------------------------------------------------------*/ + +const FName SWaapiPicker::WaapiPickerTabName = FName("WaapiPicker"); +const FText SWaapiPicker::ModalWarning = LOCTEXT("WaapiModalOpened", "Wwise currently has a modal window opened. Please close it to use WAAPI functionality."); + +static inline void CallWaapiGetProjectNamePath(FString& ProjectName, FString& ProjectPath) +{ + auto waapiClient = FAkWaapiClient::Get(); + if (!waapiClient) + return; + + TSharedRef Args = MakeShared(); + { + TSharedPtr OfType = MakeShared(); + OfType->SetArrayField(WwiseWaapiHelper::OF_TYPE, TArray> { MakeShared(WwiseWaapiHelper::PROJECT) }); + Args->SetObjectField(WwiseWaapiHelper::FROM, OfType); + } + + TSharedRef Options = MakeShared(); + { + Options->SetArrayField(WwiseWaapiHelper::RETURN, TArray> + { + MakeShared(WwiseWaapiHelper::NAME), + MakeShared(WwiseWaapiHelper::FILEPATH), + }); + } +#if AK_SUPPORT_WAAPI + TSharedPtr outJsonResult; + if (waapiClient->Call(ak::wwise::core::object::get, Args, Options, outJsonResult)) + { + // Recover the information from the Json object Result and use it to get the item id. + TArray> StructJsonArray = outJsonResult->GetArrayField(WwiseWaapiHelper::RETURN); + if (StructJsonArray.Num()) + { + auto Path = StructJsonArray[0]->AsObject()->GetStringField(WwiseWaapiHelper::FILEPATH); + ProjectPath = FPaths::GetPath(Path); + ProjectName = FPaths::GetCleanFilename(Path); + } + else + { + UE_LOG(LogAkAudio, Log, TEXT("Unable to get the project name")); + } + } +#endif +} + +inline TSharedPtr SWaapiPicker::FindItemFromPath(const TSharedPtr& ParentItem, const FString& CurrentItemPath) +{ + // We get the element to create in an array and loop over it to create them. + TArray itemPathArray; + CurrentItemPath.ParseIntoArray(itemPathArray, *WwiseWaapiHelper::BACK_SLASH); + TSharedPtr PreviousItem = ParentItem; + for (int i = 1; i < itemPathArray.Num(); i++) + { + TSharedPtr ChildItem = PreviousItem->GetChild(itemPathArray[i]); + if (!ChildItem.IsValid()) + { + return TSharedPtr(NULL); + } + PreviousItem = ChildItem; + } + return PreviousItem; +} + +inline void SWaapiPicker::FindAndCreateItems(TSharedPtr CurrentItem) +{ + LastExpandedItems.Add(CurrentItem->ItemId); + FString LastPathVisited = CurrentItem->FolderPath; + LastPathVisited.RemoveFromEnd(WwiseWaapiHelper::BACK_SLASH + CurrentItem->DisplayName); + TSharedPtr RootItem = GetRootItem(CurrentItem->FolderPath); + if (CurrentItem->FolderPath == RootItem->FolderPath) + { + return; + } + else if (LastPathVisited == RootItem->FolderPath) + { + CurrentItem->Parent = RootItem->Parent.Pin(); + RootItem->AddChild(CurrentItem); + return; + } + TSharedPtr ParentItem = FindItemFromPath(RootItem, LastPathVisited); + if (ParentItem.IsValid()) + { + CurrentItem->Parent = ParentItem->Parent.Pin(); + ParentItem->AddChild(CurrentItem); + } + else + { + TSharedPtr Result; + // Request data from Wwise UI using WAAPI and use them to create a Wwise tree item, getting the informations from a specific "PATH". + if (CallWaapiGetInfoFrom(WwiseWaapiHelper::PATH, LastPathVisited, Result, {})) + { + // Recover the information from the Json object Result and use it to construct the tree item. + TSharedPtr NewRootItem = ConstructWwiseTreeItem(Result->GetArrayField(WwiseWaapiHelper::RETURN)[0]); + CurrentItem->Parent = NewRootItem; + NewRootItem->AddChild(CurrentItem); + FindAndCreateItems(NewRootItem); + } + else + { + UE_LOG(LogAkAudio, Log, TEXT("Failed to get information from path : %s"), *LastPathVisited); + } + } +} + +inline TSharedPtr SWaapiPicker::GetRootItem(const FString& InFullPath) +{ + for (int i = EWwiseItemType::Event; i <= EWwiseItemType::LastWwiseBrowserType; ++i) + { + if (InFullPath.StartsWith(RootItems[i]->FolderPath)) + { + return RootItems[i]; + } + } + + return {}; +} + +bool SWaapiPicker::CallWaapiGetInfoFrom(const FString& inFromField, const FString& inFromString, TSharedPtr& outJsonResult, const TArray& TransformFields) +{ + auto waapiClient = FAkWaapiClient::Get(); + if (!waapiClient) + return false; +#if AK_SUPPORT_WAAPI + // Construct the arguments Json object : Getting infos "from - a specific id/path" + TSharedRef Args = MakeShared(); + { + TSharedPtr from = MakeShared(); + from->SetArrayField(inFromField, TArray> { MakeShared(inFromString) }); + Args->SetObjectField(WwiseWaapiHelper::FROM, from); + + // In case we would recover the children of the object that have the id : ID or the path : PATH, then we set isGetChildren to true. + + if (TransformFields.Num()) + { + TArray> transform; + + for (auto TransformValue : TransformFields) + { + TSharedPtr insideTransform = MakeShared(); + TArray> JsonArray; + for (auto TransformStringValueArg : TransformValue.valueStringArgs) + { + JsonArray.Add(MakeShared(TransformStringValueArg)); + } + for (auto TransformNumberValueArg : TransformValue.valueNumberArgs) + { + JsonArray.Add(MakeShared(TransformNumberValueArg)); + } + insideTransform->SetArrayField(TransformValue.keyArg, JsonArray); + transform.Add(MakeShared(insideTransform)); + } + Args->SetArrayField(WwiseWaapiHelper::TRANSFORM, transform); + } + } + + // Construct the Options Json object : Getting specific infos to construct the wwise tree item "id - name - type - childrenCount - path - parent" + TSharedRef Options = MakeShared(); + Options->SetArrayField(WwiseWaapiHelper::RETURN, TArray> + { + MakeShared(WwiseWaapiHelper::ID), + MakeShared(WwiseWaapiHelper::NAME), + MakeShared(WwiseWaapiHelper::TYPE), + MakeShared(WwiseWaapiHelper::CHILDREN_COUNT), + MakeShared(WwiseWaapiHelper::PATH), + MakeShared(WwiseWaapiHelper::WORKUNIT_TYPE), + }); + + // Request data from Wwise using WAAPI + + return waapiClient->Call(ak::wwise::core::object::get, Args, Options, outJsonResult); +#endif + return false; +} + +TSharedPtr SWaapiPicker::ConstructWwiseTreeItem(const TSharedPtr& InJsonItem) +{ + return ConstructWwiseTreeItem(InJsonItem->AsObject()); +} + +TSharedPtr SWaapiPicker::ConstructWwiseTreeItem(const TSharedPtr& ItemInfoObj) +{ + static const FString ValidPaths[] = { + EWwiseItemType::FolderNames[EWwiseItemType::Event], + EWwiseItemType::FolderNames[EWwiseItemType::AuxBus], + EWwiseItemType::FolderNames[EWwiseItemType::ActorMixer], + EWwiseItemType::FolderNames[EWwiseItemType::GameParameter], + EWwiseItemType::FolderNames[EWwiseItemType::State], + EWwiseItemType::FolderNames[EWwiseItemType::Switch], + EWwiseItemType::FolderNames[EWwiseItemType::Trigger], + EWwiseItemType::FolderNames[EWwiseItemType::AcousticTexture], + EWwiseItemType::FolderNames[EWwiseItemType::EffectShareSet] + + }; + + static auto isValidPath = [](const FString& input, const auto& source) -> bool { + for (const auto& item : source) + { + if (input.StartsWith(WwiseWaapiHelper::BACK_SLASH + item)) + { + return true; + } + } + + return false; + }; + + const FString itemTypeString = ItemInfoObj->GetStringField(WwiseWaapiHelper::TYPE); + + auto itemType = EWwiseItemType::FromString(itemTypeString); + if (itemType == EWwiseItemType::None) + { + return {}; + } + + const FString itemPath = ItemInfoObj->GetStringField(WwiseWaapiHelper::PATH); + if (isValidPath(itemPath, ValidPaths)) + { + const FString itemIdString = ItemInfoObj->GetStringField(WwiseWaapiHelper::ID); + FGuid in_ItemId = FGuid::NewGuid(); + FGuid::ParseExact(itemIdString, EGuidFormats::DigitsWithHyphensInBraces, in_ItemId); + const FString itemName = ItemInfoObj->GetStringField(WwiseWaapiHelper::NAME); + + if (itemName.IsEmpty()) + { + return {}; + } + + const uint32_t ItemChildrenCount = ItemInfoObj->GetNumberField(WwiseWaapiHelper::CHILDREN_COUNT); + + if (itemType == EWwiseItemType::StandaloneWorkUnit) + { + FString WorkUnitType; + if (ItemInfoObj->TryGetStringField(WwiseWaapiHelper::WORKUNIT_TYPE, WorkUnitType) && WorkUnitType == "FOLDER") + { + itemType = EWwiseItemType::PhysicalFolder; + } + } + TSharedPtr treeItem = MakeShared(itemName, itemPath, nullptr, itemType, in_ItemId); + if ((itemType != EWwiseItemType::Event) && (itemType != EWwiseItemType::Sound)) + { + treeItem->ChildCountInWwise = ItemChildrenCount; + } + return treeItem; + } + + return {}; +} + +/*------------------------------------------------------------------------------------ +Implementation +------------------------------------------------------------------------------------*/ +SWaapiPicker::SWaapiPicker() : CommandList(MakeShared()) +{ + AllowTreeViewDelegates = true; + isPickerVisible = FAkWaapiClient::IsProjectLoaded(); +} + +void SWaapiPicker::RemoveClientCallbacks() +{ + auto waapiClient = FAkWaapiClient::Get(); + if (waapiClient == nullptr) + return; + + if (ProjectLoadedHandle.IsValid()) + { + waapiClient->OnProjectLoaded.Remove(ProjectLoadedHandle); + ProjectLoadedHandle.Reset(); + } + + if (ConnectionLostHandle.IsValid()) + { + waapiClient->OnConnectionLost.Remove(ConnectionLostHandle); + ConnectionLostHandle.Reset(); + } + + UnsubscribeWaapiCallbacks(); +} + +SWaapiPicker::~SWaapiPicker() +{ + RootItems.Empty(); + + RemoveClientCallbacks(); + + if (auto waapiClient = FAkWaapiClient::Get()) + { + waapiClient->OnClientBeginDestroy.Remove(ClientBeginDestroyHandle); + } + + StopAndDestroyAllTransports(); +} + +void SWaapiPicker::Construct(const FArguments& InArgs) +{ + OnDragDetected = InArgs._OnDragDetected; + OnSelectionChanged = InArgs._OnSelectionChanged; + OnGenerateSoundBanksClicked = InArgs._OnGenerateSoundBanksClicked; + OnRefreshClicked = InArgs._OnRefreshClicked; + OnImportWwiseAssetsClicked = InArgs._OnImportWwiseAssetsClicked; + + + CallWaapiGetProjectNamePath(ProjectName, ProjectFolder); + bRestrictContextMenu = InArgs._RestrictContextMenu; + + if (InArgs._FocusSearchBoxWhenOpened) + { + RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &SWaapiPicker::SetFocusPostConstruct)); + } + FGenericCommands::Register(); + FWaapiPickerViewCommands::Register(); + CreateWaapiPickerCommands(); + + SearchBoxFilter = MakeShared(StringFilter::FItemToStringArray::CreateSP(this, &SWaapiPicker::PopulateSearchStrings)); + SearchBoxFilter->OnChanged().AddSP(this, &SWaapiPicker::FilterUpdated); + + if (auto* settings = GetMutableDefault()) + { + settings->bRequestRefresh = false; + } + + ChildSlot + [ + SNew(SBorder) + .Padding(4) + .BorderImage(FAkAudioStyle::GetBrush("AudiokineticTools.GroupBorder")) + [ + SNew(SOverlay) + + // Picker + + SOverlay::Slot() + .VAlign(VAlign_Fill) + [ + SNew(SVerticalBox) + .Visibility(this, &SWaapiPicker::isPickerAllowed) + + // Search + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 1, 0, 3) + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .AutoWidth() + [ + InArgs._SearchContent.Widget + ] + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SAssignNew(SearchBoxPtr,SSearchBox) + .HintText( LOCTEXT( "WaapiPickerSearchHint", "Search Wwise Item" ) ) + .ToolTipText(LOCTEXT("WaapiPickerSearchTooltip", "Type here to search for a Wwise asset")) + .OnTextChanged( this, &SWaapiPicker::OnSearchBoxChanged ) + .SelectAllTextWhenFocused(false) + .DelayChangeNotificationsWhileTyping(true) + ] + ] + + // Tree title + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(3.0f) + [ + SNew(SImage) + .Image(FAkAudioStyle::GetBrush(EWwiseItemType::Project)) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0,0,3,0) + [ + SNew(STextBlock) + .Font(FAkAudioStyle::GetFontStyle("ContentBrowser.SourceTitleFont") ) + .Text( this, &SWaapiPicker::GetProjectName ) + .Visibility(InArgs._ShowTreeTitle ? EVisibility::Visible : EVisibility::Collapsed) + ] + + + SHorizontalBox::Slot() + .FillWidth(1) + [ + SNew( SSpacer ) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton) + .Text(LOCTEXT("AkPickerRefresh", "Refresh")) + .OnClicked(this, &SWaapiPicker::OnRefreshButtonClicked) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton) + .Text(LOCTEXT("AkPickerGenerateSoundData", "Generate SoundBanks...")) + .OnClicked(this, &SWaapiPicker::OnGenerateSoundBanksButtonClicked) + .Visibility(InArgs._ShowGenerateSoundBanksButton ? EVisibility::Visible : EVisibility::Collapsed) + ] + ] + + // Separator + +SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 0, 0, 1) + [ + SNew(SSeparator) + .Visibility( ( InArgs._ShowSeparator) ? EVisibility::Visible : EVisibility::Collapsed ) + ] + + // Tree + +SVerticalBox::Slot() + .FillHeight(1.f) + [ + SAssignNew(TreeViewPtr, STreeView< TSharedPtr >) + .TreeItemsSource(&RootItems) + .OnGenerateRow( this, &SWaapiPicker::GenerateRow ) + //.OnItemScrolledIntoView( this, &SPathView::TreeItemScrolledIntoView ) + .ItemHeight(18) + .SelectionMode(InArgs._SelectionMode) + .OnSelectionChanged(this, &SWaapiPicker::TreeSelectionChanged) + .OnExpansionChanged(this, &SWaapiPicker::TreeExpansionChanged) + .OnGetChildren( this, &SWaapiPicker::GetChildrenForTree ) + .OnContextMenuOpening(this, &SWaapiPicker::MakeWaapiPickerContextMenu) + .ClearSelectionOnClick(false) + ] + ] + + // Empty Picker + + SOverlay::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .AutoHeight() + [ + SNew(STextBlock) + .Visibility(this, &SWaapiPicker::isWarningVisible) + .AutoWrapText(true) + .Justification(ETextJustify::Center) + .Text(this, &SWaapiPicker::GetWarningText) + ] + + SVerticalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .AutoHeight() + [ + SNew(SHyperlink) + .Visibility(this, &SWaapiPicker::isWarningVisible) + .Text(LOCTEXT("WaapiDucumentation", "For more informaton, please Visit Waapi Documentation.")) + .ToolTipText(LOCTEXT("WaapiDucumentationTooltip", "Opens Waapi documentation in a new browser window")) + .OnNavigate_Lambda([] { FPlatformProcess::LaunchURL(*FString("https://www.audiokinetic.com/library/?source=SDK&id=waapi.html"), nullptr, nullptr); }) + ] + ] + ] + ]; + ConstructTree(); + ExpandFirstLevel(); + + auto waapiClient = FAkWaapiClient::Get(); + if (!waapiClient) + return; + + ProjectLoadedHandle = waapiClient->OnProjectLoaded.AddSP(this, &SWaapiPicker::OnProjectLoadedCallback); + + ConnectionLostHandle = waapiClient->OnConnectionLost.AddSP(this, &SWaapiPicker::OnConnectionLostCallback); + + ClientBeginDestroyHandle = waapiClient->OnClientBeginDestroy.AddSP(this, &SWaapiPicker::RemoveClientCallbacks); + + SubscribeWaapiCallbacks(); +} + + +void SWaapiPicker::OnProjectLoadedCallback() +{ + /* Construct the tree when we have the same project */ + isPickerVisible = true; + isModalActiveInWwise = false; + SubscribeWaapiCallbacks(); + CallWaapiGetProjectNamePath(ProjectName, ProjectFolder); + ConstructTree(); +} + +void SWaapiPicker::OnConnectionLostCallback() +{ + /* Empty the tree when we have different projects */ + isPickerVisible = false; + UnsubscribeWaapiCallbacks(); + ConstructTree(); +} + +EVisibility SWaapiPicker::isPickerAllowed() const +{ + return (isPickerVisible && !isModalActiveInWwise) ? EVisibility::Visible : EVisibility::Hidden; +} + +EVisibility SWaapiPicker::isWarningVisible() const +{ + return (isPickerVisible && !isModalActiveInWwise) ? EVisibility::Hidden : EVisibility::Visible; +} + +FText SWaapiPicker::GetWarningText() const +{ + const FText NotConnected = LOCTEXT("EmptyWaapiTree", "Could not establish a WAAPI connection; WAAPI picker is disabled. Please enable WAAPI in your Wwise settings, or use the Wwise Picker."); + + return isModalActiveInWwise ? ModalWarning : NotConnected; +} + +void SWaapiPicker::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) +{ + auto* waapiClient = FAkWaapiClient::Get(); + auto AkSettings = GetMutableDefault(); + bool NeedRefresh = AkSettings->bRequestRefresh; + if (isModalActiveInWwise && waapiClient) + { +#if AK_SUPPORT_WAAPI + TSharedRef Args = MakeShareable(new FJsonObject()); + TSharedRef Options = MakeShareable(new FJsonObject()); + TSharedPtr Result = MakeShareable(new FJsonObject()); + waapiClient->Call(ak::wwise::core::getInfo, Args, Options, Result, 10, true); + if (Result->GetStringField(TEXT("uri")) != TEXT("ak.wwise.locked")) + { + NeedRefresh = true; + isModalActiveInWwise = false; + } +#endif + } + + if (NeedRefresh) + { + ConstructTree(); + AkSettings->bRequestRefresh = false; + } +} + +FText SWaapiPicker::GetProjectName() const +{ + return FText::FromString(ProjectName); +} + +FReply SWaapiPicker::OnRefreshButtonClicked() +{ + ConstructTree(); + OnRefreshClicked.ExecuteIfBound(); + return FReply::Handled(); +} + +FReply SWaapiPicker::OnGenerateSoundBanksButtonClicked() +{ + OnGenerateSoundBanksClicked.ExecuteIfBound(); + return FReply::Handled(); +} + +void SWaapiPicker::ConstructTree() +{ + if (FAkWaapiClient::IsProjectLoaded()) + { + if (ConstructTreeTask.IsValid() && !ConstructTreeTask->IsComplete()) + { + if (auto AkSettings = GetMutableDefault()) + { + AkSettings->bRequestRefresh = true; + } + + return; + } + + FString CurrentFilterText = SearchBoxFilter.IsValid() ? SearchBoxFilter->GetRawFilterText().ToString() : TEXT(""); + if (!CurrentFilterText.IsEmpty()) + { + FilterUpdated(); + return; + } + + ConstructTreeTask = FFunctionGraphTask::CreateAndDispatchWhenReady([sharedThis = SharedThis(this)] + { + { + FScopeLock autoLock(&sharedThis->RootItemsLock); + sharedThis->RootItems.Empty(EWwiseItemType::LastWwiseBrowserType - EWwiseItemType::Event + 1); + } + + auto PopulateTask = FFunctionGraphTask::CreateAndDispatchWhenReady([sharedThis] + { + for (int i = EWwiseItemType::Event; i <= EWwiseItemType::LastWwiseBrowserType; ++i) + { + FGuid in_ItemId = FGuid::NewGuid(); + TSharedPtr Result; + uint32_t ItemChildrenCount = 0; + FString Path = WwiseWaapiHelper::BACK_SLASH + EWwiseItemType::FolderNames[i]; + // Request data from Wwise UI using WAAPI and use them to create a Wwise tree item, getting the informations from a specific "PATH". + if (sharedThis->CallWaapiGetInfoFrom(WwiseWaapiHelper::PATH, Path, Result, {})) + { + // Recover the information from the Json object Result and use it to get the item id. + const TSharedPtr& ItemInfoObj = Result->GetArrayField(WwiseWaapiHelper::RETURN)[0]->AsObject(); + const FString ItemIdString = ItemInfoObj->GetStringField(WwiseWaapiHelper::ID); + Path = ItemInfoObj->GetStringField(WwiseWaapiHelper::PATH); + ItemChildrenCount = ItemInfoObj->GetNumberField(WwiseWaapiHelper::CHILDREN_COUNT); + FGuid::ParseExact(ItemIdString, EGuidFormats::DigitsWithHyphensInBraces, in_ItemId); + } + else + { + UE_LOG(LogAkAudio, Log, TEXT("Failed to get information from id : %s"), *Path); + if (Result->GetStringField(TEXT("uri")) == TEXT("ak.wwise.locked")) + { + UE_LOG(LogAkAudio, Warning, TEXT("%s"), *ModalWarning.ToString()); + sharedThis->isModalActiveInWwise = true; + } + else if (auto AkSettings = GetMutableDefault()) + { + AkSettings->bRequestRefresh = true; + } + return; + } + // Create a new tree item and add it the root list. + TSharedPtr NewRootParent = MakeShared(EWwiseItemType::BrowserDisplayNames[i], Path, nullptr, EWwiseItemType::PhysicalFolder, in_ItemId); + NewRootParent->ChildCountInWwise = ItemChildrenCount; + { + FScopeLock autoLock(&sharedThis->RootItemsLock); + sharedThis->RootItems.Add(NewRootParent); + } + } + }, GET_STATID(STAT_WaapiPickerConstructTree), nullptr, ENamedThreads::AnyThread); + + FTaskGraphInterface::Get().WaitUntilTaskCompletes(PopulateTask); + + FFunctionGraphTask::CreateAndDispatchWhenReady( [sharedThis] + { + sharedThis->AllowTreeViewDelegates = true; + + sharedThis->ExpandFirstLevel(); + sharedThis->RestoreTreeExpansion(sharedThis->RootItems); + + sharedThis->TreeViewPtr->RequestTreeRefresh(); + }, GET_STATID(STAT_WaapiPickerConstructTree), nullptr, ENamedThreads::GameThread); + + }, GET_STATID(STAT_WaapiPickerConstructTree), nullptr, ENamedThreads::AnyThread); + } +} + +void SWaapiPicker::ExpandFirstLevel() +{ + // Expand root items and first-level work units. + for (int32 i = 0; i < RootItems.Num(); i++) + { + TreeViewPtr->SetItemExpansion(RootItems[i], true); + } +} + +void SWaapiPicker::ExpandParents(TSharedPtr Item) +{ + if (Item->Parent.IsValid()) + { + ExpandParents(Item->Parent.Pin()); + TreeViewPtr->SetItemExpansion(Item->Parent.Pin(), true); + } +} + +TSharedRef SWaapiPicker::GenerateRow(TSharedPtr TreeItem, const TSharedRef& OwnerTable) +{ + check(TreeItem.IsValid()); + + EVisibility RowVisibility = TreeItem->IsVisible ? EVisibility::Visible : EVisibility::Collapsed; + + TSharedPtr NewRow = SNew(STableRow< TSharedPtr >, OwnerTable) + .OnDragDetected(this, &SWaapiPicker::HandleOnDragDetected) + .Visibility(RowVisibility) + [ + SNew(SWaapiPickerRow) + .WaapiPickerItem(TreeItem) + .HighlightText(this, &SWaapiPicker::GetHighlightText) + .IsSelected(this, &SWaapiPicker::IsTreeItemSelected, TreeItem) + ]; + + TreeItem->TreeRow = NewRow; + return NewRow.ToSharedRef(); +} + +void SWaapiPicker::GetChildrenForTree(TSharedPtr< FWwiseTreeItem > TreeItem, TArray< TSharedPtr >& OutChildren) +{ + // In case the item is "unexpanded" and have children (in the Wwise tree), we need to add a default item to show the arrow that says, this item have children. + FString CurrentFilterText = SearchBoxFilter->GetRawFilterText().ToString(); + + if (!TreeItem->ChildCountInWwise) + { + // This is useful in case when the item contains elements that are being moved to an other path and the item + // has no longer children and was expanded, so we need to remove it form the expansion items list. + LastExpandedItems.Remove(TreeItem->ItemId); + } + else if (CurrentFilterText.IsEmpty()) + { + if (!LastExpandedItems.Contains(TreeItem->ItemId)) + { + TreeItem->EmptyChildren(); + TSharedPtr emptyTreeItem = MakeShared(WwiseWaapiHelper::NAME, WwiseWaapiHelper::PATH, nullptr, EWwiseItemType::PhysicalFolder, FGuid::NewGuid()); + TreeItem->AddChild(emptyTreeItem); + } + else + { + // Update the item expansion to be visible in the tree, since it is being expanded by the user. + TreeViewPtr->SetItemExpansion(TreeItem, true); + } + } + + OutChildren = TreeItem->GetChildren(); +} + +FReply SWaapiPicker::HandleOnDragDetected(const FGeometry& Geometry, const FPointerEvent& MouseEvent) +{ + // Refresh the contents + if (OnDragDetected.IsBound()) + { + return OnDragDetected.Execute(Geometry, MouseEvent); + } + + return FReply::Unhandled(); +} + +void SWaapiPicker::PopulateSearchStrings(const FString& FolderName, OUT TArray< FString >& OutSearchStrings) const +{ + OutSearchStrings.Add(FolderName); +} + +void SWaapiPicker::OnSearchBoxChanged(const FText& InSearchText) +{ + SearchBoxFilter->SetRawFilterText(InSearchText); +} + +FText SWaapiPicker::GetHighlightText() const +{ + return SearchBoxFilter->GetRawFilterText(); +} + +void SWaapiPicker::FilterUpdated() +{ + FScopedSlowTask SlowTask(2.f, LOCTEXT("AK_PopulatingPicker", "Populating Waapi Picker...")); + SlowTask.MakeDialog(); + if (RootItems.Num()) + { + ApplyFilter(); + } + TreeViewPtr->RequestTreeRefresh(); +} + +void SWaapiPicker::SetItemVisibility(TSharedPtr Item, bool IsVisible) +{ + if (!Item.IsValid()) + return; + + if (IsVisible) + { + // Propagate visibility to parents. + SetItemVisibility(Item->Parent.Pin(), IsVisible); + } + Item->IsVisible = IsVisible; + if (Item->TreeRow.IsValid()) + { + TSharedRef wid = Item->TreeRow.Pin()->AsWidget(); + wid->SetVisibility(IsVisible ? EVisibility::Visible : EVisibility::Collapsed); + } +} + +void SWaapiPicker::ApplyFilter() +{ + for (int i = EWwiseItemType::Event; i <= EWwiseItemType::LastWwiseBrowserType; ++i) + { + RootItems[i]->EmptyChildren(); + } + + static TSet LastExpandedItemsBeforFilter; + AllowTreeViewDelegates = false; + FString CurrentFilterText = SearchBoxFilter->GetRawFilterText().ToString(); + if (CurrentFilterText.IsEmpty()) + { + // Recover the last expanded items before filtering. + LastExpandedItems.Empty(); + LastExpandedItems = LastExpandedItemsBeforFilter; + LastExpandedItemsBeforFilter.Empty(); + AllowTreeViewDelegates = true; + ConstructTree(); + return; + } + + if (!LastExpandedItemsBeforFilter.Num()) + { + // We preserve the last expanded items to re-expand the tree as it was in non filtering mode. + LastExpandedItemsBeforFilter = LastExpandedItems; + LastExpandedItems.Empty(); + } + + TSharedPtr Result; + if (CallWaapiGetInfoFrom(WwiseWaapiHelper::SEARCH, CurrentFilterText, Result, + { + { WwiseWaapiHelper::WHERE , { WwiseWaapiHelper::NAMECONTAINS, CurrentFilterText }, {} }, + { WwiseWaapiHelper::RANGE, {}, { 0, 2000 * CurrentFilterText.Len() } } + })) + { + // Recover the information from the Json object Result and use it to construct the tree item. + TArray> SearchResultArray = Result->GetArrayField(WwiseWaapiHelper::RETURN); + if (SearchResultArray.Num()) + { + // The map contains each path and the correspondent object of the search result. + TMap < FString, TSharedPtr> SearchedResultTreeItem; + for (int i = 0; i < SearchResultArray.Num(); i++) + { + // Fill the map with the path-object elements. + TSharedPtr NewRootChild = ConstructWwiseTreeItem(SearchResultArray[i]); + if (NewRootChild.IsValid()) + { + FindAndCreateItems(NewRootChild); + } + } + } + } + else + { + UE_LOG(LogAkAudio, Log, TEXT("Failed to get information from item search : %s"), *CurrentFilterText); + } + + RestoreTreeExpansion(RootItems); + AllowTreeViewDelegates = true; +} + +void SWaapiPicker::RestoreTreeExpansion(const TArray< TSharedPtr >& Items) +{ + for (int i = 0; i < Items.Num(); i++) + { + if (LastExpandedItems.Contains(Items[i]->ItemId)) + { + TreeViewPtr->SetItemExpansion(Items[i], true); + } + RestoreTreeExpansion(Items[i]->GetChildren()); + } +} + +void SWaapiPicker::TreeSelectionChanged(TSharedPtr< FWwiseTreeItem > TreeItem, ESelectInfo::Type /*SelectInfo*/) +{ + if (AllowTreeViewDelegates) + { + auto& SelectedItems = GetSelectedItems(); + + LastSelectedItems.Empty(); + for (int32 ItemIdx = 0; ItemIdx < SelectedItems.Num(); ++ItemIdx) + { + auto& Item = SelectedItems[ItemIdx]; + if (Item.IsValid()) + { + LastSelectedItems.Add(Item->ItemId); + } + } + + const UAkSettingsPerUser* AkSettingsPerUser = GetDefault(); + if (AkSettingsPerUser && AkSettingsPerUser->AutoSyncSelection) + { + HandleFindWwiseItemInProjectExplorerCommandExecute(); + } + + OnSelectionChanged.ExecuteIfBound(TreeItem, ESelectInfo::OnMouseClick); + } +} + +void SWaapiPicker::TreeExpansionChanged(TSharedPtr< FWwiseTreeItem > TreeItem, bool bIsExpanded) +{ + if (!AllowTreeViewDelegates) + { + if (bIsExpanded) + TreeItem->SortChildren(); + + return; + } + + // If the item is not expanded we don't need to request the server to get any information(the children are hidden). + if (!bIsExpanded) + { + LastExpandedItems.Remove(TreeItem->ItemId); + return; + } + + LastExpandedItems.Add(TreeItem->ItemId); + + FString CurrentFilterText = SearchBoxFilter->GetRawFilterText().ToString(); + if (!CurrentFilterText.IsEmpty()) + return; + + const FString itemIdStringField = TreeItem->ItemId.ToString(EGuidFormats::DigitsWithHyphensInBraces); + + FFunctionGraphTask::CreateAndDispatchWhenReady([sharedThis = SharedThis(this), TreeItem, itemIdStringField] + { + TSharedPtr Result; + // Request data from Wwise UI using WAAPI and use them to create a Wwise tree item, getting the informations from a specific "ID". + if (!sharedThis->CallWaapiGetInfoFrom(WwiseWaapiHelper::ID, itemIdStringField, Result, { { WwiseWaapiHelper::SELECT, { WwiseWaapiHelper::CHILDREN }, {} } })) + { + UE_LOG(LogAkAudio, Log, TEXT("Failed to get information from id : %s"), *itemIdStringField); + return; + } + + FFunctionGraphTask::CreateAndDispatchWhenReady([sharedThis, Result, TreeItem] + { + // The tree view might have been destroyed between scheduling and running this task + // Recover the information from the Json object Result and use it to construct the tree item. + TArray> StructJsonArray = Result->GetArrayField(WwiseWaapiHelper::RETURN); + /** If the item have just one child and we are expanding it, this means that we need to construct the children list. + * In case the the number of children gotten form Wwise is not the same of the item, this means that there is some children added/removed, + * so we also need to construct the new list. + */ + if ((TreeItem->GetChildren().Num() == 1) || (TreeItem->GetChildren().Num() != StructJsonArray.Num())) + { + TreeItem->EmptyChildren(); + for (int i = 0; i < StructJsonArray.Num(); i++) + { + TSharedPtr NewRootChild = sharedThis->ConstructWwiseTreeItem(StructJsonArray[i]); + if (NewRootChild.IsValid()) + { + TreeItem->AddChild(NewRootChild); + } + } + + TreeItem->SortChildren(); + + sharedThis->TreeViewPtr->RequestTreeRefresh(); + } + }, GET_STATID(STAT_WaapiPickerTreeExpansionChanged), nullptr, ENamedThreads::GameThread); + }, GET_STATID(STAT_WaapiPickerTreeExpansionChanged)); +} + +bool SWaapiPicker::IsTreeItemSelected(TSharedPtr TreeItem) const +{ + return TreeViewPtr->IsItemSelected(TreeItem); +} + +TSharedPtr SWaapiPicker::MakeWaapiPickerContextMenu() +{ + const FWaapiPickerViewCommands& Commands = FWaapiPickerViewCommands::Get(); + + // Build up the menu + FMenuBuilder MenuBuilder(true, CommandList); + { + MenuBuilder.BeginSection("WaapiPickerCreate", LOCTEXT("MenuHeader", "WaapiPicker")); + { + MenuBuilder.AddMenuEntry(Commands.RequestPlayWwiseItem); + MenuBuilder.AddMenuEntry(Commands.RequestStopAllWwiseItem); + } + MenuBuilder.EndSection(); + MenuBuilder.BeginSection("WaapiPickerEdit", LOCTEXT("EditMenuHeader", "Edit")); + { + MenuBuilder.AddMenuEntry(Commands.RequestRenameWwiseItem); + MenuBuilder.AddMenuEntry(Commands.RequestDeleteWwiseItem); + } + MenuBuilder.EndSection(); + if (!bRestrictContextMenu) + { + MenuBuilder.BeginSection("WaapiPickerExplore", LOCTEXT("ExploreMenuHeader", "Explore")); + { + MenuBuilder.AddMenuEntry(Commands.RequestExploreWwiseItem); + MenuBuilder.AddMenuEntry(Commands.RequestFindInProjectExplorerWwisetem); + } + MenuBuilder.EndSection(); + } + + MenuBuilder.BeginSection("WaapiPickerRefreshAll"); + { + MenuBuilder.AddMenuEntry(Commands.RequestRefreshWaapiPicker); + } + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("WaapiPickerImport"); + { + MenuBuilder.AddMenuEntry(Commands.RequestImportWwiseItem); + } + MenuBuilder.EndSection(); + } + return MenuBuilder.MakeWidget(); +} + +void SWaapiPicker::CreateWaapiPickerCommands() +{ + const FWaapiPickerViewCommands& Commands = FWaapiPickerViewCommands::Get(); + FUICommandList& ActionList = *CommandList; + + // Action for rename a Wwise item. + ActionList.MapAction(Commands.RequestRenameWwiseItem, + FExecuteAction::CreateRaw(this, &SWaapiPicker::HandleRenameWwiseItemCommandExecute), + FCanExecuteAction::CreateRaw(this, &SWaapiPicker::HandleRenameWwiseItemCommandCanExecute)); + + // Action to play a Wwise item (event). + ActionList.MapAction(Commands.RequestPlayWwiseItem, + FExecuteAction::CreateRaw(this, &SWaapiPicker::HandlePlayWwiseItemCommandExecute), + FCanExecuteAction::CreateRaw(this, &SWaapiPicker::HandlePlayWwiseItemCommandCanExecute)); + + // Action to stop all playing Wwise item (event). + ActionList.MapAction(Commands.RequestStopAllWwiseItem, + FExecuteAction::CreateSP(this, &SWaapiPicker::StopAndDestroyAllTransports)); + + // Action for rename a Wwise item. + ActionList.MapAction(Commands.RequestDeleteWwiseItem, + FExecuteAction::CreateRaw(this, &SWaapiPicker::HandleDeleteWwiseItemCommandExecute), + FCanExecuteAction::CreateRaw(this, &SWaapiPicker::HandleDeleteWwiseItemCommandCanExecute)); + + // Explore an item in the containing folder. + ActionList.MapAction(Commands.RequestExploreWwiseItem, + FExecuteAction::CreateRaw(this, &SWaapiPicker::HandleExploreWwiseItemCommandExecute), + FCanExecuteAction::CreateRaw(this, &SWaapiPicker::HandleWwiseCommandCanExecute)); + + // Explore an item in the containing folder. + ActionList.MapAction(Commands.RequestFindInProjectExplorerWwisetem, + FExecuteAction::CreateRaw(this, &SWaapiPicker::HandleFindWwiseItemInProjectExplorerCommandExecute), + FCanExecuteAction::CreateRaw(this, &SWaapiPicker::HandleWwiseCommandCanExecute)); + + // Action for refresh the Waapi Picker. + ActionList.MapAction(Commands.RequestRefreshWaapiPicker, + FExecuteAction::CreateSP(this, &SWaapiPicker::HandleRefreshWaapiPickerCommandExecute)); + + // Action for undo last action in the Waapi Picker. + ActionList.MapAction( + FGenericCommands::Get().Undo, + FExecuteAction::CreateSP(this, &SWaapiPicker::HandleUndoWaapiPickerCommandExecute)); + + // Action for redo last action in the Waapi Picker. + ActionList.MapAction( + FGenericCommands::Get().Redo, + FExecuteAction::CreateSP(this, &SWaapiPicker::HandleRedoWaapiPickerCommandExecute)); + + // Action for importing the selected items from the Waapi Picker. + ActionList.MapAction( + Commands.RequestImportWwiseItem, + FExecuteAction::CreateSP(this, &SWaapiPicker::HandleImportWwiseItemCommandExecute)); + +} + + +bool SWaapiPicker::HandleRenameWwiseItemCommandCanExecute() const +{ + auto& SelectedItems = GetSelectedItems(); + return SelectedItems.Num() == 1 && SelectedItems[0]->IsNotOfType({ EWwiseItemType::PhysicalFolder, EWwiseItemType::StandaloneWorkUnit, EWwiseItemType::NestedWorkUnit }); +} + +void SWaapiPicker::HandleRenameWwiseItemCommandExecute() const +{ + auto& SelectedItems = GetSelectedItems(); + if (SelectedItems.Num()) + { + TSharedPtr TableRow = TreeViewPtr->WidgetFromItem(SelectedItems[0]); + // If the Wwise item is selected but not visible, we scroll it into the view. + if (!TableRow.IsValid()) + { + TreeViewPtr->RequestScrollIntoView(SelectedItems[0]); + return; + } + // Get the right Row to enter in editing mode. + TSharedPtr> > TableRowItem = StaticCastSharedPtr>>(TableRow); + if (TableRowItem.IsValid()) + { + TSharedPtr RowContent = TableRowItem->GetContent(); + TSharedPtr ItemWidget = StaticCastSharedPtr(RowContent); + if (ItemWidget.IsValid()) + { + ItemWidget->EnterEditingMode(); + } + } + } +} + +bool SWaapiPicker::HandlePlayWwiseItemCommandCanExecute() const +{ + auto& SelectedItems = GetSelectedItems(); + if (SelectedItems.Num() == 0) + return false; + + for (int32 i = 0; i < SelectedItems.Num(); ++i) + { + if (SelectedItems[i]->IsNotOfType({ EWwiseItemType::Event, EWwiseItemType::Sound, EWwiseItemType::BlendContainer, EWwiseItemType::SwitchContainer, EWwiseItemType::RandomSequenceContainer })) + return false; + } + + return true; +} + +int32 SWaapiPicker::CreateTransport(const FGuid& in_ItemId) +{ + const FString itemIdStringField = in_ItemId.ToString(EGuidFormats::DigitsWithHyphensInBraces); + TSharedPtr Result; + int32 transportID = -1; +#if AK_SUPPORT_WAAPI + if (SWaapiPickerRow::CallWaapiExecuteUri(ak::wwise::core::transport::create, { { WwiseWaapiHelper::OBJECT, itemIdStringField } }, Result)) + { + transportID = Result->GetIntegerField(WwiseWaapiHelper::TRANSPORT); + uint64 subscriptionID = SubscribeToTransportStateChanged(transportID); + ItemToTransport.Add(in_ItemId, TransportInfo(transportID, subscriptionID)); + } +#endif + + return transportID; +} + +void SWaapiPicker::DestroyTransport(const FGuid& in_itemID) +{ + auto waapiClient = FAkWaapiClient::Get(); + if (!waapiClient) + return; + + if (!ItemToTransport.Contains(in_itemID)) + return; + + TSharedRef Args = MakeShared(); + Args->SetNumberField(WwiseWaapiHelper::TRANSPORT, ItemToTransport[in_itemID].TransportID); + + TSharedPtr Result; + if (ItemToTransport[in_itemID].SubscriptionID != 0) + waapiClient->Unsubscribe(ItemToTransport[in_itemID].SubscriptionID, Result); + +#if AK_SUPPORT_WAAPI + TSharedRef Options = MakeShared(); + if (waapiClient->Call(ak::wwise::core::transport::destroy, Args, Options, Result)) + ItemToTransport.Remove(in_itemID); +#endif +} + +void SWaapiPicker::TogglePlayStop(int32 in_transportID) +{ + auto waapiClient = FAkWaapiClient::Get(); + if (!waapiClient) + { + UE_LOG(LogAkAudio, Log, TEXT("Unable to connect to localhost")); + return; + } + + TSharedRef Args = MakeShared(); + Args->SetStringField(WwiseWaapiHelper::ACTION, WwiseWaapiHelper::PLAYSTOP); + Args->SetNumberField(WwiseWaapiHelper::TRANSPORT, in_transportID); + +#if AK_SUPPORT_WAAPI + TSharedPtr Result; + TSharedRef Options = MakeShared(); + if (!waapiClient->Call(ak::wwise::core::transport::executeAction, Args, Options, Result)) + { + UE_LOG(LogAkAudio, Log, TEXT("Failed to trigger playback")); + } +#endif +} + +void SWaapiPicker::StopTransport(int32 in_transportID) +{ + auto waapiClient = FAkWaapiClient::Get(); + if (!waapiClient) + return; + + TSharedRef Args = MakeShared(); + Args->SetStringField(WwiseWaapiHelper::ACTION, WwiseWaapiHelper::STOP); + Args->SetNumberField(WwiseWaapiHelper::TRANSPORT, in_transportID); + +#if AK_SUPPORT_WAAPI + TSharedPtr Result; + TSharedRef Options = MakeShared(); + if (!waapiClient->Call(ak::wwise::core::transport::executeAction, Args, Options, Result)) + { + UE_LOG(LogAkAudio, Log, TEXT("Cannot stop event.")); + } +#endif +} + +void SWaapiPicker::HandleStateChanged(TSharedPtr in_UEJsonObject) +{ + const FString newState = in_UEJsonObject->GetStringField(WwiseWaapiHelper::STATE); + FGuid itemID; + FGuid::Parse(in_UEJsonObject->GetStringField(WwiseWaapiHelper::OBJECT), itemID); + const int32 transportID = in_UEJsonObject->GetNumberField(WwiseWaapiHelper::TRANSPORT); + if (newState == WwiseWaapiHelper::STOPPED) + { + DestroyTransport(itemID); + } + else if (newState == WwiseWaapiHelper::PLAYING && !ItemToTransport.Contains(itemID)) + { + ItemToTransport.Add(itemID, TransportInfo(transportID, 0)); + } +} + +uint64 SWaapiPicker::SubscribeToTransportStateChanged(int32 TransportID) +{ + auto WaapiClient = FAkWaapiClient::Get(); + if (!WaapiClient) + return 0; + auto WampEventCallback = WampEventCallback::CreateLambda( + [sharedThis = SharedThis(this)](uint64_t ID, TSharedPtr UEJsonObject) + { + AsyncTask(ENamedThreads::GameThread, [sharedThis, UEJsonObject] + { + sharedThis->HandleStateChanged(UEJsonObject); + }); + }); + + TSharedRef Options = MakeShared(); + Options->SetNumberField(WwiseWaapiHelper::TRANSPORT, TransportID); + + TSharedPtr OutJsonResult; + uint64 SubscriptionID = 0; +#if AK_SUPPORT_WAAPI + WaapiClient->Subscribe(ak::wwise::core::transport::stateChanged, Options, WampEventCallback, SubscriptionID, OutJsonResult); +#endif + return SubscriptionID; +} + +void SWaapiPicker::HandlePlayWwiseItemCommandExecute() +{ + auto& SelectedItems = GetSelectedItems(); + + // Loop to play all selected items. + for (int32 i = 0; i < SelectedItems.Num(); ++i) + { + const FGuid& ItemId = SelectedItems[i]->ItemId; + int32 transportID = -1; + if (ItemToTransport.Contains(ItemId)) + { + transportID = ItemToTransport[ItemId].TransportID; + } + else + { + transportID = CreateTransport(ItemId); + } + + TogglePlayStop(transportID); + } +} + +void SWaapiPicker::StopAndDestroyAllTransports() +{ + for (auto iter = ItemToTransport.CreateIterator(); iter; ++iter) + { + StopTransport(iter->Value.TransportID); + DestroyTransport(iter->Key); + } + ItemToTransport.Empty(); +} + +bool SWaapiPicker::HandleDeleteWwiseItemCommandCanExecute() const +{ + auto& SelectedItems = GetSelectedItems(); + if ((SelectedItems.Num() > 0) && + !(TreeViewPtr->IsItemSelected(RootItems[EWwiseItemType::Event]) || TreeViewPtr->IsItemSelected(RootItems[EWwiseItemType::AuxBus]) + || TreeViewPtr->IsItemSelected(RootItems[EWwiseItemType::ActorMixer]) || TreeViewPtr->IsItemSelected(RootItems[EWwiseItemType::AcousticTexture]))) + { + for (int32 i = 0; i < SelectedItems.Num(); ++i) + { + if (SelectedItems[i]->IsOfType({ EWwiseItemType::PhysicalFolder, EWwiseItemType::StandaloneWorkUnit, EWwiseItemType::NestedWorkUnit })) + return false; + } + return true; + } + return false; +} + +void SWaapiPicker::HandleDeleteWwiseItemCommandExecute() +{ +#if AK_SUPPORT_WAAPI + TSharedPtr Result; + SWaapiPickerRow::CallWaapiExecuteUri(ak::wwise::core::undo::beginGroup, {}, Result); + auto& SelectedItems = GetSelectedItems(); + for (int32 i = 0; i < SelectedItems.Num(); ++i) + { + const FString itemIdStringField = SelectedItems[i]->ItemId.ToString(EGuidFormats::DigitsWithHyphensInBraces); + SWaapiPickerRow::CallWaapiExecuteUri(ak::wwise::core::object::delete_, { { WwiseWaapiHelper::OBJECT, itemIdStringField } }, Result); + } + SWaapiPickerRow::CallWaapiExecuteUri(ak::wwise::core::undo::endGroup, { {WwiseWaapiHelper::DISPLAY_NAME, WwiseWaapiHelper::DELETE_ITEMS} }, Result); + ConstructTree(); +#endif +} + +void SWaapiPicker::HandleExploreWwiseItemCommandExecute() const +{ + auto waapiClient = FAkWaapiClient::Get(); + if (!waapiClient) + { + UE_LOG(LogAkAudio, Log, TEXT("Unable to connect to localhost")); + return; + } + + auto& SelectedItems = GetSelectedItems(); + if (SelectedItems.Num() == 0) + return; + + TSharedRef Args = MakeShared(); + { + TSharedPtr from = MakeShared(); + from->SetArrayField(WwiseWaapiHelper::PATH, TArray> { MakeShared(SelectedItems[0]->FolderPath) }); + Args->SetObjectField(WwiseWaapiHelper::FROM, from); + } + + TSharedRef Options = MakeShared(); + Options->SetArrayField(WwiseWaapiHelper::RETURN, TArray> { MakeShared(WwiseWaapiHelper::FILEPATH) }); + +#if AK_SUPPORT_WAAPI + TSharedPtr outJsonResult; + if (!waapiClient->Call(ak::wwise::core::object::get, Args, Options, outJsonResult)) + { + UE_LOG(LogAkAudio, Log, TEXT("Call Failed")); + return; + } + + auto Path = outJsonResult->GetArrayField(WwiseWaapiHelper::RETURN)[0]->AsObject()->GetStringField(WwiseWaapiHelper::FILEPATH); + FPlatformProcess::ExploreFolder(*Path); +#endif +} + +bool SWaapiPicker::HandleWwiseCommandCanExecute() const +{ + return GetSelectedItems().Num() == 1; +} + +void SWaapiPicker::HandleFindWwiseItemInProjectExplorerCommandExecute() const +{ + auto waapiClient = FAkWaapiClient::Get(); + if (!waapiClient) + { + UE_LOG(LogAkAudio, Log, TEXT("Unable to connect to localhost")); + return; + } + + auto& SelectedItems = GetSelectedItems(); + if (SelectedItems.Num() == 0) + return; + + TSharedRef Args = MakeShared(); + Args->SetStringField(WwiseWaapiHelper::COMMAND, WwiseWaapiHelper::FIND_IN_PROJECT_EXPLORER); + TArray> SelectedObjects; + for (auto selectedItem : SelectedItems) + { + SelectedObjects.Add(MakeShared(selectedItem->ItemId.ToString(EGuidFormats::DigitsWithHyphensInBraces))); + } + Args->SetArrayField(WwiseWaapiHelper::OBJECTS, SelectedObjects); + +#if AK_SUPPORT_WAAPI + TSharedPtr Result; + TSharedRef Options = MakeShared(); + if (!waapiClient->Call(ak::wwise::ui::commands::execute, Args, Options, Result)) + { + UE_LOG(LogAkAudio, Log, TEXT("Call Failed")); + } +#endif +} + +void SWaapiPicker::HandleRefreshWaapiPickerCommandExecute() +{ + ConstructTree(); +} + +void SWaapiPicker::HandleUndoWaapiPickerCommandExecute() const +{ +#if AK_SUPPORT_WAAPI + TSharedPtr Result; + SWaapiPickerRow::CallWaapiExecuteUri(ak::wwise::ui::commands::execute, { {WwiseWaapiHelper::COMMAND, WwiseWaapiHelper::UNDO} }, Result); +#endif +} + +void SWaapiPicker::HandleImportWwiseItemCommandExecute() const +{ +#if WITH_EDITOR + const FString& PackagePath = "/Game"; + + FString LastWwiseImportPath = FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_IMPORT); + + const FString& ContentFolder = AkUnrealHelper::GetContentDirectory(); + + FString FolderName; + + // If not prompting individual files, prompt the user to select a target directory. + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); + if (DesktopPlatform) + { + const FString Title = NSLOCTEXT("UnrealEd", "ChooseADirectory", "Choose A Directory").ToString(); + const bool bFolderSelected = DesktopPlatform->OpenDirectoryDialog( + FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), + Title, + LastWwiseImportPath, + FolderName + ); + + if (bFolderSelected) + { + FEditorDirectories::Get().SetLastDirectory(ELastDirectory::GENERIC_IMPORT, LastWwiseImportPath); + FPaths::MakePathRelativeTo(FolderName, *FPaths::ProjectContentDir()); + OnImportWwiseAssetsClicked.ExecuteIfBound(PackagePath / FolderName); + } + } +#endif // WITH_EDITOR +} + +void SWaapiPicker::HandleRedoWaapiPickerCommandExecute() const +{ +#if AK_SUPPORT_WAAPI + TSharedPtr Result; + SWaapiPickerRow::CallWaapiExecuteUri(ak::wwise::ui::commands::execute, { {WwiseWaapiHelper::COMMAND, WwiseWaapiHelper::REDO} }, Result); +#endif +} + +FReply SWaapiPicker::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyboardEvent) +{ + const FKey KeyPressed = InKeyboardEvent.GetKey(); + + if ((KeyPressed == EKeys::SpaceBar)) + { + // Play the wwise item. + if (HandlePlayWwiseItemCommandCanExecute()) + { + HandlePlayWwiseItemCommandExecute(); + return FReply::Handled(); + } + } + else if (KeyPressed == EKeys::F2) + { + // Rename key : Rename selected Wwise item. + if (HandleRenameWwiseItemCommandCanExecute()) + { + HandleRenameWwiseItemCommandExecute(); + return FReply::Handled(); + } + } + else if (KeyPressed == EKeys::Delete) + { + // Delete key : Delete selected Wwise item(s). + if (HandleDeleteWwiseItemCommandCanExecute()) + { + HandleDeleteWwiseItemCommandExecute(); + return FReply::Handled(); + } + } + else if (KeyPressed == EKeys::F5) + { // Populates the Waapi Picker. + HandleRefreshWaapiPickerCommandExecute(); + return FReply::Handled(); + } + else if ((KeyPressed == EKeys::Z) && InKeyboardEvent.IsControlDown()) + { + // Undo + HandleUndoWaapiPickerCommandExecute(); + return FReply::Handled(); + } + else if ((KeyPressed == EKeys::Y) && InKeyboardEvent.IsControlDown()) + { + // Redo + HandleRedoWaapiPickerCommandExecute(); + return FReply::Handled(); + } + else if (!bRestrictContextMenu && (KeyPressed == EKeys::One) && InKeyboardEvent.IsControlDown() && InKeyboardEvent.IsShiftDown()) + { + // Finds the specified object in the Project Explorer (Sync Group 1). + if (HandleWwiseCommandCanExecute()) + { + HandleFindWwiseItemInProjectExplorerCommandExecute(); + return FReply::Handled(); + } + } + return FReply::Unhandled(); +} + +const TArray> SWaapiPicker::GetSelectedItems() const +{ + return TreeViewPtr->GetSelectedItems(); +} + +const FString SWaapiPicker::GetSearchText() const +{ + return SearchBoxFilter->GetRawFilterText().ToString(); +} + +const void SWaapiPicker::SetSearchText(const FString& newText) +{ + SearchBoxPtr->SetText(FText::FromString(newText)); +} + +EActiveTimerReturnType SWaapiPicker::SetFocusPostConstruct(double InCurrentTime, float InDeltaTime) +{ + FWidgetPath WidgetToFocusPath; + FSlateApplication::Get().GeneratePathToWidgetUnchecked(SearchBoxPtr.ToSharedRef(), WidgetToFocusPath); + FSlateApplication::Get().SetKeyboardFocus(WidgetToFocusPath, EFocusCause::SetDirectly); + + return EActiveTimerReturnType::Stop; +} + +void SWaapiPicker::SubscribeWaapiCallbacks() +{ + struct SubscriptionData + { + const char* Uri; + WampEventCallback Callback; + uint64* SubscriptionId; + }; +#if AK_SUPPORT_WAAPI + const SubscriptionData Subscriptions[] = { + {ak::wwise::core::object::nameChanged, WampEventCallback::CreateSP(this, &SWaapiPicker::OnWaapiRenamed), &WaapiSubscriptionIds.Renamed}, + {ak::wwise::core::object::childAdded, WampEventCallback::CreateSP(this, &SWaapiPicker::OnWaapiChildAdded), &WaapiSubscriptionIds.ChildAdded}, + {ak::wwise::core::object::childRemoved, WampEventCallback::CreateSP(this, &SWaapiPicker::OnWaapiChildRemoved), &WaapiSubscriptionIds.ChildRemoved}, + {ak::wwise::ui::selectionChanged, WampEventCallback::CreateSP(this, &SWaapiPicker::OnWwiseSelectionChanged), &WaapiSubscriptionIds.SelectionChanged}, + }; +#endif + + auto waapiClient = FAkWaapiClient::Get(); + if (!waapiClient) + { + return; + } + + TSharedRef Options = MakeShared(); + Options->SetArrayField(WwiseWaapiHelper::RETURN, TArray> + { + MakeShared(WwiseWaapiHelper::ID), + MakeShared(WwiseWaapiHelper::NAME), + MakeShared(WwiseWaapiHelper::TYPE), + MakeShared(WwiseWaapiHelper::CHILDREN_COUNT), + MakeShared(WwiseWaapiHelper::PATH), + MakeShared(WwiseWaapiHelper::PARENT), + MakeShared(WwiseWaapiHelper::WORKUNIT_TYPE), + }); + + TSharedPtr Result; +#if AK_SUPPORT_WAAPI + for (auto& SubscriptionData : Subscriptions) + { + if (*SubscriptionData.SubscriptionId == 0) + { + waapiClient->Subscribe(SubscriptionData.Uri, + Options, + SubscriptionData.Callback, + *SubscriptionData.SubscriptionId, + Result + ); + } + } +#endif +} + +void SWaapiPicker::UnsubscribeWaapiCallbacks() +{ + auto waapiClient = FAkWaapiClient::Get(); + if (!waapiClient) + { + return; + } + + auto doUnsubscribe = [waapiClient](uint64& subscriptionId) { + if (subscriptionId > 0) + { + TSharedPtr Result; + waapiClient->Unsubscribe(subscriptionId, Result); + subscriptionId = 0; + } + }; + + doUnsubscribe(WaapiSubscriptionIds.Renamed); + doUnsubscribe(WaapiSubscriptionIds.ChildAdded); + doUnsubscribe(WaapiSubscriptionIds.ChildRemoved); + doUnsubscribe(WaapiSubscriptionIds.SelectionChanged); +} + +TSharedPtr SWaapiPicker::FindTreeItemFromJsonObject(const TSharedPtr& ObjectJson, const FString& OverrideLastPart) +{ + FString objectPath; + if (!ObjectJson->TryGetStringField(WwiseWaapiHelper::PATH, objectPath)) + { + return {}; + } + + FString stringId; + if (!ObjectJson->TryGetStringField(WwiseWaapiHelper::ID, stringId)) + { + return {}; + } + + FGuid id; + FGuid::ParseExact(stringId, EGuidFormats::DigitsWithHyphensInBraces, id); + + TArray pathParts; + objectPath.ParseIntoArray(pathParts, *WwiseWaapiHelper::BACK_SLASH); + + if (pathParts.Num() == 0) + { + return {}; + } + + if (!OverrideLastPart.IsEmpty()) + { + pathParts[pathParts.Num() - 1] = OverrideLastPart; + } + + TSharedPtr treeItem; + TArray>* children = &RootItems; + + FString folderPath; + + for (auto& part : pathParts) + { + folderPath += FString::Printf(TEXT("%s%s"), *WwiseWaapiHelper::BACK_SLASH, *part); + + bool found = false; + for (auto& item : *children) + { + if (item->ItemId == id) + { + return item; + } + + if (item->FolderPath == folderPath) + { + treeItem = item; + children = treeItem->GetChildrenMutable(); + found = true; + } + } + + if (!found) + { + return {}; + } + } + + if (treeItem.IsValid() && treeItem->ItemId != id) + { + return {}; + } + + return treeItem; +} + +void SWaapiPicker::OnWaapiRenamed(uint64_t Id, TSharedPtr Response) +{ + FString oldName; + if (Response->TryGetStringField(WwiseWaapiHelper::OLD_NAME, oldName) && oldName.IsEmpty()) + { + const TSharedPtr* objectJsonPtr = nullptr; + if (!Response->TryGetObjectField(WwiseWaapiHelper::OBJECT, objectJsonPtr)) + { + return; + } + + auto& objectJson = *objectJsonPtr; + + FString stringId; + if (!objectJson->TryGetStringField(WwiseWaapiHelper::ID, stringId)) + { + return; + } + + FGuid id; + FGuid::ParseExact(stringId, EGuidFormats::DigitsWithHyphensInBraces, id); + + if (auto pendingIt = pendingTreeItems.Find(id)) + { + CreateTreeItemWaapi(*pendingIt, objectJson); + + pendingTreeItems.Remove(id); + + AsyncTask(ENamedThreads::GameThread, [sharedThis = SharedThis(this)] + { + sharedThis->TreeViewPtr->RequestTreeRefresh(); + }); + } + + return; + } + + const TSharedPtr* objectJsonPtr = nullptr; + if (Response->TryGetObjectField(WwiseWaapiHelper::OBJECT, objectJsonPtr)) + { + TSharedPtr objectJson = *objectJsonPtr; + + auto treeItem = FindTreeItemFromJsonObject(*objectJsonPtr, oldName); + + if (treeItem) + { + Response->TryGetStringField(WwiseWaapiHelper::NEW_NAME, treeItem->DisplayName); + objectJson->TryGetStringField(WwiseWaapiHelper::PATH, treeItem->FolderPath); + + if (treeItem->Parent.IsValid()) + { + treeItem->Parent.Pin()->SortChildren(); + } + + AsyncTask(ENamedThreads::GameThread, [sharedThis = SharedThis(this)] + { + sharedThis->TreeViewPtr->RequestTreeRefresh(); + }); + } + } +} + +template +void SWaapiPicker::HandleOnWaapiChildResponse(TSharedPtr Response, const ActionFunctor& Action) +{ + const TSharedPtr* parentJsonPtr = nullptr; + if (!Response->TryGetObjectField(WwiseWaapiHelper::PARENT, parentJsonPtr)) + { + return; + } + + const TSharedPtr* childJsonPtr = nullptr; + if (!Response->TryGetObjectField(WwiseWaapiHelper::CHILD, childJsonPtr)) + { + return; + } + + FString childName; + if (childJsonPtr->Get()->TryGetStringField(WwiseWaapiHelper::NAME, childName) && childName.IsEmpty()) + { + auto parentTreeItem = FindTreeItemFromJsonObject(*parentJsonPtr); + + FString childStringId; + childJsonPtr->Get()->TryGetStringField(WwiseWaapiHelper::ID, childStringId); + FGuid childId; + FGuid::ParseExact(childStringId, EGuidFormats::DigitsWithHyphensInBraces, childId); + + if (parentTreeItem && childId.IsValid()) + { + pendingTreeItems.Add(childId, parentTreeItem); + return; + } + } + + auto parentTreeItem = FindTreeItemFromJsonObject(*parentJsonPtr); + + if (parentTreeItem) + { + Action(parentTreeItem, *childJsonPtr); + + AsyncTask(ENamedThreads::GameThread, [sharedThis = SharedThis(this)] + { + sharedThis->TreeViewPtr->RequestTreeRefresh(); + }); + } +} + +void SWaapiPicker::OnWaapiChildAdded(uint64_t Id, TSharedPtr Response) +{ + HandleOnWaapiChildResponse(Response, + [sharedThis = SharedThis(this)](const TSharedPtr& parentTreeItem, const TSharedPtr& childJson) + { + sharedThis->CreateTreeItemWaapi(parentTreeItem, childJson); + }); +} + +void SWaapiPicker::CreateTreeItemWaapi(const TSharedPtr& parentTreeItem, const TSharedPtr& childJson) +{ + if (!parentTreeItem) + { + return; + } + + auto newChild = ConstructWwiseTreeItem(childJson); + if (newChild && parentTreeItem) + { + parentTreeItem->AddChild(newChild); + parentTreeItem->SortChildren(); + + ++parentTreeItem->ChildCountInWwise; + + if (parentTreeItem->TreeRow.IsValid() && parentTreeItem->TreeRow.Pin()->IsItemExpanded()) + { + LastExpandedItems.Add(parentTreeItem->ItemId); + } + } +} + +void SWaapiPicker::OnWaapiChildRemoved(uint64_t Id, TSharedPtr Response) +{ + HandleOnWaapiChildResponse(Response, + [sharedThis=SharedThis(this)](const TSharedPtr& ParentTreeItem, const TSharedPtr& ChildJson) + { + auto StringId = ChildJson->GetStringField(WwiseWaapiHelper::ID); + FGuid ParsedID; + FGuid::ParseExact(StringId, EGuidFormats::DigitsWithHyphensInBraces, ParsedID); + + for (int32 ChildIndex = 0; ChildIndex < ParentTreeItem->GetChildren().Num(); ++ChildIndex) + { + ParentTreeItem->RemoveChild(ParsedID); + if (ParentTreeItem->ChildCountInWwise <= 0) + { + sharedThis->LastExpandedItems.Remove(ParentTreeItem->ItemId); + } + } + }); +} + +TSharedPtr SWaapiPicker::FindOrConstructTreeItemFromJsonObject(const TSharedPtr& ObjectJson) +{ + FString objectPath; + if (!ObjectJson->TryGetStringField(WwiseWaapiHelper::PATH, objectPath)) + { + return {}; + } + + FString stringId; + if (!ObjectJson->TryGetStringField(WwiseWaapiHelper::ID, stringId)) + { + return {}; + } + + FGuid id; + FGuid::ParseExact(stringId, EGuidFormats::DigitsWithHyphensInBraces, id); + + TArray pathParts; + objectPath.ParseIntoArray(pathParts, *WwiseWaapiHelper::BACK_SLASH); + + if (pathParts.Num() == 0) + { + return {}; + } + + TSharedPtr treeItem; + TArray>* children = &RootItems; + TArray> itemsToExpand; + FString folderPath; + + for (auto& part : pathParts) + { + folderPath += FString::Printf(TEXT("%s%s"), *WwiseWaapiHelper::BACK_SLASH, *part); + + bool found = false; + for (auto& item : *children) + { + if (item->ItemId == id) + { + treeItem = item; + break; + } + + if (item->FolderPath == folderPath) + { + if (!TreeViewPtr->IsItemExpanded(item)) + { + const FString itemIdStringField = item->ItemId.ToString(EGuidFormats::DigitsWithHyphensInBraces); + + TSharedPtr Result; + // Request data from Wwise UI using WAAPI and use them to create a Wwise tree item, getting the informations from a specific "ID". + if (!CallWaapiGetInfoFrom(WwiseWaapiHelper::ID, itemIdStringField, Result, { { WwiseWaapiHelper::SELECT, { WwiseWaapiHelper::CHILDREN }, {} } })) + { + UE_LOG(LogAkAudio, Log, TEXT("Failed to get information from id : %s"), *itemIdStringField); + return {}; + } + + // The tree view might have been destroyed between scheduling and running this task + // Recover the information from the Json object Result and use it to construct the tree item. + TArray> StructJsonArray = Result->GetArrayField(WwiseWaapiHelper::RETURN); + /** If the item have just one child and we are expanding it, this means that we need to construct the children list. + * In case the the number of children gotten form Wwise is not the same of the item, this means that there is some children added/removed, + * so we also need to construct the new list. + */ + if ((item->GetChildren().Num() == 1) || (item->GetChildren().Num() != StructJsonArray.Num())) + { + item->EmptyChildren(); + for (int i = 0; i < StructJsonArray.Num(); i++) + { + TSharedPtr NewRootChild = ConstructWwiseTreeItem(StructJsonArray[i]); + if (NewRootChild.IsValid()) + { + item->AddChild(NewRootChild); + } + } + + item->SortChildren(); + } + itemsToExpand.Add(item); + } + treeItem = item; + children = treeItem->GetChildrenMutable(); + found = true; + } + } + + if (treeItem && treeItem->ItemId == id) + { + break; + } + + if (!found) + { + return {}; + } + } + + if (treeItem.IsValid() && treeItem->ItemId != id) + { + return {}; + } + + for (auto& itemToExpand : itemsToExpand) + { + LastExpandedItems.Add(itemToExpand->ItemId); + TreeViewPtr->SetItemExpansion(itemToExpand, true); + } + return treeItem; +} + +void SWaapiPicker::OnWwiseSelectionChanged(uint64_t Id, TSharedPtr Response) +{ + AsyncTask(ENamedThreads::GameThread, [sharedThis = SharedThis(this), Response]() + { + const UAkSettingsPerUser* AkSettingsPerUser = GetDefault(); + if (AkSettingsPerUser && AkSettingsPerUser->AutoSyncSelection) + { + const TArray>* objectsJsonArray = nullptr; + if (Response->TryGetArrayField(WwiseWaapiHelper::OBJECTS, objectsJsonArray)) + { + sharedThis->AllowTreeViewDelegates = false; + TArray> TreeItems; + for (auto JsonObject : *objectsJsonArray) + { + auto TreeItem = sharedThis->FindOrConstructTreeItemFromJsonObject(JsonObject->AsObject()); + if (TreeItem) + { + TreeItems.Add(TreeItem); + } + } + sharedThis->TreeViewPtr->RequestTreeRefresh(); + + if (TreeItems.Num() > 0) + { + + sharedThis->TreeViewPtr->ClearSelection(); + sharedThis->TreeViewPtr->SetItemSelection(TreeItems, true); + sharedThis->TreeViewPtr->RequestScrollIntoView(TreeItems[0]); + } + sharedThis->AllowTreeViewDelegates = true; + } + } + }); +} +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/WaapiPicker/SWaapiPickerRow.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/WaapiPicker/SWaapiPickerRow.cpp new file mode 100644 index 0000000..393122e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/WaapiPicker/SWaapiPickerRow.cpp @@ -0,0 +1,276 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ + SWaapiPickerRow.cpp +------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------ + includes. +------------------------------------------------------------------------------------*/ +#include "WaapiPicker/SWaapiPickerRow.h" +#include "AkAudioDevice.h" +#include "AkWaapiUtils.h" +#include "WaapiPicker/SWaapiPicker.h" +#include "Widgets/Input/SSearchBox.h" +#include "AkAudioStyle.h" +#include "Widgets/Images/SImage.h" + +/*------------------------------------------------------------------------------------ +Defines +------------------------------------------------------------------------------------*/ +#define LOCTEXT_NAMESPACE "AkAudio" + + +bool SWaapiPickerRow::CallWaapiExecuteUri(const char* inUri, const TArray& values, TSharedPtr& outJsonResult) +{ + TSharedRef args = MakeShareable(new FJsonObject()); + + for (auto value : values) + { + args->SetStringField(value.keyArg, value.valueArg); + } + + // Construct the options Json object; + TSharedRef options = MakeShareable(new FJsonObject()); + + // Connect to Wwise Authoring on localhost. + FAkWaapiClient* waapiClient = FAkWaapiClient::Get(); + if (waapiClient) + { + // Request infos/changes in Waapi Picker using WAAPI + if (waapiClient->Call(inUri, args, options, outJsonResult)) + { + return true; + } + else + { + UE_LOG(LogAkAudio, Log, TEXT("Call Failed")); + } + } + else + { + UE_LOG(LogAkAudio, Log, TEXT("Unable to connect to localhost")); + } + return false; +} + +/*------------------------------------------------------------------------------------ +Implementation +------------------------------------------------------------------------------------*/ +void SWaapiPickerRow::Construct(const FArguments& InArgs) +{ + ParentWidget = InArgs._ParentWidget; + WaapiPickerItem = InArgs._WaapiPickerItem; + + bool bIsRoot = !WaapiPickerItem.Pin()->Parent.IsValid() && + ((WaapiPickerItem.Pin()->FolderPath == WwiseWaapiHelper::BACK_SLASH + EWwiseItemType::FolderNames[EWwiseItemType::Event]) || + (WaapiPickerItem.Pin()->FolderPath == WwiseWaapiHelper::BACK_SLASH + EWwiseItemType::FolderNames[EWwiseItemType::AuxBus]) || + (WaapiPickerItem.Pin()->FolderPath == WwiseWaapiHelper::BACK_SLASH + EWwiseItemType::FolderNames[EWwiseItemType::ActorMixer]) || + (WaapiPickerItem.Pin()->FolderPath == WwiseWaapiHelper::BACK_SLASH + EWwiseItemType::FolderNames[EWwiseItemType::AcousticTexture]) || + (WaapiPickerItem.Pin()->FolderPath == WwiseWaapiHelper::BACK_SLASH + EWwiseItemType::FolderNames[EWwiseItemType::EffectShareSet])); + + ChildSlot + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0, bIsRoot? 2 : 1, 2, 1) + .VAlign(VAlign_Center) + [ + // Item Icon + SNew(SImage) + .Image(FAkAudioStyle::GetBrush(WaapiPickerItem.Pin()->ItemType)) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SAssignNew(InlineRenameWidget, SInlineEditableTextBlock) + .Text(this, &SWaapiPickerRow::GetNameText) + .ToolTipText(this, &SWaapiPickerRow::GetToolTipText) + .HighlightText(InArgs._HighlightText) + .OnTextCommitted(this, &SWaapiPickerRow::HandleNameCommitted) + .OnVerifyTextChanged(this, &SWaapiPickerRow::HandleVerifyNameChanged) + .IsSelected(InArgs._IsSelected) + .IsReadOnly(this, &SWaapiPickerRow::IsWiseItemNameReadOnly) + ] + ]; + + if (InlineRenameWidget.IsValid()) + { + EnterEditingModeDelegateHandle = OnRenamedRequestEvent.AddSP(InlineRenameWidget.Get(), &SInlineEditableTextBlock::EnterEditingMode); + } +} + +void SWaapiPickerRow::EnterEditingMode() +{ + InlineRenameWidget->EnterEditingMode(); +} + +SWaapiPickerRow::~SWaapiPickerRow() +{ + if (InlineRenameWidget.IsValid()) + { + OnRenamedRequestEvent.Remove(EnterEditingModeDelegateHandle); + } +} + +void SWaapiPickerRow::HandleNameCommitted(const FText& NewText, ETextCommit::Type CommitInfo) +{ + TSharedPtr WaapiPickerItemPtr = WaapiPickerItem.Pin(); + + if (WaapiPickerItemPtr.IsValid()) + { + FText WarningMessage; + const bool bIsCommitted = (CommitInfo != ETextCommit::OnCleared); + if (!OnItemRenameCommitted(WaapiPickerItemPtr, NewText.ToString(), WarningMessage) && ParentWidget.IsValid() && bIsCommitted) + { + // Failed to rename/create a WaapiPickerItem, display a warning. + UE_LOG(LogAkAudio, Log, TEXT("Rename Failed : %s"), *WarningMessage.ToString()); + } + } +} + +bool SWaapiPickerRow::OnItemRenameCommitted(const TSharedPtr< FWwiseTreeItem >& WwiseItem, const FString& InNewItemName, FText& OutWarningMessage) +{ + if (WwiseItem.IsValid() && !InNewItemName.Equals(WwiseItem->DisplayName, ESearchCase::CaseSensitive)) + { +#if AK_SUPPORT_WAAPI + const FGuid& in_ItemId = WwiseItem->ItemId; + const FString itemIdStringField = in_ItemId.ToString(EGuidFormats::DigitsWithHyphensInBraces); + TSharedPtr getResult; + if (CallWaapiExecuteUri(ak::wwise::core::object::setName, { {WwiseWaapiHelper::OBJECT, itemIdStringField} , {WwiseWaapiHelper::VALUE, InNewItemName } }, getResult)) + { + return true; + } +#endif + } + return false; +} + +bool SWaapiPickerRow::HandleVerifyNameChanged(const FText& NewText, FText& OutErrorMessage) +{ + TSharedPtr WaapiPickerItemPtr = WaapiPickerItem.Pin(); + + if (WaapiPickerItemPtr.IsValid()) + { + return OnVerifyItemNameChanged(WaapiPickerItemPtr, NewText.ToString(), OutErrorMessage); + } + + return false; +} + +bool SWaapiPickerRow::OnVerifyItemNameChanged(const TSharedPtr< FWwiseTreeItem >& WwiseItem, const FString& InNewItemName, FText& OutErrorMessage) +{ + if (!WwiseItem.IsValid()) + { + OutErrorMessage = LOCTEXT("RenameFailed_TreeItemDeleted", "Tree item no longer exists"); + return false; + } + + if ((WwiseItem->ItemType == EWwiseItemType::PhysicalFolder) || (WwiseItem->ItemType == EWwiseItemType::StandaloneWorkUnit) || (WwiseItem->ItemType == EWwiseItemType::NestedWorkUnit)) + { + OutErrorMessage = LOCTEXT("RenameFailed_WorkUnitItem", "You can't change the name of a PhysicalFolder/WorkUnit"); + return false; + } + + if (WwiseItem->Parent == NULL) + { + OutErrorMessage = LOCTEXT("RenameFailed_RootTreeItem", "A root tree item can not be renamed"); + return false; + } + + FText TrimmedLabel = FText::TrimPrecedingAndTrailing(FText::FromString(InNewItemName)); + + if (TrimmedLabel.IsEmpty()) + { + OutErrorMessage = LOCTEXT("RenameFailed_LeftBlank", "Names cannot be left blank"); + return false; + } + + if (TrimmedLabel.ToString().Len() >= NAME_SIZE) + { + FFormatNamedArguments Arguments; + Arguments.Add(TEXT("CharCount"), NAME_SIZE); + OutErrorMessage = FText::Format(LOCTEXT("RenameFailed_TooLong", "Names must be less than {CharCount} characters long."), Arguments); + return false; + } + + // If the new name is the same as the old name, consider this to be unchanged, and accept it. + const FString& LabelString = TrimmedLabel.ToString(); + if (WwiseItem->DisplayName == LabelString) + { + return true; + } + + int32 Dummy = 0; + if (LabelString.FindChar('/', Dummy) || LabelString.FindChar('\\', Dummy)) + { + OutErrorMessage = LOCTEXT("RenameFailed_InvalidChar", "Item names cannot contain / or \\."); + return false; + } + + // Validate that this folder doesn't exist already + FName NewPath = FName(*(WwiseItem->Parent.Pin()->FolderPath)); + NewPath = FName(*(NewPath.ToString() + WwiseWaapiHelper::BACK_SLASH + LabelString)); + + + if (WwiseItem->Parent.Pin()->GetChild(InNewItemName).IsValid()) + { + OutErrorMessage = LOCTEXT("RenameFailed_AlreadyExists", "An item with this name already exists at this level"); + return false; + } + return true; +} + +bool SWaapiPickerRow::IsWiseItemNameReadOnly() const +{ + // We can't rename roots or Wise items of type "PhysicalFolder || StandaloneWorkUnit || NestedWorkUnit" + const TSharedPtr& WaapiPickerItemPtr = WaapiPickerItem.Pin(); + return ((WaapiPickerItemPtr->Parent == NULL) || (WaapiPickerItemPtr->ItemType == EWwiseItemType::PhysicalFolder) || (WaapiPickerItemPtr->ItemType == EWwiseItemType::StandaloneWorkUnit) || (WaapiPickerItemPtr->ItemType == EWwiseItemType::NestedWorkUnit)); +} + +FText SWaapiPickerRow::GetNameText() const +{ + const TSharedPtr& WaapiPickerItemPtr = WaapiPickerItem.Pin(); + + if (WaapiPickerItemPtr.IsValid()) + { + return FText::FromString(WaapiPickerItemPtr->DisplayName); + } + else + { + return FText(); + } +} + +FText SWaapiPickerRow::GetToolTipText() const +{ + const TSharedPtr& WaapiPickerItemPtr = WaapiPickerItem.Pin(); + + if (WaapiPickerItemPtr.IsValid()) + { + return FText::FromString(WaapiPickerItemPtr->FolderPath); + } + else + { + return FText(); + } +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/WaapiPicker/WwiseTreeItem.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/WaapiPicker/WwiseTreeItem.cpp new file mode 100644 index 0000000..865604e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/WaapiPicker/WwiseTreeItem.cpp @@ -0,0 +1,382 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "WaapiPicker/WwiseTreeItem.h" + +bool FWwiseTreeItem::UEAssetExists() const +{ + return Assets.Num() > 0; +} + +bool FWwiseTreeItem::WwiseBankRefExists() const +{ +#if WITH_EDITORONLY_DATA + return WwiseItemRef.IsValid() && WwiseItemRef->GUID.IsValid(); +#endif + return false; +} + +bool FWwiseTreeItem::WaapiRefExists() const +{ + return bWaapiRefExists; +} + +bool FWwiseTreeItem::IsRenamedInWwise() const +{ + if (!WwiseBankRefExists() || !WaapiRefExists()) + { + return false; + } + return DisplayName != WaapiName; +} + +bool FWwiseTreeItem::IsDeletedInWwise() const +{ + return !WaapiRefExists() && WwiseBankRefExists(); +} + +bool FWwiseTreeItem::IsNotInWwiseOrSoundBank() const +{ + return !WaapiRefExists() && !WwiseBankRefExists(); +} + +bool FWwiseTreeItem::IsNewInWwise() const +{ + return WaapiRefExists() && !WwiseBankRefExists(); +} + +bool FWwiseTreeItem::IsMovedInWwise() const +{ + return !bSameLocation && !IsRenamedInWwise(); +} + +bool FWwiseTreeItem::IsSoundBankUpToDate() const +{ + return WwiseBankRefExists() && WaapiRefExists() + && !IsRenamedInWwise() && !IsMovedInWwise(); +} + +bool FWwiseTreeItem::IsRenamedInSoundBank() const +{ + if (!WwiseBankRefExists() || !HasUniqueUAsset()) + { + return false; + } + FString NameToCompare = DisplayName; + if(IsOfType({ EWwiseItemType::Switch , EWwiseItemType::State })) + { + NameToCompare = GetSwitchAssetName(); + } + return FName(NameToCompare) != UAssetName; +} + +bool FWwiseTreeItem::IsUAssetMissing() const +{ + return WwiseBankRefExists() && !UEAssetExists(); +} + +bool FWwiseTreeItem::IsUAssetOrphaned() const +{ + return !WwiseBankRefExists() && UEAssetExists(); +} + +bool FWwiseTreeItem::IsNotInSoundBankOrUnreal() const +{ + return !WwiseBankRefExists() && !UEAssetExists(); +} + +bool FWwiseTreeItem::IsUAssetUpToDate() const +{ + return WwiseBankRefExists() && HasUniqueUAsset() && !IsRenamedInSoundBank(); +} + +bool FWwiseTreeItem::HasUniqueUAsset() const +{ + return Assets.Num() == 1; +} + +bool FWwiseTreeItem::HasMultipleUAssets() const +{ + return Assets.Num() > 1; +} + +bool FWwiseTreeItem::IsItemUpToDate() const +{ + return IsUAssetUpToDate() && IsSoundBankUpToDate(); +} + +bool FWwiseTreeItem::IsFolder() const +{ + return IsAuxBus() || IsOfType({ + EWwiseItemType::StandaloneWorkUnit, + EWwiseItemType::NestedWorkUnit, + EWwiseItemType::Folder, + EWwiseItemType::Bus, + EWwiseItemType::MotionBus, + EWwiseItemType::PhysicalFolder, + EWwiseItemType::SwitchContainer, + EWwiseItemType::SwitchGroup, + EWwiseItemType::StateGroup, + EWwiseItemType::RandomSequenceContainer + }); +} + +bool FWwiseTreeItem::IsAuxBus() const +{ + return IsOfType({ + EWwiseItemType::AuxBus + }); +} + +bool FWwiseTreeItem::ShouldDisplayInfo() const +{ + return !(IsFolder() && !IsAuxBus()); +} + +bool FWwiseTreeItem::IsRootItem() const +{ + return !Parent.Pin(); +} + +TSharedPtr FWwiseTreeItem::GetRoot() +{ + if (!Parent.IsValid()) + { + return MakeShared(*this); + } + + auto Root = Parent.Pin(); + while (Root->Parent.IsValid()) + { + Root = Root->Parent.Pin(); + } + return Root; +} + +void FWwiseTreeItem::SetWaapiRef(bool bExistsInWaapi) +{ + bWaapiRefExists = bExistsInWaapi; +} + +FString FWwiseTreeItem::GetSwitchAssetName() const +{ + if(Parent.IsValid()) + { + return Parent.Pin()->DisplayName + "-" + DisplayName; + } + return FString(); +} + +const FString FWwiseTreeItem::GetDefaultAssetName() const +{ + if (IsOfType({ EWwiseItemType::Switch , EWwiseItemType::State })) + { + return GetSwitchAssetName(); + } + return DisplayName; +} + +void FWwiseTreeItem::AddChild(TSharedPtr Child) +{ + Child->Parent = TWeakPtr(this->AsShared()); + m_Children.Add(Child); + ChildCountInWwise = m_Children.Num(); +} + +void FWwiseTreeItem::AddChildren(TArray> Children) +{ + for (auto Child : Children) + { + Child->Parent = TWeakPtr(this->AsShared()); + m_Children.Add(Child); + } + ChildCountInWwise = m_Children.Num(); +} + +void FWwiseTreeItem::EmptyChildren() +{ + m_Children.Empty(); + ChildCountInWwise = m_Children.Num(); +} + +void FWwiseTreeItem::RemoveChild(const FGuid& childGuid) +{ + m_Children.RemoveAll([childGuid](TSharedPtr child) { return child->ItemId == childGuid; }); + ChildCountInWwise = m_Children.Num(); +} + +void FWwiseTreeItem::RemoveChild(const TSharedPtr< FWwiseTreeItem> child) +{ + m_Children.Remove(child); + ChildCountInWwise = m_Children.Num(); +} + +void FWwiseTreeItem::RemoveChildren(const TArray> Children) +{ + for (auto& Child : Children) + { + m_Children.Remove(Child); + } + ChildCountInWwise = m_Children.Num(); +} + +/** Returns true if this item is a child of the specified item */ +bool FWwiseTreeItem::IsChildOf(const FWwiseTreeItem& InParent) +{ + auto CurrentParent = Parent.Pin(); + while (CurrentParent.IsValid()) + { + if (CurrentParent.Get() == &InParent) + { + return true; + } + + CurrentParent = CurrentParent->Parent.Pin(); + } + + return false; +} + +bool FWwiseTreeItem::IsBrowserType() const +{ + return IsOfType({ EWwiseItemType::Event, + EWwiseItemType::Bus, + EWwiseItemType::AuxBus, + EWwiseItemType::AcousticTexture, + EWwiseItemType::State, + EWwiseItemType::Switch, + EWwiseItemType::GameParameter, + EWwiseItemType::Trigger, + EWwiseItemType::EffectShareSet + }); +} + +bool FWwiseTreeItem::IsOfType(const TArray& Types) const +{ + for (const auto& Type : Types) + { + if (ItemType == Type) + { + return true; + } + } + + return false; +} + +bool FWwiseTreeItem::IsNotOfType(const TArray& Types) const +{ + return !IsOfType(Types); +} + +/** Returns the child item by name or NULL if the child does not exist */ +TSharedPtr FWwiseTreeItem::GetChild(const FString& InChildName) +{ + for (int32 ChildIdx = 0; ChildIdx < m_Children.Num(); ++ChildIdx) + { + if (m_Children[ChildIdx]->DisplayName == InChildName) + { + return m_Children[ChildIdx]; + } + } + + return TSharedPtr(); +} + +/** Returns the child item by name or NULL if the child does not exist */ +TSharedPtr FWwiseTreeItem::GetChild(const FGuid& InGuid, const AkUInt32 InShortId, const FString& InChildName) +{ + for (int32 ChildIdx = 0; ChildIdx < m_Children.Num(); ++ChildIdx) + { + if (m_Children[ChildIdx]->ItemId == InGuid && !IsFolder()) + { + return m_Children[ChildIdx]; + } + if (m_Children[ChildIdx]->ShortId == InShortId && InShortId > 0) + { + return m_Children[ChildIdx]; + } + if (m_Children[ChildIdx]->DisplayName == InChildName) + { + return m_Children[ChildIdx]; + } + } + + return TSharedPtr(); +} + +/** Finds the child who's path matches the one specified */ +TSharedPtr FWwiseTreeItem::FindItemRecursive(const FString& InFullPath) +{ + if (InFullPath == FolderPath) + { + return SharedThis(this); + } + + for (int32 ChildIdx = 0; ChildIdx < m_Children.Num(); ++ChildIdx) + { + if (InFullPath.StartsWith(m_Children[ChildIdx]->FolderPath)) + { + const TSharedPtr& Item = m_Children[ChildIdx]->FindItemRecursive(InFullPath); + if (Item.IsValid()) + { + return Item; + } + } + } + + return TSharedPtr(NULL); + +} + +/** Finds the child who's Guid matches the one specified */ +TSharedPtr FWwiseTreeItem::FindItemRecursive(const TSharedPtr& InItem) +{ + if(InItem->ItemType == ItemType) + { + if (InItem->ItemId == ItemId) + { + return SharedThis(this); + } + + if(InItem->ShortId == ShortId && ShortId > 0) + { + return SharedThis(this); + } + + if(InItem->DisplayName == DisplayName) + { + return SharedThis(this); + } + } + + for (int32 ChildIdx = 0; ChildIdx < m_Children.Num(); ++ChildIdx) + { + const TSharedPtr& Item = m_Children[ChildIdx]->FindItemRecursive(InItem); + if (Item.IsValid()) + { + return Item; + } + } + + return TSharedPtr(NULL); +} +/** Sort the children by name */ +void FWwiseTreeItem::SortChildren() +{ + m_Children.Sort(FCompareWwiseTreeItem()); +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Wwise/Stats/AkAudio.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Wwise/Stats/AkAudio.cpp new file mode 100644 index 0000000..597984b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/Wwise/Stats/AkAudio.cpp @@ -0,0 +1,23 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Stats/AkAudio.h" + +DEFINE_STAT(STAT_PostEventAsync); + +DEFINE_LOG_CATEGORY(LogAkAudio); +DEFINE_LOG_CATEGORY(LogWwiseMonitor); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/WwiseEventTracking.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/WwiseEventTracking.cpp new file mode 100644 index 0000000..516a350 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/WwiseEventTracking.cpp @@ -0,0 +1,400 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "WwiseEventTracking.h" +#include "AkAudioEvent.h" +#include "Wwise/WwiseExternalSourceManager.h" + +void FWwiseEventTracker::PostEventCallbackHandler(AkCallbackType in_eType, AkCallbackInfo * in_pCallbackInfo) +{ + if (in_pCallbackInfo == nullptr) + return; + + auto Tracker = (FWwiseEventTracker*)in_pCallbackInfo->pCookie; + if (Tracker == nullptr) + return; + + /* Event end */ + if (in_eType == AkCallbackType::AK_EndOfEvent) + { + const auto CBInfo = (AkEventCallbackInfo*)in_pCallbackInfo; + const auto IDToStop = CBInfo->playingID; + Tracker->RemovePlayingID(IDToStop); + Tracker->RemoveScheduledStop(IDToStop); + }/* Received close to the beginning of the event */ + else if (in_eType == AkCallbackType::AK_Duration) + { + const auto CBInfo = (AkDurationCallbackInfo*)in_pCallbackInfo; + Tracker->CurrentDurationEstimation = (CBInfo->fEstimatedDuration * Tracker->CurrentDurationProportionRemaining) / 1000.0f; + } +} + +void FWwiseEventTracker::RemoveScheduledStop(AkPlayingID InID) +{ + FScopeLock autoLock(&ScheduledStopsLock); + + for (auto PlayingID : ScheduledStops) + { + if (PlayingID == InID) + { + ScheduledStops.Remove(PlayingID); + break; + } + } +} + +void FWwiseEventTracker::RemovePlayingID(AkPlayingID InID) +{ + FScopeLock autoLock(&PlayingIDsLock); + + for (auto PlayingID : PlayingIDs) + { + if (PlayingID == InID) + { + PlayingIDs.Remove(PlayingID); + break; + } + } +} + +void FWwiseEventTracker::TryAddPlayingID(const AkPlayingID & PlayingID) +{ + if (PlayingID != AK_INVALID_PLAYING_ID) + { + FScopeLock autoLock(&PlayingIDsLock); + PlayingIDs.Add(PlayingID); + } +} + +void FWwiseEventTracker::EmptyPlayingIDs() +{ + FScopeLock autoLock(&PlayingIDsLock); + PlayingIDs.Empty(); +} + +void FWwiseEventTracker::EmptyScheduledStops() +{ + FScopeLock autoLock(&ScheduledStopsLock); + ScheduledStops.Empty(); +} + +bool FWwiseEventTracker::PlayingIDHasScheduledStop(AkPlayingID InID) +{ + FScopeLock autoLock(&ScheduledStopsLock); + + for (auto PlayingID : ScheduledStops) + { + if (PlayingID == InID) + { + return true; + } + } + + return false; +} + +void FWwiseEventTracker::AddScheduledStop(AkPlayingID InID) +{ + FScopeLock autoLock(&ScheduledStopsLock); + ScheduledStops.Add(InID); +} + +namespace WwiseEventTriggering +{ + TArray> GetPlayingIds(FWwiseEventTracker & EventTracker) + { + FScopeLock autoLock(&EventTracker.PlayingIDsLock); + return TArray> { EventTracker.PlayingIDs }; + } + + void LogDirtyPlaybackWarning() + { + UE_LOG(LogAkAudio, Warning, TEXT("Playback occurred from sequencer section with new changes. You may need to save your diry work units and re-generate your soundbanks.")); + } + + void StopAllPlayingIDs(FAkAudioDevice * AudioDevice, FWwiseEventTracker & EventTracker) + { + ensure(AudioDevice != nullptr); + if (AudioDevice) + { + for (auto PlayingID : GetPlayingIds(EventTracker)) + { + AudioDevice->StopPlayingID(PlayingID); + } + } + } + + AkPlayingID PostEventOnDummyObject(FAkAudioDevice * AudioDevice, FWwiseEventTracker & EventTracker, float CurrentTime) + { + ensure(AudioDevice != nullptr); + if (EventTracker.EventName.IsEmpty()) + { + UE_LOG(LogAkAudio, Warning, TEXT("Attempted to post an AkEvent from an empty Sequencer section.")); + return AK_INVALID_PLAYING_ID; + } + + if (AudioDevice) + { + AkPlayingID PlayingID; + if (EventTracker.Event) + { + PlayingID = EventTracker.Event->PostAmbient(nullptr, &FWwiseEventTracker::PostEventCallbackHandler, &EventTracker, + (AkCallbackType)(AK_EndOfEvent | AK_Duration), nullptr); + } + else + { + AActor* DummyActor = nullptr; + const AkUInt32 ShortID = AudioDevice->GetShortID(EventTracker.Event, EventTracker.EventName); + PlayingID = AudioDevice->PostEventOnActor(ShortID, DummyActor, + AkCallbackType::AK_EndOfEvent | AkCallbackType::AK_Duration, + &FWwiseEventTracker::PostEventCallbackHandler, &EventTracker, false, {}); + } + if (LIKELY(PlayingID != AK_INVALID_PLAYING_ID)) + { + EventTracker.TryAddPlayingID(PlayingID); + if (EventTracker.IsDirty) + LogDirtyPlaybackWarning(); + return PlayingID; + } + } + return AK_INVALID_PLAYING_ID; + } + + AkPlayingID PostEvent(UObject * Object, FAkAudioDevice * AudioDevice, FWwiseEventTracker & EventTracker, float CurrentTime) + { + ensure(AudioDevice != nullptr); + + if (EventTracker.EventName.IsEmpty()) + { + UE_LOG(LogAkAudio, Warning, TEXT("Attempted to post an AkEvent from an empty Sequencer section.")); + return AK_INVALID_PLAYING_ID; + } + + if (Object && AudioDevice) + { + auto AkComponent = Cast(Object); + + if (!IsValid(AkComponent)) + { + auto Actor = CastChecked(Object); + if (IsValid(Actor)) + { + AkComponent = AudioDevice->GetAkComponent(Actor->GetRootComponent(), FName(), NULL, EAttachLocation::KeepRelativeOffset); + } + } + + if (IsValid(AkComponent)) + { + AkPlayingID PlayingID; + if (EventTracker.Event) + { + PlayingID = EventTracker.Event->PostOnComponent(AkComponent, nullptr, &FWwiseEventTracker::PostEventCallbackHandler, &EventTracker, (AkCallbackType)(AK_EndOfEvent | AK_Duration), nullptr, AkComponent->StopWhenOwnerDestroyed); + } + else + { + const AkUInt32 ShortID = AudioDevice->GetShortID(nullptr, EventTracker.EventName); + PlayingID = AudioDevice->PostEventOnAkComponent(ShortID, AkComponent, + AkCallbackType::AK_EndOfEvent | AkCallbackType::AK_Duration, + &FWwiseEventTracker::PostEventCallbackHandler, &EventTracker); + } + EventTracker.TryAddPlayingID(PlayingID); + if (EventTracker.IsDirty) + LogDirtyPlaybackWarning(); + return PlayingID; + } + } + return AK_INVALID_PLAYING_ID; + } + + void StopEvent(FAkAudioDevice * AudioDevice, AkPlayingID InPlayingID, FWwiseEventTracker * EventTracker) + { + ensure(AudioDevice != nullptr); + if (AudioDevice) + AudioDevice->StopPlayingID(InPlayingID); + } + + void TriggerStopEvent(FAkAudioDevice * AudioDevice, FWwiseEventTracker & EventTracker, AkPlayingID PlayingID) + { + AudioDevice->StopPlayingID(PlayingID, (float)EventTracker.ScrubTailLengthMs, AkCurveInterpolation::AkCurveInterpolation_Log1); + EventTracker.AddScheduledStop(PlayingID); + } + + void ScheduleStopEventsForCurrentlyPlayingIDs(FAkAudioDevice * AudioDevice, FWwiseEventTracker & EventTracker) + { + ensure(AudioDevice != nullptr); + if (AudioDevice) + { + for (auto PlayingID : GetPlayingIds(EventTracker)) + { + if (!EventTracker.PlayingIDHasScheduledStop(PlayingID)) + { + TriggerStopEvent(AudioDevice, EventTracker, PlayingID); + } + } + } + } + + void TriggerScrubSnippetOnDummyObject(FAkAudioDevice * AudioDevice, FWwiseEventTracker & EventTracker) + { + ensure(AudioDevice != nullptr); + if (EventTracker.EventName.IsEmpty()) + { + return; + } + + if (AudioDevice) + { + AkPlayingID PlayingID; + if (EventTracker.Event) + { + PlayingID = EventTracker.Event->PostAmbient(nullptr, &FWwiseEventTracker::PostEventCallbackHandler, &EventTracker, + (AkCallbackType)(AK_EndOfEvent | AK_Duration), nullptr); + } + else + { + AActor* DummyActor = nullptr; + const AkUInt32 ShortID = AudioDevice->GetShortID(EventTracker.Event, EventTracker.EventName); + PlayingID = AudioDevice->PostEventOnActor(ShortID, DummyActor, + AkCallbackType::AK_EndOfEvent | AkCallbackType::AK_Duration, + &FWwiseEventTracker::PostEventCallbackHandler, &EventTracker, false, {}); + } + if (LIKELY(PlayingID != AK_INVALID_PLAYING_ID)) + { + EventTracker.TryAddPlayingID(PlayingID); + if (EventTracker.IsDirty) + LogDirtyPlaybackWarning(); + TriggerStopEvent(AudioDevice, EventTracker, PlayingID); + } + } + } + + void TriggerScrubSnippet(UObject * Object, FAkAudioDevice * AudioDevice, FWwiseEventTracker & EventTracker) + { + ensure(AudioDevice != nullptr); + + if (EventTracker.EventName.IsEmpty()) + { + return; + } + + if (Object && AudioDevice) + { + auto AkComponent = Cast(Object); + + if (!IsValid(AkComponent)) + { + auto Actor = CastChecked(Object); + if (IsValid(Actor)) + { + AkComponent = AudioDevice->GetAkComponent(Actor->GetRootComponent(), FName(), NULL, EAttachLocation::KeepRelativeOffset); + } + } + + if (IsValid(AkComponent)) + { + AkPlayingID PlayingID; + if (EventTracker.Event) + { + PlayingID = EventTracker.Event->PostOnComponent(AkComponent, nullptr, &FWwiseEventTracker::PostEventCallbackHandler, &EventTracker, + (AkCallbackType)(AK_EndOfEvent | AK_Duration), nullptr, AkComponent->StopWhenOwnerDestroyed); + } + else + { + const AkUInt32 ShortID = AudioDevice->GetShortID(EventTracker.Event, EventTracker.EventName); + PlayingID = AudioDevice->PostEventOnAkComponent(ShortID, AkComponent, + AkCallbackType::AK_EndOfEvent | AkCallbackType::AK_Duration, + &FWwiseEventTracker::PostEventCallbackHandler, &EventTracker); + } + if (LIKELY(PlayingID != AK_INVALID_PLAYING_ID)) + { + EventTracker.TryAddPlayingID(PlayingID); + if (EventTracker.IsDirty) + LogDirtyPlaybackWarning(); + TriggerStopEvent(AudioDevice, EventTracker, PlayingID); + } + } + } + } + + void SeekOnEvent(UObject * Object, FAkAudioDevice * AudioDevice, AkReal32 in_fPercent, FWwiseEventTracker & EventTracker, AkPlayingID InPlayingID) + { + ensure(AudioDevice != nullptr); + + if (EventTracker.EventName.IsEmpty()) + { + return; + } + + if (Object && AudioDevice) + { + auto AkComponent = Cast(Object); + if (!IsValid(AkComponent)) + { + auto Actor = CastChecked(Object); + if (IsValid(Actor)) + { + AkComponent = AudioDevice->GetAkComponent(Actor->GetRootComponent(), FName(), NULL, EAttachLocation::KeepRelativeOffset); + } + } + + if (IsValid(AkComponent)) + { + const AkUInt32 ShortID = AudioDevice->GetShortID(EventTracker.Event, EventTracker.EventName); + AudioDevice->SeekOnEvent(ShortID, AkComponent, in_fPercent, false, InPlayingID); + } + } + } + + void SeekOnEvent(UObject * Object, FAkAudioDevice * AudioDevice, AkReal32 in_fPercent, FWwiseEventTracker & EventTracker) + { + for (auto PlayingID : GetPlayingIds(EventTracker)) + { + WwiseEventTriggering::SeekOnEvent(Object, AudioDevice, in_fPercent, EventTracker, PlayingID); + } + } + + void SeekOnEventWithDummyObject(FAkAudioDevice * AudioDevice, AkReal32 ProportionalTime, FWwiseEventTracker & EventTracker, AkPlayingID InPlayingID) + { + ensure(AudioDevice != nullptr); + if (EventTracker.EventName.IsEmpty()) + { + return; + } + + if (AudioDevice) + { + if (ProportionalTime < 1.0f && ProportionalTime >= 0.0f) + { + AActor* DummyActor = nullptr; + const AkUInt32 ShortID = AudioDevice->GetShortID(EventTracker.Event, EventTracker.EventName); + AudioDevice->SeekOnEvent(ShortID, DummyActor, ProportionalTime, false, InPlayingID); + // Update the duration proportion remaining property of the event tracker, rather than updating the current duration directly here. + // This way, we ensure that the current duration is updated first by any PostEvent callback, + // before it is then multiplied by the remaining proportion. + EventTracker.CurrentDurationProportionRemaining = 1.0f - ProportionalTime; + } + } + } + + void SeekOnEventWithDummyObject(FAkAudioDevice * AudioDevice, AkReal32 ProportionalTime, FWwiseEventTracker & EventTracker) + { + for (auto PlayingID : GetPlayingIds(EventTracker)) + { + WwiseEventTriggering::SeekOnEventWithDummyObject(AudioDevice, ProportionalTime, EventTracker, PlayingID); + } + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/WwiseInitBankLoader/WwiseInitBankLoader.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/WwiseInitBankLoader/WwiseInitBankLoader.cpp new file mode 100644 index 0000000..c2e90b4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Private/WwiseInitBankLoader/WwiseInitBankLoader.cpp @@ -0,0 +1,155 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "WwiseInitBankLoader/WwiseInitBankLoader.h" + +#if WITH_EDITORONLY_DATA +#include "Platforms/AkPlatformInfo.h" +#include "Wwise/WwiseProjectDatabase.h" +#endif + +#if WITH_EDITOR +#include "AssetToolsModule.h" +#include "FileHelpers.h" +#endif + +#include "AkAudioDevice.h" +#include "AkSettings.h" +#include "AkUnrealEditorHelper.h" +#include "Interfaces/IPluginManager.h" +#include "Misc/ScopedSlowTask.h" + +#define LOCTEXT_NAMESPACE "WwiseInitBankLoader" + +FWwiseInitBankLoader* FWwiseInitBankLoader::Get() +{ + static FWwiseInitBankLoader* Instance = new FWwiseInitBankLoader; + return Instance; +} + +FWwiseInitBankLoader::FWwiseInitBankLoader() +{ +} + +#if WITH_EDITORONLY_DATA +void FWwiseInitBankLoader::UpdateInitBankInSettings() +{ + UE_LOG(LogAkAudio, VeryVerbose, TEXT("FWwiseInitBankLoader::UpdateInitBankInSettings: Updating InitBank in Settings.")); + + if (!GetInitBankAsset()) + { + auto* Settings = GetMutableDefault(); + if (UNLIKELY(!Settings)) + { + UE_LOG(LogAkAudio, Error, TEXT("UpdateInitBankInSettings: Could not retrieve AkSettings.")); + return; + } + + const FSoftObjectPath Path = Settings->DefaultAssetCreationPath / TEXT("InitBank"); + if (const auto LoadedUObject = Path.TryLoad()) + { + Settings->InitBank = Cast(LoadedUObject); + UE_CLOG(Settings->InitBank.IsValid(), LogAkAudio, Display, TEXT("UpdateInitBankInSettings: Using existing InitBank at %s"), *Settings->DefaultAssetCreationPath); + } + + if (!Settings->InitBank.IsValid()) + { + FScopedSlowTask SlowTask(0, LOCTEXT("WwiseInitBankCreating", "Creating InitBank asset...")); + UE_LOG(LogAkAudio, Display, TEXT("UpdateInitBankInSettings: Creating required InitBank at %s"), *Settings->DefaultAssetCreationPath); + + auto& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools").Get(); + Settings->InitBank = Cast(AssetToolsModule.CreateAsset(TEXT("InitBank"), Settings->DefaultAssetCreationPath, UAkInitBank::StaticClass(), nullptr)); + } + + const ELoadingPhase::Type CurrentPhase = IPluginManager::Get().GetLastCompletedLoadingPhase(); + if (CurrentPhase == ELoadingPhase::None || CurrentPhase < ELoadingPhase::PostEngineInit) + { + PostInitDelegate = FCoreDelegates::OnPostEngineInit.AddRaw(this, &FWwiseInitBankLoader::OnPostInitSavePackage); + } + else + { + OnPostInitSavePackage(); + } + + } +} +#endif + + +void FWwiseInitBankLoader::LoadInitBank() const +{ + auto* InitBankAsset = GetInitBankAsset(); + if (UNLIKELY(!InitBankAsset)) + { + UE_LOG(LogAkAudio, Warning, TEXT("LoadInitBank: InitBankAsset not initialized")); + return; + } + + UE_LOG(LogAkAudio, Verbose, TEXT("LoadInitBank: Loading Init Bank asset")); + InitBankAsset->LoadInitBank(); +} + +void FWwiseInitBankLoader::UnloadInitBank() const +{ + auto* InitBankAsset = GetInitBankAsset(); + if (UNLIKELY(!InitBankAsset)) + { + UE_LOG(LogAkAudio, Warning, TEXT("UnloadInitBank: InitBankAsset not initialized")); + return; + } + + UE_LOG(LogAkAudio, Verbose, TEXT("UnloadInitBank: Unloading init bank asset")); + InitBankAsset->UnloadInitBank(true); +} + +UAkInitBank* FWwiseInitBankLoader::GetInitBankAsset() const +{ + const auto* Settings = GetDefault(); + if (UNLIKELY(!Settings)) + { + UE_LOG(LogAkAudio, Error, TEXT("GetInitBankAsset: Could not retrieve AkSettings.")); + return nullptr; + } + if (Settings->InitBank.IsNull()) + { + UE_LOG(LogAkAudio, Log, TEXT("FWwiseInitBankLoader::GetInitBankAsset: InitBank asset is not set in Settings.")); + return nullptr; + } + + return Settings->InitBank.LoadSynchronous(); +} + +#if WITH_EDITORONLY_DATA +void FWwiseInitBankLoader::OnPostInitSavePackage() const +{ + auto* Settings = GetMutableDefault(); + if (UNLIKELY(!Settings)) + { + UE_LOG(LogAkAudio, Error, TEXT("OnPostInitSavePackage: Could not retrieve AkSettings.")); + return; + } + if (UNLIKELY(!Settings->InitBank.IsValid())) + { + UE_LOG(LogAkAudio, Error, TEXT("OnPostInitSavePackage: Trying to save invalid Init Bank in %s"), *Settings->DefaultAssetCreationPath); + } + UE_LOG(LogAkAudio, Display, TEXT("OnPostInitSavePackage: Saving new Init Bank in %s..."), *Settings->DefaultAssetCreationPath); + UEditorLoadingAndSavingUtils::SavePackages({ Settings->InitBank->GetPackage() }, true); + AkUnrealEditorHelper::SaveConfigFile(Settings); +} +#endif + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Public/AkAudioDevice.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Public/AkAudioDevice.h new file mode 100644 index 0000000..fe22edb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Public/AkAudioDevice.h @@ -0,0 +1,2029 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkAudioDevice.h: Audiokinetic audio interface object. +=============================================================================*/ + +#pragma once + +/*------------------------------------------------------------------------------------ + AkAudioDevice system headers +------------------------------------------------------------------------------------*/ + +#include "AkEnvironmentIndex.h" +#include "AkGameplayTypes.h" +#include "AkGroupValue.h" +#include "AkInclude.h" +#include "AkJobWorkerScheduler.h" +#include "Wwise/WwiseSharedLanguageId.h" +#include "Engine/EngineTypes.h" + +#include "Wwise/Stats/AkAudio.h" + +#if WITH_EDITORONLY_DATA +#include "EditorViewportClient.h" +#endif + +#define GET_AK_EVENT_NAME(AkEvent, EventName) ((AkEvent) ? ((AkEvent)->GetName()) : (EventName)) + +DECLARE_EVENT(FAkAudioDevice, SoundbanksLoaded); +DECLARE_EVENT(FAkAudioDevice, FOnWwiseProjectModification); +DECLARE_EVENT_OneParam(FAkAudioDevice, FOnSwitchValueLoaded, UAkGroupValue*); +DECLARE_DELEGATE_OneParam(FOnSetCurrentAudioCultureCompleted, bool); + +/*------------------------------------------------------------------------------------ + Dependencies, helpers & forward declarations. +------------------------------------------------------------------------------------*/ + +class UAkPortalComponent; +class AkCallbackInfoPool; +class AkLegacyFileCustomParamPolicy; +class CAkDiskPackage; +class FAkComponentCallbackManager; +class FWwiseIOHook; +class UAkComponent; +class UAkGameObject; +class UAkGroupValue; +class UAkLateReverbComponent; +class UAkRoomComponent; +class UAkStateValue; +class UAkSwitchValue; +class UAkAudioType; +class UAkAudioEvent; +class UAkEffectShareSet; +class AkXMLErrorMessageTranslator; +class AkWAAPIErrorMessageTranslator; +class AkUnrealErrorTranslator; + +typedef TSet UAkComponentSet; + +#define DUMMY_GAMEOBJ ((AkGameObjectID)0x2) +#define SOUNDATLOCATION_GAMEOBJ ((AkGameObjectID)0x3) + +/** Define hashing for AkGameObjectID. */ +template +struct AkGameObjectIdKeyFuncs : TDefaultMapKeyFuncs +{ + static FORCEINLINE uint32 GetKeyHash(AkGameObjectID Key) + { + if (sizeof(Key) <= 4) + { + return (uint32)Key; + } + else + { + return GetTypeHash((uint64)Key); + } + } +}; + + +struct AKAUDIO_API FAkAudioDeviceDelegates +{ + DECLARE_MULTICAST_DELEGATE_TwoParams(FOnAkGlobalCallback, AK::IAkGlobalPluginContext*, AkGlobalCallbackLocation); +}; + + +/*------------------------------------------------------------------------------------ + Audiokinetic audio device. +------------------------------------------------------------------------------------*/ + +class AKAUDIO_API FAkAudioDevice final +{ +public: + UE_NONCOPYABLE(FAkAudioDevice); + FAkAudioDevice() {} + + /** + * Initializes the audio device and creates sources. + * + * @return true if initialization was successful, false otherwise + */ + bool Init( void ); + + /** + * Update the audio device and calculates the cached inverse transform later + * on used for spatialization. + */ + bool Update( float DeltaTime ); + + /** + * Tears down audio device by stopping all sounds, removing all buffers, + * destroying all sources, ... Called by both Destroy and ShutdownAfterError + * to perform the actual tear down. + */ + void Teardown(); + + /** + * Stops all game sounds (and possibly UI) sounds + * + * @param bShouldStopUISounds If true, this function will stop UI sounds as well + */ + void StopAllSounds( bool bShouldStopUISounds = false ); + + /** + * Stops all game sounds (and possibly UI) sounds + * + * @param AudioContext Stop sounds associated with this Context only + */ + void StopAllSounds(EAkAudioContext AudioContext); + + /** + * Stop all audio associated with a scene + * + * @param WorldToFlush Interface of the scene to flush + */ + void Flush(UWorld* WorldToFlush); + + /** + * Determine if any rooms or reverb volumes have been added to World during the current frame + */ + bool WorldSpatialAudioVolumesUpdated(UWorld* World); + + /** + * Clears all loaded soundbanks + * + * @return Result from ak sound engine + */ + AK_DEPRECATED(2022.1.0, "This method is now deprecated, please use ClearSoundBanksAndMedia.") + AKRESULT ClearBanks(); + + /** + * Clears all loaded SoundBanks and associated media + * + * @return Result from ak sound engine + */ + AKRESULT ClearSoundBanksAndMedia(); + + /** + * Load a soundbank by name + * + * @param in_BankName The name of the bank to load + * @param out_bankID Returned bank ID + * @return Result from ak sound engine + */ + AKRESULT LoadBank( + const FString& in_BankName, + AkBankID & out_bankID + ); + + /** + * Load a soundbank asynchronously + * + * @param in_BankName The bank to load + * @param in_pfnBankCallback Callback function + * @param in_pCookie Callback cookie (reserved to user, passed to the callback function) + * @param out_bankID Returned bank ID + * @return Result from ak sound engine + */ + AKRESULT LoadBank( + const FString& in_BankName, + AkBankCallbackFunc in_pfnBankCallback, + void * in_pCookie, + AkBankID & out_bankID + ); + + /** + * Load a soundbank asynchronously, flagging completion in the latent action + * + * @param in_BankName The bank to load + * @param LoadBankLatentAction Blueprint Latent action to flag completion + * @param out_bankID Returned bank ID + * @return Result from ak sound engine + */ + AKRESULT LoadBank( + const FString& in_BankName, + FWaitEndBankAction* LoadBankLatentAction + ); + + /** + * Load a sound bank from a memory pointer + * + * @param in_MemoryPtr Pointer to the bank data + * @param in_MemorySize Size of the bank data + * @param out_banKID Returned bank ID + */ + AKRESULT LoadBankFromMemory( + const void* MemoryPtr, + uint32 MemorySize, + AkBankType BankType, + AkBankID& OutBankID + ); + + /** + * Load a soundbank asynchronously, using a Blueprint Delegate for completion + * + * @param in_BankName The bank to load + * @param BankLoadedCallback Blueprint Delegate called upon completion + * @param out_bankID Returned bank ID + * @return Result from ak sound engine + */ + AKRESULT LoadBankAsync( + const FString& in_BankName, + const FOnAkBankCallback& BankLoadedCallback, + AkBankID & out_bankID + ); + + /** + * Unload a soundbank by its name + * + * @param in_BankName The name of the bank to unload + * @return Result from ak sound engine + */ + AKRESULT UnloadBank( + const FString& in_BankName + ); + + /** + * Unload a soundbank asynchronously + * + * @param in_BankName The bank to unload + * @param in_pfnBankCallback Callback function + * @param in_pCookie Callback cookie (reserved to user, passed to the callback function) + * @return Result from ak sound engine + */ + AKRESULT UnloadBank( + const FString& in_BankName, + AkBankCallbackFunc in_pfnBankCallback, + void * in_pCookie + ); + + /** + * Unload a soundbank asynchronously, flagging completion in the latent action + * + * @param in_BankName The bank to unload + * @param UnloadBankLatentAction Blueprint Latent action to flag completion + * @return Result from ak sound engine + */ + AKRESULT UnloadBank( + const FString& in_BankName, + FWaitEndBankAction* UnloadBankLatentAction + ); + + /** + * Unload bank with bank data pointer + * + * @param in_bankID The BankID returned by the LoadBankFromMemory call. + * @param in_memoryPtr The same bank data pointer used with th LoadBankFromMemory call. + */ + AKRESULT UnloadBankFromMemory( + AkBankID in_bankID, + const void* in_memoryPtr + ); + + + /** + * Unload bank with bank data pointer + * + * @param in_bankID The BankID returned by the LoadBankFromMemory call. + * @param in_memoryPtr The same bank data pointer used with th LoadBankFromMemory call. + */ + AKRESULT UnloadBankFromMemoryAsync( + AkBankID in_bankID, + const void* in_memoryPtr, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + uint32 BankType + ); + + + /** + * Unload a soundbank asynchronously, using a Blueprint Delegate for completion + * + * @param in_BankName The bank to load + * @param BankUnloadedCallback Blueprint Delegate called upon completion + * @param out_bankID Returned bank ID + * @return Result from ak sound engine + */ + AKRESULT UnloadBankAsync( + const FString& in_BankName, + const FOnAkBankCallback& BankUnloadedCallback + ); + + /** + * FString-friendly GetIDFromString + */ + static AkUInt32 GetShortIDFromString(const FString& InString); + + /** + * NUll-check asset and if invalid return ID based on BackupName + */ + static AkUInt32 GetShortID(UAkAudioType* AudioAsset, const FString& BackupName); + + /** + * Indicates the location of a specific Media ID in memory. + * + * @param in_pSourceSettings Array of Source Settings + * @param in_uNumSourceSettings Number of Source Settings in the array + */ + AKRESULT SetMedia(AkSourceSettings* in_pSourceSettings, uint32 in_uNumSourceSettings); + + /** + * Removes the specified source from the list of loaded media, even if this media is already in use. + * + * @param in_pSourceSettings Array of Source Settings + * @param in_uNumSourceSettings Number of Source Settings in the array + * @param out_pUnsetResults Array of AKRESULT results per Source Settings + */ + AKRESULT TryUnsetMedia(AkSourceSettings* in_pSourceSettings, uint32 in_uNumSourceSettings, AKRESULT* out_pUnsetResults = nullptr); + + /** + * Removes the specified source from the list of loaded media. + * + * @param in_pSourceSettings Array of Source Settings + * @param un_uNumSourceSettings Number of Source Settings in the array + */ + AKRESULT UnsetMedia(AkSourceSettings* in_pSourceSettings, uint32 in_uNumSourceSettings); + + /** + * Get the currently selected audio culture + */ + FString GetCurrentAudioCulture() const; + + /** + * Get the default language set in the Wwise project + */ + FString GetDefaultLanguage(); + + /** + * Get the list of available audio cultures + */ + TArray GetAvailableAudioCultures() const; + + /** + * Get a FWwiseSharedLanguageId from the name as set in Wwise + */ + FWwiseLanguageCookedData GetLanguageCookedDataFromString(const FString& WwiseLanguage); + + /** + * Change the audio culture + */ + void SetCurrentAudioCulture(const FString& AudioCulture); + + /** + * Change the audio culture asynchronously and signal the latent action when done + */ + void SetCurrentAudioCultureAsync(const FString& AudioCulture, FSetCurrentAudioCultureAction* LatentAction); + + /** + * Change the audio culture asynchronous and call the callback when done + */ + void SetCurrentAudioCultureAsync(const FString& AudioCulture, const FOnSetCurrentAudioCultureCompleted& CompletedCallback); + + /** + * Post an event to ak soundengine + * + * @warning This function is deprecated. Please see \ref UAkAudioEvent::PostOnActor. + * + * @param AkEvent Event to post + * @param Actor Actor on which to play the event + * @param Flags Bitmask: see \ref AkCallbackType + * @param Callback Callback function + * @param Cookie Callback cookie that will be sent to the callback function along with additional information. + * @param bStopWhenOwnerDestroyed If true, then the sound should be stopped if the owning actor is destroyed + * @param AudioContext Context in which sound is played. Only gameplay sounds are affected by PIE pause/stop + * @return ID assigned by ak soundengine + */ + AK_DEPRECATED(2022.1, "Use UAkAudioEvent::PostOnActor") + AkPlayingID PostAkAudioEventOnActor( + class UAkAudioEvent * AkEvent, + AActor * Actor, + AkUInt32 Flags = 0, + AkCallbackFunc Callback = NULL, + void * Cookie = NULL, + bool bStopWhenOwnerDestroyed = false, + EAkAudioContext AudioContext = EAkAudioContext::GameplayAudio + ); + + + /** + * Post an event to ak soundengine + * + * @warning This function is deprecated. You are expected to use an UAkAudioEvent. Please see \ref UAkAudioEvent::PostOnComponent. + * + * @param AkEvent Event to post + * @param Actor Actor on which to play the event + * @param Flags Bitmask: see \ref AkCallbackType + * @param Callback Callback function + * @param Cookie Callback cookie that will be sent to the callback function along with additional information. + * @param bStopWhenOwnerDestroyed If true, then the sound should be stopped if the owning actor is destroyed + * @param AudioContext Context in which sound is played. Only gameplay sounds are affected by PIE pause/stop + * @return ID assigned by ak soundengine + */ + AkPlayingID PostAkAudioEventOnComponent( + UAkAudioEvent* AkEvent, + UAkComponent* Component, + AkUInt32 Flags = 0, + AkCallbackFunc Callback = NULL, + void* Cookie = NULL, + bool bStopWhenOwnerDestroyed = false, + EAkAudioContext AudioContext = EAkAudioContext::GameplayAudio + ); + + + /** + * Post an event to ak soundengine by ID + * + * @warning This function is deprecated. You are expected to use an UAkAudioEvent. Please see \ref UAkAudioEvent::PostOnActor. + * + * @param EventShortID ID of the event to post + * @param Actor Actor on which to play the event + * @param Flags Bitmask: see \ref AkCallbackType + * @param Callback Callback function + * @param Cookie Callback cookie that will be sent to the callback function along with additional information. + * @param bStopWhenOwnerDestroyed If true, then the sound should be stopped if the owning actor is destroyed + * @param AudioContext Context in which sound is played. Only gameplay sounds are affected by PIE pause/stop + * @return ID assigned by ak soundengine + */ + AkPlayingID PostEventOnActor( + const AkUInt32 EventShortID, + AActor * Actor, + AkUInt32 Flags = 0, + AkCallbackFunc Callback = NULL, + void * Cookie = NULL, + bool bStopWhenOwnerDestroyed = false, + const TArray ExternalSources = TArray(), + EAkAudioContext AudioContext = EAkAudioContext::GameplayAudio + ); + + /** + * Post an event to ak soundengine by ID + * + * @warning This function is deprecated. You are expected to use an UAkAudioEvent. Please see \ref UAkAudioEvent::PostOnActor. + * + * @param EventShortID ID of the event to post + * @param Actor Actor on which to play the event + * @param PostEventCallback Callback delegate + * @param Flags Bitmask: see \ref EAkCallbackType + * @param bStopWhenOwnerDestroyed If true, then the sound should be stopped if the owning actor is destroyed + * @param AudioContext Context in which sound is played. Only gameplay sounds are affected by PIE pause/stop + * @return ID assigned by ak soundengine + */ + AkPlayingID PostEventOnActor( + const AkUInt32 EventShortID, + AActor * Actor, + const FOnAkPostEventCallback& PostEventCallback, + AkUInt32 Flags = 0, + bool bStopWhenOwnerDestroyed = false, + const TArray& ExternalSources = TArray(), + EAkAudioContext AudioContext = EAkAudioContext::GameplayAudio + ); + + /** + * Post an event to ak soundengine by ID + * + * @warning This function is deprecated. You are expected to use an UAkAudioEvent. Please see \ref UAkAudioEvent::PostOnGameObjectID. + * + * @param EventShortID ID of the event to post + * @param Actor Actor on which to play the event + * @param Flags Bitmask: see \ref AkCallbackType + * @param Callback Callback function + * @param Cookie Callback cookie that will be sent to the callback function along with additional information. + * @param bStopWhenOwnerDestroyed If true, then the sound should be stopped if the owning actor is destroyed + * @param AudioContext Context in which sound is played. Only gameplay sounds are affected by PIE pause/stop + * @return ID assigned by ak soundengine + */ + AkPlayingID PostEventOnGameObjectID( + const AkUInt32 EventShortID, + AkGameObjectID GameObject, + AkUInt32 Flags /*= 0*/, + AkCallbackFunc Callback /*= NULL*/, + void* Cookie, + /*= NULL*/ + const TArray& ExternalSources, /*= TArray()*/ + EAkAudioContext AudioContext = EAkAudioContext::GameplayAudio + ); + + /** + * Post an event to ak soundengine by ID + * + * @warning This function is deprecated. You are expected to use an UAkAudioEvent. Please see \ref UAkAudioEvent::PostOnActor. + * + * @param EventShortID ID of the event to post + * @param Actor Actor on which to play the event + * @param bStopWhenOwnerDestroyed If true, then the sound should be stopped if the owning actor is destroyed + * @param LatentAction Pointer to a Blueprint latent action.Used in the EndOfEvent callback. + * @param AudioContext Context in which sound is played. Only gameplay sounds are affected by PIE pause/stop + * @return ID assigned by ak soundengine + */ + AkPlayingID PostEventOnActorWithLatentAction( + const AkUInt32 EventShortID, + AActor * Actor, + bool bStopWhenOwnerDestroyed, + FWaitEndOfEventAction* LatentAction, + const TArray& ExternalSources = TArray(), + EAkAudioContext AudioContext = EAkAudioContext::GameplayAudio + ); + + /** + * Post an event to ak soundengine by name + * + * @warning This function is deprecated. You are expected to use an UAkAudioEvent. Please see \ref UAkAudioEvent::PostOnComponent. + * + * @param EventShortID ID of the event to post + * @param AkComponent AkComponent on which to play the event + * @param LatentAction Pointer to a Blueprint latent action. Used in the EndOfEvent callback. + * @param AudioContext Context in which sound is played. Only gameplay sounds are affected by PIE pause/stop + * @return ID assigned by ak soundengine + */ + AkPlayingID PostEventOnComponentWithLatentAction( + const AkUInt32 EventShortID, + UAkComponent* AkComponent, + FWaitEndOfEventAction* LatentAction, + const TArray& ExternalSources = TArray(), + EAkAudioContext AudioContext = EAkAudioContext::GameplayAudio + ); + + /** + * Post an event to ak soundengine by ID + * + * @warning This function is deprecated. You are expected to use an UAkAudioEvent. Please see \ref UAkAudioEvent::PostOnComponent. + * + * @param EventShortID ID of the event to post + * @param Component AkComponent on which to play the event + * @param Flags Bitmask: see \ref AkCallbackType + * @param Callback Callback function + * @param Cookie Callback cookie that will be sent to the callback function along with additional information. + * @param AudioContext Context in which sound is played. Only gameplay sounds are affected by PIE pause/stop + * @return ID assigned by ak soundengine + */ + AkPlayingID PostEventOnAkComponent( + const AkUInt32 EventShortID, + UAkComponent* Component, + AkUInt32 Flags = 0, + AkCallbackFunc Callback = NULL, + void * Cookie = NULL, + const TArray& ExternalSources = TArray(), + EAkAudioContext AudioContext = EAkAudioContext::GameplayAudio + ); + + /** + * Post an event to ak soundengine by ID + * + * @warning This function is deprecated. You are expected to use an UAkAudioEvent. Please see \ref UAkAudioEvent::PostOnGameObject. + * + * @param EventShortID ID of the event to post + * @param AkGameObject UAkGameObject on which to play the event + * @param PostEventCallback Callback delegate + * @param Flags Bitmask: see \ref EAkCallbackType + * @param AudioContext Context in which sound is played. Only gameplay sounds are affected by PIE pause/stop + * @return ID assigned by ak soundengine + */ + AkPlayingID PostEventOnAkGameObject( + const AkUInt32 EventShortID, + UAkGameObject* AkGameObject, + const FOnAkPostEventCallback& PostEventCallback, + AkUInt32 Flags = 0, + const TArray& ExternalSources = TArray(), + EAkAudioContext AudioContext = EAkAudioContext::GameplayAudio + ); + + /** + * Post an event at location to ak soundengine + * + * @warning This function is deprecated. Please see \ref UAkAudioEvent::PostAtLocation. + * + * @param Event Event to post + * @param Location Location at which to play the event + * @param Orientation + * @param World + * @param AudioContext Context in which sound is played. Only gameplay sounds are affected by PIE pause/stop + * @return ID assigned by ak soundengine + */ + AK_DEPRECATED(2022.1, "Use UAkAudioEvent::PostAtLocation") + AkPlayingID PostAkAudioEventAtLocation( + UAkAudioEvent* Event, + FVector Location, + FRotator Orientation, + UWorld* World, + EAkAudioContext AudioContext = EAkAudioContext::GameplayAudio + ); + + /** + * Post an event by name at location to ak soundengine + * + * @warning This function is deprecated. You are expected to use an UAkAudioEvent. Please see \ref UAkAudioEvent::PostAtLocation. + * + * @param EventName Name of the event to post (Used to register the game object) + * @param EventShortID ID of the event to post + * @param Location Location at which to play the event + * @param Orientation + * @param World + * @param AudioContext Context in which sound is played. Only gameplay sounds are affected by PIE pause/stop + * @return ID assigned by ak soundengine + */ + AkPlayingID PostEventAtLocation( + const FString& EventName, + const AkUInt32 EventShortID, + FVector Location, + FRotator Orientation, + UWorld* World, + const TArray& ExternalSources = TArray(), + EAkAudioContext AudioContext = EAkAudioContext::GameplayAudio + ); + + /** + * Post an event at location to ak soundengine + * + * @warning This function is deprecated. You are expected to use an UAkAudioEvent. Please see \ref UAkAudioEvent::PostAtLocation. + * Async operations are deprecated. + * + * @param Event Event to post + * @param Location Location at which to play the event + * @param Orientation + * @param World + * @param AudioContext Context in which sound is played. Only gameplay sounds are affected by PIE pause/stop + * @return ID assigned by ak soundengine + */ + TFuture PostAkAudioEventAtLocationAsync( + UAkAudioEvent* Event, + FVector Location, + FRotator Orientation, + UWorld* World, + EAkAudioContext AudioContext = EAkAudioContext::GameplayAudio + ); + + /** + * Wait for event asset to be fully loaded and then post the event to ak soundengine + * + * @warning This function is deprecated. Please see \ref UAkAudioEvent::PostOnActor. + * Async operations are deprecated. + * + * @param AudioEvent The event to post + * @param Actor Actor on which to play the event + * @param PostEventCallback Callback function + * @param CallbackFlags Bitmask: see \ref AkCallbackType + * @param bStopWhenOwnerDestroyed If true, then the sound should be stopped if the owning actor is destroyed + * @param AudioContext Context in which sound is played. Only gameplay sounds are affected by PIE pause/stop + * @return ID assigned by ak soundengine + */ + TFuture PostAkAudioEventOnActorAsync( + class UAkAudioEvent* AudioEvent, + AActor* Actor, + const FOnAkPostEventCallback& PostEventCallback, + AkUInt32 CallbackFlags = 0, + bool bStopWhenOwnerDestroyed = false, + EAkAudioContext AudioContext = EAkAudioContext::GameplayAudio + ); + + /** + * Wait for event asset to be fully loaded and then post the event to ak soundengine + * + * @warning This function is deprecated. Please see \ref UAkAudioEvent::PostOnAkGameObject. + * Async operations are deprecated. + * + * @param AudioEvent The event to post + * @param GameObject UAkGameObject on which to play the event + * @param PostEventCallback Callback delegate + * @param CallbackFlags Bitmask: see \ref EAkCallbackType + * @param AudioContext Context in which sound is played. Only gameplay sounds are affected by PIE pause/stop + * @return ID assigned by ak soundengine + */ + TFuture PostAkAudioEventOnAkGameObjectAsync( + class UAkAudioEvent* AudioEvent, + UAkGameObject* GameObject, + const FOnAkPostEventCallback& PostEventCallback, + AkUInt32 CallbackFlags = 0, + EAkAudioContext AudioContext = EAkAudioContext::GameplayAudio + ); + + /** + * Post an event to ak soundengine + * + * @warning This function is deprecated. You are expected to use an UAkAudioEvent. Please see \ref UAkAudioEvent::PostOnActor. + * Async operations are deprecated. + * + * @param AudioEvent The event to post + * @param Actor Actor on which to play the event + * @param bStopWhenOwnerDestroyed If true, then the sound should be stopped if the owning actor is destroyed + * @param LatentAction Pointer to a Blueprint latent action. Used in the EndOfEvent callback. + * @param AudioContext Context in which sound is played. Only gameplay sounds are affected by PIE pause/stop + * @return ID assigned by ak soundengine + */ + TFuture PostAkAudioEventWithLatentActionOnActorAsync( + class UAkAudioEvent* AudioEvent, + AActor* Actor, + bool bStopWhenOwnerDestroyed, + FWaitEndOfEventAction* LatentAction, + EAkAudioContext AudioContext = EAkAudioContext::GameplayAudio + ); + + /** + * Post an event to ak soundengine + * + * @warning This function is deprecated. You are expected to use an UAkAudioEvent. Please see \ref UAkAudioEvent::PostOnComponent. + * Async operations are deprecated. + * + * @param AudioEvent The event to post + * @param AkComponent AkComponent on which to play the event + * @param bStopWhenOwnerDestroyed If true, then the sound should be stopped if the owning actor is destroyed + * @param LatentAction Pointer to a Blueprint latent action.Used in the EndOfEvent callback. + * @param AudioContext Context in which sound is played. Only gameplay sounds are affected by PIE pause/stop + * @return ID assigned by ak soundengine + */ + TFuture PostAkAudioEventWithLatentActionOnAkComponentAsync( + class UAkAudioEvent* AudioEvent, + UAkComponent* AkComponent, + bool bStopWhenOwnerDestroyed, + FWaitEndOfEventAction* LatentAction, + EAkAudioContext AudioContext = EAkAudioContext::GameplayAudio + ); + + /** Spawn an AkComponent at a location. Allows, for example, to set a switch on a fire and forget sound. + * @param AkEvent - Wwise Event to post. + * @param Location - Location from which to post the Wwise Event. + * @param Orientation - Orientation of the event. + * @param AutoPost - Automatically post the event once the AkComponent is created. + * @param EarlyReflectionsBusName - Use the provided auxiliary bus to process early reflections. If empty, no early reflections will be processed. + * @param AutoDestroy - Automatically destroy the AkComponent once the event is finished. + */ + class UAkComponent* SpawnAkComponentAtLocation( class UAkAudioEvent* AkEvent, FVector Location, FRotator Orientation, bool AutoPost, const FString& EventName, bool AutoDestroy, class UWorld* in_World ); + + /** + * Executes an action on all nodes that are referenced in the specified event in an action of type play. + * + * @warning This function is deprecated. You are expected to use an UAkAudioEvent. Please see \ref UAkAudioEvent::ExecuteAction. + * + * @param EventShortID ID of the event to post + * @param ActionType Action to execute on all the elements that were played using the specified event. + * @param Actor Associated actor + * @param TransitionDuration Fade duration + * @param FadeCurve Curve type to be used for the transition + * @param PlayingID Associated PlayingID + */ + AKRESULT ExecuteActionOnEvent( + const AkUInt32 EventShortID, + AkActionOnEventType ActionType, + AActor* Actor, + AkTimeMs TransitionDuration = 0, + EAkCurveInterpolation FadeCurve = EAkCurveInterpolation::Linear, + AkPlayingID PlayingID = AK_INVALID_PLAYING_ID + ); + + /** + * + * @warning This function is deprecated. You are expected to use an UAkAudioEvent. Please see \ref UAkAudioEvent::ExecuteAction. + * + * Executes an Action on the content associated to the specified playing ID. + * @param in_ActionType Action to execute on the specified playing ID. + * @param in_PlayingID Playing ID on which to execute the action. + * @param in_uTransitionDuration Fade duration + * @param in_eFadeCurve Curve type to be used for the transition + */ + void ExecuteActionOnPlayingID( + AkActionOnEventType in_ActionType, + AkPlayingID in_PlayingID, + AkTimeMs in_uTransitionDuration = 0, + EAkCurveInterpolation in_eFadeCuve = EAkCurveInterpolation::Linear + ); + + /** Seek on an event in the ak soundengine. + * @param EventShortID ID of the event on which to seek. + * @param Component The associated Actor. + * @param Position Desired percent where playback should restart. + * @param bSeekToNearestMarker If true, the final seeking position will be made equal to the nearest marker. + * + * @return Success or failure. + */ + AKRESULT SeekOnEvent( + const AkUInt32 EventShortID, + AActor* Actor, + AkReal32 Percent, + bool bSeekToNearestMarker = false, + AkPlayingID PlayingID = AK_INVALID_PLAYING_ID + ); + + /** Seek on an event in the ak soundengine. + * @param EventShortID ID of the event on which to seek. + * @param Component The associated AkComponent. + * @param Percent Desired percent where playback should restart. + * @param bSeekToNearestMarker If true, the final seeking position will be made equal to the nearest marker. + * + * @return Success or failure. + */ + AKRESULT SeekOnEvent( + const AkUInt32 EventShortID, + UAkComponent* Component, + AkReal32 Percent, + bool bSeekToNearestMarker = false, + AkPlayingID PlayingID = AK_INVALID_PLAYING_ID + ); + + /** + * Post a trigger to ak soundengine + * + * @param in_pszTrigger Name of the trigger + * @param in_pAkComponent AkComponent on which to post the trigger + * @return Result from ak sound engine + */ + AKRESULT PostTrigger( + const TCHAR * in_pszTrigger, + AActor * in_pActor + ); + + /** + * Post a trigger to ak soundengine + * + * @param in_TriggerValue Trigger value + * @param in_pAkComponent AkComponent on which to post the trigger + * @return Result from ak sound engine + */ + AKRESULT PostTrigger( + class UAkTrigger const* in_TriggerValue, + AActor * in_pActor + ); + + /** + * Set a RTPC in ak soundengine + * + * @param in_pszRtpcName Name of the RTPC + * @param in_value Value to set + * @param in_interpolationTimeMs - Duration during which the RTPC is interpolated towards in_value (in ms) + * @param in_pActor AActor on which to set the RTPC + * @return Result from ak sound engine + */ + AKRESULT SetRTPCValue( + const TCHAR * in_pszRtpcName, + AkRtpcValue in_value, + int32 in_interpolationTimeMs, + AActor * in_pActor + ); + + /** + * Set a RTPC in ak soundengine + * + * @param in_Rtpc RTPC Short ID + * @param in_value Value to set + * @param in_interpolationTimeMs - Duration during which the RTPC is interpolated towards in_value (in ms) + * @param in_pActor AActor on which to set the RTPC + * @return Result from ak sound engine + */ + AKRESULT SetRTPCValue( + AkRtpcID in_Rtpc, + AkRtpcValue in_value, + int32 in_interpolationTimeMs, + AActor * in_pActor + ); + + /** + * Set a RTPC in ak soundengine + * + * @param in_RtpcValue RTPC Value + * @param in_value Value to set + * @param in_interpolationTimeMs - Duration during which the RTPC is interpolated towards in_value (in ms) + * @param in_pActor AActor on which to set the RTPC + * @return Result from ak sound engine + */ + AKRESULT SetRTPCValue( + class UAkRtpc const* in_RtpcValue, + AkRtpcValue in_value, + int32 in_interpolationTimeMs, + AActor * in_pActor + ); + + /** + * Set a RTPC in ak soundengine by PlayingID + * + * @param in_Rtpc RTPC Short ID + * @param in_value Value to set + * @param in_playingID PlayingID on which to set the value + * @param in_interpolationTimeMs - Duration during which the RTPC is interpolated towards in_value (in ms) + * @return Result from ak sound engine + */ + AKRESULT SetRTPCValueByPlayingID( + AkRtpcID in_Rtpc, + AkRtpcValue in_value, + AkPlayingID in_playingID, + int32 in_interpolationTimeMs + ); + + /** + * Get the value of a real-time parameter control (by ID) + * An RTPC can have a any combination of a global value, a unique value for each game object, or a unique value for each playing ID. + * The value requested is determined by RTPCValue_type, in_gameObjectID and in_playingID. + * If a value at the requested scope (determined by RTPCValue_type) is not found, the value that is available at the the next broadest scope will be returned, and io_rValueType will be changed to indicate this. + * @note + * When looking up RTPC values via playing ID (ie. io_rValueType is RTPC_PlayingID), in_gameObjectID can be set to a specific game object (if it is available to the caller) to use as a fall back value. + * If the game object is unknown or unavailable, AK_INVALID_GAME_OBJECT can be passed in in_gameObjectID, and the game object will be looked up via in_playingID. + * However in this case, it is not possible to retrieve a game object value as a fall back value if the playing id does not exist. It is best to pass in the game object if possible. + * + * @return AK_Success if succeeded, AK_IDNotFound if the game object was not registered, or AK_Fail if the RTPC value could not be obtained + */ + AKRESULT GetRTPCValue( + const TCHAR * in_pszRtpcName, + AkGameObjectID in_gameObjectID, ///< Associated game object ID, ignored if io_rValueType is RTPCValue_Global. + AkPlayingID in_playingID, ///< Associated playing ID, ignored if io_rValueType is not RTPC_PlayingID. + AkRtpcValue& out_rValue, ///< Value returned + AK::SoundEngine::Query::RTPCValue_type& io_rValueType ///< In/Out value, the user must specify the requested type. The function will return in this variable the type of the returned value. ); + ); + + /** + * Get the value of a real-time parameter control (by ID) + * An RTPC can have a any combination of a global value, a unique value for each game object, or a unique value for each playing ID. + * The value requested is determined by RTPCValue_type, in_gameObjectID and in_playingID. + * If a value at the requested scope (determined by RTPCValue_type) is not found, the value that is available at the the next broadest scope will be returned, and io_rValueType will be changed to indicate this. + * @note + * When looking up RTPC values via playing ID (ie. io_rValueType is RTPC_PlayingID), in_gameObjectID can be set to a specific game object (if it is available to the caller) to use as a fall back value. + * If the game object is unknown or unavailable, AK_INVALID_GAME_OBJECT can be passed in in_gameObjectID, and the game object will be looked up via in_playingID. + * However in this case, it is not possible to retrieve a game object value as a fall back value if the playing id does not exist. It is best to pass in the game object if possible. + * + * @return AK_Success if succeeded, AK_IDNotFound if the game object was not registered, or AK_Fail if the RTPC value could not be obtained + */ + AKRESULT GetRTPCValue( + AkRtpcID in_Rtpc, + AkGameObjectID in_gameObjectID, ///< Associated game object ID, ignored if io_rValueType is RTPCValue_Global. + AkPlayingID in_playingID, ///< Associated playing ID, ignored if io_rValueType is not RTPC_PlayingID. + AkRtpcValue& out_rValue, ///< Value returned + AK::SoundEngine::Query::RTPCValue_type& io_rValueType ///< In/Out value, the user must specify the requested type. The function will return in this variable the type of the returned value. ); + ); + + /** + * Get the value of a real-time parameter control (by ID) + * An RTPC can have a any combination of a global value, a unique value for each game object, or a unique value for each playing ID. + * The value requested is determined by RTPCValue_type, in_gameObjectID and in_playingID. + * If a value at the requested scope (determined by RTPCValue_type) is not found, the value that is available at the the next broadest scope will be returned, and io_rValueType will be changed to indicate this. + * @note + * When looking up RTPC values via playing ID (ie. io_rValueType is RTPC_PlayingID), in_gameObjectID can be set to a specific game object (if it is available to the caller) to use as a fall back value. + * If the game object is unknown or unavailable, AK_INVALID_GAME_OBJECT can be passed in in_gameObjectID, and the game object will be looked up via in_playingID. + * However in this case, it is not possible to retrieve a game object value as a fall back value if the playing id does not exist. It is best to pass in the game object if possible. + * + * @return AK_Success if succeeded, AK_IDNotFound if the game object was not registered, or AK_Fail if the RTPC value could not be obtained + */ + AKRESULT GetRTPCValue( + class UAkRtpc const* in_RtpcValue, + AkGameObjectID in_gameObjectID, ///< Associated game object ID, ignored if io_rValueType is RTPCValue_Global. + AkPlayingID in_playingID, ///< Associated playing ID, ignored if io_rValueType is not RTPC_PlayingID. + AkRtpcValue& out_rValue, ///< Value returned + AK::SoundEngine::Query::RTPCValue_type& io_rValueType ///< In/Out value, the user must specify the requested type. The function will return in this variable the type of the returned value. ); + ); + + /// Resets the value of the game parameter to its default value, as specified in the Wwise project. + /// With this function, you may reset a game parameter to its default value with global scope or with game object scope. + /// Game object scope supersedes global scope. Game parameter values reset with global scope are applied to all + /// game objects that were not overridden with a value with game object scope. + /// To reset a game parameter value with global scope, pass AK_INVALID_GAME_OBJECT as the game object. + /// With this function, you may also reset the value of a game parameter over time. To do so, specify a non-zero + /// value for in_interpolationTimeMs. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValue() or ResetRTPCValue() with in_interpolationTimeMs = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. + AKRESULT ResetRTPCValue( + const UAkRtpc* in_RtpcValue, + AkGameObjectID in_gameObjectID, + int32 in_interpolationTimeMs + ); + + /// Resets the value of the game parameter to its default value, as specified in the Wwise project. + /// With this function, you may reset a game parameter to its default value with global scope or with game object scope. + /// Game object scope supersedes global scope. Game parameter values reset with global scope are applied to all + /// game objects that were not overridden with a value with game object scope. + /// To reset a game parameter value with global scope, pass AK_INVALID_GAME_OBJECT as the game object. + /// With this function, you may also reset the value of a game parameter over time. To do so, specify a non-zero + /// value for in_interpolationTimeMs. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValue() or ResetRTPCValue() with in_interpolationTimeMs = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. + AKRESULT ResetRTPCValue( + AkRtpcID in_rtpcID, ///< ID of the game parameter + AkGameObjectID in_gameObjectID, ///< Associated game object ID + int32 in_interpolationTimeMs ///< Duration during which the game parameter is interpolated towards its default value + ); + + /// Resets the value of the game parameter to its default value, as specified in the Wwise project. + /// With this function, you may reset a game parameter to its default value with global scope or with game object scope. + /// Game object scope supersedes global scope. Game parameter values reset with global scope are applied to all + /// game objects that were not overridden with a value with game object scope. + /// To reset a game parameter value with global scope, pass AK_INVALID_GAME_OBJECT as the game object. + /// With this function, you may also reset the value of a game parameter over time. To do so, specify a non-zero + /// value for in_interpolationTimeMs. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValue() or ResetRTPCValue() with in_interpolationTimeMs = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. + AKRESULT ResetRTPCValue( + const TCHAR * in_pszRtpcName, ///< Name of the game parameter + AkGameObjectID in_gameObjectID, ///< Associated game object ID + int32 in_interpolationTimeMs ///< Duration during which the game parameter is interpolated towards its default value + ); + + /** + * Set a state in ak soundengine + * + * @param in_pszStateGroup Name of the state group + * @param in_pszState Name of the state + * @return Result from ak sound engine + */ + AKRESULT SetState( + const TCHAR* in_pszStateGroup, + const TCHAR* in_pszState + ); + + /** + * Set a state in ak soundengine + * + * @param in_StateGroup State group short ID + * @param in_State State short ID + * @return Result from ak sound engine + */ + AKRESULT SetState( + AkStateGroupID in_StateGroup, + AkStateID in_State + ); + + /** + * Set a state in ak soundengine + * + * @param in_stateValue State to set + * @return Result from ak sound engine + */ + AKRESULT SetState( + const UAkStateValue* in_stateValue + ); + + /** + * Set a switch in ak soundengine + * + * @param in_pszSwitchGroup Name of the switch group + * @param in_pszSwitchState Name of the switch + * @param in_pComponent AkComponent on which to set the switch + * @return Result from ak sound engine + */ + AKRESULT SetSwitch( + const TCHAR * in_pszSwitchGroup, + const TCHAR * in_pszSwitchState, + AActor * in_pActor + ); + + /** + * Set a switch in ak soundengine + * + * @param in_SwitchGroup Short ID of the switch group + * @param in_SwitchState Short ID of the switch + * @param in_pComponent AkComponent on which to set the switch + * @return Result from ak sound engine + */ + AKRESULT SetSwitch( + AkSwitchGroupID in_SwitchGroup, + AkSwitchStateID in_SwitchState, + AActor* in_pActor + ); + + /** + * Set a switch in ak soundengine + * + * @param in_switchValue Switch to set + * @param in_pActor AkActor on which to set the switch + * @return Result from ak sound engine + */ + AKRESULT SetSwitch( + const UAkSwitchValue* in_switchValue, + AActor * in_pActor + ); + + /** Sets multiple positions to a single game object. + * Setting multiple positions on a single game object is a way to simulate multiple emission sources while using the resources of only one voice. + * This can be used to simulate wall openings, area sounds, or multiple objects emitting the same sound in the same area. + * Note: Calling AK::SoundEngine::SetMultiplePositions() with only one position is the same as calling AK::SoundEngine::SetPosition() + * @param in_pGameObjectAkComponent UAkComponent of the game object. + * @param in_aPositions Array of positions to apply. + * @param in_eMultiPositionType Position type + * @return AK_Success when successful, AK_InvalidParameter if parameters are not valid. + */ + AKRESULT SetMultiplePositions( + UAkComponent* in_pGameObjectAkComponent, + TArray in_aPositions, + AkMultiPositionType in_eMultiPositionType = AkMultiPositionType::MultiDirections + ); + + /** Sets multiple positions to a single game object, with flexible assignment of input channels. + * Setting multiple positions on a single game object is a way to simulate multiple emission sources while using the resources of only one voice. + * This can be used to simulate wall openings, area sounds, or multiple objects emitting the same sound in the same area. + * Note: Calling AK::SoundEngine::SetMultiplePositions() with only one position is the same as calling AK::SoundEngine::SetPosition() + * @param in_pGameObjectAkComponent Game Object AkComponent. + * @param in_aChannelConfigurations Array of channel configurations for each position. + * @param in_pPositions Array of positions to apply. + * @param in_eMultiPositionType Position type + * @return AK_Success when successful, AK_InvalidParameter if parameters are not valid. + */ + AKRESULT SetMultiplePositions( + UAkComponent* in_pGameObjectAkComponent, + const TArray& in_aChannelConfigurations, + const TArray& in_aPositions, + AkMultiPositionType in_eMultiPositionType = AkMultiPositionType::MultiDirections + ); + + /** Sets multiple positions to a single game object, with flexible assignment of input channels. + * Setting multiple positions on a single game object is a way to simulate multiple emission sources while using the resources of only one voice. + * This can be used to simulate wall openings, area sounds, or multiple objects emitting the same sound in the same area. + * Note: Calling AK::SoundEngine::SetMultiplePositions() with only one position is the same as calling AK::SoundEngine::SetPosition() + * @param in_pGameObjectAkComponent Game Object AkComponent. + * @param in_channelMasks Array of channel mask for each position. + * @param in_pPositions Array of positions to apply. + * @param in_eMultiPositionType Position type + * @return AK_Success when successful, AK_InvalidParameter if parameters are not valid. + */ + AKRESULT SetMultiplePositions( + UAkComponent* in_pGameObjectAkComponent, + const TArray& in_channelMasks, + const TArray& in_aPositions, + AkMultiPositionType in_eMultiPositionType = AkMultiPositionType::MultiDirections + ); + + /** Sets multiple positions to a single game object. + * Setting multiple positions on a single game object is a way to simulate multiple emission sources while using the resources of only one voice. + * This can be used to simulate wall openings, area sounds, or multiple objects emitting the same sound in the same area. + * Note: Calling AK::SoundEngine::SetMultiplePositions() with only one position is the same as calling AK::SoundEngine::SetPosition() + * @param in_GameObjectID Game Object identifier. + * @param in_pPositions Array of positions to apply. + * @param in_NumPositions Number of positions specified in the provided array. + * @param in_eMultiPositionType Position type + * @return AK_Success when successful, AK_InvalidParameter if parameters are not valid. + * + */ + AKRESULT SetMultiplePositions( + AkGameObjectID in_GameObjectID, + const AkSoundPosition * in_pPositions, + AkUInt16 in_NumPositions, + AK::SoundEngine::MultiPositionType in_eMultiPositionType = AK::SoundEngine::MultiPositionType_MultiDirections + ); + + /** Sets multiple positions to a single game object, with flexible assignment of input channels. + * Setting multiple positions on a single game object is a way to simulate multiple emission sources while using the resources of only one voice. + * This can be used to simulate wall openings, area sounds, or multiple objects emitting the same sound in the same area. + * Note: Calling AK::SoundEngine::SetMultiplePositions() with only one position is the same as calling AK::SoundEngine::SetPosition() + * @param in_GameObjectID Game Object identifier. + * @param in_pPositions Array of positions to apply. + * @param in_NumPositions Number of positions specified in the provided array. + * @param in_eMultiPositionType Position type + * @return AK_Success when successful, AK_InvalidParameter if parameters are not valid. + */ + AKRESULT SetMultiplePositions( + AkGameObjectID in_GameObjectID, + const AkChannelEmitter * in_pPositions, + AkUInt16 in_NumPositions, + AK::SoundEngine::MultiPositionType in_eMultiPositionType = AK::SoundEngine::MultiPositionType_MultiDirections + ); + + /** + * Set auxiliary sends + * + * @param in_GameObjId Wwise Game Object ID + * @param in_AuxSendValues Array of AkAuxSendValue, containing all Aux Sends to set on the game object + * @return Result from ak sound engine + */ + AKRESULT SetAuxSends( + const UAkComponent* in_akComponent, + TArray& in_AuxSendValues + ); + + /** + * Set spatial audio room + * + * @param in_GameObjId Wwise Game Object ID + * @param in_RoomID ID of the room that the game object is inside. + * @return Result from ak sound engine + */ + AKRESULT SetInSpatialAudioRoom( + const AkGameObjectID in_GameObjId, + AkRoomID in_RoomID + ); + + /** + * Force channel configuration for the specified bus. + * This function has unspecified behavior when changing the configuration of a bus that + * is currently playing. + * You cannot change the configuration of the master bus. + * + * @param in_BusName Bus Name + * @param in_Config Desired channel configuration. An invalid configuration (from default constructor) means "as parent". + * @return Always returns AK_Success + */ + AKRESULT SetBusConfig( + const FString& in_BusName, + AkChannelConfig in_Config + ); + + /** + * Set the panning rule of the specified output. + * This may be changed anytime once the sound engine is initialized. + * \warning This function posts a message through the sound engine's internal message queue, whereas GetPanningRule() queries the current panning rule directly. + */ + AKRESULT SetPanningRule( + AkPanningRule in_ePanningRule ///< Panning rule. + ); + + /** + * Gets the compounded output ID from ShareSet and device id. + * Outputs are defined by their type (Audio Device ShareSet) and their specific system ID. + * A system ID could be reused for other device types on some OS or platforms, hence the compounded ID. + * + * @param in_szShareSet Audio Device ShareSet Name, as defined in the Wwise Project. If Null, will select the Default Output ShareSet (always available) + * @param in_idDevice Device specific identifier, when multiple devices of the same type are possible. If only one device is possible, leave to 0. + * @return The id of the output + */ + AkOutputDeviceID GetOutputID( + const FString& in_szShareSet, + AkUInt32 in_idDevice = 0 + ); + + + /** + * Replaces the main output device previously created during engine initialization with a new output device. + * In addition to simply removing one output device and adding a new one, the new output device will also be used on all of the master busses + * that the old output device was associated with, and preserve all listeners that were attached to the old output device. + * + * @param MainOutputSettings Creation parameters for this output + * + * @return + * - AK_InvalidID: The audioDeviceShareSet on in_settings was not valid. + * - AK_IDNotFound: The audioDeviceShareSet on in_settings doesn't exist. Possibly, the Init bank isn't loaded yet or was not updated with latest changes. + * - AK_DeviceNotReady: The idDevice on in_settings doesn't match with a valid hardware device. Either the device doesn't exist or is disabled. Disconnected devices (headphones) are not considered "not ready" therefore won't cause this error. + * - AK_DeviceNotFound: The in_outputDeviceId provided does not match with any of the output devices that the sound engine is currently using. + * - AK_InvalidParameter: Out of range parameters or unsupported parameter combinations on in_settings + * - AK_Success: parameters were valid, and the remove and add will occur. + */ + AKRESULT ReplaceMainOutput(const AkOutputSettings& MainOutputSettings); + + /** + * Gets speaker angles of the specified device. Speaker angles are used for 3D positioning of sounds over standard configurations. + * Note that the current version of Wwise only supports positioning on the plane. + * The speaker angles are expressed as an array of loudspeaker pairs, in degrees, relative to azimuth ]0,180]. + * Supported loudspeaker setups are always symmetric; the center speaker is always in the middle and thus not specified by angles. + * Angles must be set in ascending order. + * Typical usage: + * - AkReal32 heightAngle; + * - TArray speakerAngles; + * - GetSpeakerAngles(speakerAngles, heightAngle, AkOutput_Main ); + * \aknote + * On most platforms, the angle set on the plane consists of 3 angles, to account for 7.1. + * - When panning to stereo (speaker mode, see AK::SoundEngine::SetPanningRule()), only angle[0] is used, and 3D sounds in the back of the listener are mirrored to the front. + * - When panning to 5.1, the front speakers use angle[0], and the surround speakers use (angle[2] - angle[1]) / 2. + * \endaknote + * \warning Call this function only after the sound engine has been properly initialized. + * + * @param io_pfSpeakerAngles Returned array of loudspeaker pair angles, in degrees relative to azimuth [0,180]. Pass NULL to get the required size of the array. + * @param out_fHeightAngle Elevation of the height layer, in degrees relative to the plane [-90,90]. + * @param in_idOutput Output ID to set the bus on. As returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + * @return AK_Success if device exists + * + */ + AKRESULT GetSpeakerAngles( + TArray& io_pfSpeakerAngles, + AkReal32& out_fHeightAngle, + AkOutputDeviceID in_idOutput = 0 + ); + + /** + * Sets speaker angles of the specified device. Speaker angles are used for 3D positioning of sounds over standard configurations. + * Note that the current version of Wwise only supports positioning on the plane. + * The speaker angles are expressed as an array of loudspeaker pairs, in degrees, relative to azimuth ]0,180]. + * Supported loudspeaker setups are always symmetric; the center speaker is always in the middle and thus not specified by angles. + * Angles must be set in ascending order. + * Typical usage: + * - Initialize the sound engine and/or add secondary output(s). + * - Get number of speaker angles and their value into an array using GetSpeakerAngles(). + * - Modify the angles and call SetSpeakerAngles(). + * This function posts a message to the audio thread through the command queue, so it is thread safe. However the result may not be immediately read with GetSpeakerAngles(). + * \warning This function only applies to configurations (or subset of these configurations) that are standard and whose speakers are on the plane (2D). + * \sa GetSpeakerAngles() + * + * @param in_pfSpeakerAngles Array of loudspeaker pair angles, in degrees relative to azimuth [0,180] + * @param in_fHeightAngle Elevation of the height layer, in degrees relative to the plane [-90,90] + * @param in_idOutput Output ID to set the bus on. As returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + * @return AK_Success if successful (device exists and angles are valid), AK_NotCompatible if the channel configuration of the device is not standard (AK_ChannelConfigType_Standard), AK_Fail otherwise. + * + */ + AKRESULT SetSpeakerAngles( + const TArray& in_pfSpeakerAngles, + AkReal32 in_fHeightAngle, + AkOutputDeviceID in_idOutput = 0 + ); + + /** + * Set the output bus volume (direct) to be used for the specified game object. + * The control value is a number ranging from 0.0f to 1.0f. + * + * @param in_GameObjId Wwise Game Object ID + * @param in_fControlValue Control value to set + * @return Always returns Ak_Success + */ + AKRESULT SetGameObjectOutputBusVolume( + const UAkComponent* in_pEmitter, + const UAkComponent* in_pListener, + float in_fControlValue + ); + + /** + * Registers a callback that can run within the global callback at a specific AkGlobalCallbackLocation. + * + * @param Callback The callback that will be called. + * @param Location The location in the sound engine processing loop + * @return Returns the handle of the delegate that must be used to unregister the callback. + */ + FDelegateHandle RegisterGlobalCallback(FAkAudioDeviceDelegates::FOnAkGlobalCallback::FDelegate Callback, AkGlobalCallbackLocation Location); + + /** + * Unregisters a callback that can run within the global callback at a specific AkGlobalCallbackLocation. + * + * @param Handle The handle of the registered callback + * @param Location The location in the sound engine processing loop + */ + void UnregisterGlobalCallback(FDelegateHandle Handle, AkGlobalCallbackLocation Location); + + /** + * Registers a callback to be called to allow the game to access metering data from any output device. + * @param OutputID Output ID, as returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + * @param Callback Callback function + * @param MeteringFlags Metering flags + * @param Cookie User cookie + * @return AK_Success if callback has been registered + */ + AKRESULT RegisterOutputDeviceMeteringCallback(AkOutputDeviceID OutputID, + AkOutputDeviceMeteringCallbackFunc Callback, + AkMeteringFlags MeteringFlags, + void* Cookie); + + /** + * Unregisters the output device's metering callback + * @param OutputID + * @return AK_Success if callback has been unregistered + */ + AKRESULT UnregisterOutputDeviceMeteringCallback(AkOutputDeviceID OutputID); + + /** + * Obtain a pointer to the singleton instance of FAkAudioDevice + * + * @return Pointer to the singleton instance of FAkAudioDevice + */ + static FAkAudioDevice* Get(); + + + /** + * Is the Wwise SoundEngine initialized? + */ + static bool IsInitialized() { return m_bSoundEngineInitialized; } + + /** + * Gets the system sample rate + * + * @return Sample rate + */ + AkUInt32 GetSampleRate(); + + /** + * Enables/disables offline rendering + * + * @param bEnable Set to true to enable offline rendering + */ + AKRESULT SetOfflineRendering(bool bEnable); + + /** + * Sets the offline rendering frame time in seconds. + * + * @param FrameTimeInSeconds Frame time in seconds used during offline rendering + */ + AKRESULT SetOfflineRenderingFrameTime(AkReal32 FrameTimeInSeconds); + + /** + * Registers a callback used for retrieving audio samples. + * + * @param Callback Capture callback function to register + * @param OutputId The audio device specific id, return by AK::SoundEngine::AddOutput or AK::SoundEngine::GetOutputID + * @param Cookie Callback cookie that will be sent to the callback function along with additional information + */ + AKRESULT RegisterCaptureCallback(AkCaptureCallbackFunc Callback, AkOutputDeviceID OutputId = AK_INVALID_OUTPUT_DEVICE_ID, void* Cookie = nullptr); + + /** + * Unregisters a callback used for retrieving audio samples. + * + * @param Callback Capture callback function to register + * @param OutputId The audio device specific id, return by AK::SoundEngine::AddOutput or AK::SoundEngine::GetOutputID + * @param Cookie Callback cookie that will be sent to the callback function along with additional information + */ + AKRESULT UnregisterCaptureCallback(AkCaptureCallbackFunc Callback, AkOutputDeviceID OutputId = AK_INVALID_OUTPUT_DEVICE_ID, void* Cookie = nullptr); + + /** + * Stop all audio associated with a game object + * + * @param in_pComponent AkComponent which should be stopped + */ + void StopGameObject(UAkComponent * in_pComponent); + + /** + * Stop all audio associated with a playing ID + * + * @param in_playingID AkPlayingID which should be stopped + */ + void StopPlayingID( AkPlayingID in_playingID, + AkTimeMs in_uTransitionDuration = 0, + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear); + + /** + * Register an ak audio component with ak sound engine + * + * @param in_pComponent Pointer to the component to register + */ + void RegisterComponent(UAkComponent * in_pComponent); + + /** + * Register a game object with ak sound engine + * + * @param GameObjectID ID of the game object to register + */ + void RegisterComponent(AkGameObjectID GameObjectID); + + /** + * Unregister an ak audio component with ak sound engine + * + * @param in_pComponent Pointer to the component to unregister + */ + void UnregisterComponent(UAkComponent * in_pComponent); + + /** + * Unregister an ak game object with ak sound engine + * + * @param GameObjectID ID of the game object to unregister + */ + void UnregisterComponent(AkGameObjectID GameObjectID); + + /** + * Send a set of triangles to the Spatial Audio Engine + */ + AKRESULT SetGeometry(AkGeometrySetID GeometrySetID, const AkGeometryParams& Params); + + /** + * Create a geometry instance in Spatial Audio + */ + AKRESULT SetGeometryInstance(AkGeometryInstanceID GeometryInstanceID, const AkGeometryInstanceParams& Params); + + /** + * Remove a set of triangles from the Spatial Audio Engine + */ + AKRESULT RemoveGeometrySet(AkGeometrySetID GeometrySetID); + + /** + * Remove a geometry instance from Spatial Audio + */ + AKRESULT RemoveGeometryInstance(AkGeometryInstanceID GeometryInstanceID); + + /** + * Set the early reflections aux bus for an AK Component + */ + AKRESULT SetEarlyReflectionsAuxBus(UAkComponent* in_pComponent, const AkUInt32 AuxBusID); + + /** + * Set the early reflections send volume for an AK Component + */ + AKRESULT SetEarlyReflectionsVolume(UAkComponent* in_pComponent, float in_fSendVolume); + + /** + * Set the reflections order for the project + */ + AKRESULT SetReflectionsOrder(int Order, bool RefreshPaths); + + /** + * Sets a game object's obstruction and occlusion levels. + */ + AKRESULT SetObjectObstructionAndOcclusion(AkGameObjectID in_Object, AkGameObjectID in_listener, AkReal32 Obstruction, AkReal32 Occlusion); + + /** + * Sets a game object's obstruction and occlusion levels for each position defined by SetMultiplePositions. + */ + AKRESULT SetMultipleObstructionAndOcclusion(AkGameObjectID in_Object, AkGameObjectID in_listener, AkObstructionOcclusionValues* ObstructionAndOcclusionValues, AkUInt32 in_uNumObstructionAndOcclusion); + + /** + * Set obstruction and occlusion on sounds going through this portal + */ + AKRESULT SetPortalObstructionAndOcclusion(UAkPortalComponent* in_pPortal, float in_fObstructionValue, float in_fOcclusionValue); + + /** + * Set obstruction on sounds from this game object going through this portal + */ + AKRESULT SetGameObjectToPortalObstruction(UAkComponent* in_pComponent, UAkPortalComponent* in_pPortal, float in_fObstructionValue); + + /** + * Set obstruction on sounds from a first portal going through the next portal + */ + AKRESULT SetPortalToPortalObstruction(UAkPortalComponent* in_pPortal0, UAkPortalComponent* in_pPortal1, float in_fObstructionValue); + + /** @brief Sets an effect ShareSet on an output device + * + * Make sure the new effect ShareSet is included in a soundbank, and that sound bank is loaded. Otherwise you will see errors in the Capture Log. + * This function will replace existing effects of the audio device ShareSet. + * Audio device effects support is limited to one ShareSet per plug-in type at any time. + * Errors are reported in the Wwise Capture Log if the effect cannot be set on the output device. + * ShareSet must be in a loaded bank. + * + * @param InDeviceID Output ID, as returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + * @param InFXIndex Effect slot index (0-3) + * @param InFXShareSetID Effect ShareSet ID; pass AK_INVALID_UNIQUE_ID to use the effect from the Audio Device ShareSet. + * @return Always returns AK_Success + */ + AKRESULT SetOutputDeviceEffect(AkOutputDeviceID InDeviceID, AkUInt32 InFXIndex, AkUniqueID InFXShareSetID); + + /** @brief Sets an Effect ShareSet at the specified Bus and Effect slot index. + * + * The Bus can either be an Audio Bus or an Auxiliary Bus. + * This adds a reference on the audio node to an existing ShareSet. + * This function has unspecified behavior when adding an Effect to a currently playing + * Bus which does not have any effects, or removing the last Effect on a currently playing bus. + * Make sure the new effect ShareSet is included in a soundbank, and that sound bank is loaded. Otherwise you will see errors in the Capture Log. + * This function will replace existing Effects on the node. If the target node is not at + * the top of the hierarchy and is in the Actor-Mixer Hierarchy, the option "Override Parent" in + * the Effect section in Wwise must be enabled for this node, otherwise the parent's Effect will + * still be the one in use and the call to SetBusEffect will have no impact. + * ShareSet must be in a loaded bank. + * + * @param InBusName Bus name + * @param InFXIndex Effect slot index (0-3) + * @param InFXShareSetID Effect ShareSet ID; pass AK_INVALID_UNIQUE_ID to clear the Effect slot + * @return - AK_Success when successfully posted, AK_IDNotFound if the Bus name doesn't point to a valid bus, AK_InvalidParameter if in_uFXIndex isn't in range. + */ + AKRESULT SetBusEffect(const FString& InBusName, AkUInt32 InFXIndex, AkUniqueID InFXShareSetID); + + /** @briefSets an Effect ShareSet at the specified Bus and Effect slot index. + * + * The Bus can either be an Audio Bus or an Auxiliary Bus. + * This adds a reference on the audio node to an existing ShareSet. + * This function has unspecified behavior when adding an Effect to a currently playing + * Bus which does not have any effects, or removing the last Effect on a currently playing bus. + * Make sure the new effect ShareSet is included in a soundbank, and that sound bank is loaded. Otherwise you will see errors in the Capture Log. + * This function will replace existing Effects on the node. If the target node is not at + * the top of the hierarchy and is in the Actor-Mixer Hierarchy, the option "Override Parent" in + * the Effect section in Wwise must be enabled for this node, otherwise the parent's Effect will + * still be the one in use and the call to SetBusEffect will have no impact. + * ShareSet must be in a loaded bank. + * + * @param InBusID Bus Short ID. + * @param InFXIndex Effect slot index (0-3) + * @param InFXShareSetID Effect ShareSet ID; pass AK_INVALID_UNIQUE_ID to clear the Effect slot + * @return AK_Success when successfully posted, AK_IDNotFound if the Bus name doesn't point to a valid bus, AK_InvalidParameter if in_uFXIndex isn't in range. + */ + AKRESULT SetBusEffect(AkUniqueID InBusID, AkUInt32 InFXIndex, AkUniqueID InFXShareSetID); + + + /** @brief Sets an Effect ShareSet at the specified audio node and Effect slot index. + * + * The target node cannot be a Bus, to set effects on a bus, use SetBusEffect() instead. + * The option "Override Parent" in the Effect section in Wwise must be enabled for this node, otherwise the parent's effect will + * still be the one in use and the call to SetActorMixerEffect will have no impact. + * ShareSet must be in a loaded bank. + * + * @param InAudioNodeID Can be a member of the Actor-Mixer or Interactive Music Hierarchy (not a bus). + * @param InFXIndex Effect slot index (0-3) + * @param InShareSetID ShareSets ID; pass AK_INVALID_UNIQUE_ID to clear the effect slot + * @return Always returns AK_Success + */ + AKRESULT SetActorMixerEffect(AkUniqueID InAudioNodeID, AkUInt32 InFXIndex, AkUniqueID InShareSetID); + AKRESULT SetActorMixerEffect(const FString& InBusName, AkUInt32 InFXIndex, AkUniqueID InFXShareSetID); + + /** + * Get an ak audio component, or create it if none exists that fit the attachment criteria. + */ + static class UAkComponent* GetAkComponent( + class USceneComponent* AttachToComponent, FName AttachPointName, const FVector * Location, EAttachLocation::Type LocationType); + + static class UAkComponent* GetAkComponent( + class USceneComponent* AttachToComponent, FName AttachPointName, const FVector * Location, EAttachLocation::Type LocationType, bool& ComponentCreated); + + /** + * Cancel the callback cookie for a dispatched event + * + * @param in_cookie The cookie to cancel + */ + void CancelEventCallbackCookie(void* in_cookie); + + void CancelEventCallbackDelegate(const FOnAkPostEventCallback& in_Delegate); + + /** + * Set the scaling factor of a game object. + * Modify the attenuation computations on this game object to simulate sounds with a a larger or smaller area of effect. + */ + AKRESULT SetAttenuationScalingFactor(AActor* Actor, float ScalingFactor); + + /** + * Set the scaling factor of a AkComponent. + * Modify the attenuation computations on this game object to simulate sounds with a a larger or smaller area of effect. + */ + AKRESULT SetAttenuationScalingFactor(UAkComponent* AkComponent, float ScalingFactor); + + /** + * Use the position of a separate AkComponent for distance calculations for a specified listener. + * When SetDistanceProbe() is called, Wwise calculates distance attenuation and filtering + * based on the distance between the distance probe AkComponent and the sound source. + * In third-person perspective applications, the distance probe may be set to the player character's position, + * and the listener position to that of the camera. In this scenario, attenuation is based on + * the distance between the character and the sound, whereas panning, spatialization, and spread and focus calculations are base on the camera. + */ + AKRESULT SetDistanceProbe(UAkComponent* Listener, UAkComponent* DistanceProbe); + + /** + * Starts a Wwise output capture. The output file will be located in the same folder as the SoundBanks. + * @param Filename - The name to give to the output file. + */ + void StartOutputCapture(const FString& Filename); + + /** + * Add text marker in output capture file. + * @param MarkerText - The name text to put in the marker. + */ + void AddOutputCaptureMarker(const FString& MarkerText); + + /** + * Stops a Wwise output capture. The output file will be located in the same folder as the SoundBanks. + */ + void StopOutputCapture(); + + /** + * Starts a Wwise profiler capture. The output file will be located in the same folder as the SoundBanks. + * @param Filename - The name to give to the output file. + */ + void StartProfilerCapture(const FString& Filename); + + /** + * Stops a Wwise profiler capture. The output file will be located in the same folder as the SoundBanks. + */ + void StopProfilerCapture(); + + /** + * Allows to register a Wwise plugin from a DLL name and path + */ + AKRESULT RegisterPluginDLL(const FString& in_DllName, const FString& in_DllPath); + + /** + * Gets the path where the SoundBanks are located on disk + */ + FString GetBasePath(); + + /** + * Suspends the SoundEngine + * @param in_bRenderAnyway If set to true, audio processing will still occur, but not outputted. When set to false, no audio will be processed at all, even upon reception of RenderAudio(). + */ + void Suspend(bool in_bRenderAnyway = false); + + /** + * Return from a suspended state + */ + void WakeupFromSuspend(); + + /** + * Event called when the Wwise project is modified + */ + FOnWwiseProjectModification OnWwiseProjectModification; + + static inline void FVectorToAKVector( const FVector & in_vect, AkVector & out_vect ) + { +#if UE_5_0_OR_LATER + checkf(in_vect.X <= FLT_MAX && in_vect.Y <= FLT_MAX && in_vect.Z <= FLT_MAX, TEXT("FVectorToAKVector: Data truncation when converting from FVector to AkVector.")); +#endif + out_vect.X = in_vect.X; + out_vect.Y = in_vect.Y; + out_vect.Z = in_vect.Z; + } + + static inline AkVector FVectorToAKVector(const FVector& in_vect) + { +#if UE_5_0_OR_LATER + checkf(in_vect.X <= FLT_MAX && in_vect.Y <= FLT_MAX && in_vect.Z <= FLT_MAX, TEXT("FVectorToAKVector: Data truncation when converting from FVector to AkVector.")); +#endif + return AkVector{ (float)in_vect.X, (float)in_vect.Y, (float)in_vect.Z }; + } + + static inline void FVectorToAKVector64( const FVector & in_vect, AkVector64 & out_vect ) + { + out_vect.X = in_vect.X; + out_vect.Y = in_vect.Y; + out_vect.Z = in_vect.Z; + } + + static inline AkVector64 FVectorToAKVector64(const FVector& in_vect) + { + return AkVector64{ in_vect.X, in_vect.Y, in_vect.Z }; + } + + static inline AkExtent FVectorToAkExtent(const FVector& in_vect) + { +#if UE_5_0_OR_LATER + checkf(in_vect.X <= FLT_MAX && in_vect.Y <= FLT_MAX && in_vect.Z <= FLT_MAX, TEXT("FVectorToAkExtent: Data truncation when converting from FVector to AkExtent.")); +#endif + /* Unreal: right=y, up=z, front=x */ + return AkExtent{ (float)in_vect.Y, (float)in_vect.Z, (float)in_vect.X }; + } + + static inline void FVectorsToAKWorldTransform(const FVector& in_Position, const FVector& in_Front, const FVector& in_Up, AkWorldTransform& out_AkTransform) + { + // Convert from the UE axis system to the Wwise axis system + out_AkTransform.Set(FVectorToAKVector64(in_Position), FVectorToAKVector(in_Front), FVectorToAKVector(in_Up)); + } + + static inline void AKVectorToFVector(const AkVector & in_vect, FVector & out_vect) + { + out_vect.X = in_vect.X; + out_vect.Y = in_vect.Y; + out_vect.Z = in_vect.Z; + } + + static inline FVector AKVectorToFVector(const AkVector& in_vect) + { + return FVector(in_vect.X, in_vect.Y, in_vect.Z); + } + + // Note that this is a loss of precision due to conversion from 64-bit to 32-bit + static inline void AKVector64ToFVector(const AkVector64 & in_vect, FVector & out_vect) + { + out_vect.X = (float)in_vect.X; + out_vect.Y = (float)in_vect.Y; + out_vect.Z = (float)in_vect.Z; + } + + // Note that this is a loss of precision due to conversion from 64-bit to 32-bit + static inline FVector AKVector64ToFVector(const AkVector64& in_vect) + { + return FVector((float)in_vect.X, (float)in_vect.Y, (float)in_vect.Z); + } + + FAkJobWorkerScheduler* GetAkJobWorkerScheduler() { return &AkJobWorkerScheduler; } + + uint8 GetMaxAuxBus() const { return MaxAuxBus; } + + AkCallbackInfoPool* GetAkCallbackInfoPool() + { + return CallbackInfoPool; + } + +#if WITH_EDITOR + void SetMaxAuxBus(uint8 ValToSet) { MaxAuxBus = ValToSet; } +#endif + + static const int32 FIND_COMPONENTS_DEPTH_INFINITE = -1; + + /** Find UAkLateReverbComponents at a given location. */ + TArray FindLateReverbComponentsAtLocation(const FVector& Loc, const UWorld* in_World); + + /** Add a UAkLateReverbComponent to the spatial index data structure. */ + void IndexLateReverb(class UAkLateReverbComponent* ComponentToAdd); + + /** Remove a UAkLateReverbComponent from the spatial index data structure. */ + void UnindexLateReverb(class UAkLateReverbComponent* ComponentToRemove); + + /** Update the bounds of a UAkLateReverbComponent in the spatial index data structure. Must be called if the component's transform changes.*/ + void ReindexLateReverb(class UAkLateReverbComponent* ComponentToAdd); + + /** Get whether the given world has room registered in it. */ + bool WorldHasActiveRooms(UWorld* World); + + /** Find UAkRoomComponents at a given location. */ + TArray FindRoomComponentsAtLocation(const FVector& Loc, const UWorld* World); + + /** Return true if any UAkRoomComponents have been added to the prioritized list of rooms for the in_World**/ + bool UsingSpatialAudioRooms(const UWorld* World); + + /** Get the aux send values corresponding to a point in the world.**/ + void GetAuxSendValuesAtLocation(FVector Loc, TArray& AkAuxSendValues, const UWorld* in_World); + + /** Update all portals. */ + void UpdateAllSpatialAudioPortals(UWorld* InWorld); + + /** Queue an update for all portals in a world to reconnect to their front and back rooms */ + void PortalsNeedRoomUpdate(UWorld* World) { WorldsInNeedOfPortalRoomsUpdate.Add(World); } + + /** Register a Portal in AK Spatial Audio. Can be called again to update the portal parameters. */ + void SetSpatialAudioPortal(UAkPortalComponent* in_Portal); + + /** Remove a Portal from AK Spatial Audio */ + void RemoveSpatialAudioPortal(UAkPortalComponent* in_Portal); + + void OnActorSpawned(AActor* SpawnedActor); + + UAkComponentSet& GetDefaultListeners() { return m_defaultListeners; } + UAkComponentSet& GetDefaultEmitters() { return m_defaultEmitters; } + + void SetListeners(UAkComponent* in_pEmitter, const TArray& in_listenerSet); + void AddDefaultListener(UAkComponent* in_pListener); + void RemoveDefaultListener(UAkComponent* in_pListener); + void UpdateDefaultActiveListeners(); +#if WITH_EDITORONLY_DATA + FTransform GetEditorListenerPosition(int32 ViewIndex) const; +#endif + + /** Specifies which listener is used for Wwise Spatial Audio**/ + bool SetSpatialAudioListener(UAkComponent* in_pListener); + + /** Get the listener that has been choosen to be used for Wwise Spatial Audio**/ + UAkComponent* GetSpatialAudioListener() const; + + AKRESULT SetPosition(UAkComponent* in_akComponent, const AkSoundPosition& in_SoundPosition); + + /** Add a UAkRoomComponent to the spatial index data structure. */ + void IndexRoom(class UAkRoomComponent* ComponentToAdd); + + /** Remove a UAkRoomComponent from the spatial index data structure. */ + void UnindexRoom(class UAkRoomComponent* ComponentToRemove); + + /** Update the bounds of a UAkRoomComponent in the spatial index data structure. Must be called if the component's transform changes.*/ + void ReindexRoom(class UAkRoomComponent* ComponentToAdd); + + AKRESULT AddRoom(UAkRoomComponent* in_pRoom, const AkRoomParams& in_RoomParams); + AKRESULT UpdateRoom(UAkRoomComponent* in_pRoom, const AkRoomParams& in_RoomParams); + AKRESULT RemoveRoom(UAkRoomComponent* in_pRoom); + + AKRESULT SetGameObjectRadius(UAkComponent* in_akComponent, float in_outerRadius, float in_innerRadius); + + AKRESULT SetImageSource(class AAkSpotReflector* in_pSpotReflector, const AkImageSourceSettings& in_ImageSourceInfo, AkUniqueID in_AuxBusID, UAkComponent* in_AkComponent); + AKRESULT RemoveImageSource(class AAkSpotReflector* in_pSpotReflector, AkUniqueID in_AuxBusID, UAkComponent* in_AkComponent); + AKRESULT ClearImageSources(AkUniqueID in_AuxBusID = AK_INVALID_AUX_ID, UAkComponent* in_AkComponent = NULL); + + static void GetChannelConfig(AkChannelConfiguration ChannelConfiguration, AkChannelConfig& config); + static void GetChannelConfig(FAkChannelMask SpeakerConfiguration, AkChannelConfig& config); + + FAkEnvironmentIndex& GetRoomIndex() { return RoomIndex; } + + struct SetCurrentAudioCultureAsyncTask + { + enum CompletionType + { + LatentAction, + Callback + }; + + FWwiseLanguageCookedData Language; + FThreadSafeBool IsDone = false; + FThreadSafeBool Succeeded = false; + + SetCurrentAudioCultureAsyncTask(FWwiseLanguageCookedData NewLanguage, FSetCurrentAudioCultureAction* LatentAction); + SetCurrentAudioCultureAsyncTask(FWwiseLanguageCookedData NewLanguage, const FOnSetCurrentAudioCultureCompleted& CompletedCallback); + + bool Start(); + void Update(); + + private: + TSharedPtr LatentActionValidityToken; + CompletionType CompletionActionType; + FSetCurrentAudioCultureAction* SetAudioCultureLatentAction; + FOnSetCurrentAudioCultureCompleted SetAudioCultureCompletedCallback; + }; + + void AddPlayingID(uint32 EventID, uint32 PlayingID, EAkAudioContext AudioContext); + bool IsPlayingIDActive(uint32 EventID, uint32 PlayingID); + bool IsEventIDActive(uint32 EventID); + void RemovePlayingID(uint32 EventID, uint32 PlayingID); + void StopEventID(uint32 EventID); + + FOnSwitchValueLoaded& GetOnSwitchValueLoaded(uint32 SwitchID); + void BroadcastOnSwitchValueLoaded(UAkGroupValue* GroupValue); + void SetLocalOutput(); + + FAkComponentCallbackManager* GetCallbackManager() { return CallbackManager; } + AKRESULT RegisterGameObject(AkGameObjectID GameObjectID, const FString& Name); + + static void LoadAudioObjectsAfterInitialization(TWeakObjectPtr&& InAudioType); + void LoadDelayedObjects(); + +private: + bool EnsureInitialized(); + + /** Determine whether the Wwise sound engine should be updated for the given world type */ + static bool ShouldNotifySoundEngine(EWorldType::Type WorldType); + + void* AllocatePermanentMemory( int32 Size, /*OUT*/ bool& AllocatedInPool ); + + AKRESULT GetGameObjectID(AActor * in_pActor, AkGameObjectID& io_GameObject ); + + template + AkPlayingID PostEventWithCallbackPackageOnGameObjectId( + const AkUInt32 EventShortID, + const AkGameObjectID GameObjectID, + const TArray& ExternalSources, + FCreateCallbackPackage CreateCallbackPackage, + EAkAudioContext AudioContext + ); + + template + AkPlayingID PostEventWithCallbackPackageOnAkGameObject( + const AkUInt32 EventShortID, + UAkGameObject* GameObject, + const TArray& ExternalSources, + FCreateCallbackPackage CreateCallbackPackage, + EAkAudioContext AudioContext + ); + + template + AKRESULT SetMultiplePositions( + UAkComponent* in_pGameObjectAkComponent, + const TArray& in_aChannelConfigurations, + const TArray& in_aPositions, + AkMultiPositionType in_eMultiPositionType + ); + + // Overload allowing to modify StopWhenOwnerDestroyed after getting the AkComponent + AKRESULT GetGameObjectID(AActor * in_pActor, AkGameObjectID& io_GameObject, bool in_bStopWhenOwnerDestroyed ); + + /** Update the room connections for all portals */ + void UpdateRoomsForPortals(); + +#if WITH_EDITORONLY_DATA + UAkComponent* CreateListener(UWorld* World, FEditorViewportClient* ViewportClient = nullptr); + TArray ListenerTransforms; + UAkComponent* EditorListener = nullptr; + + /** + * Update Default Listeners when going in and out of Play mode in editor + */ + void EndPIE(const bool bIsSimulating); + void BeginPIE(const bool bIsSimulating); + void PausePIE(const bool bIsSimulating); + void ResumePie(const bool bIsSimulating); + void OnSwitchBeginPIEAndSIE(const bool bIsSimulating); + +#endif + + /** Map to track whether new reverbs or rooms have been added to worlds in the previous frame. + * AkComponent checks this in its TickComponent function and updates its assigned room and/or reverb volumes. + */ + TMap WorldVolumesUpdatedMap; + void SAComponentAddedRemoved(UWorld* World); + + /** We keep a spatial index of UAkLateReverbComponents sorted by priority for faster finding of reverb volumes at a specific location. + */ + FAkEnvironmentIndex LateReverbIndex; + + /** We keep a spatial index of Spatial audio Rooms for faster finding of reverb volumes at a specific location. + */ + FAkEnvironmentIndex RoomIndex; + + /** We keep track of the portals in each world so their rooms can be updated when room and portal parameters change. + */ + TMap> WorldPortalsMap; + + void CleanupComponentMapsForWorld(UWorld* World); + + bool FindWwiseLanguage(const FString& NewAudioCulture, FString& FoundWwiseLanguage); + void UpdateSetCurrentAudioCultureAsyncTasks(); + + static bool m_bSoundEngineInitialized; + UAkComponentSet m_defaultListeners; + UAkComponentSet m_defaultEmitters; + UAkComponent* m_SpatialAudioListener; + + bool m_isSuspended = false; + + uint8 MaxAuxBus; + + FAkComponentCallbackManager* CallbackManager; + AkCallbackInfoPool* CallbackInfoPool; + FAkJobWorkerScheduler AkJobWorkerScheduler; + FWwiseIOHook* IOHook = nullptr; + + static bool m_EngineExiting; + + /* WAAPI Callback handles. */ + FDelegateHandle ProjectLoadedHandle; + FDelegateHandle ConnectionLostHandle; + FDelegateHandle ClientBeginDestroyHandle; + + struct FWaapiSubscriptionIds + { + uint64 Renamed = 0; + uint64 PreDeleted = 0; + uint64 ChildRemoved = 0; + uint64 ChildAdded = 0; + uint64 Created = 0; + } WaapiSubscriptionIds; + + TArray AudioCultureAsyncTasks; + + TSet WorldsInNeedOfPortalRoomsUpdate; + +#if !WITH_EDITOR + TMap CachedUnrealToWwiseCulture; +#endif + static FCriticalSection EventToPlayingIDMapCriticalSection; + static TMap> EventToPlayingIDMap; + static TMap PlayingIDToAudioContextMap; + + static void PostEventAtLocationEndOfEventCallback(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo); + + static TMap OnSwitchValueLoadedMap; + + static TArray> AudioObjectsToLoadAfterInitialization; + +#if WITH_EDITORONLY_DATA +#ifndef AK_OPTIMIZED + static AkErrorMessageTranslator* m_UnrealErrorTranslator; +#if AK_SUPPORT_WAAPI + static AkWAAPIErrorMessageTranslator m_waapiErrorMessageTranslator; +#endif //AK_SUPPORT_WAAPI +#endif //AK_OPTIMIZED +#endif //WITH_EDITORONLY_DATA +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Public/AkAudioModule.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Public/AkAudioModule.h new file mode 100644 index 0000000..27f62d7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Public/AkAudioModule.h @@ -0,0 +1,80 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkAudioDevice.h" +#include "AkUEFeatures.h" +#include "Modules/ModuleManager.h" +#include "Containers/Ticker.h" + +/** + * The public interface to this module. In most cases, this interface is only public to sibling modules + * within this plugin. + */ +class IAkAudioModule : public IModuleInterface +{ + +public: + /** + * Singleton-like access to this module's interface. This is just for convenience! + * Beware of calling this during the shutdown phase, though. Your module might have been unloaded already. + * + * @return Returns singleton instance, loading the module on demand if needed + */ + static inline IAkAudioModule& Get() + { + return FModuleManager::LoadModuleChecked< IAkAudioModule >(TEXT("AkAudio")); + } + + /** + * Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true. + * + * @return True if the module is loaded and ready to use + */ + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded(TEXT("AkAudio")); + } +}; + +class AKAUDIO_API FAkAudioModule : public IAkAudioModule +{ +public: + static FAkAudioModule* AkAudioModuleInstance; + static FSimpleMulticastDelegate OnModuleInitialized; + bool bModuleInitialized; + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + FAkAudioDevice* GetAkAudioDevice() const; + void ReloadWwiseAssetData() const; + static void UpdateWwiseResourceLoaderSettings(); +#if WITH_EDITORONLY_DATA + static void ParseGeneratedSoundBankData(); +#endif + FAkAudioDevice* AkAudioDevice; + + /** Call to update AkAudioDevice. */ + FTickerDelegate OnTick; + + /** Handle for OnTick. */ + FTickerDelegateHandle TickDelegateHandle; +}; + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Public/AkCustomVersion.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Public/AkCustomVersion.h new file mode 100644 index 0000000..420269d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Public/AkCustomVersion.h @@ -0,0 +1,59 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkCustomVersion.h: +=============================================================================*/ +#pragma once + +#include "CoreMinimal.h" +#include "Misc/Guid.h" + +// Custom serialization version for all packages containing bank asset types +struct AKAUDIO_API FAkCustomVersion +{ + enum Type + { + // Before any version changes were made in the plugin + BeforeCustomVersionWasAdded = 0, + + AddedSpatialAudio = 1, + NewRTPCTrackDataContainer = 2, + + NewAssetManagement = 3, + + APIChange = 4, + + SpatialAudioExtentAPIChange = 5, + + SpatialAudioComponentisation = 6, + + ReverbAuxBusAutoAssignment = 7, + + SSOTAkAssetRefactor =8, + + // ------------------------------------------------------ + VersionPlusOne, + LatestVersion = VersionPlusOne - 1 + }; + + // The GUID for this custom version number + const static FGuid GUID; + +private: + FAkCustomVersion() {} +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Public/AkUnrealEditorHelper.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Public/AkUnrealEditorHelper.h new file mode 100644 index 0000000..f406074 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Public/AkUnrealEditorHelper.h @@ -0,0 +1,42 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#if WITH_EDITOR + +#include "Engine/EngineTypes.h" +#include "Input/Reply.h" + +DECLARE_DELEGATE_RetVal(FReply, FOnButtonClickedMigration); + +namespace AkUnrealEditorHelper +{ + AKAUDIO_API void ShowEventBasedPackagingMigrationDialog(FOnButtonClickedMigration in_OnclickedYes, FOnButtonClickedMigration in_OnclickedNo); + AKAUDIO_API void SanitizePath(FString& Path, const FString& PreviousPath, const FText& DialogMessage); + AKAUDIO_API bool SanitizeFolderPathAndMakeRelativeToContentDir(FString& Path, const FString& PreviousPath, const FText& DialogMessage); + + AKAUDIO_API bool SaveConfigFile(UObject* ConfigObject); + AKAUDIO_API void DeleteOldSoundBanks(); + + AKAUDIO_API FString GetLegacySoundBankDirectory(); + AKAUDIO_API FString GetContentDirectory(); + AKAUDIO_API void DeleteLegacySoundBanks(); + + extern AKAUDIO_API const TCHAR* LocalizedFolderName; +} +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Public/AkWaapiClient.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Public/AkWaapiClient.h new file mode 100644 index 0000000..119a57b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Public/AkWaapiClient.h @@ -0,0 +1,429 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkWaapiClient.h: Audiokinetic WAAPI interface object. +=============================================================================*/ + +#pragma once + +/*------------------------------------------------------------------------------------ + AkWaapiClient system headers. +------------------------------------------------------------------------------------*/ + +#include "AkInclude.h" +#include "HAL/Runnable.h" +#include "Dom/JsonObject.h" +#include "HAL/ThreadSafeBool.h" + +/*------------------------------------------------------------------------------------ +Dependencies, helpers & forward declarations. +------------------------------------------------------------------------------------*/ + +/** Defines WampEventCallback delegate interface */ +DECLARE_DELEGATE_TwoParams(WampEventCallback, uint64_t, TSharedPtr); + +/** Events fired when WAAPI connection is started or lost */ +DECLARE_EVENT(FAkWaapiClient, WwiseProjectLoaded); +DECLARE_EVENT(FAkWaapiClient, WAAPIConnectionLost); +DECLARE_EVENT(FAkWaapiClient, BeginDestroyClient); + +#define WAAPI_LOCAL_HOST_IP_STRING "127.0.0.1" +#define WAAPI_PORT 8080 + +/*------------------------------------------------------------------------------------ +- FAkWaapiClientConnectionHandler +------------------------------------------------------------------------------------*/ + +class FAkWaapiClient; + +struct KeyValueArgs +{ + const FString KeyArg; + const FString ValueArg; +}; + +class FAkWaapiClientConnectionHandler : public FRunnable +{ +public: + FAkWaapiClientConnectionHandler(FAkWaapiClient& in_Client); + ~FAkWaapiClientConnectionHandler(); + // FRunnable interface. + virtual bool Init() override; + virtual uint32 Run() override; + virtual void Stop() override; + virtual void Exit() override; + + void ResetReconnectionDelay(); + void RegisterAutoConnectChangedCallback(); + void Wake(); +private: + FThreadSafeBool ThreadShouldExit = false; + FThreadSafeCounter ReconnectDelay = 2; + FThreadSafeCounter ConnectionMonitorDelay = 7; + FThreadSafeCounter LogOutputCount = 0; + int m_iMaxReconnectDelay = 32; + FEvent* WaitEvent; + FCriticalSection AkSettingsSection; + FAkWaapiClient& m_Client; + bool hadConnection = false; + bool AttemptReconnect(); + FDelegateHandle AutoConnectChangedHandle; +}; + +/*------------------------------------------------------------------------------------ +AkWaapiClient audio device. +------------------------------------------------------------------------------------*/ + +class AKAUDIO_API FAkWaapiClient +{ +public: + + virtual ~FAkWaapiClient(); + + /** + * Initialize the singleton instance of FAkWaapiClient. + * The singleton instance owns a connection thread which will monitor the WAAPI connection. + * This function should therefore be called once, in the module startup function, such that WAAPI is ready to be used, if available. + */ + static void Initialize(); + + /** + * Obtain a pointer to the singleton instance of FAkWaapiClient. + * Be sure to call FAkWaapiClient::Initialize() first (i.e. during Module startup). + * + * @return Pointer to the singleton instance of FAkWaapiClient. + */ + static FAkWaapiClient* Get(); + + /** + * Delete the singleton instance of FAkWaapiClient. + */ + static void DeleteInstance(); + + /** + * Obtain a pointer to the delegate associated to the in_subscriptionId + * + * @return Pointer to the delegate associated to the in_subscriptionId + */ + WampEventCallback* GetWampEventCallback(const uint64_t& in_subscriptionId); + + + /** + * Allows clients to subscribe to notifications according to the event. + * + * @param in_jsonObject An FJsonObject that we want to convert to string. + * @param ou_jsonObjectString The result of conversation in string format. + * @return A boolean to ensure that the conversion was successfully done. + */ + static bool JsonObjectToString(const TSharedRef& in_jsonObject, FString& ou_jsonObjectString); + + bool IsConnected(); + bool AttemptConnection(); + + /** Checks whether the current project is dirty */ + static bool IsProjectDirty(); + + /** + * Allows clients to subscribe to notifications according to the event. + * + * @param in_uri The reference to the event that we would be aware of when it happens. + * @param in_options Optional flag to get more information about the event happened. + * @param in_callback A delegate to be executed during the subscription event. + * @param out_subscriptionId Gets the id of this subscription. + * @param out_result A JSON object that contains useful informations about the subscription process to a specific event, gets an error infos in case the subscription failed. + * @return A boolean to ensure that the subscription was successfully done. + */ + bool Subscribe(const char* in_uri, const FString& in_options, WampEventCallback in_callback, + uint64& out_subscriptionId, FString& out_result, int in_iTimeoutMs = 500); + bool Subscribe(const char* in_uri, const TSharedRef& in_options, WampEventCallback in_callback, + uint64& out_subscriptionId, TSharedPtr& out_result, int in_iTimeoutMs = 500); + + /** + * Unsubscribe to notifications + * + * @param in_subscriptionId Gets the id of the current subscription to the event from which we want to be unsubscribed. + * @param out_result A JSON object that contains useful informations about the unsubscription process from a specific event, gets an error infos in case the unsubscription failed. + * @return A boolean to ensure that the unsubscription was successfully done. + */ + bool Unsubscribe(const uint64_t& in_subscriptionId, FString& out_result, int in_iTimeoutMs = 500, bool in_bSilenceLog = false); + bool Unsubscribe(const uint64_t& in_subscriptionId, TSharedPtr& out_result, int in_iTimeoutMs = 500, bool in_bSilenceLog = false); + + /** + * Remove manually the WampEventCallback, used by the WAAPI picker when the connection is lost + * + * @param in_subscriptionId The subscription id to the event from which we want to be unsubscribed. + */ + bool RemoveWampEventCallback(const uint64_t in_subscriptionId); + + /** + * + * + * @param in_uri The Function that will be called when an event that we would be aware of happens. + * @param in_args The arguments referenced to the in_uri function. + * @param in_options Optional flag to get more information about the event happened. + * @param out_result A JSON object that contains useful informations about the Call process to a specific event, gets an error infos in case the Call fails. + * @return A boolean to ensure that the call was successfully passed. + */ + bool Call(const char* in_uri, const FString& in_args, const FString& in_options, FString& out_result, int in_iTimeoutMs = 500, bool silenceLog = false); + bool Call(const char* in_uri, const TSharedRef& in_args, const TSharedRef& in_options, + TSharedPtr& out_result, int in_iTimeoutMs = 500, bool silenceLog = false); + + /** + * Call WAAPI to change the object name form the path or the id of the object (inFromIdOrPath). + * + * @param inUri The Unique Resource Identifier used to indicate a specific action to WAAPI; i.e. ak::wwise::core::object::setName + * @param Values An array that contains the pair of field and field value; e.i. when asking WAAPI to rename an item, the arguments are like this : {{object,id},{value,newname}} + * @return A boolean to ensure that the call was successfully done. + */ + bool Call(const char* inUri, const TArray& Values, TSharedPtr& outJsonResult); + + /** Sets in_outParentGUID to the object ID of a parent of object in_objectGUID of type in_strType. */ + static void GetParentOfType(FGuid in_objectGUID, FGuid& in_outParentGUID, FString in_strType); + /** Gets the path of the currently loaded project in Wwise Authoring. */ + static bool GetProjectPath(TSharedPtr& inOutJsonReslut, FString& ProjectPath); + + WwiseProjectLoaded OnProjectLoaded; + WAAPIConnectionLost OnConnectionLost; + BeginDestroyClient OnClientBeginDestroy; + + /** This is called when the reconnection handler successfully connects to WAAPI. + * We check if the correct project is loaded on a background thread. If it is, we broadcast OnProjectLoaded. + * We also subscribe to ak::wwise::core::project::loaded in order to check the project whenever one is loaded. + * If an incorrect project is loaded we broadcast OnConnectionLost. + */ + void ConnectionEstablished(); + void BroadcastConnectionLost(); + static bool IsProjectLoaded(); + + /** + * Indicates that the connection to WAAPI should be stopped and shouldn't be reconnected. + */ + bool IsDisconnecting(); + bool AppIsExiting(); + + void SetConnectionClosing(bool isClosing); + + /** + * Since it's a singleton WaapiClient, we want to make sure these methods (copying constructor and assignment operator). + * are unacceptable otherwise we may accidentally get copies of our WaapiClient singleton appearing. + */ + FAkWaapiClient(FAkWaapiClient const&) = delete; + void operator=(FAkWaapiClient const&) = delete; + + //=================================================================================================== + // WAAPI enums + //=================================================================================================== + enum class WAAPIGetFromOption : AkUInt16 + { + ID = 0, + SEARCH, + PATH, + OF_TYPE, + QUERY + }; + + static FString GetFromOptionString(WAAPIGetFromOption from); + + enum class WAAPIGetTransformOption : AkUInt16 + { + SELECT = 0, + RANGE, + WHERE, + NONE + }; + + static FString GetTransformOptionString(WAAPIGetTransformOption transform); + + enum class WAAPIGetReturnOptionFlag : AkInt64 + { + ID = 0x1, + NAME = 0x2, + NOTES = 0x4, + TYPE = 0x8, + PATH = 0x10, + PARENT = 0x20, + OWNER = 0x40, + IS_PLAYABLE = 0x80, + SHORT_ID = 0x100, + CATEGORY = 0x200, + FILEPATH = 0x400, + WORKUNIT = 0x800, + CHILDREN_COUNT = 0x1000, + MUSIC_TRANSITION_ROOT = 0x2000, + MUSIC_PLAYLIST_ROOT = 0x4000, + SOUND_ORIGINAL_WAV_FILE_PATH = 0x8000, + SOUND_CONVERTED_WEM_FILE_PATH = 0x10000, + SOUNDBANK_BANK_FILE_PATH = 0x20000, + AUDIO_SOURCE_PLAYBACK_DURATION = 0x40000, + AUDIO_SOURCE_MAX_DURATION_SOURCE = 0x80000, + AUDIO_SOURCE_TRIM_VALUES = 0x100000, + WORKUNIT_IS_DEFAULT = 0x200000, + WORKUNIT_TYPE = 0x400000, + WORKUNIT_IS_DIRTY = 0x800000, + NUM_FLAGS = 0x19 + }; + + //=================================================================================================== + // WAAPI JSon Helpers + //=================================================================================================== + /** Returns a shared reference to a FJsonObject with the requested WAAPI FROM and TRANSFORM items. */ + static TSharedRef CreateWAAPIGetArgumentJson(WAAPIGetFromOption in_FromOption, TArray> in_FromItems, + WAAPIGetTransformOption in_TransformOption = WAAPIGetTransformOption::NONE, + TArray> in_TransformItems = TArray>()); + /** Returns a shared reference to a FJsonObject with the requested WAAPI RETURN options. */ + static TSharedRef CreateWAAPIGetReturnOptionsJson(AkInt64 ReturnOptions); + + static WAAPIGetReturnOptionFlag GetReturnOptionFlagValue(int in_iFlagIndex); + /** Returns the correct string for a given WAAPI RETURN option. */ + static FString GetReturnOptionString(WAAPIGetReturnOptionFlag returnOption); + /** Calls wwise.core.object.get with the given WAAPI FROM, RETURN, and TRANSFORM items.*/ + static bool WAAPIGet(WAAPIGetFromOption inFromField, + TArray> inFromItems, + AkInt64 inReturnOptionsFlags, + TSharedPtr& outJsonResult, + WAAPIGetTransformOption inTransformField = WAAPIGetTransformOption::NONE, + TArray> inTransformItems = TArray>(), + bool in_bSilenceLog = false); + /** Searches for an object of type in_sTypeName with name in_sName via WAAPI. */ + static bool GetGUIDForObjectOfTypeWithName(FGuid& io_GUID, const FString& in_sTypeName, const FString& in_sName); + /** Saves the Wwise project via WAAPI.*/ + static void SaveProject(); + + //=================================================================================================== + // WAAPI Strings + //=================================================================================================== + + struct WAAPIStrings + { + static const FString BACK_SLASH; + static const FString ID; + static const FString RETURN; + static const FString PATH; + static const FString FILEPATH; + static const FString FROM; + static const FString NAME; + static const FString TYPE; + static const FString CHILDREN; + static const FString CHILDREN_COUNT; + static const FString ANCESTORS; + static const FString DESCENDANTS; + static const FString WOKUNIT_TYPE; + static const FString FOLDER; + static const FString PHYSICAL_FOLDER; + static const FString SEARCH; + static const FString PARENT; + static const FString SELECT; + static const FString TRANSFORM; + static const FString OBJECT; + static const FString OBJECTS; + static const FString VALUE; + static const FString COMMAND; + static const FString TRANSPORT; + static const FString ACTION; + static const FString PLAY; + static const FString STOP; + static const FString STOPPED; + static const FString DISPLAY_NAME; + static const FString DELETE_ITEMS; + static const FString DRAG_DROP_ITEMS; + static const FString UNDO; + static const FString REDO; + static const FString STATE; + static const FString OF_TYPE; + static const FString PROJECT; + static const FString PROPERTY; + static const FString VOLUME; + static const FString FIND_IN_PROJECT_EXPLORER; + static const FString TRIMMED_DURATION; + }; + + struct WwiseTypeStrings + { + static const FString SOUND; + static const FString WORKUNIT; + }; + + struct AudioPeaksStrings + { + struct Args + { + static const FString OBJECT; + static const FString NUM_PEAKS; + static const FString TIME_FROM; + static const FString TIME_TO; + static const FString CROSS_CHANNEL_PEAKS; + }; + + struct Results + { + static const FString PEAKS_BINARY; + static const FString MAX_ABS_VALUE; + static const FString PEAKS_ARRAY_LENGTH; + static const FString PEAKS_DATA_SIZE; + }; + }; + + struct PropertyChangedStrings + { + struct RequiredOptions + { + static const FString OBJECT; + static const FString PROPERTY; + }; + struct OptionalOptions + { + static const FString RETURN; + static const FString PLATFORM; + }; + }; + + struct AudioSourceProperties + { + static const FString TRIM_END; + static const FString TRIM_BEGIN; + }; + + struct PlaybackDurationStrings + { + static const FString MIN; + static const FString MAX; + static const FString TYPE; + }; + + struct TrimValuesStrings + { + static const FString TRIM_BEGIN; + static const FString TRIM_END; + }; + + bool bIsWrongProjectLoaded = false; + +private: + /** + * Since it's a singleton WaapiClient, we want to make sure this method (default constructor). + * is inaccessible otherwise we may accidentally get instances of our WaapiClient singleton appearing. + */ + FAkWaapiClient(); + + /** Checks if the currently loaded Wwise project matches the project path set in AkSettings (Wwise plugin settings). + * NOTE: This function will block while Wwise has a modal window open. It should not be called on the Game thread. + */ + static bool CheckProjectLoaded(); + + struct FAkWaapiClientImpl* m_Impl; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Public/Wwise/Stats/AkAudio.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Public/Wwise/Stats/AkAudio.h new file mode 100644 index 0000000..afa8dc7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudio/Public/Wwise/Stats/AkAudio.h @@ -0,0 +1,36 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Stats/Stats.h" +#include "Wwise/Stats/NamedEvents.h" + +DECLARE_STATS_GROUP(TEXT("AkAudioDevice"), STATGROUP_AkAudioDevice, STATCAT_Wwise); +DECLARE_CYCLE_STAT_EXTERN(TEXT("Post Event Async"), STAT_PostEventAsync, STATGROUP_AkAudioDevice, AKAUDIO_API); + +AKAUDIO_API DECLARE_LOG_CATEGORY_EXTERN(LogAkAudio, Log, All); +AKAUDIO_API DECLARE_LOG_CATEGORY_EXTERN(LogWwiseMonitor, Log, All); + +#define SCOPED_AKAUDIO_EVENT(Text) SCOPED_WWISE_NAMED_EVENT(TEXT("Wwise::AkAudio"), Text) +#define SCOPED_AKAUDIO_EVENT_2(Text) SCOPED_WWISE_NAMED_EVENT_2(TEXT("Wwise::AkAudio"), Text) +#define SCOPED_AKAUDIO_EVENT_3(Text) SCOPED_WWISE_NAMED_EVENT_3(TEXT("Wwise::AkAudio"), Text) +#define SCOPED_AKAUDIO_EVENT_4(Text) SCOPED_WWISE_NAMED_EVENT_4(TEXT("Wwise::AkAudio"), Text) +#define SCOPED_AKAUDIO_EVENT_F(Format, ...) SCOPED_WWISE_NAMED_EVENT_F(TEXT("Wwise::AkAudio"), Format, __VA_ARGS__) +#define SCOPED_AKAUDIO_EVENT_F_2(Format, ...) SCOPED_WWISE_NAMED_EVENT_F_2(TEXT("Wwise::AkAudio"), Format, __VA_ARGS__) +#define SCOPED_AKAUDIO_EVENT_F_3(Format, ...) SCOPED_WWISE_NAMED_EVENT_F_3(TEXT("Wwise::AkAudio"), Format, __VA_ARGS__) +#define SCOPED_AKAUDIO_EVENT_F_4(Format, ...) SCOPED_WWISE_NAMED_EVENT_F_4(TEXT("Wwise::AkAudio"), Format, __VA_ARGS__) diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudioMixer/AkAudioMixer.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/AkAudioMixer/AkAudioMixer.Build.cs new file mode 100644 index 0000000..6637864 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudioMixer/AkAudioMixer.Build.cs @@ -0,0 +1,45 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; + +public class AkAudioMixer : ModuleRules +{ + public AkAudioMixer(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + + "AudioMixer", + "AudioMixerCore", +#if UE_5_0_OR_LATER + "BinkAudioDecoder", +#endif + + "AkAudio" + } + ); + + PublicDefinitions.Add("WITH_OGGVORBIS"); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudioMixer/Private/AkAudioMixer.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudioMixer/Private/AkAudioMixer.cpp new file mode 100644 index 0000000..8b9fa81 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudioMixer/Private/AkAudioMixer.cpp @@ -0,0 +1,46 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkAudioMixer.h" + +#include "AkMixerPlatform.h" +#include "AudioMixer.h" +#include "AudioMixerDevice.h" +#include "AkAudioModule.h" + +void FAkAudioMixerModule::StartupModule() +{ +} + + +void FAkAudioMixerModule::ShutdownModule() +{ +} + +// Implementation of IAudioDeviceModule +bool FAkAudioMixerModule::IsAudioMixerModule() const +{ + return true; +} + +Audio::IAudioMixerPlatformInterface* FAkAudioMixerModule::CreateAudioMixerPlatformInterface() +{ + return new FAkMixerPlatform(); +} + + +IMPLEMENT_MODULE( FAkAudioMixerModule, AkAudioMixer ) diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudioMixer/Private/AkMixerPlatform.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudioMixer/Private/AkMixerPlatform.cpp new file mode 100644 index 0000000..f5e35ed --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudioMixer/Private/AkMixerPlatform.cpp @@ -0,0 +1,520 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkMixerPlatform.h" + +#include "AkAudioEvent.h" +#include "AkAudioModule.h" +#include "AudioMixerTypes.h" +#include "CoreGlobals.h" +#include "UObject/UObjectGlobals.h" + +#include "AkSettings.h" +#include "AudioDevice.h" +#include "AudioMixerInputComponent.h" + +#if WITH_ENGINE +#include "AudioPluginUtilities.h" +#include "OpusAudioInfo.h" +#include "VorbisAudioInfo.h" +#include "ADPCMAudioInfo.h" +#endif // WITH_ENGINE + +#if UE_5_0_OR_LATER +#include "BinkAudioInfo.h" +#endif + + +DECLARE_LOG_CATEGORY_EXTERN(LogAkAudioMixer, Log, All); +DEFINE_LOG_CATEGORY(LogAkAudioMixer); + +FName FAkMixerPlatform::NAME_OGG(TEXT("OGG")); +FName FAkMixerPlatform::NAME_OPUS(TEXT("OPUS")); +FName FAkMixerPlatform::NAME_ADPCM(TEXT("ADPCM")); + + +FAkMixerPlatform::FAkMixerPlatform() : + AkAudioMixerInputComponent(nullptr), + bIsInitialized(false), + bIsDeviceOpen(false), + InputEvent(nullptr), + OutputBuffer(nullptr), + OutputBufferByteLength(0) +{ +#if !WITH_EDITOR + LoadVorbisLibraries(); +#endif +} + +FAkMixerPlatform::~FAkMixerPlatform() +{ + if (bIsInitialized) + { + TeardownHardware(); + } +} + +void FAkMixerPlatform::OnAkAudioModuleInit() +{ + Audio::FAudioMixerOpenStreamParams CurrentStreamParams = OpenStreamParams; + CloseAudioStream(); + OpenAudioStream(CurrentStreamParams); + StartAudioStream(); +} + +bool FAkMixerPlatform::OnNextBuffer(uint32 NumChannels, uint32 NumSamples, float** OutBufferToFill) +{ + if (!bIsDeviceInitialized + || !(AudioStreamInfo.StreamState == Audio::EAudioOutputStreamState::Open + || AudioStreamInfo.StreamState == Audio::EAudioOutputStreamState::Running)) + { + return false; + } + + checkf(OutputBufferByteLength == NumChannels * NumSamples * sizeof(float), TEXT("Please ensure the Wwise \"Samples per frame\" initialization setting matches the Unreal Audio \"Callback Buffer Size\" setting for the current platform.")); + + OutputBuffer = OutBufferToFill; + + ReadNextBuffer(); + + return true; +} + +bool FAkMixerPlatform::InitializeHardware() +{ + if (!FAkAudioDevice::IsInitialized()) + { + AkAudioModuleInitHandle = FAkAudioModule::OnModuleInitialized.AddRaw(this, &FAkMixerPlatform::OnAkAudioModuleInit); + } + +#if ENGINE_MAJOR_VERSION >= 5 + if(Audio::IAudioMixer::ShouldRecycleThreads()) + { + // Pre-create the null render device thread, so we can simple wake it up when we need it. + // Give it nothing to do, with a slow tick as the default, but ask it to wait for a signal to wake up. + // Ensuring this exists prevents a crash in FMixerNullCallback::Run if the callback does not already exist. + CreateNullDeviceThread([] {}, 1.0f, true); + } +#endif + + bIsInitialized = true; + + // Must always return true at Editor startup at least, since a failed + // initialization results in a failed initialized AudioDevice, which later + // results in a crash. + return true; +} + +bool FAkMixerPlatform::TeardownHardware() +{ + + if (!bIsInitialized) + { + return true; + } + + StopAudioStream(); + CloseAudioStream(); + + if (AkAudioMixerInputComponent) + { + delete AkAudioMixerInputComponent; + AkAudioMixerInputComponent = nullptr; + } + + AkAudioModuleInitHandle.Reset(); + + bIsInitialized = false; + return true; +} + +bool FAkMixerPlatform::IsInitialized() const +{ + return bIsInitialized; +} + +bool FAkMixerPlatform::GetNumOutputDevices(uint32& OutNumOutputDevices) +{ + // TODO Define constant + OutNumOutputDevices = 1; + return true; +} + +bool FAkMixerPlatform::GetOutputDeviceInfo(const uint32 InDeviceIndex, Audio::FAudioPlatformDeviceInfo& OutInfo) +{ + AkAudioFormat AudioFormat; + AkAudioMixerInputComponent->GetChannelConfig(AudioFormat); + + OutInfo.Format = Audio::EAudioMixerStreamDataFormat::Float; + OutInfo.Name = GetDefaultDeviceName(); + OutInfo.DeviceId = TEXT("WwiseDevice"); + OutInfo.NumChannels = AudioFormat.GetNumChannels(); + OutInfo.SampleRate = AudioFormat.uSampleRate; + + OutInfo.OutputChannelArray.SetNum(OutInfo.NumChannels); + + for (int32 ChannelNum = 0; ChannelNum < OutInfo.NumChannels; ++ChannelNum) + { + OutInfo.OutputChannelArray.Add(EAudioMixerChannel::Type(ChannelNum)); + } + + return true; +} +bool FAkMixerPlatform::GetDefaultOutputDeviceIndex(uint32& OutDefaultDeviceIndex) const +{ + OutDefaultDeviceIndex = AUDIO_MIXER_DEFAULT_DEVICE_INDEX; + return true; +} + +bool FAkMixerPlatform::OpenAudioStream(const Audio::FAudioMixerOpenStreamParams& Params) +{ + if (!bIsInitialized || AudioStreamInfo.StreamState != Audio::EAudioOutputStreamState::Closed) + { + return false; + } + + OpenStreamParams = Params; + + AudioStreamInfo.Reset(); + AudioStreamInfo.OutputDeviceIndex = OpenStreamParams.OutputDeviceIndex; + AudioStreamInfo.NumOutputFrames = OpenStreamParams.NumFrames; + AudioStreamInfo.NumBuffers = OpenStreamParams.NumBuffers; + AudioStreamInfo.AudioMixer = OpenStreamParams.AudioMixer; + + // Allow negotiating output format with the Sound Engine + if (!GetOutputDeviceInfo(AudioStreamInfo.OutputDeviceIndex, AudioStreamInfo.DeviceInfo)) + { + return false; + } + + OutputBufferByteLength = OpenStreamParams.NumFrames * AudioStreamInfo.DeviceInfo.NumChannels * GetAudioStreamChannelSize(); + + UE_LOG(LogAkAudioMixer, Verbose, TEXT("Opening Audio stream for device: %s"), *GetDeviceId()) + + AudioStreamInfo.StreamState = Audio::EAudioOutputStreamState::Open; + + + if (FAkAudioDevice::IsInitialized()) + { + bool bOpenStreamError = false; + + AkAudioMixerInputComponent = new FAudioMixerInputComponent(); + AkAudioMixerInputComponent->OnNextBuffer = FAkGlobalAudioInputDelegate::CreateRaw(this, &FAkMixerPlatform::OnNextBuffer); + + UAkSettings* AkSettings = GetMutableDefault(); + if (AkSettings != nullptr) + { + AkSettings->GetAudioInputEvent(InputEvent); + } + + if (!InputEvent) + { + UE_LOG(LogAkAudioMixer, Error, TEXT("Unable to open audio stream. Ak Audio Event is not set.")); + bOpenStreamError = true; + } + else + { + InputEvent->AddToRoot(); // Make sure the event can't be garbage collected. + AkPlayingID PlayingID = AkAudioMixerInputComponent->PostAssociatedAudioInputEvent(InputEvent); + + if (PlayingID == AK_INVALID_PLAYING_ID) + { + UE_LOG(LogAkAudioMixer, Error, TEXT("Unable to open audio stream. Could not post Ak Audio Event.")); + bOpenStreamError = true; + } + } + + if (!bOpenStreamError) + { + UE_LOG(LogAkAudioMixer, Verbose, TEXT("Opened Audio stream for device: %s"), *GetDeviceId()) + bIsDeviceOpen = true; + } + } + + else + { + UE_LOG(LogAkAudioMixer, Verbose, TEXT("Audio stream deferred for device %s, AkAudioDevice is not yet initialized"), *GetDeviceId()) + } + + // Must always return true Editor startup at least, since a failed + // initialization results in a failed initialized AudioDevice, which later + // results in a crash. + return true; +} + +bool FAkMixerPlatform::CloseAudioStream() +{ + if (AudioStreamInfo.StreamState == Audio::EAudioOutputStreamState::Closed) + { + return false; + } + + UE_LOG(LogAkAudioMixer, Verbose, TEXT("Closing Audio stream for device: %s"), *GetDeviceId()) + + if (!StopAudioStream()) + { + return false; + } + + if (bIsUsingNullDevice) + { + StopRunningNullDevice(); + } + + { + FScopeLock ScopedLock(&OutputBufferMutex); + OutputBuffer = nullptr; + OutputBufferByteLength = 0; + } + + if (FAkAudioDevice::IsInitialized()) + { + if (AkAudioMixerInputComponent) + { + AkAudioMixerInputComponent->PostUnregisterGameObject(); + delete AkAudioMixerInputComponent; + AkAudioMixerInputComponent = nullptr; + } + + if (InputEvent) + { + InputEvent->RemoveFromRoot(); // Allow garbage collection on the event. + InputEvent = nullptr; + } + } + + bIsDeviceOpen = false; + + AudioStreamInfo.StreamState = Audio::EAudioOutputStreamState::Closed; + + return true; +} + +bool FAkMixerPlatform::StartAudioStream() +{ + if (!bIsInitialized || (AudioStreamInfo.StreamState != Audio::EAudioOutputStreamState::Open + && AudioStreamInfo.StreamState != Audio::EAudioOutputStreamState::Stopped)) + { + return false; + } + + UE_LOG(LogAkAudioMixer, Verbose, TEXT("Starting Audio stream for device: %s"), *GetDeviceId()) + + BeginGeneratingAudio(); + + if (!bIsDeviceOpen) + { + check(!bIsUsingNullDevice); + StartRunningNullDevice(); + } + + check(AudioStreamInfo.StreamState == Audio::EAudioOutputStreamState::Running); + + return true; + +} + +bool FAkMixerPlatform::StopAudioStream() +{ + if (AudioStreamInfo.StreamState != Audio::EAudioOutputStreamState::Stopped + && AudioStreamInfo.StreamState != Audio::EAudioOutputStreamState::Closed) + { + + UE_LOG(LogAkAudioMixer, Verbose, TEXT("Stopping Audio stream for device: %s"), *GetDeviceId()) + + if (AudioStreamInfo.StreamState == Audio::EAudioOutputStreamState::Running) + { + StopGeneratingAudio(); + } + + check(AudioStreamInfo.StreamState == Audio::EAudioOutputStreamState::Stopped); + } + + return true; +} + +Audio::FAudioPlatformDeviceInfo FAkMixerPlatform::GetPlatformDeviceInfo() const +{ + return AudioStreamInfo.DeviceInfo; +} + +void FAkMixerPlatform::SubmitBuffer(const uint8* Buffer) +{ + FScopeLock ScopedLock(&OutputBufferMutex); + + if (OutputBuffer) + { + for (int32 Channel = 0; Channel < AudioStreamInfo.DeviceInfo.NumChannels; Channel++) + { + for (int32 Frame = 0; Frame < AudioStreamInfo.NumOutputFrames; Frame++) + { + FMemory::Memcpy(&OutputBuffer[Channel][Frame], + &Buffer[sizeof(float) * ((AudioStreamInfo.DeviceInfo.NumChannels * Frame) + Channel)], + sizeof(float)); + } + } + } +} + +#if UE_5_0_OR_LATER +FName FAkMixerPlatform::GetRuntimeFormat(const USoundWave* InSoundWave) const +{ + const FName RuntimeFormat = Audio::ToName(InSoundWave->GetSoundAssetCompressionType()); + + if (RuntimeFormat == Audio::NAME_PLATFORM_SPECIFIC) + { +#if defined(PLATFORM_PS5) && PLATFORM_PS5 + if (InSoundWave->IsStreaming() && InSoundWave->IsSeekable()) + { + return Audio::NAME_ADPCM; + } + + checkf(false, TEXT("Please set your Unreal audio sources to Streaming and Seekable in order to play them through Wwise on the PS5")); + return NAME_ADPCM; +#else + if (InSoundWave->IsStreaming()) + { + if (InSoundWave->IsSeekable()) + { + return Audio::NAME_ADPCM; + } + + return Audio::NAME_OPUS; + } + return Audio::NAME_OGG; +#endif + } + return RuntimeFormat; +} + +ICompressedAudioInfo* FAkMixerPlatform::CreateCompressedAudioInfo(const FName& InRuntimeFormat) const +{ + ICompressedAudioInfo* Decoder = nullptr; + + if (InRuntimeFormat == Audio::NAME_OGG) + { + Decoder = new FVorbisAudioInfo(); + } + else if (InRuntimeFormat == Audio::NAME_OPUS) + { + Decoder = new FOpusAudioInfo(); + } +#if WITH_BINK_AUDIO + else if (InRuntimeFormat == Audio::NAME_BINKA) + { + Decoder = new FBinkAudioInfo(); + } +#endif // WITH_BINK_AUDIO + else + { + Decoder = Audio::CreateSoundAssetDecoder(InRuntimeFormat); + } + ensureMsgf(Decoder != nullptr, TEXT("Failed to create a sound asset decoder for compression type: %s"), *InRuntimeFormat.ToString()); + return Decoder; +} + +#else // UE4.27 + +FName FAkMixerPlatform::GetRuntimeFormat(USoundWave* InSoundWave) +{ +#if WITH_ENGINE + check(InSoundWave); +#if defined(PLATFORM_PS5) && PLATFORM_PS5 + static FName NAME_ADPCM(TEXT("ADPCM")); + + if (InSoundWave->IsStreaming() && InSoundWave->IsSeekableStreaming()) + { + return NAME_ADPCM; + } + + checkf(false, TEXT("Please set your Unreal audio sources to Streaming and Seekable in order to play them through Wwise on the PS5")); + return NAME_ADPCM; +#else + if (InSoundWave->IsStreaming(nullptr)) + { + if (InSoundWave->IsSeekableStreaming()) + { + return NAME_ADPCM; + } + + return NAME_OPUS; + } + return NAME_OGG; +#endif +#else + checkNoEntry(); + return FName(); +#endif // WITH_ENGINE +} + +bool FAkMixerPlatform::HasCompressedAudioInfoClass(USoundWave* InSoundWave) +{ + return true; +} + +ICompressedAudioInfo* FAkMixerPlatform::CreateCompressedAudioInfo(USoundWave* InSoundWave) +{ +#if WITH_ENGINE + check(InSoundWave); + +#if defined(PLATFORM_PS5) && PLATFORM_PS5 + if (InSoundWave->IsStreaming() && InSoundWave->IsSeekableStreaming()) + { + return new FADPCMAudioInfo(); + } + + checkf(false, TEXT("Please set your Unreal audio sources to Streaming and Seekable in order to play them through Wwise on the PS5")); + return new FADPCMAudioInfo(); +#else + if (InSoundWave->IsStreaming()) + { + if (InSoundWave->IsSeekableStreaming()) + { + return new FADPCMAudioInfo(); + } + + return new FOpusAudioInfo(); + } + + if (InSoundWave->HasCompressedData(NAME_OGG)) + { + return new FVorbisAudioInfo(); + } + + return new FADPCMAudioInfo(); +#endif +#else + checkNoEntry(); + return nullptr; +#endif // WITH_ENGINE +} +#endif + +FString FAkMixerPlatform::GetDefaultDeviceName() { + static FString DefaultName(TEXT("Wwise Audio Mixer Device.")); + return DefaultName; +} + +FString FAkMixerPlatform::GetDeviceId() const +{ + return AudioStreamInfo.DeviceInfo.DeviceId; +} + +FAudioPlatformSettings FAkMixerPlatform::GetPlatformSettings() const +{ + return FAudioPlatformSettings::GetPlatformSettings(FPlatformProperties::GetRuntimeSettingsClassName()); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudioMixer/Private/AudioMixerInputComponent.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AkAudioMixer/Private/AudioMixerInputComponent.cpp new file mode 100644 index 0000000..226253a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudioMixer/Private/AudioMixerInputComponent.cpp @@ -0,0 +1,93 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AudioMixerInputComponent.h" +#include "AkAudioDevice.h" +#include "AkAudioInputManager.h" +#include "AkAudioEvent.h" + +#define AK_AUDIO_INPUT_EVENT_NAME "Play_UnrealAudio" + +FAudioMixerInputComponent::FAudioMixerInputComponent() : + PlayingID(AK_INVALID_PLAYING_ID) +{ + auto Device = FAkAudioDevice::Get(); + if (Device != nullptr) + { + Device->RegisterComponent(GetAkGameObjectID()); + } +} + +FAudioMixerInputComponent::~FAudioMixerInputComponent() +{ + if (OnNextBuffer.IsBound()) + { + OnNextBuffer.Unbind(); + } + auto Device = FAkAudioDevice::Get(); + Device->UnregisterComponent(GetAkGameObjectID()); +} + +bool FAudioMixerInputComponent::FillSamplesBuffer(uint32 NumChannels, uint32 NumSamples, float** BufferToFill) +{ + if (OnNextBuffer.IsBound()) + { + OnNextBuffer.Execute(NumChannels, NumSamples, BufferToFill); + } + return true; +} + +/** This callback is used to provide the Wwise sound engine with the required audio format. */ +void FAudioMixerInputComponent::GetChannelConfig(AkAudioFormat& OutAudioFormat) +{ + const int sampleRate = 48000; + OutAudioFormat.uSampleRate = sampleRate; + OutAudioFormat.channelConfig.SetStandard(AK_SPEAKER_SETUP_STEREO); + + UE_LOG(LogAkAudio, Log, TEXT("Wwise Channel configuration:")); + UE_LOG(LogAkAudio, Log, TEXT("Wwise Input Sample Rate: %d"), OutAudioFormat.uSampleRate); + UE_LOG(LogAkAudio, Log, TEXT("Wwise Channel num: %d"), 2); +} + +AkPlayingID FAudioMixerInputComponent::PostAssociatedAudioInputEvent(UAkAudioEvent* InputEvent) +{ + PlayingID = FAkAudioInputManager::PostAudioInputEvent( + InputEvent, + OnNextBuffer, + FAkGlobalAudioFormatDelegate::CreateRaw(this, &FAudioMixerInputComponent::GetChannelConfig), + EAkAudioContext::AlwaysActive); + + return PlayingID; +} + +void FAudioMixerInputComponent::PostUnregisterGameObject() +{ + auto Device = FAkAudioDevice::Get(); + if (Device != nullptr) + { + if (PlayingID != AK_INVALID_PLAYING_ID) + { + Device->StopPlayingID(PlayingID); + PlayingID = AK_INVALID_PLAYING_ID; + } + } +} + +AkGameObjectID FAudioMixerInputComponent::GetAkGameObjectID() const +{ + return (AkGameObjectID)this; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudioMixer/Public/AkAudioMixer.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudioMixer/Public/AkAudioMixer.h new file mode 100644 index 0000000..661e2e5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudioMixer/Public/AkAudioMixer.h @@ -0,0 +1,69 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" +#include "AudioDevice.h" + + + +/** + * The public interface to this module. In most cases, this interface is only public to sibling modules + * within this plugin. + */ +class IAkAudioMixerModule : public IAudioDeviceModule +{ + +public: + + /** + * Singleton-like access to this module's interface. This is just for convenience! + * Beware of calling this during the shutdown phase, though. Your module might have been unloaded already. + * + * @return Returns singleton instance, loading the module on demand if needed + */ + static inline IAkAudioMixerModule& Get() + { + return FModuleManager::LoadModuleChecked< IAkAudioMixerModule >( "AkAudioMixer" ); + } + + /** + * Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true. + * + * @return True if the module is loaded and ready to use + */ + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded( "AkAudioMixer" ); + } +}; + +class FAkAudioMixerModule : public IAkAudioMixerModule +{ + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + /** IAudioDeviceModule implementation */ + virtual bool IsAudioMixerModule() const override; + //virtual FAudioDevice* CreateAudioDevice() override; + virtual Audio::IAudioMixerPlatformInterface* CreateAudioMixerPlatformInterface() override; +}; + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudioMixer/Public/AkMixerPlatform.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudioMixer/Public/AkMixerPlatform.h new file mode 100644 index 0000000..cd058ce --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudioMixer/Public/AkMixerPlatform.h @@ -0,0 +1,82 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AudioMixer.h" +#include "WwiseDefines.h" + +class FAudioMixerInputComponent; +class UAkAudioEvent; + +class FAkMixerPlatform : public Audio::IAudioMixerPlatformInterface +{ +public: + FAkMixerPlatform(); + ~FAkMixerPlatform(); + +#if UE_5_0_OR_LATER + virtual FString GetPlatformApi() const override { return TEXT("AkMixerPlatform"); } +#else + virtual Audio::EAudioMixerPlatformApi::Type GetPlatformApi() const override { return Audio::EAudioMixerPlatformApi::Other; } +#endif + virtual bool InitializeHardware() override; + virtual bool TeardownHardware() override; + virtual bool IsInitialized() const override; + virtual bool GetNumOutputDevices(uint32& OutNumOutputDevices) override; + virtual bool GetOutputDeviceInfo(const uint32 InDeviceIndex, Audio::FAudioPlatformDeviceInfo& OutInfo) override; + virtual bool GetDefaultOutputDeviceIndex(uint32& OutDefaultDeviceIndex) const override; + virtual bool OpenAudioStream(const Audio::FAudioMixerOpenStreamParams& Params) override; + virtual bool CloseAudioStream() override; + virtual bool StartAudioStream() override; + virtual bool StopAudioStream() override; + virtual Audio::FAudioPlatformDeviceInfo GetPlatformDeviceInfo() const override; + virtual void SubmitBuffer(const uint8* Buffer) override; +#if UE_5_0_OR_LATER + virtual FName GetRuntimeFormat(const USoundWave* InSoundWave) const override; + virtual ICompressedAudioInfo* CreateCompressedAudioInfo(const FName& InRuntimeFormat) const override; +#else + virtual FName GetRuntimeFormat(USoundWave* InSoundWave) override; + virtual bool HasCompressedAudioInfoClass(USoundWave* InSoundWave) override; + virtual ICompressedAudioInfo* CreateCompressedAudioInfo(USoundWave* InSoundWave) override; +#endif + virtual bool SupportsRealtimeDecompression() const { return true; } + virtual FString GetDefaultDeviceName() override; + FString GetDeviceId() const; + virtual FAudioPlatformSettings GetPlatformSettings() const override; + +private: + FAudioMixerInputComponent* AkAudioMixerInputComponent; + bool bIsInitialized; + bool bIsDeviceOpen; + UAkAudioEvent* InputEvent; + float** OutputBuffer; + int OutputBufferByteLength; + FCriticalSection OutputBufferMutex; + FDelegateHandle AkAudioModuleInitHandle; + + void OnAkAudioModuleInit(); + bool OnNextBuffer(uint32 NumChannels, uint32 NumSamples, float** OutBufferToFill); + int32 GetAudioStreamChannelSize() { return sizeof(float); } + +private: + static FName NAME_OGG; + static FName NAME_OPUS; + static FName NAME_ADPCM; + +}; + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AkAudioMixer/Public/AudioMixerInputComponent.h b/Pawn_Unreal/Plugins/Wwise/Source/AkAudioMixer/Public/AudioMixerInputComponent.h new file mode 100644 index 0000000..cbdc16b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AkAudioMixer/Public/AudioMixerInputComponent.h @@ -0,0 +1,47 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "AkInclude.h" +#include "AkAudioInputManager.h" + +// FIXME Rename this class +class FAudioMixerInputComponent +{ + +public: + FAudioMixerInputComponent(); + ~FAudioMixerInputComponent(); + + + bool FillSamplesBuffer(uint32 NumChannels, uint32 NumSamples, float** BufferToFill); + /** This callback is used to provide the Wwise sound engine with the required audio format. */ + void GetChannelConfig(AkAudioFormat& OutAudioFormat); + + AkPlayingID PostAssociatedAudioInputEvent(class UAkAudioEvent* InputEvent); + void PostUnregisterGameObject(); + + FAkGlobalAudioInputDelegate OnNextBuffer; + +private: + AkGameObjectID GetAkGameObjectID() const; + + AkPlayingID PlayingID; + +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/AudiokineticTools.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/AudiokineticTools.Build.cs new file mode 100644 index 0000000..b7945eb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/AudiokineticTools.Build.cs @@ -0,0 +1,105 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; + +public class AudiokineticTools : ModuleRules +{ + public AudiokineticTools(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + PrivateIncludePaths.Add("AudiokineticTools/Private"); + PrivateIncludePaths.Add("AkAudio/Classes/GTE"); + PrivateIncludePathModuleNames.AddRange( + new string[] + { + "TargetPlatform", + "MainFrame", + "MovieSceneTools" + } + ); + + PublicIncludePathModuleNames.AddRange( + new string[] + { + "AssetTools", + "ContentBrowser", + "ToolMenus" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + + "ContentBrowser", +#if UE_4_26_OR_LATER + "ContentBrowserData", +#endif + "DesktopPlatform", + "DesktopWidgets", +#if UE_5_0_OR_LATER + "DeveloperToolSettings", +#endif + "DirectoryWatcher", +#if UE_5_0_OR_LATER + "EditorFramework", +#endif + "EditorStyle", + "InputCore", + "Json", + "LevelEditor", + "MovieScene", + "MovieSceneTools", + "MovieSceneTracks", + "Projects", + "PropertyEditor", + "RenderCore", +#if UE_4_26_OR_LATER + "RHI", +#endif + "Sequencer", + "SharedSettingsWidgets", + "Slate", + "SlateCore", + "SourceControl", + "ToolMenus", + "UnrealEd", + "WorkspaceMenuStructure", + "XmlParser", + + "AkAudio", + "WwiseProjectDatabase", + "WwiseResourceLoader", + "WwiseSoundEngine" + } + ); + + if (Target.bBuildWithEditorOnlyData) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "WwiseProjectDatabase" + } + ); + } + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/AssetManagement/AkMigrationCommandlet.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/AssetManagement/AkMigrationCommandlet.h new file mode 100644 index 0000000..94707da --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/AssetManagement/AkMigrationCommandlet.h @@ -0,0 +1,40 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "Commandlets/Commandlet.h" +#include "AkMigrationCommandlet.generated.h" + +/** + * + */ +UCLASS() +class AUDIOKINETICTOOLS_API UAkMigrationCommandlet : public UCommandlet +{ + GENERATED_BODY() +public: + + UAkMigrationCommandlet(); + + // UCommandlet interface + virtual int32 Main(const FString& Params) override; + +private: + void PrintHelp() const; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/AssetManagement/GenerateSoundBanksCommandlet.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/AssetManagement/GenerateSoundBanksCommandlet.h new file mode 100644 index 0000000..5452c9f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/AssetManagement/GenerateSoundBanksCommandlet.h @@ -0,0 +1,40 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "Commandlets/Commandlet.h" +#include "GenerateSoundBanksCommandlet.generated.h" + +/** + * + */ +UCLASS() +class AUDIOKINETICTOOLS_API UGenerateSoundBanksCommandlet : public UCommandlet +{ + GENERATED_BODY() +public: + + UGenerateSoundBanksCommandlet(); + + // UCommandlet interface + virtual int32 Main(const FString& Params) override; + +private: + void PrintHelp() const; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/AssetManagement/GeneratedSoundBanksDirectoryWatcher.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/AssetManagement/GeneratedSoundBanksDirectoryWatcher.h new file mode 100644 index 0000000..ebf100e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/AssetManagement/GeneratedSoundBanksDirectoryWatcher.h @@ -0,0 +1,80 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#include "DirectoryWatcher/Public/IDirectoryWatcher.h" +#include "Widgets/Notifications/SNotificationList.h" + +DECLARE_MULTICAST_DELEGATE(OnSoundBankGenerationDoneDelegate); + +class AUDIOKINETICTOOLS_API GeneratedSoundBanksDirectoryWatcher +{ +public: + bool DoesWwiseProjectExist(); + void CheckIfCachePathChanged(); + void Initialize(); + void Uninitialize(const bool bIsModuleShutdown = false); + void StartWatchers(); + + //Watch the SoundBankInfoCache.dat file. Changes to this file indicate that sound data generation is done + bool StartCacheWatcher(const FString& CachePath); + + //Watch for changes to GeneratedSoundbanks folder, triggering a countdown timer to parse once no more changes are detected after a certain delay + void StartSoundBanksWatcher(const FString& GeneratedSoundBanksFolder); + + void StopWatchers(); + void StopSoundBanksWatcher(); + void StopCacheWatcher(); + void RestartWatchers(); + void ConditionalRestartWatchers(); + bool ShouldRestartWatchers(); + + OnSoundBankGenerationDoneDelegate OnSoundBanksGenerated; + +private: + FString CachePath; + FString SoundBankDirectory; + void OnCacheChanged(const TArray& ChangedFiles); + void OnGeneratedSoundBanksChanged(const TArray& ChangedFiles); + + void TimerTick(float DeltaSeconds); + void EndParseTimer(); + void OnSoundBankGenerationDone() const; + void NotifyFilesChanged(); + void HideNotification(); + void UpdateNotificationOnGenerationComplete() const; + void UpdateNotification() const; + + FDelegateHandle GeneratedSoundBanksHandle; + FDelegateHandle CacheChangedHandle; + FDelegateHandle PostEditorTickHandle; + FDelegateHandle ProjectParsedHandle; + + FDelegateHandle SettingsChangedHandle; + FDelegateHandle UserSettingsChangedHandle; + + bool bParseTimerRunning = false; + float ParseTimer= 0; + const float ParseDelaySeconds = 10.0f; + + TSharedPtr NotificationItem; + + bool bCacheFolderExists = false; + bool bGeneratedSoundBanksFolderExists = false; + +}; + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/Factories/ActorFactoryAkAmbientSound.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/Factories/ActorFactoryAkAmbientSound.h new file mode 100644 index 0000000..c35d79e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/Factories/ActorFactoryAkAmbientSound.h @@ -0,0 +1,46 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + ActorFactoryAkAmbientSound.h: +=============================================================================*/ +#pragma once + +#include "ActorFactories/ActorFactory.h" +#include "ActorFactoryAkAmbientSound.generated.h" + +/*------------------------------------------------------------------------------------ + UActorFactoryAkAmbientSound +------------------------------------------------------------------------------------*/ +UCLASS(config=Editor, collapsecategories, hidecategories=Object, MinimalAPI) +class UActorFactoryAkAmbientSound : public UActorFactory +{ + GENERATED_BODY() + +public: + UActorFactoryAkAmbientSound(const class FObjectInitializer& ObjectInitializer); + + // Begin UActorFactory Interface + virtual void PostSpawnActor( UObject* Asset, AActor* NewActor ) override; + virtual void PostCreateBlueprint( UObject* Asset, AActor* CDO ) override; + virtual bool CanCreateActorFrom( const FAssetData& AssetData, FText& OutErrorMsg ) override; + virtual UObject* GetAssetFromActorInstance(AActor* ActorInstance) override; + // End UActorFactory Interface +}; + + + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/Factories/AkJsonFactory.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/Factories/AkJsonFactory.h new file mode 100644 index 0000000..9bfc4f0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/Factories/AkJsonFactory.h @@ -0,0 +1,64 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkJsonFactory.h: +=============================================================================*/ +#pragma once + +#include "Factories/Factory.h" +#include "AkJsonFactory.generated.h" + +/*------------------------------------------------------------------------------------ + UAkJsonFactory +------------------------------------------------------------------------------------*/ +UCLASS(hidecategories=Object) +class UAkJsonFactory : public UFactory +{ + GENERATED_BODY() + +public: + UAkJsonFactory(const class FObjectInitializer& ObjectInitializer); + +#if CPP + /*------------------------------------------------------------------------------------ + UFactory Interface + ------------------------------------------------------------------------------------*/ + /** + * Create a new instance + * + * @param Class The type of class to create + * @param InParent The parent class + * @param Name The name of the new instance + * @param Flags Creation flags + * @param Context Creation context + * @param Warn Warnings + * @return The new object if creation was successful, otherwise false + */ + virtual UObject* FactoryCreateNew(UClass* Class,UObject* InParent,FName Name,EObjectFlags Flags,UObject* Context,FFeedbackContext* Warn) override; +#endif + + /** + * @return true if this factory can deal with the file sent in. + */ + virtual bool FactoryCanImport(const FString& Filename) override; + virtual bool ShouldShowInNewMenu() const override; + +}; + + + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/GeneratedSoundBanksWarning.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/GeneratedSoundBanksWarning.h new file mode 100644 index 0000000..ec14ef2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/GeneratedSoundBanksWarning.h @@ -0,0 +1,37 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ + SGeneratedSoundBanksWarning.h +------------------------------------------------------------------------------------*/ +#pragma once + +#include "Widgets/Notifications/SNotificationList.h" +#include "Templates/SharedPointer.h" + +/*------------------------------------------------------------------------------------ + SGeneratedSoundBanksWarning +------------------------------------------------------------------------------------*/ +class FGeneratedSoundBanksWarning +{ + static TSharedPtr GeneratedSoundBanksWarning; +public: + FGeneratedSoundBanksWarning(); + void DisplayGeneratedSoundBanksWarning(); + void HideGeneratedSoundBanksNotification(); + void OpenSettingsMenu(); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/ReloadPopup.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/ReloadPopup.h new file mode 100644 index 0000000..953c6ee --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/ReloadPopup.h @@ -0,0 +1,33 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#include "Widgets/Notifications/SNotificationList.h" +#include "Templates/SharedPointer.h" + +/*------------------------------------------------------------------------------------ + FReloadPopup +------------------------------------------------------------------------------------*/ +class FReloadPopup +{ + static TSharedPtr RefreshNotificationItem; +public: + FReloadPopup(); + void NotifyProjectRefresh(); + void Reload(); + void HideRefreshNotification(); +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/WwiseBrowser/WwiseAssetDragDropOp.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/WwiseBrowser/WwiseAssetDragDropOp.h new file mode 100644 index 0000000..3695eea --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Classes/WwiseBrowser/WwiseAssetDragDropOp.h @@ -0,0 +1,62 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ + WwiseEventDragDropOp.h +------------------------------------------------------------------------------------*/ +#pragma once + +#include "Containers/Map.h" +#include "DragAndDrop/AssetDragDropOp.h" +#include "ContentBrowserDelegates.h" +#include "AssetTools/Public/AssetToolsModule.h" +#include "WwiseBrowser/WwiseBrowserHelpers.h" + +class FWwiseAssetDragDropOp : public FAssetDragDropOp +{ +public: + DRAG_DROP_OPERATOR_TYPE(FWwiseEventDragDropOp, FAssetDragDropOp) + + static TSharedRef New(TArray InAssetData, UActorFactory* ActorFactory = nullptr); + + static TSharedRef New(TArray InAssetData, TArray InAssetPaths, UActorFactory* ActorFactory); + + bool OnAssetViewDrop(const FAssetViewDragAndDropExtender::FPayload& Payload); + bool OnAssetViewDragOver(const FAssetViewDragAndDropExtender::FPayload& Payload); + void SaveAssets(); + void DeleteAssets(); + + virtual void OnDrop(bool bDropWasHandled, const FPointerEvent& MouseEvent) override; + + void SetCanDrop(const bool InCanDrop); + + void SetTooltipText(); + FText GetTooltipText() const; + + ~FWwiseAssetDragDropOp(); + +public: + FText CurrentHoverText; + bool CanDrop = true; + FAssetViewDragAndDropExtender* Extender = nullptr; + +private: + // Assets contained within the drag operation. Value is true if the asset existed prior to initiating the drop. + TArray WwiseAssetsToDrop; + bool bDroppedOnContentBrowser = false; + FString AssetViewDropTargetPackagePath; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AkAudioBankGenerationHelpers.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AkAudioBankGenerationHelpers.cpp new file mode 100644 index 0000000..1fe9aa8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AkAudioBankGenerationHelpers.cpp @@ -0,0 +1,147 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ + AkAudioBankGenerationHelpers.cpp: Wwise Helpers to generate banks from the editor and when cooking. +------------------------------------------------------------------------------------*/ + +#include "AkAudioBankGenerationHelpers.h" + +#include "AkAudioDevice.h" +#include "AkSettings.h" +#include "AkSettingsPerUser.h" +#include "AkUnrealHelper.h" +#include "IAudiokineticTools.h" +#include "AssetManagement/AkAssetDatabase.h" + +#include "Editor/UnrealEd/Public/ObjectTools.h" +#if UE_5_0_OR_LATER +#include "HAL/PlatformFileManager.h" +#else +#include "HAL/PlatformFilemanager.h" +#endif +#include "MainFrame/Public/Interfaces/IMainFrameModule.h" +#include "Misc/Paths.h" +#include "Misc/ScopedSlowTask.h" +#include "Slate/Public/Framework/Application/SlateApplication.h" +#include "SlateCore/Public/Widgets/SWindow.h" +#include "AssetManagement/WwiseProjectInfo.h" +#include "UI/SGenerateSoundBanks.h" + +#define LOCTEXT_NAMESPACE "AkAudio" + +namespace AkAudioBankGenerationHelper +{ + FString GetWwiseConsoleApplicationPath() + { + const UAkSettingsPerUser* AkSettingsPerUser = GetDefault(); + FString ApplicationToRun; + ApplicationToRun.Empty(); + + if (AkSettingsPerUser) + { +#if PLATFORM_WINDOWS + ApplicationToRun = AkSettingsPerUser->WwiseWindowsInstallationPath.Path; +#else + ApplicationToRun = AkSettingsPerUser->WwiseMacInstallationPath.FilePath; +#endif + if (FPaths::IsRelative(ApplicationToRun)) + { + ApplicationToRun = FPaths::ConvertRelativePathToFull(AkUnrealHelper::GetProjectDirectory(), ApplicationToRun); + } + if (!(ApplicationToRun.EndsWith(TEXT("/")) || ApplicationToRun.EndsWith(TEXT("\\")))) + { + ApplicationToRun += TEXT("/"); + } + +#if PLATFORM_WINDOWS + if (FPaths::FileExists(ApplicationToRun + TEXT("Authoring/x64/Release/bin/WwiseConsole.exe"))) + { + ApplicationToRun += TEXT("Authoring/x64/Release/bin/WwiseConsole.exe"); + } + else + { + ApplicationToRun += TEXT("Authoring/Win32/Release/bin/WwiseConsole.exe"); + } + ApplicationToRun.ReplaceInline(TEXT("/"), TEXT("\\")); +#elif PLATFORM_MAC + ApplicationToRun += TEXT("Contents/Tools/WwiseConsole.sh"); + ApplicationToRun = TEXT("\"") + ApplicationToRun + TEXT("\""); +#endif + } + + return ApplicationToRun; + } + + void CreateGenerateSoundDataWindow(bool ProjectSave) + { + if (!FApp::CanEverRender()) + { + return; + } + + if (AkAssetDatabase::Get().CheckIfLoadingAssets()) + { + return; + } + + TSharedRef WidgetWindow = SNew(SWindow) + .Title(LOCTEXT("AkAudioGenerateSoundData", "Generate SoundBanks")) + .ClientSize(FVector2D(600.f, 332.f)) + .SupportsMaximize(false).SupportsMinimize(false) + .SizingRule(ESizingRule::FixedSize) + .FocusWhenFirstShown(true); + + TSharedRef WindowContent = SNew(SGenerateSoundBanks); + if (!WindowContent->ShouldDisplayWindow()) + { + return; + } + + // Add our SGenerateSoundBanks to the window + WidgetWindow->SetContent(WindowContent); + + // Set focus to our SGenerateSoundBanks widget, so our keyboard keys work right away + WidgetWindow->SetWidgetToFocusOnActivate(WindowContent); + + // This creates a windows that blocks the rest of the UI. You can only interact with the "Generate SoundBanks" window. + // If you choose to use this, comment the rest of the function. + //GEditor->EditorAddModalWindow(WidgetWindow); + + // This creates a window that still allows you to interact with the rest of the editor. If there is an attempt to delete + // a UAkAudioBank (from the content browser) while this window is opened, the editor will generate a (cryptic) error message + TSharedPtr ParentWindow; + if (FModuleManager::Get().IsModuleLoaded("MainFrame")) + { + IMainFrameModule& MainFrame = FModuleManager::GetModuleChecked("MainFrame"); + ParentWindow = MainFrame.GetParentWindow(); + } + + if (ParentWindow.IsValid()) + { + // Parent the window to the main frame + FSlateApplication::Get().AddModalWindow(WidgetWindow, ParentWindow.ToSharedRef()); + } + else + { + // Spawn new window + FSlateApplication::Get().AddWindow(WidgetWindow); + } + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AkEventAssetBroker.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AkEventAssetBroker.h new file mode 100644 index 0000000..00c6dbd --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AkEventAssetBroker.h @@ -0,0 +1,60 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "ComponentAssetBroker.h" + +#include "AkAudioEvent.h" + +////////////////////////////////////////////////////////////////////////// +// FAkEventAssetBroker + +class FAkEventAssetBroker : public IComponentAssetBroker +{ +public: + UClass* GetSupportedAssetClass() override + { + return UAkAudioEvent::StaticClass(); + } + + virtual bool AssignAssetToComponent(UActorComponent* InComponent, UObject* InAsset) override + { + UAkComponent* AkComp = Cast(InComponent); + UAkAudioEvent* AkEvent = Cast(InAsset); + + if (AkComp && AkEvent) + { + AkComp->AkAudioEvent = AkEvent; + return true; + } + + return false; + } + + virtual UObject* GetAssetFromComponent(UActorComponent* InComponent) override + { + UAkComponent* AkComp = Cast(InComponent); + + if (AkComp) + { + return AkComp->AkAudioEvent; + } + return NULL; + } +}; + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AkUnrealAssetDataHelper.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AkUnrealAssetDataHelper.cpp new file mode 100644 index 0000000..419a559 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AkUnrealAssetDataHelper.cpp @@ -0,0 +1,226 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkUnrealAssetDataHelper.h" + +#include "AssetRegistry/AssetData.h" + +#include "AkAudioEvent.h" +#include "AkAuxBus.h" +#include "AkEffectShareSet.h" +#include "AkGroupValue.h" +#include "AkSettings.h" +#include "AkStateValue.h" +#include "AkSwitchValue.h" +#include "AkTrigger.h" +#include "IAudiokineticTools.h" +#include "PackageTools.h" +#include "Wwise/Metadata/WwiseMetadataStateGroup.h" +#include "Wwise/Metadata/WwiseMetadataSwitchGroup.h" +#include "Wwise/Ref/WwiseAnyRef.h" +#include "Wwise/Ref/WwiseRefType.h" + +namespace AkUnrealAssetDataHelper +{ + bool IsSameType(const FAssetData& AssetData, EWwiseItemType::Type ItemType) + { + return GetUClassName(ItemType) == GetAssetClassName(AssetData); + } + + FName GetUClassName(EWwiseItemType::Type ItemType) + { + UClass* Class = nullptr; + switch (ItemType) + { + case EWwiseItemType::Event: + Class = UAkAudioEvent::StaticClass(); + break; + case EWwiseItemType::AuxBus: + Class = UAkAuxBus::StaticClass(); + break; + case EWwiseItemType::AcousticTexture: + Class = UAkAcousticTexture::StaticClass(); + break; + case EWwiseItemType::State: + Class = UAkStateValue::StaticClass(); + break; + case EWwiseItemType::Switch: + Class = UAkSwitchValue::StaticClass(); + break; + case EWwiseItemType::GameParameter: + Class = UAkRtpc::StaticClass(); + break; + case EWwiseItemType::Trigger: + Class = UAkTrigger::StaticClass(); + break; + case EWwiseItemType::EffectShareSet: + Class = UAkEffectShareSet::StaticClass(); + break; + } + if (Class) + { +#if UE_5_1_OR_LATER + return Class->GetClassPathName().GetAssetName(); +#else + return Class->GetFName(); +#endif + } + return FName(); + } + + FName GetAssetClassName(const FAssetData& AssetData) + { +#if UE_5_1_OR_LATER + return AssetData.AssetClassPath.GetAssetName(); +#else + return AssetData.AssetClass; +#endif + } + + bool IsAssetAkAudioType(const FAssetData& AssetData) + { + auto Value = AssetData.TagsAndValues.FindTag(FName("WwiseGuid")); + return Value.IsSet(); + } + + bool IsAssetTransient(const FAssetData& AssetData) + { + return AssetData.PackagePath.ToString() == UPackageTools::SanitizePackageName(GetTransientPackage()->GetPathName()); + } + + void SetAssetClassName(FAssetData& AssetData, UClass* Class) + { +#if UE_5_1_OR_LATER + AssetData.AssetClassPath = Class->GetClassPathName(); +#else + AssetData.AssetClass = Class->GetFName(); +#endif + } + + FString GetAssetDefaultPackagePath(const FAssetData& AssetData) + { + if (auto AkAudioAsset = Cast(AssetData.GetAsset())) + { + return AkAudioAsset->GetAssetDefaultPackagePath().ToString(); + } + + return {}; + } + + FString GetAssetDefaultPackagePath(const FWwiseAnyRef* WwiseRef) + { + auto AkSettings = GetMutableDefault(); + if (!AkSettings) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Could not fetch AkSettings")); + return {}; + } + + auto WwiseRefType = WwiseRef->GetType(); + + const FString DefaultPath = AkSettings->DefaultAssetCreationPath; + + switch (WwiseRefType) + { + case EWwiseRefType::AcousticTexture: + return DefaultPath / "VirtualAcoustics"; + + case EWwiseRefType::AuxBus: + return DefaultPath / "AuxBusses/"; + + case EWwiseRefType::Event: + return DefaultPath / "Events/"; + + case EWwiseRefType::GameParameter: + return DefaultPath / "GameParameters/"; + + case EWwiseRefType::PluginShareSet: + return DefaultPath / "Sharesets/"; + + case EWwiseRefType::Switch: + { + FString GroupName = WwiseRef->GetSwitchGroup()->Name.ToString(); + return DefaultPath / "Switches/" / GroupName; + } + + case EWwiseRefType::State: + { + FString GroupName = WwiseRef->GetStateGroup()->Name.ToString(); + return DefaultPath / "States/" / GroupName; + } + + case EWwiseRefType::Trigger: + return DefaultPath / "Triggers/"; + + default: + return {}; + } + } + + FName GetAssetDefaultName(const FAssetData& AssetData) + { + if (auto AkAudioAsset = Cast(AssetData.GetAsset())) + { + return AkAudioAsset->GetAssetDefaultName(); + } + + return {}; + } + + FName GetAssetDefaultName(const FWwiseAnyRef* WwiseRef) + { + EWwiseRefType WwiseRefType = WwiseRef->GetType(); + FName WwiseName = WwiseRef->GetName(); + FNameBuilder DefaultName; + + switch (WwiseRefType) + { + case EWwiseRefType::AcousticTexture: + case EWwiseRefType::AuxBus: + case EWwiseRefType::Event: + case EWwiseRefType::GameParameter: + case EWwiseRefType::PluginShareSet: + case EWwiseRefType::Trigger: + return WwiseName; + + case EWwiseRefType::Switch: + { + FString GroupName = WwiseRef->GetSwitchGroup()->Name.ToString(); + DefaultName << GroupName << TEXT("-") << WwiseName; +#if UE_5_0_OR_LATER + return FName(DefaultName.ToView()); +#else + return FName(DefaultName.ToString()); +#endif + } + + case EWwiseRefType::State: + { + FString GroupName = WwiseRef->GetStateGroup()->Name.ToString(); + DefaultName << GroupName << TEXT("-") << WwiseName; +#if UE_5_0_OR_LATER + return FName(DefaultName.ToView()); +#else + return FName(DefaultName.ToString()); +#endif + } + + default: + return {}; + } + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkAssetDatabase.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkAssetDatabase.cpp new file mode 100644 index 0000000..a3fa8d0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkAssetDatabase.cpp @@ -0,0 +1,262 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AssetManagement/AkAssetDatabase.h" +#include "AkAudioType.h" +#include "AkAcousticTexture.h" +#include "AkAudioEvent.h" +#include "AkAuxBus.h" +#include "AkRtpc.h" +#include "AkStateValue.h" +#include "AkSwitchValue.h" +#include "AkTrigger.h" +#include "AkUnrealHelper.h" +#include "AkAudioDevice.h" +#include "AkSettingsPerUser.h" +#include "IAudiokineticTools.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "AssetToolsModule.h" +#include "Async/Async.h" +#include "Misc/FeedbackContext.h" +#include "ObjectTools.h" +#include "Logging/LogMacros.h" + +#define LOCTEXT_NAMESPACE "AkAudio" + +AkAssetDatabase& AkAssetDatabase::Get() +{ + static AkAssetDatabase instance; + return instance; +} + +AkAssetDatabase::AkAssetDatabase() +{ + AssetRegistryModule = &FModuleManager::LoadModuleChecked("AssetRegistry"); + AssetToolsModule = &FModuleManager::LoadModuleChecked("AssetTools"); +} + +bool AkAssetDatabase::FindAllAssets(TArray& OutData) +{ +#if UE_5_1_OR_LATER + AssetRegistryModule->Get().GetAssetsByClass(UAkAudioType::StaticClass()->GetClassPathName(), OutData, true); +#else + AssetRegistryModule->Get().GetAssetsByClass(UAkAudioType::StaticClass()->GetFName(), OutData, true); +#endif + return OutData.Num() > 0; +} + +bool AkAssetDatabase::FindAssets(const FGuid& AkGuid, TArray& OutData) +{ + TMultiMap Search; + Search.Add(GET_MEMBER_NAME_CHECKED(FWwiseObjectInfo, WwiseGuid), AkGuid.ToString(EGuidFormats::Digits)); + AssetRegistryModule->Get().GetAssetsByTagValues(Search, OutData); + + return OutData.Num() > 0; +} + + +bool AkAssetDatabase::FindAssets(const FString& AssetName, TArray& OutData) +{ + TMultiMap Search; + Search.Add(GET_MEMBER_NAME_CHECKED(FAssetData, AssetName), AssetName); + AssetRegistryModule->Get().GetAssetsByTagValues(Search, OutData); + + return OutData.Num() > 0; +} + +FAssetData AkAssetDatabase::FindAssetByObjectPath(const FSoftObjectPath& AssetPath) +{ +#if UE_5_1_OR_LATER + return AssetRegistryModule->Get().GetAssetByObjectPath(AssetPath); +#else + return AssetRegistryModule->Get().GetAssetByObjectPath(AssetPath.GetAssetPathName()); +#endif +} + +bool AkAssetDatabase::FindFirstAsset(const FGuid& AkGuid, FAssetData& OutAsset) +{ + TArray Assets; + if (FindAssets(AkGuid, Assets)) + { + OutAsset = Assets[0]; + return true; + } + return false; +} + +bool AkAssetDatabase::FindFirstAsset(const FString& AssetName, FAssetData& OutAsset) +{ + TArray Assets; + if (FindAssets(AssetName, Assets)) + { + OutAsset = Assets[0]; + return true; + } + return false; +} + +bool AkAssetDatabase::FindAssetsByGuidAndClass(const FGuid& AkGuid, const UClass* StaticClass, TArray& OutWwiseAssets) +{ + TMultiMap Search; + FARFilter Filter; +#if UE_5_1_OR_LATER + Filter.ClassPaths.Add(StaticClass->GetClassPathName()); +#else + Filter.ClassNames.Add(StaticClass->GetFName()); +#endif + Filter.bRecursiveClasses = true; + Filter.TagsAndValues.AddUnique(GET_MEMBER_NAME_CHECKED(FWwiseObjectInfo, WwiseGuid), AkGuid.ToString(EGuidFormats::Digits)); + AssetRegistryModule->Get().GetAssets(Filter, OutWwiseAssets); + + return OutWwiseAssets.Num() > 0; +} + +bool AkAssetDatabase::RenameAsset(const FGuid& Id, const FString& AssetName, + const FString& RelativePath) +{ + check(IsInGameThread()); + + auto parentPath = RelativePath; + + TArray AssetData; + if (!FindAssets(Id, AssetData)) + { + UE_LOG(LogAudiokineticTools, Verbose, TEXT("Can't find Wwise asset to rename with ID %s"), *Id.ToString()); + return false; + } + + TArray AssetsToRename; + + for (FAssetData Asset : AssetData) + { + if (Asset.AssetName.ToString() != AssetName || parentPath != Asset.PackagePath.ToString()) + { + if (parentPath.IsEmpty()) + { + parentPath = Asset.PackagePath.ToString(); + } + + FAssetRenameData NewAssetRenameData(Asset.GetAsset(), parentPath, AssetName); + AssetsToRename.Add(NewAssetRenameData); + UE_LOG(LogAudiokineticTools, Verbose, TEXT("Renaming Wwise asset %s"), *AssetName); + } + } + + if (!AssetToolsModule->Get().RenameAssets(AssetsToRename)) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to rename Wwise Assets")); + return false; + } + + return true; +} + +void AkAssetDatabase::DeleteAsset(const FGuid& Id) +{ + check(IsInGameThread()); + + TArray AssetsToDelete; + + if (FindAssets(Id, AssetsToDelete)) + { + ObjectTools::DeleteAssets(AssetsToDelete, true); + } +} + +void AkAssetDatabase::DeleteAssets(const TSet& AssetsId) +{ + + for (auto& ID : AssetsId) + { + DeleteAsset(ID); + } +} + + +void AkAssetDatabase::FixUpRedirectors(const FString& AssetPackagePath) +{ + TArray redirectorsToFix; + + TArray foundRedirectorsData; +#if UE_5_1_OR_LATER + AssetRegistryModule->Get().GetAssetsByClass(UObjectRedirector::StaticClass()->GetClassPathName(), foundRedirectorsData); +#else + AssetRegistryModule->Get().GetAssetsByClass(UObjectRedirector::StaticClass()->GetFName(), foundRedirectorsData); +#endif + + if (foundRedirectorsData.Num() > 0) + { + for (auto& entry : foundRedirectorsData) + { + if (auto redirector = Cast(entry.GetAsset())) + { + if (redirector->DestinationObject) + { + auto pathName = redirector->DestinationObject->GetPathName(); + if (pathName.StartsWith(AssetPackagePath)) + { + redirectorsToFix.Add(redirector); + } + } + } + } + } + + if (redirectorsToFix.Num() > 0) + { + AssetToolsModule->Get().FixupReferencers(redirectorsToFix); + } +} + +bool AkAssetDatabase::IsAkAudioType(const FAssetData& AssetData) +{ +#if UE_5_1_OR_LATER + static const TArray AkAudioClassPaths = { + UAkAcousticTexture::StaticClass()->GetClassPathName(), + UAkAudioEvent::StaticClass()->GetClassPathName(), + UAkAuxBus::StaticClass()->GetClassPathName(), + UAkRtpc::StaticClass()->GetClassPathName(), + UAkStateValue::StaticClass()->GetClassPathName(), + UAkSwitchValue::StaticClass()->GetClassPathName(), + UAkTrigger::StaticClass()->GetClassPathName() + }; + + if (AkAudioClassPaths.Contains(AssetData.AssetClassPath)) + return true; +#else + static const TArray AkAudioClassNames = { + UAkAcousticTexture::StaticClass()->GetFName(), + UAkAudioEvent::StaticClass()->GetFName(), + UAkAuxBus::StaticClass()->GetFName(), + UAkRtpc::StaticClass()->GetFName(), + UAkStateValue::StaticClass()->GetFName(), + UAkSwitchValue::StaticClass()->GetFName(), + UAkTrigger::StaticClass()->GetFName() + }; + + if (AkAudioClassNames.Contains(AssetData.AssetClass)) + return true; +#endif + return false; +} + +bool AkAssetDatabase::CheckIfLoadingAssets() +{ + return AssetRegistryModule->Get().IsLoadingAssets(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkAssetDatabase.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkAssetDatabase.h new file mode 100644 index 0000000..27173aa --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkAssetDatabase.h @@ -0,0 +1,60 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "HAL/CriticalSection.h" +#include "Misc/Guid.h" +#include "AssetRegistry/AssetData.h" + +class FAssetRegistryModule; +class FAssetToolsModule; +struct FAssetRenameData; + +class AUDIOKINETICTOOLS_API AkAssetDatabase +{ +public: + static AkAssetDatabase& Get(); + + bool FindAllAssets(TArray& OutData); + bool FindAssets(const FGuid& AkGuid, TArray& OutData); + bool FindAssets(const FString& AkAssetName, TArray& OutData); + FAssetData FindAssetByObjectPath(const FSoftObjectPath& AssetPath); + bool FindFirstAsset(const FGuid& AkGuid, FAssetData& OutAsset); + bool FindFirstAsset(const FString& AkAssetName, FAssetData& OutAsset); + bool FindAssetsByGuidAndClass(const FGuid& AkGuid, const UClass* StaticClass, TArray& OutWwiseAssets); + + bool RenameAsset(const FGuid& Id, const FString& AssetName, const FString& RelativePath); + + void DeleteAsset(const FGuid& Id); + void DeleteAssets(const TSet& AssetsId); + + void FixUpRedirectors(const FString& AssetPackagePath); + + bool CheckIfLoadingAssets(); + + mutable FCriticalSection InitBankLock; + +private: + AkAssetDatabase(); + + bool IsAkAudioType(const FAssetData& AssetData); + +private: + FAssetRegistryModule* AssetRegistryModule = nullptr; + FAssetToolsModule* AssetToolsModule = nullptr; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkAssetMigrationHelper.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkAssetMigrationHelper.cpp new file mode 100644 index 0000000..39a6e9e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkAssetMigrationHelper.cpp @@ -0,0 +1,877 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkAssetMigrationHelper.h" + +#include "AkAssetDatabase.h" +#include "AkAudioEvent.h" +#include "AkAuxBus.h" +#include "AkDeprecated.h" +#include "AkAudioBank.h" +#include "AkWaapiClient.h" +#include "AkWaapiUtils.h" +#include "AkMigrationWidgets.h" +#include "AkCustomVersion.h" +#include "AkUnrealEditorHelper.h" +#include "AkUnrealHelper.h" +#include "WwiseDefines.h" +#include "IAudiokineticTools.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "AssetTools/Public/AssetToolsModule.h" + +#include "GenericPlatform/GenericPlatformFile.h" +#if UE_5_0_OR_LATER +#include "HAL/PlatformFileManager.h" +#else +#include "HAL/PlatformFilemanager.h" +#endif +#include "UnrealEd/Public/ObjectTools.h" +#include "UnrealEd/Public/FileHelpers.h" +#include "Misc/FileHelper.h" +#include "IDesktopPlatform.h" +#include "DesktopPlatformModule.h" +#include "Interfaces/IMainFrameModule.h" +#include "Mathematics/APConversion.h" +#include "Misc/App.h" + +#define LOCTEXT_NAMESPACE "AkAudio" + +namespace AkAssetMigration +{ + void PromptMigration(const FMigrationContext& MigrationOptions, FMigrationOperations& OutMigrationOperations) + { + if (!FApp::CanEverRender()) + { + OutMigrationOperations.bCancelled =true; + return; + } + + TSharedPtr Dialog = SNew(SWindow) + .Title(LOCTEXT("BankMigrationDialog", "Wwise Integration Migration")) + .SupportsMaximize(false) + .SupportsMinimize(false) + .FocusWhenFirstShown(true) + .HasCloseButton(false) + .SizingRule(ESizingRule::Autosized); + + TSharedPtr MigrationWidget; + + Dialog->SetContent( + SAssignNew(MigrationWidget, SMigrationWidget) + .Dialog(Dialog) + .ShowBankTransfer(MigrationOptions.bBanksInProject) + .ShowDeprecatedAssetCleanup(MigrationOptions.bDeprecatedAssetsInProject) + .ShowAssetMigration(MigrationOptions.bAssetsNeedMigration) + .ShowProjectMigration(MigrationOptions.bProjectSettingsNotUpToDate) + .NumDeprecatedAssets(MigrationOptions.NumDeprecatedAssetsInProject) + ); + + FSlateApplication::Get().AddModalWindow(Dialog.ToSharedRef(), nullptr); + + if (MigrationOptions.bBanksInProject) + { + OutMigrationOperations.BankTransferMethod = MigrationWidget->BankTransferWidget->BankTransferMethod; + OutMigrationOperations.bDoBankCleanup = MigrationWidget->BankTransferWidget->DeleteSoundBanksCheckBox->IsChecked(); + OutMigrationOperations.bTransferAutoload = MigrationWidget->BankTransferWidget->TransferAutoLoadCheckBox->IsChecked(); + OutMigrationOperations.DefinitionFilePath = MigrationWidget->BankTransferWidget->SoundBankDefinitionFilePath; + } + if (MigrationOptions.bDeprecatedAssetsInProject) + { + OutMigrationOperations.bDoDeprecatedAssetCleanup = MigrationWidget->DeprecatedAssetCleanupWidget->DeleteAssetsCheckBox->IsChecked(); + } + if (MigrationOptions.bAssetsNeedMigration) + { + OutMigrationOperations.bDoAssetMigration = MigrationWidget->AssetMigrationWidget->MigrateAssetsCheckBox->IsChecked(); + } + if (MigrationOptions.bProjectSettingsNotUpToDate) + { + OutMigrationOperations.bDoProjectUpdate = MigrationWidget->ProjectMigrationWidget->AutoMigrateCheckbox->IsChecked(); + OutMigrationOperations.GeneratedSoundBankDirectory = MigrationWidget->ProjectMigrationWidget->GeneratedSoundBanksFolderPickerWidget->GetDirectory(); + } + OutMigrationOperations.bCancelled = MigrationWidget->bCancel; + } + + bool PromptFailedBankTransfer(const FString& ErrorMessage) + { + if (!FApp::CanEverRender()) + { + return false; + } + TSharedPtr Dialog = SNew(SWindow) + .Title(LOCTEXT("BankTransfer", "Bank Transfer Failure")) + .SupportsMaximize(false) + .SupportsMinimize(false) + .FocusWhenFirstShown(true) + .HasCloseButton(false) + .SizingRule(ESizingRule::Autosized); + + TSharedPtr FailedBankTransferWidget; + + Dialog->SetContent( + SAssignNew(FailedBankTransferWidget, SBankMigrationFailureWidget) + .Dialog(Dialog) + .ErrorMessage(FText::Format( LOCTEXT("BankTransferErrorMessage", "{0}"), FText::FromString(ErrorMessage))) + ); + + FSlateApplication::Get().AddModalWindow(Dialog.ToSharedRef(), nullptr); + return !FailedBankTransferWidget->bCancel; + } + + + FString FormatWaapiErrorMessage(const TArray& ErrorMessages) + { + FString CombinedErrors; + for (auto BankErrors : ErrorMessages) + { + if (BankErrors.bHasBankEntry) + { + CombinedErrors += "SoundBank: " + BankErrors.BankEntry.BankAssetData.AssetName.ToString() + "\n"; + } + CombinedErrors += "Error: " + BankErrors.ErrorMessage + "\n"; + if (BankErrors.bHasBankEntry) + { + CombinedErrors += "Wwise Assets in SoundBank : \n"; + + for (auto LinkedAsset : BankErrors.BankEntry.LinkedEvents) + { + CombinedErrors += FString::Format(TEXT("GUID: {0} - Name: {1}\n"), { LinkedAsset.WwiseGuid.ToString(), LinkedAsset.AssetName }); + } + for (auto LinkedAsset : BankErrors.BankEntry.LinkedAuxBusses) + { + CombinedErrors += FString::Format(TEXT("GUID: {0} - Name: {1}\n"), { LinkedAsset.WwiseGuid.ToString(), LinkedAsset.AssetName }); + } + } + } + return CombinedErrors; + } + + void FindDeprecatedAssets(TArray& OutDeprecatedAssets) + { + OutDeprecatedAssets.Empty(); + FARFilter Filter; +#if UE_5_1_OR_LATER + Filter.ClassPaths.Add(UAkMediaAsset::StaticClass()->GetClassPathName()); + Filter.ClassPaths.Add(UAkLocalizedMediaAsset::StaticClass()->GetClassPathName()); + Filter.ClassPaths.Add(UAkExternalMediaAsset::StaticClass()->GetClassPathName()); + Filter.ClassPaths.Add(UAkFolder::StaticClass()->GetClassPathName()); + Filter.ClassPaths.Add(UAkAssetPlatformData::StaticClass()->GetClassPathName()); +#else + Filter.ClassNames.Add(UAkMediaAsset::StaticClass()->GetFName()); + Filter.ClassNames.Add(UAkLocalizedMediaAsset::StaticClass()->GetFName()); + Filter.ClassNames.Add(UAkExternalMediaAsset::StaticClass()->GetFName()); + Filter.ClassNames.Add(UAkFolder::StaticClass()->GetFName()); + Filter.ClassNames.Add(UAkAssetPlatformData::StaticClass()->GetFName()); +#endif + auto& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + AssetRegistryModule.Get().GetAssets(Filter, OutDeprecatedAssets); + } + + bool DeleteAssets(const TArray& InAssetsToDelete) + { + /* + *Deleting an Asset that is referenced somewhere needs a confirmation which is impossible using a commandlet. + *Calling ForceDelete on the UObjects of the Assets will ignore the confirmation and delete the assets. + */ + if(!FApp::CanEverRender()) + { + TArray Objects; + for(auto Asset : InAssetsToDelete) + { + Objects.Add(Asset.GetAsset()); + } + int DeletedObjects = ObjectTools::ForceDeleteObjects(Objects, false); + return DeletedObjects == Objects.Num(); + } + else + { + int DeletedObjects = ObjectTools::DeleteAssets(InAssetsToDelete, true); + return DeletedObjects == InAssetsToDelete.Num(); + } + } + + bool DeleteDeprecatedAssets(const TArray& InAssetsToDelete) + { + bool bSuccess = true; + bSuccess &= DeleteAssets(InAssetsToDelete); + + const FString MediaFolderpath = FPaths::Combine(AkUnrealEditorHelper::GetLegacySoundBankDirectory(), AkUnrealHelper::MediaFolderName); + IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); + + PlatformFile.DeleteDirectoryRecursively(*MediaFolderpath); + + const FString LocalizedFolderPath = FPaths::Combine(AkUnrealEditorHelper::GetLegacySoundBankDirectory(), AkUnrealEditorHelper::LocalizedFolderName); + PlatformFile.DeleteDirectoryRecursively(*LocalizedFolderPath); + + return bSuccess; + } + + void FindWwiseAssetsInProject(TArray& OutWwiseAssets) + { + FARFilter Filter; + Filter.bRecursiveClasses = true; +#if UE_5_1_OR_LATER + Filter.ClassPaths.Add(UAkAudioType::StaticClass()->GetClassPathName()); + //We want to delete these asset types during cleanup so no need to dirty them + Filter.RecursiveClassPathsExclusionSet.Add(UAkAudioBank::StaticClass()->GetClassPathName()); + Filter.RecursiveClassPathsExclusionSet.Add(UAkFolder::StaticClass()->GetClassPathName()); +#else + Filter.ClassNames.Add(UAkAudioType::StaticClass()->GetFName()); + Filter.RecursiveClassesExclusionSet.Add(UAkAudioBank::StaticClass()->GetFName()); + Filter.RecursiveClassesExclusionSet.Add(UAkFolder::StaticClass()->GetFName()); +#endif + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + AssetRegistryModule.Get().GetAssets(Filter, OutWwiseAssets); + } + + bool MigrateWwiseAssets(const TArray & WwiseAssets, bool bShouldSplitSwitchContainerMedia) + { + TArray WwisePackagesToSave; + EWwiseEventSwitchContainerLoading SwitchContainerShouldLoad = bShouldSplitSwitchContainerMedia + ? EWwiseEventSwitchContainerLoading::LoadOnReference + : EWwiseEventSwitchContainerLoading::AlwaysLoad; + + for (auto& Asset : WwiseAssets) + { + UAkAudioType* AssetPtr = Cast(Asset.GetAsset()); + + if (AssetPtr) + { + AssetPtr->MigrateWwiseObjectInfo(); + if (AssetPtr->GetClass() == UAkAudioEvent::StaticClass()) + { + UAkAudioEvent* Event = Cast(AssetPtr); + Event->EventInfo.SwitchContainerLoading = SwitchContainerShouldLoad; + } + + if (!AssetPtr->MarkPackageDirty()) + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("Could not dirty asset %s during migration, will still try to save it."), *AssetPtr->GetFullName()); + } + WwisePackagesToSave.Add(AssetPtr->GetPackage()); + } + else + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Could not get asset '%s' in order to migrate it."), *Asset.AssetName.ToString()); + } + } + + if (WwisePackagesToSave.Num() > 0) + { + if (!UEditorLoadingAndSavingUtils::SavePackages(WwisePackagesToSave, false)) + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("MigrateWwiseAssets: Could not save assets during asset migration. Please save them manually, or run the migration operation again.")); + return false; + } + } + + return true; + } + + bool MigrateAudioBanks(const FMigrationOperations& MigrationOperations, const bool bCleanupAssets, const bool bWasUsingEBP, const bool bTransferAutoLoad, const FString DefinitionFile) + { + TSet FailedBanks; + TMap BanksToTransfer; + FillBanksToTransfer(BanksToTransfer); + const bool bIncludeMedia = !bWasUsingEBP; + bool bContinueMigration = true; + bool bTransferSuccess = true; + + FString ErrorMessage; + if (MigrationOperations.BankTransferMethod == EBankTransferMode::WAAPI) + { + TArray ErrorMessages = TransferUserBanksWaapi( BanksToTransfer, FailedBanks, bIncludeMedia); + ErrorMessage = FormatWaapiErrorMessage(ErrorMessages); + bTransferSuccess = FailedBanks.Num() == 0; + } + else if (MigrationOperations.BankTransferMethod == EBankTransferMode::DefinitionFile) + { + auto Result = WriteBankDefinitionFile( BanksToTransfer, bIncludeMedia, DefinitionFile); + bTransferSuccess = Result == Success; + + if (!bTransferSuccess) + { + switch (Result) + { + case OpenFailure: + ErrorMessage = FString::Format(TEXT("Failed to open bank definition file for write '{0}'."), {DefinitionFile}); + break; + case WriteFailure: + ErrorMessage = FString::Format(TEXT("Failed to write to bank definition file '{0}'."), {DefinitionFile}); + break; + default: + break; + } + + } + } + + if (!bTransferSuccess) + { + if (!FApp::CanEverRender()) + { + bContinueMigration = MigrationOperations.bIgnoreBankTransferErrors; + } + else //Migrating in Unreal Editor + { + bContinueMigration = PromptFailedBankTransfer(ErrorMessage); + } + } + + if (!bContinueMigration) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("MigrateAudioBanks : Errors encountered while transferring SoundBanks: \n%s"), *ErrorMessage); + UE_LOG(LogAudiokineticTools, Error, TEXT("MigrateAudioBanks : Migration aborted due to SoundBank transfer errors.")); + return false; + } + + if (!ErrorMessage.IsEmpty()) + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("MigrateAudioBanks : Errors encountered while transferring SoundBanks: \n%s"), *ErrorMessage); + UE_LOG(LogAudiokineticTools, Display, TEXT("MigrateAudioBanks : Migration will continue and ignore SoundBank transfer errors.")); + } + + if (bTransferAutoLoad) + { + UE_LOG(LogAudiokineticTools, Display, TEXT("MigrateAudioBanks: Transferring AutoLoad setting from AkAudioBank assets to Event and Aux Bus assets.")); + + for (auto& Bank : BanksToTransfer) + { + UAkAudioBank* BankAsset = Cast(Bank.Value.BankAssetData.GetAsset()); + if (!BankAsset) + { +#if UE_5_1_OR_LATER + UE_LOG(LogAudiokineticTools, Warning, TEXT("MigrateAudioBanks: Could not load UAkAudioBank Asset '%s'. AutoLoad property will not be transferred. "), + *Bank.Value.BankAssetData.GetObjectPathString()); +#else + UE_LOG(LogAudiokineticTools, Warning, TEXT("MigrateAudioBanks: Could not load UAkAudioBank Asset '%s'. AutoLoad property will not be transferred. "), + *Bank.Value.BankAssetData.ObjectPath.ToString()); +#endif + continue; + } + if (!BankAsset->AutoLoad_DEPRECATED) + { + TArray& LinkedAuxBusses = Bank.Value.LinkedAuxBusses; + TArray& LinkedEvents = Bank.Value.LinkedEvents; + for (auto& LinkedAuxBus : LinkedAuxBusses) + { + bool MigrateSuccess = false; + auto FoundAssetData = AkAssetDatabase::Get().FindAssetByObjectPath(LinkedAuxBus.AssetPath); + if (FoundAssetData.IsValid()) + { + if (auto* BusAsset = Cast(FoundAssetData.GetAsset())) + { + BusAsset->bAutoLoad = false; + MigrateSuccess = BusAsset->MarkPackageDirty(); + } + } + + if(!MigrateSuccess) + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("MigrateAudioBanks: Could not successfully disable AutoLoad on AuxBus asset %s during migration. It will be automatically loaded."), *LinkedAuxBus.AssetPath); + } + } + for (auto& LinkedEvent : LinkedEvents) + { + bool MigrateSuccess = false; + auto FoundAssetData = AkAssetDatabase::Get().FindAssetByObjectPath(LinkedEvent.AssetPath); + if (FoundAssetData.IsValid()) + { + if (auto* EventAsset = Cast(FoundAssetData.GetAsset())) + { + EventAsset->bAutoLoad = false; + MigrateSuccess = EventAsset->MarkPackageDirty(); + } + } + + if (!MigrateSuccess) + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("MigrateAudioBanks: Could not successfully disable AutoLoad on Event asset %s during migration. It will be automatically loaded."), *LinkedEvent.AssetPath); + } + } + } + } + } + if (bCleanupAssets) + { + UE_LOG(LogAudiokineticTools, Display, TEXT("MigrateAudioBanks: Deleting deprecated AkAudioBank assets.")); + TSet ProjectBanks; + for (auto Bank : BanksToTransfer) + { + ProjectBanks.Add(Bank.Value.BankAssetData); + } + TArray BanksToDelete = ProjectBanks.Array(); + DeleteAssets(BanksToDelete); + } + return true; + } + + void FillBanksToTransfer(TMap& BanksToTransfer) + { + auto& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + auto& AssetRegistry = AssetRegistryModule.Get(); + + TArray Banks; +#if UE_5_1_OR_LATER + AssetRegistry.GetAssetsByClass(UAkAudioBank::StaticClass()->GetClassPathName(), Banks); +#else + AssetRegistry.GetAssetsByClass(UAkAudioBank::StaticClass()->GetFName(), Banks); +#endif + for (FAssetData& BankData : Banks) + { + FString BankName = BankData.AssetName.ToString(); + BanksToTransfer.Add(BankName, { BankData, {}, {} }); + } + + TArray< FAssetData> Events; +#if UE_5_1_OR_LATER + AssetRegistry.GetAssetsByClass(UAkAudioEvent::StaticClass()->GetClassPathName(), Events); +#else + AssetRegistry.GetAssetsByClass(UAkAudioEvent::StaticClass()->GetFName(), Events); +#endif + for (FAssetData EventData : Events) + { + if (UAkAudioEvent* Event = Cast(EventData.GetAsset())) + { + if (Event->RequiredBank_DEPRECATED != nullptr) + { + FString BankName = Event->RequiredBank_DEPRECATED->GetName(); + FLinkedAssetEntry EventEntry = { Event->GetName(), Event->EventInfo.WwiseGuid, Event->GetPathName() }; + + if (BanksToTransfer.Contains(BankName)) + { + BanksToTransfer[BankName].LinkedEvents.Add(EventEntry); + } + else + { + FAssetData BankAssetData = FAssetData(Event->RequiredBank_DEPRECATED); + BanksToTransfer.Add(BankName, { BankAssetData, {EventEntry}, {} }); + } + } + } + } + + TArray< FAssetData> AuxBusses; +#if UE_5_1_OR_LATER + AssetRegistry.GetAssetsByClass(UAkAuxBus::StaticClass()->GetClassPathName(), AuxBusses); +#else + AssetRegistry.GetAssetsByClass(UAkAuxBus::StaticClass()->GetFName(), AuxBusses); +#endif + for (FAssetData AuxBusData : AuxBusses) + { + if (UAkAuxBus* AuxBus = Cast(AuxBusData.GetAsset())) + { + if (AuxBus->RequiredBank_DEPRECATED != nullptr) + { + FString BankName = AuxBus->RequiredBank_DEPRECATED->GetName(); + FLinkedAssetEntry AuxBusEntry = { AuxBus->GetName(), AuxBus->AuxBusInfo.WwiseGuid, AuxBus->GetPathName() }; + if (BanksToTransfer.Contains(BankName)) + { + BanksToTransfer[BankName].LinkedAuxBusses.Add(AuxBusEntry); + } + else + { + FAssetData BankAssetData = FAssetData(AuxBus->RequiredBank_DEPRECATED); + BanksToTransfer.Add(BankName, { BankAssetData, {}, {AuxBusEntry} }); + } + } + } + } + UE_LOG(LogAudiokineticTools, Display, TEXT("MigrateAudioBanks: Found %d SoundBank assets in project."), BanksToTransfer.Num()); + } + + TArray TransferUserBanksWaapi(const TMap& InBanksToTransfer, TSet& OutFailedBanks, const bool bIncludeMedia) + { + UE_LOG(LogAudiokineticTools, Display, TEXT("MigrateAudioBanks: Transfering SoundBanks with WAAPI.")); + TArray ErrorMessages; + for (TPair BankEntry : InBanksToTransfer) + { + FGuid BankID; + if (!CreateBankWaapi(BankEntry.Key, BankEntry.Value, BankID, ErrorMessages)) + { + OutFailedBanks.Add(BankEntry.Value.BankAssetData); + continue; + } + if (!SetBankIncludesWaapi(BankEntry.Value, BankID, bIncludeMedia, ErrorMessages)) + { + OutFailedBanks.Add(BankEntry.Value.BankAssetData); + } + } + SaveProjectWaapi(ErrorMessages); + return ErrorMessages; + } + + EDefinitionFileCreationResult WriteBankDefinitionFile(const TMap& InBanksToTransfer, const bool bIncludeMedia, const FString DefinitionFilePath) + { + UE_LOG(LogAudiokineticTools, Display, TEXT("MigrateAudioBanks: Writing SoundBanks Definition File to '%s'."), *DefinitionFilePath); + // open file to start writing + IPlatformFile* PlatformFile = &FPlatformFileManager::Get().GetPlatformFile();; + FString FileLocation = IFileManager::Get().ConvertToRelativePath(*DefinitionFilePath); + TUniquePtr FileWriter = TUniquePtr(PlatformFile->OpenWrite(*FileLocation)); + if (!FileWriter.IsValid()) + { + return OpenFailure; + } + + bool bWriteSuccess = true; + for (TPair BankEntry : InBanksToTransfer) + { + bWriteSuccess &= WriteBankDefinition(BankEntry.Value, FileWriter, bIncludeMedia); + } + + if (!bWriteSuccess) + { + return WriteFailure; + } + + FileWriter->Flush(); + return Success; + } + + bool WriteBankDefinition(const FBankEntry& BankEntry, TUniquePtr& FileWriter, const bool bIncludeMedia) + { + bool bWriteSuccess = true; + FString MediaString = bIncludeMedia? "\tMedia": ""; + for (FLinkedAssetEntry Event : BankEntry.LinkedEvents) + { + auto Line = BankEntry.BankAssetData.AssetName.ToString() + TEXT("\t\"") + Event.AssetName + TEXT("\"") + TEXT("\tEvent") + MediaString + TEXT("\tStructure") + LINE_TERMINATOR; + FTCHARToUTF8 Utf8Formatted(*Line); + bWriteSuccess &= FileWriter->Write(reinterpret_cast(Utf8Formatted.Get()), Utf8Formatted.Length()); + } + for (FLinkedAssetEntry Bus : BankEntry.LinkedAuxBusses) + { + auto Line = BankEntry.BankAssetData.AssetName.ToString() + TEXT("\t-AuxBus\t\"") + Bus.AssetName + TEXT("\"") + MediaString + TEXT("\tStructure") + LINE_TERMINATOR; + FTCHARToUTF8 Utf8Formatted(*Line); + bWriteSuccess &= FileWriter->Write(reinterpret_cast(Utf8Formatted.Get()), Utf8Formatted.Length()); + } + return bWriteSuccess; + } + + bool CreateBankWaapi(const FString& BankName, const FBankEntry& BankEntry, FGuid& OutBankGuid, TArray& ErrorMessages) + { +#if AK_SUPPORT_WAAPI + FAkWaapiClient* WaapiClient = FAkWaapiClient::Get(); + if (!WaapiClient) + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("Failed to create SoundBank for <%s>. \nCould not get WAAPI Client."), *BankEntry.BankAssetData.PackageName.ToString()); + ErrorMessages.Add({"Could not get WAAPI Client", true, BankEntry}); + return false; + } + else if (!WaapiClient->IsConnected()) + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("Failed to create SoundBank for <%s>. \nWAAPI Client not connected."), *BankEntry.BankAssetData.PackageName.ToString()); + ErrorMessages.Add({"WAAPI Client not connected", true, BankEntry}); + return false; + } + + TSharedRef Args = MakeShared(); + Args->SetStringField(WwiseWaapiHelper::PARENT, TEXT("\\SoundBanks\\Default Work Unit")); + Args->SetStringField(WwiseWaapiHelper::ON_NAME_CONFLICT, WwiseWaapiHelper::RENAME); + Args->SetStringField(WwiseWaapiHelper::TYPE, WwiseWaapiHelper::SOUNDBANK_TYPE); + Args->SetStringField(WwiseWaapiHelper::NAME, BankName); + + TSharedRef Options = MakeShared(); + TSharedPtr Result; + FString IdString; + if (!WaapiClient->Call(ak::wwise::core::object::create, Args, Options, Result)) + { + FString ErrorMessage; + if (Result.IsValid()) + { + Result->TryGetStringField(TEXT("message"), ErrorMessage); + } + UE_LOG(LogAudiokineticTools, Warning, TEXT("Failed to create SoundBank for <%s>.\nMessage : <%s>."), *BankEntry.BankAssetData.PackageName.ToString(), *ErrorMessage); + ErrorMessages.Add({ErrorMessage, true, BankEntry}); + return false; + } + + if (Result.IsValid() && !Result->TryGetStringField(WwiseWaapiHelper::ID, IdString)) + { + FString ErrorMessage; + if (Result.IsValid()) + { + Result->TryGetStringField(TEXT("message"), ErrorMessage); + } + UE_LOG(LogAudiokineticTools, Warning, TEXT("Failed to create SoundBank for <%s>.\nMessage : <%s>."), *BankEntry.BankAssetData.PackageName.ToString(), *ErrorMessage); + ErrorMessages.Add({ErrorMessage, true, BankEntry}); + return false; // error parsing Json + } + FGuid::ParseExact(IdString, EGuidFormats::DigitsWithHyphensInBraces, OutBankGuid); + return true; +#else + UE_LOG(LogAudiokineticTools, Error, TEXT("SetBankIncludesWaapi: WAAPI not supported")); + ErrorMessages.Add({TEXT("WAAPI not supported"), false, {}}); + return false; +#endif + } + + bool SetBankIncludesWaapi(const FBankEntry& BankEntry, const FGuid& BankId, const bool bIncludeMedia, TArray& ErrorMessages) + { +#if AK_SUPPORT_WAAPI + FAkWaapiClient* WaapiClient = FAkWaapiClient::Get(); + if (!WaapiClient) + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("WAAPI command to add Wwise objects to SoundBank '%s' failed. \nCould not get WAAPI Client."), *BankEntry.BankAssetData.PackageName.ToString()); + ErrorMessages.Add({"Could not get WAAPI Client", true, BankEntry}); + return false; + } + else if (!WaapiClient->IsConnected()) + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("WAAPI command to add Wwise objects to SoundBank '%s' failed. \nWAAPI Client not connected."), *BankEntry.BankAssetData.PackageName.ToString()); + ErrorMessages.Add({"WAAPI Client not connected", true, BankEntry}); + return false; + } + + TSet IncludeIds; + for (FLinkedAssetEntry Event : BankEntry.LinkedEvents) + { + if (Event.WwiseGuid.IsValid()) + { + IncludeIds.Add(Event.WwiseGuid.ToString(EGuidFormats::DigitsWithHyphensInBraces)); + } + else + { + IncludeIds.Add(TEXT("Event:") + Event.AssetName); + } + } + + for (FLinkedAssetEntry AuxBus : BankEntry.LinkedAuxBusses) + { + if (AuxBus.WwiseGuid.IsValid()) + { + IncludeIds.Add(AuxBus.WwiseGuid.ToString(EGuidFormats::DigitsWithHyphensInBraces)); + } + else + { + IncludeIds.Add(TEXT("AuxBus:") + AuxBus.AssetName); + } + } + if (IncludeIds.Num() < 0) + { + return true; + } + + TArray> Filters; + Filters.Add(MakeShared< FJsonValueString>(TEXT("events"))); + Filters.Add(MakeShared< FJsonValueString>(TEXT("structures"))); + if (bIncludeMedia) + { + Filters.Add(MakeShared< FJsonValueString>(TEXT("media"))); + } + + TArray> IncludeIdJson; + for (const FString IncludedId : IncludeIds) + { + TSharedPtr IncludedObject = MakeShared< FJsonObject>(); + IncludedObject->SetStringField(WwiseWaapiHelper::OBJECT, IncludedId); + IncludedObject->SetArrayField(WwiseWaapiHelper::FILTER, Filters); + IncludeIdJson.Add(MakeShared< FJsonValueObject>(IncludedObject)); + } + + TSharedRef Args = MakeShared(); + Args->SetStringField(WwiseWaapiHelper::SOUNDBANK_FIELD, BankId.ToString(EGuidFormats::DigitsWithHyphensInBraces)); + Args->SetStringField(WwiseWaapiHelper::OPERATION, TEXT("add")); + Args->SetArrayField(WwiseWaapiHelper::INCLUSIONS, IncludeIdJson); + + TSharedRef Options = MakeShared(); + TSharedPtr Result; + if (!WaapiClient->Call(ak::wwise::core::soundbank::setInclusions, Args, Options, Result)) + { + FString ErrorMessage; + if (Result.IsValid()) + { + Result->TryGetStringField(TEXT("message"), ErrorMessage); + } + UE_LOG(LogAudiokineticTools, Warning, TEXT("WAAPI command to add Wwise objects to SoundBank '%s' failed. \nError Message : <%s>."), *BankEntry.BankAssetData.PackageName.ToString(), *ErrorMessage); + ErrorMessages.Add({ErrorMessage, true, BankEntry}); + return false; + } + return true; + +#else + UE_LOG(LogAudiokineticTools, Error, TEXT("SetBankIncludesWaapi: WAAPI not supported")); + ErrorMessages.Add({TEXT("WAAPI not supported"), false, {}}); + return false; +#endif + } + + bool SaveProjectWaapi(TArray& ErrorMessages) + { +#if AK_SUPPORT_WAAPI + FAkWaapiClient* WaapiClient = FAkWaapiClient::Get(); + if (!WaapiClient) + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("Failed to save Wwise project.\n Could not get Waapi Client.")); + ErrorMessages.Add({TEXT("Failed to save Wwise project over WAAPI. Please save the project manually."), false, {}}); + return false; + } + else if (!WaapiClient->IsConnected()) + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("Failed to save Wwise project.\n Waapi Client not connected.")); + ErrorMessages.Add({TEXT("Failed to save Wwise project over WAAPI. Please save the project manually."), false, {}}); + return false; + } + + TSharedRef Args = MakeShared(); + TSharedRef Options = MakeShared(); + TSharedPtr Result; + FString IdString; + if (!WaapiClient->Call(ak::wwise::core::project::save, Args, Options, Result)) + { + FString ErrorMessage; + if (Result.IsValid()) + { + Result->TryGetStringField(TEXT("message"), ErrorMessage); + } + UE_LOG(LogAudiokineticTools, Warning, TEXT("Failed to save Wwise project.\nMessage : <%s>."), *ErrorMessage); + ErrorMessages.Add({TEXT("Failed to save Wwise project over WAAPI. Please save the project manually."), false, {}}); + ErrorMessages.Add({ErrorMessage, false, {}}); + return false; + } + return true; +#else + UE_LOG(LogAudiokineticTools, Error, TEXT("SetBankIncludesWaapi: WAAPI not supported")); + ErrorMessages.Add({TEXT("WAAPI not supported"), false, {}}); + return false; +#endif + } + + bool MigrateProjectSettings(FString& ProjectContent, const bool bWasUsingEBP, const bool bUseGeneratedSubFolders, const FString& GeneratedSoundBanksFolder ) + { + //migrate split media per id + TArray PropertiesToAdd; + if (bWasUsingEBP) + { + PropertiesToAdd.Add({ TEXT("AutoSoundBankEnabled"), TEXT("True"), TEXT("") }); + } + + if (bUseGeneratedSubFolders) + { + PropertiesToAdd.Add({ TEXT("MediaAutoBankSubFolders"), TEXT("True"), TEXT("") }); + } + + static const TArray LogCentralItemsToRemove = + { + TEXT(""), + TEXT("") + }; + + bool bModified = false; + if (PropertiesToAdd.Num() >0) + { + bModified = InsertProperties(PropertiesToAdd, ProjectContent); + } + for (const FString& LogItemToRemove : LogCentralItemsToRemove) + { + if (ProjectContent.Contains(LogItemToRemove)) + { + ProjectContent.ReplaceInline(*LogItemToRemove, TEXT("")); + bModified = true; + } + } + return bModified; + } + + bool SetStandardSettings(FString& ProjectContent) + { + static const TArray PropertiesToAdd = { + { TEXT("GenerateMultipleBanks"), TEXT("True"), TEXT("") }, + { TEXT("GenerateSoundBankJSON"), TEXT("True"), TEXT("") }, + { TEXT("SoundBankGenerateEstimatedDuration"), TEXT("True"), TEXT("") }, + { TEXT("SoundBankGenerateMaxAttenuationInfo"), TEXT("True"), TEXT("") }, + { TEXT("SoundBankGeneratePrintGUID"), TEXT("True"), TEXT("") }, + { TEXT("SoundBankGeneratePrintPath"), TEXT("True"), TEXT("") }, + { TEXT("CopyLooseStreamedMedia"), TEXT("True"), TEXT("") }, + { TEXT("RemoveUnusedGeneratedFiles"), TEXT("True"), TEXT("") }, + }; + + return InsertProperties(PropertiesToAdd, ProjectContent); + } + + bool InsertProperties(const TArray& PropertiesToChange, FString& ProjectContent) + { + static const auto PropertyListStart = TEXT(""); + static const FString EndTag = TEXT(">"); + static const TCHAR EmptyElementEndChar = '/'; + static const FString ValueTag = TEXT(""); + static const FString EndValueTag = TEXT(""); + + static const FString ValueAttribute = TEXT("Value=\""); + static const FString EndValueAttribute = TEXT("\""); + + bool bModified = false; + + int32 PropertyListPosition = ProjectContent.Find(PropertyListStart); + if (PropertyListPosition != -1) + { + int32 InsertPosition = PropertyListPosition + FCString::Strlen(PropertyListStart); + + for (PropertyToChange ItemToAdd : PropertiesToChange) + { + auto idx = ProjectContent.Find(ItemToAdd.Name); + if (idx == -1) + { + ProjectContent.InsertAt(InsertPosition, FString::Printf(TEXT("\n\t\t\t\t%s"), *ItemToAdd.Xml)); + bModified = true; + } + else + { + FString ValueText; + FString EndValueText; + int32 EndTagIdx = ProjectContent.Find(EndTag, ESearchCase::IgnoreCase, ESearchDir::FromStart, idx); + if (ProjectContent[EndTagIdx - 1] == EmptyElementEndChar) + { + // The property is an empty element, the value will be in an attribute + ValueText = ValueAttribute; + EndValueText = EndValueAttribute; + } + else + { + // We are in a ValueList + ValueText = ValueTag; + EndValueText = EndValueTag; + } + + int32 ValueIdx = ProjectContent.Find(ValueText, ESearchCase::IgnoreCase, ESearchDir::FromStart, idx); + int32 EndValueIdx = ProjectContent.Find(EndValueText, ESearchCase::IgnoreCase, ESearchDir::FromStart, ValueIdx); + if (ValueIdx != -1 && ValueIdx > idx && ValueIdx < EndValueIdx) + { + ValueIdx += ValueText.Len(); + auto ValueEndIdx = ProjectContent.Find(EndValueText, ESearchCase::IgnoreCase, ESearchDir::FromStart, ValueIdx); + if (ValueEndIdx != -1) + { + FString value = ProjectContent.Mid(ValueIdx, ValueEndIdx - ValueIdx); + if (value != ItemToAdd.Value) + { + ProjectContent.RemoveAt(ValueIdx, ValueEndIdx - ValueIdx, false); + ProjectContent.InsertAt(ValueIdx, ItemToAdd.Value); + bModified = true; + } + } + } + else + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("Could not change value for %s in Wwise project. Some features might not work properly."), *ItemToAdd.Name); + } + } + } + } + + return bModified; + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkAssetMigrationHelper.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkAssetMigrationHelper.h new file mode 100644 index 0000000..2a6ca45 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkAssetMigrationHelper.h @@ -0,0 +1,124 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Containers/Map.h" +#include "Containers/Array.h" +#include "Misc/Guid.h" +#include "AssetRegistry/AssetData.h" + +struct PropertyToChange; + +namespace AkAssetMigration +{ + enum EBankTransferMode + { + NoTransfer, + WAAPI, + DefinitionFile, + }; + + struct FMigrationOperations + { + bool bDoDeprecatedAssetCleanup = false; + bool bDoAssetMigration= false; + bool bDoBankCleanup= false; + bool bTransferAutoload = false; + bool bDoProjectUpdate= false; + bool bCancelled = false; + bool bIgnoreBankTransferErrors = false; + FString GeneratedSoundBankDirectory = ""; + FString DefinitionFilePath = ""; + EBankTransferMode BankTransferMethod = EBankTransferMode::NoTransfer; + }; + + struct FMigrationContext + { + bool bBanksInProject = false; + bool bDeprecatedAssetsInProject = false; + bool bAssetsNeedMigration = false; + bool bProjectSettingsNotUpToDate = false; + int NumDeprecatedAssetsInProject = 0; + }; + + struct FLinkedAssetEntry + { + FString AssetName; + FGuid WwiseGuid; + FString AssetPath; + UPackage* Package; + }; + + struct FBankEntry + { + FAssetData BankAssetData; + TArray LinkedEvents; + TArray LinkedAuxBusses; + }; + + struct FBankTransferError + { + FString ErrorMessage; + bool bHasBankEntry; + FBankEntry BankEntry; + }; + + enum EDefinitionFileCreationResult + { + Success, + OpenFailure, + WriteFailure + }; + + void PromptMigration(const FMigrationContext& MigrationOptions, FMigrationOperations& OutMigrationOperations); + bool MigrateAudioBanks(const FMigrationOperations& TransferMode, const bool bCleanupAssets, const bool bWasUsingEBP, const bool bTransferAutoLoad, const FString DefinitionFilePath); + bool MigrateWwiseAssets(const TArray & WwiseAssets, bool bShouldSplitSwitchContainerMedia); + void FindWwiseAssetsInProject(TArray& OutFoundAssets); + + bool PromptFailedBankTransfer(const TArray& ErrorMessages); + FString FormatWaapiErrorMessage(const TArray& ErrorMessages); + + void FindDeprecatedAssets(TArray& OutDeprecatedAssets); + bool DeleteDeprecatedAssets(const TArray& InAssetsToDelete); + + void FillBanksToTransfer(TMap& BanksToTransfer); + TArray TransferUserBanksWaapi(const TMap& BanksToTransfer, TSet& FailedBanks, const bool bIncludeMedia); + bool CreateBankWaapi(const FString& BankName, const FBankEntry& BankEntry, FGuid& OutBankGuid, TArray& ErrorMessages); + bool SetBankIncludesWaapi(const FBankEntry& BankEntry, const FGuid& BankId, const bool bIncludeMedia, TArray& ErrorMessages); + bool SaveProjectWaapi(TArray& ErrorMessages); + EDefinitionFileCreationResult WriteBankDefinitionFile(const TMap& InBanksToTransfer, const bool bIncludeMedia, const FString DefinitionFilePath); + bool WriteBankDefinition(const FBankEntry& BankEntry, TUniquePtr& FileWriter, const bool bIncludeMedia); + + bool MigrateProjectSettings(FString& ProjectContent, const bool bWasUsingEBP, const bool bUseGeneratedSubFolders, const FString& GeneratedSoundBanksFolder); + bool SetStandardSettings(FString& ProjectContent); + + struct PropertyToChange + { + FString Name; + FString Value; + FString Xml; + + PropertyToChange(FString n, FString v, FString x) + : Name(n) + , Value(v) + , Xml(x) + {} + }; + + bool InsertProperties(const TArray& PropertiesToChange, FString& ProjectContent); +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkAssetMigrationManager.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkAssetMigrationManager.cpp new file mode 100644 index 0000000..80a892f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkAssetMigrationManager.cpp @@ -0,0 +1,517 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkAssetMigrationManager.h" +#include "AkAudioStyle.h" +#include "AkSettings.h" +#include "AkUnrealEditorHelper.h" +#include "AkAssetMigrationHelper.h" +#include "AkAudioBank.h" +#include "AkAudioEvent.h" +#include "AkAudioModule.h" +#include "AkInitBank.h" +#include "AkWaapiClient.h" +#include "ContentBrowserModule.h" +#include "Editor.h" +#include "Framework/Notifications/NotificationManager.h" +#if UE_5_0_OR_LATER +#include "HAL/PlatformFileManager.h" +#else +#include "HAL/PlatformFilemanager.h" +#endif +#include "IAudiokineticTools.h" +#include "Misc/FileHelper.h" +#include "Misc/MessageDialog.h" +#include "Widgets/Notifications/SNotificationList.h" +#include "Settings/ProjectPackagingSettings.h" +#include "AkUnrealHelper.h" +#include "ToolMenus.h" +#include "UnrealEd/Public/FileHelpers.h" + +#define LOCTEXT_NAMESPACE "AkAudio" + +bool AkAssetMigrationManager::IsProjectMigrated() +{ + UAkSettings* AkSettings = GetMutableDefault(); + if (!AkSettings) + { + UE_LOG(LogAudiokineticTools, Display, TEXT("AkAssetMigrationManager::IsProjectMigrated: Could not get AkSettings. Cannot determine whether the project is migrated.")); + return false; + } + return AkSettings->bAssetsMigrated && AkSettings->bProjectMigrated && AkSettings->bSoundBanksTransfered; + +} + +bool AkAssetMigrationManager::IsMigrationRequired(AkAssetMigration::FMigrationContext& MigrationOptions ) +{ + UAkSettings* AkSettings = GetMutableDefault(); + if (!AkSettings) + { + UE_LOG(LogAudiokineticTools, Display, TEXT("AkAssetMigrationManager::IsMigrationRequired: Could not getAkSettings. Cannot determine whether the project should be migrated.")); + return false; + } + auto& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + auto& AssetRegistry = AssetRegistryModule.Get(); + bool bUpdateAkSettings = false; + if (!AkSettings->bAssetsMigrated) + { + TArray WwiseAssetsInProject; + AkAssetMigration::FindWwiseAssetsInProject(WwiseAssetsInProject); + + if (WwiseAssetsInProject.Num() == 0) + { + MigrationOptions.bAssetsNeedMigration = false; + AkSettings->bAssetsMigrated = true; + bUpdateAkSettings = true; + } + else if (WwiseAssetsInProject.Num() == 1) + { + //Init bank asset is automatically created by the integration + if (WwiseAssetsInProject[0].GetClass() == UAkInitBank::StaticClass()) + { + AkSettings->bAssetsMigrated = true; + MigrationOptions.bAssetsNeedMigration = false; + bUpdateAkSettings= true; + } + else + { + MigrationOptions.bAssetsNeedMigration = true; + } + } + else + { + MigrationOptions.bAssetsNeedMigration = true; + } + } + else + { + MigrationOptions.bAssetsNeedMigration = false; + } + + //Check if project migration is needed + if (!AkSettings->bProjectMigrated) + { + if (AkSettings->UseEventBasedPackaging || AkSettings->SplitMediaPerFolder || IsSoundDataPathInDirectoriesToAlwaysStage(AkUnrealEditorHelper::GetLegacySoundBankDirectory()) ) + { + // project migration needed + MigrationOptions.bProjectSettingsNotUpToDate = true; + } + else + { + MigrationOptions.bProjectSettingsNotUpToDate = false; + AkSettings->bProjectMigrated = true; + bUpdateAkSettings = true; + } + } + else + { + MigrationOptions.bProjectSettingsNotUpToDate = false; + } + + TArray Banks; +#if UE_5_1_OR_LATER + AssetRegistry.GetAssetsByClass(UAkAudioBank::StaticClass()->GetClassPathName(), Banks); +#else + AssetRegistry.GetAssetsByClass(UAkAudioBank::StaticClass()->GetFName(), Banks); +#endif + if (Banks.Num() > 0) + { + MigrationOptions.bBanksInProject = true; + } + else + { + MigrationOptions.bBanksInProject = false; + if (!AkSettings->bSoundBanksTransfered) + { + AkSettings->bSoundBanksTransfered = true; + bUpdateAkSettings = true; + } + } + + TArray DeprecatedAssetsInProject; + AkAssetMigration::FindDeprecatedAssets(DeprecatedAssetsInProject); + MigrationOptions.NumDeprecatedAssetsInProject = DeprecatedAssetsInProject.Num(); + if (DeprecatedAssetsInProject.Num() > 0) + { + MigrationOptions.bDeprecatedAssetsInProject = true; + } + else + { + MigrationOptions.bDeprecatedAssetsInProject = false; + } + + if (bUpdateAkSettings) + { + if(!AkUnrealEditorHelper::SaveConfigFile(AkSettings)) + { + const FString Message = TEXT("Unable to checkout settings file for Wwise integration settings (DefaultGame.ini). Please revert all changes done, and restart the migration process."); + if (FApp::CanEverRender()) + { + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(Message)); + } + else + { + UE_LOG(LogAudiokineticTools, Error, TEXT("AkAssetMigrationManager: %s"), *Message); + } + } + } + + if ((MigrationOptions.bBanksInProject || !AkSettings->bSoundBanksTransfered) || MigrationOptions.bDeprecatedAssetsInProject || MigrationOptions.bAssetsNeedMigration || MigrationOptions.bProjectSettingsNotUpToDate) + { + return true; + } + return false; +} + +void AkAssetMigrationManager::EditorTryMigration() +{ + //This should only be done with the editor open + if (!FApp::CanEverRender()) + { + return; + } + + AkAssetMigration::FMigrationContext MigrationOptions; + if (!IsMigrationRequired(MigrationOptions)) + { + RemoveMigrationMenuOption(); + return; + } + + AkAssetMigration::FMigrationOperations MigrationOperations; + AkAssetMigration::PromptMigration(MigrationOptions, MigrationOperations); + if (MigrationOperations.bCancelled) + { + return; + } + PerformMigration(MigrationOperations); +} + +AkAssetMigrationManager::MigrationResult AkAssetMigrationManager::PerformMigration(AkAssetMigration::FMigrationOperations MigrationOperations) +{ + UAkSettings* AkSettings = GetMutableDefault(); + bool bShouldUpdateConfig = false; + const bool bWasUsingEBP = AkSettings->UseEventBasedPackaging; + MigrationResult Result; + // Do bank migration first as users may back out at this point and modifying Wwise settings can lock the Wwise project for WAAPI + bool bContinue= true; + if (MigrationOperations.BankTransferMethod != AkAssetMigration::EBankTransferMode::NoTransfer || MigrationOperations.bDoBankCleanup || MigrationOperations.bTransferAutoload) + { + UE_LOG(LogAudiokineticTools, Display, TEXT("AkAssetMigrationManager: Migrating SoundBanks.")); + const bool bSuccess = AkAssetMigration::MigrateAudioBanks(MigrationOperations, MigrationOperations.bDoBankCleanup, bWasUsingEBP, MigrationOperations.bTransferAutoload, MigrationOperations.DefinitionFilePath); + Result.bBankTransferSucceeded = bSuccess; + if (bSuccess) + { + AkSettings->bSoundBanksTransfered = true; + bShouldUpdateConfig = true; + } + else + { + bContinue = false; + if (MigrationOperations.bDoAssetMigration) + { + Result.bAssetMigrationSucceeded = false; + } + if (MigrationOperations.bDoDeprecatedAssetCleanup) + { + Result.bAssetCleanupSucceeded = false; + } + } + } + + if (MigrationOperations.bDoAssetMigration && bContinue) + { + + UE_LOG(LogAudiokineticTools, Display, TEXT("AkAssetMigrationManager: Migrating Wwise assets.")); + TArray WwiseAssetsInProject; + AkAssetMigration::FindWwiseAssetsInProject(WwiseAssetsInProject); + if (AkAssetMigration::MigrateWwiseAssets(WwiseAssetsInProject, AkSettings->SplitSwitchContainerMedia)) + { + AkSettings->bAssetsMigrated = true; + bShouldUpdateConfig = true; + } + else + { + Result.bAssetMigrationSucceeded = false; + } + } + + if (MigrationOperations.bDoDeprecatedAssetCleanup && bContinue) + { + UE_LOG(LogAudiokineticTools, Display, TEXT("AkAssetMigrationManager: Deleting deprecated assets.")); + TArray DeprecatedAssetsInProject; + AkAssetMigration::FindDeprecatedAssets(DeprecatedAssetsInProject); + Result.bAssetCleanupSucceeded = AkAssetMigration::DeleteDeprecatedAssets(DeprecatedAssetsInProject); + DeprecatedAssetsInProject.Empty(); + } + + + if (MigrationOperations.bDoProjectUpdate && bContinue) + { + UE_LOG(LogAudiokineticTools, Display, TEXT("AkAssetMigrationManager: Updating project settings.")); + if (!bWasUsingEBP) + { + AkSettings->RemoveSoundDataFromAlwaysStageAsUFS(AkSettings->WwiseSoundDataFolder.Path); + AkUnrealEditorHelper::DeleteLegacySoundBanks(); + } + else + { + AkSettings->RemoveSoundDataFromAlwaysCook(FString::Printf(TEXT("/Game/%s"), *AkSettings->WwiseSoundDataFolder.Path)); + } + Result.bProjectMigrationSucceeded = MigrateProjectSettings(bWasUsingEBP, AkSettings->SplitMediaPerFolder, MigrationOperations.GeneratedSoundBankDirectory); + AkSettings->UpdateGeneratedSoundBanksPath(MigrationOperations.GeneratedSoundBankDirectory); + AkSettings->SplitMediaPerFolder = false; + AkSettings->UseEventBasedPackaging = false; + AkSettings->bProjectMigrated = true; + + bShouldUpdateConfig = true; + } + + if (bShouldUpdateConfig) + { + if(!AkUnrealEditorHelper::SaveConfigFile(AkSettings)) + { + const FString Message = TEXT("Unable to checkout settings file for Wwise integration settings (DefaultGame.ini). Please revert all changes done, and restart the migration process."); + if (FApp::CanEverRender()) + { + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(Message)); + } + else + { + UE_LOG(LogAudiokineticTools, Error, TEXT("AkAssetMigrationManager: %s"), *Message); + } + } + } + + //Any of these operations may have dirtied some assets, asset migration should handle saving on its own, but might as well be safe. + if ( MigrationOperations.bDoAssetMigration || MigrationOperations.bDoBankCleanup || MigrationOperations.bTransferAutoload || MigrationOperations.bDoDeprecatedAssetCleanup) + { + UE_LOG(LogAudiokineticTools, Display, TEXT("AkAssetMigrationManager: Saving all dirty assets in the project.")); + + if (!UEditorLoadingAndSavingUtils::SaveDirtyPackages(true, true)) + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("AkAssetMigrationManager: Failed to save all dirty packages. Please inspect the log for more details.")); + } + } + + if (!Result.bAssetCleanupSucceeded || !Result.bAssetMigrationSucceeded || (!Result.bBankTransferSucceeded && !MigrationOperations.bIgnoreBankTransferErrors) || !Result.bProjectMigrationSucceeded) + { + Result.bSuccess = false; + } + + if (!FApp::CanEverRender()) + { + return Result; + } + + FString ResultString = Result.bSuccess ? "Success" : "Failure"; + FNotificationInfo Info(FText::Format(LOCTEXT("AkAssetManagementManagerResult", "Migration completed - {0}"), FText::FromString(ResultString))); + Info.Image = FAkAudioStyle::GetBrush(TEXT("AudiokineticTools.AkBrowserTabIcon")); + Info.bFireAndForget = true; + Info.FadeOutDuration = 0.6f; + Info.ExpireDuration = 4.6f; + FSlateNotificationManager::Get().AddNotification(Info); + if (Result.bSuccess) + { + GEditor->PlayEditorSound(TEXT("/Engine/EditorSounds/Notifications/CompileSuccess_Cue.CompileSuccess_Cue")); + } + else + { + GEditor->PlayEditorSound(TEXT("/Engine/EditorSounds/Notifications/CompileFailed_Cue.CompileFailed_Cue")); + } + + return Result; +} + + +bool AkAssetMigrationManager::IsSoundDataPathInDirectoriesToAlwaysStage(const FString& SoundDataPath) +{ + UProjectPackagingSettings* PackagingSettings = GetMutableDefault(); + for (int32 i = PackagingSettings->DirectoriesToAlwaysStageAsUFS.Num() - 1; i >= 0; --i) + { + if (PackagingSettings->DirectoriesToAlwaysStageAsUFS[i].Path == SoundDataPath) + { + return true; + } + } + for (int32 i = PackagingSettings->DirectoriesToAlwaysCook.Num() - 1; i >= 0; --i) + { + if (PackagingSettings->DirectoriesToAlwaysCook[i].Path == SoundDataPath) + { + return true; + } + } + return false; +} + + +void AkAssetMigrationManager::CreateMigrationMenuOption() +{ + // Extend the build menu to handle Audiokinetic-specific entries +#if UE_5_0_OR_LATER + { + UToolMenu* BuildMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Build"); + FToolMenuSection& WwiseBuildSection = BuildMenu->AddSection(MigrationMenuSectionName, LOCTEXT("AkBuildLabel", "Audiokinetic Migration"), FToolMenuInsert("LevelEditorGeometry", EToolMenuInsertType::Default)); + FUIAction MigrationUIAction; + MigrationUIAction.ExecuteAction.BindRaw(this, &AkAssetMigrationManager::EditorTryMigration); + WwiseBuildSection.AddMenuEntry( + NAME_None, + LOCTEXT("AKAudioBank_PostMigration", "Finish Project Migration"), + LOCTEXT("AkAudioBank_PostMigrationTooltip", "Transfer Bank hierarchy to Wwise, clean up bank files, delete Wwise media assets, clean up Wwise assets"), + FSlateIcon(), + MigrationUIAction + ); + } +#else + FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); + LevelViewportToolbarBuildMenuExtenderAkMigration = FLevelEditorModule::FLevelEditorMenuExtender::CreateLambda([this](const TSharedRef CommandList) + { + TSharedPtr Extender = MakeShared(); + Extender->AddMenuExtension("LevelEditorGeometry", EExtensionHook::After, CommandList, FMenuExtensionDelegate::CreateLambda([this](FMenuBuilder& MenuBuilder) + { + MenuBuilder.BeginSection("Audiokinetic Migration", LOCTEXT("AudiokineticMigration", "Audiokinetic Migration")); + { + FUIAction MigrationAction; + MigrationAction.ExecuteAction.BindRaw(this, &AkAssetMigrationManager::EditorTryMigration); + MenuBuilder.AddMenuEntry( + LOCTEXT("AKAudioBank_PostMigration", "Finish Project Migration"), + LOCTEXT("AkAudioBank_PostMigrationTooltip", "Transfer Bank hierarchy to Wwise, clean up bank files, delete Wwise media assets, clean up Wwise assets"), + FSlateIcon(), + MigrationAction + ); + } + MenuBuilder.EndSection(); + })); + + return Extender.ToSharedRef(); + }); + + LevelEditorModule.GetAllLevelEditorToolbarBuildMenuExtenders().Add(LevelViewportToolbarBuildMenuExtenderAkMigration); + LevelViewportToolbarBuildMenuExtenderAkMigrationHandle = LevelEditorModule.GetAllLevelEditorToolbarBuildMenuExtenders().Last().GetHandle(); +#endif +} + +void AkAssetMigrationManager::RemoveMigrationMenuOption() +{ +#if UE_5_0_OR_LATER + UToolMenu* BuildMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Build"); + BuildMenu->RemoveSection(MigrationMenuSectionName); +#else + if (LevelViewportToolbarBuildMenuExtenderAkMigrationHandle.IsValid()) + { + if (FModuleManager::Get().IsModuleLoaded("LevelEditor")) + { + auto& LevelEditorModule = FModuleManager::GetModuleChecked("LevelEditor"); + LevelEditorModule.GetAllLevelEditorToolbarBuildMenuExtenders().RemoveAll([=](const FLevelEditorModule::FLevelEditorMenuExtender& Extender) + { + return Extender.GetHandle() == LevelViewportToolbarBuildMenuExtenderAkMigrationHandle; + }); + } + LevelViewportToolbarBuildMenuExtenderAkMigrationHandle.Reset(); + } +#endif +} + +void AkAssetMigrationManager::ClearSoundBanksForMigration() +{ + auto soundBankDirectory = AkUnrealEditorHelper::GetLegacySoundBankDirectory(); + + TArray foundFiles; + + auto& platformFile = FPlatformFileManager::Get().GetPlatformFile(); + platformFile.FindFilesRecursively(foundFiles, *soundBankDirectory, TEXT(".bnk")); + platformFile.FindFilesRecursively(foundFiles, *soundBankDirectory, TEXT(".json")); + + if (foundFiles.Num() > 0) + { + platformFile.DeleteDirectoryRecursively(*AkUnrealEditorHelper::GetLegacySoundBankDirectory()); + } +} + +bool AkAssetMigrationManager::MigrateProjectSettings(const bool bWasUsingEBP, const bool bUseGeneratedSubFolders, const FString& GeneratedSoundBanksFolder) +{ + const auto ProjectPath = AkUnrealHelper::GetWwiseProjectPath(); + FString ProjectContent; + bool bSuccess = FFileHelper::LoadFileToString(ProjectContent, *ProjectPath); + if (bSuccess) + { + bool bModified = AkAssetMigration::MigrateProjectSettings(ProjectContent, bWasUsingEBP, bUseGeneratedSubFolders, GeneratedSoundBanksFolder); + bModified |= AkAssetMigration::SetStandardSettings(ProjectContent); + + if (bModified) + { + bSuccess = FFileHelper::SaveStringToFile(ProjectContent, *ProjectPath, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM); + } + } + return bSuccess; +} + + +bool AkAssetMigrationManager::SetStandardProjectSettings() +{ + const auto ProjectPath = AkUnrealHelper::GetWwiseProjectPath(); + FString ProjectContent; + bool bSuccess = FFileHelper::LoadFileToString(ProjectContent, *ProjectPath); + if (bSuccess) + { + const bool bModified = AkAssetMigration::SetStandardSettings(ProjectContent); + if (bModified) + { + bSuccess = FFileHelper::SaveStringToFile(ProjectContent, *ProjectPath, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM); + } + } + return bSuccess; +} + +// This whole hack is because Unreal XML classes doesn't +// handle which the Wwise project file use. +// Doing it the dirty way instead. +bool AkAssetMigrationManager::SetGeneratedSoundBanksPath(const FString& ProjectContent, const FString& GeneratedSoundBanksFolder) +{ + const auto SoundBankPathPosition = ProjectContent.Find(TEXT("SoundBankHeaderFilePath")); + if (SoundBankPathPosition != INDEX_NONE) + { + const FString ValueDelimiter = TEXT("Value=\""); + const auto SoundBankPathValueStartPosition = ProjectContent.Find(*ValueDelimiter, ESearchCase::IgnoreCase, ESearchDir::FromStart, SoundBankPathPosition) + ValueDelimiter.Len(); + if (SoundBankPathValueStartPosition != INDEX_NONE) + { + const auto SoundBankPathValueEndPosition = ProjectContent.Find(TEXT("\""), ESearchCase::IgnoreCase, ESearchDir::FromStart, SoundBankPathValueStartPosition); + + if (SoundBankPathValueEndPosition != INDEX_NONE) + { + auto GeneratedPath = ProjectContent.Mid(SoundBankPathValueStartPosition, SoundBankPathValueEndPosition - SoundBankPathValueStartPosition); + if(FPaths::IsRelative(GeneratedPath)) + { + auto WwiseProjectDirectory = FPaths::GetPath(AkUnrealHelper::GetWwiseProjectPath()); + GeneratedPath = FPaths::Combine(WwiseProjectDirectory, GeneratedPath); + } + FPaths::MakePathRelativeTo(GeneratedPath, *FPaths::ProjectContentDir()); + UAkSettings* AkSettings = GetMutableDefault(); + if (AkSettings) + { + AkSettings->UpdateGeneratedSoundBanksPath(GeneratedPath); + return true; + } + } + } + } + + return false; +} + + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkAssetMigrationManager.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkAssetMigrationManager.h new file mode 100644 index 0000000..8ef2631 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkAssetMigrationManager.h @@ -0,0 +1,60 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkAssetMigrationHelper.h" +#include "AkSettings.h" +#include "Editor/LevelEditor/Public/LevelEditor.h" + +class AkAssetMigrationManager +{ +public: + struct MigrationResult + { + bool bSuccess = true; + bool bBankTransferSucceeded = true; + bool bProjectMigrationSucceeded = true; + bool bAssetMigrationSucceeded = true; + bool bAssetCleanupSucceeded = true; + + TArray ErrorReport; + }; + + void Init(); + void Uninit(); + + void EditorTryMigration(); + MigrationResult PerformMigration(AkAssetMigration::FMigrationOperations MigrationOperations); + bool IsProjectMigrated(); + bool IsMigrationRequired(AkAssetMigration::FMigrationContext& MigrationOptions); + void CreateMigrationMenuOption(); + void RemoveMigrationMenuOption(); + + static void ClearSoundBanksForMigration(); + static bool MigrateProjectSettings(const bool bWasUsingEBP, const bool bUseGeneratedSubFolders, const FString& GeneratedSoundBanksFolder); + static bool SetStandardProjectSettings(); + static bool SetGeneratedSoundBanksPath(const FString& ProjectContent, const FString& GeneratedSoundBanksFolder); + static bool IsSoundDataPathInDirectoriesToAlwaysStage(const FString& SoundDataPath); + +private: +#if !UE_5_0_OR_LATER + FLevelEditorModule::FLevelEditorMenuExtender LevelViewportToolbarBuildMenuExtenderAkMigration; + FDelegateHandle LevelViewportToolbarBuildMenuExtenderAkMigrationHandle; +#endif + FName MigrationMenuSectionName = "AkMigration"; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkGenerateSoundBanksTask.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkGenerateSoundBanksTask.cpp new file mode 100644 index 0000000..255e9fb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkGenerateSoundBanksTask.cpp @@ -0,0 +1,64 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkGenerateSoundBanksTask.h" +#include "AkWaapiClient.h" +#include "Async/Async.h" + +AkGenerateSoundBanksTask::AkGenerateSoundBanksTask(const AkSoundBankGenerationManager::FInitParameters& InitParameters) +{ + GenerationManager = MakeShared(InitParameters); + GenerationManager->Init(); +} + +AkGenerateSoundBanksTask::~AkGenerateSoundBanksTask() +{ +} + +void AkGenerateSoundBanksTask::ExecuteForEditorPlatform() +{ + AkSoundBankGenerationManager::FInitParameters InitParameters; + InitParameters.Platforms = { FPlatformProperties::IniPlatformName() }; + + if (FAkWaapiClient::IsProjectLoaded()) + { + InitParameters.GenerationMode = AkSoundBankGenerationManager::ESoundBankGenerationMode::WAAPI; + } + + CreateAndExecuteTask(InitParameters); +} + +void AkGenerateSoundBanksTask::CreateAndExecuteTask(const AkSoundBankGenerationManager::FInitParameters& InitParameters) +{ + AsyncTask(ENamedThreads::Type::AnyBackgroundThreadNormalTask, [InitParameters] + { + auto task = new FAsyncTask(InitParameters); + task->StartSynchronousTask(); + task->EnsureCompletion(); + + //Probably no longer necessary as we are no longer loading/unloading assets + AsyncTask(ENamedThreads::GameThread, [task] + { + delete task; + }); + }); +} + +void AkGenerateSoundBanksTask::DoWork() +{ + GenerationManager->DoGeneration(); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkGenerateSoundBanksTask.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkGenerateSoundBanksTask.h new file mode 100644 index 0000000..31995c9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkGenerateSoundBanksTask.h @@ -0,0 +1,43 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Async/AsyncWork.h" +#include "Templates/SharedPointer.h" +#include "AssetManagement/WwiseProjectInfo.h" +#include "AssetManagement/AkSoundBankGenerationManager.h" + +class AkGenerateSoundBanksTask : public FNonAbandonableTask +{ +public: + AkGenerateSoundBanksTask(const AkSoundBankGenerationManager::FInitParameters& InitParameters); + ~AkGenerateSoundBanksTask(); + + void DoWork(); + + FORCEINLINE TStatId GetStatId() const + { + RETURN_QUICK_DECLARE_CYCLE_STAT(AkGenerateSoundBanksTask, STATGROUP_ThreadPoolAsyncTasks); + } + + static void ExecuteForEditorPlatform(); + static void CreateAndExecuteTask(const AkSoundBankGenerationManager::FInitParameters& InitParameters); + +private: + TSharedPtr GenerationManager; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkMigrationCommandlet.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkMigrationCommandlet.cpp new file mode 100644 index 0000000..4ecc92e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkMigrationCommandlet.cpp @@ -0,0 +1,255 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AssetManagement/AkMigrationCommandlet.h" + +#include "AkAssetMigrationManager.h" +#include "AkAudioBankGenerationHelpers.h" +#include "AkWaapiClient.h" +#include "AkSoundBankGenerationManager.h" +#include "IAudiokineticTools.h" + +#define LOCTEXT_NAMESPACE "AkAudio" + +static constexpr auto MigrationHelpSwitch = TEXT("help"); +static constexpr auto PerformWaapiBankTransfer = TEXT("transfer-banks-waapi"); +static constexpr auto WriteBankDefinitionFileSwitch = TEXT("generate-bank-definition"); +static constexpr auto DoBankCleanupSwitch = TEXT("delete-banks"); +static constexpr auto TransferAutoLoadSwitch = TEXT("transfer-bank-autoload"); +static constexpr auto IgnoreBankErrorsSwitch = TEXT("ignore-bank-errors"); + +static constexpr auto DoAssetMigrationSwitch = TEXT("migrate-assets"); +static constexpr auto DoProjectUpdateSwitch = TEXT("update-settings"); +static constexpr auto GeneratedSoundBanksPathSwitch = TEXT("generated-soundbanks-folder"); + +static constexpr auto DoDeprecatedAssetCleanupSwitch = TEXT("delete-deprecated-assets"); + +UAkMigrationCommandlet::UAkMigrationCommandlet() +{ + IsClient = false; + IsEditor = true; + IsServer = false; + LogToConsole = true; + + HelpDescription = TEXT("Commandlet for migrating Wwise SoundBanks."); + + HelpParamNames.Add(MigrationHelpSwitch); + HelpParamDescriptions.Add(TEXT("(Optional) Print this help message. This will quit the commandlet immediately.")); + + HelpParamNames.Add(PerformWaapiBankTransfer); + HelpParamDescriptions.Add(TEXT("(Optional) Transfer SoundBanks in Unreal project to Wwise using WAAPI. This parameter can't be used with 'generate-bank-definition'.")); + + HelpParamNames.Add(WriteBankDefinitionFileSwitch); + HelpParamDescriptions.Add(TEXT("(Optional) Create a SoundBank definition file at the specified path. Use an absolute file path (C:/...).\nThe file will then have to be imported in Wwise manually to create the SoundBanks. This parameter can't be used with 'waapi-bank-transfer'.")); + + HelpParamNames.Add(TransferAutoLoadSwitch); + HelpParamDescriptions.Add(TEXT("(Optional) Transfer AkAudioBank's AutoLoad property to assets that were grouped in it.")); + + HelpParamNames.Add(DoBankCleanupSwitch); + HelpParamDescriptions.Add(TEXT("(Optional) Delete all SoundBank assets in the Unreal project (after performing SoundBank Transfer).")); + + HelpParamNames.Add(IgnoreBankErrorsSwitch); + HelpParamDescriptions.Add(TEXT("(Optional) Ignore any errors that occurred during bank transfer through WAAPI or when writing to the SoundBank Definition file and continue migration.")); + + HelpParamNames.Add(DoAssetMigrationSwitch); + HelpParamDescriptions.Add(TEXT("(Optional) Migrate the Wwise assets in the project." + "- Will dirty and save all Wwise assets.\n" + "- The Split Switch Container Media setting will be applied to events individually.\n" + "- WARNING : After performing this operation, it will no longer be possible to transfer the contents of AkAudioBanks to Wwise as the references will have been cleared.\n")); + + HelpParamNames.Add(DoProjectUpdateSwitch); + HelpParamDescriptions.Add(TEXT("(Optional) Update the Wwise and Unreal Project settings." + "If you were using EBP : \n" + " - The \"Enable Auto Defined SoundBanks\" setting will be enabled in the Wwise Project \n" + " - The \"Split Media Per Folder\" setting will be migrated to the \"Create Sub-Folders for Generated Files\" setting the Wwise Project \n" + " - The Wwise Sound Data Folder will be removed from DirectoriesToAlwaysCook \n" + "\n" + "If you are using the Legacy Workflow: \n" + " - The Wwise Sound Data Folder will be removed from DirectoriesToAlwaysStageAsUFS\n" + " - Generated .bnk and .wem files in {0} will be deleted\n" + "In all cases: \n" + " - The \"Root Output Path\" SoundBank setting in Wwise will be imported to the \"Generated SoundBanks Folder\" integration setting in Unreal Engine.\n")); + + + HelpParamNames.Add(GeneratedSoundBanksPathSwitch); + HelpParamDescriptions.Add(TEXT("(Optional) The path to the GeneratedSoundBanks folder used with the 'update-settings' switch. Use an absolute file path.\nThis sets the Generated SoundBank Folder setting in the Unreal project.")); + + HelpParamNames.Add(DoDeprecatedAssetCleanupSwitch); + HelpParamDescriptions.Add(TEXT("(Optional) Delete all deprecated Wwise assets that are still in the project")); + + HelpUsage = TEXT(" -run=AkMigration [ [-waapi-bank-transfer] OR [-generate-bank-definition=.tsv] ] [-delete-banks] [-migrate-assets] [-update-settings] [-delete-deprecated-assets]"); +} + +void UAkMigrationCommandlet::PrintHelp() const +{ + UE_LOG(LogAudiokineticTools, Display, TEXT("%s"), *HelpDescription); + UE_LOG(LogAudiokineticTools, Display, TEXT("Usage: %s"), *HelpUsage); + UE_LOG(LogAudiokineticTools, Display, TEXT("Parameters:")); + for (int32 i = 0; i < HelpParamNames.Num(); ++i) + { + UE_LOG(LogAudiokineticTools, Display, TEXT("\t- %s:\n %s"), *HelpParamNames[i], *HelpParamDescriptions[i]); + } + UE_LOG(LogAudiokineticTools, Display, TEXT("For more information, see %s"), *HelpWebLink); + //TODO vdcormier: Add link to the online documentation when it exists +} + +int32 UAkMigrationCommandlet::Main(const FString& Params) +{ + int32 ReturnCode = 0; + FString DefinitionPath = ""; + TArray Tokens; + TArray Switches; + TMap ParamVals; + + ParseCommandLine(*Params, Tokens, Switches, ParamVals); + + AkAssetMigration::FMigrationContext MigrationOptions; + AkAssetMigration::FMigrationOperations MigrationOperations; + AkAssetMigrationManager AssetMigrationManager; + UE_LOG(LogAudiokineticTools, Display, TEXT("UAkMigrationCommandlet: Beginning project migration")); + + if (Switches.Contains(MigrationHelpSwitch)) + { + PrintHelp(); + return 0; + } + if(!AssetMigrationManager.IsMigrationRequired(MigrationOptions)) + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("UAkMigrationCommandlet: Project has been already migrated.")); + } + + //Bank transfer options + const bool bPerformWaapiBankTransfer = Switches.Contains(PerformWaapiBankTransfer); + const bool bWriteBankDefinitionFile = Switches.Contains(WriteBankDefinitionFileSwitch) || ParamVals.Contains(WriteBankDefinitionFileSwitch); + const FString* BankDefinitionValue = ParamVals.Find(WriteBankDefinitionFileSwitch); + const bool bDoBankCleanup = Switches.Contains(DoBankCleanupSwitch); + const bool bTransferAutoload = Switches.Contains(TransferAutoLoadSwitch); + const bool bIgnoreBankErrors = Switches.Contains(IgnoreBankErrorsSwitch); + + //Asset migration options + const bool bDoAssetMigration = Switches.Contains(DoAssetMigrationSwitch); + const bool bDoDeprecatedAssetCleanup = Switches.Contains(DoDeprecatedAssetCleanupSwitch); + + //Project settings options + const bool bUpdateProjectSettings = Switches.Contains(DoProjectUpdateSwitch); + const FString* GeneratedSoundBanksPathValue = ParamVals.Find(GeneratedSoundBanksPathSwitch); + + if(bPerformWaapiBankTransfer && bWriteBankDefinitionFile) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("UAkMigrationCommandlet: Can't use both 'waapi-bank-transfer' and 'generate-bank-definition' at the same time.")); + return 1; + } + + if (bPerformWaapiBankTransfer) + { +#if AK_SUPPORT_WAAPI + FAkWaapiClient::Initialize(); + if (UAkSettings* AkSettings = GetMutableDefault()) + { + AkSettings->InitWaapiSync(); + } + FAkWaapiClient* WaapiClient = FAkWaapiClient::Get(); + + if (!WaapiClient) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("'transfer-banks-waapi' switch is set, but could not get WAAPI Client, cancelling migration.")); + return 1; + } + else if (!WaapiClient->IsConnected()) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("'transfer-banks-waapi' switch is set, but could not connect WAAPI Client, cancelling migration.")); + return 1; + } +#else + UE_LOG(LogAudiokineticTools, Error, TEXT("UAkMigrationCommandlet: 'waapi-bank-transfer' switch is enabled, but the Unreal project does not support WAAPI. Ensure your current platform supports AkAutobahn.")); + return 1; +#endif + MigrationOperations.BankTransferMethod = AkAssetMigration::WAAPI; + } + + if (bWriteBankDefinitionFile) + { + MigrationOperations.BankTransferMethod = AkAssetMigration::DefinitionFile; + if (!BankDefinitionValue) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("UAkMigrationCommandlet: 'generate-bank-definition' file name not provided. Usage: '-generate-bank-definition=.tsv'.")); + return 1; + } + FString Path = *BankDefinitionValue; + FString Extension = FPaths::GetExtension(Path); + if (Extension != "tsv") + { + UE_LOG(LogAudiokineticTools, Error, TEXT("UAkMigrationCommandlet: Wrong file extension. The Definition File should be a '.tsv' file. The provided extension was '.%s'."), *Extension); + return 1; + } + MigrationOperations.DefinitionFilePath = Path; + } + + MigrationOperations.bDoBankCleanup = bDoBankCleanup; + MigrationOperations.bTransferAutoload = bTransferAutoload; + MigrationOperations.bDoAssetMigration = bDoAssetMigration; + MigrationOperations.bDoDeprecatedAssetCleanup = bDoDeprecatedAssetCleanup; + MigrationOperations.bIgnoreBankTransferErrors = bIgnoreBankErrors; + + MigrationOperations.bDoProjectUpdate = bUpdateProjectSettings; + if (GeneratedSoundBanksPathValue) + { + MigrationOperations.GeneratedSoundBankDirectory = *GeneratedSoundBanksPathValue; + if (!bUpdateProjectSettings) + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("UAkMigrationCommandlet: 'generated-soundbanks-folder' switch is set, but not 'update-settings'. The Generated SoundBanks Path setting will not be updated.")); + } + } + + AkAssetMigrationManager::MigrationResult Result = AssetMigrationManager.PerformMigration(MigrationOperations); + + if (!Result.bSuccess) + { + ReturnCode = 1; + } + + if (!Result.bProjectMigrationSucceeded) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("UAkMigrationCommandlet: Failed to migrate all project settings. Inspect the log for more details.")); + } + + if (!Result.bBankTransferSucceeded) + { + if (MigrationOperations.BankTransferMethod == AkAssetMigration::WAAPI) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("UAkMigrationCommandlet: Failed to transfer banks using WAAPI. Inspect the log for more details.")); + } + else if (MigrationOperations.BankTransferMethod == AkAssetMigration::WAAPI) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("UAkMigrationCommandlet: Failed to create SoundBank definition file. Inspect the log for more details.")); + } + } + + if (!Result.bAssetMigrationSucceeded) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("UAkMigrationCommandlet: Failed to update all Wwise assets. Inspect the log for more details.")); + } + + if (!Result.bAssetCleanupSucceeded) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("UAkMigrationCommandlet: Failed to delete all deprecated assets. Inspect the log for more details.")); + } + + return ReturnCode; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkMigrationWidgets.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkMigrationWidgets.cpp new file mode 100644 index 0000000..56a6a60 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkMigrationWidgets.cpp @@ -0,0 +1,993 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkMigrationWidgets.h" +#include "AkSettingsPerUser.h" +#include "AkUEFeatures.h" +#include "AkUnrealEditorHelper.h" +#include "AkUnrealHelper.h" +#include "AkWaapiClient.h" + +#include "DesktopPlatformModule.h" +#include "Framework/Application/SlateApplication.h" +#include "Framework/Application/SlateApplication.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "IDesktopPlatform.h" +#include "Interfaces/IMainFrameModule.h" + +#include "Widgets/Layout/SHeader.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SHyperlink.h" +#include "Widgets/Input/SMultiLineEditableTextBox.h" +#include "Widgets/Layout/SSpacer.h" +#include "Widgets/Layout/SExpandableArea.h" +#include "Widgets/Text/STextBlock.h" + +#define LOCTEXT_NAMESPACE "AkAudio" + +namespace MigrationDialogUtils +{ + inline const FSlateBrush* GetExpandableAreaBorderImage(const SExpandableArea& Area) + { + if (Area.IsTitleHovered()) + { + return Area.IsExpanded() ? FAkAppStyle::Get().GetBrush("DetailsView.CategoryTop_Hovered") : FAkAppStyle::Get().GetBrush("DetailsView.CollapsedCategory_Hovered"); + } + return Area.IsExpanded() ? FAkAppStyle::Get().GetBrush("DetailsView.CategoryTop") : FAkAppStyle::Get().GetBrush("DetailsView.CollapsedCategory"); + } +} + +void SMigrationWidget::Construct(const FArguments& InArgs) +{ + bShowBankTransfer = InArgs._ShowBankTransfer; + bShowMediaCleanup = InArgs._ShowDeprecatedAssetCleanup; + bShowAssetMigration = InArgs._ShowAssetMigration; + bShowProjectMigration = InArgs._ShowProjectMigration; + Dialog = InArgs._Dialog; + + ChildSlot + [ + SNew(SBox) + .MaxDesiredWidth(800) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + [ + + SNew(SBorder) + .BorderImage(FAkAppStyle::Get().GetBrush("ToolPanel.GroupBorder")) + .Padding(4.0f) + [ + SNew(SVerticalBox) + + /// Intro title + + SVerticalBox::Slot() + .Padding(0, 5) + .AutoHeight() + .HAlign(EHorizontalAlignment::HAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("MigrationWelcome", + "Migrating to Single Source of Truth\n" + )) + .Font(FAkAppStyle::Get().GetFontStyle("StandardDialog.LargeFont")) + ] + + /// Intro text + + SVerticalBox::Slot() + .Padding(0, 0) + .AutoHeight() + [ + SNew(STextBlock) + .Text(LOCTEXT("MigrationIntroText", + "We recommend performing all necessary migration steps as soon as possible, in a single operation. Since the migration operation has an impact on Unreal assets, ensure to checkout all Wwise-related assets from Source control. " + "Migration will also potentially update settings in your Wwise project. It is important to first check-out, open, and migrate the Wwise Project in Wwise Authoring BEFORE pressing 'Continue'.\n" + )) + .AutoWrapText(true) + ] + + /// More info hyperlink + + SVerticalBox::Slot() + .Padding(0, 0) + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2.0f) + .AutoWidth() + [ + SNew(SHyperlink) + .Text(LOCTEXT("MigrationNotesLink", "Please refer to Wwise 2022.1 migration notes for more information about the migration process.")) + .OnNavigate_Lambda([=]{ FPlatformProcess::LaunchURL(TEXT("https://www.audiokinetic.com/library/edge/?source=UE4&id=pg_important_migration_notes_2022_1_0.html"), nullptr, nullptr); }) + + ] + + SHorizontalBox::Slot() + [ + SNew(SSpacer) + ] + ] + + /// Necessary steps text: + + SVerticalBox::Slot() + .Padding(0, 0) + .AutoHeight() + [ + SNew(STextBlock) + .Text(LOCTEXT("MigrationIntroNecessarySteps", + "\nBelow are the necessary migration steps for your project:\n" + )) + .AutoWrapText(true) + ] + + /// Transfer SoundBanks option + + SVerticalBox::Slot() + .AutoHeight() + [ + SAssignNew(BankTransferWidget, SBankTransferWidget) + .Visibility(this, &SMigrationWidget::GetBankTransferWidgetVisibility) + ] + + /// Deleting deprecated assets + + SVerticalBox::Slot() + .Padding(0, 5) + .AutoHeight() + [ + SAssignNew(DeprecatedAssetCleanupWidget, SDeprecatedAssetCleanupWidget) + .Visibility(this, &SMigrationWidget::GetMediaCleanupWidgetVisibility) + .NumDeprecatedAssets(InArgs._NumDeprecatedAssets) + ] + + /// Migrating assets + + SVerticalBox::Slot() + .Padding(0, 5) + .AutoHeight() + [ + SAssignNew(AssetMigrationWidget, SAssetMigrationWidget) + .Visibility(this, &SMigrationWidget::GetAssetMigrationWidgetVisibility) + ] + + // Migrating Wwise Project options + + SVerticalBox::Slot() + .Padding(0, 5) + .AutoHeight() + [ + SAssignNew(ProjectMigrationWidget, SProjectMigrationWidget) + .Visibility(this, &SMigrationWidget::GetProjectMigrationWidgetVisibility) + ] + + SVerticalBox::Slot() + .Padding(0, 5) + .AutoHeight() + [ + SNew(STextBlock) + .Text(LOCTEXT("GeneralInfo", + "If you choose the cancel option or a subset of operations to perform, you can open this dialog at a later time from the Build Menu under Audiokinetic > Finish Project Migration.\n" + )) + .Font(FAkAppStyle::Get().GetFontStyle("StandardDialog.LargeFont")) + .AutoWrapText(true) + ] + + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SBorder) + .BorderImage(FAkAppStyle::Get().GetBrush("ToolPanel.GroupBorder")) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2.0f) + .AutoWidth() + [ + SNew(SButton) + .ButtonStyle(FAkAppStyle::Get(), "FlatButton.Success") + .ForegroundColor(FLinearColor::White) + .OnClicked(this, &SMigrationWidget::OnContinueClicked) + .IsEnabled(this, &SMigrationWidget::CanClickContinue) + .ToolTipText(this, &SMigrationWidget::GetContinueToolTip) + [ + SNew(STextBlock) + .TextStyle(FAkAppStyle::Get(), "FlatButton.DefaultTextStyle") + .Text(FText::FromString("Continue")) + ] + ] + + + SHorizontalBox::Slot() + .Padding(2.0f) + .AutoWidth() + [ + SNew(SButton) + .ButtonStyle(FAkAppStyle::Get(), "FlatButton.Default") + .ForegroundColor(FLinearColor::White) + .OnClicked(this, &SMigrationWidget::OnCancelClicked) + .ToolTipText(FText::FromString("Cancel")) + [ + SNew(STextBlock) + .TextStyle(FAkAppStyle::Get(), "FlatButton.DefaultTextStyle") + .Text(FText::FromString("Cancel")) + ] + ] + ] + ] + ] + ] + ] + ]; +} + +bool SMigrationWidget::CanClickContinue() const +{ + if (bShowBankTransfer && BankTransferWidget->SoundBankTransferCheckBox->IsChecked()) + { + if (BankTransferWidget->BankTransferMethod == AkAssetMigration::EBankTransferMode::NoTransfer) + { + return false; + } + if (BankTransferWidget->BankTransferMethod == AkAssetMigration::EBankTransferMode::DefinitionFile && BankTransferWidget->SoundBankDefinitionFilePath.IsEmpty()) + { + return false; + } + } + return true; +} + +FText SMigrationWidget::GetContinueToolTip() const +{ + if (bShowBankTransfer && BankTransferWidget->SoundBankTransferCheckBox->IsChecked()) + { + if (BankTransferWidget->BankTransferMethod == AkAssetMigration::EBankTransferMode::NoTransfer) + { + return FText::FromString("Please choose a SoundBank transfer method first"); + } + if (BankTransferWidget->BankTransferMethod == AkAssetMigration::EBankTransferMode::DefinitionFile && BankTransferWidget->SoundBankDefinitionFilePath.IsEmpty()) + { + return FText::FromString("Please choose a SoundBank Defintion file path first"); + } + } + return FText::FromString("Continue"); +} + +FReply SMigrationWidget::OnContinueClicked() +{ + FSlateApplication::Get().RequestDestroyWindow(Dialog.ToSharedRef()); + return FReply::Handled(); +} + +FReply SMigrationWidget::OnCancelClicked() +{ + BankTransferWidget->BankTransferMethod = AkAssetMigration::EBankTransferMode::NoTransfer; + BankTransferWidget->DeleteSoundBanksCheckBox->SetIsChecked(false); + BankTransferWidget->TransferAutoLoadCheckBox->SetIsChecked(false); + DeprecatedAssetCleanupWidget->DeleteAssetsCheckBox->SetIsChecked(false); + AssetMigrationWidget->MigrateAssetsCheckBox->SetIsChecked(false); + bCancel = true; + + FSlateApplication::Get().RequestDestroyWindow(Dialog.ToSharedRef()); + return FReply::Handled(); +} + +EVisibility SMigrationWidget::GetBankTransferWidgetVisibility() const +{ + return bShowBankTransfer ? EVisibility::Visible : EVisibility::Collapsed; +} + +EVisibility SMigrationWidget::GetMediaCleanupWidgetVisibility() const +{ + return bShowMediaCleanup ? EVisibility::Visible : EVisibility::Collapsed; +} + +EVisibility SMigrationWidget::GetAssetMigrationWidgetVisibility() const +{ + return bShowAssetMigration ? EVisibility::Visible : EVisibility::Collapsed; +} + +EVisibility SMigrationWidget::GetProjectMigrationWidgetVisibility() const +{ + return bShowProjectMigration ? EVisibility::Visible : EVisibility::Collapsed; +} + +void SBankTransferWidget::Construct(const FArguments& InArgs) +{ + SoundBankDefinitionFilePath = AkUnrealHelper::GetProjectDirectory() + "SoundBankDefinition.tsv"; + + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + [ + SAssignNew(ExpandableSection, SExpandableArea) + .InitiallyCollapsed(false) + .BorderBackgroundColor(FLinearColor(0.6f, 0.6f, 0.6f, 1.0f)) + .BorderImage_Lambda([this]() { return MigrationDialogUtils::GetExpandableAreaBorderImage(*ExpandableSection); }) + .HeaderContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("SounbankMigrationHeader", "Soundbank Migration")) + .Font(FAkAppStyle::Get().GetFontStyle("DetailsView.CategoryFontStyle")) + .ShadowOffset(FVector2D(1.0f, 1.0f)) + ] + .BodyContent() + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + [ + SNew(SBorder) + .BorderImage(FAkAppStyle::Get().GetBrush("ToolPanel.GroupBorder")) + .Padding(4.0f) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .FillHeight(0.05f) + [ + SNew(SSpacer) + ] + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(EHorizontalAlignment::HAlign_Left) + [ + SNew(STextBlock) + .Text(LOCTEXT("BankMigrationMessageBegin", + "AkAudioBanks assets are no longer used in the integration and must be deleted.\n" + "You can optionally transfer your SoundBank structures defined in Unreal back to Wwise Authoring (before deleting them).\n" + "The 'Auto Load' property on AkAudioBank assets can also optionally be transferred to the assets that were grouped in them.\n" + + )) + .AutoWrapText(true) + ] + + SVerticalBox::Slot() + .FillHeight(0.10f) + [ + SNew(SSpacer) + ] + + + SVerticalBox::Slot() + .FillHeight(0.10f) + [ + SNew(SSpacer) + ] + + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0f, 2.0f) + .HAlign(EHorizontalAlignment::HAlign_Left) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0.0f, 2.0f) + [ + SAssignNew(SoundBankTransferCheckBox, SCheckBox) + .IsChecked(ECheckBoxState::Checked) + .OnCheckStateChanged(this, &SBankTransferWidget::OnCheckedTransferBanks) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("DoTransferMessage", "Transfer SoundBanks To Wwise")) + .ToolTipText(LOCTEXT("TransferAkAudioBankToolTip", "Create SoundBanks in the Wwise project matching the AkAudioBank assets in Unreal.\nThey will contain the same Events and Aux Busses that were grouped into the Unreal assets.")) + .Font(FAkAppStyle::Get().GetFontStyle("StandardDialog.LargeFont")) + ] + ] + + + SHorizontalBox::Slot() + .HAlign(EHorizontalAlignment::HAlign_Left) + .AutoWidth() + .Padding(8.0f, 2.0f) + [ + SNew(SBorder) + .BorderBackgroundColor(this, &SBankTransferWidget::GetDropDownBorderColour) + .ColorAndOpacity(this, &SBankTransferWidget::GetDropDownColour) + .BorderImage(FAkAppStyle::Get().GetBrush("ToolPanel.GroupBorder")) + [ + SNew(SComboButton) + .ToolTipText(LOCTEXT("ComboButtonTip", "Choose a transfer method from the drop-down menu.")) + .OnGetMenuContent(this, &SBankTransferWidget::OnGetTransferMethodMenu) + .ContentPadding(FMargin(0)) + .Visibility(this, &SBankTransferWidget::GetTransferMethodVisibility) + .ButtonContent() + [ + SNew(STextBlock) + .Text(this, &SBankTransferWidget::GetTransferMethodText) + ] + ] + ] + ] + + SVerticalBox::Slot() + .Padding(0, 5) + .AutoHeight() + [ + SAssignNew(SoundBankDefinitionFilePathPicker, SDefinitionFilePicker) + .FilePath(SoundBankDefinitionFilePath) + .OnFileChanged(this, &SBankTransferWidget::SetDefinitionFilePath) + .Visibility(this, &SBankTransferWidget::GetDefinitionFilePathVisibility) + ] + +SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0f, 2.0f) + .HAlign(EHorizontalAlignment::HAlign_Left) + [ + SAssignNew(TransferAutoLoadCheckBox, SCheckBox) + .IsChecked(ECheckBoxState::Checked) + .ToolTipText(LOCTEXT("TransferAutoloadTooltip", "Transfer SoundBank Auto Load property from deprecated AkAudioBank assets to the AkAudioType assets that were grouped in them.")) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("TransferAutoloadMessage", "Transfer Auto Load property from AkAudioBank assets to AkAudioType assets")) + .Font(FAkAppStyle::Get().GetFontStyle("StandardDialog.LargeFont")) + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0f, 2.0f) + .HAlign(EHorizontalAlignment::HAlign_Left) + [ + SAssignNew(DeleteSoundBanksCheckBox, SCheckBox) + .IsChecked(ECheckBoxState::Checked) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("DeleteAkAudioBankMessage", " Delete AkAudioBank Assets")) + .ToolTipText(LOCTEXT("DeleteAkAudioBankToolTip", "Delete all AkAudioBank assets in the unreal project.")) + .Font(FAkAppStyle::Get().GetFontStyle("StandardDialog.LargeFont")) + ] + ] + ] + ] + ] + ] + ]; +} + +EVisibility SBankTransferWidget::GetDefinitionFilePathVisibility() const +{ + return SoundBankTransferCheckBox->IsChecked() && BankTransferMethod == AkAssetMigration::EBankTransferMode::DefinitionFile ? EVisibility::Visible : EVisibility::Collapsed; +} + +void SBankTransferWidget::SetDefinitionFilePath(const FString& PickedPath) +{ + SoundBankDefinitionFilePath = PickedPath; +} + +void SBankTransferWidget::SetTransferMethod(AkAssetMigration::EBankTransferMode TransferMethod) +{ + BankTransferMethod = TransferMethod; +} + +void SBankTransferWidget::OnCheckedTransferBanks(ECheckBoxState NewState) +{ + if (NewState != ECheckBoxState::Checked) + { + BankTransferMethod = AkAssetMigration::EBankTransferMode::NoTransfer; + } +} + +EVisibility SBankTransferWidget::GetTransferMethodVisibility() const +{ + return SoundBankTransferCheckBox->IsChecked() ? EVisibility::Visible : EVisibility::Collapsed; +} + +FText SBankTransferWidget::GetTransferMethodText() const +{ + if (BankTransferMethod == AkAssetMigration::EBankTransferMode::DefinitionFile) + { + return LOCTEXT("SoundBankDefinition", "Create SoundBank Definition File"); + } + else if (BankTransferMethod == AkAssetMigration::EBankTransferMode::WAAPI) + { + return LOCTEXT("WaapiTransfer", "WAAPI"); + } + + return LOCTEXT("NoTransferMethodSet", "Choose a transfer method..."); +} + +FSlateColor SBankTransferWidget::GetDropDownBorderColour() const +{ + if (BankTransferMethod == AkAssetMigration::EBankTransferMode::NoTransfer && SoundBankTransferCheckBox->IsChecked()) + { + return FLinearColor::Red; + } + + return FLinearColor::White; +} + +FLinearColor SBankTransferWidget::GetDropDownColour() const +{ + if (BankTransferMethod == AkAssetMigration::EBankTransferMode::NoTransfer && SoundBankTransferCheckBox->IsChecked()) + { + return FLinearColor::Red; + } + + return FLinearColor::White; +} + +TSharedRef SBankTransferWidget::OnGetTransferMethodMenu() +{ + FMenuBuilder MenuBuilder(true, nullptr); + MenuBuilder.AddMenuEntry( + LOCTEXT("SoundBankDefinition", "Create SoundBank Definition File"), + FText(), + FSlateIcon(), + FUIAction( + FExecuteAction::CreateSP(this, &SBankTransferWidget::SetTransferMethod, AkAssetMigration::EBankTransferMode::DefinitionFile) + ) + ); + + MenuBuilder.AddMenuEntry( + LOCTEXT("WaapiTransferMenuItemText","WAAPI"), + FText(), + FSlateIcon(), + FUIAction( + FExecuteAction::CreateSP(this, &SBankTransferWidget::SetTransferMethod, AkAssetMigration::EBankTransferMode::WAAPI), + FCanExecuteAction::CreateSP(this, &SBankTransferWidget::CheckWaapiConnection) + ) + ); + MenuBuilder.EndSection(); + return MenuBuilder.MakeWidget(); +} + +bool SBankTransferWidget::CheckWaapiConnection() const +{ + bool bWaapiConnected = false; + if (auto UserSettings = GetDefault()) + { + if (!UserSettings->bAutoConnectToWAAPI) + { + LOCTEXT("WaapiTransferMenuItemText","WAAPI (Auto Connect to WAAPI disabled in user settings)"); + } + else + { + FAkWaapiClient* WaapiClient = FAkWaapiClient::Get(); + bWaapiConnected = WaapiClient && WaapiClient->IsConnected(); + if (!bWaapiConnected) + { + LOCTEXT("WaapiTransferMenuItemText","WAAPI (WAAPI connection not established)"); + } + } + } + + if (bWaapiConnected) + { + LOCTEXT("WaapiTransferMenuItemText","WAAPI"); + } + return bWaapiConnected; +} + +void SDeprecatedAssetCleanupWidget::Construct(const FArguments& InArgs) +{ + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + [ + SAssignNew(ExpandableSection, SExpandableArea) + .InitiallyCollapsed(false) + .BorderBackgroundColor(FLinearColor(0.6f, 0.6f, 0.6f, 1.0f)) + .BorderImage_Lambda([this]() { return MigrationDialogUtils::GetExpandableAreaBorderImage(*ExpandableSection); }) + .HeaderContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("MediaCleanupHeader", "Delete deprecated assets")) + .Font(FAkAppStyle::Get().GetFontStyle("DetailsView.CategoryFontStyle")) + .ShadowOffset(FVector2D(1.0f, 1.0f)) + ] + .BodyContent() + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + [ + SNew(SBorder) + .BorderImage(FAkAppStyle::Get().GetBrush("ToolPanel.GroupBorder")) + .Padding(4.0f) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(4.0f) + .HAlign(EHorizontalAlignment::HAlign_Left) + [ + SNew(STextBlock) + .Text(FText::FormatOrdered(LOCTEXT("AssetCleanupMessageBegin", + "AkMediaAsset, AkFolder and AkPlatformAssetData have been deprecated, all assets of this type will be deleted from the project. " + "The project currently contains {0} such assets.\n"), FText::FromString(FString::FromInt(InArgs._NumDeprecatedAssets)) + )) + .AutoWrapText(true) + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0f, 2.0f) + .HAlign(EHorizontalAlignment::HAlign_Left) + [ + SAssignNew(DeleteAssetsCheckBox, SCheckBox) + .IsChecked(ECheckBoxState::Checked) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("DeleteAssetMessage", " Delete deprecated assets")) + .ToolTipText(LOCTEXT("DeleteMediaToolTip", "Delete all deprecated assets that are still in the project")) + .Font(FAkAppStyle::Get().GetFontStyle("StandardDialog.LargeFont")) + ] + ] + ] + ] + ] + ] + ]; +} + +void SAssetMigrationWidget::Construct(const FArguments& InArgs) +{ + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + [ + SAssignNew(ExpandableSection, SExpandableArea) + .InitiallyCollapsed(false) + .BorderBackgroundColor(FLinearColor(0.6f, 0.6f, 0.6f, 1.0f)) + .BorderImage_Lambda([this]() { return MigrationDialogUtils::GetExpandableAreaBorderImage(*ExpandableSection); }) + .HeaderContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("UpdateAssetsHeader", "Migrate Wwise Assets")) + .Font(FAkAppStyle::Get().GetFontStyle("DetailsView.CategoryFontStyle")) + .ShadowOffset(FVector2D(1.0f, 1.0f)) + ] + .BodyContent() + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + [ + SNew(SBorder) + .BorderImage(FAkAppStyle::Get().GetBrush("ToolPanel.GroupBorder")) + .Padding(4.0f) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .FillHeight(0.05f) + [ + SNew(SSpacer) + ] + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(EHorizontalAlignment::HAlign_Left) + [ + SNew(STextBlock) + .Text(LOCTEXT("AssetMigrationMessageBegin", + "Wwise asset properties have changed and they no longer serialize SoundBank or media binary data.\n" + "Wwise asset properties will be updated and reserialized to the disk." + )) + .AutoWrapText(true) + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0f, 2.0f) + .HAlign(EHorizontalAlignment::HAlign_Left) + [ + SAssignNew(MigrateAssetsCheckBox, SCheckBox) + .IsChecked(ECheckBoxState::Checked) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("MigrateAssetsMessage", " Migrate Wwise assets")) + .Font(FAkAppStyle::Get().GetFontStyle("StandardDialog.LargeFont")) + .ToolTipText(LOCTEXT("MigrateAssetsTooltip", "Dirty and save all Wwise assets")) + ] + ] + ] + ] + ] + ] + ]; +} + +void SProjectMigrationWidget::Construct(const FArguments& InArgs) +{ + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + [ + SAssignNew(ExpandableSection, SExpandableArea) + .InitiallyCollapsed(false) + .BorderBackgroundColor(FLinearColor(0.6f, 0.6f, 0.6f, 1.0f)) + .BorderImage_Lambda([this]() { return MigrationDialogUtils::GetExpandableAreaBorderImage(*ExpandableSection); }) + .HeaderContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("ProjectChangesHeader", "Update Wwise and Unreal project settings")) + .Font(FAkAppStyle::Get().GetFontStyle("DetailsView.CategoryFontStyle")) + .ShadowOffset(FVector2D(1.0f, 1.0f)) + ] + .BodyContent() + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + [ + SNew(SBorder) + .BorderImage(FAkAppStyle::Get().GetBrush("ToolPanel.GroupBorder")) + .Padding(4.0f) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .FillHeight(0.05f) + [ + SNew(SSpacer) + ] + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(EHorizontalAlignment::HAlign_Left) + [ + SNew(STextBlock) + .Text(LOCTEXT("ProjectSettingsMigrationMessageBegin", + "SoundBank generation settings in your Wwise project, and the Wwise Integration settings in your Unreal project, need to be updated.\n" + )) + .AutoWrapText(true) + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0f, 2.0f) + .HAlign(EHorizontalAlignment::HAlign_Left) + [ + SAssignNew(AutoMigrateCheckbox, SCheckBox) + .IsChecked(ECheckBoxState::Checked) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("UpdateProjectMessage", "Update project settings")) + .Font(FAkAppStyle::Get().GetFontStyle("StandardDialog.LargeFont")) + .ToolTipText(LOCTEXT("UpdateProjectTooltip", "Update Unreal project settings")) + ] + ] + + SVerticalBox::Slot() + .Padding(0, 5) + .AutoHeight() + [ + SNew(STextBlock) + .Text(LOCTEXT("AkGeneratedSoundBanksFolderMigration", + "\nGeneratedSoundBanks folder location:" + )) + .Visibility(this, &SProjectMigrationWidget::GetPathVisibility) + + ] + + SVerticalBox::Slot() + .Padding(0, 5) + .AutoHeight() + [ + SAssignNew(GeneratedSoundBanksFolderPickerWidget, SDirectoryPicker) + .Directory(AkUnrealHelper::GetSoundBankDirectory()) + .Visibility(this, &SProjectMigrationWidget::GetPathVisibility) + ] + ] + ] + ] + ] + ]; +} + +EVisibility SProjectMigrationWidget::GetPathVisibility() const +{ + return AutoMigrateCheckbox->IsChecked() ? EVisibility::Visible : EVisibility::Collapsed; +} + +void SBankMigrationFailureWidget::Construct(const FArguments& InArgs) +{ + Dialog = InArgs._Dialog; + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .Padding(4.0f) + + .AutoHeight() + [ + SNew(STextBlock) + .Text(LOCTEXT("BankMigrationFailureHeader", "Errors encountered while transfering banks")) + .Font(FAkAppStyle::Get().GetFontStyle("DetailsView.CategoryFontStyle")) + .ShadowOffset(FVector2D(1.0f, 1.0f)) + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(4.0f) + [ + SNew(SBorder) + .BorderImage(FAkAppStyle::Get().GetBrush("ToolPanel.GroupBorder")) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .MaxHeight(600) + .HAlign(EHorizontalAlignment::HAlign_Left) + [ + SNew(SMultiLineEditableTextBox) + .Text(InArgs._ErrorMessage) + .AutoWrapText(true) + .IsReadOnly(true) + ] + ] + ] + + SVerticalBox::Slot() + .AutoHeight() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(4.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2.0f) + .AutoWidth() + [ + SNew(SButton) + .ButtonStyle(FAkAppStyle::Get(), "FlatButton.Default") + .ForegroundColor(FLinearColor::White) + .OnClicked(this, &SBankMigrationFailureWidget::OnIgnoreClicked) + .ToolTipText(FText::FromString("Ignore warnings and continue migration")) + [ + SNew(STextBlock) + .TextStyle(FAkAppStyle::Get(), "FlatButton.DefaultTextStyle") + .Text(FText::FromString("Ignore Errors and Continue")) + ] + ] + + SHorizontalBox::Slot() + .Padding(2.0f) + .AutoWidth() + [ + SNew(SButton) + .ButtonStyle(FAkAppStyle::Get(), "FlatButton.Default") + .ForegroundColor(FLinearColor::White) + .OnClicked(this, &SBankMigrationFailureWidget::OnCancelClicked) + .ToolTipText(FText::FromString("Cancel the migration process to re-attempt at a later time")) + [ + SNew(STextBlock) + .TextStyle(FAkAppStyle::Get(), "FlatButton.DefaultTextStyle") + .Text(FText::FromString("Cancel Migration")) + ] + ] + ] + ] + ]; +} + +FReply SBankMigrationFailureWidget::OnCancelClicked() +{ + bCancel = true; + FSlateApplication::Get().RequestDestroyWindow(Dialog.ToSharedRef()); + return FReply::Handled(); +} + +FReply SBankMigrationFailureWidget::OnIgnoreClicked() +{ + bCancel = false; + FSlateApplication::Get().RequestDestroyWindow(Dialog.ToSharedRef()); + return FReply::Handled(); +} + +void SDefinitionFilePicker::Construct( const FArguments& InArgs ) +{ + OnFileChanged = InArgs._OnFileChanged; + + FilePath = InArgs._FilePath; + Message = InArgs._Message; + + TSharedPtr OpenButton; + ChildSlot + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SAssignNew(EditableTextBox, SEditableTextBox) + .Text(this, &SDefinitionFilePicker::GetFilePathText) + .OnTextChanged(this, &SDefinitionFilePicker::OnFileTextChanged) + .OnTextCommitted(this, &SDefinitionFilePicker::OnFileTextCommited) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(4.0f, 0.0f, 0.0f, 0.0f) + [ + SAssignNew(OpenButton, SButton) + .ToolTipText(Message) + .OnClicked(this, &SDefinitionFilePicker::BrowseForFile) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("..."))) + ] + ] + ]; + + OpenButton->SetEnabled(InArgs._IsEnabled); +} + +void SDefinitionFilePicker::OnFileTextChanged(const FText& InFilePath) +{ + FilePath = InFilePath.ToString(); +} + +void SDefinitionFilePicker::OnFileTextCommited(const FText& InText, ETextCommit::Type InCommitType) +{ + FilePath = InText.ToString(); + OnFileChanged.ExecuteIfBound(FilePath); +} + +FString SDefinitionFilePicker::GetFilePath() const +{ + return FilePath; +} + +FText SDefinitionFilePicker::GetFilePathText() const +{ + return FText::FromString(GetFilePath()); +} + +bool SDefinitionFilePicker::OpenDefinitionFilePicker(FString& OutFilePath, const FString& DefaultFilePath) +{ + TArray OpenFileNames; + bool bOpened = false; + + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); + if (DesktopPlatform) + { + void* ParentWindowWindowHandle = NULL; + + IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked(TEXT("MainFrame")); + const TSharedPtr& MainFrameParentWindow = MainFrameModule.GetParentWindow(); + if (MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid()) + { + ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle(); + } + + bool bAllowMultiSelect = false; + FString FileTypes = TEXT("TSV Files (*.tsv)|*.tsv"); + FString DefaultFolder = FPaths::GetPath(DefaultFilePath); + FString DefaultFile = FPaths::GetCleanFilename(DefaultFilePath); + const FString Title = TEXT("Choose location for definition file"); + bOpened = DesktopPlatform->SaveFileDialog( + ParentWindowWindowHandle, + Title, + *DefaultFolder, + *DefaultFile, + FileTypes, + bAllowMultiSelect, + OpenFileNames + ); + } + if (bOpened) + { + OutFilePath = FPaths::ConvertRelativePathToFull(OpenFileNames[0]); + } + + return bOpened; +} + +FReply SDefinitionFilePicker::BrowseForFile() +{ + if ( OpenDefinitionFilePicker(FilePath, FilePath) ) + { + OnFileChanged.ExecuteIfBound(FilePath); + } + + return FReply::Handled(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkMigrationWidgets.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkMigrationWidgets.h new file mode 100644 index 0000000..6a3551c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkMigrationWidgets.h @@ -0,0 +1,220 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkAssetMigrationHelper.h" + +#include "Application/SlateWindowHelper.h" +#include "CoreMinimal.h" +#include "Input/Reply.h" +#include "Styling/SlateTypes.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "Widgets/SCompoundWidget.h" +#include "Widgets/SWidget.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SDirectoryPicker.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Input/SFilePathPicker.h" +#include "Widgets/Layout/SExpandableArea.h" + +class SEditableTextBox; + +/** + * A File path box (that actually lets you create a new file). + */ +class SDefinitionFilePicker : public SCompoundWidget +{ +public: + DECLARE_DELEGATE_OneParam(FOnFileChanged, const FString& /*Directory*/); + + SLATE_BEGIN_ARGS(SDefinitionFilePicker) + : _IsEnabled(true) {} + SLATE_ARGUMENT(FString, FilePath) + SLATE_ARGUMENT(FText, Message) + SLATE_ATTRIBUTE(bool, IsEnabled) + /** Called when a path has been picked or modified. */ + SLATE_EVENT(FOnFileChanged, OnFileChanged) + SLATE_END_ARGS() + +public: + void Construct(const FArguments& InArgs); + FString GetFilePath() const; + + /** + * Declares a delegate that is executed when a file was picked in the SFilePicker widget. + * + * The first parameter will contain the path to the picked file. + */ + DECLARE_DELEGATE_OneParam(FOnFilePicked, const FString& /*PickedPath*/); + +private: + void OnFileTextChanged(const FText& InFilePath); + void OnFileTextCommited(const FText& InText, ETextCommit::Type InCommitType); + FText GetFilePathText() const; + bool OpenDefinitionFilePicker(FString& OutDirectory, const FString& DefaultPath); + FReply BrowseForFile(); + +private: + FString FilePath; + FText Message; + + /** Holds a delegate that is executed when a file was picked. */ + FOnFileChanged OnFileChanged; + TSharedPtr EditableTextBox; +}; + + +class SBankTransferWidget : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SBankTransferWidget) {} + SLATE_END_ARGS() + + TSharedPtr SoundBankTransferCheckBox; + TSharedPtr TransferAutoLoadCheckBox; + TSharedPtr DeleteSoundBanksCheckBox; + TSharedPtr SoundBankDefinitionFilePathPicker; + + AkAssetMigration::EBankTransferMode BankTransferMethod = AkAssetMigration::EBankTransferMode::NoTransfer; + FString SoundBankDefinitionFilePath = TEXT(""); + + void Construct(const FArguments& InArgs); + void SetDefinitionFilePath(const FString& PickedPath); + void SetTransferMethod(AkAssetMigration::EBankTransferMode TransferMethod); + void OnCheckedTransferBanks(ECheckBoxState NewState); + bool GetDefinitionFilePath(FString& OutFilePath) const ; + bool CheckWaapiConnection() const; + + EVisibility GetDefinitionFilePathVisibility() const; + EVisibility GetTransferMethodVisibility() const; + TSharedRef OnGetTransferMethodMenu(); + FText GetTransferMethodText() const; + FLinearColor GetDropDownColour() const; + FSlateColor GetDropDownBorderColour() const; + +private : + TSharedPtr ExpandableSection; + TSharedPtr ExpandableDetails; +}; + + +class SDeprecatedAssetCleanupWidget : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SDeprecatedAssetCleanupWidget) {} + SLATE_ARGUMENT(int, NumDeprecatedAssets) + SLATE_END_ARGS() + TSharedPtr DeleteAssetsCheckBox; + void Construct(const FArguments& InArgs); + +private: + TSharedPtr ExpandableSection; + TSharedPtr ExpandableDetails; + +}; + + +class SAssetMigrationWidget : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SAssetMigrationWidget) {} + + SLATE_END_ARGS() + TSharedPtr MigrateAssetsCheckBox; + void Construct(const FArguments& InArgs); + +private : + TSharedPtr ExpandableSection; + TSharedPtr ExpandableDetails; +}; + + +class SProjectMigrationWidget : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SProjectMigrationWidget) {} + SLATE_END_ARGS() + + TSharedPtr AutoMigrateCheckbox; + TSharedPtr GeneratedSoundBanksFolderPickerWidget; + + void Construct(const FArguments& InArgs); + + EVisibility GetPathVisibility() const; + +private : + TSharedPtr ExpandableSection; + TSharedPtr ExpandableDetails; +}; + +class SMigrationWidget : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SMigrationWidget) {} + SLATE_ARGUMENT(TSharedPtr, Dialog) + SLATE_ARGUMENT(bool, ShowBankTransfer) + SLATE_ARGUMENT(bool, ShowDeprecatedAssetCleanup) + SLATE_ARGUMENT(bool, ShowAssetMigration) + SLATE_ARGUMENT(bool, ShowProjectMigration) + SLATE_ARGUMENT(int, NumDeprecatedAssets) + + SLATE_END_ARGS() + + TSharedPtr Dialog; + TSharedPtr BankTransferWidget; + TSharedPtr DeprecatedAssetCleanupWidget; + TSharedPtr AssetMigrationWidget; + TSharedPtr ProjectMigrationWidget; + + void Construct(const FArguments& InArgs); + FReply OnContinueClicked(); + FReply OnCancelClicked(); + EVisibility GetBankTransferWidgetVisibility() const; + EVisibility GetMediaCleanupWidgetVisibility() const; + EVisibility GetAssetMigrationWidgetVisibility() const; + EVisibility GetProjectMigrationWidgetVisibility() const; + + bool CanClickContinue() const; + FText GetContinueToolTip() const; + bool bCancel = false; + +private: + bool bShowBankTransfer; + bool bShowMediaCleanup; + bool bShowAssetMigration; + bool bShowProjectMigration; +}; + +class SBankMigrationFailureWidget : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SBankMigrationFailureWidget) {} + SLATE_ARGUMENT(TSharedPtr, Dialog) + SLATE_ARGUMENT(FText, ErrorMessage); + SLATE_END_ARGS() + + TSharedPtr Dialog; + void Construct(const FArguments& InArgs); + + FReply OnCancelClicked(); + FReply OnIgnoreClicked(); + + bool bCancel = false; +}; + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkSoundBankGenerationManager.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkSoundBankGenerationManager.h new file mode 100644 index 0000000..5bb096b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkSoundBankGenerationManager.h @@ -0,0 +1,94 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#include "Containers/UnrealString.h" +#include "Containers/Array.h" +#include "Dom/JsonObject.h" +#include "HAL/ThreadSafeBool.h" +#include "Widgets/Notifications/SNotificationList.h" +#include "AssetManagement/WwiseProjectInfo.h" +#include "AkUnrealHelper.h" + +DECLARE_STATS_GROUP(TEXT("AkSoundBankGeneration"), STATGROUP_AkSoundBankGenerationSource, STATCAT_Wwise); + + +class AkSoundBankGenerationManager : public TSharedFromThis +{ + +public: + enum ESoundBankGenerationMode + { + WwiseConsole = 0, + Commandlet, + WAAPI + }; + + struct FInitParameters + { + TArray Platforms; + TArray Languages; + bool SkipLanguages = false; + ESoundBankGenerationMode GenerationMode = WwiseConsole; + }; + + + AkSoundBankGenerationManager(const FInitParameters& InitParameters); + ~AkSoundBankGenerationManager(); + + void Init(); + void DoGeneration(); + + void SetOverrideWwiseConsolePath(const FString& value) { OverrideWwiseConsolePath = value; } + + +private: + void CreateNotificationItem(); + void Notify(const FString& key, const FString& message, const FString& AudioCuePath, bool bSuccess); + void NotifyGenerationFailed(); + void NotifyGenerationSucceeded(); + void NotifyProfilingInProgress(); + void NotifyAuthoringUnavailable(); + + void WrapUpGeneration(const bool bSuccess, const FString& BuilderName); + void SetIsBuilding(bool bIsBuilding); + + TSharedPtr NotificationItem; + uint64 StartTime; + FInitParameters InitParameters; + IPlatformFile* PlatformFile = nullptr; + + //WwiseConsole generation + bool WwiseConsoleGenerate(); + bool RunWwiseConsole(const FString& WwiseConsoleCommand, const FString& WwiseConsoleArguments); + FString OverrideWwiseConsolePath; + + //WAAPI generation +#if AK_SUPPORT_WAAPI + bool WAAPIGenerate(); + bool SubscribeToGenerationDone(); + void CleanupWaapiSubscriptions(); + void OnSoundBankGenerationDone(uint64_t Id, TSharedPtr ResponseJson); +#endif + + uint64 GenerationDoneSubscriptionId = 0; + FDelegateHandle ConnectionLostHandle; + FEvent* WaitForGenerationDoneEvent = nullptr; + FThreadSafeBool WaapiGenerationSuccess = true; + FThreadSafeBool bIsBuildingData = false; + +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkSoundbankGenerationManager.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkSoundbankGenerationManager.cpp new file mode 100644 index 0000000..920d16a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/AkSoundbankGenerationManager.cpp @@ -0,0 +1,448 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkSoundBankGenerationManager.h" +#include "AkAudioBankGenerationHelpers.h" +#include "AkAudioDevice.h" +#include "AkAudioStyle.h" +#include "AkWaapiClient.h" +#include "AkWaapiUtils.h" +#include "IAudiokineticTools.h" + +#include "Async/Async.h" +#include "Framework/Docking/TabManager.h" +#include "Framework/Notifications/NotificationManager.h" +#if UE_5_0_OR_LATER +#include "HAL/PlatformFileManager.h" +#else +#include "HAL/PlatformFilemanager.h" +#endif +#include "Misc/ScopeExit.h" +#include "Internationalization/Text.h" + +#define LOCTEXT_NAMESPACE "AkAudio" + +DECLARE_CYCLE_STAT(TEXT("AkSoundData - Waapi Call"), STAT_WaapiCall, STATGROUP_AkSoundBankGenerationSource); + + +AkSoundBankGenerationManager::AkSoundBankGenerationManager(const FInitParameters& InitParameters) + : InitParameters(InitParameters) +{ + PlatformFile = &FPlatformFileManager::Get().GetPlatformFile(); +} + +AkSoundBankGenerationManager::~AkSoundBankGenerationManager() +{ + +} + +void AkSoundBankGenerationManager::SetIsBuilding(bool bIsBuilding) +{ + bIsBuildingData = bIsBuilding; +} + +void AkSoundBankGenerationManager::Init() +{ + +} + +void AkSoundBankGenerationManager::DoGeneration() +{ + if (bIsBuildingData) + { + Notify(TEXT("SoundBankGenerationAborted"), + TEXT("Wwise SoundBank generation aborted, there is already a generation task in progress!"), + TEXT("/Engine/EditorSounds/Notifications/CompileFailed_Cue.CompileFailed_Cue"), + true); + return; + } + + SetIsBuilding(true); + StartTime = FPlatformTime::Cycles64(); + + if (InitParameters.GenerationMode != ESoundBankGenerationMode::Commandlet) + { + CreateNotificationItem(); + } + + bool bGenerationSuccess = false; + switch (InitParameters.GenerationMode) + { +#if AK_SUPPORT_WAAPI + case ESoundBankGenerationMode::WAAPI: + bGenerationSuccess = WAAPIGenerate(); + break; +#endif + case ESoundBankGenerationMode::WwiseConsole: + case ESoundBankGenerationMode::Commandlet: + default: + bGenerationSuccess = WwiseConsoleGenerate(); + break; + + } + WrapUpGeneration(bGenerationSuccess, TEXT("WwiseConsole")); +} + +void AkSoundBankGenerationManager::WrapUpGeneration(const bool bSuccess, const FString& BuilderName) +{ + SetIsBuilding(false); + + FString SuccessMessage; + + if(!bSuccess) + { + SuccessMessage = BuilderName + TEXT(" Sound Data Builder task failed"); + NotifyGenerationFailed(); + } + else + { + SuccessMessage = BuilderName + TEXT(" Sound Data Builder task was successful"); + NotifyGenerationSucceeded(); + } + + auto EndTime = FPlatformTime::Cycles64(); + UE_LOG(LogAudiokineticTools, Display, TEXT("%s and took %f seconds."), *SuccessMessage, + FPlatformTime::ToSeconds64(EndTime - StartTime)); +} + +void AkSoundBankGenerationManager::CreateNotificationItem() +{ + if (InitParameters.GenerationMode != ESoundBankGenerationMode::Commandlet) + { + AsyncTask(ENamedThreads::Type::GameThread, [sharedThis = SharedThis(this)] + { + FNotificationInfo Info(LOCTEXT("GeneratingSoundBanks", "Generating Wwise SoundBanks...")); + + Info.Image = FAkAudioStyle::GetBrush(TEXT("AudiokineticTools.AkBrowserTabIcon")); + Info.bFireAndForget = false; + Info.FadeOutDuration = 0.0f; + Info.ExpireDuration = 0.0f; + #if UE_4_26_OR_LATER + Info.Hyperlink = FSimpleDelegate::CreateLambda([]() { FGlobalTabmanager::Get()->TryInvokeTab(FName("OutputLog")); }); + #else + Info.Hyperlink = FSimpleDelegate::CreateLambda([]() { FGlobalTabmanager::Get()->InvokeTab(FName("OutputLog")); }); + #endif + Info.HyperlinkText = LOCTEXT("ShowOutputLogHyperlink", "Show Output Log"); + sharedThis->NotificationItem = FSlateNotificationManager::Get().AddNotification(Info); + }); + } +} + +void AkSoundBankGenerationManager::Notify(const FString& key, const FString& message, const FString& AudioCuePath, bool bSuccess) +{ + if (InitParameters.GenerationMode != ESoundBankGenerationMode::Commandlet) + { + AsyncTask(ENamedThreads::Type::GameThread, [sharedThis = SharedThis(this), key, message, AudioCuePath, bSuccess] + { + if (sharedThis->NotificationItem) + { + const FTextId TextId(TEXT(LOCTEXT_NAMESPACE), key); + FText LocText = FText::ChangeKey(TextId.GetNamespace(), TextId.GetKey(), FText::FromString(message)); + sharedThis->NotificationItem->SetText(LocText); + + sharedThis->NotificationItem->SetCompletionState( + bSuccess ? SNotificationItem::CS_Success : SNotificationItem::CS_Fail); + sharedThis->NotificationItem->SetExpireDuration(3.5f); + sharedThis->NotificationItem->SetFadeOutDuration(0.5f); + sharedThis->NotificationItem->ExpireAndFadeout(); + } + + GEditor->PlayEditorSound(AudioCuePath); + }); + } +} + +void AkSoundBankGenerationManager::NotifyGenerationSucceeded() +{ + Notify(TEXT("SoundBankGenerationCompleted"), + TEXT("Wwise SoundBank generation completed!"), + TEXT("/Engine/EditorSounds/Notifications/CompileSuccess_Cue.CompileSuccess_Cue"), + true); +} + +void AkSoundBankGenerationManager::NotifyGenerationFailed() +{ + Notify(TEXT("SoundBankGenerationFailed"), + TEXT("Generating Wwise SoundBanks failed!"), + TEXT("/Engine/EditorSounds/Notifications/CompileFailed_Cue.CompileFailed_Cue"), + false); +} + +void AkSoundBankGenerationManager::NotifyProfilingInProgress() +{ + Notify(TEXT("SoundBankGenerationProfiling"), + TEXT("Cannot generate SoundBanks while Authoring is profiling."), + TEXT("/Engine/EditorSounds/Notifications/CompileFailed_Cue.CompileFailed_Cue"), + false); +} + +void AkSoundBankGenerationManager::NotifyAuthoringUnavailable() +{ + Notify( + TEXT("SoundBankGenerationAuthoringLocked"), + TEXT("Cannot generate SoundBanks while Authoring is in its current state."), + TEXT("/Engine/EditorSounds/Notifications/CompileFailed_Cue.CompileFailed_Cue"), + false); +} + +bool AkSoundBankGenerationManager::WwiseConsoleGenerate() +{ + FString WwiseConsoleCommand = OverrideWwiseConsolePath.IsEmpty() ? AkAudioBankGenerationHelper::GetWwiseConsoleApplicationPath() : OverrideWwiseConsolePath; + + FString WwiseConsoleArguments; +#if PLATFORM_MAC + WwiseConsoleArguments = WwiseConsoleCommand + TEXT(" "); + WwiseConsoleCommand = TEXT("/bin/sh"); +#endif + WwiseConsoleArguments += FString::Printf(TEXT("generate-soundbank \"%s\" --use-stable-guid "), + *PlatformFile->ConvertToAbsolutePathForExternalAppForWrite(*AkUnrealHelper::GetWwiseProjectPath())); + + auto GeneratedSoundBanksPath = AkUnrealHelper::GetSoundBankDirectory(); + + if (InitParameters.Platforms.Num() > 0) + { + WwiseConsoleArguments += FString::Printf(TEXT(" --platform")); + for (auto& Platform : InitParameters.Platforms) + { + WwiseConsoleArguments += FString::Printf(TEXT(" \"%s\""), *Platform); + } + } + + if (InitParameters.SkipLanguages) + { + WwiseConsoleArguments += TEXT(" --skip-languages"); + } + else + { + if (InitParameters.Languages.Num() > 0) + { + WwiseConsoleArguments += FString::Printf(TEXT(" --language")); + for (auto& Language : InitParameters.Languages) + { + WwiseConsoleArguments += FString::Printf(TEXT(" \"%s\""), *Language); + } + } + } + + return RunWwiseConsole(WwiseConsoleCommand, WwiseConsoleArguments); +} + +bool AkSoundBankGenerationManager::RunWwiseConsole(const FString& WwiseConsoleCommand,const FString& WwiseConsoleArguments) +{ + UE_LOG(LogAudiokineticTools, Display, TEXT("Running WwiseConsole command : %s %s"), *WwiseConsoleCommand, *WwiseConsoleArguments); + bool bGenerationSuccess = true; + + // Create a pipe for the child process's STDOUT. + int32 ReturnCode = 0; + void* WritePipe = nullptr; + void* ReadPipe = nullptr; + FPlatformProcess::CreatePipe(ReadPipe, WritePipe); + + ON_SCOPE_EXIT{ + FPlatformProcess::ClosePipe(ReadPipe, WritePipe); + }; + + FProcHandle ProcHandle = FPlatformProcess::CreateProc(*WwiseConsoleCommand, *WwiseConsoleArguments, true, true, true, nullptr, 0, nullptr, WritePipe); + if (ProcHandle.IsValid()) + { + FString NewLine; + FPlatformProcess::Sleep(0.1f); + // Wait for it to finish and get return code + while (FPlatformProcess::IsProcRunning(ProcHandle)) + { + NewLine = FPlatformProcess::ReadPipe(ReadPipe); + if (NewLine.Len() > 0) + { + UE_LOG(LogAudiokineticTools, Display, TEXT("%s"), *NewLine); + NewLine.Empty(); + } + FPlatformProcess::Sleep(0.25f); + } + + NewLine = FPlatformProcess::ReadPipe(ReadPipe); + if (NewLine.Len() > 0) + { + UE_LOG(LogAudiokineticTools, Display, TEXT("%s"), *NewLine); + } + + FPlatformProcess::GetProcReturnCode(ProcHandle, &ReturnCode); + + switch (ReturnCode) + { + case 0: + UE_LOG(LogAudiokineticTools, Display, TEXT("WwiseConsole successfully completed.")); + break; + case 2: + UE_LOG(LogAudiokineticTools, Warning, TEXT("WwiseConsole completed with warnings :\n %s %s"), *WwiseConsoleCommand, *WwiseConsoleArguments); + break; + default: + UE_LOG(LogAudiokineticTools, Error, TEXT("WwiseConsole failed with error %d :\n %s %s"), ReturnCode, *WwiseConsoleCommand, *WwiseConsoleArguments); + bGenerationSuccess = false; + break; + } + } + else + { + bGenerationSuccess = false; + ReturnCode = -1; + // Most chances are the path to the .exe or the project were not set properly in GEditorIni file. + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to run WwiseConsole:\n %s %s"), *WwiseConsoleCommand, *WwiseConsoleArguments); + } + + return bGenerationSuccess; +} + +#if AK_SUPPORT_WAAPI +bool AkSoundBankGenerationManager::WAAPIGenerate() +{ + if (!SubscribeToGenerationDone()) + { + return false; + } + + TSharedRef args = MakeShared(); + TSharedRef options = MakeShared(); + TSharedPtr result; + + if (FAkWaapiClient::Get()->Call(ak::wwise::core::remote::getConnectionStatus, args, options, result, -1)) + { + bool isConnected = false; + if (result->TryGetBoolField(WwiseWaapiHelper::IS_CONNECTED, isConnected) && isConnected) + { + NotifyAuthoringUnavailable(); + return false; + } + } + + TArray> platformJsonArray; + for (auto& platform : InitParameters.Platforms) + { + platformJsonArray.Add(MakeShared(platform)); + } + args->SetArrayField(WwiseWaapiHelper::PLATFORMS, platformJsonArray); + + if (InitParameters.Languages.Num() > 0) + { + TArray> LanguageJsonArray; + for (auto& Language : InitParameters.Languages) + { + LanguageJsonArray.Add(MakeShared(Language)); + } + args->SetArrayField(WwiseWaapiHelper::LANGUAGES, LanguageJsonArray); + } + + args->SetBoolField(WwiseWaapiHelper::SKIP_LANGUAGES, InitParameters.SkipLanguages); + args->SetBoolField(WwiseWaapiHelper::WRITE_TO_DISK, true); + + // do we always want to rebuild init bank now? + args->SetBoolField(WwiseWaapiHelper::REBUILD_INIT_BANK, true); + + bool WaapiCallSuccess = false; + { + SCOPE_CYCLE_COUNTER(STAT_WaapiCall); + WaapiCallSuccess = FAkWaapiClient::Get()->Call(ak::wwise::core::soundbank::generate, args, options, result, -1); + + + if (!WaapiCallSuccess) + { + auto Message = result->GetStringField(WwiseWaapiHelper::MESSSAGE); + + UE_LOG(LogAkAudio, Error, TEXT("WAAPI Sound Data generation failed: %s"), *Message); + } + } + + if (WaapiCallSuccess) + { + WaitForGenerationDoneEvent->Wait(); + } + + CleanupWaapiSubscriptions(); + return WaapiGenerationSuccess; + +} + +bool AkSoundBankGenerationManager::SubscribeToGenerationDone() +{ + TSharedPtr Result; + TSharedRef DoneOptions = MakeShared(); + auto SoundBankGenerationDoneCallback = WampEventCallback::CreateRaw(this, &AkSoundBankGenerationManager::OnSoundBankGenerationDone); + FAkWaapiClient::Get()->Subscribe(ak::wwise::core::soundbank::generationDone, DoneOptions, SoundBankGenerationDoneCallback, GenerationDoneSubscriptionId, Result); + WaitForGenerationDoneEvent = FGenericPlatformProcess::GetSynchEventFromPool(); + + return GenerationDoneSubscriptionId != 0; + +} + +void AkSoundBankGenerationManager::CleanupWaapiSubscriptions() +{ + TSharedPtr result; + FAkWaapiClient::Get()->Unsubscribe(GenerationDoneSubscriptionId, result); + FGenericPlatformProcess::ReturnSynchEventToPool(WaitForGenerationDoneEvent); +} + +void AkSoundBankGenerationManager::OnSoundBankGenerationDone(uint64_t id, TSharedPtr responseJson) +{ + const TArray>* logs = nullptr; + if (responseJson->TryGetArrayField(TEXT("logs"), logs)) + { + for (auto& entry : *logs) + { + const TSharedPtr* jsonEntry = nullptr; + if (entry->TryGetObject(jsonEntry)) + { + const auto severity = jsonEntry->Get()->GetStringField(TEXT("severity")); + const auto message = jsonEntry->Get()->GetStringField(WwiseWaapiHelper::MESSSAGE); + + FString platform = ""; + + const TSharedPtr* jsonPlatform = nullptr; + if (jsonEntry->Get()->TryGetObjectField(TEXT("platform"), jsonPlatform)) + { + jsonPlatform->Get()->TryGetStringField(WwiseWaapiHelper::NAME, platform); + } + + if (severity == TEXT("Message")) + { + UE_LOG(LogAudiokineticTools, Display, TEXT("%s: %s"), *platform, *message); + } + else if (severity == TEXT("Warning")) + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("%s: %s"), *platform, *message); + } + else if (severity == TEXT("Error") || severity == TEXT("Fatal Error")) + { + WaapiGenerationSuccess = false; + UE_LOG(LogAudiokineticTools, Error, TEXT("%s: %s"), *platform, *message); + } + } + } + } + else + { + FString outError; + if (responseJson->TryGetStringField(TEXT("error"), outError)) + { + WaapiGenerationSuccess = false; + + UE_LOG(LogAudiokineticTools, Error, TEXT("%s"), *outError); + } + } + + WaitForGenerationDoneEvent->Trigger(); +} +#endif +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/GenerateSoundBanksCommandlet.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/GenerateSoundBanksCommandlet.cpp new file mode 100644 index 0000000..0618ca5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/GenerateSoundBanksCommandlet.cpp @@ -0,0 +1,135 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AssetManagement/GenerateSoundBanksCommandlet.h" + +#if WITH_EDITOR +#include "AkAudioBankGenerationHelpers.h" +#include "AkSoundBankGenerationManager.h" +#include "AkSettings.h" +#include "AkSettingsPerUser.h" +#include "Containers/Ticker.h" +#include "Framework/Application/SlateApplication.h" +#include "IAudiokineticTools.h" + +#include "ISourceControlModule.h" +#include "SourceControlHelpers.h" +#include "AssetManagement/WwiseProjectInfo.h" +#include "ShaderCompiler.h" + +#define LOCTEXT_NAMESPACE "AkAudio" +#endif + +static constexpr auto HelpSwitch = TEXT("help"); +static constexpr auto LanguagesSwitch = TEXT("languages"); +static constexpr auto PlatformsSwitch = TEXT("platforms"); +static constexpr auto WwiseConsolePathSwitch = TEXT("wwiseConsolePath"); + +UGenerateSoundBanksCommandlet::UGenerateSoundBanksCommandlet() +{ + IsClient = false; + IsEditor = true; + IsServer = false; + LogToConsole = true; + + HelpDescription = TEXT("Commandlet allowing to generate Wwise SoundBanks."); + + HelpParamNames.Add(PlatformsSwitch); + HelpParamDescriptions.Add(TEXT("(Optional) Comma separated list of platforms for which SoundBanks will be generated, as specified in the Wwise project. If not specified, SoundBanks will be generated for all platforms.")); + + HelpParamNames.Add(LanguagesSwitch); + HelpParamDescriptions.Add(TEXT("(Optional) Comma separated list of languages for which SoundBanks will be generated, as specified in the Wwise project. If not specified, SoundBanks will be generated for all platforms.")); + + HelpParamNames.Add(WwiseConsolePathSwitch); + HelpParamDescriptions.Add(TEXT("(Optional) Full path to the Wwise command-line application to use to generate the SoundBanks. If not specified, the path found in the Wwise settings will be used.")); + + HelpParamNames.Add(HelpSwitch); + HelpParamDescriptions.Add(TEXT("(Optional) Print this help message. This will quit the commandlet immediately.")); + + HelpUsage = TEXT(" -run=GenerateSoundBanks [-platforms=listOfPlatforms] [-languages=listOfLanguages] [-rebuild]"); + HelpWebLink = TEXT("https://www.audiokinetic.com/library/edge/?source=UE4&id=using_features_generatecommandlet.html"); +} + +void UGenerateSoundBanksCommandlet::PrintHelp() const +{ + UE_LOG(LogAudiokineticTools, Display, TEXT("%s"), *HelpDescription); + UE_LOG(LogAudiokineticTools, Display, TEXT("Usage: %s"), *HelpUsage); + UE_LOG(LogAudiokineticTools, Display, TEXT("Parameters:")); + for (int32 i = 0; i < HelpParamNames.Num(); ++i) + { + UE_LOG(LogAudiokineticTools, Display, TEXT("\t- %s: %s"), *HelpParamNames[i], *HelpParamDescriptions[i]); + } + UE_LOG(LogAudiokineticTools, Display, TEXT("For more information, see %s"), *HelpWebLink); +} + +int32 UGenerateSoundBanksCommandlet::Main(const FString& Params) +{ + int32 ReturnCode = 0; +#if WITH_EDITOR + AkSoundBankGenerationManager::FInitParameters InitParameters; + + TArray Tokens; + TArray Switches; + TMap ParamVals; + + ParseCommandLine(*Params, Tokens, Switches, ParamVals); + + if (Switches.Contains(HelpSwitch)) + { + PrintHelp(); + return 0; + } + + WwiseProjectInfo wwiseProjectInfo; + wwiseProjectInfo.Parse(); + const FString* PlatformValue = ParamVals.Find(PlatformsSwitch); + if (PlatformValue) + { + TArray PlatformNames; + PlatformValue->ParseIntoArray(PlatformNames, TEXT(",")); + InitParameters.Platforms.Append(PlatformNames); + } + + const FString* LanguageValue = ParamVals.Find(LanguagesSwitch); + if (LanguageValue) + { + TArray LanguageNames; + LanguageValue->ParseIntoArray(LanguageNames, TEXT(",")); + InitParameters.Languages.Append(LanguageNames); + } + + InitParameters.GenerationMode = AkSoundBankGenerationManager::ESoundBankGenerationMode::Commandlet; + + auto AkSettings = GetMutableDefault(); + auto AkSettingsPerUser = GetMutableDefault(); + + auto SoundBankGenerationManager = MakeShared(InitParameters); + + if (const FString* overrideWwiseConsolePath = ParamVals.Find(WwiseConsolePathSwitch)) + { + SoundBankGenerationManager->SetOverrideWwiseConsolePath(*overrideWwiseConsolePath); + } + + SoundBankGenerationManager->Init(); + + SoundBankGenerationManager->DoGeneration(); +#endif + + return ReturnCode; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/GeneratedSoundBanksDirectoryWatcher.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/GeneratedSoundBanksDirectoryWatcher.cpp new file mode 100644 index 0000000..7b7a691 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/GeneratedSoundBanksDirectoryWatcher.cpp @@ -0,0 +1,414 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AssetManagement/GeneratedSoundBanksDirectoryWatcher.h" + +#include "AkAudioModule.h" +#include "AkAudioStyle.h" +#include "AkSettings.h" +#include "AkSettingsPerUser.h" +#include "AkUnrealHelper.h" +#include "IAudiokineticTools.h" +#include "DirectoryWatcherModule.h" +#include "Async/Async.h" +#include "Framework/Docking/TabManager.h" +#include "Framework/Notifications/NotificationManager.h" +#include "Wwise/WwiseProjectDatabase.h" +#include "Wwise/WwiseProjectDatabaseDelegates.h" +#include "Wwise/WwiseSoundEngineModule.h" +#include "Wwise/Metadata/WwiseMetadataProjectInfo.h" + +#define LOCTEXT_NAMESPACE "AkAudio" + + +bool GeneratedSoundBanksDirectoryWatcher::DoesWwiseProjectExist() +{ + return FPaths::FileExists(AkUnrealHelper::GetWwiseProjectPath()); +} + +void GeneratedSoundBanksDirectoryWatcher::CheckIfCachePathChanged() +{ + auto* ProjectDatabase = FWwiseProjectDatabase::Get(); + if (UNLIKELY(!ProjectDatabase) || IWwiseProjectDatabaseModule::IsInACookingCommandlet()) + { + return; + } + + const FWwiseDataStructureScopeLock DataStructure(*ProjectDatabase); + const FWwiseRefPlatform Platform = DataStructure.GetPlatform(ProjectDatabase->GetCurrentPlatform()); + if (auto* ProjectInfo = Platform.ProjectInfo.GetProjectInfo()) + { + const FString SourceCachePath = AkUnrealHelper::GetSoundBankDirectory() / ProjectInfo->CacheRoot.ToString(); + if (SourceCachePath != CachePath || !CacheChangedHandle.IsValid()) + { + UE_LOG(LogAudiokineticTools, Verbose, TEXT("GeneratedSoundBanksDirectoryWatcher::CheckIfCachePathChanged: Cache path changed, restarting cache watcher.")); + + StopCacheWatcher(); + StartCacheWatcher(SourceCachePath); + } + } +} + +void GeneratedSoundBanksDirectoryWatcher::Initialize() +{ + ProjectParsedHandle = FWwiseProjectDatabaseDelegates::Get().GetOnDatabaseUpdateCompletedDelegate().AddRaw(this, &GeneratedSoundBanksDirectoryWatcher::CheckIfCachePathChanged); + if (UAkSettings* AkSettings = GetMutableDefault()) + { + // When GeneratedSoundbanks folder changes we need to reset the watcher + if (SettingsChangedHandle.IsValid()) + { + AkSettings->OnGeneratedSoundBanksPathChanged.Remove(SettingsChangedHandle); + SettingsChangedHandle.Reset(); + } + + SettingsChangedHandle = AkSettings->OnGeneratedSoundBanksPathChanged.AddRaw(this, &GeneratedSoundBanksDirectoryWatcher::RestartWatchers); + } + + if (UAkSettingsPerUser* UserSettings = GetMutableDefault()) + { + // When GeneratedSoundbanks Override folder changes we need to reset the watcher + if (UserSettingsChangedHandle.IsValid()) + { + UserSettings->OnGeneratedSoundBanksPathChanged.Remove(UserSettingsChangedHandle); + UserSettingsChangedHandle.Reset(); + } + + UserSettingsChangedHandle = UserSettings->OnGeneratedSoundBanksPathChanged.AddRaw(this, &GeneratedSoundBanksDirectoryWatcher::RestartWatchers); + } + + StartWatchers(); +} + +void GeneratedSoundBanksDirectoryWatcher::StartWatchers() +{ + if (IWwiseProjectDatabaseModule::IsInACookingCommandlet()) + { + return; + } + + //Start GeneratedSoundBanksWatcher to watch files which can be updated by source control (or direct manipulation) + StartSoundBanksWatcher(AkUnrealHelper::GetSoundBankDirectory()); + + // If there is a wwise project, we also watch the cache root file which notifies us when bank generation is done + if (DoesWwiseProjectExist()) + { + auto* ProjectDatabase = FWwiseProjectDatabase::Get(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("GeneratedSoundBanksDirectoryWatcher::StartWatchers: Could not get WwiseProjectDatabase. Wwise Cache watcher will not be initialized")); + return; + } + const FWwiseDataStructureScopeLock DataStructure(*ProjectDatabase); + const FWwiseRefPlatform Platform = DataStructure.GetPlatform(ProjectDatabase->GetCurrentPlatform()); + if (Platform.IsValid()) + { + if (auto* ProjectInfo = Platform.ProjectInfo.GetProjectInfo()) + { + const FString SourceCachePath = AkUnrealHelper::GetSoundBankDirectory() / ProjectInfo->CacheRoot.ToString(); + StartCacheWatcher(SourceCachePath); + } + } + else + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("GeneratedSoundBanksDirectoryWatcher::StartWatchers: Could not get Project Info for current platform from WwiseProjectDatabase. Wwise Cache watcher will not be initialized")); + } + } +} + +bool GeneratedSoundBanksDirectoryWatcher::StartCacheWatcher(const FString& InCachePath) +{ + if (CacheChangedHandle.IsValid()) + { + StopCacheWatcher(); + } + + if (!FPaths::DirectoryExists(InCachePath)) + { + UE_LOG(LogAudiokineticTools, Log, TEXT("GeneratedSoundBanksDirectoryWatcher::StartCacheWatcher: Cache directory to watch does not exist %s"), *InCachePath); + bCacheFolderExists = false; + return false; + } + + bCacheFolderExists = true; + CachePath = InCachePath; + UE_LOG(LogAudiokineticTools, Verbose, TEXT("GeneratedSoundBanksDirectoryWatcher::StartCacheWatcher: Starting cache watcher - %s."), *CachePath); + + auto& DirectoryWatcherModule = FModuleManager::LoadModuleChecked(TEXT("DirectoryWatcher")); + return DirectoryWatcherModule.Get()->RegisterDirectoryChangedCallback_Handle( + CachePath + , IDirectoryWatcher::FDirectoryChanged::CreateRaw(this, &GeneratedSoundBanksDirectoryWatcher::OnCacheChanged) + , CacheChangedHandle + , IDirectoryWatcher::WatchOptions::IgnoreChangesInSubtree + ); +} + +void GeneratedSoundBanksDirectoryWatcher::StartSoundBanksWatcher(const FString& GeneratedSoundBanksFolder) +{ + if (GeneratedSoundBanksHandle.IsValid()) + { + StopSoundBanksWatcher(); + } + + if (!FPaths::DirectoryExists(GeneratedSoundBanksFolder)) + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("GeneratedSoundBanksDirectoryWatcher::StartSoundBanksWatcher: Generated Soundbanks Folder '%s' to watch not found.\nMake sure the Generated SoundBanks Folder setting is correct and ensure that SoundBanks are generated. Press the 'Refresh' button in the Wwise Browser to restart the watcher."), *GeneratedSoundBanksFolder); + bGeneratedSoundBanksFolderExists = false; + return; + } + + bGeneratedSoundBanksFolderExists = true; + UE_LOG(LogAudiokineticTools, Verbose, TEXT("GeneratedSoundBanksDirectoryWatcher::StartSoundBanksWatcher: Starting Generated Soundbanks watcher - %s."), *GeneratedSoundBanksFolder); + SoundBankDirectory = GeneratedSoundBanksFolder; + auto& DirectoryWatcherModule = FModuleManager::LoadModuleChecked(TEXT("DirectoryWatcher")); + DirectoryWatcherModule.Get()->RegisterDirectoryChangedCallback_Handle( + GeneratedSoundBanksFolder + , IDirectoryWatcher::FDirectoryChanged::CreateRaw(this, &GeneratedSoundBanksDirectoryWatcher::OnGeneratedSoundBanksChanged) + , GeneratedSoundBanksHandle + , IDirectoryWatcher::WatchOptions::IncludeDirectoryChanges + ); +} + +void GeneratedSoundBanksDirectoryWatcher::OnCacheChanged(const TArray& ChangedFiles) +{ + for (FFileChangeData FileData : ChangedFiles) + { + const FString FileName = FPaths::GetBaseFilename(FileData.Filename); + if (FileName == TEXT("SoundBankInfoCache")) + { + if (bParseTimerRunning) + { + UpdateNotificationOnGenerationComplete(); + EndParseTimer(); + } + UE_LOG(LogAudiokineticTools, Verbose, TEXT("GeneratedSoundBanksDirectoryWatcher: SoundBankInfoCache updated.")); + OnSoundBankGenerationDone(); + break; + } + } + UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("GeneratedSoundBanksDirectoryWatcher: Modifications to files in Wwise project cache detected.")); +} + +void GeneratedSoundBanksDirectoryWatcher::OnGeneratedSoundBanksChanged(const TArray& ChangedFiles) +{ + ParseTimer = ParseDelaySeconds; + UE_LOG(LogAudiokineticTools, Verbose, TEXT("GeneratedSoundBanksDirectoryWatcher: %d files changed in the Generated Soundbanks folder."), ChangedFiles.Num()); + if (!PostEditorTickHandle.IsValid() && !bParseTimerRunning) + { + bParseTimerRunning = true; + PostEditorTickHandle = GEngine->OnPostEditorTick().AddRaw(this, &GeneratedSoundBanksDirectoryWatcher::TimerTick); + NotifyFilesChanged(); + } +} + +bool GeneratedSoundBanksDirectoryWatcher::ShouldRestartWatchers() +{ + const bool bCacheWatcherNeedsRestart = DoesWwiseProjectExist() && (!bCacheFolderExists || !CacheChangedHandle.IsValid()); + const bool bGeneratedSoundBanksWatcherNeedsRestart = !bGeneratedSoundBanksFolderExists || !GeneratedSoundBanksHandle.IsValid(); + return bCacheWatcherNeedsRestart || bGeneratedSoundBanksWatcherNeedsRestart; +} + +void GeneratedSoundBanksDirectoryWatcher::TimerTick(float DeltaSeconds) +{ + if (ParseTimer < 0 && bParseTimerRunning) + { + UE_LOG(LogAudiokineticTools, Verbose, TEXT("GeneratedSoundBanksDirectoryWatcher: No files have changed in the last %d seconds."), ParseDelaySeconds); + OnSoundBankGenerationDone(); + EndParseTimer(); + } + else if (!bParseTimerRunning) + { + EndParseTimer(); + } + else if (ParseTimer > 0) + { + ParseTimer -= DeltaSeconds; + } + UpdateNotification(); + +} + +void GeneratedSoundBanksDirectoryWatcher::EndParseTimer() +{ + bParseTimerRunning = false; + ParseTimer = 0; + GEngine->OnPostEditorTick().Remove(PostEditorTickHandle); + PostEditorTickHandle.Reset(); + HideNotification(); +} + +void GeneratedSoundBanksDirectoryWatcher::OnSoundBankGenerationDone() const +{ + UE_LOG(LogAudiokineticTools, Verbose, TEXT("GeneratedSoundBanksDirectoryWatcher: Soundbank generation done.")); + OnSoundBanksGenerated.Broadcast(); +} + +void GeneratedSoundBanksDirectoryWatcher::NotifyFilesChanged() +{ + if (!FApp::CanEverRender()) + { + return; + } + AsyncTask(ENamedThreads::Type::GameThread, [this] + { + + FText InfoString = LOCTEXT("GeneratedSoundbanksWatcherInfoString", "Changes in generated soundbanks detected. \nProject data will be re-parsed in {parseSeconds} seconds."); + FFormatNamedArguments NamedArguments; + NamedArguments.Add(TEXT("parseSeconds"), static_cast(this->ParseTimer)); + InfoString = FText::Format(InfoString, NamedArguments); + FNotificationInfo Info(InfoString); + + Info.Image = FAkAudioStyle::GetBrush(TEXT("AudiokineticTools.AkBrowserTabIcon")); + Info.bFireAndForget = false; + Info.FadeOutDuration = 0.5f; + Info.ExpireDuration = 0.0f; +#if UE_4_26_OR_LATER + Info.Hyperlink = FSimpleDelegate::CreateLambda([]() { FGlobalTabmanager::Get()->TryInvokeTab(FName("OutputLog")); }); +#else + Info.Hyperlink = FSimpleDelegate::CreateLambda([]() { FGlobalTabmanager::Get()->InvokeTab(FName("OutputLog")); }); +#endif + Info.HyperlinkText = LOCTEXT("ShowOutputLogHyperlink", "Show Output Log"); + this->NotificationItem = FSlateNotificationManager::Get().AddNotification(Info); + }); +} + +void GeneratedSoundBanksDirectoryWatcher::HideNotification() +{ + if (!FApp::CanEverRender()) + { + return; + } + AsyncTask(ENamedThreads::Type::GameThread, [this] + { + if (this->NotificationItem) + { + this->NotificationItem->Fadeout(); + } + }); +} + +void GeneratedSoundBanksDirectoryWatcher::UpdateNotificationOnGenerationComplete() const +{ + if (!FApp::CanEverRender()) + { + return; + } + AsyncTask(ENamedThreads::Type::GameThread, [this] + { + if (this->NotificationItem) + { + FText InfoString = LOCTEXT("GeneratedSoundbanksWatcherInfoString", "Detected that sound data generation is finished."); + this->NotificationItem->SetText(InfoString); + this->NotificationItem->SetFadeOutDuration(2.0f); + } + }); +} + +void GeneratedSoundBanksDirectoryWatcher::UpdateNotification() const +{ + if (!FApp::CanEverRender()) + { + return; + } + AsyncTask(ENamedThreads::Type::GameThread, [this] + { + if (this->NotificationItem) + { + FText InfoString = LOCTEXT("GeneratedSoundbanksWatcherInfoString", "Changes in generated soundbanks detected. \nProject data will be re-parsed in {parseSeconds} seconds."); + FFormatNamedArguments NamedArguments; + NamedArguments.Add(TEXT("parseSeconds"), static_cast(this->ParseTimer)); + InfoString = FText::Format(InfoString, NamedArguments); + this->NotificationItem->SetText(InfoString); + } + }); +} + +void GeneratedSoundBanksDirectoryWatcher::StopWatchers() +{ + StopCacheWatcher(); + StopSoundBanksWatcher(); +} + +void GeneratedSoundBanksDirectoryWatcher::StopSoundBanksWatcher() +{ + if (GeneratedSoundBanksHandle.IsValid()) + { + auto& DirectoryWatcherModule = FModuleManager::LoadModuleChecked(TEXT("DirectoryWatcher")); + DirectoryWatcherModule.Get()->UnregisterDirectoryChangedCallback_Handle(SoundBankDirectory, GeneratedSoundBanksHandle); + GeneratedSoundBanksHandle.Reset(); + } +} + +void GeneratedSoundBanksDirectoryWatcher::StopCacheWatcher() +{ + if (CacheChangedHandle.IsValid()) + { + auto& DirectoryWatcherModule = FModuleManager::LoadModuleChecked(TEXT("DirectoryWatcher")); + DirectoryWatcherModule.Get()->UnregisterDirectoryChangedCallback_Handle(CachePath, CacheChangedHandle); + CacheChangedHandle.Reset(); + } +} + +void GeneratedSoundBanksDirectoryWatcher::RestartWatchers() +{ + AsyncTask(ENamedThreads::Type::GameThread, [this] + { + StopWatchers(); + StartWatchers(); + }); +} + +void GeneratedSoundBanksDirectoryWatcher::ConditionalRestartWatchers() +{ + if(ShouldRestartWatchers()) + { + RestartWatchers(); + } +} + +void GeneratedSoundBanksDirectoryWatcher::Uninitialize(const bool bIsModuleShutdown) +{ + StopWatchers(); + + if (ProjectParsedHandle.IsValid()) + { + FWwiseProjectDatabaseDelegates::Get().GetOnDatabaseUpdateCompletedDelegate().Remove(ProjectParsedHandle); + ProjectParsedHandle.Reset(); + } + + //Can't access settings while module is being shutdown + if (!bIsModuleShutdown) + { + if (SettingsChangedHandle.IsValid()) + { + if (UAkSettings* AkSettings = GetMutableDefault()) + { + AkSettings->OnGeneratedSoundBanksPathChanged.Remove(SettingsChangedHandle); + } + SettingsChangedHandle.Reset(); + } + if (UserSettingsChangedHandle.IsValid()) + { + if (UAkSettingsPerUser* UserSettings = GetMutableDefault()) + { + UserSettings->OnGeneratedSoundBanksPathChanged.Remove(UserSettingsChangedHandle); + } + UserSettingsChangedHandle.Reset(); + } + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/StaticPluginWriter.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/StaticPluginWriter.cpp new file mode 100644 index 0000000..79aa35c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/StaticPluginWriter.cpp @@ -0,0 +1,218 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "StaticPluginWriter.h" + +#include "AkAudioBankGenerationHelpers.h" +#include "IAudiokineticTools.h" +#include "Platforms/AkPlatformInfo.h" +#include "Platforms/AkUEPlatform.h" + +#include "Containers/Map.h" +#include "Misc/FileHelper.h" + +namespace StaticPluginWriter_Helper +{ + // known packaged plug-in IDs + enum class PluginID + { + Invalid = 0x0, + + Ak3DAudioBedMixer = 0x00BE0003, + AkCompressor = 0x006C0003, + AkDelay = 0x006A0003, + AkExpander = 0x006D0003, + AkGain = 0x008B0003, + AkMatrixReverb = 0x00730003, + AkMeter = 0x00810003, + AkParametricEQ = 0x00690003, + AkPeakLimiter = 0x006E0003, + AkRoomVerb = 0x00760003, + SineGenerator = 0x00640002, + SinkAuxiliary = 0xB40007, // * + SinkCommunication = 0xB00007, // * + SinkControllerHeadphones = 0xB10007, // * + SinkControllerSpeaker = 0xB30007, // * + SinkDVRByPass = 0xAF0007, // * + SinkNoOutput = 0xB50007, // * + SinkSystem = 0xAE0007, // * + ToneGenerator = 0x00660002, + WwiseSilence = 0x00650002, + + AkAudioInput = 0xC80002, + AkConvolutionReverb = 0x7F0003, + AkFlanger = 0x7D0003, + AkGuitarDistortion = 0x7E0003, + AkHarmonizer = 0x8A0003, + AkMotionSink = 0x1FB0007, + AkMotionSource = 0x1990002, + AkMotionGeneratorSource = 0x1950002, + AkPitchShifter = 0x880003, + AkRecorder = 0x840003, + AkReflect = 0xAB0003, + AkSoundSeedGrain = 0xB70002, + AkSoundSeedWind = 0x770002, + AkSoundSeedWoosh = 0x780002, + AkStereoDelay = 0x870003, + AkSynthOne = 0x940002, + AkTimeStretch = 0x820003, + AkTremolo = 0x830003, + AuroHeadphone = 0x44C1073, + CrankcaseAudioREVModelPlayer = 0x1A01052, + iZHybridReverb = 0x21033, + iZTrashBoxModeler = 0x71033, + iZTrashDelay = 0x41033, + iZTrashDistortion = 0x31033, + iZTrashDynamics = 0x51033, + iZTrashFilters = 0x61033, + iZTrashMultibandDistortion = 0x91033, + McDSPFutzBox = 0x6E1003, + McDSPLimiter = 0x671003, + ResonanceAudio = 0x641103, + }; + + const TMap PluginIDToStaticLibraryName = + { + { PluginID::Ak3DAudioBedMixer, "Ak3DAudioBedMixer" }, + { PluginID::AkAudioInput, "AkAudioInputSource" }, + { PluginID::AkCompressor, "AkCompressorFX" }, + { PluginID::AkConvolutionReverb, "AkConvolutionReverbFX" }, + { PluginID::AkDelay, "AkDelayFX" }, + { PluginID::AkExpander, "AkExpanderFX" }, + { PluginID::AkFlanger, "AkFlangerFX" }, + { PluginID::AkGain, "AkGainFX" }, + { PluginID::AkGuitarDistortion, "AkGuitarDistortionFX" }, + { PluginID::AkHarmonizer, "AkHarmonizerFX" }, + { PluginID::AkMatrixReverb, "AkMatrixReverbFX" }, + { PluginID::AkMeter, "AkMeterFX" }, + { PluginID::AkMotionSink, "AkMotionSink" }, + { PluginID::AkMotionSource, "AkMotionSourceSource" }, + { PluginID::AkMotionGeneratorSource, "AkMotionGeneratorSource" }, + { PluginID::AkParametricEQ, "AkParametricEQFX" }, + { PluginID::AkPeakLimiter, "AkPeakLimiterFX" }, + { PluginID::AkPitchShifter, "AkPitchShifterFX" }, + { PluginID::AkRecorder, "AkRecorderFX" }, + { PluginID::AkReflect, "AkReflectFX" }, + { PluginID::AkRoomVerb, "AkRoomVerbFX" }, + { PluginID::WwiseSilence, "AkSilenceSource" }, + { PluginID::SineGenerator, "AkSineSource" }, + { PluginID::AkSoundSeedGrain, "AkSoundSeedGrainSource" }, + { PluginID::AkSoundSeedWind, "AkSoundSeedWindSource" }, + { PluginID::AkSoundSeedWoosh, "AkSoundSeedWooshSource" }, + { PluginID::AkStereoDelay, "AkStereoDelayFX" }, + { PluginID::AkSynthOne, "AkSynthOneSource" }, + { PluginID::ToneGenerator, "AkToneSource" }, + { PluginID::AkTimeStretch, "AkTimeStretchFX" }, + { PluginID::AkTremolo, "AkTremoloFX" }, + { PluginID::AuroHeadphone, "AuroHeadphoneFX" }, + { PluginID::CrankcaseAudioREVModelPlayer, "CrankcaseAudioREVModelPlayerFX" }, + { PluginID::iZHybridReverb, "iZHybridReverbFX" }, + { PluginID::iZTrashBoxModeler, "iZTrashBoxModelerFX" }, + { PluginID::iZTrashDelay, "iZTrashDelayFX" }, + { PluginID::iZTrashDistortion, "iZTrashDistortionFX" }, + { PluginID::iZTrashDynamics, "iZTrashDynamicsFX" }, + { PluginID::iZTrashFilters, "iZTrashFiltersFX" }, + { PluginID::iZTrashMultibandDistortion, "iZTrashMultibandDistortionFX" }, + { PluginID::McDSPFutzBox, "McDSPFutzBoxFX" }, + { PluginID::McDSPLimiter, "McDSPLimiterFX" }, + { PluginID::ResonanceAudio, "ResonanceAudioFX" }, + }; + + FString GetLibraryName(const FAkPluginInfo& PluginInfo) + { + auto pluginID = static_cast(PluginInfo.PluginID); + + if (pluginID != PluginID::Invalid) + { + if (auto StaticLibraryName = PluginIDToStaticLibraryName.FindRef(pluginID)) + { + return StaticLibraryName; + } + } + + return PluginInfo.DLL; + } + + TArray GetLibraryNames(const FString& Platform) + { + TArray result; + FWwiseProjectDatabase* ProjectDatabase = FWwiseProjectDatabase::Get(); + const FWwiseDataStructureScopeLock DataStructure(*ProjectDatabase); + + WwisePluginLibGlobalIdsMap PluginLibs = DataStructure.GetPluginLibs(); + for (const auto& PluginLib: PluginLibs) + { + FString PluginLibName = PluginLib.Value.PluginLibName().ToString(); + result.Add(PluginLibName); + } + + return result; + }; + + void ModifySourceCode(const TCHAR* FilePath, const FString& PlatformName, const TArray& Plugins) + { + constexpr auto AkFactoryHeaderFormatString = TEXT("#include "); + + constexpr auto StaticFactoryHeaderFileTemplate = TEXT("#if PLATFORM_{0}\n{1}\n#endif\n"); + + TArray pluginLines; + + for (auto& pluginName : Plugins) + { + //Don't include AkAudioInputSourceFactory.h in file because it is already linked + if (pluginName.Equals(TEXT("AkAudioInputSource"))) + { + continue; + } + pluginLines.Add(FString::Format(AkFactoryHeaderFormatString, { pluginName })); + } + + FString staticFactoryHeaderContent = FString::Format(StaticFactoryHeaderFileTemplate, { PlatformName.ToUpper(), FString::Join(pluginLines, TEXT("\n")) }); + + if (FFileHelper::SaveStringToFile(staticFactoryHeaderContent, FilePath)) + { + UE_LOG(LogAudiokineticTools, Display, TEXT("Modified <%s> for <%s> platform."), FilePath, *PlatformName); + } + else + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("Could not modify <%s> for <%s> platform."), FilePath, *PlatformName); + } + } +} + +namespace StaticPluginWriter +{ + void OutputPluginInformation(const FString& Platform) + { + auto* PlatformInfo = UAkPlatformInfo::GetAkPlatformInfo(Platform); + if (!PlatformInfo) + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("AkPlatformInfo class not found for <%s> platform."), *Platform); + return; + } + + if (PlatformInfo->bUsesStaticLibraries) + { + const auto PluginArray = StaticPluginWriter_Helper::GetLibraryNames( Platform); + + const FString AkPluginIncludeFileName = FString::Format(TEXT("Ak{0}Plugins.h"), { PlatformInfo->WwisePlatform }); + const FString AkPluginIncludeFilePath = FPaths::Combine(FAkPlatform::GetWwisePluginDirectory(), TEXT("Source"), TEXT("WwiseSoundEngine"), TEXT("Public"), TEXT("Generated"), AkPluginIncludeFileName); + + StaticPluginWriter_Helper::ModifySourceCode(*AkPluginIncludeFilePath, PlatformInfo->WwisePlatform, PluginArray); + } + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/StaticPluginWriter.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/StaticPluginWriter.h new file mode 100644 index 0000000..fbfc863 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AssetManagement/StaticPluginWriter.h @@ -0,0 +1,53 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + + +#include "StaticPluginWriter.generated.h" + +class FString; +USTRUCT() +struct FAkPluginInfo +{ + GENERATED_BODY() + +public: + FAkPluginInfo() = default; + + FAkPluginInfo(const FString& InName, uint32 InPluginID, const FString& InDLL) + : Name(InName) + , PluginID(InPluginID) + , DLL(InDLL) + { + } + + UPROPERTY(VisibleAnywhere, Category = "FAkPluginInfo") + FString Name; + + UPROPERTY(VisibleAnywhere, Category = "FAkPluginInfo") + uint32 PluginID = 0; + + UPROPERTY(VisibleAnywhere, Category = "FAkPluginInfo") + FString DLL; +}; + + +namespace StaticPluginWriter +{ + void OutputPluginInformation(const FString& Platform); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AudiokineticToolsModule.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AudiokineticToolsModule.cpp new file mode 100644 index 0000000..f5e3828 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AudiokineticToolsModule.cpp @@ -0,0 +1,957 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AudiokineticToolsModule.cpp +=============================================================================*/ +#include "AudiokineticToolsModule.h" + +#include "AudiokineticToolsPrivatePCH.h" +#include "AkAudioBankGenerationHelpers.h" +#include "AkAudioDevice.h" +#include "AkAudioStyle.h" +#include "AkComponent.h" +#include "AkEventAssetBroker.h" +#include "AkGeometryComponent.h" +#include "AkLateReverbComponent.h" +#include "AkRoomComponent.h" +#include "AkSettings.h" +#include "AkSettingsPerUser.h" +#include "AkSurfaceReflectorSetComponent.h" +#include "AkUnrealHelper.h" +#include "AssetManagement/AkAssetDatabase.h" +#include "AssetManagement/AkAssetMigrationManager.h" +#include "AssetManagement/AkGenerateSoundBanksTask.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "AssetToolsModule.h" +#include "ComponentAssetBroker.h" +#include "ContentBrowserModule.h" +#include "DetailsCustomization/AkGeometryComponentDetailsCustomization.h" +#include "DetailsCustomization/AkLateReverbComponentDetailsCustomization.h" +#include "DetailsCustomization/AkRoomComponentDetailsCustomization.h" +#include "DetailsCustomization/AkPortalComponentDetailsCustomization.h" +#include "DetailsCustomization/AkSurfaceReflectorSetDetailsCustomization.h" +#include "DetailsCustomization/AkSettingsDetailsCustomization.h" +#include "Editor/LevelEditor/Public/LevelEditor.h" +#include "Editor/UnrealEdEngine.h" +#include "Factories/ActorFactoryAkAmbientSound.h" +#include "Factories/AkAssetTypeActions.h" +#include "Framework/Application/SlateApplication.h" +#if UE_5_0_OR_LATER +#include "HAL/PlatformFileManager.h" +#else +#include "HAL/PlatformFilemanager.h" +#endif +#include "Interfaces/IProjectManager.h" +#include "Internationalization/Culture.h" +#include "Internationalization/Internationalization.h" +#include "ISequencerModule.h" +#include "ISettingsModule.h" +#include "ISettingsSection.h" +#include "IAudiokineticTools.h" +#include "Misc/MessageDialog.h" +#include "Modules/ModuleManager.h" +#include "MovieScene.h" +#include "Platforms/AkUEPlatform.h" +#include "ProjectDescriptor.h" +#include "PropertyEditorModule.h" +#include "Sequencer/MovieSceneAkAudioEventTrackEditor.h" +#include "Sequencer/MovieSceneAkAudioRTPCTrackEditor.h" +#include "Settings/ProjectPackagingSettings.h" +#include "SettingsEditor/Public/ISettingsEditorModule.h" +#include "AkUnrealEditorHelper.h" +#include "UnrealEd/Public/EditorBuildUtils.h" +#include "UnrealEdGlobals.h" +#include "UnrealEdMisc.h" +#include "Visualizer/AkAcousticPortalVisualizer.h" +#include "Visualizer/AkComponentVisualizer.h" +#include "Visualizer/AkSurfaceReflectorSetComponentVisualizer.h" +#include "WaapiPicker/WwiseTreeItem.h" +#include "Widgets/Docking/SDockTab.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SHyperlink.h" +#include "Widgets/Layout/SSpacer.h" +#include "WorkspaceMenuStructure.h" +#include "WorkspaceMenuStructureModule.h" +#include "AssetManagement/GeneratedSoundBanksDirectoryWatcher.h" +#include "AssetManagement/WwiseProjectInfo.h" +#include "WwiseProject/AcousticTextureParamLookup.h" +#include "Developer/ToolMenus/Public/ToolMenu.h" +#include "Developer/ToolMenus/Public/ToolMenus.h" +#include "Wwise/WwiseProjectDatabase.h" +#include "Wwise/WwiseProjectDatabaseDelegates.h" +#include "AkAudioModule.h" +#include "WwiseInitBankLoader/WwiseInitBankLoader.h" + +#define LOCTEXT_NAMESPACE "AkAudio" + +FAudiokineticToolsModule* FAudiokineticToolsModule::AudiokineticToolsModuleInstance = nullptr; + + +TSharedRef FAudiokineticToolsModule::CreateWwiseBrowserTab(const FSpawnTabArgs& SpawnTabArgs) +{ + const TSharedRef BrowserTab = + SNew(SDockTab) + .Label(LOCTEXT("AkAudioWwiseBrowserTabTitle", "Wwise Browser")) + .TabRole(ETabRole::NomadTab) + .ContentPadding(5) + [ + SNew(SWwiseBrowser) + ]; + return BrowserTab; +} + +void FAudiokineticToolsModule::RefreshWwiseProject() +{ + SoundBanksDirectoryWatcher.ConditionalRestartWatchers(); + if (auto* ProjectDatabase = FWwiseProjectDatabase::Get()) + { + ProjectDatabase->UpdateDataStructure(); + } +} + +void FAudiokineticToolsModule::OpenOnlineHelp() +{ + FPlatformProcess::LaunchFileInDefaultExternalApplication(TEXT("https://www.audiokinetic.com/library/?source=UE4&id=index.html")); +} + +void FAudiokineticToolsModule::ToggleVisualizeRoomsAndPortals() +{ + UAkSettings* AkSettings = GetMutableDefault(); + if (AkSettings != nullptr) + { + AkSettings->ToggleVisualizeRoomsAndPortals(); + } +} + +bool FAudiokineticToolsModule::IsVisualizeRoomsAndPortalsEnabled() +{ + const UAkSettings* AkSettings = GetDefault(); + if (AkSettings == nullptr) + return false; + return AkSettings->VisualizeRoomsAndPortals; +} + +ECheckBoxState FAudiokineticToolsModule::GetVisualizeRoomsAndPortalsCheckBoxState() +{ + return IsVisualizeRoomsAndPortalsEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +void FAudiokineticToolsModule::ToggleShowReverbInfo() +{ + UAkSettings* AkSettings = GetMutableDefault(); + if (AkSettings != nullptr) + { + AkSettings->ToggleShowReverbInfo(); + } +} + +bool FAudiokineticToolsModule::IsReverbInfoEnabled() +{ + const UAkSettings* AkSettings = GetDefault(); + if (AkSettings == nullptr) + return false; + return AkSettings->bShowReverbInfo; +} + +ECheckBoxState FAudiokineticToolsModule::GetReverbInfoCheckBoxState() +{ + return IsReverbInfoEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +void FAudiokineticToolsModule::CreateAkViewportCommands() +{ + // Extend the viewport menu and add the Audiokinetic commands + { + UToolMenu* ViewportMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelViewportToolBar.Options"); + FToolMenuSection& AkSection = ViewportMenu->AddSection("Audiokinetic", LOCTEXT("AkLabel", "Audiokinetic"), FToolMenuInsert("Audiokinetic", EToolMenuInsertType::Default)); + + ToggleVizRoomsPortalsAction.ExecuteAction.BindStatic(&FAudiokineticToolsModule::ToggleVisualizeRoomsAndPortals); + ToggleVizRoomsPortalsAction.GetActionCheckState.BindStatic(&FAudiokineticToolsModule::GetVisualizeRoomsAndPortalsCheckBoxState); + + AkSection.AddMenuEntry( + NAME_None, + LOCTEXT("ToggleVizRoomsAndPortals_Label", "Visualize Rooms And Portals"), + LOCTEXT("ToggleVizRoomsAndPortals_Tip", "Toggles the visualization of rooms and portals in the viewport. This requires 'realtime' to be enabled in the viewport."), + FSlateIcon(), + ToggleVizRoomsPortalsAction, + EUserInterfaceActionType::ToggleButton + ); + + ToggleReverbInfoAction.ExecuteAction.BindStatic(&FAudiokineticToolsModule::ToggleShowReverbInfo); + ToggleReverbInfoAction.GetActionCheckState.BindStatic(&FAudiokineticToolsModule::GetReverbInfoCheckBoxState); + + AkSection.AddMenuEntry( + NAME_None, + LOCTEXT("ToggleReverbInfo_Label", "Show Reverb Info"), + LOCTEXT("ToggleReverbInfo_Tip", "When enabled, information about AkReverbComponents will be displayed in viewports, above the component's UPrimitiveComponent parent. This requires 'realtime' to be enabled in the viewport."), + FSlateIcon(), + ToggleReverbInfoAction, + EUserInterfaceActionType::ToggleButton + ); + } +} + +void FAudiokineticToolsModule::RegisterWwiseMenus() +{ + // Extend the build menu to handle Audiokinetic-specific entries +#if UE_5_0_OR_LATER + { + UToolMenu* BuildMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Build"); + FToolMenuSection& WwiseBuildSection = BuildMenu->AddSection("AkBuild", LOCTEXT("AkBuildLabel", "Audiokinetic"), FToolMenuInsert("LevelEditorGeometry", EToolMenuInsertType::Default)); + + FUIAction GenerateSoundDataUIAction; + GenerateSoundDataUIAction.ExecuteAction.BindStatic(&AkAudioBankGenerationHelper::CreateGenerateSoundDataWindow, false); + WwiseBuildSection.AddMenuEntry( + NAME_None, + LOCTEXT("AkAudioBank_GenerateSoundBanks", "Generate SoundBanks..."), + LOCTEXT("AkAudioBank_GenerateSoundBanksTooltip", "Generates Wwise SoundBanks."), + FSlateIcon(), + GenerateSoundDataUIAction + ); + + FUIAction RefreshProjectUIAction; + RefreshProjectUIAction.ExecuteAction.BindRaw(this, &FAudiokineticToolsModule::RefreshWwiseProject); + WwiseBuildSection.AddMenuEntry( + NAME_None, + LOCTEXT("RefreshWwiseProject", "Refresh Project Database"), + LOCTEXT("RefreshWwiseProjectTooltip", "Reparse the the Wwise Project in GeneratedSoundBanks and reload Wwise assets."), + FSlateIcon(), + RefreshProjectUIAction + ); + } +#else + FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); + LevelViewportToolbarBuildMenuExtenderAk = FLevelEditorModule::FLevelEditorMenuExtender::CreateLambda([this](const TSharedRef CommandList) + { + TSharedPtr Extender = MakeShared(); + Extender->AddMenuExtension("LevelEditorGeometry", EExtensionHook::After, CommandList, FMenuExtensionDelegate::CreateLambda([this](FMenuBuilder& MenuBuilder) + { + MenuBuilder.BeginSection("Audiokinetic", LOCTEXT("Audiokinetic", "Audiokinetic")); + { + FUIAction GenerateSoundDataUIAction; + GenerateSoundDataUIAction.ExecuteAction.BindStatic(&AkAudioBankGenerationHelper::CreateGenerateSoundDataWindow, false); + MenuBuilder.AddMenuEntry( + LOCTEXT("AkAudioBank_GenerateSoundBanks", "Generate SoundBanks..."), + LOCTEXT("AkAudioBank_GenerateSoundBanksTooltip", "Generates Wwise SoundBanks."), + FSlateIcon(), + GenerateSoundDataUIAction + ); + + FUIAction RefreshProjectUIAction; + RefreshProjectUIAction.ExecuteAction.BindRaw(this, &FAudiokineticToolsModule::RefreshWwiseProject); + MenuBuilder.AddMenuEntry( + LOCTEXT("AkAudioBank_RefreshProject", "Refresh Project"), + LOCTEXT("AkAudioBank_RefreshProjectTooltip", "Refresh the Wwise Project"), + FSlateIcon(), + RefreshProjectUIAction + ); + } + MenuBuilder.EndSection(); + + })); + + return Extender.ToSharedRef(); + }); + LevelEditorModule.GetAllLevelEditorToolbarBuildMenuExtenders().Add(LevelViewportToolbarBuildMenuExtenderAk); + LevelViewportToolbarBuildMenuExtenderAkHandle = LevelEditorModule.GetAllLevelEditorToolbarBuildMenuExtenders().Last().GetHandle(); +#endif + + // Extend the Help menu to display a link to our documentation + { + UToolMenu* HelpMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Help"); + FToolMenuSection& WwiseHelpSection = HelpMenu->AddSection("AkHelp", LOCTEXT("AkHelpLabel", "Audiokinetic"), FToolMenuInsert("HelpBrowse", EToolMenuInsertType::Default)); + + WwiseHelpSection.AddEntry(FToolMenuEntry::InitMenuEntry( + NAME_None, + LOCTEXT("AkWwiseHelpEntry", "Wwise Help"), + LOCTEXT("AkWwiseHelpEntryToolTip", "Shows the online Wwise documentation."), + FSlateIcon(), + FUIAction(FExecuteAction::CreateRaw(this, &FAudiokineticToolsModule::OpenOnlineHelp)) + )); + } +} + +void FAudiokineticToolsModule::UpdateUnrealCultureToWwiseCultureMap(const WwiseProjectInfo& wwiseProjectInfo) +{ + + if (!wwiseProjectInfo.IsProjectInfoParsed()) + { + UE_LOG(LogAudiokineticTools, Verbose, TEXT("AudiokineticToolsModule::UpdateUnrealCultureToWwiseCultureMap: Wwise project not parsed. Unreal culture to Wwise culture map will not be updated.")); + return; + } + static constexpr auto InvariantCultureLCID = 0x007F; + + UAkSettings* AkSettings = GetMutableDefault(); + if (!AkSettings) + { + return; + } + + TMap wwiseToUnrealMap; + for (auto& entry : WwiseLanguageToUnrealCultureList) + { + wwiseToUnrealMap.Add(entry.WwiseLanguage, entry.UnrealCulture); + } + + TMap languageCountMap; + + for (auto& language : wwiseProjectInfo.GetSupportedLanguages()) + { + if (auto* foundUnrealCulture = wwiseToUnrealMap.Find(language.Name)) + { + auto culturePtr = FInternationalization::Get().GetCulture(*foundUnrealCulture); + + if (culturePtr && culturePtr->GetLCID() != InvariantCultureLCID) + { + int& langCount = languageCountMap.FindOrAdd(culturePtr->GetTwoLetterISOLanguageName()); + ++langCount; + } + } + } + + TSet foundCultures; + + bool modified = false; + for (auto& language : wwiseProjectInfo.GetSupportedLanguages()) + { + if (auto* foundUnrealCulture = wwiseToUnrealMap.Find(language.Name)) + { + auto culturePtr = FInternationalization::Get().GetCulture(*foundUnrealCulture); + + if (culturePtr && culturePtr->GetLCID() != InvariantCultureLCID) + { + int* langCount = languageCountMap.Find(culturePtr->GetTwoLetterISOLanguageName()); + + if (langCount && *langCount > 1) + { + auto newKey = *foundUnrealCulture; + if (!AkSettings->UnrealCultureToWwiseCulture.Contains(newKey)) + { + AkSettings->UnrealCultureToWwiseCulture.Add(newKey, language.Name); + modified = true; + } + + foundCultures.Add(newKey); + } + else + { + auto newKey = culturePtr->GetTwoLetterISOLanguageName(); + if (!AkSettings->UnrealCultureToWwiseCulture.Contains(newKey)) + { + AkSettings->UnrealCultureToWwiseCulture.Add(newKey, language.Name); + modified = true; + } + + foundCultures.Add(newKey); + } + } + } + else + { + for (auto& entry : AkSettings->UnrealCultureToWwiseCulture) + { + if (entry.Value == language.Name) + { + foundCultures.Add(entry.Key); + break; + } + } + } + } + + TSet keysToRemove; + for (auto& entry : AkSettings->UnrealCultureToWwiseCulture) + { + if (!foundCultures.Contains(entry.Key)) + { + keysToRemove.Add(entry.Key); + } + } + + for (auto& keyToRemove : keysToRemove) + { + AkSettings->UnrealCultureToWwiseCulture.Remove(keyToRemove); + modified = true; + } + + if (modified) + { + AkSettings->SaveConfig(); + } +} + +void FAudiokineticToolsModule::VerifyGeneratedSoundBanksPath(UAkSettings* AkSettings, UAkSettingsPerUser* AkSettingsPerUser) +{ + if (!AkSettings->GeneratedSoundBanksPathExists()) + { + if (!AkSettingsPerUser->SuppressGeneratedSoundBanksPathWarnings && FApp::CanEverRender()) + { + if (EAppReturnType::Yes == FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("SettingsNotSet", "GeneratedSoundBanks folder does not seem to be set. Would you like to open the settings window to set it?"))) + { + FModuleManager::LoadModuleChecked("Settings").ShowViewer(FName("Project"), FName("Wwise"), FName("Integration")); + } + } + else + { + UE_LOG(LogAudiokineticTools, Log, TEXT("GeneratedSoundBanks folder not found. The Wwise Browser will not be usable.")); + } + } + else + { + // First-time plugin migration: Project might be relative to Engine path. Fix-up the path to make it relative to the game. + const auto ProjectDir = FPaths::ProjectContentDir(); + FString FullGameDir = FPaths::ConvertRelativePathToFull(ProjectDir); + FString TempPath = FPaths::ConvertRelativePathToFull(FullGameDir, AkSettings->GeneratedSoundBanksFolder.Path); + if (!FPaths::DirectoryExists(TempPath)) + { + if (!AkSettingsPerUser->SuppressGeneratedSoundBanksPathWarnings && FApp::CanEverRender()) + { + TSharedPtr Dialog = SNew(SWindow) + .Title(LOCTEXT("ResetWwisePath", "Reset GeneratedSoundBanks Folder Path")) + .SupportsMaximize(false) + .SupportsMinimize(false) + .FocusWhenFirstShown(true) + .SizingRule(ESizingRule::Autosized); + + TSharedRef DialogContent = SNew(SVerticalBox) + + SVerticalBox::Slot() + .FillHeight(0.25f) + [ + SNew(SSpacer) + ] + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(STextBlock) + .Text(LOCTEXT("AkUpdateWwisePath", "The Wwise Unreal Engine Integration plug-in's update process requires the Wwise GeneratedSoundBanks Folder to be set in the Project Settings dialog. Would you like to open the Project Settings?")) .AutoWrapText(true) + ] + + SVerticalBox::Slot() + .FillHeight(0.75f) + [ + SNew(SSpacer) + ] + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SCheckBox) + .Padding(FMargin(6.0, 2.0)) + .OnCheckStateChanged_Lambda([&](ECheckBoxState DontAskState) { + AkSettingsPerUser->SuppressGeneratedSoundBanksPathWarnings = (DontAskState == ECheckBoxState::Checked); + }) + [ + SNew(STextBlock) + .Text(LOCTEXT("AkDontShowAgain", "Don't show this again")) + ] + ] + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SSpacer) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0.0f, 3.0f, 0.0f, 3.0f) + [ + SNew(SButton) + .Text(LOCTEXT("Yes", "Yes")) + .OnClicked_Lambda([&]() -> FReply { + FModuleManager::LoadModuleChecked("Settings").ShowViewer(FName("Project"), FName("Plugins"), FName("Wwise")); + Dialog->RequestDestroyWindow(); + AkSettingsPerUser->SaveConfig(); + return FReply::Handled(); + }) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0.0f, 3.0f, 0.0f, 3.0f) + [ + SNew(SButton) + .Text(LOCTEXT("No", "No")) + .OnClicked_Lambda([&]() -> FReply { + Dialog->RequestDestroyWindow(); + AkSettingsPerUser->SaveConfig(); + return FReply::Handled(); + }) + ] + ] + ; + + Dialog->SetContent(DialogContent); + FSlateApplication::Get().AddModalWindow(Dialog.ToSharedRef(), nullptr); + } + else + { + UE_LOG(LogAudiokineticTools, Log, TEXT("GeneratedSoundBanks folder not found. The Wwise Browser will not be usable.")); + } + } + else + { + FPaths::MakePathRelativeTo(TempPath, *ProjectDir); + if (AkSettings->GeneratedSoundBanksFolder.Path != TempPath) + { + AkSettings->WwiseProjectPath.FilePath = TempPath; + AkUnrealEditorHelper::SaveConfigFile(AkSettings); + } + } + } +} + +void FAudiokineticToolsModule::OnAssetRegistryFilesLoaded() +{ + UAkSettings* AkSettings = GetMutableDefault(); + UAkSettingsPerUser* AkSettingsPerUser = GetMutableDefault(); + auto* CurrentProject = IProjectManager::Get().GetCurrentProject(); + bool doModifyProject = true; + + WwiseProjectInfo wwiseProjectInfo; + wwiseProjectInfo.Parse(); + UpdateUnrealCultureToWwiseCultureMap(wwiseProjectInfo); + + if (GUnrealEd != NULL) + { + GUnrealEd->RegisterComponentVisualizer(UAkComponent::StaticClass()->GetFName(), MakeShareable(new FAkComponentVisualizer)); + GUnrealEd->RegisterComponentVisualizer(UAkSurfaceReflectorSetComponent::StaticClass()->GetFName(), MakeShareable(new FAkSurfaceReflectorSetComponentVisualizer)); + GUnrealEd->RegisterComponentVisualizer(UAkPortalComponent::StaticClass()->GetFName(), MakeShareable(new UAkPortalComponentVisualizer)); + } + + AkSettings->InitAkGeometryMap(); + AkSettings->EnsurePluginContentIsInAlwaysCook(); + + AkAcousticTextureParamLookup AcousticTextureParamLookup; + AcousticTextureParamLookup.UpdateParamsMap(); + + if (!IsRunningCommandlet() ) + { + if (FApp::CanEverRender()) + { + AssetMigrationManager.CreateMigrationMenuOption(); + AssetMigrationManager.EditorTryMigration(); + } + SoundBanksDirectoryWatcher.Initialize(); + SoundBanksDirectoryWatcher.OnSoundBanksGenerated.AddStatic(&FAudiokineticToolsModule::ParseGeneratedSoundBankData); + } + + // If we're on the project loader screen, we don't want to display the dialog. + // In that case, CurrentProject is nullptr. + if (CurrentProject && AkSettings && AkSettingsPerUser) + { + VerifyGeneratedSoundBanksPath(AkSettings, AkSettingsPerUser); + + if (doModifyProject) + { + AssetMigrationManager.SetStandardProjectSettings(); + } + } +} + +void FAudiokineticToolsModule::StartupModule() +{ + AudiokineticToolsModuleInstance = this; + if (FModuleManager::Get().IsModuleLoaded("AssetTools")) + { + auto& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); + auto AudiokineticAssetCategoryBit = AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("Audiokinetic")), LOCTEXT("AudiokineticAssetCategory", "Audiokinetic")); + + AkAssetTypeActionsArray = + { + MakeShared(AudiokineticAssetCategoryBit), + MakeShared(AudiokineticAssetCategoryBit), + MakeShared(AudiokineticAssetCategoryBit), + MakeShared(AudiokineticAssetCategoryBit), + MakeShared(AudiokineticAssetCategoryBit), + }; + + for (auto& AkAssetTypeActions : AkAssetTypeActionsArray) + AssetTools.RegisterAssetTypeActions(AkAssetTypeActions.ToSharedRef()); + } + + if (FModuleManager::Get().IsModuleLoaded("LevelEditor") && !IsRunningCommandlet() && FApp::CanEverRender()) + { + RegisterWwiseMenus(); + CreateAkViewportCommands(); + } + + RegisterSettings(); + + AkEventBroker = MakeShared(); + FComponentAssetBrokerage::RegisterBroker(AkEventBroker, UAkComponent::StaticClass(), true, true); + + auto& TabSpawnerEntry = FGlobalTabmanager::Get()->RegisterNomadTabSpawner(SWwiseBrowser::WwiseBrowserTabName, FOnSpawnTab::CreateRaw(this, &FAudiokineticToolsModule::CreateWwiseBrowserTab)) + .SetDisplayName(NSLOCTEXT("FAudiokineticToolsModule", "BrowserTabTitle", "Wwise Browser")) + .SetTooltipText(NSLOCTEXT("FAudiokineticToolsModule", "BrowserTooltipText", "Open the Wwise Browser tab.")) + .SetGroup(WorkspaceMenu::GetMenuStructure().GetLevelEditorCategory()) + .SetIcon(FSlateIcon(FAkAudioStyle::GetStyleSetName(), "AudiokineticTools.AkBrowserTabIcon")); + + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + OnAssetRegistryFilesLoadedHandle = AssetRegistryModule.Get().OnFilesLoaded().AddRaw(this, &FAudiokineticToolsModule::OnAssetRegistryFilesLoaded); + + ISequencerModule& SequencerModule = FModuleManager::LoadModuleChecked(TEXT("Sequencer")); + RTPCTrackEditorHandle = SequencerModule.RegisterTrackEditor(FOnCreateTrackEditor::CreateStatic(&FMovieSceneAkAudioRTPCTrackEditor::CreateTrackEditor)); + EventTrackEditorHandle = SequencerModule.RegisterTrackEditor(FOnCreateTrackEditor::CreateStatic(&FMovieSceneAkAudioEventTrackEditor::CreateTrackEditor)); + + // Since we are initialized in the PostEngineInit phase, our Ambient Sound actor factory is not registered. We need to register it ourselves. + if (GEditor) + { + if (auto NewFactory = NewObject()) + { + GEditor->ActorFactories.Add(NewFactory); + } + } + + FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); + PropertyModule.RegisterCustomClassLayout(UAkSurfaceReflectorSetComponent::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FAkSurfaceReflectorSetDetailsCustomization::MakeInstance)); + PropertyModule.RegisterCustomClassLayout(UAkLateReverbComponent::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FAkLateReverbComponentDetailsCustomization::MakeInstance)); + PropertyModule.RegisterCustomClassLayout(UAkRoomComponent::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FAkRoomComponentDetailsCustomization::MakeInstance)); + PropertyModule.RegisterCustomClassLayout(UAkPortalComponent::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FAkPortalComponentDetailsCustomization::MakeInstance)); + PropertyModule.RegisterCustomClassLayout(UAkGeometryComponent::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FAkGeometryComponentDetailsCustomization::MakeInstance)); + PropertyModule.RegisterCustomClassLayout(UAkSettings::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FAkSettingsDetailsCustomization::MakeInstance)); + + if (IWwiseProjectDatabaseModule::IsInACookingCommandlet()) + { + return; + } + + if (FAkAudioModule::AkAudioModuleInstance && FAkAudioModule::AkAudioModuleInstance->bModuleInitialized) + { + OnAkAudioInit(); + } + else + { + FAkAudioModule::OnModuleInitialized.AddRaw(this, &FAudiokineticToolsModule::OnAkAudioInit); + } + + FEditorDelegates::BeginPIE.AddRaw(this, &FAudiokineticToolsModule::BeginPIE); +} + +void FAudiokineticToolsModule::OnAkAudioInit() +{ + if (AssetMigrationManager.IsProjectMigrated()) + { + //We want to reload asset data after having parsed generated SoundBank data AND after initializing the AkAudioDevice + FAkAudioModule::AkAudioModuleInstance->ReloadWwiseAssetData(); + } + + FAkAudioStyle::Initialize(); + + if (UAkSettings* Settings = GetMutableDefault()) + { + Settings->OnGeneratedSoundBanksPathChanged.AddRaw(this, &FAudiokineticToolsModule::OnSoundBanksFolderChanged); + OnDatabaseUpdateTextureHandle = FWwiseProjectDatabaseDelegates::Get().GetOnDatabaseUpdateCompletedDelegate().AddRaw(this, &FAudiokineticToolsModule::RefreshAndUpdateTextureParams); + } + if (UAkSettingsPerUser* UserSettings = GetMutableDefault()) + { + UserSettings->OnGeneratedSoundBanksPathChanged.AddRaw(this, &FAudiokineticToolsModule::OnSoundBanksFolderChanged); + } + OnDatabaseUpdateCompleteHandle = FWwiseProjectDatabaseDelegates::Get().GetOnDatabaseUpdateCompletedDelegate().AddRaw(this, &FAudiokineticToolsModule::AssetReloadPrompt); + +#if AK_SUPPORT_WAAPI + if (!IsRunningCommandlet()) + { + FAkWaapiClient::Initialize(); + if (UAkSettings* AkSettings = GetMutableDefault()) + { + AkSettings->InitWaapiSync(); + } + } +#endif +} + +void FAudiokineticToolsModule::OnSoundBanksFolderChanged() +{ + FAkAudioModule::AkAudioModuleInstance->UpdateWwiseResourceLoaderSettings(); + ParseGeneratedSoundBankData(); +} + +void FAudiokineticToolsModule::BeginPIE(const bool bIsSimulating) +{ + + UAkSettings* Settings = GetMutableDefault(); + if(Settings && !Settings->GeneratedSoundBanksPathExists() && FAkAudioModule::AkAudioModuleInstance) + { + DisplayGeneratedSoundBanksWarning(); + } +} + +void FAudiokineticToolsModule::DisplayGeneratedSoundBanksWarning() +{ + if (!FApp::CanEverRender()) + { + return; + } + GeneratedSoundBanksWarning.HideGeneratedSoundBanksNotification(); + GeneratedSoundBanksWarning.DisplayGeneratedSoundBanksWarning(); +} + +void FAudiokineticToolsModule::AssetReloadPrompt() +{ + const UAkSettingsPerUser* UserSettings = GetDefault(); + if (UserSettings->AskForWwiseAssetReload && FApp::CanEverRender()) + { + OpenAssetReloadPopup(); + } + else + { + FAkAudioModule::AkAudioModuleInstance->ReloadWwiseAssetData(); + } +} + +void FAudiokineticToolsModule::OpenAssetReloadPopup() +{ + ReloadPopup.HideRefreshNotification(); + ReloadPopup.NotifyProjectRefresh(); +} + + +void FAudiokineticToolsModule::ParseGeneratedSoundBankData() +{ + FAkAudioModule::ParseGeneratedSoundBankData(); +} + +void FAudiokineticToolsModule::ShutdownModule() +{ + if (FModuleManager::Get().IsModuleLoaded("AssetTools")) + { + auto& AssetTools = FModuleManager::GetModuleChecked("AssetTools").Get(); + + for (auto AkAssetTypeActions : AkAssetTypeActionsArray) + if (AkAssetTypeActions.IsValid()) + AssetTools.UnregisterAssetTypeActions(AkAssetTypeActions.ToSharedRef()); + } + + AkAssetTypeActionsArray.Empty(); + + if (FModuleManager::Get().IsModuleLoaded("LevelEditor")) + { + auto& LevelEditorModule = FModuleManager::GetModuleChecked("LevelEditor"); + LevelEditorModule.GetAllLevelEditorToolbarBuildMenuExtenders().RemoveAll([=](const FLevelEditorModule::FLevelEditorMenuExtender& Extender) + { + return Extender.GetHandle() == LevelViewportToolbarBuildMenuExtenderAkHandle; + }); + + if (MainMenuExtender.IsValid()) + { + LevelEditorModule.GetMenuExtensibilityManager()->RemoveExtender(MainMenuExtender); + } + } + LevelViewportToolbarBuildMenuExtenderAkHandle.Reset(); + + UnregisterSettings(); + + if (GUnrealEd != NULL) + { + GUnrealEd->UnregisterComponentVisualizer(UAkComponent::StaticClass()->GetFName()); + } + + FGlobalTabmanager::Get()->UnregisterTabSpawner(SWwiseBrowser::WwiseBrowserTabName); + + if (FModuleManager::Get().IsModuleLoaded(TEXT("Sequencer"))) + { + auto& SequencerModule = FModuleManager::GetModuleChecked(TEXT("Sequencer")); + SequencerModule.UnRegisterTrackEditor(RTPCTrackEditorHandle); + SequencerModule.UnRegisterTrackEditor(EventTrackEditorHandle); + } + + // Only found way to close the tab in the case of a hot-reload. We need a pointer to the DockTab, and the only way of getting it seems to be InvokeTab. + if (IsValid(GUnrealEd)) + { +#if UE_4_26_OR_LATER + auto WwiseBrowserTab = FGlobalTabmanager::Get()->TryInvokeTab(SWwiseBrowser::WwiseBrowserTabName); + if (WwiseBrowserTab.IsValid()) + { + WwiseBrowserTab->RequestCloseTab(); + } +#else + FGlobalTabmanager::Get()->InvokeTab(SWwiseBrowser::WwiseBrowserTabName)->RequestCloseTab(); +#endif + } + + FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(SWwiseBrowser::WwiseBrowserTabName); + + if (UObjectInitialized()) + { + FComponentAssetBrokerage::UnregisterBroker(AkEventBroker); + } + + if (UObjectInitialized() && !IsEngineExitRequested()) + { + FPropertyEditorModule* PropertyModule = FModuleManager::Get().GetModulePtr("PropertyEditor"); + if (PropertyModule) + { + PropertyModule->UnregisterCustomClassLayout(UAkSurfaceReflectorSetComponent::StaticClass()->GetFName()); + PropertyModule->UnregisterCustomClassLayout(UAkLateReverbComponent::StaticClass()->GetFName()); + PropertyModule->UnregisterCustomClassLayout(UAkRoomComponent::StaticClass()->GetFName()); + } + } + + if (IWwiseProjectDatabaseModule::IsInACookingCommandlet()) + { + return; + } + + if (OnDatabaseUpdateTextureHandle.IsValid()) + { + FWwiseProjectDatabaseDelegates::Get().GetOnDatabaseUpdateCompletedDelegate().Remove(OnDatabaseUpdateTextureHandle); + OnDatabaseUpdateTextureHandle.Reset(); + } + +#if WITH_EDITOR + FAkAudioStyle::Shutdown(); +#if AK_SUPPORT_WAAPI + FAkWaapiClient::DeleteInstance(); +#endif +#endif + + SoundBanksDirectoryWatcher.Uninitialize(true); + AudiokineticToolsModuleInstance = nullptr; +} + +void FAudiokineticToolsModule::RefreshAndUpdateTextureParams() +{ + AkAcousticTextureParamLookup AcousticTextureParamLookup; + AcousticTextureParamLookup.UpdateParamsMap(); + + UAkSettings* AkSettings = GetMutableDefault(); + if (AkSettings) + { + AkSettings->RefreshAcousticTextureParams(); + } +} + +EEditorBuildResult FAudiokineticToolsModule::BuildAkEventData(UWorld* world, FName name) +{ + if (!AkAssetDatabase::Get().CheckIfLoadingAssets()) + { + AkGenerateSoundBanksTask::ExecuteForEditorPlatform(); + return EEditorBuildResult::InProgress; + } + else + { + return EEditorBuildResult::Skipped; + } +} + +TMap& FAudiokineticToolsModule::GetWwisePlatformNameToSettingsRegistrationMap() +{ + static TMap WwisePlatformNameToWwiseSettingsRegistrationMap; + if (WwisePlatformNameToWwiseSettingsRegistrationMap.Num() == 0) + { + auto RegisterIntegrationSettings = SettingsRegistrationStruct(UAkSettings::StaticClass(), + "Integration", + LOCTEXT("WwiseIntegrationSettingsName", "Integration Settings"), + LOCTEXT("WwiseIntegrationSettingsDescription", "Configure the Wwise Integration")); + + auto RegisterPerUserSettings = SettingsRegistrationStruct(UAkSettingsPerUser::StaticClass(), + "User Settings", + LOCTEXT("WwiseRuntimePerUserSettingsName", "User Settings"), + LOCTEXT("WwiseRuntimePerUserSettingsDescription", "Configure the Wwise Integration per user")); + + WwisePlatformNameToWwiseSettingsRegistrationMap.Add(FString("Integration"), RegisterIntegrationSettings); + WwisePlatformNameToWwiseSettingsRegistrationMap.Add(FString("User"), RegisterPerUserSettings); + + for (const auto& AvailablePlatform : AkUnrealPlatformHelper::GetAllSupportedUnrealPlatforms()) + { + FString SettingsClassName = FString::Format(TEXT("/Script/AkAudio.Ak{0}InitializationSettings"), { *AvailablePlatform }); +#if UE_5_1_OR_LATER + auto* SettingsClass = UClass::TryFindTypeSlow(*SettingsClassName); +#else + auto* SettingsClass = FindObject(ANY_PACKAGE, *SettingsClassName); +#endif + if (SettingsClass) + { + FString CategoryNameKey = FString::Format(TEXT("Wwise{0}SettingsName"), { *AvailablePlatform }); + FString DescriptionNameKey = FString::Format(TEXT("Wwise{0}SettingsDescription"), { *AvailablePlatform }); + FString DescriptionText = FString::Format(TEXT("Configure the Wwise {0} Initialization Settings"), { *AvailablePlatform }); + FText PlatformNameText = FText::FromString(*AvailablePlatform); + FString AdditionalDescriptionText = TEXT(""); + if (AkUnrealPlatformHelper::IsEditorPlatform(AvailablePlatform)) + { + AdditionalDescriptionText = TEXT("\nYou must restart the Unreal Editor for changes to be applied to the Wwise Sound Engine running in the Editor"); + } + FText PlatformDescriptionText = FText::Format(LOCTEXT("WwiseSettingsDescription", "Configure the Wwise {0} Initialization Settings{1}"), PlatformNameText, FText::FromString(*AdditionalDescriptionText)); + auto RegisterPlatform = SettingsRegistrationStruct(SettingsClass, FName(*AvailablePlatform), + PlatformNameText, + PlatformDescriptionText); + WwisePlatformNameToWwiseSettingsRegistrationMap.Add(*AvailablePlatform, RegisterPlatform); + } + } + } + return WwisePlatformNameToWwiseSettingsRegistrationMap; +} + +void FAudiokineticToolsModule::RegisterSettings() +{ + if (auto SettingsModule = FModuleManager::GetModulePtr("Settings")) + { + auto UpdatePlatformSettings = [SettingsModule, this] + { + auto SettingsRegistrationMap = GetWwisePlatformNameToSettingsRegistrationMap(); + + TSet SettingsThatShouldBeRegistered = { FString("Integration"), FString("User") }; + + for (const auto& AvailablePlatform : AkUnrealPlatformHelper::GetAllSupportedUnrealPlatformsForProject()) + { + if (SettingsRegistrationMap.Contains(AvailablePlatform)) + { + SettingsThatShouldBeRegistered.Add(AvailablePlatform); + } + } + + auto SettingsToBeUnregistered = RegisteredSettingsNames.Difference(SettingsThatShouldBeRegistered); + for (const auto& SettingsName : SettingsToBeUnregistered) + { + SettingsRegistrationMap[SettingsName].Unregister(SettingsModule); + RegisteredSettingsNames.Remove(SettingsName); + } + + auto SettingsToBeRegistered = SettingsThatShouldBeRegistered.Difference(RegisteredSettingsNames); + for (const auto& SettingsName : SettingsToBeRegistered) + { + if (RegisteredSettingsNames.Contains(SettingsName)) + continue; + + SettingsRegistrationMap[SettingsName].Register(SettingsModule); + RegisteredSettingsNames.Add(SettingsName); + } + }; + + UpdatePlatformSettings(); + + IProjectManager& ProjectManager = IProjectManager::Get(); + ProjectManager.OnTargetPlatformsForCurrentProjectChanged().AddLambda(UpdatePlatformSettings); + } +} + +void FAudiokineticToolsModule::UnregisterSettings() +{ + if (auto SettingsModule = FModuleManager::GetModulePtr("Settings")) + { + auto SettingsRegistrationMap = GetWwisePlatformNameToSettingsRegistrationMap(); + for (const auto& SettingsName : RegisteredSettingsNames) + { + SettingsRegistrationMap[SettingsName].Unregister(SettingsModule); + } + RegisteredSettingsNames.Empty(); + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AudiokineticToolsPrivatePCH.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AudiokineticToolsPrivatePCH.h new file mode 100644 index 0000000..dd3fbb2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/AudiokineticToolsPrivatePCH.h @@ -0,0 +1,24 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AudiokineticToolsPrivatePCH.h: +=============================================================================*/ +#pragma once + +#include "IAudiokineticTools.h" +#include "WwiseDefines.h" diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkGeometryComponentDetailsCustomization.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkGeometryComponentDetailsCustomization.cpp new file mode 100644 index 0000000..2bc9292 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkGeometryComponentDetailsCustomization.cpp @@ -0,0 +1,296 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkGeometryComponentDetailsCustomization.h" + +#include "DetailCategoryBuilder.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "PropertyCustomizationHelpers.h" +#include "Editor/TransBuffer.h" +#include "UI/SAkGeometrySurfaceOverrideController.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Layout/SBox.h" + +#include "AkGeometryComponent.h" + +#define LOCTEXT_NAMESPACE "AudiokineticTools" + +static int SurfacePropertiesHeight = 72; + +////////////////////////////////////////////////////////////////////////// +// FAkGeometryDetailsCustomization + +FAkGeometryComponentDetailsCustomization::FAkGeometryComponentDetailsCustomization() +{ + ComponentBeingCustomized = nullptr; +} + +FAkGeometryComponentDetailsCustomization::~FAkGeometryComponentDetailsCustomization() +{ + if (ComponentBeingCustomized.IsValid() && ComponentBeingCustomized->GetOnRefreshDetails()) + { + if (ComponentBeingCustomized->GetOnRefreshDetails()->IsBoundToObject(this)) + { + ComponentBeingCustomized->ClearOnRefreshDetails(); + } + } + + ComponentBeingCustomized.Reset(); +} + +void FAkGeometryComponentDetailsCustomization::BeginModify(FText TransactionText) +{ + if (GEditor && GEditor->Trans) + { + UTransBuffer* TransBuffer = CastChecked(GEditor->Trans); + if (TransBuffer != nullptr) + TransBuffer->Begin(*FString("AkGeometry Acoustic Surfaces"), TransactionText); + } + + if (ComponentBeingCustomized.IsValid()) + ComponentBeingCustomized->Modify(); +} + +void FAkGeometryComponentDetailsCustomization::EndModify() +{ + if (GEditor && GEditor->Trans) + { + UTransBuffer* TransBuffer = CastChecked(GEditor->Trans); + if (TransBuffer != nullptr) + TransBuffer->End(); + } +} + +TSharedRef FAkGeometryComponentDetailsCustomization::MakeInstance() +{ + return MakeShared(); +} + +void FAkGeometryComponentDetailsCustomization::CustomizeDetails(const TSharedPtr& InDetailBuilder) +{ + DetailBuilder = InDetailBuilder; + CustomizeDetails(*InDetailBuilder); +} + +void FAkGeometryComponentDetailsCustomization::CustomizeDetails(IDetailLayoutBuilder& InDetailBuilder) +{ + TArray> ObjectsBeingCustomized; + InDetailBuilder.GetObjectsBeingCustomized(ObjectsBeingCustomized); + + InDetailBuilder.EditCategory("Geometry", FText::GetEmpty(), ECategoryPriority::TypeSpecific); + + for (TWeakObjectPtr& Object : ObjectsBeingCustomized) + { + UAkGeometryComponent* GeometryComponentBeingCustomized = Cast(Object.Get()); + if (GeometryComponentBeingCustomized) + { + UObject* OuterObj = GeometryComponentBeingCustomized->GetOuter(); + UActorComponent* OuterComponent = Cast(OuterObj); + AActor* OuterActor = Cast(OuterObj); + // Do not hide the transform if the component has been created from within a component or actor, as this will hide the transform for that component / actor as well + // (i.e. - only hide the transform if the component has been added to the hierarchy of a blueprint class or actor instance from the editor) + if (OuterComponent == nullptr && OuterActor == nullptr) + { + IDetailCategoryBuilder& TransformCategory = InDetailBuilder.EditCategory("TransformCommon", LOCTEXT("TransformCommonCategory", "Transform"), ECategoryPriority::Transform); + TransformCategory.SetCategoryVisibility(false); + break; + } + } + } + + if (ObjectsBeingCustomized.Num() != 1) + { + return; + } + + auto Component = Cast(ObjectsBeingCustomized[0].Get()); + if (Component == nullptr) + { + return; + } + + auto LockedDetailBuilder = DetailBuilder.Pin(); + if (UNLIKELY(!LockedDetailBuilder)) + { + return; + } + + ComponentBeingCustomized = TWeakObjectPtr(Component); + auto meshTypeChangedHandle = LockedDetailBuilder->GetProperty("MeshType"); + meshTypeChangedHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FAkGeometryComponentDetailsCustomization::RefreshDetails)); + + FOnRefreshDetails refreshDetails = FOnRefreshDetails::CreateSP(this, &FAkGeometryComponentDetailsCustomization::RefreshDetails); + ComponentBeingCustomized->SetOnRefreshDetails(refreshDetails); + + if (ComponentBeingCustomized->MeshType == AkMeshType::StaticMesh) + { + InDetailBuilder.HideProperty("CollisionMeshSurfaceOverride"); + if (ObjectsBeingCustomized.Num() == 1) + { + IDetailCategoryBuilder& CategoryBuilder = InDetailBuilder.EditCategory("Surface Overrides", FText::GetEmpty(), ECategoryPriority::TypeSpecific); + + InDetailBuilder.HideProperty("StaticMeshSurfaceOverride"); + auto SurfacesPropHandle = InDetailBuilder.GetProperty("StaticMeshSurfaceOverride"); + CategoryBuilder.HeaderContent + ( + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .Text(FText::FromString("Reset to Defaults")) + .ToolTipText(FText::FromString(FString("Reset the surface properties to their default values."))) + .OnClicked_Lambda([this, SurfacesPropHandle]() + { + BeginModify(FText::FromString(FString("Reset surface properties to defaults"))); + if (ComponentBeingCustomized->HasAnyFlags(RF_ArchetypeObject) + || ComponentBeingCustomized->CreationMethod == EComponentCreationMethod::Instance) + { + TArray Materials; + ComponentBeingCustomized->StaticMeshSurfaceOverride.GetKeys(Materials); + for (UMaterialInterface* Material : Materials) + { + ComponentBeingCustomized->StaticMeshSurfaceOverride[Material].AcousticTexture = nullptr; + ComponentBeingCustomized->StaticMeshSurfaceOverride[Material].bEnableOcclusionOverride = false; + ComponentBeingCustomized->StaticMeshSurfaceOverride[Material].OcclusionValue = 1.0f; + } + } + else + { + ComponentBeingCustomized->StaticMeshSurfaceOverride.Empty(); + SurfacesPropHandle->ResetToDefault(); + } + EndModify(); + return FReply::Handled(); + }) + ] + ); + ComponentBeingCustomized->UpdateStaticMeshOverride(); + TArray Materials; + ComponentBeingCustomized->StaticMeshSurfaceOverride.GetKeys(Materials); + for (UMaterialInterface* Material : Materials) + { + FDetailWidgetRow& SurfacesRow = CategoryBuilder.AddCustomRow(FText::FromString("Texture Surface Occlusion")); + SurfacesRow.NameContent() + [ + SNew(SBox) + .HeightOverride(SurfacePropertiesHeight) + .ToolTipText(FText::FromString("The material(s) in this list are populated using the materials assigned to the AkGeometry Component's Static Mesh parent Component.")) + [ + SNew(SObjectPropertyEntryBox) + .AllowedClass(UMaterialInterface::StaticClass()) + .ObjectPath_Lambda([Material]() { return FSoftObjectPath(Material).GetAssetPathString(); }) + .ToolTipText(FText::FromString("The material(s) in this list are populated using the materials assigned to the AkGeometry Component's Static Mesh parent Component.")) + .IsEnabled(false) + ] + ]; + SurfacesRow.ValueContent() + [ + SNew(SBox) + .HeightOverride(SurfacePropertiesHeight) + [ + SNew(SAkGeometryStaticMeshSurfaceController, ObjectsBeingCustomized[0], LockedDetailBuilder, Material) + ] + ]; + } + } + } + else if (ComponentBeingCustomized->MeshType == AkMeshType::CollisionMesh) + { + InDetailBuilder.HideProperty("LOD"); + InDetailBuilder.HideProperty("WeldingThreshold"); + InDetailBuilder.HideProperty("StaticMeshSurfaceOverride"); + + if (ObjectsBeingCustomized.Num() == 1) + { + IDetailCategoryBuilder& CategoryBuilder = InDetailBuilder.EditCategory("Surface Overrides", FText::GetEmpty(), ECategoryPriority::TypeSpecific); + + auto SurfacesPropHandle = InDetailBuilder.GetProperty("CollisionMeshSurfaceOverride"); + InDetailBuilder.HideProperty("CollisionMeshSurfaceOverride"); + + CategoryBuilder.HeaderContent + ( + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .Text(FText::FromString("Reset to Defaults")) + .ToolTipText(FText::FromString(FString("Reset the surface properties to their default values."))) + .OnClicked_Lambda([this, SurfacesPropHandle]() + { + BeginModify(FText::FromString(FString("Reset surface properties to defaults"))); + if (ComponentBeingCustomized->HasAnyFlags(RF_ArchetypeObject) + || ComponentBeingCustomized->CreationMethod == EComponentCreationMethod::Instance) + { + ComponentBeingCustomized->CollisionMeshSurfaceOverride.AcousticTexture = nullptr; + ComponentBeingCustomized->CollisionMeshSurfaceOverride.bEnableOcclusionOverride = false; + ComponentBeingCustomized->CollisionMeshSurfaceOverride.OcclusionValue = 1.0f; + } + else + { + SurfacesPropHandle->ResetToDefault(); + } + EndModify(); + return FReply::Handled(); + }) + ] + ); + + FDetailWidgetRow& SurfacesRow = CategoryBuilder.AddCustomRow(FText::FromString("Texture Surface Occlusion")); + + SurfacesRow.ValueContent() + [ + SNew(SBox) + .HeightOverride(SurfacePropertiesHeight) + [ + SNew(SAkGeometryCollisionMeshSurfaceController, ObjectsBeingCustomized[0], LockedDetailBuilder) + ] + ]; + } + } +} + +void FAkGeometryComponentDetailsCustomization::RefreshDetails() +{ + if (ComponentBeingCustomized.IsValid()) + { + ComponentBeingCustomized->ClearOnRefreshDetails(); + } + + if (DetailBuilder.IsValid()) + { + IDetailLayoutBuilder* Layout = nullptr; + if (auto LockedDetailBuilder = DetailBuilder.Pin()) + { + Layout = LockedDetailBuilder.Get(); + } + if (LIKELY(Layout)) + { + Layout->ForceRefreshDetails(); + } + } +} + +////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkGeometryComponentDetailsCustomization.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkGeometryComponentDetailsCustomization.h new file mode 100644 index 0000000..e5b63bb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkGeometryComponentDetailsCustomization.h @@ -0,0 +1,44 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "PropertyEditorModule.h" +#include "IDetailCustomization.h" +////////////////////////////////////////////////////////////////////////// +// FAkGeometryDetailsCustomization + +class FAkGeometryComponentDetailsCustomization : public IDetailCustomization +{ +public: + FAkGeometryComponentDetailsCustomization(); + ~FAkGeometryComponentDetailsCustomization(); + // Makes a new instance of this detail layout class for a specific detail view requesting it + static TSharedRef MakeInstance(); + + // IDetailCustomization interface + virtual void CustomizeDetails(const TSharedPtr& InDetailBuilder) override; + virtual void CustomizeDetails(IDetailLayoutBuilder& InDetailBuilder) override; + // End of IDetailCustomization interface + +private: + TWeakPtr DetailBuilder; + TWeakObjectPtr ComponentBeingCustomized; + void RefreshDetails(); + void BeginModify(FText TransactionText); + void EndModify(); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkLateReverbComponentDetailsCustomization.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkLateReverbComponentDetailsCustomization.cpp new file mode 100644 index 0000000..26c1177 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkLateReverbComponentDetailsCustomization.cpp @@ -0,0 +1,113 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkLateReverbComponentDetailsCustomization.h" +#include "AkComponent.h" +#include "AkLateReverbComponent.h" + +#include "DetailLayoutBuilder.h" +#include "DetailCategoryBuilder.h" +#include "ScopedTransaction.h" +#include "IPropertyUtilities.h" +#include "Widgets/Text/STextBlock.h" + +#define LOCTEXT_NAMESPACE "AudiokineticTools" + + +////////////////////////////////////////////////////////////////////////// +// FAkLateReverbComponentDetailsCustomization + +FAkLateReverbComponentDetailsCustomization::FAkLateReverbComponentDetailsCustomization() +{ +} + +TSharedRef FAkLateReverbComponentDetailsCustomization::MakeInstance() +{ + return MakeShareable(new FAkLateReverbComponentDetailsCustomization()); +} + +void FAkLateReverbComponentDetailsCustomization::CustomizeDetails(const TSharedPtr& InDetailBuilder) +{ + InDetailBuilder->EditCategory("Toggle", FText::GetEmpty(), ECategoryPriority::Important); + InDetailBuilder->EditCategory("Late Reverb", FText::GetEmpty(), ECategoryPriority::TypeSpecific); + DetailBuilder = InDetailBuilder; + + CustomizeDetails(*InDetailBuilder); +} + +void FAkLateReverbComponentDetailsCustomization::CustomizeDetails(IDetailLayoutBuilder& InDetailBuilder) +{ + TArray> ObjectsBeingCustomized; + InDetailBuilder.GetObjectsBeingCustomized(ObjectsBeingCustomized); + InDetailBuilder.HideProperty("AuxBusManual"); + for (TWeakObjectPtr& Object : ObjectsBeingCustomized) + { + UAkLateReverbComponent* LateReverbBeingCustomized = Cast(Object.Get()); + if (LateReverbBeingCustomized) + { + UObject* OuterObj = LateReverbBeingCustomized->GetOuter(); + UActorComponent* OuterComponent = Cast(OuterObj); + AActor* OuterActor = Cast(OuterObj); + // Do not hide the transform if the component has been created from within a component or actor, as this will hide the transform for that component / actor as well + // (i.e. - only hide the transform if the component has been added to the hierarchy of a blueprint class or actor instance from the editor) + if (OuterComponent == nullptr && OuterActor == nullptr) + { + IDetailCategoryBuilder& TransformCategory = InDetailBuilder.EditCategory("TransformCommon", LOCTEXT("TransformCommonCategory", "Transform"), ECategoryPriority::Transform); + TransformCategory.SetCategoryVisibility(false); + break; + } + } + } + + if (ObjectsBeingCustomized.Num() != 1) + { + return; + } + + UAkLateReverbComponent* LateReverbBeingCustomized = Cast(ObjectsBeingCustomized[0].Get()); + if (LateReverbBeingCustomized) + { + IDetailCategoryBuilder& ToggleDetailCategory = InDetailBuilder.EditCategory("Toggle"); + auto EnableHandle = InDetailBuilder.GetProperty("bEnable"); + EnableHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FAkLateReverbComponentDetailsCustomization::OnEnableValueChanged)); + + if (!LateReverbBeingCustomized->bEnable) + { + InDetailBuilder.HideCategory("Late Reverb"); + } + } +} + +void FAkLateReverbComponentDetailsCustomization::OnEnableValueChanged() +{ + if (DetailBuilder.IsValid()) + { + IDetailLayoutBuilder* Layout = nullptr; + if (auto LockedDetailBuilder = DetailBuilder.Pin()) + { + Layout = LockedDetailBuilder.Get(); + } + if (LIKELY(Layout)) + { + Layout->ForceRefreshDetails(); + } + } +} + +////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkLateReverbComponentDetailsCustomization.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkLateReverbComponentDetailsCustomization.h new file mode 100644 index 0000000..3cf3b01 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkLateReverbComponentDetailsCustomization.h @@ -0,0 +1,41 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "PropertyEditorModule.h" +#include "IDetailCustomization.h" +////////////////////////////////////////////////////////////////////////// +// FAkLateReverbComponentDetailsCustomization + +class FAkLateReverbComponentDetailsCustomization : public IDetailCustomization +{ +public: + FAkLateReverbComponentDetailsCustomization(); + // Makes a new instance of this detail layout class for a specific detail view requesting it + static TSharedRef MakeInstance(); + + // IDetailCustomization interface + virtual void CustomizeDetails(const TSharedPtr& InDetailBuilder) override; + virtual void CustomizeDetails(IDetailLayoutBuilder& InDetailBuilder) override; + // End of IDetailCustomization interface + +private: + TWeakPtr DetailBuilder; + void OnEnableValueChanged(); + +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkPortalComponentDetailsCustomization.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkPortalComponentDetailsCustomization.cpp new file mode 100644 index 0000000..ea416ef --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkPortalComponentDetailsCustomization.cpp @@ -0,0 +1,68 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkPortalComponentDetailsCustomization.h" +#include "AkAcousticPortal.h" +#include "DetailLayoutBuilder.h" +#include "DetailCategoryBuilder.h" + +#define LOCTEXT_NAMESPACE "AudiokineticTools" + + +////////////////////////////////////////////////////////////////////////// +// FAkPortalComponentDetailsCustomization + +FAkPortalComponentDetailsCustomization::FAkPortalComponentDetailsCustomization() +{ +} + +TSharedRef FAkPortalComponentDetailsCustomization::MakeInstance() +{ + return MakeShareable(new FAkPortalComponentDetailsCustomization()); +} + +void FAkPortalComponentDetailsCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailLayout) +{ + TArray> ObjectsBeingCustomized; + DetailLayout.GetObjectsBeingCustomized(ObjectsBeingCustomized); + + DetailLayout.EditCategory("AkPortalComponent", FText::GetEmpty(), ECategoryPriority::TypeSpecific); + DetailLayout.EditCategory("Fit To Geometry", FText::GetEmpty(), ECategoryPriority::TypeSpecific); + + for (TWeakObjectPtr& Object : ObjectsBeingCustomized) + { + UAkPortalComponent* PortalBeingCustomized = Cast(Object.Get()); + if (PortalBeingCustomized) + { + UObject* OuterObj = PortalBeingCustomized->GetOuter(); + UActorComponent* OuterComponent = Cast(OuterObj); + AActor* OuterActor = Cast(OuterObj); + // Do not hide the transform if the component has been created from within a component or actor, as this will hide the transform for that component / actor as well + // (i.e. - only hide the transform if the component has been added to the hierarchy of a blueprint class or actor instance from the editor) + if (OuterComponent == nullptr && OuterActor == nullptr) + { + IDetailCategoryBuilder& TransformCategory = DetailLayout.EditCategory("TransformCommon", LOCTEXT("TransformCommonCategory", "Transform"), ECategoryPriority::Transform); + TransformCategory.SetCategoryVisibility(false); + break; + } + } + } +} + +////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkPortalComponentDetailsCustomization.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkPortalComponentDetailsCustomization.h new file mode 100644 index 0000000..2468487 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkPortalComponentDetailsCustomization.h @@ -0,0 +1,36 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "PropertyEditorModule.h" +#include "IDetailCustomization.h" +////////////////////////////////////////////////////////////////////////// +// FAkPortalComponentDetailsCustomization + +class FAkPortalComponentDetailsCustomization : public IDetailCustomization +{ +public: + FAkPortalComponentDetailsCustomization(); + // Makes a new instance of this detail layout class for a specific detail view requesting it + static TSharedRef MakeInstance(); + + // IDetailCustomization interface + virtual void CustomizeDetails(IDetailLayoutBuilder& DetailLayout) override; + // End of IDetailCustomization interface + +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkRoomComponentDetailsCustomization.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkRoomComponentDetailsCustomization.cpp new file mode 100644 index 0000000..0ec466e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkRoomComponentDetailsCustomization.cpp @@ -0,0 +1,109 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkRoomComponentDetailsCustomization.h" +#include "AkRoomComponent.h" +#include "DetailLayoutBuilder.h" +#include "DetailCategoryBuilder.h" + +#define LOCTEXT_NAMESPACE "AudiokineticTools" + + +////////////////////////////////////////////////////////////////////////// +// FAkRoomComponentDetailsCustomization + +FAkRoomComponentDetailsCustomization::FAkRoomComponentDetailsCustomization() +{ +} + +TSharedRef FAkRoomComponentDetailsCustomization::MakeInstance() +{ + return MakeShareable(new FAkRoomComponentDetailsCustomization()); +} + +void FAkRoomComponentDetailsCustomization::CustomizeDetails(const TSharedPtr& InDetailBuilder) +{ + InDetailBuilder->EditCategory("Toggle", FText::GetEmpty(), ECategoryPriority::Important); + InDetailBuilder->EditCategory("Room", FText::GetEmpty(), ECategoryPriority::TypeSpecific); + InDetailBuilder->EditCategory("AkEvent", FText::GetEmpty(), ECategoryPriority::TypeSpecific); + DetailBuilder = InDetailBuilder; + + CustomizeDetails(*InDetailBuilder); +} + +void FAkRoomComponentDetailsCustomization::CustomizeDetails(IDetailLayoutBuilder& InDetailBuilder) +{ + TArray> ObjectsBeingCustomized; + InDetailBuilder.GetObjectsBeingCustomized(ObjectsBeingCustomized); + + for (TWeakObjectPtr& Object : ObjectsBeingCustomized) + { + UAkRoomComponent* RoomBeingCustomized = Cast(Object.Get()); + if (RoomBeingCustomized) + { + UObject* OuterObj = RoomBeingCustomized->GetOuter(); + UActorComponent* OuterComponent = Cast(OuterObj); + AActor* OuterActor = Cast(OuterObj); + // Do not hide the transform if the component has been created from within a component or actor, as this will hide the transform for that component / actor as well + // (i.e. - only hide the transform if the component has been added to the hierarchy of a blueprint class or actor instance from the editor) + if (OuterComponent == nullptr && OuterActor == nullptr) + { + IDetailCategoryBuilder& TransformCategory = InDetailBuilder.EditCategory("TransformCommon", LOCTEXT("TransformCommonCategory", "Transform"), ECategoryPriority::Transform); + TransformCategory.SetCategoryVisibility(false); + break; + } + } + } + + if (ObjectsBeingCustomized.Num() != 1) + { + return; + } + + UAkRoomComponent* RoomBeingCustomized = Cast(ObjectsBeingCustomized[0].Get()); + if (RoomBeingCustomized) + { + IDetailCategoryBuilder& ToggleDetailCategory = InDetailBuilder.EditCategory("Toggle"); + auto EnableHandle = InDetailBuilder.GetProperty("bEnable"); + EnableHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FAkRoomComponentDetailsCustomization::OnEnableValueChanged)); + + if (!RoomBeingCustomized->bEnable) + { + InDetailBuilder.HideCategory("Room"); + } + } +} + +void FAkRoomComponentDetailsCustomization::OnEnableValueChanged() +{ + if (DetailBuilder.IsValid()) + { + IDetailLayoutBuilder* Layout = nullptr; + if (auto LockedDetailBuilder = DetailBuilder.Pin()) + { + Layout = LockedDetailBuilder.Get(); + } + if (LIKELY(Layout)) + { + Layout->ForceRefreshDetails(); + } + } +} + +////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkRoomComponentDetailsCustomization.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkRoomComponentDetailsCustomization.h new file mode 100644 index 0000000..84a7a55 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkRoomComponentDetailsCustomization.h @@ -0,0 +1,41 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "PropertyEditorModule.h" +#include "IDetailCustomization.h" +////////////////////////////////////////////////////////////////////////// +// FAkRoomComponentDetailsCustomization + +class FAkRoomComponentDetailsCustomization : public IDetailCustomization +{ +public: + FAkRoomComponentDetailsCustomization(); + // Makes a new instance of this detail layout class for a specific detail view requesting it + static TSharedRef MakeInstance(); + + // IDetailCustomization interface + virtual void CustomizeDetails(const TSharedPtr& InDetailBuilder) override; + virtual void CustomizeDetails(IDetailLayoutBuilder& InDetailBuilder) override; + // End of IDetailCustomization interface + +private: + TWeakPtr DetailBuilder; + void OnEnableValueChanged(); + +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkSettingsDetailsCustomization.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkSettingsDetailsCustomization.cpp new file mode 100644 index 0000000..9635226 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkSettingsDetailsCustomization.cpp @@ -0,0 +1,332 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkSettingsDetailsCustomization.h" +#include "AkSettings.h" +#include "AkUEFeatures.h" +#include "DetailCategoryBuilder.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "IStructureDetailsView.h" +#include "Widgets/Input/SNumericEntryBox.h" +#include "Widgets/Input/SButton.h" +#include "Editor.h" + +#define LOCTEXT_NAMESPACE "AudiokineticTools" + +/** Dialog widget used to display function properties */ +class SDecayKeyEntryDialog : public SCompoundWidget +{ + SLATE_BEGIN_ARGS(SDecayKeyEntryDialog) {} + SLATE_END_ARGS() + + // We call this from a delegate that is called when the window has correctly been added to the slate application. + // See FAkSettingsDetailsCustomization::InsertKeyModal. + void FocusNumericEntryBox() + { + if (numberBox != nullptr) + { + FSlateApplication::Get().ClearAllUserFocus(); + FSlateApplication::Get().SetAllUserFocus(numberBox); + FSlateApplication::Get().ClearKeyboardFocus(); + FSlateApplication::Get().SetKeyboardFocus(numberBox); + } + } + + void Construct(const FArguments& InArgs, TWeakPtr InParentWindow, float& decay) + { + bCommitted = false; + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SBorder) + .BorderImage(FAkAppStyle::Get().GetBrush("ToolPanel.GroupBorder")) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .Padding(2.0f) + .AutoHeight() + [ + SAssignNew(numberBox, SNumericEntryBox) + .MinValue(0.0f) + .MaxValue(100.0f) + .Value_Lambda([&decay]() {return decay;}) + .OnValueCommitted_Lambda([this, InParentWindow, &decay](const float& value, ETextCommit::Type commitType) + { + if (commitType == ETextCommit::Type::OnCleared) + decay = 0.0f; + else + decay = value; + + if (commitType == ETextCommit::OnEnter) + { + bCommitted = true; + if (InParentWindow.IsValid()) + { + InParentWindow.Pin()->RequestDestroyWindow(); + } + } + }) + .OnValueChanged_Lambda([&decay](const float& value) + { + decay = value; + }) + ] + + SVerticalBox::Slot() + .Padding(2.0f) + .AutoHeight() + [ + SNew(SButton) + .ForegroundColor(FLinearColor::White) + .OnClicked_Lambda([this, InParentWindow, InArgs]() + { + if (InParentWindow.IsValid()) + { + InParentWindow.Pin()->RequestDestroyWindow(); + } + bCommitted = true; + return FReply::Handled(); + }) + .ToolTipText(FText::FromString("Insert given key value")) + [ + SNew(STextBlock) + .TextStyle(FAkAppStyle::Get(), "FlatButton.DefaultTextStyle") + .Text(FText::FromString("Insert")) + ] + ] + ] + ] + ]; + } + + bool bCommitted = false; + TSharedPtr> numberBox = nullptr; +}; + +/** Dialog widget used to display function properties */ +class SClearWarningDialog : public SCompoundWidget +{ + SLATE_BEGIN_ARGS(SClearWarningDialog) {} + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs, TWeakPtr InParentWindow) + { + bOKPressed = false; + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SBorder) + .BorderImage(FAkAppStyle::Get().GetBrush("ToolPanel.GroupBorder")) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .Padding(2.0f) + .AutoHeight() + [ + SNew(STextBlock) + .Text(FText::FromString("Warning: This will remove all aux bus values in the map. Do you want to continue?")) + ] + ] + ] + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SBorder) + .BorderImage(FAkAppStyle::Get().GetBrush("ToolPanel.GroupBorder")) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2.0f) + .AutoWidth() + [ + SNew(SButton) + .ButtonStyle(FAkAppStyle::Get(), "FlatButton.Success") + .ForegroundColor(FLinearColor::White) + .OnClicked_Lambda([this, InParentWindow, InArgs]() + { + if (InParentWindow.IsValid()) + { + InParentWindow.Pin()->RequestDestroyWindow(); + } + bOKPressed = true; + return FReply::Handled(); + }) + .ToolTipText(FText::FromString("Clear aux bus assignment map")) + [ + SNew(STextBlock) + .TextStyle(FAkAppStyle::Get(), "FlatButton.DefaultTextStyle") + .Text(FText::FromString("Clear")) + ] + ] + + SHorizontalBox::Slot() + .Padding(2.0f) + .AutoWidth() + [ + SNew(SButton) + .ButtonStyle(FAkAppStyle::Get(), "FlatButton.Default") + .ForegroundColor(FLinearColor::White) + .OnClicked_Lambda([this, InParentWindow, InArgs]() + { + if (InParentWindow.IsValid()) + { + InParentWindow.Pin()->RequestDestroyWindow(); + } + return FReply::Handled(); + }) + .ToolTipText(FText::FromString("Cancel")) + [ + SNew(STextBlock) + .TextStyle(FAkAppStyle::Get(), "FlatButton.DefaultTextStyle") + .Text(FText::FromString("Cancel")) + ] + ] + ] + ] + ]; + } + + bool bOKPressed; +}; + +TSharedRef FAkSettingsDetailsCustomization::MakeInstance() +{ + return MakeShareable(new FAkSettingsDetailsCustomization); +} + +void FAkSettingsDetailsCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailLayout) +{ + IDetailCategoryBuilder& CategoryBuilder = DetailLayout.EditCategory("Reverb Assignment Map", FText::GetEmpty(), ECategoryPriority::Uncommon); + CategoryBuilder.AddCustomRow(FText::FromString("Clear Map")).WholeRowContent() + [ + SNew(SVerticalBox) + + SVerticalBox::Slot().AutoHeight().Padding(2) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth() + [ + SNew(SButton) + .Text(FText::FromString("Clear Map")) + .ToolTipText(FText::FromString("Clear all of the entries in the map")) + .OnClicked_Raw(this, &FAkSettingsDetailsCustomization::ClearAkSettingsRoomDecayAuxBusMap) + ] + + SHorizontalBox::Slot().FillWidth(8) + ] + + SVerticalBox::Slot().AutoHeight().Padding(2) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth() + [ + SNew(SButton) + .Text(FText::FromString("Insert Decay Key")) + .OnClicked_Raw(this, &FAkSettingsDetailsCustomization::InsertKeyModal) + ] + + SHorizontalBox::Slot().FillWidth(8) + ] + ]; + + TArray> Properties; + CategoryBuilder.GetDefaultProperties(Properties); + + for (TSharedRef Property : Properties) + { + CategoryBuilder.AddProperty(Property); + } +} + +FReply FAkSettingsDetailsCustomization::ClearAkSettingsRoomDecayAuxBusMap() +{ + // pop up a dialog to input params to the function + TSharedRef Window = SNew(SWindow) + .Title(FText::FromString("Clear aux bus assignment map?")) + .ScreenPosition(FSlateApplication::Get().GetCursorPos()) + .AutoCenter(EAutoCenter::None) + .SizingRule(ESizingRule::Autosized) + .SupportsMinimize(false) + .SupportsMaximize(false); + + TSharedPtr Dialog; + Window->SetContent(SAssignNew(Dialog, SClearWarningDialog, Window)); + + GEditor->EditorAddModalWindow(Window); + + if (Dialog->bOKPressed) + { + UAkSettings* AkSettings = GetMutableDefault(); + if (AkSettings != nullptr) + AkSettings->ClearAkRoomDecayAuxBusMap(); + } + + return FReply::Handled(); +} + +FReply FAkSettingsDetailsCustomization::InsertKeyModal() +{ + // pop up a dialog to input params to the function + TSharedRef Window = SNew(SWindow) + .Title(FText::FromString("Insert Decay Key")) + .ScreenPosition(FSlateApplication::Get().GetCursorPos()) + .AutoCenter(EAutoCenter::None) + .SizingRule(ESizingRule::Autosized) + .SupportsMinimize(false) + .SupportsMaximize(false); + + float decay = 0.0f; + TSharedPtr Dialog; + Window->SetContent(SAssignNew(Dialog, SDecayKeyEntryDialog, Window, decay)); + + // In order to give focus to the textbox in SDecayKeyEntryDialog when the dialog is created, + // we need to wait until the window has been added to the slate application. If we try to focus the + // text box during the construction of the dialog, the window will not be found in the slate application's + // list of top level windows, and the text box will not be focused. + // To achieve this, we need to set up a delegate to call after the modal window has been added to the slate application. + FModalWindowStackStarted modalWindowCallback; + modalWindowCallback.BindLambda([&Dialog]() + { + if (Dialog.IsValid()) + Dialog->FocusNumericEntryBox(); + }); + FSlateApplication::Get().SetModalWindowStackStartedDelegate(modalWindowCallback); + + // During this call, the ModalWindowStackStartedDelegate will be called. + GEditor->EditorAddModalWindow(Window); + + // Clear the ModalWindowStackStartedDelegate. + FSlateApplication::Get().SetModalWindowStackStartedDelegate(nullptr); + + if (Dialog->bCommitted) + { + UAkSettings* AkSettings = GetMutableDefault(); + if (AkSettings != nullptr) + AkSettings->InsertDecayKeyValue(decay); + } + + return FReply::Handled(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkSettingsDetailsCustomization.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkSettingsDetailsCustomization.h new file mode 100644 index 0000000..6daf15b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkSettingsDetailsCustomization.h @@ -0,0 +1,35 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "IDetailCustomization.h" +#include "Input/Reply.h" + +class FAkSettingsDetailsCustomization : public IDetailCustomization +{ +public: + /** Makes a new instance of this detail layout class for a specific detail view requesting it */ + static TSharedRef MakeInstance(); + + // IDetailCustomization interface + virtual void CustomizeDetails(IDetailLayoutBuilder& DetailLayout) override; + // End of IDetailCustomization interface + + FReply ClearAkSettingsRoomDecayAuxBusMap(); + FReply InsertKeyModal(); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkSurfaceReflectorSetDetailsCustomization.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkSurfaceReflectorSetDetailsCustomization.cpp new file mode 100644 index 0000000..27ce2f8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkSurfaceReflectorSetDetailsCustomization.cpp @@ -0,0 +1,356 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkSurfaceReflectorSetDetailsCustomization.h" +#include "AkComponent.h" +#include "AkSurfaceReflectorSetComponent.h" + +#include "DetailCategoryBuilder.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "EditorModeManager.h" +#include "EditorSupportDelegates.h" +#include "IPropertyUtilities.h" +#include "LevelEditorActions.h" +#include "Model.h" +#include "GameFramework/Volume.h" +#include "UI/SAcousticSurfacesController.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Text/STextBlock.h" + +#include "Builders/ConeBuilder.h" +#include "Builders/CubeBuilder.h" +#include "Builders/CurvedStairBuilder.h" +#include "Builders/CylinderBuilder.h" +#include "Builders/LinearStairBuilder.h" +#include "Builders/SpiralStairBuilder.h" +#include "Builders/TetrahedronBuilder.h" + + +#define LOCTEXT_NAMESPACE "AudiokineticTools" + + +////////////////////////////////////////////////////////////////////////// +// FAkSurfaceReflectorSetDetailsCustomization + +FAkSurfaceReflectorSetDetailsCustomization::FAkSurfaceReflectorSetDetailsCustomization() +{ + ReflectorSetBeingCustomized = nullptr; + FCoreUObjectDelegates::OnObjectModified.AddRaw(this, &FAkSurfaceReflectorSetDetailsCustomization::OnObjectModified); + FEditorSupportDelegates::RedrawAllViewports.AddRaw(this, &FAkSurfaceReflectorSetDetailsCustomization::OnRedrawViewports); +} + +FAkSurfaceReflectorSetDetailsCustomization::~FAkSurfaceReflectorSetDetailsCustomization() +{ + FCoreUObjectDelegates::OnObjectModified.RemoveAll(this); + FEditorSupportDelegates::RedrawAllViewports.RemoveAll(this); + + if (ReflectorSetBeingCustomized.IsValid() && ReflectorSetBeingCustomized->GetOnRefreshDetails()) + { + if (ReflectorSetBeingCustomized->GetOnRefreshDetails()->IsBoundToObject(this)) + { + ReflectorSetBeingCustomized->ClearOnRefreshDetails(); + } + } + + ReflectorSetBeingCustomized.Reset(); +} + +FReply FAkSurfaceReflectorSetDetailsCustomization::OnEnableEditModeClicked() +{ + FLevelEditorActionCallbacks::OnShowOnlySelectedActors(); + GLevelEditorModeTools().ActivateMode(FEditorModeID(TEXT("EM_Geometry")), false); + return FReply::Handled(); +} + +FReply FAkSurfaceReflectorSetDetailsCustomization::OnDisableEditModeClicked() +{ + GLevelEditorModeTools().DeactivateMode(FEditorModeID(TEXT("EM_Geometry"))); + FLevelEditorActionCallbacks::ExecuteExecCommand(FString(TEXT("ACTOR UNHIDE ALL"))); + return FReply::Handled(); +} + +void FAkSurfaceReflectorSetDetailsCustomization::OnObjectModified(UObject* Object) +{ + if (!SelectedObjectModifiedThisFrame) + { + for (TWeakObjectPtr UObjectPtr : ObjectsBeingCustomized) + { + if (Object == UObjectPtr.Get()) + { + SelectedObjectModifiedThisFrame = true; + return; + } + } + } +} + +void FAkSurfaceReflectorSetDetailsCustomization::OnRedrawViewports() +{ + if (SelectedObjectModifiedThisFrame && DetailBuilder.IsValid()) + { + // If there is any user interaction going on, we don't want to refresh the details panel. + // (This would interupt the interaction and make sliders unusable) + for (TWeakObjectPtr UObjectPtr : ObjectsBeingCustomized) + { + if (UAkSurfaceReflectorSetComponent* reflectorSet = Cast(UObjectPtr.Get())) + { + if (reflectorSet->UserInteractionInProgress) + { + return; + } + } + } + + IDetailLayoutBuilder* Layout = nullptr; + if (auto LockedDetailBuilder = DetailBuilder.Pin()) + { + Layout = LockedDetailBuilder.Get(); + } + if (LIKELY(Layout)) + { + Layout->ForceRefreshDetails(); + } + + SelectedObjectModifiedThisFrame = false; + } +} + +TSharedRef FAkSurfaceReflectorSetDetailsCustomization::MakeInstance() +{ + return MakeShareable(new FAkSurfaceReflectorSetDetailsCustomization()); +} + +void FAkSurfaceReflectorSetDetailsCustomization::CustomizeDetails(const TSharedPtr& InDetailBuilder) +{ + InDetailBuilder->EditCategory("Toggle", FText::GetEmpty(), ECategoryPriority::Important); + InDetailBuilder->EditCategory("Geometry Settings", FText::GetEmpty(), ECategoryPriority::TypeSpecific); + InDetailBuilder->EditCategory("Fit To Geometry", FText::GetEmpty(), ECategoryPriority::TypeSpecific); + DetailBuilder = InDetailBuilder; + + CustomizeDetails(*InDetailBuilder); +} + +void FAkSurfaceReflectorSetDetailsCustomization::CustomizeDetails(IDetailLayoutBuilder& InDetailBuilder) +{ + InDetailBuilder.GetObjectsBeingCustomized(ObjectsBeingCustomized); + auto AcousticPolysPropHandle = InDetailBuilder.GetProperty("AcousticPolys"); + InDetailBuilder.HideProperty("AcousticPolys"); + + bool showGeometrySettings = false; + for (int i = 0; i < ObjectsBeingCustomized.Num(); ++i) + { + auto Component = Cast(ObjectsBeingCustomized[i].Get()); + if (Component) + { + ReflectorSetBeingCustomized = TWeakObjectPtr(Component); + if (ReflectorSetBeingCustomized->bEnableSurfaceReflectors) + { + showGeometrySettings = true; + break; + } + } + } + + if (!showGeometrySettings) + InDetailBuilder.HideCategory("Geometry Settings"); + + IDetailCategoryBuilder& CategoryBuilder = InDetailBuilder.EditCategory("Geometry Surfaces", FText::GetEmpty(), ECategoryPriority::TypeSpecific); + FString enableEditSurfacesTooltip(FString("Enable ") + GEOMETRY_EDIT_DISPLAY_NAME + " and show only selected actors"); + FString disableEditSurfacesTooltip(FString("Disable ") + GEOMETRY_EDIT_DISPLAY_NAME + " and show all actors"); + + CategoryBuilder.HeaderContent + ( + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SAssignNew(SelectionInfoLabel, STextBlock) + // SelectionInfoLabel is passed in to a SAcousticSurfacesController below, and the selection text is set during its construction. + .Text(FText::FromString("")) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .Text(FText::FromString("Enable Edit Surfaces ")) + .ToolTipText(FText::FromString(enableEditSurfacesTooltip)) + .OnClicked(this, &FAkSurfaceReflectorSetDetailsCustomization::OnEnableEditModeClicked) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .Text(FText::FromString("Disable Edit Surfaces")) + .ToolTipText(FText::FromString(disableEditSurfacesTooltip)) + .OnClicked(this, &FAkSurfaceReflectorSetDetailsCustomization::OnDisableEditModeClicked) + ] + ); + // Assume Transmission Loss controls will be hidden + int surfacePropertiesControlsHeight = 48; + for (TWeakObjectPtr ObjectBeingCustomized : ObjectsBeingCustomized) + { + UAkSurfaceReflectorSetComponent* reflectorSetComponent = Cast(ObjectBeingCustomized.Get()); + if (reflectorSetComponent && reflectorSetComponent->bEnableSurfaceReflectors) + { + // There is a surface reflector set with bEnableSurfaceReflectors enabled - add room for Transmission Loss controls. + surfacePropertiesControlsHeight = 72; + break; + } + } + + if (auto LockedDetailBuilder = DetailBuilder.Pin()) + { + FDetailWidgetRow& acousticSurfacesRow = CategoryBuilder.AddCustomRow(AcousticPolysPropHandle->GetPropertyDisplayName()); + acousticSurfacesRow.NameContent() + [ + SNew(SBox) + .HeightOverride(surfacePropertiesControlsHeight) + [ + SNew(SAcousticSurfacesLabels, ObjectsBeingCustomized) + ] + ]; + acousticSurfacesRow.ValueContent() + [ + SNew(SBox) + .HeightOverride(surfacePropertiesControlsHeight) + [ + SNew(SAcousticSurfacesController, ObjectsBeingCustomized, LockedDetailBuilder) + ] + ]; + } + + if (ObjectsBeingCustomized.Num() == 1) + { + auto Component = Cast(ObjectsBeingCustomized[0].Get()); + if (Component) + { + ReflectorSetBeingCustomized = TWeakObjectPtr(Component); + SetupGeometryModificationHandlers(); + } + else + { + ReflectorSetBeingCustomized.Reset(); + UE_LOG(LogAkAudio, Log, TEXT("FAkSurfaceReflectorSetDetailsCustomization::CustomizeDetails: Could not get ObjectsBeingCustomized.")); + } + } +} + +#define REGISTER_PROPERTY_CHANGED(Class, Property, LockedDetailBuilder) \ + auto Property ## Handle = LockedDetailBuilder->GetProperty(GET_MEMBER_NAME_CHECKED(Class, Property), Class::StaticClass(), BrushBuilderName); \ + if (Property ## Handle->IsValidHandle()) Property ## Handle->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FAkSurfaceReflectorSetDetailsCustomization::OnGeometryChanged)) + +void FAkSurfaceReflectorSetDetailsCustomization::SetupGeometryModificationHandlers() +{ + if (!ReflectorSetBeingCustomized.IsValid()) + { + return; + } + + static const FName BrushBuilderName(TEXT("BrushBuilder")); + auto ParentBrush = ReflectorSetBeingCustomized->ParentBrush; + if(!ParentBrush) + return; + + if (auto LockedDetailBuilder = DetailBuilder.Pin()) + { + auto EnableHandle = LockedDetailBuilder->GetProperty("bEnableSurfaceReflectors"); + EnableHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FAkSurfaceReflectorSetDetailsCustomization::OnEnableValueChanged)); + + // This is to detect if the BrushBuilder changed. + if (ReflectorSetBeingCustomized->AcousticPolys.Num() != ParentBrush->Nodes.Num()) + LockedDetailBuilder->GetPropertyUtilities()->EnqueueDeferredAction(FSimpleDelegate::CreateSP(this, &FAkSurfaceReflectorSetDetailsCustomization::OnGeometryChanged)); + } + + // Need to register to a LOT of different properties, because some change the geometry but don't force a refresh of the details panel + AVolume* ParentVolume = Cast(ReflectorSetBeingCustomized->GetOwner()); + UClass* BrushBuilderClass = nullptr; + if (ParentVolume && ParentVolume->BrushBuilder) + { + BrushBuilderClass = ParentVolume->BrushBuilder->GetClass(); + if (BrushBuilderClass == nullptr) + { + return; + } + } + + if (auto LockedDetailBuilder = DetailBuilder.Pin()) + { + if (BrushBuilderClass == UConeBuilder::StaticClass()) + { + REGISTER_PROPERTY_CHANGED(UConeBuilder, Sides, LockedDetailBuilder); + REGISTER_PROPERTY_CHANGED(UConeBuilder, Hollow, LockedDetailBuilder); + } + else if (BrushBuilderClass == UCubeBuilder::StaticClass()) + { + REGISTER_PROPERTY_CHANGED(UCubeBuilder, Hollow, LockedDetailBuilder); + REGISTER_PROPERTY_CHANGED(UCubeBuilder, Tessellated, LockedDetailBuilder); + } + else if (BrushBuilderClass == UCurvedStairBuilder::StaticClass()) + { + REGISTER_PROPERTY_CHANGED(UCurvedStairBuilder, NumSteps, LockedDetailBuilder); + } + else if (BrushBuilderClass == UCylinderBuilder::StaticClass()) + { + REGISTER_PROPERTY_CHANGED(UCylinderBuilder, Sides, LockedDetailBuilder); + REGISTER_PROPERTY_CHANGED(UCylinderBuilder, Hollow, LockedDetailBuilder); + } + else if (BrushBuilderClass == ULinearStairBuilder::StaticClass()) + { + REGISTER_PROPERTY_CHANGED(ULinearStairBuilder, NumSteps, LockedDetailBuilder); + } + else if (BrushBuilderClass == USpiralStairBuilder::StaticClass()) + { + REGISTER_PROPERTY_CHANGED(USpiralStairBuilder, NumSteps, LockedDetailBuilder); + } + else if (BrushBuilderClass == UTetrahedronBuilder::StaticClass()) + { + REGISTER_PROPERTY_CHANGED(UTetrahedronBuilder, SphereExtrapolation, LockedDetailBuilder); + } + } + + FOnRefreshDetails DetailsChanged = FOnRefreshDetails::CreateRaw(this, &FAkSurfaceReflectorSetDetailsCustomization::OnEnableValueChanged); + ReflectorSetBeingCustomized->SetOnRefreshDetails(DetailsChanged); +} + +void FAkSurfaceReflectorSetDetailsCustomization::OnEnableValueChanged() +{ + if (ReflectorSetBeingCustomized.IsValid()) + { + ReflectorSetBeingCustomized->ClearOnRefreshDetails(); + } +} + +void FAkSurfaceReflectorSetDetailsCustomization::OnGeometryChanged() +{ + if (ReflectorSetBeingCustomized.IsValid()) + { + ReflectorSetBeingCustomized->UpdatePolys(); + ReflectorSetBeingCustomized->ClearOnRefreshDetails(); + } +} + +////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkSurfaceReflectorSetDetailsCustomization.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkSurfaceReflectorSetDetailsCustomization.h new file mode 100644 index 0000000..230db94 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/DetailsCustomization/AkSurfaceReflectorSetDetailsCustomization.h @@ -0,0 +1,60 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "PropertyEditorModule.h" +#include "IDetailCustomization.h" +#include "UObject/StrongObjectPtr.h" +////////////////////////////////////////////////////////////////////////// +// FAkSurfaceReflectorSetDetailsCustomization + +class IDetailCategoryBuilder; +class STextBlock; +class UAkSurfaceReflectorSetComponent; + +class FAkSurfaceReflectorSetDetailsCustomization : public IDetailCustomization +{ +public: + FAkSurfaceReflectorSetDetailsCustomization(); + ~FAkSurfaceReflectorSetDetailsCustomization(); + // Makes a new instance of this detail layout class for a specific detail view requesting it + static TSharedRef MakeInstance(); + + // IDetailCustomization interface + virtual void CustomizeDetails(const TSharedPtr& InDetailBuilder) override; + virtual void CustomizeDetails(IDetailLayoutBuilder& InDetailBuilder) override; + // End of IDetailCustomization interface + +private: + TWeakPtr DetailBuilder; + TSharedPtr SelectionInfoLabel; + TArray> ObjectsBeingCustomized; + TWeakObjectPtr ReflectorSetBeingCustomized; + FReply OnEnableEditModeClicked(); + FReply OnDisableEditModeClicked(); + // In geometry edit mode, when the face selection is changed for an actor, the OnObjectModified delegate is broadcast before the selected faces are updated. + // For that reason, we use a flag to indicate when an object has been modified this frame. + // That flag is used in OnRedrawViewports - which happens later - to refresh the details panel in case the selected faces have changed. + bool SelectedObjectModifiedThisFrame = false; + void OnObjectModified(UObject* Object); + void OnRedrawViewports(); + + void OnEnableValueChanged(); + void OnGeometryChanged(); + void SetupGeometryModificationHandlers(); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Factories/ActorFactoryAkAmbientSound.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Factories/ActorFactoryAkAmbientSound.cpp new file mode 100644 index 0000000..a9753f1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Factories/ActorFactoryAkAmbientSound.cpp @@ -0,0 +1,98 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + ActorFactoryAkAmbientSound.cpp: +=============================================================================*/ +#include "Factories/ActorFactoryAkAmbientSound.h" + +#include "AkAmbientSound.h" +#include "AkAudioEvent.h" +#include "AkComponent.h" +#include "AssetRegistry/AssetData.h" +#include "Editor/EditorEngine.h" + +#define LOCTEXT_NAMESPACE "ActorFactoryAkAmbientSound" + +/*----------------------------------------------------------------------------- + UActorFactoryAkAmbientSound +-----------------------------------------------------------------------------*/ + +UActorFactoryAkAmbientSound::UActorFactoryAkAmbientSound(const class FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + // Property initialization + DisplayName = LOCTEXT("AkAmbientSoundName", "AkAmbientSound"); + NewActorClass = AAkAmbientSound::StaticClass(); + bShowInEditorQuickMenu = true; +} + +bool UActorFactoryAkAmbientSound::CanCreateActorFrom( const FAssetData& AssetData, FText& OutErrorMsg ) +{ + //We allow creating AAmbientSounds without an existing sound asset + if ( UActorFactory::CanCreateActorFrom( AssetData, OutErrorMsg ) ) + { + return true; + } + + if ( AssetData.IsValid() && !AssetData.GetClass()->IsChildOf( UAkAudioEvent::StaticClass() ) ) + { + OutErrorMsg = NSLOCTEXT("CanCreateActor", "NoSoundAsset", "A valid sound asset must be specified."); + return false; + } + + return true; +} + +void UActorFactoryAkAmbientSound::PostSpawnActor( UObject* Asset, AActor* NewActor ) +{ + UAkAudioEvent* AmbientSound = Cast( Asset ); + + if ( AmbientSound != NULL ) + { + AAkAmbientSound* NewSound = CastChecked( NewActor ); + FActorLabelUtilities::SetActorLabelUnique(NewSound, AmbientSound->EventInfo.WwiseName.ToString()); + NewSound->AkComponent->AkAudioEvent = AmbientSound; + if (AmbientSound->bAutoLoad) + { + AmbientSound->LoadData(); + } + } +} + +UObject* UActorFactoryAkAmbientSound::GetAssetFromActorInstance(AActor* Instance) +{ + check(Instance->IsA(NewActorClass)); + AAkAmbientSound* SoundActor = CastChecked(Instance); + + check(SoundActor->AkComponent->AkAudioEvent); + return SoundActor->AkComponent->AkAudioEvent; +} + +void UActorFactoryAkAmbientSound::PostCreateBlueprint( UObject* Asset, AActor* CDO ) +{ + UAkAudioEvent* AmbientSound = Cast( Asset ); + + if ( AmbientSound != NULL ) + { + AAkAmbientSound* NewSound = CastChecked( CDO ); + NewSound->AkComponent->AkAudioEvent = AmbientSound; + AmbientSound->LoadData(); + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Factories/AkAssetFactories.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Factories/AkAssetFactories.cpp new file mode 100644 index 0000000..e5b88c9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Factories/AkAssetFactories.cpp @@ -0,0 +1,288 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkAssetFactories.h" + +#include "AkAcousticTexture.h" +#include "AkAudioBank.h" +#include "AkAudioDevice.h" +#include "AkAudioEvent.h" +#include "AkAuxBus.h" +#include "AkRtpc.h" +#include "AkSettings.h" +#include "AkSettingsPerUser.h" +#include "AkStateValue.h" +#include "AkSwitchValue.h" +#include "AkTrigger.h" +#include "AkEffectShareSet.h" +#include "AssetManagement/AkAssetDatabase.h" +#include "AssetTools/Public/AssetToolsModule.h" + +struct AkAssetFactory_Helper +{ + template + static UObject* FactoryCreateNew(UClass* Class, UObject* InParent, const FName& Name, EObjectFlags Flags, FGuid AssetID = FGuid{}, uint32 ShortID = AK_INVALID_UNIQUE_ID, FString WwiseObjectName = "") + { + auto ContainingPath = InParent->GetName(); + + auto NewWwiseObject = NewObject(InParent, Name, Flags); + FWwiseObjectInfo* Info = NewWwiseObject->GetInfoMutable(); + Info->WwiseGuid = AssetID; + if (WwiseObjectName.IsEmpty()) + { + Info->WwiseName = Name; + } + else + { + Info->WwiseName = FName(WwiseObjectName); + } + if (ShortID == AK_INVALID_UNIQUE_ID) + { + Info->WwiseShortId = FAkAudioDevice::GetShortID(nullptr, Name.ToString()); + } + else + { + Info->WwiseShortId = ShortID; + } + NewWwiseObject->MarkPackageDirty(); + if(NewWwiseObject->bAutoLoad) + { + NewWwiseObject->LoadData(); + } + return NewWwiseObject; + } + + template + static UObject* FactoryCreateNewGroupValue(UClass* Class, UObject* InParent, const FName& Name, EObjectFlags Flags, FGuid AssetID = FGuid{}, uint32 ShortID = AK_INVALID_UNIQUE_ID, FString WwiseObjectName = "") + { + auto ContainingPath = InParent->GetName(); + + AkAssetType* NewStateValue = NewObject(InParent, Name, Flags); + FWwiseGroupValueInfo* Info = static_cast(NewStateValue->GetInfoMutable()); + Info->WwiseGuid = AssetID; + + FString StringName = Name.ToString(); + FString ValueName = StringName; + FString GroupName; + if (StringName.Contains(TEXT("-"))) + { + StringName.Split(TEXT("-"), &GroupName, &ValueName); + } + + if (WwiseObjectName.IsEmpty() && ValueName.IsEmpty()) + { + Info->WwiseName = FName(StringName); + } + else if (WwiseObjectName.IsEmpty()) + { + Info->WwiseName = FName(ValueName); + } + else + { + Info->WwiseName = FName(WwiseObjectName); + } + + + if (ShortID == AK_INVALID_UNIQUE_ID) + { + Info->WwiseShortId = FAkAudioDevice::GetShortID(nullptr, Info->WwiseName.ToString()); + } + else + { + Info->WwiseShortId = ShortID; + } + if (!GroupName.IsEmpty()) + { + Info->GroupShortId = FAkAudioDevice::GetShortID(nullptr, GroupName); + } + else + { + Info->GroupShortId = AK_INVALID_UNIQUE_ID; + UE_LOG(LogAkAudio, Warning, TEXT("FactoryCreateNewGroupValue: New Group Value asset '%s' in '%s' will have an invalid group ID, please set the group ID manually."), *StringName, *InParent->GetPathName()); + } + NewStateValue->MarkPackageDirty(); + if(NewStateValue->bAutoLoad) + { + NewStateValue->LoadData(); + } + return NewStateValue; + } + + template + static bool CanCreateNew() + { + const UAkSettings* AkSettings = GetDefault(); + if (AkSettings) + { + return true; + } + return false; + } + +private: + static FString ConvertAssetPathToWwisePath(FString ContainingPath, const FString& AssetName, const FString& BasePath) + { + ContainingPath.RemoveFromStart(BasePath, ESearchCase::IgnoreCase); + ContainingPath.RemoveFromEnd(FString("/") + AssetName); + return ContainingPath.Replace(TEXT("/"), TEXT("\\")).Replace(TEXT("_"), TEXT(" ")); + } +}; + +////////////////////////////////////////////////////////////////////////// +// UAkAcousticTextureFactory + +UAkAcousticTextureFactory::UAkAcousticTextureFactory(const class FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + SupportedClass = UAkAcousticTexture::StaticClass(); + bCreateNew = bEditorImport = bEditAfterNew = true; +} + +UObject* UAkAcousticTextureFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + return AkAssetFactory_Helper::FactoryCreateNew(Class, InParent, Name, Flags, AssetID, ShortID, WwiseObjectName); +} + +bool UAkAcousticTextureFactory::CanCreateNew() const +{ + return AkAssetFactory_Helper::CanCreateNew(); +} + +////////////////////////////////////////////////////////////////////////// +// UAkAudioEventFactory + +UAkAudioEventFactory::UAkAudioEventFactory(const class FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + SupportedClass = UAkAudioEvent::StaticClass(); + bCreateNew = bEditorImport = bEditAfterNew = true; +} + +UObject* UAkAudioEventFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + return AkAssetFactory_Helper::FactoryCreateNew(Class, InParent, Name, Flags, AssetID, ShortID, WwiseObjectName); +} + +bool UAkAudioEventFactory::CanCreateNew() const +{ + return AkAssetFactory_Helper::CanCreateNew(); +} + +////////////////////////////////////////////////////////////////////////// +// UAkAuxBusFactory + +UAkAuxBusFactory::UAkAuxBusFactory(const class FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + SupportedClass = UAkAuxBus::StaticClass(); + bCreateNew = bEditorImport = bEditAfterNew = true; +} + +UObject* UAkAuxBusFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + return AkAssetFactory_Helper::FactoryCreateNew(Class, InParent, Name, Flags, AssetID, ShortID, WwiseObjectName); +} + +bool UAkAuxBusFactory::CanCreateNew() const +{ + return AkAssetFactory_Helper::CanCreateNew(); +} + +////////////////////////////////////////////////////////////////////////// +// UAkRtpcFactory + +UAkRtpcFactory::UAkRtpcFactory(const class FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + SupportedClass = UAkRtpc::StaticClass(); + bCreateNew = bEditorImport = bEditAfterNew = true; +} + +UObject* UAkRtpcFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + return AkAssetFactory_Helper::FactoryCreateNew(Class, InParent, Name, Flags, AssetID, ShortID, WwiseObjectName); +} + +bool UAkRtpcFactory::CanCreateNew() const +{ + return AkAssetFactory_Helper::CanCreateNew(); +} + +////////////////////////////////////////////////////////////////////////// +// UAkTriggerFactory + +UAkTriggerFactory::UAkTriggerFactory(const class FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + SupportedClass = UAkTrigger::StaticClass(); + bCreateNew = bEditorImport = bEditAfterNew = true; +} + +UObject* UAkTriggerFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + return AkAssetFactory_Helper::FactoryCreateNew(Class, InParent, Name, Flags, AssetID, ShortID, WwiseObjectName); +} + +bool UAkTriggerFactory::CanCreateNew() const +{ + return AkAssetFactory_Helper::CanCreateNew(); +} + +////////////////////////////////////////////////////////////////////////// +// UAkStateValueFactory + +UAkStateValueFactory::UAkStateValueFactory(const class FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + SupportedClass = UAkStateValue::StaticClass(); + bCreateNew = bEditorImport = bEditAfterNew = true; +} + +UObject* UAkStateValueFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + return AkAssetFactory_Helper::FactoryCreateNewGroupValue(Class, InParent, Name, Flags, AssetID, ShortID, WwiseObjectName); +} + +////////////////////////////////////////////////////////////////////////// +// UAkSwitchValueFactory + +UAkSwitchValueFactory::UAkSwitchValueFactory(const class FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + SupportedClass = UAkSwitchValue::StaticClass(); + bCreateNew = bEditorImport = bEditAfterNew = true; +} + +UObject* UAkSwitchValueFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + return AkAssetFactory_Helper::FactoryCreateNewGroupValue(Class, InParent, Name, Flags, AssetID, ShortID, WwiseObjectName); +} + +////////////////////////////////////////////////////////////////////////// +// UAkEffectShareSetFactory + +UAkEffectShareSetFactory::UAkEffectShareSetFactory(const class FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + SupportedClass = UAkEffectShareSet::StaticClass(); + bCreateNew = bEditorImport = bEditAfterNew = true; +} + +UObject* UAkEffectShareSetFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + return AkAssetFactory_Helper::FactoryCreateNew(Class, InParent, Name, Flags, AssetID, ShortID, WwiseObjectName); +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Factories/AkAssetTypeActions.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Factories/AkAssetTypeActions.cpp new file mode 100644 index 0000000..05fa1f5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Factories/AkAssetTypeActions.cpp @@ -0,0 +1,186 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkAssetTypeActions.h" +#include "AkAudioDevice.h" +#include "AkAudioEvent.h" +#include "Framework/Application/SlateApplication.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "IAssetTools.h" +#include "Interfaces/IMainFrameModule.h" +#include "Misc/ScopeLock.h" +#include "Toolkits/SimpleAssetEditor.h" +#include "UObject/Package.h" + +#define LOCTEXT_NAMESPACE "AkAssetTypeActions" + +namespace FAkAssetTypeActions_Helpers +{ + FCriticalSection CriticalSection; + TMap PlayingAkEvents; + + void AkEventPreviewCallback(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo) + { + auto EventInfo = static_cast(in_pCallbackInfo); + if (!EventInfo) + return; + + FScopeLock Lock(&CriticalSection); + for (auto& PlayingEvent : PlayingAkEvents) + { + if (PlayingEvent.Value == EventInfo->playingID) + { + PlayingAkEvents.Remove(PlayingEvent.Key); + return; + } + } + } + + template + void PlayEvents(const TArray>& InObjects) + { + auto AudioDevice = FAkAudioDevice::Get(); + if (!AudioDevice) + return; + + for (auto& Obj : InObjects) + { + auto Event = Obj.Get(); + if (!Event) + continue; + + AkPlayingID* foundID; + { + FScopeLock Lock(&CriticalSection); + foundID = PlayingAkEvents.Find(Event->GetName()); + } + + if (foundID) + { + AudioDevice->StopPlayingID(*foundID); + } + else + { + if(!Event->bAutoLoad) + { + Event->LoadEventDataForContentBrowserPreview(); + } + const auto CurrentPlayingID = Event->PostAmbient(nullptr, &AkEventPreviewCallback, nullptr, AK_EndOfEvent, nullptr, EAkAudioContext::EditorAudio); + if (CurrentPlayingID != AK_INVALID_PLAYING_ID) + { + FScopeLock Lock(&CriticalSection); + PlayingAkEvents.FindOrAdd(Event->GetName()) = CurrentPlayingID; + } + } + + if (PlayOne) + break; + } + } +} + +////////////////////////////////////////////////////////////////////////// +// FAssetTypeActions_AkAcousticTexture + +void FAssetTypeActions_AkAcousticTexture::OpenAssetEditor(const TArray& InObjects, TSharedPtr EditWithinLevelEditor) +{ + FSimpleAssetEditor::CreateEditor(EToolkitMode::Standalone, EditWithinLevelEditor, InObjects); +} + + +////////////////////////////////////////////////////////////////////////// +// FAssetTypeActions_AkAudioEvent + +void FAssetTypeActions_AkAudioEvent::GetActions(const TArray& InObjects, FMenuBuilder& MenuBuilder) +{ + auto Events = GetTypedWeakObjectPtrs(InObjects); + + MenuBuilder.AddMenuEntry( + LOCTEXT("AkAudioEvent_PlayEvent", "Play Event"), + LOCTEXT("AkAudioEvent_PlayEventTooltip", "Plays the selected event."), + FSlateIcon(), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_AkAudioEvent::PlayEvent, Events), + FCanExecuteAction() + ) + ); + + MenuBuilder.AddMenuEntry( + LOCTEXT("AkAudioEvent_StopEvent", "Stop Event"), + LOCTEXT("AkAudioEvent_StopEventTooltip", "Stops the selected event."), + FSlateIcon(), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_AkAudioEvent::StopEvent, Events), + FCanExecuteAction() + ) + ); +} + +void FAssetTypeActions_AkAudioEvent::OpenAssetEditor(const TArray& InObjects, TSharedPtr EditWithinLevelEditor) +{ + FSimpleAssetEditor::CreateEditor(EToolkitMode::Standalone, EditWithinLevelEditor, InObjects); +} + +bool FAssetTypeActions_AkAudioEvent::AssetsActivatedOverride(const TArray& InObjects, EAssetTypeActivationMethod::Type ActivationType) +{ + if (ActivationType == EAssetTypeActivationMethod::DoubleClicked || ActivationType == EAssetTypeActivationMethod::Opened) + { + if (InObjects.Num() == 1) + { + return GEditor->GetEditorSubsystem()->OpenEditorForAsset(InObjects[0]); + } + else if (InObjects.Num() > 1) + { + return GEditor->GetEditorSubsystem()->OpenEditorForAssets(InObjects); + } + } + else if (ActivationType == EAssetTypeActivationMethod::Previewed) + { + auto Events = GetTypedWeakObjectPtrs(InObjects); + FAkAssetTypeActions_Helpers::PlayEvents(Events); + } + + return true; +} + +void FAssetTypeActions_AkAudioEvent::PlayEvent(TArray> Objects) +{ + FAkAssetTypeActions_Helpers::PlayEvents(Objects); +} + +void FAssetTypeActions_AkAudioEvent::StopEvent(TArray> Objects) +{ + FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); + if (AudioDevice) + { + AudioDevice->StopGameObject(nullptr); + } +} + +////////////////////////////////////////////////////////////////////////// +// FAssetTypeActions_AkAuxBus + +void FAssetTypeActions_AkAuxBus::OpenAssetEditor(const TArray& InObjects, TSharedPtr EditWithinLevelEditor) +{ + FSimpleAssetEditor::CreateEditor(EToolkitMode::Standalone, EditWithinLevelEditor, InObjects); +} + +void FAssetTypeActions_AkAuxBus::GetActions(const TArray& InObjects, FMenuBuilder& MenuBuilder) +{ + auto AuxBusses = GetTypedWeakObjectPtrs(InObjects); +} +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Factories/AkAssetTypeActions.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Factories/AkAssetTypeActions.h new file mode 100644 index 0000000..e548be4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Factories/AkAssetTypeActions.h @@ -0,0 +1,107 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AssetTypeActions_Base.h" + +#include "AkAcousticTexture.h" +#include "AkAudioBank.h" +#include "AkAudioEvent.h" +#include "AkAuxBus.h" +#include "AkTrigger.h" +#include "AkRtpc.h" + +template +class FAkAssetTypeActions_Base : public FAssetTypeActions_Base +{ +public: + FAkAssetTypeActions_Base(EAssetTypeCategories::Type InAssetCategory) : MyAssetCategory(InAssetCategory) {} + + // IAssetTypeActions Implementation + virtual UClass* GetSupportedClass() const override { return AkAssetType::StaticClass(); } + virtual uint32 GetCategories() override { return MyAssetCategory; } + virtual bool ShouldForceWorldCentric() { return true; } + +private: + EAssetTypeCategories::Type MyAssetCategory; +}; + +class FAssetTypeActions_AkAcousticTexture : public FAkAssetTypeActions_Base +{ +public: + FAssetTypeActions_AkAcousticTexture(EAssetTypeCategories::Type InAssetCategory) : FAkAssetTypeActions_Base(InAssetCategory) {} + + // IAssetTypeActions Implementation + virtual FText GetName() const override { return NSLOCTEXT("AkAssetTypeActions", "AssetTypeActions_AkAcousticTexture", "Audiokinetic Texture"); } + virtual FColor GetTypeColor() const override { return FColor(1, 185, 251); } + virtual void OpenAssetEditor(const TArray& InObjects, TSharedPtr EditWithinLevelEditor = TSharedPtr()); +}; + +class FAssetTypeActions_AkAudioEvent : public FAkAssetTypeActions_Base +{ +public: + FAssetTypeActions_AkAudioEvent(EAssetTypeCategories::Type InAssetCategory) : FAkAssetTypeActions_Base(InAssetCategory) {} + + // IAssetTypeActions Implementation + virtual FText GetName() const override { return NSLOCTEXT("AkAssetTypeActions", "AssetTypeActions_AkAudioEvent", "Audiokinetic Event"); } + virtual FColor GetTypeColor() const override { return FColor(0, 128, 192); } + virtual bool HasActions(const TArray& InObjects) const override { return true; } + virtual void GetActions(const TArray& InObjects, FMenuBuilder& MenuBuilder) override; + virtual void OpenAssetEditor(const TArray& InObjects, TSharedPtr EditWithinLevelEditor = TSharedPtr()); + bool AssetsActivatedOverride(const TArray& InObjects, EAssetTypeActivationMethod::Type ActivationType) override; + +private: + void PlayEvent(TArray> Objects); + void StopEvent(TArray> Objects); +}; + + +class FAssetTypeActions_AkAuxBus : public FAkAssetTypeActions_Base +{ +public: + FAssetTypeActions_AkAuxBus(EAssetTypeCategories::Type InAssetCategory) : FAkAssetTypeActions_Base(InAssetCategory) {} + + // IAssetTypeActions Implementation + virtual FText GetName() const override { return NSLOCTEXT("AkAssetTypeActions", "AssetTypeActions_AkAuxBus", "Audiokinetic Auxiliary Bus"); } + virtual FColor GetTypeColor() const override { return FColor(192, 128, 0); } + virtual bool HasActions ( const TArray& InObjects ) const override { return true; } + virtual void GetActions(const TArray& InObjects, FMenuBuilder& MenuBuilder) override; + virtual void OpenAssetEditor( const TArray& InObjects, TSharedPtr EditWithinLevelEditor = TSharedPtr() ); +}; + + +class FAssetTypeActions_AkRtpc : public FAkAssetTypeActions_Base +{ +public: + FAssetTypeActions_AkRtpc(EAssetTypeCategories::Type InAssetCategory) : FAkAssetTypeActions_Base(InAssetCategory) {} + + // IAssetTypeActions Implementation + virtual FText GetName() const override { return NSLOCTEXT("AkAssetTypeActions", "AssetTypeActions_AkRtpc", "Audiokinetic Game Parameter"); } + virtual FColor GetTypeColor() const override { return FColor(192, 128, 128); } +}; + +class FAssetTypeActions_AkTrigger : public FAkAssetTypeActions_Base +{ +public: + FAssetTypeActions_AkTrigger(EAssetTypeCategories::Type InAssetCategory) : FAkAssetTypeActions_Base(InAssetCategory) {} + + // IAssetTypeActions Implementation + virtual FText GetName() const override { return NSLOCTEXT("AkAssetTypeActions", "AssetTypeActions_AkTrigger", "Audiokinetic Trigger"); } + virtual FColor GetTypeColor() const override { return FColor(128, 192, 128); } +}; + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Factories/AkJsonFactory.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Factories/AkJsonFactory.cpp new file mode 100644 index 0000000..b334308 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Factories/AkJsonFactory.cpp @@ -0,0 +1,65 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkJsonFactory.cpp: +=============================================================================*/ +#include "Factories/AkJsonFactory.h" + +#include "AkAudioEvent.h" +#include "AkSettings.h" +#include "AkUnrealHelper.h" +#include "Misc/Paths.h" + +/*------------------------------------------------------------------------------ + UAkJsonFactory. +------------------------------------------------------------------------------*/ +UAkJsonFactory::UAkJsonFactory(const class FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + SupportedClass = UAkAudioEvent::StaticClass(); + Formats.Add(TEXT("json;Audiokinetic SoundBank Metadata")); + bCreateNew = true; + bEditorImport = true; + ImportPriority = 101; +} + +UObject* UAkJsonFactory::FactoryCreateNew( UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn ) +{ + return nullptr; +} + +bool UAkJsonFactory::FactoryCanImport(const FString& Filename) +{ + //check extension + if (FPaths::GetExtension(Filename) == TEXT("json")) + { + const UAkSettings* AkSettings = GetDefault(); + + if (Filename.Contains(AkUnrealHelper::GetSoundBankDirectory())) + { + return true; + } + } + + return false; +} + +bool UAkJsonFactory::ShouldShowInNewMenu() const +{ + return false; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/GeneratedSoundBanksWarning.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/GeneratedSoundBanksWarning.cpp new file mode 100644 index 0000000..1978ecc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/GeneratedSoundBanksWarning.cpp @@ -0,0 +1,108 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ + SGeneratedSoundBanks.cpp +------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------ + includes. +------------------------------------------------------------------------------------*/ +#include "GeneratedSoundBanksWarning.h" +#include "Async/Async.h" +#include "AkAudioStyle.h" +#include "AkSettings.h" +#include "AkUnrealHelper.h" +#include "IAudiokineticTools.h" +#include "DesktopPlatformModule.h" +#include "IDesktopPlatform.h" +#include "Framework/Application/SlateApplication.h" +#include "Framework/Notifications/NotificationManager.h" +#include "Widgets/Notifications/SNotificationList.h" + +#define LOCTEXT_NAMESPACE "AkAudio" + +TSharedPtr FGeneratedSoundBanksWarning::GeneratedSoundBanksWarning; + +FGeneratedSoundBanksWarning::FGeneratedSoundBanksWarning() +{ +} + +void FGeneratedSoundBanksWarning::DisplayGeneratedSoundBanksWarning() +{ + if (!FApp::CanEverRender()) + { + FString SoundBankDirectory = AkUnrealHelper::GetSoundBankDirectory(); + UE_LOG(LogAudiokineticTools, Warning, TEXT("Couldn't find GeneratedSoundBanks info at path: \n%s"), *SoundBankDirectory); + return; + } + AsyncTask(ENamedThreads::Type::GameThread, [this] + { + FString SoundBankDirectory = AkUnrealHelper::GetSoundBankDirectory(); + FText InfoString = FText::FormatOrdered(LOCTEXT("GeneratedSoundBanksWarning", "Couldn't find GeneratedSoundBanks info at path: \n{0}\nSet the GeneratedSoundBanks folder?"), FText::FromString(SoundBankDirectory)); + FSimpleDelegate SetGeneratedSoundBanksPathDelegate = FSimpleDelegate(); + SetGeneratedSoundBanksPathDelegate.BindRaw(this, &FGeneratedSoundBanksWarning::OpenSettingsMenu); + + FSimpleDelegate HideDelegate = FSimpleDelegate(); + HideDelegate.BindRaw(this, &FGeneratedSoundBanksWarning::HideGeneratedSoundBanksNotification); + FNotificationButtonInfo SetGeneratedSoundBanksPathButton = FNotificationButtonInfo(LOCTEXT("WwiseOpenGeneratedSoundbanks", "Set GeneratedSoundBanks Path"), FText(), SetGeneratedSoundBanksPathDelegate); + FNotificationButtonInfo DismissButton = FNotificationButtonInfo(LOCTEXT("WwiseDismiss", "Dismiss"), FText(), HideDelegate); + FNotificationInfo Info(InfoString); + Info.WidthOverride = 400; + Info.ButtonDetails.Add(SetGeneratedSoundBanksPathButton); + Info.ButtonDetails.Add(DismissButton); + Info.Image = FAkAudioStyle::GetBrush(TEXT("AudiokineticTools.AkBrowserTabIcon")); + Info.bUseSuccessFailIcons = false; + Info.FadeOutDuration = 0.5f; + Info.ExpireDuration = 10.0f; + + GeneratedSoundBanksWarning = FSlateNotificationManager::Get().AddNotification(Info); + + if (GeneratedSoundBanksWarning.IsValid()) + { + GeneratedSoundBanksWarning->SetCompletionState(SNotificationItem::CS_Pending); + } + }); +} + +void FGeneratedSoundBanksWarning::HideGeneratedSoundBanksNotification() +{ + AsyncTask(ENamedThreads::Type::GameThread, [this] + { + if (GeneratedSoundBanksWarning) + { + GeneratedSoundBanksWarning->Fadeout(); + } + }); +} + +void FGeneratedSoundBanksWarning::OpenSettingsMenu() +{ + const void* ParentWindowHandle = FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr); + const FString Title = LOCTEXT("GeneratedSoundBanksFolderSelector", "Select the folder containing the Generated SoundBanks info").ToString(); + FString Output; + bool bSuccess = FDesktopPlatformModule::Get()->OpenDirectoryDialog(ParentWindowHandle, Title, AkUnrealHelper::GetContentDirectory(), Output); + if(bSuccess) + { + UAkSettings* Settings = GetMutableDefault(); + if(Settings->UpdateGeneratedSoundBanksPath(Output)) + { + Settings->SaveConfig(); + } + } +} +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/ReloadPopup.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/ReloadPopup.cpp new file mode 100644 index 0000000..ebc88cc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/ReloadPopup.cpp @@ -0,0 +1,98 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "ReloadPopup.h" + +#include "AkAudioStyle.h" +#include "AkAudioModule.h" +#include "Async/Async.h" +#include "Framework/Notifications/NotificationManager.h" +#include "Widgets/Notifications/SNotificationList.h" + +#define LOCTEXT_NAMESPACE "AkAudio" + +TSharedPtr FReloadPopup::RefreshNotificationItem; + +FReloadPopup::FReloadPopup() +{ + +} + +void FReloadPopup::NotifyProjectRefresh() +{ + if (!FApp::CanEverRender()) + { + return; + } + AsyncTask(ENamedThreads::Type::GameThread, [this] + { + FText InfoString = LOCTEXT("ReloadPopupRefreshData", "Wwise project database was updated.\nReload Wwise Asset Data?"); + FSimpleDelegate RefreshDelegate = FSimpleDelegate(); + RefreshDelegate.BindRaw(this, &FReloadPopup::Reload); + + FSimpleDelegate HideDelegate = FSimpleDelegate(); + HideDelegate.BindRaw(this, &FReloadPopup::HideRefreshNotification); + FNotificationButtonInfo RefreshButton = FNotificationButtonInfo(LOCTEXT("WwiseDataRefresh", "Refresh"), FText(), RefreshDelegate); + FNotificationButtonInfo HideButton = FNotificationButtonInfo(LOCTEXT("WwiseDataNotNow", "Not Now"), FText(), HideDelegate); + FNotificationInfo Info(InfoString); + Info.ButtonDetails.Add(RefreshButton); + Info.ButtonDetails.Add(HideButton); + Info.Image = FAkAudioStyle::GetBrush(TEXT("AudiokineticTools.AkBrowserTabIcon")); + Info.bUseSuccessFailIcons = false; + Info.FadeOutDuration = 0.5f; + Info.ExpireDuration = 10.0f; + + RefreshNotificationItem = FSlateNotificationManager::Get().AddNotification(Info); + + if (RefreshNotificationItem.IsValid()) + { + RefreshNotificationItem->SetCompletionState(SNotificationItem::CS_Pending); + } + }); +} + +void FReloadPopup::Reload() +{ + if (!FApp::CanEverRender()) + { + return; + } + AsyncTask(ENamedThreads::Type::GameThread, [this] + { + FAkAudioModule::AkAudioModuleInstance->ReloadWwiseAssetData(); + if (RefreshNotificationItem) + { + RefreshNotificationItem->Fadeout(); + } + }); +} + +void FReloadPopup::HideRefreshNotification() +{ + if (!FApp::CanEverRender()) + { + return; + } + AsyncTask(ENamedThreads::Type::GameThread, [this] + { + if (RefreshNotificationItem) + { + RefreshNotificationItem->Fadeout(); + } + }); +} +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Sequencer/MovieSceneAkAudioEventTrackEditor.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Sequencer/MovieSceneAkAudioEventTrackEditor.cpp new file mode 100644 index 0000000..4b621f0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Sequencer/MovieSceneAkAudioEventTrackEditor.cpp @@ -0,0 +1,1095 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "MovieSceneAkAudioEventTrackEditor.h" + +#include "AkAudioBankGenerationHelpers.h" +#include "AkAudioDevice.h" +#include "AkAudioStyle.h" +#include "AkWaapiClient.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "Async/Async.h" +#include "CommonMovieSceneTools.h" +#include "ContentBrowserModule.h" +#include "Framework/Application/SlateApplication.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "AssetRegistry/IAssetRegistry.h" +#include "IAudiokineticTools.h" +#include "IContentBrowserSingleton.h" +#include "ISequencerSection.h" +#include "MovieScene.h" +#include "MovieSceneAkAudioEventSection.h" +#include "MovieSceneAkAudioEventTrack.h" +#include "MovieSceneCommonHelpers.h" +#include "Rendering/DrawElements.h" +#include "RenderUtils.h" +#include "ScopedTransaction.h" +#include "SequencerSectionPainter.h" +#include "SequencerUtilities.h" +#include "Slate/SlateTextures.h" +#include "Textures/SlateTextureData.h" +#include "Wwise/WwiseProjectDatabaseDelegates.h" + +#define LOCTEXT_NAMESPACE "MovieSceneAkAudioEventTrackEditor" + +/** A viewport that maintains a waveform texture for a Wwise event. */ +class AkAudioWaveformViewport + : public ISlateViewport +{ +public: + /** Represents a waveform texture with a given size that covers a given time range. */ + struct WaveformDescriptor + { + WaveformDescriptor() + { + TimeRangeInView = TRange(0.0f, 0.0f); + TextureSize = FVector2D(0.0f, 0.0f); + } + + WaveformDescriptor(TRange in_TimeRangeInView, FVector2D in_TextureSize) + : TimeRangeInView(in_TimeRangeInView) + , TextureSize(in_TextureSize) + {} + + WaveformDescriptor(const WaveformDescriptor& in_Other) + : TimeRangeInView(in_Other.TimeRangeInView) + , TextureSize(in_Other.TextureSize) + {} + + bool Equals(const WaveformDescriptor& in_Other) + { + if (!FMath::IsNearlyEqual(TimeRangeInView.GetLowerBoundValue(), in_Other.TimeRangeInView.GetLowerBoundValue(), 0.01f)) + return false; + if (!FMath::IsNearlyEqual(TimeRangeInView.GetUpperBoundValue(), in_Other.TimeRangeInView.GetUpperBoundValue(), 0.01f)) + return false; + if (!FMath::IsNearlyEqual(TextureSize.X, in_Other.TextureSize.X, 0.01f) || !FMath::IsNearlyEqual(TextureSize.Y, in_Other.TextureSize.Y, 0.01f)) + return false; + return true; + } + + /** The range of time within the AK audio event section that can be seen within the view. */ + TRange TimeRangeInView; + /** The size of the waveform texture. */ + FVector2D TextureSize; + }; + + /** AkAudioWaveformViewport Constructor. */ + AkAudioWaveformViewport(UMovieSceneAkAudioEventSection& InSection, AkAudioWaveformViewport::WaveformDescriptor in_Descriptor, + int in_iNumPeaksRequired, int in_iLeftOverPixels, FTimeToPixel in_TimeToPixelConverter, + TArray in_aPeakDataCache, float InDisplayScale = 1.0f); + + ~AkAudioWaveformViewport() + { + if (ShouldRender()) + { + BeginReleaseResource(Texture); + FlushRenderingCommands(); + } + + if (Texture) + { + delete Texture; + } + } + + + /** The AK audio event section in the UE sequencer */ + UMovieSceneAkAudioEventSection& Section; + + /** The waveform texture is created with a cache of the latest peak data for the Wwise event. */ + TArrayPeakDataCache; + + /** The waveform texture */ + class FSlateTexture2DRHIRef* Texture; + + /** Represents a waveform texture with a given size that covers a given time range. */ + WaveformDescriptor Descriptor; + + /** The number of peaks required depends on the zoom level, the section length, and whether the section is clipped. + * If the section is clipped, we ask for some extra peaks beyond the clipped region (see FMovieSceneAkAudioEventSection::UpdatePeaks) + */ + int NumPeaksRequired = 0; + /** Pixels left over between end of smoothed waveform and end of waveform duration. */ + int LeftOverPixels = 0; + + /** Used to convert pixels to time (seconds) */ + FTimeToPixel TimeToPixelConverter; + + bool ShouldRender() const { return Descriptor.TextureSize.X > 0; } + + /** NumChannels should come from WAAPI data. */ + int NumChannels = 1; + + /** Amount by which waveform peaks are smoothed (SmoothingAmount pixels per peak) */ + static const int SmoothingAmount = 5; + + float DisplayScale = 1.0f; + + /* ISlateViewport interface */ + virtual FIntPoint GetSize() const override + { + return FIntPoint((int)Descriptor.TextureSize.X, (int)Descriptor.TextureSize.Y); + } + virtual class FSlateShaderResource* GetViewportRenderTargetTexture() const override; + virtual bool RequiresVsync() const override { return false; } + virtual bool AllowScaling() const override { return false; } + +private: + /** Generates the waveform texture data. The texture is generated as subsequent vertical pixel strips. + * Strips are first initialized (InitializeStrip()), then waveform peak data is used to generate waveform color + * (GenerateWaveformStrip()). Indicator lines are then generated which indicate whether or not the event is + * retriggered after it ends (GenerateIndicatorLine()). + * + * @param OutData - the texture data buffer to fill. + */ + void GenerateTextureData (TArray& OutData); + + /** Gets the interpolated min & max pair values between in_PeakIndex and the next peak index, according to pixel value in_XPosition. + * Num pixels per min & max pair in the audio peaks is controlled by SmoothingAmount. + */ + FVector2D GetInterpolatedMinMaxPeaks(int in_XPosition, int in_PeakIndex); + + /** Initializes a vertical strip in the texture data to 0. */ + void InitializeStrip(int32 X, int32 ChannelIndex, TArray& OutData); + /** Generates a white vertical strip in the texture. The bottom and top of the strip are determined by in_MinPeakValue and in_MaxPeakValue. */ + void GeneratePeakStrip(int32 X, int32 ChannelIndex, TArray& OutData, float in_MinPeakValue, float in_MaxPeakValue); + /** Generates a white vertical strip in the texture according to the X position. Returns true if X is within the waveform, false otherwise. */ + bool GenerateWaveformStrip(int32 X, int32 ChannelIndex, TArray& OutData); + /** Generates indicators for silence or retriggering in the event sectinon. For silence, a horizontal line is generated. For retriggering, diagonal lines are generated. */ + void GenerateIndicatorLine(int32 X, int32 ChannelIndex, TArray& OutData); + + /** Fills a pixel with white (if in_bDirty is false) or red (if in_bDirty is true) color. */ + void FillPixel (uint8* Pixel, bool in_bDirty, uint8 A = 255); + /** Sets a pixel's color and opacity to 0. */ + void EmptyPixel (uint8* Pixel); +}; + +class FSlateShaderResource* AkAudioWaveformViewport::GetViewportRenderTargetTexture() const { return Texture; } + +/** Lookup a pixel in the given data buffer based on the specified X and Y */ +uint8* LookupPixel(TArray& Data, int32 X, int32 YPos, int32 Width, int32 Height, int32 Channel, int32 NumChannels) +{ + int32 Y = Height - YPos - 1; + if (NumChannels == 2) + { + Y = Channel == 0 ? Height / 2 - YPos : Height / 2 + YPos; + } + + int32 Index = (Y * Width + X) * GPixelFormats[PF_B8G8R8A8].BlockBytes; + if (Index + 3 < Data.Num()) + return &Data[Index]; + UE_LOG(LogAudiokineticTools, Warning, TEXT("Array overrun in MovieSceneAkAudioEventTrackEditor!")); + return 0; +} + +AkAudioWaveformViewport::AkAudioWaveformViewport(UMovieSceneAkAudioEventSection& InSection, AkAudioWaveformViewport::WaveformDescriptor in_Descriptor, + int in_iNumPeaksRequired, int in_iLeftOverPixels, FTimeToPixel in_TimeToPixelConverter, + TArray in_aPeakDataCache, float InDisplayScale /* = 1.0f */) + : Section(InSection) + , PeakDataCache(in_aPeakDataCache) + , Texture(NULL) + , Descriptor(in_Descriptor) + , NumPeaksRequired(in_iNumPeaksRequired) + , LeftOverPixels(in_iLeftOverPixels) + , TimeToPixelConverter(in_TimeToPixelConverter) + , DisplayScale(InDisplayScale) +{ + if (ShouldRender()) + { + uint32 Size = GetSize().X * GetSize().Y * GPixelFormats[PF_B8G8R8A8].BlockBytes; + TArray RawData; + RawData.AddZeroed(Size); + + GenerateTextureData(RawData); + + FSlateTextureDataPtr BulkData(new FSlateTextureData(GetSize().X, GetSize().Y, GPixelFormats[PF_B8G8R8A8].BlockBytes, RawData)); + + Texture = new FSlateTexture2DRHIRef(GetSize().X, GetSize().Y, PF_B8G8R8A8, BulkData, TexCreate_Dynamic); + + BeginInitResource(Texture); + } +} + +void AkAudioWaveformViewport::EmptyPixel(uint8* Pixel) +{ + *Pixel = (uint8)0; + *(Pixel + 1) = (uint8)0; + *(Pixel + 2) = (uint8)0; + *(Pixel + 3) = (uint8)0; +} + +void AkAudioWaveformViewport::FillPixel(uint8* Pixel, bool in_bDirty, uint8 A /*= 255*/) +{ + float AlphaPreMultiply = (float)A / 255.0f; + *Pixel = (uint8)((in_bDirty ? 50 : 130) * AlphaPreMultiply); //B + *(Pixel + 1) = (uint8)((in_bDirty ? 50 : 130) * AlphaPreMultiply); //G + *(Pixel + 2) = (uint8)((in_bDirty ? 130 : 130) * AlphaPreMultiply); //R + *(Pixel + 3) = A; +} + +/** Initializes a vertical strip in the texture data to 0. */ +void AkAudioWaveformViewport::InitializeStrip(int32 X, int32 ChannelIndex, TArray& OutData) +{ + int32 Width = (int32)GetSize().X; + int32 Height = (int32)GetSize().Y; + int32 MaxAmplitude = (int32)(Height * DisplayScale); + for (int32 PixelIndex = 0; PixelIndex < MaxAmplitude; ++PixelIndex) + { + uint8* Pixel = LookupPixel(OutData, X, PixelIndex, Width, Height, ChannelIndex, NumChannels); + EmptyPixel(Pixel); + } +} + +/** Generates a white vertical strip in the texture. The bottom and top of the strip are determined by in_MinPeakValue and in_MaxPeakValue. */ +void AkAudioWaveformViewport::GeneratePeakStrip(int32 X, int32 ChannelIndex, TArray& OutData, float in_MinPeakValue, float in_MaxPeakValue) +{ + const int Height = GetSize().Y; + const int ZeroLine = Height / 2; + const float AbsMaxAmp = Height * DisplayScale * 0.5f; + const int32 MinPixelIndex = ZeroLine + (int32)(in_MinPeakValue * AbsMaxAmp); + const int32 MaxPixelIndex = ZeroLine + (int32)(in_MaxPeakValue * AbsMaxAmp); + for (int32 PixelIndex = 0; PixelIndex < Height; ++PixelIndex) + { + uint8* Pixel = LookupPixel(OutData, X, PixelIndex, GetSize().X, Height, ChannelIndex, NumChannels); + if (PixelIndex >= MinPixelIndex && PixelIndex <= MaxPixelIndex) + { + FillPixel(Pixel, Section.EventTracker->IsDirty); + } + } +} + +/** Gets the interpolated min & max pair values between in_PeakIndex and the next peak index, according to pixel value in_XPosition. + * Num pixels per min & max pair in the audio peaks is controlled by SmoothingAmount. + */ +FVector2D AkAudioWaveformViewport::GetInterpolatedMinMaxPeaks(int in_XPosition, int in_PeakIndex) +{ + const int NumPeaks = PeakDataCache.Num() / 2; + + const float CurrentMinPeak = (float)PeakDataCache[in_PeakIndex * 2]; + const float CurrentMaxPeak = (float)PeakDataCache[in_PeakIndex * 2 + 1]; + float MinPeakValue = CurrentMinPeak; + float MaxPeakValue = CurrentMaxPeak; + if (in_PeakIndex < NumPeaks - 1) + { + const float NextMinPeak = (float)PeakDataCache[(in_PeakIndex + 1) * 2]; + const float NextMaxPeak = (float)PeakDataCache[(in_PeakIndex + 1) * 2 + 1]; + const float ModX = FMath::Fmod((float)in_XPosition, SmoothingAmount); + const float SmoothedSlope = FMath::SmoothStep(0.0f, 1.0f, ModX / (float)SmoothingAmount); + MinPeakValue = CurrentMinPeak + (NextMinPeak - CurrentMinPeak) * SmoothedSlope; + MaxPeakValue = CurrentMaxPeak + (NextMaxPeak - CurrentMaxPeak) * SmoothedSlope; + } + return FVector2D(MinPeakValue, MaxPeakValue); +} + +/** Generates indicators for silence or retriggering in the event sectinon. For silence, a horizontal line is generated. For retriggering, diagonal lines are generated. */ +void AkAudioWaveformViewport::GenerateIndicatorLine(int32 X, int32 ChannelIndex, TArray& OutData) +{ + auto iSizeY = GetSize().Y; + int32 iLineY = Section.DoesEventRetrigger() ? FMath::Fmod((float)X, (float)iSizeY) : iSizeY / 2; + int32 iLineHeight = Section.DoesEventRetrigger() ? 2 : 1; + for (int32 i = 0; i < iLineHeight; ++i) + { + int32 Y = iLineY - i; + if (Y >= 0 && Y < GetSize().Y && X > 0 && X < GetSize().X) + { + uint8* Pixel = LookupPixel(OutData, X, Y, GetSize().X, iSizeY, ChannelIndex, NumChannels); + FillPixel(Pixel, false); + } + } +} + +/** Generates a white vertical strip in the texture according to the X position. */ +bool AkAudioWaveformViewport::GenerateWaveformStrip(int32 X, int32 ChannelIndex, TArray& OutData) +{ + const int NumPeaks = PeakDataCache.Num() / 2; + if (NumPeaks > 0) + { + const int NumPixelsPerPeak = ((NumPeaksRequired - NumPeaks) / NumPeaks) + 1; + const int PeakIndex = (X / NumPixelsPerPeak) / SmoothingAmount; + if (PeakIndex < NumPeaks) + { + FVector2D MinMaxPeaks = GetInterpolatedMinMaxPeaks(X, PeakIndex); + GeneratePeakStrip(X, ChannelIndex, OutData, MinMaxPeaks.X, MinMaxPeaks.Y); + return true; + } + } + return false; +} + +/** Generates the waveform texture data. The texture is generated as subsequent vertical pixel strips. + * Strips are first initialized (InitializeStrip()), then waveform peak data is used to generate waveform color + * (GenerateWaveformStrip()), then vertical delimitars are drawn at the beginnings and ends of event retriggers + * (CheckEventEndStart()). + * + * @param OutData - the texture data buffer to fill. + */ +void AkAudioWaveformViewport::GenerateTextureData(TArray& OutData) +{ + //Draw the longest waveform in the event, then leave a space for the event duration, then indicate retrigger or silence. + int32 iXPosition = 0; + if (NumPeaksRequired > 0) + { + bool bDrawingWaveform = true; + while (iXPosition < GetSize().X && bDrawingWaveform) + { + for (int32 ChannelIndex = 0; ChannelIndex < NumChannels; ++ChannelIndex) + { + InitializeStrip(iXPosition, ChannelIndex, OutData); + bDrawingWaveform = GenerateWaveformStrip(iXPosition, ChannelIndex, OutData); + if (!bDrawingWaveform) + break; + } + if (bDrawingWaveform) + ++iXPosition; + } + /* In the case where we have some pixels left over at the end of the smoothed waveform, we fill the zero line. */ + const int32 SourceEnd = iXPosition + LeftOverPixels; + for (int32 iLeftOver = iXPosition; iLeftOver < SourceEnd; ++iLeftOver) + { + if (iLeftOver >= GetSize().X) + break; + for (int32 ChannelIndex = 0; ChannelIndex < NumChannels; ++ChannelIndex) + { + uint8* Pixel = LookupPixel(OutData, iLeftOver, GetSize().Y / 2, GetSize().X, GetSize().Y, ChannelIndex, NumChannels); + FillPixel(Pixel, Section.EventTracker->IsDirty); + } + } + } + /* Generate indicator lines (flat horizontal line for silence, repeating diagonal lines for retrigger). This starts after the duration of the Wwise event. */ + const int EventDurationEndPixel = AkMax(0, TimeToPixelConverter.SecondsToPixel(Section.GetStartTime() + Section.GetEventDuration().GetUpperBoundValue())); + const int ClippedEventDurationEndPixel = AkMax(0, EventDurationEndPixel - TimeToPixelConverter.SecondsToPixel(Descriptor.TimeRangeInView.GetLowerBoundValue())); + if (ClippedEventDurationEndPixel >= 0 && ClippedEventDurationEndPixel < GetSize().X && iXPosition < GetSize().X) + { + for (int32 X = ClippedEventDurationEndPixel; X < GetSize().X; ++X) + { + for (int32 ChannelIndex = 0; ChannelIndex < NumChannels; ++ChannelIndex) + { + GenerateIndicatorLine(X, ChannelIndex, OutData); + } + } + } +} + +/** + * Class that draws a transform section in the sequencer. + * This class maintains an AkAudioWaveformViewport and various properties about the AK audio event section within the UE sequencer. + * When any of these properties change, the AkAudioWaveformViewport is regenerated. + */ +class FMovieSceneAkAudioEventSection + : public ISequencerSection +{ +public: + + FMovieSceneAkAudioEventSection(UMovieSceneSection& InSection, TWeakPtr InSequencer) + : Section(Cast(&InSection)) + , Sequencer(InSequencer) + { + PeakDataCache.Empty(); + + if (FAkWaapiClient::Get()) + { + /* Waveform upates depend on WAAPI connection and which project is loaded. */ + ShouldUpdateWaveform = WaapiReconnected = FAkWaapiClient::IsProjectLoaded(); + /* When the correct project is loaded (and we get the project loaded callback) we resume waveform updates. */ + ProjectLoadedHandle = FAkWaapiClient::Get()->OnProjectLoaded.AddLambda([this] + { + ResumeWaveformUpdates(); + }); + /* Stop waveform updates when connection is lost (this is also called when a different project is loaded). */ + ConnectionLostHandle = FAkWaapiClient::Get()->OnConnectionLost.AddLambda([this] + { + StopWaveformUpdates(); + }); + } + + /* When SoundBanks are loaded, we need to update the section's source info, in case Event assets or metdata have changed */ + if (FAkAudioDevice::Get()) + { + SoundbanksLoadedHandle = FWwiseProjectDatabaseDelegates::Get().GetOnDatabaseUpdateCompletedDelegate().AddLambda([this]() + { + if (Section != nullptr && Section->EventTracker.IsValid()) + { + Section->UpdateAudioSourceInfo(); + StoredEventName = Section->GetEventName(); + } + }); + } + } + + ~FMovieSceneAkAudioEventSection() + { + auto pAudioDevice = FAkAudioDevice::Get(); + if (pAudioDevice != nullptr) + WwiseEventTriggering::StopAllPlayingIDs(pAudioDevice, *Section->EventTracker); + + if (ProjectLoadedHandle.IsValid()) + { + FAkWaapiClient::Get()->OnProjectLoaded.Remove(ProjectLoadedHandle); + ProjectLoadedHandle.Reset(); + } + if (ConnectionLostHandle.IsValid()) + { + FAkWaapiClient::Get()->OnConnectionLost.Remove(ConnectionLostHandle); + ConnectionLostHandle.Reset(); + } + if (SoundbanksLoadedHandle.IsValid()) + { + FWwiseProjectDatabaseDelegates::Get().GetOnDatabaseUpdateCompletedDelegate().Remove(SoundbanksLoadedHandle); + } + + // Wait for any get peak tasks to finish. + FScopeLock Lock(&FetchPeaksSection); + while (PeakDataUpdating) {} + } +public: + + // ISequencerSection interface + + virtual UMovieSceneSection* GetSectionObject() override { return Section; } + + virtual FText GetSectionTitle() const override + { + if (Section != nullptr) + { + FString name = Section->GetEventName(); + if (name.IsEmpty()) + { + name = TEXT("(None)"); + } + if (Section->EventTracker->IsDirty) + { + name += "*"; + } + return FText::FromString(name); + } + return FText::GetEmpty(); + } + + virtual int32 OnPaintSection(FSequencerSectionPainter& InPainter) const override + { + int32 LayerId = InPainter.PaintSectionBackground(); + + if (WaveformViewport.IsValid() && WaveformViewport->ShouldRender()) + { + FSlateLayoutTransform t = FSlateLayoutTransform(1.0f, FVector2D(PixelOffsetLeft, 0.0f)); + + FSlateDrawElement::MakeViewport( + InPainter.DrawElements, + LayerId++, + InPainter.SectionGeometry.ToPaintGeometry(t), + WaveformViewport, + (InPainter.bParentEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect) | ESlateDrawEffect::NoGamma, + FLinearColor::White + ); + } + + return LayerId; + } + + /** + * Builds up the section context menu for the outliner + * + * @param MenuBuilder The menu builder to change + * @param ObjectBinding The object guid bound to this section + */ + virtual void BuildSectionContextMenu(FMenuBuilder& MenuBuilder, const FGuid& ObjectBinding) override + { + MenuBuilder.BeginSection("Audiokinetic", LOCTEXT("AKMenu", "Audiokinetic")); + { + + MenuBuilder.AddMenuEntry( + LOCTEXT("SnapLength", "Match section length to Wwise event length"), + LOCTEXT("SnapLengthTooltip", "Set the section length to equal the maximum possible duration of the Wwise event."), + FSlateIcon(), + FUIAction( + FExecuteAction::CreateLambda([=] { return SnapSectionLengthToEventLength(); }), + FCanExecuteAction::CreateLambda([=] { return Section != nullptr; }) + ), + NAME_None, + EUserInterfaceActionType::Button + ); + } + MenuBuilder.EndSection();//Audiokinetic + } + +private: + /* Callback handles. */ + FDelegateHandle ProjectLoadedHandle; + FDelegateHandle ConnectionLostHandle; + FDelegateHandle SoundbanksLoadedHandle; + + /** The section we are visualizing */ + UMovieSceneAkAudioEventSection* Section; + /** The peak data that we use to construct the waveform texture. + * This comes from the Section's peak data, and is copied during the UpdatePeaks function. + */ + TArray PeakDataCache; + + /** The waveform viewport render object. */ + TSharedPtr WaveformViewport; + + /* Stored data to determine when the waveform is invalidated. */ + AkAudioWaveformViewport::WaveformDescriptor StoredWaveformDescriptor; + float StoredPixelsPerSecond = 0.0f; + bool StoredEventRetriggers = false; + float StoredPixelWidth = 0.0f; + float StoredStartTime = 0.0f; + bool StoredSectionIsDirty = false; + FString StoredEventName = ""; + + /** Indicates where the audio data should be requested and the waveform re-created on tick when new data is available. + * Should be false when there is no connection to WAAPI. + */ + bool ShouldUpdateWaveform = false; + + /** This flag is used to update the peaks data and the texture when the WAAPI connection is re-established (or started). + */ + bool WaapiReconnected = false; + + FCriticalSection FetchPeaksSection; + FThreadSafeBool PeakDataUpdating = false; + FThreadSafeBool WaveformNeedsUpdate = false; + + /** The amount of pixels to offset the waveform texture by (if any part of it is clipped beyond the left of the editor view). */ + float PixelOffsetLeft = 0.0f; + /** The number of pixels "left over" at the end of the smoothed waveform. This will be between 0 and AkAudioWaveformViewport::SmoothingAmount. */ + int LeftOverPixels = 0; + /** This depends on the zoom level, as well as the position and length of the section*/ + int NumPeaksRequired = 0; + + TWeakPtr Sequencer; + + /** Sets the length of the section to equal the duration of the Wwise event. */ + void SnapSectionLengthToEventLength() + { + if (Section != nullptr) + { + Section->MatchSectionLengthToEventLength(); + } + } + + /** Creates the waveform viewport according to the waveform descriptor and the TimeToPixelConverter. */ + void CreateWaveformViewport(FTimeToPixel in_TimeToPixelConverter, AkAudioWaveformViewport::WaveformDescriptor in_Descriptor) + { + if (in_Descriptor.TimeRangeInView.IsDegenerate() || in_Descriptor.TimeRangeInView.IsEmpty()) + WaveformViewport.Reset(); + else + { + WaveformViewport = MakeShareable(new AkAudioWaveformViewport(*Section, in_Descriptor, NumPeaksRequired, LeftOverPixels, in_TimeToPixelConverter, PeakDataCache)); + WaveformNeedsUpdate = false; + } + } + + /** Regenerates the waveform viewport and updates stored data about the waveform, section and event. */ + void RegenerateWaveform(FTimeToPixel in_TimeToPixelConverter, AkAudioWaveformViewport::WaveformDescriptor in_Descriptor) + { + CreateWaveformViewport(in_TimeToPixelConverter, in_Descriptor); + + StoredWaveformDescriptor = AkAudioWaveformViewport::WaveformDescriptor(in_Descriptor); + StoredEventRetriggers = Section->DoesEventRetrigger(); + + if (Section->EventTracker.IsValid()) + { + StoredSectionIsDirty = Section->EventTracker->IsDirty; + } + } + + /** Calculates the required number of peaks according to the section's position and length, and the zoom level (captured in in_TimeToPixelConverter). + * In a background thread, the section updates its peak data and it is then copied to the peak data cache that is used to generate the waveform viewport. + */ + void UpdatePeaks(AkAudioWaveformViewport::WaveformDescriptor in_Descriptor, FTimeToPixel in_TimeToPixelConverter) + { + const float dTrimmedSourceDuration = Section->GetMaxSourceDuration(); + const double dSectionStart = (double)Section->GetStartTime(); + /* Get the timeFrom and timeTo of the portion of the source that is in view, relative to the sequencer timeline. */ + double dSequencerTimeFrom = AkMin((double)in_Descriptor.TimeRangeInView.GetLowerBoundValue(), dSectionStart + (double)dTrimmedSourceDuration); + double dSequencerTimeTo = AkMin(dSectionStart + (double)dTrimmedSourceDuration, (double)in_Descriptor.TimeRangeInView.GetUpperBoundValue()); + /* Convert to Pixels. */ + const int NumPixelsInWaveform = in_TimeToPixelConverter.SecondsToPixel((float)dSequencerTimeTo) - in_TimeToPixelConverter.SecondsToPixel((float)dSequencerTimeFrom); + /* Get the timeFrom and timeTo values relative to the source (subtract section start time and add trim begin). */ + const double dTrimmedSourceTimeFrom = dSequencerTimeFrom - dSectionStart + Section->GetTrimBegin(); + double dTrimmedSourceTimeTo = dSequencerTimeTo - dSectionStart + Section->GetTrimBegin(); + + /* The extra peak data is for the final pixels, to ensure there is not an empty space of + * (PixelsInWaveform - (NumPeaks * (PixelsInWaveform / SmoothingAmount))) + * pixels at the end of the section. + */ + const int numExtraPeaks = 2; + NumPeaksRequired = (NumPixelsInWaveform / AkAudioWaveformViewport::SmoothingAmount) + numExtraPeaks; + LeftOverPixels = AkMax(NumPixelsInWaveform - NumPeaksRequired * AkAudioWaveformViewport::SmoothingAmount, 0); + if (NumPeaksRequired > 0 && (float)dTrimmedSourceTimeFrom < (float)dTrimmedSourceTimeTo) + { + PeakDataUpdating = true; + AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this, dTrimmedSourceTimeFrom, dTrimmedSourceTimeTo, in_Descriptor]() + { + FScopeLock Lock(&FetchPeaksSection); + Section->UpdateAudioSourcePeaks(NumPeaksRequired, dTrimmedSourceTimeFrom, dTrimmedSourceTimeTo); + Section->RequiresUpdate = false; + auto newPeakData = Section->GetAudioSourcePeaks(); + PeakDataCache.Empty(); + PeakDataCache.SetNum(newPeakData.Num()); + memcpy(PeakDataCache.GetData(), newPeakData.GetData(), sizeof(double) * newPeakData.Num()); + WaveformNeedsUpdate = true; + PeakDataUpdating = false; + }); + } + } + + FTimeToPixel CreateTimeToPixelConverter(const FGeometry& AllottedGeometry, float& out_PixelsPerSecond) + { + TRange ViewRange(Section->GetStartTime(), Section->GetEndTime()); + double VisibleWidth = FMath::Min(1.0, ViewRange.Size()); + out_PixelsPerSecond = AllottedGeometry.GetLocalSize().X / VisibleWidth; + + FFrameRate TickResolution = Section->GetTypedOuter()->GetTickResolution(); + FTimeToPixel TimeToPixelConverter(AllottedGeometry, ViewRange, TickResolution); + return TimeToPixelConverter; + } + + /** Each tick we check whether any of the stored data about the section, event, or waveform has changed such that we need to update */ + void Tick(const FGeometry& AllottedGeometry, const FGeometry& ParentGeometry, const double InCurrentTime, const float InDeltaTime) override + { + UMovieSceneTrack* Track = Section->GetTypedOuter(); + + if (Track && ShouldUpdateWaveform) + { + if (Section->InitState == AkEventSectionState::EInitialized) + { + if (StoredEventName == "") + { + StoredEventName = Section->GetEventName(); + } + else if (StoredEventName != Section->GetEventName()) + { + /** In the case where the event target for a section is changed, we need to ensure UpdateAudioSourceInfo is called before UpdateAudioSourcePeaks, + * otherwise we'll try to call update peaks with invalid audio source info. + */ + Section->UpdateAudioSourceInfo(); + StoredEventName = Section->GetEventName(); + } + + if (Section->AudioSourceInfoIsValid()) + { + /** Pass on the new ScrubTailLength to the underlying section, if it has been changed via the Editor UI. */ + if (Section->EventTracker->ScrubTailLengthMs != Section->GetScrubTailLength()) + Section->EventTracker->ScrubTailLengthMs = Section->GetScrubTailLength(); + + auto Concatenated = Concatenate(ParentGeometry.GetAccumulatedLayoutTransform(), AllottedGeometry.GetAccumulatedLayoutTransform().Inverse()); + const FSlateRect ParentRect = TransformRect(Concatenated, FSlateRect(FVector2D(0, 0), ParentGeometry.GetLocalSize())); + + const float LeftMostVisiblePixel = FMath::Max(ParentRect.Left, 0.f); + const float RightMostVisiblePixel = FMath::Min(ParentRect.Right, AllottedGeometry.GetLocalSize().X); + + /** If the section is in view ... */ + if (RightMostVisiblePixel > LeftMostVisiblePixel) + { + float PixelsPerSecond; + FTimeToPixel TimeToPixelConverter = CreateTimeToPixelConverter(AllottedGeometry, PixelsPerSecond); + TRange TimeRangeInView = TRange(TimeToPixelConverter.PixelToSeconds(LeftMostVisiblePixel), + TimeToPixelConverter.PixelToSeconds(RightMostVisiblePixel)); + + PixelOffsetLeft = LeftMostVisiblePixel; + + FVector2D TextureSize((float)(RightMostVisiblePixel - LeftMostVisiblePixel), (float)AllottedGeometry.GetLocalSize().Y); + + AkAudioWaveformViewport::WaveformDescriptor Descriptor(TimeRangeInView, TextureSize); + + /** Check if the peak range has changed. + * This occurs when the texture size changes, or when the section is + * clipped beyond the left of the sequencer view (i.e. it's start time is out of view) + * and it has been dragged. + */ + const bool peakRangeChanged = (StoredPixelWidth != TextureSize.X) || (ParentRect.Left > 0.0f && StoredStartTime != Section->GetStartTime()); + + if (StoredPixelsPerSecond != PixelsPerSecond || Section->RequiresUpdate || WaapiReconnected || peakRangeChanged) + { + StoredPixelWidth = TextureSize.X; + StoredStartTime = Section->GetStartTime(); + /** Update the peak data if it is not currently being updated, and if the duration in view is sufficiently large. */ + const float minimumDuration = 0.01f; + const float durationInView = TimeRangeInView.GetUpperBoundValue() - TimeRangeInView.GetLowerBoundValue(); + if (!PeakDataUpdating && durationInView > minimumDuration && Descriptor.TextureSize.Y > 0.0f) + { + UpdatePeaks(Descriptor, TimeToPixelConverter); + StoredPixelsPerSecond = PixelsPerSecond; + } + if (WaapiReconnected) + WaapiReconnected = false; + } + + bool WaveformUpdateRequired = WaveformNeedsUpdate || + ((!StoredWaveformDescriptor.Equals(Descriptor)) && Descriptor.TextureSize.Y > 0.0f) || + StoredEventRetriggers != Section->DoesEventRetrigger(); + if (Section->EventTracker.IsValid()) + { + WaveformUpdateRequired |= StoredSectionIsDirty != Section->EventTracker->IsDirty; + } + if (WaveformUpdateRequired) + { + if (!PeakDataUpdating) + RegenerateWaveform(TimeToPixelConverter, Descriptor); + } + } + } + else + { + if (PeakDataCache.Num() > 0) + { + FScopeLock Lock(&FetchPeaksSection); + PeakDataCache.Empty(); + StoredWaveformDescriptor = AkAudioWaveformViewport::WaveformDescriptor(); + FTimeToPixel TempTimeToPixel(FGeometry(), TRange(0.0f, 0.0f), FFrameRate()); + RegenerateWaveform(TempTimeToPixel, StoredWaveformDescriptor); + } + } + } + else + { + Section->Initialize(); + } + } + else + { + WaveformViewport.Reset(); + StoredWaveformDescriptor = AkAudioWaveformViewport::WaveformDescriptor(); + } + } + + void ResumeWaveformUpdates() + { + Section->UpdateAudioSourceInfo(); + StoredEventName = Section->GetEventName(); + WaapiReconnected = true; + ShouldUpdateWaveform = true; + } + + void StopWaveformUpdates() + { + ShouldUpdateWaveform = false; + Section->InvalidateAudioSourceInfo(); + } +}; + + +FMovieSceneAkAudioEventTrackEditor::FMovieSceneAkAudioEventTrackEditor(TSharedRef InSequencer) + : FMovieSceneTrackEditor(InSequencer) +{ +} + +TSharedRef FMovieSceneAkAudioEventTrackEditor::CreateTrackEditor(TSharedRef InSequencer) +{ + return MakeShareable(new FMovieSceneAkAudioEventTrackEditor(InSequencer)); +} + +bool FMovieSceneAkAudioEventTrackEditor::SupportsType(TSubclassOf Type) const +{ + return Type == UMovieSceneAkAudioEventTrack::StaticClass(); +} + +bool FMovieSceneAkAudioEventTrackEditor::SupportsSequence(UMovieSceneSequence* InSequence) const +{ +#if UE_5_1_OR_LATER + static UClass* LevelSequenceClass = UClass::TryFindTypeSlow(TEXT("/Script/LevelSequence.LevelSequence"), EFindFirstObjectOptions::ExactClass); +#else + static UClass* LevelSequenceClass = FindObject(ANY_PACKAGE, TEXT("LevelSequence"), true); +#endif + return InSequence != nullptr && LevelSequenceClass != nullptr && InSequence->GetClass()->IsChildOf(LevelSequenceClass); +} + +void FMovieSceneAkAudioEventTrackEditor::BuildTrackContextMenu(FMenuBuilder& MenuBuilder, UMovieSceneTrack* Track) +{ + auto AkAudioEventTrack = Cast(Track); + + MenuBuilder.BeginSection("Audiokinetic", LOCTEXT("AKMenu", "Audiokinetic")); + { + MenuBuilder.AddMenuEntry( + LOCTEXT("RefreshAllWaveforms", "Save Wwise project and refresh all sections"), + LOCTEXT("RefreshAllWaveformsTooltip", "Saves the Wwise project, generates required soundbanks for all sections and refreshes all waveforms."), + FSlateIcon(), + FUIAction( + FExecuteAction::CreateLambda([=] { CreateGenerateSoundbanksWindowForAllSections(Track); }), + FCanExecuteAction::CreateLambda([=] + { + auto aSections = Track->GetAllSections(); + if (aSections.Num() <= 0) + return false; + for (auto pSection : aSections) + { + UMovieSceneAkAudioEventSection* pAkEventSection = dynamic_cast(pSection); + if (pAkEventSection != nullptr) + { + return true; + } + } + return false; + }) + ) + ); + } + MenuBuilder.EndSection();//Audiokinetic +} + +/** Creates a soundbank generation window. + */ +void FMovieSceneAkAudioEventTrackEditor::CreateGenerateSoundbanksWindowForAllSections(UMovieSceneTrack* in_pTrack) +{ + UMovieSceneAkAudioEventTrack* pAkEventTrack = dynamic_cast(in_pTrack); + if (pAkEventTrack != nullptr) + { + AkAudioBankGenerationHelper::CreateGenerateSoundDataWindow(true); + } +} + +TSharedRef FMovieSceneAkAudioEventTrackEditor::MakeSectionInterface(UMovieSceneSection& SectionObject, UMovieSceneTrack& Track, FGuid ObjectBinding) +{ + return MakeShareable(new FMovieSceneAkAudioEventSection(SectionObject, GetSequencer())); +} + +bool FMovieSceneAkAudioEventTrackEditor::HandleAssetAdded(UObject* Asset, const FGuid& TargetObjectGuid) +{ + if (Asset->IsA()) + { + if (!SupportsSequence(GetMovieSceneSequence())) + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("AkAudioEventTrack only supports Level Sequences")); + return false; + } + + auto Event = Cast(Asset); + if (!Event->IsDataFullyLoaded()) + { + Event->LoadEventDataForContentBrowserPreview(); + } + + if (TargetObjectGuid.IsValid()) + { + TArray> ObjectsToAttachTo; + for (TWeakObjectPtr<> Object : GetSequencer()->FindObjectsInCurrentSequence(TargetObjectGuid)) + ObjectsToAttachTo.Add(Object); + + auto AddNewAttachedSound = [Event, ObjectsToAttachTo, this](FFrameNumber KeyTime) + { + bool bHandleCreated = false; + bool bTrackCreated = false; + bool bTrackModified = false; + + for (int32 ObjectIndex = 0; ObjectIndex < ObjectsToAttachTo.Num(); ++ObjectIndex) + { + UObject* Object = ObjectsToAttachTo[ObjectIndex].Get(); + + FFindOrCreateHandleResult HandleResult = FindOrCreateHandleToObject(Object); + FGuid ObjectHandle = HandleResult.Handle; + bHandleCreated |= HandleResult.bWasCreated; + + if (ObjectHandle.IsValid()) + { + FFindOrCreateTrackResult TrackResult = FindOrCreateTrackForObject(ObjectHandle, UMovieSceneAkAudioEventTrack::StaticClass()); + bTrackCreated |= TrackResult.bWasCreated; + + if (ensure(TrackResult.Track)) + { + auto AudioTrack = Cast(TrackResult.Track); + AudioTrack->AddNewEvent(KeyTime, Event); + bTrackModified = true; + } + } + } + + FKeyPropertyResult Result; + Result.bTrackModified = bTrackModified; + Result.bHandleCreated = bHandleCreated; + Result.bTrackCreated = bTrackCreated; + return Result; + }; + + AnimatablePropertyChanged(FOnKeyProperty::CreateLambda(AddNewAttachedSound)); + } + else + { + auto AddNewMasterSound = [Event, this](FFrameNumber KeyTime) + { +#if UE_5_2_OR_LATER + auto TrackResult = FindOrCreateRootTrack(); +#else + auto TrackResult = FindOrCreateMasterTrack(); +#endif + UMovieSceneTrack* Track = TrackResult.Track; + + auto AkEventTrack = Cast(Track); + AkEventTrack->AddNewEvent(KeyTime, Event); + FKeyPropertyResult Result; + Result.bTrackCreated = TrackResult.bWasCreated; + Result.bTrackModified = true; + return Result; + }; + + AnimatablePropertyChanged(FOnKeyProperty::CreateLambda(AddNewMasterSound)); + } + + return true; + } + return false; +} + + +const FSlateBrush* FMovieSceneAkAudioEventTrackEditor::GetIconBrush() const +{ + return FAkAudioStyle::Get().GetBrush("AudiokineticTools.EventIcon"); +} + +void FMovieSceneAkAudioEventTrackEditor::BuildAddTrackMenu(FMenuBuilder& MenuBuilder) +{ + MenuBuilder.AddMenuEntry( + LOCTEXT("AddAkAudioEventTrack", "AkAudioEvent"), + LOCTEXT("AddAkAudioEventMasterTrackTooltip", "Adds a master AkAudioEvent track."), + FSlateIcon(FAkAudioStyle::GetStyleSetName(), "AudiokineticTools.EventIcon"), + FUIAction(FExecuteAction::CreateLambda([=] + { + auto FocusedMovieScene = GetFocusedMovieScene(); + + if (FocusedMovieScene == nullptr) + { + return; + } + + const FScopedTransaction Transaction(LOCTEXT("AddAkAudioEventMasterTrack_Transaction", "Add master AkAudioEvent Track")); + FocusedMovieScene->Modify(); + +#if UE_5_2_OR_LATER + auto NewTrack = FocusedMovieScene->AddTrack(); +#else + auto NewTrack = FocusedMovieScene->AddMasterTrack(); +#endif + ensure(NewTrack); + NewTrack->SetIsAMasterTrack(true); + + GetSequencer()->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded); + })) + ); +} + +TSharedPtr FMovieSceneAkAudioEventTrackEditor::BuildOutlinerEditWidget(const FGuid& ObjectBinding, UMovieSceneTrack* Track, const FBuildEditWidgetParams& Params) +{ + // Create a container edit box + return SNew(SHorizontalBox) + + // Add the audio combo box + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + FSequencerUtilities::MakeAddButton(LOCTEXT("AudioText", "AkAudioEvent"), FOnGetContent::CreateSP(this, &FMovieSceneAkAudioEventTrackEditor::BuildAudioSubMenu, Track), Params.NodeIsHovered, GetSequencer()) + ]; +} + +TSharedRef FMovieSceneAkAudioEventTrackEditor::BuildAudioSubMenu(UMovieSceneTrack* Track) +{ + static const FName AssetRegistryModuleName = TEXT("AssetRegistry"); + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(AssetRegistryModuleName); +#if UE_5_1_OR_LATER + TArray ClassPaths; + ClassPaths.Add(UAkAudioEvent::StaticClass()->GetClassPathName()); + TSet DerivedClassPaths; + AssetRegistryModule.Get().GetDerivedClassNames(ClassPaths, {}, DerivedClassPaths); +#else + TArray ClassNames; + ClassNames.Add(UAkAudioEvent::StaticClass()->GetFName()); + TSet DerivedClassNames; + AssetRegistryModule.Get().GetDerivedClassNames(ClassNames, {}, DerivedClassNames); +#endif + + FMenuBuilder MenuBuilder(true, nullptr); + + FAssetPickerConfig AssetPickerConfig; + { + AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateRaw(this, &FMovieSceneAkAudioEventTrackEditor::OnAudioAssetSelected, Track); + AssetPickerConfig.bAllowNullSelection = true; + AssetPickerConfig.InitialAssetViewType = EAssetViewType::List; +#if UE_5_1_OR_LATER + for (auto ClassPath : DerivedClassPaths) + { + AssetPickerConfig.Filter.ClassPaths.Add(ClassPath); + } +#else + for (auto ClassName : DerivedClassNames) + { + AssetPickerConfig.Filter.ClassNames.Add(ClassName); + } +#endif + } + + FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked(TEXT("ContentBrowser")); + + TSharedPtr MenuEntry = SNew(SBox) + .WidthOverride(300.0f) + .HeightOverride(300.f) + [ + ContentBrowserModule.Get().CreateAssetPicker(AssetPickerConfig) + ]; + + MenuBuilder.AddWidget(MenuEntry.ToSharedRef(), FText::GetEmpty(), true); + + return MenuBuilder.MakeWidget(); +} + + +void FMovieSceneAkAudioEventTrackEditor::OnAudioAssetSelected(const FAssetData& AssetData, UMovieSceneTrack* Track) +{ + FSlateApplication::Get().DismissAllMenus(); + + const FScopedTransaction Transaction(LOCTEXT("AddAkAudioEvent_Transaction", "Add AkAudioEvent")); + + Track->Modify(); + FFrameNumber KeyTime = GetSequencer()->GetGlobalTime().Time.FrameNumber; + auto AudioTrack = Cast(Track); + + UAkAudioEvent* NewEvent = Cast(AssetData.GetAsset()); + AudioTrack->AddNewEvent(KeyTime, NewEvent); + GetSequencer()->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded); +} + +void FMovieSceneAkAudioEventTrackEditor::BuildObjectBindingTrackMenu(FMenuBuilder& MenuBuilder, const TArray& ObjectBindings, const UClass* ObjectClass) +{ + auto ObjectBinding = ObjectBindings[0]; + if (ObjectClass->IsChildOf(AActor::StaticClass()) || ObjectClass->IsChildOf(USceneComponent::StaticClass())) + { + MenuBuilder.AddMenuEntry( + LOCTEXT("AddAkAudioEventTrack", "AkAudioEvent"), + LOCTEXT("AddAkAudioEventTrackTooltip", "Adds an AkAudioEvent track."), + FSlateIcon(FAkAudioStyle::GetStyleSetName(), "AudiokineticTools.EventIcon"), + FUIAction(FExecuteAction::CreateLambda([=] + { + auto FocusedMovieScene = GetFocusedMovieScene(); + + if (FocusedMovieScene == nullptr) + { + return; + } + + const FScopedTransaction Transaction(LOCTEXT("AddAkAudioEventTrack_Transaction", "Add AkAudioEvent Track")); + FocusedMovieScene->Modify(); + + auto NewTrack = FocusedMovieScene->AddTrack(ObjectBinding); + ensure(NewTrack); + + GetSequencer()->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded); + })) + ); + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Sequencer/MovieSceneAkAudioEventTrackEditor.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Sequencer/MovieSceneAkAudioEventTrackEditor.h new file mode 100644 index 0000000..8602ebe --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Sequencer/MovieSceneAkAudioEventTrackEditor.h @@ -0,0 +1,75 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "MovieSceneTrackEditor.h" +#include "MovieSceneAkAudioEventTrack.h" +#include "AssetRegistry/AssetData.h" + +/** + * Tools for AkAudioEvent tracks + */ +class FMovieSceneAkAudioEventTrackEditor + : public FMovieSceneTrackEditor +{ +public: + + /** + * Constructor + * + * @param InSequencer The sequencer instance to be used by this tool + */ + FMovieSceneAkAudioEventTrackEditor(TSharedRef InSequencer); + + /** + * Creates an instance of this class. Called by a sequencer + * + * @param OwningSequencer The sequencer instance to be used by this tool + * @return The new instance of this class + */ + static TSharedRef CreateTrackEditor(TSharedRef OwningSequencer); + +public: + + // ISequencerTrackEditor interface + + virtual void BuildObjectBindingTrackMenu(FMenuBuilder& MenuBuilder, const TArray& ObjectBindings, const UClass* ObjectClass) override; + virtual void BuildAddTrackMenu(FMenuBuilder& MenuBuilder) override; + virtual TSharedPtr BuildOutlinerEditWidget(const FGuid& ObjectBinding, UMovieSceneTrack* Track, const FBuildEditWidgetParams& Params) override; + virtual bool HandleAssetAdded(UObject* Asset, const FGuid& TargetObjectGuid) override; + + virtual TSharedRef MakeSectionInterface(UMovieSceneSection& SectionObject, UMovieSceneTrack& Track, FGuid ObjectBinding) override; + + virtual bool SupportsType(TSubclassOf Type) const override; + virtual bool SupportsSequence(UMovieSceneSequence* InSequence) const override; + + virtual void BuildTrackContextMenu(FMenuBuilder& MenuBuilder, UMovieSceneTrack* Track) override; + + virtual const FSlateBrush* GetIconBrush() const override; + +private: + + /** Audio sub menu */ + TSharedRef BuildAudioSubMenu(UMovieSceneTrack* Track); + + /** Audio asset selected */ + void OnAudioAssetSelected(const FAssetData& AssetData, UMovieSceneTrack* Track); + + /** Creates a soundbank generation window. Iterates through all of the sections in the track and adds their required banks to the selected banks in the window.*/ + static void CreateGenerateSoundbanksWindowForAllSections(UMovieSceneTrack* in_pTrack); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Sequencer/MovieSceneAkAudioRTPCTrackEditor.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Sequencer/MovieSceneAkAudioRTPCTrackEditor.cpp new file mode 100644 index 0000000..1188727 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Sequencer/MovieSceneAkAudioRTPCTrackEditor.cpp @@ -0,0 +1,408 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "MovieSceneAkAudioRTPCTrackEditor.h" +#include "AkAudioDevice.h" +#include "AkRtpc.h" + +#include "MovieScene.h" +#include "MovieSceneCommonHelpers.h" +#include "MovieSceneAkAudioRTPCTrack.h" +#include "MovieSceneAkAudioRTPCSection.h" + +#include "SequencerUtilities.h" +#include "ISequencerSection.h" +#include "ISequencerObjectChangeListener.h" +#include "ISectionLayoutBuilder.h" +#include "SequencerSectionPainter.h" + +#include "AkAudioStyle.h" + +#include "ContentBrowserModule.h" +#include "IContentBrowserSingleton.h" + +#include "ScopedTransaction.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Layout/SUniformGridPanel.h" +#include "Widgets/Layout/SExpandableArea.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SButton.h" + +#include "Editor.h" + +#define LOCTEXT_NAMESPACE "MovieSceneAkAudioRTPCTrackEditor" + + +/** + * Class that draws a transform section in the sequencer + */ +class FMovieSceneAkAudioRTPCSection + : public ISequencerSection +{ +public: + + FMovieSceneAkAudioRTPCSection(UMovieSceneSection& InSection) + : Section(Cast(&InSection)) + { } + +public: + + // ISequencerSection interface + + virtual UMovieSceneSection* GetSectionObject() override { return Section; } + + virtual FText GetSectionTitle() const override + { + return FText::FromString(Section->GetRTPCName()); + } + + virtual int32 OnPaintSection(FSequencerSectionPainter& InPainter) const override + { + return InPainter.PaintSectionBackground(); + } + +private: + + /** The section we are visualizing */ + UMovieSceneAkAudioRTPCSection* Section; +}; + +FMovieSceneAkAudioRTPCTrackEditor::FMovieSceneAkAudioRTPCTrackEditor(TSharedRef InSequencer) + : FKeyframeTrackEditor(InSequencer) +{ +} + +TSharedRef FMovieSceneAkAudioRTPCTrackEditor::CreateTrackEditor(TSharedRef InSequencer) +{ + return MakeShareable(new FMovieSceneAkAudioRTPCTrackEditor(InSequencer)); +} + +TSharedRef FMovieSceneAkAudioRTPCTrackEditor::MakeSectionInterface(UMovieSceneSection& SectionObject, UMovieSceneTrack& Track, FGuid ObjectBinding) +{ + return MakeShareable(new FMovieSceneAkAudioRTPCSection(SectionObject)); +} + +const FSlateBrush* FMovieSceneAkAudioRTPCTrackEditor::GetIconBrush() const +{ + return FAkAudioStyle::Get().GetBrush("AudiokineticTools.RTPCIcon"); +} + + + +struct FRTPCSectionCreateDialogOptions +{ + FString RTPCName; + UAkRtpc* RTPC = nullptr; + bool OkClicked; + + FRTPCSectionCreateDialogOptions() : OkClicked(false) {} + + bool Validate() + { + return OkClicked && (RTPC != nullptr || RTPCName.Len() > 0); + } +}; + + +class SCreateAkAudioRTPCSectionDialog + : public SCompoundWidget +{ +public: + + SLATE_BEGIN_ARGS(SCreateAkAudioRTPCSectionDialog) { } + SLATE_END_ARGS() + + /** Construct this widget. */ + void Construct(const FArguments& InArgs, FRTPCSectionCreateDialogOptions& InOptions, TSharedRef InWindow) + { + Options = &InOptions; + Window = InWindow; + FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked(TEXT("ContentBrowser")); + + FAssetPickerConfig AssetPickerConfig; + AssetPickerConfig.InitialAssetViewType = EAssetViewType::List; + AssetPickerConfig.bAllowNullSelection = false; +#if UE_5_1_OR_LATER + AssetPickerConfig.Filter.ClassPaths.Add(UAkRtpc::StaticClass()->GetClassPathName()); +#else + AssetPickerConfig.Filter.ClassNames.Add(UAkRtpc::StaticClass()->GetFName()); +#endif + AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateLambda([&InOptions](const FAssetData& InRTPCAssetData) { + if (InRTPCAssetData.IsValid()) + { + InOptions.RTPC = CastChecked(InRTPCAssetData.GetAsset()); + } + }); + + ChildSlot + [ + SNew(SBorder) + .Visibility(EVisibility::Visible) + .BorderImage(FAkAppStyle::Get().GetBrush("Menu.Background")) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .VAlign(VAlign_Top) + .Padding(4) + [ + SNew(SBorder) + .BorderImage(FAkAppStyle::Get().GetBrush("ToolPanel.GroupBorder")) + .Padding(4.0f) + .Content() + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2, 2, 6, 2) + [ + SNew(STextBlock) + .Text(LOCTEXT("AkAudioRTPC", "RTPC")) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2, 0, 2, 0) + [ + SNew(SBox) + .WidthOverride(300.0f) + .HeightOverride(300.f) + [ + ContentBrowserModule.Get().CreateAssetPicker(AssetPickerConfig) + ] + ] + ] + ] + + SVerticalBox::Slot() + .FillHeight(1) + .VAlign(VAlign_Top) + .Padding(4) + [ + SNew(SExpandableArea) + .AreaTitle(LOCTEXT("Advanced", "Advanced")) + .InitiallyCollapsed(true) + .BodyContent() + [ + SNew(SBorder) + .BorderImage(FAkAppStyle::Get().GetBrush("ToolPanel.GroupBorder")) + .Padding(4.0f) + .Content() + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2, 2, 6, 2) + [ + SNew(STextBlock) + .Text(LOCTEXT("AkAudioRTPCName", "RTPC Name")) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2, 0, 2, 0) + [ + SNew(SEditableTextBox) + .HintText(LOCTEXT("AkAudioRTPCNameHint", "Name of the AkAudioRTPC")) + .OnTextCommitted(this, &SCreateAkAudioRTPCSectionDialog::OnEventNameCommited) + .OnTextChanged(this, &SCreateAkAudioRTPCSectionDialog::OnEventNameCommited, ETextCommit::Default) + .MinDesiredWidth(200) + .RevertTextOnEscape(true) + ] + ] + ] + ] + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Right) + .VAlign(VAlign_Bottom) + .Padding(8) + [ + SNew(SUniformGridPanel) + .SlotPadding(FAkAppStyle::Get().GetMargin("StandardDialog.SlotPadding")) + .MinDesiredSlotWidth(FAkAppStyle::Get().GetFloat("StandardDialog.MinDesiredSlotWidth")) + .MinDesiredSlotHeight(FAkAppStyle::Get().GetFloat("StandardDialog.MinDesiredSlotHeight")) + + + SUniformGridPanel::Slot(0, 0) + [ + SNew(SButton) + .HAlign(HAlign_Center) + .ContentPadding(FAkAppStyle::Get().GetMargin("StandardDialog.ContentPadding")) + .OnClicked_Lambda([this]() -> FReply { CloseDialog(true); return FReply::Handled(); }) + .Text(LOCTEXT("OkButtonLabel", "OK")) + ] + + + SUniformGridPanel::Slot(1, 0) + [ + SNew(SButton) + .HAlign(HAlign_Center) + .ContentPadding(FAkAppStyle::Get().GetMargin("StandardDialog.ContentPadding")) + .OnClicked_Lambda([this]() -> FReply { CloseDialog(false); return FReply::Handled(); }) + .Text(LOCTEXT("CancelButtonLabel", "Cancel")) + ] + ] + ] + ]; + } + +protected: + + void CloseDialog(bool InOkClicked) + { + Options->OkClicked = InOkClicked; + + if (Window.IsValid()) + { + Window.Pin()->RequestDestroyWindow(); + } + } + +private: + + void OnEventNameCommited(const FText& InText, ETextCommit::Type InCommitType) + { + Options->RTPCName = InText.ToString(); + + if (InCommitType == ETextCommit::OnEnter || InCommitType == ETextCommit::OnCleared) + { + CloseDialog(InCommitType == ETextCommit::OnEnter); + } + } + + + FRTPCSectionCreateDialogOptions* Options; + TWeakPtr Window; +}; + + +bool ConfigureRTPCSection(FRTPCSectionCreateDialogOptions& Options) +{ + TSharedRef Window = SNew(SWindow) + .Title(LOCTEXT("CreateAkAudioRTPCSectionDialog", "Enter AkAudioRTPC Name")) + .ClientSize(FVector2D(372, 418)) + .SupportsMinimize(false) + .SupportsMaximize(false); + + Window->SetContent(SNew(SCreateAkAudioRTPCSectionDialog, Options, Window)); + GEditor->EditorAddModalWindow(Window); + + return Options.Validate(); +} + + + +void FMovieSceneAkAudioRTPCTrackEditor::TryAddAkAudioRTPCTrack(FCreateAkAudioRTPCTrack DoCreateAkAudioRTPCTrack) +{ + FRTPCSectionCreateDialogOptions Options; + if (ConfigureRTPCSection(Options)) + { + auto FocusedMovieScene = GetFocusedMovieScene(); + + if (FocusedMovieScene == nullptr) + { + return; + } + + const FScopedTransaction Transaction(LOCTEXT("AddAkAudioRTPCTrack_Transaction", "Add AkAudioRTPC Track")); + FocusedMovieScene->Modify(); + + auto NewTrack = DoCreateAkAudioRTPCTrack.Execute(FocusedMovieScene); + ensure(NewTrack); + + auto NewSection = NewTrack->CreateNewSection(); + ensure(NewSection); + + auto RTPCSection = Cast(NewSection); + ensure(RTPCSection); + if (Options.RTPC) + { + RTPCSection->SetRTPC(Options.RTPC); + } + else + { + RTPCSection->SetRTPCName(Options.RTPCName); + } + + NewSection->SetRange(TRange::All()); + NewTrack->AddSection(*NewSection); + + GetSequencer()->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded); + } +} + +void FMovieSceneAkAudioRTPCTrackEditor::BuildAddTrackMenu(FMenuBuilder& MenuBuilder) +{ + auto CreateAkAudioRTPCTrack = [=](UMovieScene* MovieScene) + { +#if UE_5_2_OR_LATER + auto NewTrack = MovieScene->AddTrack(); +#else + auto NewTrack = MovieScene->AddMasterTrack(); +#endif + if (NewTrack != nullptr) + { + NewTrack->SetIsAMasterTrack(true); + } + return NewTrack; + }; + + MenuBuilder.AddMenuEntry( + LOCTEXT("AddAkAudioRTPCTrack", "AkAudioRTPC"), + LOCTEXT("AddAkAudioRTPCMasterTrackTooltip", "Adds a master AkAudioRTPC track."), + FSlateIcon(FAkAudioStyle::GetStyleSetName(), "AudiokineticTools.RTPCIcon"), + FUIAction(FExecuteAction::CreateLambda([=] + { + TryAddAkAudioRTPCTrack(FCreateAkAudioRTPCTrack::CreateLambda(CreateAkAudioRTPCTrack)); + })) + ); +} + +bool FMovieSceneAkAudioRTPCTrackEditor::SupportsSequence(UMovieSceneSequence* InSequence) const +{ +#if UE_5_1_OR_LATER + static UClass* LevelSequenceClass = UClass::TryFindTypeSlow(TEXT("/Script/LevelSequence.LevelSequence"), EFindFirstObjectOptions::ExactClass); +#else + static UClass* LevelSequenceClass = FindObject(ANY_PACKAGE, TEXT("LevelSequence"), true); +#endif + return InSequence != nullptr && LevelSequenceClass != nullptr && InSequence->GetClass()->IsChildOf(LevelSequenceClass); +} + + +void FMovieSceneAkAudioRTPCTrackEditor::BuildObjectBindingTrackMenu(FMenuBuilder& MenuBuilder, const TArray& ObjectBindings, const UClass* ObjectClass) +{ + auto ObjectBinding = ObjectBindings[0]; + if (!ObjectClass->IsChildOf(AActor::StaticClass()) && !ObjectClass->IsChildOf(USceneComponent::StaticClass())) + { + return; + } + + auto CreateAkAudioRTPCTrack = [=](UMovieScene* MovieScene) { return MovieScene->AddTrack(ObjectBinding); }; + + MenuBuilder.AddMenuEntry( + LOCTEXT("AddAkAudioRTPCTrack", "AkAudioRTPC"), + LOCTEXT("AddAkAudioRTPCTrackTooltip", "Adds an AkAudioRTPC track."), + FSlateIcon(FAkAudioStyle::GetStyleSetName(), "AudiokineticTools.RTPCIcon"), + FUIAction(FExecuteAction::CreateLambda([=] + { + TryAddAkAudioRTPCTrack(FCreateAkAudioRTPCTrack::CreateLambda(CreateAkAudioRTPCTrack)); + })) + ); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Sequencer/MovieSceneAkAudioRTPCTrackEditor.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Sequencer/MovieSceneAkAudioRTPCTrackEditor.h new file mode 100644 index 0000000..a9a9612 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Sequencer/MovieSceneAkAudioRTPCTrackEditor.h @@ -0,0 +1,66 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "KeyframeTrackEditor.h" +#include "AkUEFeatures.h" +#include "MovieSceneAkAudioRTPCTrack.h" +#include "MovieSceneAkAudioRTPCSection.h" + + +/** + * Tools for AkAudioRTPC tracks + */ +class FMovieSceneAkAudioRTPCTrackEditor + : public FKeyframeTrackEditor +{ +public: + + /** + * Constructor + * + * @param InSequencer The sequencer instance to be used by this tool + */ + FMovieSceneAkAudioRTPCTrackEditor(TSharedRef InSequencer); + + /** + * Creates an instance of this class. Called by a sequencer + * + * @param OwningSequencer The sequencer instance to be used by this tool + * @return The new instance of this class + */ + static TSharedRef CreateTrackEditor(TSharedRef OwningSequencer); + +public: + + // ISequencerTrackEditor interface + + virtual void BuildObjectBindingTrackMenu(FMenuBuilder& MenuBuilder, const TArray& ObjectBindings, const UClass* ObjectClass) override; + virtual void BuildAddTrackMenu(FMenuBuilder& MenuBuilder) override; + virtual bool SupportsSequence(UMovieSceneSequence* InSequence) const override; + + virtual TSharedRef MakeSectionInterface(UMovieSceneSection& SectionObject, UMovieSceneTrack& Track, FGuid ObjectBinding) override; + + virtual const FSlateBrush* GetIconBrush() const override; + +private: + + DECLARE_DELEGATE_RetVal_OneParam(UMovieSceneAkAudioRTPCTrack*, FCreateAkAudioRTPCTrack, UMovieScene*); + + void TryAddAkAudioRTPCTrack(FCreateAkAudioRTPCTrack DoCreateAkAudioRTPCTrack); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/UI/SAcousticSurfacesController.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/UI/SAcousticSurfacesController.cpp new file mode 100644 index 0000000..1a12bbc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/UI/SAcousticSurfacesController.cpp @@ -0,0 +1,881 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "SAcousticSurfacesController.h" + +#include "AkSurfaceReflectorSetComponent.h" +#include "DetailCategoryBuilder.h" +#include "DetailLayoutBuilder.h" +#include "EditorModeManager.h" +#include "EditorModes.h" +#include "Engine/Selection.h" +#include "Editor/TransBuffer.h" +#include "EditorSupportDelegates.h" +#include "PropertyCustomizationHelpers.h" +#include "SlateCore/Public/Widgets/SBoxPanel.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SNumericEntryBox.h" +#include "Widgets/Input/SSlider.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/SOverlay.h" +#include "Widgets/Text/STextBlock.h" + +#if AK_SUPPORT_WAAPI +#include "AkWaapiClient.h" +#include "AkWaapiUtils.h" +#include "Async/Async.h" +#endif + +#define LOCTEXT_NAMESPACE "AkAudio" + +namespace AkAcousticSurfacesUI +{ + static FText OverrideButtonText = FText::FromString(FString("Override")); + static int OverrideButtonPadding = 5; + static int LabelWidth = 102; +} + +class SOverrideControls : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SOverrideControls) {} + /** Called when the button is clicked */ + SLATE_EVENT(FOnClicked, OnButtonClicked) + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs) + { + OnButtonClicked = InArgs._OnButtonClicked; + + ChildSlot + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() // Label + .AutoWidth() + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString(FString(TEXT("Multiple Values")))) + ] + + SHorizontalBox::Slot() // Button + .AutoWidth() + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .Padding(AkAcousticSurfacesUI::OverrideButtonPadding, 0) + [ + SNew(SButton) + .Text(AkAcousticSurfacesUI::OverrideButtonText) + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .OnClicked(this, &SOverrideControls::CallClicked) + ] + ]; + } + +private: + FOnClicked OnButtonClicked; + FReply CallClicked() { return OnButtonClicked.Execute(); } +}; + +// ================================================== +// SAcousticSurfacesLabels +// ================================================== + +void SAcousticSurfacesLabels::Construct(const FArguments& InArgs, TArray> ObjectsBeingCustomized) +{ + ComponentsBeingCustomized = ObjectsBeingCustomized; + + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() // Acoustic Surface Parameters + .FillHeight(1.0f) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() // Texture + .FillHeight(0.33f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() // Label + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(AkAcousticSurfacesUI::LabelWidth) + [ + SNew(STextBlock) + .ToolTipText(FText::FromString("The Audiokinetic Texture associated with the selected faces.")) + .Text(FText::FromString(FString(TEXT("AkAcousticTexture")))) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ] + ] + + SVerticalBox::Slot() // Occlusion + .FillHeight(0.33f) + [ + SNew(SBox) + .Visibility_Lambda([this]() { return TransmissionLossEnableSurfaceVisibility(); }) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() // Label + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(AkAcousticSurfacesUI::LabelWidth) + [ + SNew(STextBlock) + .ToolTipText(FText::FromString("Indicates how much sound is transmitted through the surface.")) + .Text(FText::FromString(FString(TEXT("Transmission Loss")))) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ] + ] + ] + + SVerticalBox::Slot() // EnableSurface + .FillHeight(0.33f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() // Label + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(AkAcousticSurfacesUI::LabelWidth) + [ + SNew(STextBlock) + .ToolTipText(FText::FromString("Indicates whether the selected faces are sent to the Spatial Audio engine.")) + .Text(FText::FromString(FString(TEXT("Enable Surface")))) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ] + ] + ] + ]; +} + +EVisibility SAcousticSurfacesLabels::TransmissionLossEnableSurfaceVisibility() +{ + for (TWeakObjectPtr ObjectBeingCustomized : ComponentsBeingCustomized) + { + UAkSurfaceReflectorSetComponent* reflectorSetComponent = Cast(ObjectBeingCustomized.Get()); + if (reflectorSetComponent && reflectorSetComponent->bEnableSurfaceReflectors) + { + return EVisibility::Visible; + } + } + return EVisibility::Collapsed; +} + +// ================================================== +// SAcousticSurfacesController +// ================================================== + +void SAcousticSurfacesController::Construct(const FArguments& InArgs, TArray> ObjectsBeingCustomized, const TSharedPtr& InLayoutBuilder) +{ + ensure(ObjectsBeingCustomized.Num() > 0); + + LayoutBuilder = InLayoutBuilder; + + FCoreUObjectDelegates::FOnObjectPropertyChanged::FDelegate OnPropertyChangedDelegate = FCoreUObjectDelegates::FOnObjectPropertyChanged::FDelegate::CreateRaw(this, &SAcousticSurfacesController::OnPropertyChanged); + OnPropertyChangedHandle = FCoreUObjectDelegates::OnObjectPropertyChanged.Add(OnPropertyChangedDelegate); + + GLevelEditorModeTools().OnEditorModeIDChanged().AddRaw(this, &SAcousticSurfacesController::OnEditorModeChanged); + + ComponentsToEdit = ObjectsBeingCustomized; + + if (GLevelEditorModeTools().IsModeActive(FEditorModeID(TEXT("EM_Geometry")))) + { + // In geometry edit mode, we only want to apply to all faces when no individual faces are selected. + bool individualSelection = false; + // Loop through all selected components to check if any indivdual faces are selected. + // If no individual faces are selected, we'll just apply the changes to every face on each component. + for (TWeakObjectPtr ObjectBeingCustomized : ComponentsToEdit) + { + UAkSurfaceReflectorSetComponent* reflectorSetComponent = Cast(ObjectBeingCustomized.Get()); + if (reflectorSetComponent) + { + TSet FacesToEdit = reflectorSetComponent->GetSelectedFaceIndices(); + if (FacesToEdit.Num() > 0) + { + individualSelection = true; + break; + } + } + } + ApplyToAllFaces = !individualSelection; + } + else + { + ApplyToAllFaces = true; + } + + InitReflectorSetsFacesToEdit(); + UpdateCurrentValues(); +#if AK_SUPPORT_WAAPI + RegisterTextureDeletedCallback(); +#endif + + BuildSlate(); +} + +SAcousticSurfacesController::~SAcousticSurfacesController() +{ +#if AK_SUPPORT_WAAPI + RemoveTextureDeletedCallback(); +#endif + FCoreUObjectDelegates::OnObjectPropertyChanged.Remove(OnPropertyChangedHandle); + GLevelEditorModeTools().OnEditorModeIDChanged().RemoveAll(this); +} + +void SAcousticSurfacesController::InitReflectorSetsFacesToEdit() +{ + NumFacesSelected = 0; + for (TWeakObjectPtr ObjectBeingCustomized : ComponentsToEdit) + { + UAkSurfaceReflectorSetComponent* reflectorSetComponent = Cast(ObjectBeingCustomized.Get()); + if (reflectorSetComponent) + { + TSet FacesToEdit; + if (ApplyToAllFaces) + { + for (int i = 0; i < reflectorSetComponent->AcousticPolys.Num(); ++i) + FacesToEdit.Add(i); + } + else + { + FacesToEdit = reflectorSetComponent->GetSelectedFaceIndices(); + } + NumFacesSelected += FacesToEdit.Num(); + if (FacesToEdit.Num() > 0) + ReflectorSetsFacesToEdit.Add(reflectorSetComponent, FacesToEdit); + } + } + + UpdateCurrentValues(); +} + +FAkSurfacePoly& SAcousticSurfacesController::GetAcousticSurfaceChecked(UAkSurfaceReflectorSetComponent* reflectorSet, int faceIndex) +{ + ensure(faceIndex <= reflectorSet->AcousticPolys.Num()); + return reflectorSet->AcousticPolys[faceIndex]; +} + +void SAcousticSurfacesController::RefreshEditor(bool reinitVisualizers /*= false*/) const +{ + if (!LayoutBuilder.IsValid()) + { + return; + } + + FEditorSupportDelegates::RedrawAllViewports.Broadcast(); + + for (auto elem : ReflectorSetsFacesToEdit) + { + UAkSurfaceReflectorSetComponent* ReflectorSetComponent = elem.Key; + if (ReflectorSetComponent != nullptr) + { + if (reinitVisualizers) + { + ReflectorSetComponent->CacheAcousticProperties(); + ReflectorSetComponent->UpdatePolys(); + } + else + { + ReflectorSetComponent->SurfacePropertiesChanged(); + } + } + } + + IDetailLayoutBuilder* Layout = nullptr; + if (auto LockedLayoutBuilder = LayoutBuilder.Pin()) + { + Layout = LockedLayoutBuilder.Get(); + } + if (LIKELY(Layout)) + { + Layout->ForceRefreshDetails(); + } +} + +void SAcousticSurfacesController::BeginModify(FText TransactionText) +{ + if (GEditor && GEditor->Trans) + { + UTransBuffer* TransBuffer = CastChecked(GEditor->Trans); + if (TransBuffer != nullptr) + TransBuffer->Begin(*FString("AkSurfaceReflectorSet Acoustic Surfaces"), TransactionText); + } + + for (auto elem : ReflectorSetsFacesToEdit) + { + UAkSurfaceReflectorSetComponent* ReflectorSetComponent = elem.Key; + if (ReflectorSetComponent != nullptr) + ReflectorSetComponent->Modify(); + } +} + +void SAcousticSurfacesController::EndModify() +{ + if (GEditor && GEditor->Trans) + { + UTransBuffer* TransBuffer = CastChecked(GEditor->Trans); + if (TransBuffer != nullptr) + TransBuffer->End(); + } +} + +void SAcousticSurfacesController::OnPropertyChanged(UObject* ObjectBeingModified, FPropertyChangedEvent& PropertyChangedEvent) +{ + for (auto elem : ReflectorSetsFacesToEdit) + { + UAkSurfaceReflectorSetComponent* ReflectorSetComponent = elem.Key; + if (ObjectBeingModified == ReflectorSetComponent) + { + const FName memberPropertyName = (PropertyChangedEvent.MemberProperty != nullptr) ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None; + if (memberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSurfaceReflectorSetComponent, AcousticPolys)) + { + UpdateCurrentValues(); + } + return; + } + } +} + +void SAcousticSurfacesController::OnEditorModeChanged(const FEditorModeID& InEditorModeID, bool bIsEnteringMode) +{ + if (InEditorModeID == FEditorModeID(TEXT("EM_Geometry"))) + { + if (bIsEnteringMode) + { + ApplyToAllFaces = false; + InitReflectorSetsFacesToEdit(); + Invalidate(EInvalidateWidgetReason::Paint); + } + else + { + ApplyToAllFaces = true; + InitReflectorSetsFacesToEdit(); + Invalidate(EInvalidateWidgetReason::Paint); + } + } +} + +FText SAcousticSurfacesController::GetSelectionText() const +{ + FString selectionInfo = "(All faces)"; + if (!ApplyToAllFaces && NumFacesSelected > 0) + { + selectionInfo = "(" + FString::FromInt(NumFacesSelected) + " faces selected)"; + } + return FText::FromString(selectionInfo); +} + +FText SAcousticSurfacesController::GetSelectionTextTooltip() const +{ + if (!ApplyToAllFaces && NumFacesSelected > 0) + { + return FText::FromString(FString("Changes will apply to all selected faces.")); + } + return FText::FromString(FString("Changes will apply to all faces. Use ") + GEOMETRY_EDIT_DISPLAY_NAME + " to select individual faces. You can enable " GEOMETRY_EDIT_DISPLAY_NAME " by clicking 'Enable Edit Surfaces'."); +} + +void SAcousticSurfacesController::UpdateCurrentValues() +{ + CurrentTexture = GetCollectiveTexture(TexturesDiffer); + CurrentOcclusion = GetCollectiveOcclusion(OcclusionsDiffer); + CurrentEnablement = GetCollectiveEnableSurface(EnablementsDiffer); +} + +void SAcousticSurfacesController::OnTextureAssetChanged(const FAssetData& InAssetData) +{ + BeginModify(FText::FromString(FString("Set Textures"))); + + CurrentTexture = Cast(InAssetData.GetAsset()); + + for (auto elem : ReflectorSetsFacesToEdit) + { + UAkSurfaceReflectorSetComponent* ReflectorSetComponent = elem.Key; + if (ReflectorSetComponent != nullptr) + { + TSet FacesToEdit = elem.Value; + for (const int& i : FacesToEdit) + { + GetAcousticSurfaceChecked(ReflectorSetComponent, i).Texture = CurrentTexture; + } + } + } + + RefreshEditor(); + + EndModify(); +} + +FString SAcousticSurfacesController::GetSelectedTextureAssetPath() const +{ + if (CurrentTexture == nullptr) + { + return ""; + } + + FSoftObjectPath path(CurrentTexture); + return path.GetAssetPathString(); +} + +EVisibility SAcousticSurfacesController::TransmissionLossEnableSurfaceVisibility() +{ + for (auto elem : ReflectorSetsFacesToEdit) + { + UAkSurfaceReflectorSetComponent* ReflectorSetComponent = elem.Key; + if (ReflectorSetComponent != nullptr && ReflectorSetComponent->bEnableSurfaceReflectors) + return EVisibility::Visible; + } + return EVisibility::Collapsed; +} + +EVisibility SAcousticSurfacesController::OverrideTextureControlsVisibility() +{ + if (TexturesDiffer) + return EVisibility::Visible; + + return EVisibility::Collapsed; +} + +FReply SAcousticSurfacesController::OnOverrideTextureButtonClicked() +{ + BeginModify(FText::FromString(FString("Override Textures"))); + + for (auto elem : ReflectorSetsFacesToEdit) + { + UAkSurfaceReflectorSetComponent* ReflectorSetComponent = elem.Key; + if (ReflectorSetComponent != nullptr) + { + TSet FacesToEdit = elem.Value; + for (const int& i : FacesToEdit) + { + GetAcousticSurfaceChecked(ReflectorSetComponent, i).Texture = nullptr; + } + } + } + + RefreshEditor(); + + EndModify(); + + return FReply::Handled(); +} + +UAkAcousticTexture* SAcousticSurfacesController::GetCollectiveTexture(bool& ValuesDiffer) +{ + ValuesDiffer = false; + + if (ReflectorSetsFacesToEdit.Num() == 0) + return nullptr; + + auto it = ReflectorSetsFacesToEdit.CreateIterator(); + ensure(it.Key() != nullptr); + int firstIndex = *(it.Value().begin()); + UAkAcousticTexture* texture = it.Key()->AcousticPolys[firstIndex].Texture; + + for (; it; ++it) + { + UAkSurfaceReflectorSetComponent* ReflectorSetComponent = it.Key(); + if (ReflectorSetComponent != nullptr) + { + TSet FacesToEdit = it.Value(); + for (const int& i : FacesToEdit) + { + if (GetAcousticSurfaceChecked(ReflectorSetComponent, i).Texture != texture) + { + ValuesDiffer = true; + texture = nullptr; + break; + } + } + } + } + + return texture; +} + +EVisibility SAcousticSurfacesController::OverrideOcclusionControlsVisibility() +{ + return OcclusionsDiffer ? EVisibility::Visible : EVisibility::Collapsed; +} + +FReply SAcousticSurfacesController::OnOverrideOcclusionButtonClicked() +{ + BeginModify(FText::FromString(FString("Override Transmission Loss Values"))); + + for (auto elem : ReflectorSetsFacesToEdit) + { + UAkSurfaceReflectorSetComponent* ReflectorSetComponent = elem.Key; + if (ReflectorSetComponent != nullptr) + { + TSet FacesToEdit = elem.Value; + for (const int& i : FacesToEdit) + { + GetAcousticSurfaceChecked(ReflectorSetComponent, i).Occlusion = 0.0f; + } + } + } + + RefreshEditor(); + + EndModify(); + + return FReply::Handled(); +} + +float SAcousticSurfacesController::GetCollectiveOcclusion(bool& ValuesDiffer) +{ + ValuesDiffer = false; + if (ReflectorSetsFacesToEdit.Num() == 0) + return 0.0f; + + auto it = ReflectorSetsFacesToEdit.CreateIterator(); + ensure(it.Key() != nullptr); + int firstIndex = *(it.Value().begin()); + float occlusion = it.Key()->AcousticPolys[firstIndex].Occlusion; + + for (; it; ++it) + { + UAkSurfaceReflectorSetComponent* ReflectorSetComponent = it.Key(); + if (ReflectorSetComponent != nullptr) + { + TSet FacesToEdit = it.Value(); + for (const int& i : FacesToEdit) + { + if (GetAcousticSurfaceChecked(ReflectorSetComponent, i).Occlusion != occlusion) + { + ValuesDiffer = true; + occlusion = 0.0f; + break; + } + } + } + } + + return occlusion; +} + +TOptional SAcousticSurfacesController::GetOcclusionSliderValue() const +{ + return CurrentOcclusion; +} + +void SAcousticSurfacesController::OnOcclusionSliderChanged(float NewValue, ETextCommit::Type Commit) +{ + // TODO: Remove this when fixed. + // There is a bug in UE5.1 when modifying numerical properties and pressing Enter. + // This is the case for the occlusion(transmission loss) value of acoustic surfaces. + // This function is getting called a second time with a wrong occlusion value. + // When that happens, LayoutBuilder is invalid, so we check it to make sure the value is valid as well. + if (!LayoutBuilder.IsValid()) + { + return; + } + + // Only apply valid values + if (NewValue >= 0.0f && NewValue <= 1.0f) + { + BeginModify(FText::FromString(FString("Set Transmission Loss Values"))); + + for (auto elem : ReflectorSetsFacesToEdit) + { + UAkSurfaceReflectorSetComponent* ReflectorSetComponent = elem.Key; + if (ReflectorSetComponent != nullptr) + { + TSet FacesToEdit = elem.Value; + for (const int& i : FacesToEdit) + { + GetAcousticSurfaceChecked(ReflectorSetComponent, i).Occlusion = NewValue; + } + } + } + + RefreshEditor(); + + EndModify(); + } +} + +bool SAcousticSurfacesController::GetCollectiveEnableSurface(bool& ValuesDiffer) +{ + ValuesDiffer = false; + if (ReflectorSetsFacesToEdit.Num() == 0) + return false; + + auto it = ReflectorSetsFacesToEdit.CreateIterator(); + ensure(it.Key() != nullptr); + int firstIndex = *(it.Value().begin()); + bool enableSurface = it.Key()->AcousticPolys[firstIndex].EnableSurface; + + for (; it; ++it) + { + UAkSurfaceReflectorSetComponent* ReflectorSetComponent = it.Key(); + if (ReflectorSetComponent != nullptr) + { + TSet FacesToEdit = it.Value(); + for (const int& i : FacesToEdit) + { + if (GetAcousticSurfaceChecked(ReflectorSetComponent, i).EnableSurface != enableSurface) + { + ValuesDiffer = true; + enableSurface = false; + break; + } + } + } + } + + return enableSurface; +} + +ECheckBoxState SAcousticSurfacesController::GetEnableSurfaceCheckBoxState() const +{ + return EnablementsDiffer ? ECheckBoxState::Undetermined + : (CurrentEnablement ? ECheckBoxState::Checked : ECheckBoxState::Unchecked); +} + +void SAcousticSurfacesController::OnEnableCheckboxChanged(ECheckBoxState NewState) +{ + BeginModify(FText::FromString(FString("Set Enable Surfaces"))); + + for (auto elem : ReflectorSetsFacesToEdit) + { + UAkSurfaceReflectorSetComponent* ReflectorSetComponent = elem.Key; + if (ReflectorSetComponent != nullptr) + { + TSet FacesToEdit = elem.Value; + if (NewState != ECheckBoxState::Undetermined) + { + bool enable = NewState == ECheckBoxState::Checked; + for (const int& i : FacesToEdit) + { + GetAcousticSurfaceChecked(ReflectorSetComponent, i).EnableSurface = enable; + } + } + } + } + + RefreshEditor(true); + + EndModify(); +} + +#if AK_SUPPORT_WAAPI +void SAcousticSurfacesController::RegisterTextureDeletedCallback() +{ + FAkWaapiClient* waapiClient = FAkWaapiClient::Get(); + if (waapiClient != nullptr && waapiClient->IsConnected()) + { + auto textureDeletedCallback = WampEventCallback::CreateLambda([this](uint64_t id, TSharedPtr jsonObject) + { + const TSharedPtr itemObj = jsonObject->GetObjectField(WwiseWaapiHelper::OBJECT); + if (itemObj != nullptr) + { + const FString itemIdString = itemObj->GetStringField(WwiseWaapiHelper::ID); + FGuid itemID = FGuid::NewGuid(); + FGuid::ParseExact(itemIdString, EGuidFormats::DigitsWithHyphensInBraces, itemID); + if (CurrentTexture != nullptr && itemID == CurrentTexture->AcousticTextureInfo.WwiseGuid) + { + AsyncTask(ENamedThreads::GameThread, [this, itemID] + { + CurrentTexture = nullptr; + }); + } + } + }); + + TSharedRef options = MakeShareable(new FJsonObject()); + options->SetArrayField(WwiseWaapiHelper::RETURN, TArray> { MakeShareable(new FJsonValueString(WwiseWaapiHelper::ID)) }); + TSharedPtr outJsonResult; + if (!waapiClient->Subscribe(ak::wwise::core::object::preDeleted, options, textureDeletedCallback, TextureDeleteSubscriptionID, outJsonResult)) + { + UE_LOG(LogAkAudio, Warning, TEXT("AkSettings: WAAPI: Acoustic texture object preDeleted subscription failed.")); + } + } +} + +void SAcousticSurfacesController::RemoveTextureDeletedCallback() +{ + FAkWaapiClient* waapiClient = FAkWaapiClient::Get(); + if (waapiClient != nullptr && waapiClient->IsConnected() && TextureDeleteSubscriptionID != 0) + { + TSharedPtr unsubscribeResult; + waapiClient->Unsubscribe(TextureDeleteSubscriptionID, unsubscribeResult); + } +} +#endif + +void SAcousticSurfacesController::BuildSlate() +{ + FSlateFontInfo SelectionInfoFont = FAkAppStyle::Get().GetFontStyle("TinyText"); + + if (LayoutBuilder.IsValid()) + { + if (auto LockedDetailBuilder = LayoutBuilder.Pin()) + { + SelectionInfoFont = LockedDetailBuilder->GetDetailFontItalic(); + } + } + + ChildSlot + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() // Acoustic Surface Parameters + .AutoWidth() + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() // Texture + .FillHeight(0.33f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() // Control + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SOverlay) + + SOverlay::Slot() + [ + SNew(SObjectPropertyEntryBox) + .AllowedClass(UAkAcousticTexture::StaticClass()) + .OnObjectChanged(this, &SAcousticSurfacesController::OnTextureAssetChanged) + .ObjectPath(this, &SAcousticSurfacesController::GetSelectedTextureAssetPath) + .ToolTipText(this, &SAcousticSurfacesController::GetSelectionTextTooltip) + .Visibility_Lambda([this]() { return (OverrideTextureControlsVisibility() == EVisibility::Collapsed) ? EVisibility::Visible : EVisibility::Collapsed; }) + ] + + SOverlay::Slot() // Multiple values controls + [ + SNew(SBox) + .Visibility_Lambda([this]() { return OverrideTextureControlsVisibility(); }) + [ + SNew(SOverrideControls) + .OnButtonClicked(this, &SAcousticSurfacesController::OnOverrideTextureButtonClicked) + .ToolTipText(this, &SAcousticSurfacesController::GetSelectionTextTooltip) + ] + ] + ] + ] + + SVerticalBox::Slot() // Occlusion + .FillHeight(0.33f) + [ + SNew(SBox) + .Visibility_Lambda([this]() { return TransmissionLossEnableSurfaceVisibility(); }) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() // Control + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SOverlay) + + SOverlay::Slot() + [ + SNew(SBox) + .Visibility_Lambda([this]() { return (OverrideOcclusionControlsVisibility() == EVisibility::Collapsed) ? EVisibility::Visible : EVisibility::Collapsed; }) + [ + SNew(SNumericEntryBox) + .MinValue(0.0f) + .MaxValue(1.0f) + .MinSliderValue(0.0f) + .MaxSliderValue(1.0f) + .ToolTipText(this, &SAcousticSurfacesController::GetSelectionTextTooltip) + .Value(this, &SAcousticSurfacesController::GetOcclusionSliderValue) + .OnValueCommitted(this, &SAcousticSurfacesController::OnOcclusionSliderChanged) + ] + ] + + SOverlay::Slot() // Multiple values controls + [ + SNew(SBox) + .Visibility_Lambda([this]() { return OverrideOcclusionControlsVisibility(); }) + [ + SNew(SOverrideControls) + .OnButtonClicked(this, &SAcousticSurfacesController::OnOverrideOcclusionButtonClicked) + .ToolTipText(this, &SAcousticSurfacesController::GetSelectionTextTooltip) + ] + ] + ] + ] + ] + + SVerticalBox::Slot() // EnableSurface + .FillHeight(0.33f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() // Control + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SCheckBox) + .IsChecked(ECheckBoxState::Undetermined) + .OnCheckStateChanged(this, &SAcousticSurfacesController::OnEnableCheckboxChanged) + .IsChecked(this, &SAcousticSurfacesController::GetEnableSurfaceCheckBoxState) + .ToolTipText(this, &SAcousticSurfacesController::GetSelectionTextTooltip) + ] + ] + ] + + SHorizontalBox::Slot() // Selection Info + .AutoWidth() + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() // Texture + .FillHeight(0.33f) + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(this, &SAcousticSurfacesController::GetSelectionText) + .ToolTipText(this, &SAcousticSurfacesController::GetSelectionTextTooltip) + .Font(SelectionInfoFont) + ] + + SVerticalBox::Slot() // Occlusion + .FillHeight(0.33f) + [ + SNew(SBox) + .Visibility_Lambda([this]() { return TransmissionLossEnableSurfaceVisibility(); }) + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(this, &SAcousticSurfacesController::GetSelectionText) + .ToolTipText(this, &SAcousticSurfacesController::GetSelectionTextTooltip) + .Font(SelectionInfoFont) + ] + ] + + SVerticalBox::Slot() // EnableSurface + .FillHeight(0.33f) + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(this, &SAcousticSurfacesController::GetSelectionText) + .ToolTipText(this, &SAcousticSurfacesController::GetSelectionTextTooltip) + .Font(SelectionInfoFont) + ] + ] + ]; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/UI/SAcousticSurfacesController.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/UI/SAcousticSurfacesController.h new file mode 100644 index 0000000..b176a37 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/UI/SAcousticSurfacesController.h @@ -0,0 +1,139 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkAcousticTexture.h" +#include "AkUEFeatures.h" +#include "AssetThumbnail.h" +#include "Framework/Application/SlateApplication.h" +#include "Widgets/SCompoundWidget.h" +#include "Editor.h" + +#if WITH_EDITOR +#define GEOMETRY_EDIT_DISPLAY_NAME "Brush Editing Mode" +#endif + +class UAkSurfaceReflectorSetComponent; +class IDetailLayoutBuilder; +class IDetailCategoryBuilder; +class UTransBuffer; +class STextBlock; +struct FAkSurfacePoly; + +class SAcousticSurfacesLabels : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SAcousticSurfacesLabels) {} + SLATE_END_ARGS() + + AUDIOKINETICTOOLS_API void Construct(const FArguments& InArgs, TArray> ObjectsBeingCustomized); +private: + TArray> ComponentsBeingCustomized; + // Transmission Loss and Enable Surface Visibility + EVisibility TransmissionLossEnableSurfaceVisibility(); +}; + +class SAcousticSurfacesController : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SAcousticSurfacesController) {} + SLATE_END_ARGS() + + AUDIOKINETICTOOLS_API void Construct(const FArguments& InArgs + ,TArray> ObjectsBeingCustomized + ,const TSharedPtr& InLayoutBuilder + ); + + ~SAcousticSurfacesController(); + +private: + void BuildSlate(); + + /** The details layout in the editor */ + TWeakPtr LayoutBuilder; + /** The list of objects being customized. This is stored because we need to change which faces to use when notified that the editor mode is changing. + (See OnEditorModeChanged, OnEditorModeExited). + */ + TArray> ComponentsToEdit; + /** Map of UAkSurfaceReflectorSetComponents to sets of selected face indices. */ + typedef TMap> ReflectorSetsSelectedFaces; + ReflectorSetsSelectedFaces ReflectorSetsFacesToEdit; + void InitReflectorSetsFacesToEdit(); + /** Helper function to get an acoustic surface, with assert for indexing. */ + FAkSurfacePoly& GetAcousticSurfaceChecked(UAkSurfaceReflectorSetComponent* reflectorSet, int faceIndex); + /** Refresh the viewport and details panel in the editor. If reinitVisualizers = true, update the edge map and recreate the text visualizers on the selected USurfaceReflectorSetComponents */ + void RefreshEditor(bool reinitVisualizers = false) const; + void BeginModify(FText TransactionText); + void EndModify(); + + FDelegateHandle OnPropertyChangedHandle; + void OnPropertyChanged(UObject* ObjectBeingModified, FPropertyChangedEvent& PropertyChangedEvent); + + int NumFacesSelected = 0; + FText GetSelectionText() const; + FText GetSelectionTextTooltip() const; + /** Determines whether we should apply the changes to all faces in the AkSurfaceReflectorComponent or just those that are selected. + Ideally we could just check if geometry mode is enabled during Construct, and refresh the details panel when the mode is changed. + However, the notifications are sent before the active modes are updated in the GLevelEditorModeTools, so this wouldn't work. */ + bool ApplyToAllFaces = false; + void OnEditorModeChanged(const FEditorModeID& InEditorModeID, bool bIsEnteringMode); + + /** Update the current collective texture, occlusion, and enablement for all considered faces on the component(s) */ + void UpdateCurrentValues(); + + // Texture state and controls + bool TexturesDiffer = false; + UAkAcousticTexture* CurrentTexture = nullptr; + + EVisibility OverrideTextureControlsVisibility(); + FReply OnOverrideTextureButtonClicked(); + + UAkAcousticTexture* GetCollectiveTexture(bool& ValuesDiffer); + void OnTextureAssetChanged(const FAssetData& InAssetData); + FString GetSelectedTextureAssetPath() const; + + // Transmission Loss and Enable Surface Visibility + EVisibility TransmissionLossEnableSurfaceVisibility(); + + // Occlusion state and controls + bool OcclusionsDiffer = false; + float CurrentOcclusion = 0.0f; + + EVisibility OverrideOcclusionControlsVisibility(); + FReply OnOverrideOcclusionButtonClicked(); + + float GetCollectiveOcclusion(bool& ValuesDiffer); + TOptional GetOcclusionSliderValue() const; + void OnOcclusionSliderChanged(float NewValue, ETextCommit::Type Commit); + + // Enable Surface state and controls + bool EnablementsDiffer = false; + bool CurrentEnablement = false; + + bool GetCollectiveEnableSurface(bool& ValuesDiffer); + ECheckBoxState GetEnableSurfaceCheckBoxState() const; + void OnEnableCheckboxChanged(ECheckBoxState NewState); + +#if AK_SUPPORT_WAAPI + // Register a pre-delete WAAPI callback for the acoustic texture asset (if it is valid) + void RegisterTextureDeletedCallback(); + // Remove the existing pre-delete WAAPI callback for the acoustic texture asset. + void RemoveTextureDeletedCallback(); + uint64 TextureDeleteSubscriptionID; +#endif +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/UI/SAkGeometrySurfaceOverrideController.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/UI/SAkGeometrySurfaceOverrideController.cpp new file mode 100644 index 0000000..1fc5ddc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/UI/SAkGeometrySurfaceOverrideController.cpp @@ -0,0 +1,333 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "SAkGeometrySurfaceOverrideController.h" + +#include "DetailCategoryBuilder.h" +#include "DetailLayoutBuilder.h" +#include "PropertyCustomizationHelpers.h" +#include "Editor/TransBuffer.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SNumericEntryBox.h" +#include "Widgets/Layout/SBox.h" + +#include "AkGeometryComponent.h" +#include "AkUEFeatures.h" + +namespace AkGeometryUI +{ + static int LabelWidth = 146; +} + +void SSurfacePropertiesLabels::Construct(const FArguments& InArgs, FAkGeometrySurfaceOverride* InSurfaceOverride) +{ + SurfaceOverride = InSurfaceOverride; + + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() // Acoustic Surface Parameters + .FillHeight(1.0f) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() // Texture + .FillHeight(0.33f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() // Label + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(AkGeometryUI::LabelWidth) + [ + SNew(STextBlock) + .ToolTipText(FText::FromString("The Audiokinetic Texture associated with the surface.")) + .Text(FText::FromString(FString(TEXT("AkAcousticTexture")))) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ] + ] + + SVerticalBox::Slot() // Transmission Loss + .FillHeight(0.33f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() // Label + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(AkGeometryUI::LabelWidth) + [ + SNew(STextBlock) + .ToolTipText(FText::FromString("Indicates whether the surface overrides the transmission loss value.")) + .Text(FText::FromString(FString(TEXT("Override Transmission Loss")))) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ] + ] + + SVerticalBox::Slot() // Enable Transmission Loss Override + .FillHeight(0.33f) + [ + SNew(SBox) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() // Label + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(AkGeometryUI::LabelWidth) + .Visibility_Lambda([this]() { return TransmissionLossVisibility(); }) + [ + SNew(STextBlock) + .ToolTipText(FText::FromString("Indicates how much sound is transmitted through the surface.")) + .Text(FText::FromString(FString(TEXT("Transmission Loss")))) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ] + ] + ] + ] + ]; +} + +EVisibility SSurfacePropertiesLabels::TransmissionLossVisibility() const +{ + if (SurfaceOverride == nullptr) + return EVisibility::Visible; + return SurfaceOverride->bEnableOcclusionOverride ? EVisibility::Visible : EVisibility::Collapsed; +} + +// ================================================== +// SAkGeometrySurfaceController +// ================================================== + +void SAkGeometrySurfaceController::Construct(const FArguments& InArgs, TWeakObjectPtr ObjectBeingCustomized, const TSharedPtr& InLayoutBuilder) +{ + LayoutBuilder = InLayoutBuilder; + ComponentBeingCustomized = Cast(ObjectBeingCustomized); + BuildSlate(); +} + +SAkGeometrySurfaceController::~SAkGeometrySurfaceController() +{ + +} + +void SAkGeometrySurfaceController::BeginModify(FText TransactionText) +{ + if (GEditor && GEditor->Trans) + { + UTransBuffer* TransBuffer = CastChecked(GEditor->Trans); + if (TransBuffer != nullptr) + TransBuffer->Begin(*FString("AkGeometry Acoustic Surfaces"), TransactionText); + } + + if (ComponentBeingCustomized != nullptr) + ComponentBeingCustomized->Modify(); +} + +void SAkGeometrySurfaceController::EndModify() +{ + if (GEditor && GEditor->Trans) + { + UTransBuffer* TransBuffer = CastChecked(GEditor->Trans); + if (TransBuffer != nullptr) + TransBuffer->End(); + } +} + +FString SAkGeometrySurfaceController::GetSelectedTextureAssetPath() const +{ + FAkGeometrySurfaceOverride* SurfaceProperties = GetSurfaceOverride(); + if (SurfaceProperties == nullptr) + return ""; + + FSoftObjectPath Path(SurfaceProperties->AcousticTexture); + return Path.GetAssetPathString(); +} + +void SAkGeometrySurfaceController::OnTextureAssetChanged(const FAssetData& InAssetData) +{ + BeginModify(FText::FromString(FString("Set AkGeometry Texture"))); + + FAkGeometrySurfaceOverride* SurfaceProperties = GetSurfaceOverride(); + if (SurfaceProperties != nullptr) + { + SurfaceProperties->AcousticTexture = Cast(InAssetData.GetAsset()); + } + + EndModify(); +} + +TOptional SAkGeometrySurfaceController::GetTransmissionLossValue() const +{ + if (GetSurfaceOverride() != nullptr) + return GetSurfaceOverride()->OcclusionValue; + return 0.0f; +} + +void SAkGeometrySurfaceController::OnTransmissionLossChanged(float NewValue, ETextCommit::Type Commit) +{ + // Only apply valid values + if (NewValue >= 0.0f && NewValue <= 1.0f) + { + if (GetSurfaceOverride() != nullptr) + { + BeginModify(FText::FromString(FString("AkGeometry: Set Transmission Loss Value"))); + GetSurfaceOverride()->OcclusionValue = NewValue; + EndModify(); + } + } +} + +EVisibility SAkGeometrySurfaceController::TransmissionLossVisibility() const +{ + if (GetSurfaceOverride() == nullptr) + return EVisibility::Visible; + return GetSurfaceOverride()->bEnableOcclusionOverride ? EVisibility::Visible : EVisibility::Collapsed; +} + +ECheckBoxState SAkGeometrySurfaceController::GetEnableTransmissionLossOverrideCheckBoxState() const +{ + if (GetSurfaceOverride() == nullptr) + return ECheckBoxState::Undetermined; + bool bEnabled = GetSurfaceOverride()->bEnableOcclusionOverride; + return bEnabled ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +void SAkGeometrySurfaceController::OnEnableTransmissionLossOverrideChanged(ECheckBoxState NewState) +{ + if (GetSurfaceOverride() != nullptr) + { + bool bEnable = NewState == ECheckBoxState::Checked; + FString ModifyString = bEnable ? "AkGeometry: Enable Transmission Loss Override" + : "AkGeometry: Disable Transmission Loss Override"; + BeginModify(FText::FromString(ModifyString)); + GetSurfaceOverride()->bEnableOcclusionOverride = bEnable; + EndModify(); + } +} + +void SAkGeometrySurfaceController::BuildSlate() +{ + FSlateFontInfo selectionInfoFont = LayoutBuilder.IsValid() ? LayoutBuilder.Pin()->GetDetailFontItalic() : FAkAppStyle::Get().GetFontStyle("TinyText"); + + ChildSlot + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() // Controls + .AutoWidth() + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() // Texture + .FillHeight(0.33f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() // Control + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SOverlay) + + SOverlay::Slot() + [ + SNew(SObjectPropertyEntryBox) + .AllowedClass(UAkAcousticTexture::StaticClass()) + .OnObjectChanged(this, &SAkGeometrySurfaceController::OnTextureAssetChanged) + .ObjectPath(this, &SAkGeometrySurfaceController::GetSelectedTextureAssetPath) + .ToolTipText(FText::FromString("The Audiokinetic Texture associated with the surface.")) + ] + ] + ] + + SVerticalBox::Slot() // Enable Transmission Loss Override + .FillHeight(0.33f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() // Control + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SCheckBox) + .OnCheckStateChanged(this, &SAkGeometrySurfaceController::OnEnableTransmissionLossOverrideChanged) + .IsChecked(this, &SAkGeometrySurfaceController::GetEnableTransmissionLossOverrideCheckBoxState) + .ToolTipText(FText::FromString("Indicates whether the surface overrides the transmission loss value.")) + ] + ] + + SVerticalBox::Slot() // Transmission Loss + .FillHeight(0.33f) + [ + SNew(SBox) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() // Control + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SOverlay) + + SOverlay::Slot() + [ + SNew(SBox) + .Visibility_Lambda([this]() { return TransmissionLossVisibility(); }) + [ + SNew(SNumericEntryBox) + .MinValue(0.0f) + .MaxValue(1.0f) + .MinSliderValue(0.0f) + .MaxSliderValue(1.0f) + .ToolTipText(FText::FromString("Indicates how much sound is transmitted through the surface.")) + .Value(this, &SAkGeometrySurfaceController::GetTransmissionLossValue) + .OnValueCommitted(this, &SAkGeometrySurfaceController::OnTransmissionLossChanged) + ] + ] + ] + ] + ] + ] + + SHorizontalBox::Slot() // Labels + .AutoWidth() + [ + SNew(SSurfacePropertiesLabels, GetSurfaceOverride()) + ] + ]; +} + +FAkGeometrySurfaceOverride* SAkGeometryCollisionMeshSurfaceController::GetSurfaceOverride() const +{ + if (ComponentBeingCustomized == nullptr) + return nullptr; + return &(ComponentBeingCustomized->CollisionMeshSurfaceOverride); +} + +void SAkGeometryStaticMeshSurfaceController::Construct(const FArguments& InArgs, TWeakObjectPtr ObjectBeingCustomized, const TSharedPtr& InLayoutBuilder, UMaterialInterface* InMaterialKey) +{ + MaterialKey = InMaterialKey; + SAkGeometrySurfaceController::Construct(InArgs, ObjectBeingCustomized, InLayoutBuilder); +} + +FAkGeometrySurfaceOverride* SAkGeometryStaticMeshSurfaceController::GetSurfaceOverride() const +{ + if (ComponentBeingCustomized == nullptr) + return nullptr; + return ComponentBeingCustomized->StaticMeshSurfaceOverride.Find(MaterialKey); +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/UI/SAkGeometrySurfaceOverrideController.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/UI/SAkGeometrySurfaceOverrideController.h new file mode 100644 index 0000000..2339902 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/UI/SAkGeometrySurfaceOverrideController.h @@ -0,0 +1,105 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Framework/Application/SlateApplication.h" +#include "Widgets/SCompoundWidget.h" +#include "Editor.h" + +class IDetailLayoutBuilder; +class IDetailCategoryBuilder; +class UMaterialInterface; + +class UAkGeometryComponent; +struct FAkGeometrySurfaceOverride; +class UAkAcousticTexture; + +class SSurfacePropertiesLabels : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SSurfacePropertiesLabels) {} + SLATE_END_ARGS() + + AUDIOKINETICTOOLS_API void Construct(const FArguments& InArgs, FAkGeometrySurfaceOverride* InSurfaceOverride); + +private: + FAkGeometrySurfaceOverride* SurfaceOverride = nullptr; + + EVisibility TransmissionLossVisibility() const; +}; + +class SAkGeometrySurfaceController : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SAkGeometrySurfaceController) {} + SLATE_END_ARGS() + + AUDIOKINETICTOOLS_API void Construct(const FArguments& InArgs + , TWeakObjectPtr ObjectBeingCustomized + , const TSharedPtr& InLayoutBuilder + ); + + ~SAkGeometrySurfaceController(); + +private: + void BeginModify(FText TransactionText); + void EndModify(); + + virtual FAkGeometrySurfaceOverride* GetSurfaceOverride() const { return nullptr; } + void BuildSlate(); + + FString GetSelectedTextureAssetPath() const; + void OnTextureAssetChanged(const FAssetData& InAssetData); + + TOptional GetTransmissionLossValue() const; + void OnTransmissionLossChanged(float NewValue, ETextCommit::Type Commit); + EVisibility TransmissionLossVisibility() const; + + ECheckBoxState GetEnableTransmissionLossOverrideCheckBoxState() const; + void OnEnableTransmissionLossOverrideChanged(ECheckBoxState NewState); + + + UAkAcousticTexture* CurrentTexture = nullptr; + /** The details layout in the editor */ + TWeakPtr LayoutBuilder = nullptr; + /** The AkGeometryComponent being customized */ + mutable UAkGeometryComponent* ComponentBeingCustomized = nullptr; + + friend class SAkGeometryCollisionMeshSurfaceController; + friend class SAkGeometryStaticMeshSurfaceController; +}; + +class SAkGeometryCollisionMeshSurfaceController : public SAkGeometrySurfaceController +{ +private: + virtual FAkGeometrySurfaceOverride* GetSurfaceOverride() const override; +}; + +class SAkGeometryStaticMeshSurfaceController : public SAkGeometrySurfaceController +{ +public: + AUDIOKINETICTOOLS_API void Construct(const FArguments& InArgs + , TWeakObjectPtr ObjectBeingCustomized + , const TSharedPtr& InLayoutBuilder + , UMaterialInterface* InMaterialKey + ); + +private: + virtual FAkGeometrySurfaceOverride* GetSurfaceOverride() const override; + UMaterialInterface* MaterialKey; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/UI/SGenerateSoundBanks.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/UI/SGenerateSoundBanks.cpp new file mode 100644 index 0000000..04ed894 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/UI/SGenerateSoundBanks.cpp @@ -0,0 +1,278 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ + SGenerateSoundBanks.cpp +------------------------------------------------------------------------------------*/ + +#include "SGenerateSoundBanks.h" + +#include "AkAudioBankGenerationHelpers.h" +#include "AkAudioDevice.h" +#include "AkSettingsPerUser.h" +#include "AkUEFeatures.h" +#include "AssetManagement/AkGenerateSoundBanksTask.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "Dialogs/Dialogs.h" +#include "Dom/JsonObject.h" +#include "Framework/Application/SlateApplication.h" +#include "GenericPlatform/GenericPlatformFile.h" +#if UE_5_0_OR_LATER +#include "HAL/PlatformFileManager.h" +#else +#include "HAL/PlatformFilemanager.h" +#endif +#include "Interfaces/ITargetPlatform.h" +#include "Interfaces/ITargetPlatformManagerModule.h" +#include "Misc/FileHelper.h" +#include "Misc/MessageDialog.h" +#include "Platforms/AkPlatformInfo.h" +#include "Platforms/AkUEPlatform.h" +#include "Serialization/JsonReader.h" +#include "Serialization/JsonSerializer.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SCheckBox.h" +#include "AssetManagement/WwiseProjectInfo.h" + +#define LOCTEXT_NAMESPACE "AkAudio" + +SGenerateSoundBanks::SGenerateSoundBanks() +{ +} + +void SGenerateSoundBanks::Construct(const FArguments& InArgs) +{ + // Generate the list of banks and platforms + PopulateList(); + if (PlatformNames.Num() == 0) + { + FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("AkAudio", "Warning_Ak_PlatformSupported", "Unable to generate Sound Data. Please select a valid Wwise supported platform in the 'Project Settings > Project > Supported Platforms' dialog.")); + return; + } + + bool skipLanguages = false; + + if (auto* akSettingsPerUser = GetDefault()) + { + skipLanguages = akSettingsPerUser->SoundDataGenerationSkipLanguage; + } + + // Build the form + ChildSlot + [ + SNew(SVerticalBox) + +SVerticalBox::Slot() + .Padding(0, 8) + .FillHeight(1.f) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .Padding(0, 8) + .AutoWidth() + [ + SNew(SBorder) + .BorderImage( FAkAppStyle::Get().GetBrush("ToolPanel.GroupBorder") ) + [ + SAssignNew(PlatformList, SListView>) + .ListItemsSource(&PlatformNames) + .SelectionMode(ESelectionMode::Multi) + .OnGenerateRow(this, &SGenerateSoundBanks::MakePlatformListItemWidget) + .HeaderRow + ( + SNew(SHeaderRow) + + SHeaderRow::Column("Available Platforms") + [ + SNew(SBorder) + .Padding(5) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("AkAvailablePlatforms", "Available Platforms")) + ] + ] + ) + ] + ] + + SHorizontalBox::Slot() + .Padding(0, 8) + .AutoWidth() + [ + SNew(SBorder) + .BorderImage(FAkAppStyle::Get().GetBrush("ToolPanel.GroupBorder")) + [ + SAssignNew(LanguageList, SListView>) + .ListItemsSource(&LanguagesNames) + .SelectionMode(ESelectionMode::Multi) + .OnGenerateRow(this, &SGenerateSoundBanks::MakePlatformListItemWidget) + .HeaderRow + ( + SNew(SHeaderRow) + + SHeaderRow::Column("Available Languages") + [ + SNew(SBorder) + .Padding(5) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("AkAvailableLanguages", "Available Languages")) + ] + ] + ) + ] + ] + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 4) + .HAlign(HAlign_Left) + [ + SAssignNew(SkipLanguagesCheckBox, SCheckBox) + .IsChecked(skipLanguages ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("AkSkipVOFiles", "Skip generation of localized assets")) + ] + ] + +SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 4) + .HAlign(HAlign_Right) + [ + SNew(SButton) + .Text(LOCTEXT("AkGenerate", "Generate")) + .OnClicked(this, &SGenerateSoundBanks::OnGenerateButtonClicked) + ] + ]; + + // Select all the platforms + for (const auto& platform : PlatformNames) + { + PlatformList->SetItemSelection(platform, true); + } + + // Select all the languages + for (const auto& language : LanguagesNames) + { + LanguageList->SetItemSelection(language, true); + } +} + +void SGenerateSoundBanks::PopulateList(void) +{ + // Get platforms in Wwise project + wwiseProjectInfo.Parse(); + PlatformNames.Empty(); + for (const auto& WwisePlatform : wwiseProjectInfo.GetSupportedPlatforms()) + { + const FString WwisePlatformName = WwisePlatform.Name; + if (!WwisePlatformName.IsEmpty() && + !PlatformNames.ContainsByPredicate([WwisePlatformName](TSharedPtr Platform) { return WwisePlatformName == *Platform; })) + { + PlatformNames.Add(MakeShared(WwisePlatformName)); + } + } + + LanguagesNames.Empty(); + for (const auto& language : wwiseProjectInfo.GetSupportedLanguages()) + { + LanguagesNames.Add(MakeShared(language.Name)); + } +} + +FReply SGenerateSoundBanks::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyboardEvent ) +{ + if( InKeyboardEvent.GetKey() == EKeys::Enter ) + { + return OnGenerateButtonClicked(); + } + else if( InKeyboardEvent.GetKey() == EKeys::Escape ) + { + TSharedPtr ParentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); + ParentWindow->RequestDestroyWindow(); + return FReply::Handled(); + } + + return SCompoundWidget::OnKeyDown(MyGeometry, InKeyboardEvent); +} + +FReply SGenerateSoundBanks::OnGenerateButtonClicked() +{ + TArray< TSharedPtr > PlatformsToGenerate = PlatformList->GetSelectedItems(); + if( PlatformsToGenerate.Num() <= 0 ) + { + FMessageDialog::Open( EAppMsgType::Ok, NSLOCTEXT("AkAudio", "Warning_Ak_NoAkPlatformsSelected", "At least one platform must be selected.")); + return FReply::Handled(); + } + + AkSoundBankGenerationManager::FInitParameters InitParameters; + + for (auto& platform : PlatformsToGenerate) + { + InitParameters.Platforms.Add(*platform.Get()); + } + + TArray> languagesToGenerate = LanguageList->GetSelectedItems(); + + for (auto& selectedLanguage : languagesToGenerate) + { + for (auto& entry : wwiseProjectInfo.GetSupportedLanguages()) + { + if (*selectedLanguage == entry.Name) + { + InitParameters.Languages.Add(entry.Name); + break; + } + } + } + + InitParameters.SkipLanguages = SkipLanguagesCheckBox->IsChecked(); + + if (auto* akSettingsPerUser = GetMutableDefault()) + { + akSettingsPerUser->SoundDataGenerationSkipLanguage = InitParameters.SkipLanguages; + akSettingsPerUser->SaveConfig(); + } + + if (FAkWaapiClient::IsProjectLoaded()) + { + InitParameters.GenerationMode = AkSoundBankGenerationManager::ESoundBankGenerationMode::WAAPI; + } + + AkGenerateSoundBanksTask::CreateAndExecuteTask(InitParameters); + + TSharedPtr ParentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); + ParentWindow->RequestDestroyWindow(); + + return FReply::Handled(); +} + +TSharedRef SGenerateSoundBanks::MakePlatformListItemWidget(TSharedPtr Platform, const TSharedRef& OwnerTable) +{ + return + SNew(STableRow< TSharedPtr >, OwnerTable) + [ + SNew(SBox) + .WidthOverride(300) + [ + SNew(STextBlock) + .Text(FText::FromString(*Platform)) + ] + ]; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/UI/SGenerateSoundBanks.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/UI/SGenerateSoundBanks.h new file mode 100644 index 0000000..12323a2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/UI/SGenerateSoundBanks.h @@ -0,0 +1,66 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ + SGenerateSoundBanks.h +------------------------------------------------------------------------------------*/ +#pragma once + +#include "Widgets/SCompoundWidget.h" +#include "Widgets/Views/SListView.h" +#include "Widgets/Views/STableRow.h" +#include "AssetManagement/WwiseProjectInfo.h" + +class SCheckBox; + +class SGenerateSoundBanks : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS( SGenerateSoundBanks ) + {} + SLATE_END_ARGS( ) + + SGenerateSoundBanks(void); + + AUDIOKINETICTOOLS_API void Construct(const FArguments& InArgs); + virtual FReply OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyboardEvent ) override; + + /** override the base method to allow for keyboard focus */ + virtual bool SupportsKeyboardFocus() const + { + return true; + } + + bool ShouldDisplayWindow() { return PlatformNames.Num() != 0; } + +private: + void PopulateList(); + +private: + FReply OnGenerateButtonClicked(); + TSharedRef MakePlatformListItemWidget(TSharedPtr Platform, const TSharedRef& OwnerTable); + +private: + TSharedPtr>> PlatformList; + TSharedPtr>> LanguageList; + TSharedPtr SkipLanguagesCheckBox; + + TArray> PlatformNames; + TArray> LanguagesNames; + + WwiseProjectInfo wwiseProjectInfo; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Visualizer/AkAcousticPortalVisualizer.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Visualizer/AkAcousticPortalVisualizer.cpp new file mode 100644 index 0000000..8b3c780 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Visualizer/AkAcousticPortalVisualizer.cpp @@ -0,0 +1,154 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= +AkAcousticPortalVisualizer.cpp: +=============================================================================*/ + +#include "AkAcousticPortalVisualizer.h" + +#include "AkUEFeatures.h" +#include "AkSpatialAudioDrawUtils.h" +#include "AkDrawPortalComponent.h" +#include "AkRoomComponent.h" +#include "SceneManagement.h" +#include "DynamicMeshBuilder.h" +#include "EditorModes.h" +#include "Materials/Material.h" + +void UAkPortalComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) +{ + const UAkPortalComponent* PortalComponent = Cast(Component); + if (IsValid(PortalComponent) && IsValid(PortalComponent->GetPrimitiveParent())) + { + const UPrimitiveComponent* PrimitiveParent = Cast(PortalComponent->GetPrimitiveParent()); + // Calculate the unscaled, unrotated box extent of the primitive parent component, at origin. + FVector BoxExtent = PrimitiveParent->CalcLocalBounds().BoxExtent; + FDynamicMeshBuilder MeshBuilderFront(ERHIFeatureLevel::Type::ES3_1); + FDynamicMeshBuilder MeshBuilderBack(ERHIFeatureLevel::Type::ES3_1); + + MeshBuilderFront.AddVertices({ + FUnrealFloatVector(BoxExtent), // FRU + FUnrealFloatVector(BoxExtent.X, BoxExtent.Y, -BoxExtent.Z), // FRD + FUnrealFloatVector(0.0f, BoxExtent.Y, -BoxExtent.Z), // RD + FUnrealFloatVector(0.0f, BoxExtent.Y, BoxExtent.Z), // RU + FUnrealFloatVector(BoxExtent.X, -BoxExtent.Y, BoxExtent.Z), // FLU + FUnrealFloatVector(BoxExtent.X, -BoxExtent.Y, -BoxExtent.Z), // FLD + FUnrealFloatVector(0.0f, -BoxExtent.Y, -BoxExtent.Z), // LD + FUnrealFloatVector(0.0f, -BoxExtent.Y, BoxExtent.Z) // LU + }); + + MeshBuilderBack.AddVertices({ + FUnrealFloatVector(-BoxExtent.X, -BoxExtent.Y, BoxExtent.Z), // BLU + FUnrealFloatVector(-BoxExtent), // BLD + FUnrealFloatVector(0.0f, -BoxExtent.Y, -BoxExtent.Z), // LD + FUnrealFloatVector(0.0f, -BoxExtent.Y, BoxExtent.Z), // LU + FUnrealFloatVector(-BoxExtent.X, BoxExtent.Y, BoxExtent.Z), // BRU + FUnrealFloatVector(-BoxExtent.X, BoxExtent.Y, -BoxExtent.Z), // BRD + FUnrealFloatVector(0.0f, BoxExtent.Y, -BoxExtent.Z), // RD + FUnrealFloatVector(0.0f, BoxExtent.Y, BoxExtent.Z) // RU + }); + + // add vertices using front - back, right - left, up - down winding. + MeshBuilderFront.AddTriangles + ({ + /*front face*/0, 3, 2, 0, 2, 1, + /*back face*/4, 7, 6, 4, 6, 5, + /*top face*/0, 3, 7, 0, 7, 4, + /*bottom face*/1, 2, 6, 1, 6, 5 + }); + + MeshBuilderBack.AddTriangles + ({ + /*front face*/0, 3, 2, 0, 2, 1, + /*back face*/4, 7, 6, 4, 6, 5, + /*top face*/0, 3, 7, 0, 7, 4, + /*bottom face*/1, 2, 6, 1, 6, 5 + }); + + // Allocate the material proxy and register it so it can be deleted properly once the rendering is done with it. + auto* renderProxy = GEngine->GeomMaterial->GetRenderProxy(); + FLinearColor FrontDrawColor; + FLinearColor BackDrawColor; + AkSpatialAudioColors::GetPortalColors(PortalComponent, FrontDrawColor, BackDrawColor); + + FDynamicColoredMaterialRenderProxy* ColorInstanceFront = new FDynamicColoredMaterialRenderProxy(renderProxy, FrontDrawColor); + PDI->RegisterDynamicResource(ColorInstanceFront); + + MeshBuilderFront.Draw(PDI, PrimitiveParent->GetComponentToWorld().ToMatrixWithScale(), ColorInstanceFront, SDPG_World, true, false); + + FDynamicColoredMaterialRenderProxy* ColorInstanceBack = new FDynamicColoredMaterialRenderProxy(renderProxy, BackDrawColor); + PDI->RegisterDynamicResource(ColorInstanceBack); + + MeshBuilderBack.Draw(PDI, PrimitiveParent->GetComponentToWorld().ToMatrixWithScale(), ColorInstanceBack, SDPG_World, true, false); + + // Draw an outline around the centre of the portal, to distinguish front and back + const FTransform& T = PrimitiveParent->GetComponentTransform(); + AkDrawBounds DrawBounds(T, BoxExtent); + const float Thickness = AkDrawConstants::PortalRoomConnectionThickness; + const FLinearColor OutlineColor = AkSpatialAudioColors::GetPortalOutlineColor(PortalComponent); + PDI->DrawLine(DrawBounds.RU(), DrawBounds.LU(), OutlineColor, SDPG_Foreground, Thickness); + PDI->DrawLine(DrawBounds.LU(), DrawBounds.LD(), OutlineColor, SDPG_Foreground, Thickness); + PDI->DrawLine(DrawBounds.LD(), DrawBounds.RD(), OutlineColor, SDPG_Foreground, Thickness); + PDI->DrawLine(DrawBounds.RD(), DrawBounds.RU(), OutlineColor, SDPG_Foreground, Thickness); + // Draw a line from back room to front room. + FVector Front = FVector(BoxExtent.X, 0.0f, 0.0f); + FVector Back = FVector(-BoxExtent.X, 0.0f, 0.0f); + PDI->DrawLine(T.TransformPosition(Back), T.TransformPosition(Front), OutlineColor, SDPG_Foreground, Thickness); + + // draw a diagonal on left and right faces if the portal is closed + if (PortalComponent->InitialState == AkAcousticPortalState::Closed) + { + PDI->DrawLine(DrawBounds.FRU(), DrawBounds.BRD(), FrontDrawColor, SDPG_Foreground, Thickness); + PDI->DrawLine(DrawBounds.FLD(), DrawBounds.BLU(), BackDrawColor, SDPG_Foreground, Thickness); + } + + PortalComponent->UpdateTextRotations(); + } + + if (GEditor->GetSelectedActorCount() == 1 && IsValid(PortalComponent)) + { + AAkAcousticPortal* pPortal = Cast(PortalComponent->GetOwner()); + if (pPortal && pPortal->GetFitToGeometry() && pPortal->GetIsDragging()) + { + FVector Point0, End0, Point1, End1; + if (pPortal->GetBestHits(Point0, End0, Point1, End1)) + { + FVector Dir0 = End0 - Point0; + float L0 = Dir0.SizeSquared(); + + FVector Dir1 = End1 - Point1; + float L1 = Dir1.SizeSquared(); + + FVector V01 = Point1 - Point0; + FVector TL0 = Point1 - FVector::DotProduct(V01, Dir0) * Dir0 / L0; + FVector TL1 = Point0 - FVector::DotProduct(-V01, Dir1) * Dir1 / L1; + FVector TL = (TL0 + TL1) / 2.f; + FVector TR = TL + Dir0; + FVector BL = TL + Dir1; + FVector BR = BL + Dir0; + + const FLinearColor PreviewColor = FAkAppStyle::Get().GetSlateColor("SelectionColor").GetSpecifiedColor() * 1.3f; + + PDI->DrawLine(TL, TR, PreviewColor, 100, 5.f, 50.f); + PDI->DrawLine(TR, BR, PreviewColor, 100, 5.f, 50.f); + PDI->DrawLine(BR, BL, PreviewColor, 100, 5.f, 50.f); + PDI->DrawLine(BL, TL, PreviewColor, 100, 5.f, 50.f); + } + } + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Visualizer/AkAcousticPortalVisualizer.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Visualizer/AkAcousticPortalVisualizer.h new file mode 100644 index 0000000..8705801 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Visualizer/AkAcousticPortalVisualizer.h @@ -0,0 +1,29 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= +AkAcousticPortalVisualizer.h: +=============================================================================*/ + +#pragma once +#include "ComponentVisualizer.h" + +class UAkPortalComponentVisualizer : public FComponentVisualizer +{ +public: + virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) override; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Visualizer/AkComponentVisualizer.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Visualizer/AkComponentVisualizer.cpp new file mode 100644 index 0000000..395b371 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Visualizer/AkComponentVisualizer.cpp @@ -0,0 +1,104 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkComponentVisualizer.cpp: +=============================================================================*/ +#include "AkComponentVisualizer.h" +#include "AkAudioDevice.h" +#include "AkComponent.h" +#include "AkAudioEvent.h" +#include "SceneView.h" +#include "SceneManagement.h" + +#include "AkWaapiClient.h" +#include "AkWaapiUtils.h" + +namespace FAkComponentVisualizer_Helper +{ + float GetRadius(const UAkComponent* AkComponent) + { + if (auto waapiClient = FAkWaapiClient::Get()) + { + auto AkAudioEventName = GET_AK_EVENT_NAME(AkComponent->AkAudioEvent, AkComponent->EventName); + if (!AkAudioEventName.IsEmpty()) + { + TSharedRef args = MakeShared(); + { + TSharedPtr from = MakeShared(); + from->SetArrayField(WwiseWaapiHelper::NAME, TArray> + { + MakeShared(FString::Printf(TEXT("Event:%s"), *AkAudioEventName)) + }); + args->SetObjectField(WwiseWaapiHelper::FROM, from); + } + + TSharedRef options = MakeShared(); + options->SetArrayField(WwiseWaapiHelper::RETURN, TArray> + { + MakeShared(WwiseWaapiHelper::MAX_RADIUS_ATTENUATION) + }); + +#if AK_SUPPORT_WAAPI + TSharedPtr result; + if (waapiClient->Call(ak::wwise::core::object::get, args, options, result, 500, true)) + { + TArray> ReturnArray = result->GetArrayField(WwiseWaapiHelper::RETURN); + if (ReturnArray.Num() > 0) + { + const TSharedPtr* AttenuationObjectPtr = nullptr; + if (ReturnArray[0]->AsObject()->TryGetObjectField(WwiseWaapiHelper::MAX_RADIUS_ATTENUATION, AttenuationObjectPtr)) + { + return (*AttenuationObjectPtr)->GetNumberField(WwiseWaapiHelper::RADIUS); + } + } + } +#endif + } + } + + return AkComponent->GetAttenuationRadius(); + } +} + +void FAkComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) +{ + const UAkComponent* AkComponent = Cast(Component); + if (!AkComponent) + return; + + if (AkComponent->outerRadius != 0.f) + { + FColor RadialEmitterColor(255, 255, 0); + DrawWireSphereAutoSides(PDI, AkComponent->GetComponentLocation(), RadialEmitterColor, AkComponent->outerRadius, SDPG_World); + } + if (AkComponent->innerRadius != 0.f) + { + FColor RadialEmitterColor(204, 204, 0); + DrawWireSphereAutoSides(PDI, AkComponent->GetComponentLocation(), RadialEmitterColor, AkComponent->innerRadius, SDPG_World); + } + + if (!View->Family->EngineShowFlags.AudioRadius) + return; + + auto radius = FAkComponentVisualizer_Helper::GetRadius(AkComponent); + if (radius <= 0.0f) + return; + + FColor AudioOuterRadiusColor(255, 153, 0); + DrawWireSphereAutoSides(PDI, AkComponent->GetComponentLocation(), AudioOuterRadiusColor, radius, SDPG_World); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Visualizer/AkComponentVisualizer.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Visualizer/AkComponentVisualizer.h new file mode 100644 index 0000000..a513e01 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Visualizer/AkComponentVisualizer.h @@ -0,0 +1,30 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkComponentVisualizer.h: +=============================================================================*/ + +#pragma once + +#include "ComponentVisualizer.h" + +class FAkComponentVisualizer : public FComponentVisualizer +{ +public: + virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) override; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Visualizer/AkSurfaceReflectorSetComponentVisualizer.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Visualizer/AkSurfaceReflectorSetComponentVisualizer.cpp new file mode 100644 index 0000000..0da2cd4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Visualizer/AkSurfaceReflectorSetComponentVisualizer.cpp @@ -0,0 +1,258 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= +AkWwiseAcousticsComponentVisualizer.cpp: +=============================================================================*/ +#include "AkSurfaceReflectorSetComponentVisualizer.h" +#include "AkAudioDevice.h" +#include "AkSurfaceReflectorSetComponent.h" +#include "AkSpatialAudioVolume.h" +#include "AkUEFeatures.h" +#include "Editor.h" +#include "EditorModeManager.h" +#include "EditorModes.h" +#include "Components/BrushComponent.h" +#include "Components/TextRenderComponent.h" +#include "Model.h" +#include "Engine/Polys.h" +#include "EngineGlobals.h" +#include "DynamicMeshBuilder.h" +#include "Materials/Material.h" +#include "Editor.h" +#include "LevelEditorViewport.h" +#include "AkSpatialAudioDrawUtils.h" +#include "Components/BrushComponent.h" +#include "Engine/Canvas.h" + +#include + +#include "SceneView.h" + +#define AK_VISUALIZE_HIT_MATERIALS 0 + +void FAkSurfaceReflectorSetComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) +{ + if (Component == nullptr) + { + return; + } + + const UAkSurfaceReflectorSetComponent* SurfaceReflectorSet = Cast(Component); + const AAkSpatialAudioVolume* SpatialAudioVolume = Cast(Component->GetOwner()); + + if (SpatialAudioVolume == nullptr) + return; + + auto ModelBrush = SurfaceReflectorSet->ParentBrush; + if (SurfaceReflectorSet != nullptr && + ModelBrush != nullptr && + !SpatialAudioVolume->IsHiddenEd()) + { + // Build a mesh by basically drawing the triangles of each + for (int32 NodeIdx = 0; + NodeIdx < ModelBrush->Nodes.Num() && NodeIdx < SurfaceReflectorSet->TextVisualizers.Num(); + ++NodeIdx) + { + FDynamicMeshBuilder MeshBuilder(ERHIFeatureLevel::Type::ES3_1); + int32 VertStartIndex = SurfaceReflectorSet->ParentBrush->Nodes[NodeIdx].iVertPool; + + FUnrealFloatVector normal(ModelBrush->Nodes[NodeIdx].Plane); + if (SurfaceReflectorSet->AcousticPolys.Num() > NodeIdx) + { + if (SurfaceReflectorSet->AcousticPolys[NodeIdx].EnableSurface && + ModelBrush->Nodes[NodeIdx].NumVertices > 2) + { + // Shift the vertices in towards the center (negatively along the normal) so that they don't obstruct face selection in geometry mode / brush editing mode. + FUnrealFloatVector offset = normal * 0.1f; + + const FVert& Vert0 = ModelBrush->Verts[VertStartIndex + 0]; + const FVert& Vert1 = ModelBrush->Verts[VertStartIndex + 1]; + const FVert& Vert2 = ModelBrush->Verts[VertStartIndex + 2]; + FUnrealFloatVector Vertex0 = ModelBrush->Points[Vert0.pVertex] - offset; + FUnrealFloatVector Vertex1 = ModelBrush->Points[Vert1.pVertex] - offset; + FUnrealFloatVector Vertex2 = ModelBrush->Points[Vert2.pVertex] - offset; + + MeshBuilder.AddVertex(Vertex0, FUnrealFloatVector2D::ZeroVector, FUnrealFloatVector(1, 0, 0), FUnrealFloatVector(0, 1, 0), FUnrealFloatVector(0, 0, 1), FColor::White); + MeshBuilder.AddVertex(Vertex1, FUnrealFloatVector2D::ZeroVector, FUnrealFloatVector(1, 0, 0), FUnrealFloatVector(0, 1, 0), FUnrealFloatVector(0, 0, 1), FColor::White); + MeshBuilder.AddVertex(Vertex2, FUnrealFloatVector2D::ZeroVector, FUnrealFloatVector(1, 0, 0), FUnrealFloatVector(0, 1, 0), FUnrealFloatVector(0, 0, 1), FColor::White); + MeshBuilder.AddTriangle(0, 2, 1); + MeshBuilder.AddTriangle(1, 2, 0); + + for (int32 VertexIdx = 3; VertexIdx < ModelBrush->Nodes[NodeIdx].NumVertices; ++VertexIdx) + { + const FVert& Vert3 = ModelBrush->Verts[VertStartIndex + VertexIdx]; + FUnrealFloatVector Vertex3 = ModelBrush->Points[Vert3.pVertex] - offset; + + MeshBuilder.AddVertex(Vertex3, FUnrealFloatVector2D::ZeroVector, FUnrealFloatVector(1, 0, 0), FUnrealFloatVector(0, 1, 0), FUnrealFloatVector(0, 0, 1), FColor::White); + MeshBuilder.AddTriangle(0, VertexIdx, VertexIdx - 1); + MeshBuilder.AddTriangle(VertexIdx - 1, VertexIdx, 0); + } + + FLinearColor SurfaceColor = AkSpatialAudioColors::GetSurfaceReflectorColor(SurfaceReflectorSet, NodeIdx, SpatialAudioVolume->IsDragging); + auto* renderProxy = GEngine->GeomMaterial->GetRenderProxy(); + // Limit the color's value (in HSV space) so that it doesn't obscure the text. + // In the new color, R = H, G = S, B = V, A = A (From Color.cpp line ~271) + FLinearColor hsv = SurfaceColor.LinearRGBToHSV(); + hsv.B = FMath::Min(0.25f, hsv.B); + SurfaceColor = hsv.HSVToLinearRGB(); + FDynamicColoredMaterialRenderProxy* MatProxy = new FDynamicColoredMaterialRenderProxy(renderProxy, SurfaceColor); + PDI->RegisterDynamicResource(MatProxy); + MeshBuilder.Draw(PDI, SpatialAudioVolume->ActorToWorld().ToMatrixWithScale(), MatProxy, SDPG_World, true); + } + } + } + + SurfaceReflectorSet->UpdateTextPositions(); + + if (!GLevelEditorModeTools().IsModeActive(FEditorModeID(TEXT("EM_Geometry")))) + { + for (auto& KV : SurfaceReflectorSet->EdgeMap) + { + auto& EdgeInfo = KV.Value; + + if (!EdgeInfo.IsFlat && (!EdgeInfo.IsBoundary || SurfaceReflectorSet->bEnableDiffractionOnBoundaryEdges)) + { + FLinearColor EdgeColor = AkSpatialAudioColors::GetSpatialAudioVolumeOutlineColor(); + float Thickness = SurfaceReflectorSet->bEnableSurfaceReflectors ? AkDrawConstants::SpatialAudioVolumeOutlineThickness : AkDrawConstants::RoomOutlineThickness; + if (EdgeInfo.IsEnabled && + SurfaceReflectorSet->bEnableSurfaceReflectors && + SurfaceReflectorSet->bEnableDiffraction) + { + EdgeColor = EdgeInfo.IsBoundary ? AkSpatialAudioColors::GetBoundaryDiffractionEdgeColor() : AkSpatialAudioColors::GetDiffractionEdgeColor(); + Thickness = AkDrawConstants::DiffractionEdgeThickness; + } + PDI->DrawLine( + SpatialAudioVolume->GetActorTransform().TransformPosition(EdgeInfo.V0()), + SpatialAudioVolume->GetActorTransform().TransformPosition(EdgeInfo.V1()), + EdgeColor, SDPG_World, Thickness); + } + } + } + } + + if (GEditor->GetSelectedActorCount() == 1 && + (!SpatialAudioVolume->IsHiddenEd() || SpatialAudioVolume->IsDragging) && + SpatialAudioVolume->FitToGeometry) + { + for (int i = 0; i < SpatialAudioVolume->GetRaycastHits().Num(); ++i) + { + int Threshold = (int)(SpatialAudioVolume->GetFitScale() * SpatialAudioVolume->GetRaycastHits().Num()); + FColor col = i <= Threshold ? FColor::Green : FColor::Red; + PDI->DrawLine(SpatialAudioVolume->FitPoints[i], SpatialAudioVolume->FitPoints[i] + SpatialAudioVolume->FitNormals[i] * 10.f, col, 100); + PDI->DrawPoint(SpatialAudioVolume->GetRaycastHits()[i], col, 5.f, 100); + } + + if (SpatialAudioVolume->IsDragging) + { + for (auto& EdgePair : SpatialAudioVolume->PreviewOutline) + { + PDI->DrawLine(EdgePair.Key, EdgePair.Value, AkSpatialAudioColors::GetSpatialAudioVolumeOutlineColor() * 1.35f, SDPG_Foreground, 7.0f); + } + } + + if (SpatialAudioVolume->FitFailed && + !SpatialAudioVolume->IsDragging && + SpatialAudioVolume->GetBrushComponent()) + { + static const float kFlashPeriodSeconds = 0.3f; + static const float kFlashDutyCycle = 0.5f; + static const float kFlashDurationSeconds = 0.6f; + + if (FlashTimer == FLT_MAX) + FlashTimer = 0.f; + + if (fmod(FlashTimer, kFlashPeriodSeconds) < kFlashDutyCycle*kFlashPeriodSeconds && + FlashTimer < kFlashDurationSeconds) + { + UModel* Brush = SpatialAudioVolume->GetBrushComponent()->Brush; + if (Brush) + { + for (int32 NodeIdx = 0; NodeIdx < Brush->Nodes.Num(); ++NodeIdx) + { + int32 VertStartIndex = Brush->Nodes[NodeIdx].iVertPool; + int32 NumVerts = Brush->Nodes[NodeIdx].NumVertices; + for (int32 j = NumVerts - 1, i = 0; i < NumVerts; j = i, ++i) + { + auto V0 = Brush->Points[Brush->Verts[VertStartIndex + i].pVertex]; + auto V1 = Brush->Points[Brush->Verts[VertStartIndex + j].pVertex]; + + FVector WorldV0 = SpatialAudioVolume->GetActorTransform().TransformPosition(FVector(V0)); + FVector WorldV1 = SpatialAudioVolume->GetActorTransform().TransformPosition(FVector(V1)); + + PDI->DrawLine(WorldV0, WorldV1, AkSpatialAudioColors::GetBadFitSpatialAudioVolumeOutlineColor(), SDPG_Foreground, 7.0f); + } + } + } + } + +#if UE_5_0_OR_LATER + FlashTimer += View->Family->Time.GetDeltaWorldTimeSeconds(); +#else + FlashTimer += View->Family->DeltaWorldTime; +#endif + } + else + { + FlashTimer = FLT_MAX; + } + } +} + +void FAkSurfaceReflectorSetComponentVisualizer::DrawVisualizationHUD(const UActorComponent* Component, const FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) +{ + if (GLevelEditorModeTools().IsModeActive(FEditorModeID(TEXT("EM_Geometry"))) || Component == nullptr) + { + return; + } + + const AAkSpatialAudioVolume* SpatialAudioVolume = Cast(Component->GetOwner()); + + if (SpatialAudioVolume == nullptr) + return; + + if (SpatialAudioVolume->FitToGeometry && + SpatialAudioVolume->FitFailed && + !SpatialAudioVolume->IsDragging) + { + static FText Text = FText::FromString("Fit to geometry failed. Try moving Spatial Audio Volume to a new location."); + FCanvasTextItem TextItem(FVector2D::ZeroVector, Text, GEngine->GetSmallFont(), AkSpatialAudioColors::GetBadFitSpatialAudioVolumeOutlineColor()); + float YPos = (Canvas->GetViewRect().Height() / Canvas->GetDPIScale()) - GEngine->GetSmallFont()->GetMaxCharHeight(); + Canvas->DrawItem(TextItem, 0, YPos); + } + +#if AK_VISUALIZE_HIT_MATERIALS + if (SpatialAudioVolume->FitToGeometry) + { + for (int i = 0; i < SpatialAudioVolume->GetRaycastHits().Num()*SpatialAudioVolume->GetFitScale(); ++i) + { + FVector Location = SpatialAudioVolume->GetRaycastHits()[i]; + FPlane Proj = View->Project(Location); + if (Proj.W > 0.f) + { + float Height = Canvas->GetViewRect().Height() / Canvas->GetDPIScale(); + float Width = Canvas->GetViewRect().Width() / Canvas->GetDPIScale(); + float X = (Width / 2.f) + (Proj.X * (Width / 2.f)); + float Y = (Height / 2.f) + (-Proj.Y * (Height / 2.f)); + + FCanvasTextItem TextItem(FVector2D::ZeroVector, FText::FromString(SpatialAudioVolume->FitMaterials[i]->GetName()), GEngine->GetTinyFont(), FColor::Green); + Canvas->DrawItem(TextItem, X, Y); + } + } + } +#endif +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Visualizer/AkSurfaceReflectorSetComponentVisualizer.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Visualizer/AkSurfaceReflectorSetComponentVisualizer.h new file mode 100644 index 0000000..1a11509 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/Visualizer/AkSurfaceReflectorSetComponentVisualizer.h @@ -0,0 +1,38 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + AkWwiseAcousticsComponentVisualizer.h: +=============================================================================*/ + +#pragma once + +#include "ComponentVisualizer.h" + +class UTextRenderComponent; + +class FAkSurfaceReflectorSetComponentVisualizer : public FComponentVisualizer +{ + +public: + virtual void DrawVisualization( const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI ) override; + virtual void DrawVisualizationHUD(const UActorComponent* Component, const FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) override; + void PointTextAtCamera(UTextRenderComponent* textComp, float actorScaling, float maxBoundsDimension); + +private: + float FlashTimer = FLT_MAX; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WaapiPlaybackTransport/WaapiPlaybackTransport.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WaapiPlaybackTransport/WaapiPlaybackTransport.cpp new file mode 100644 index 0000000..6d84756 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WaapiPlaybackTransport/WaapiPlaybackTransport.cpp @@ -0,0 +1,225 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "WaapiPlaybackTransport.h" + +#include "AkWaapiUtils.h" +#include "IAudiokineticTools.h" +#include "Async/Async.h" + + +WaapiPlaybackTransport::~WaapiPlaybackTransport() +{ + StopAndDestroyAll(); +} + +int32 WaapiPlaybackTransport::FindOrAdd(const FGuid& InItemID) +{ + const FString itemIdStringField = InItemID.ToString(EGuidFormats::DigitsWithHyphensInBraces); + TSharedPtr Result; + int32 TransportID = -1; + + { + FScopeLock AutoLock(&TransportItemsLock); + if (TransportItems.Contains(InItemID)) + { + return TransportItems[InItemID].TransportID; + } + } + +#if AK_SUPPORT_WAAPI + auto WaapiClient = FAkWaapiClient::Get(); + if(!WaapiClient) + { + UE_LOG(LogAudiokineticTools, Log, TEXT("Unable to connect to Waapi")); + return TransportID; + } + if (WaapiClient->Call(ak::wwise::core::transport::create, { { WwiseWaapiHelper::OBJECT, itemIdStringField } }, Result)) + { + TransportID = Result->GetIntegerField(WwiseWaapiHelper::TRANSPORT); + uint64 SubscriptionID = SubscribeToStateChanged(TransportID); + + { + FScopeLock AutoLock(&TransportItemsLock); + TransportItems.Add(InItemID, TransportInfo(TransportID, SubscriptionID)); + } + } +#endif + + return TransportID; +} + +void WaapiPlaybackTransport::Remove(const FGuid& InItemID) +{ + auto WaapiClient = FAkWaapiClient::Get(); + if (!WaapiClient) + return; + + uint64 SubscriptionID; + TransportInfo Item(0, 0); + + { + FScopeLock AutoLock(&TransportItemsLock); + if (!TransportItems.Contains(InItemID)) + { + return; + } + + Item = TransportItems[InItemID]; + + } + + SubscriptionID = Item.SubscriptionID; + + TSharedRef Args = MakeShared(); + Args->SetNumberField(WwiseWaapiHelper::TRANSPORT, Item.TransportID); + + TSharedPtr Result; + if (SubscriptionID != 0) + { + WaapiClient->Unsubscribe(SubscriptionID, Result); + } + +#if AK_SUPPORT_WAAPI + TSharedRef Options = MakeShared(); + if (WaapiClient->Call(ak::wwise::core::transport::destroy, Args, Options, Result)) + { + { + FScopeLock AutoLock(&TransportItemsLock); + TransportItems.Remove(InItemID); + } + } +#endif +} + +void WaapiPlaybackTransport::TogglePlay(int32 InTransportID) +{ + auto WaapiClient = FAkWaapiClient::Get(); + if (!WaapiClient) + { + UE_LOG(LogAudiokineticTools, Log, TEXT("Unable to connect to localhost")); + return; + } + + TSharedRef Args = MakeShared(); + Args->SetStringField(WwiseWaapiHelper::ACTION, WwiseWaapiHelper::PLAYSTOP); + Args->SetNumberField(WwiseWaapiHelper::TRANSPORT, InTransportID); + +#if AK_SUPPORT_WAAPI + + TSharedPtr Result; + TSharedRef Options = MakeShared(); + if (!WaapiClient->Call(ak::wwise::core::transport::executeAction, Args, Options, Result)) + { + UE_LOG(LogAudiokineticTools, Log, TEXT("Failed to trigger playback")); + } +#endif +} + +void WaapiPlaybackTransport::Stop(int32 InTransportID) +{ + auto WaapiClient = FAkWaapiClient::Get(); + if (!WaapiClient) + return; + + TSharedRef Args = MakeShared(); + Args->SetStringField(WwiseWaapiHelper::ACTION, WwiseWaapiHelper::STOP); + Args->SetNumberField(WwiseWaapiHelper::TRANSPORT, InTransportID); + +#if AK_SUPPORT_WAAPI + TSharedPtr Result; + TSharedRef Options = MakeShared(); + if (!WaapiClient->Call(ak::wwise::core::transport::executeAction, Args, Options, Result)) + { + UE_LOG(LogAudiokineticTools, Log, TEXT("Cannot stop event.")); + } +#endif +} + +void WaapiPlaybackTransport::StopAndDestroyAll() +{ + FScopeLock AutoLock(&TransportItemsLock); + + for (auto Iter = TransportItems.CreateIterator(); Iter; ++Iter) + { + Stop(Iter.Value().TransportID); + Remove(Iter.Key()); + } + + TransportItems.Empty(); +} + +bool WaapiPlaybackTransport::IsPlaying(const FGuid& InItemID) +{ + if (!FAkWaapiClient::Get()) + { + return false; + } + + return TransportItems.Contains(InItemID); +} + +uint64 WaapiPlaybackTransport::SubscribeToStateChanged(int32 TransportID) +{ + auto WaapiClient = FAkWaapiClient::Get(); + + if (!WaapiClient) + return 0; + + auto WampEventCallback = WampEventCallback::CreateLambda( + [this](uint64_t ID, TSharedPtr UEJsonObject) + { + AsyncTask(ENamedThreads::AnyThread, [this, UEJsonObject] + { + this->OnStateChanged(UEJsonObject); + }); + }); + + TSharedRef Options = MakeShared(); + Options->SetNumberField(WwiseWaapiHelper::TRANSPORT, TransportID); + + TSharedPtr OutJsonResult; + uint64 SubscriptionID = 0; +#if AK_SUPPORT_WAAPI + WaapiClient->Subscribe(ak::wwise::core::transport::stateChanged, Options, WampEventCallback, SubscriptionID, OutJsonResult); +#endif + return SubscriptionID; +} + +void WaapiPlaybackTransport::OnStateChanged(TSharedPtr InUEJsonObject) +{ + const FString NewState = InUEJsonObject->GetStringField(WwiseWaapiHelper::STATE); + FGuid ItemID; + FGuid::Parse(InUEJsonObject->GetStringField(WwiseWaapiHelper::OBJECT), ItemID); + const int32 TransportID = InUEJsonObject->GetNumberField(WwiseWaapiHelper::TRANSPORT); + + if (NewState == WwiseWaapiHelper::STOPPED) + { + Remove(ItemID); + } + + else if (NewState == WwiseWaapiHelper::PLAYING) + { + { + FScopeLock AutoLock(&TransportItemsLock); + if (!TransportItems.Contains(ItemID)) + { + TransportItems.Add(ItemID, TransportInfo(TransportID, 0)); + } + } + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/IWwiseBrowserDataSource.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/IWwiseBrowserDataSource.h new file mode 100644 index 0000000..20a95a6 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/IWwiseBrowserDataSource.h @@ -0,0 +1,32 @@ +#pragma once + +#include "WwiseItemType.h" +#include "Misc/TextFilter.h" +#include "WwiseBrowser/WwiseBrowserForwards.h" + +typedef TTextFilter< const FString& > StringFilter; + +class IWwiseBrowserDataSource +{ +public: + virtual ~IWwiseBrowserDataSource() = default; + + virtual bool Init() = 0; + + virtual void ConstructTree(bool bShouldRefresh) = 0; + + // Constructs the top-level root for the given type, along with its direct children + virtual FWwiseTreeItemPtr ConstructTreeRoot(EWwiseItemType::Type Type) = 0; + + // Loads children for the item matching first InParentId, then InParentPath + virtual int32 LoadChildren(const FGuid& InParentId, const FString& InParentPath, TArray& OutChildren) = 0; + + // Loads children for the item + virtual int32 LoadChildren(FWwiseTreeItemPtr ParentTreeItem) = 0; + + virtual int32 GetChildItemCount(const FWwiseTreeItemPtr& InParentIrem) = 0; + + virtual FWwiseTreeItemPtr GetRootItem(EWwiseItemType::Type RootType) = 0; + + virtual FWwiseTreeItemPtr LoadFilteredRootItem(EWwiseItemType::Type Type, TSharedPtr CurrentFilterText) = 0; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/UAssetDataSource.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/UAssetDataSource.cpp new file mode 100644 index 0000000..b097709 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/UAssetDataSource.cpp @@ -0,0 +1,219 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "UAssetDataSource.h" + +#include "AkUnrealAssetDataHelper.h" +#include "AssetManagement/AkAssetDatabase.h" +#include "WwiseBrowser/WwiseBrowserHelpers.h" + +UAssetDataSourceInformation FUAssetDataSource::CreateUAssetInfo(const UAssetDataSourceId& Id, const FAssetData& Asset) +{ + auto AssetInfo = UAssetDataSourceInformation(); + AssetInfo.AssetName = Asset.AssetName; + AssetInfo.Id = Id; + AssetInfo.Type = WwiseBrowserHelpers::GetTypeFromClass(Asset.GetClass()); + AssetInfo.AssetsData.Add(Asset); + return AssetInfo; +} + +void FUAssetDataSource::ConstructItems() +{ + OrphanedItems.Empty(); + UsedItems.Empty(); + TArray AllAssets; + AkAssetDatabase::Get().FindAllAssets(AllAssets); + + for (auto& Asset : AllAssets) + { + if(WwiseBrowserHelpers::GetTypeFromClass(Asset.GetClass()) == EWwiseItemType::InitBank) + { + continue; + } + auto GuidValue = Asset.TagsAndValues.FindTag(FName("WwiseGuid")); + auto ShortIdValue = Asset.TagsAndValues.FindTag(FName("WwiseShortId")); + UAssetDataSourceId Id; + Id.Name = Asset.AssetName; + if (GuidValue.IsSet()) + { + FString GuidAsString = GuidValue.GetValue(); + FGuid Guid = FGuid(GuidAsString); + Id.ItemId = Guid; + } + if (ShortIdValue.IsSet()) + { + Id.ShortId = FCString::Strtoui64(*ShortIdValue.GetValue(), NULL, 10); + } + Id.Name = Asset.AssetName; + if(Id.ItemId.IsValid()) + { + if (auto UAssetInfo = OrphanedItems.Find(Id.ItemId)) + { + UAssetInfo->AssetsData.Add(Asset); + } + else + { + auto AssetInfo = CreateUAssetInfo(Id, Asset); + OrphanedItems.Add(Id.ItemId, AssetInfo); + } + } + else if(Id.ShortId > 0) + { + if(auto UAsset = UAssetWithoutGuid.Find(Id.ShortId)) + { + UAsset->AssetsData.Add(Asset); + } + else + { + auto AssetInfo = CreateUAssetInfo(Id, Asset); + UAssetWithoutGuid.Add(Id.ShortId, AssetInfo); + } + } + else + { + if(auto UAsset = UAssetWithoutShortId.Find(Id.Name)) + { + UAsset->AssetsData.Add(Asset); + } + else + { + auto AssetInfo = CreateUAssetInfo(Id, Asset); + UAssetWithoutShortId.Add(Id.Name, AssetInfo); + } + } + } +} + +void FUAssetDataSource::GetAssetsInfo(FGuid ItemId, uint32 ShortId, FString Name, EWwiseItemType::Type& ItemType, FName& AssetName, TArray& Assets) +{ + UAssetDataSourceId Id; + Id.ItemId = ItemId; + Id.ShortId = ShortId; + Id.Name = FName(*Name); + if (auto Item = UsedItems.Find(Id.ItemId)) + { + Assets = Item->AssetsData; + ItemType = Item->Type; + AssetName = Item->AssetName; + } + else if (auto OrphanedItem = OrphanedItems.Find(Id.ItemId)) + { + UsedItems.Add(Id.ItemId, *OrphanedItem); + Assets = OrphanedItem->AssetsData; + ItemType = OrphanedItem->Type; + AssetName = OrphanedItem->AssetName; + OrphanedItems.Remove(Id.ItemId); + } + if(auto Item = UAssetWithoutGuid.Find(ShortId)) + { + auto GuidItem = UsedItems.Find(Id.ItemId); + for(auto Asset : Item->AssetsData) + { + if(!AkUnrealAssetDataHelper::IsSameType(Asset, Item->Type)) + { + continue; + } + Assets.Add(Asset); + if(AssetName == FName()) + { + AssetName = Item->AssetName; + ItemType = Item->Type; + } + if(GuidItem) + { + GuidItem->AssetsData.Add(Asset); + } + } + UAssetWithoutGuid.Remove(ShortId); + } + if(auto Item = UAssetWithoutShortId.Find(FName(*Name))) + { + auto GuidItem = UsedItems.Find(Id.ItemId); + for(auto Asset : Item->AssetsData) + { + if(!AkUnrealAssetDataHelper::IsSameType(Asset, Item->Type)) + { + continue; + } + Assets.Add(Asset); + if(AssetName == FName()) + { + AssetName = Item->AssetName; + ItemType = Item->Type; + } + if(GuidItem) + { + GuidItem->AssetsData.Add(Asset); + } + UAssetWithoutShortId.Remove(FName(*Name)); + } + } + + if(AssetName != FName()) + { + return; + } + + FGuid FoundKey = FGuid(); + for(auto& Item : OrphanedItems) + { + if(FoundKey.IsValid()) + { + break; + } + //States always share a state called None which has the same ShortId. Do not check for the ShortId in that case. + bool bShouldCheckShortId = !(Item.Value.Type == EWwiseItemType::State && Item.Value.AssetName.ToString().EndsWith("None")); + if((Item.Value.Id.ShortId == ShortId && ShortId > 0 && bShouldCheckShortId) + || Item.Value.AssetName == FName(*Name)) + { + for(auto Asset : Item.Value.AssetsData) + { + if(!AkUnrealAssetDataHelper::IsSameType(Asset, Item.Value.Type)) + { + continue; + } + Assets.Add(Asset); + if(AssetName == FName()) + { + AssetName = Item.Value.AssetName; + ItemType = Item.Value.Type; + } + FoundKey = Item.Value.Id.ItemId; + Item.Value.Id.ItemId = ItemId; + Item.Value.Id.ShortId = ShortId; + Item.Value.Id.Name = FName(*Name); + UsedItems.Add(ItemId, Item.Value); + break; + } + } + } + OrphanedItems.Remove(FoundKey); +} + +void FUAssetDataSource::GetOrphanAssets(TMap& OrphanAssets) const +{ + OrphanAssets = OrphanedItems; + for(auto ShortIdItem : UAssetWithoutGuid) + { + OrphanAssets.Add(FGuid(), ShortIdItem.Value); + } + + for(auto NameItem : UAssetWithoutShortId) + { + OrphanAssets.Add(FGuid(), NameItem.Value); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/UAssetDataSource.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/UAssetDataSource.h new file mode 100644 index 0000000..0c9e631 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/UAssetDataSource.h @@ -0,0 +1,75 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#include "CoreMinimal.h" + +#include "WwiseItemType.h" +#include "GenericPlatform/GenericPlatform.h" +#include "Templates/TypeHash.h" + +struct UAssetDataSourceId +{ + FGuid ItemId; + uint32 ShortId = 0; + FName Name; + inline const bool operator==(const UAssetDataSourceId& other) const + { + if (other.ItemId == ItemId && ItemId.IsValid()) + { + return true; + } + if (ShortId == other.ShortId && ShortId > 0) + { + return true; + } + return Name == other.Name; + } +}; + +struct UAssetDataSourceInformation +{ + EWwiseItemType::Type Type; + UAssetDataSourceId Id; + TArray AssetsData; + FName AssetName; + inline const bool operator==(const UAssetDataSourceId& other) const + { + return Id == other; + } + + inline const bool operator==(const UAssetDataSourceInformation& other) const + { + return Id == other.Id; + } +}; + +class FUAssetDataSource +{ + TMap OrphanedItems; + TMap UsedItems; + //UAssets with invalid Guid will use the ShortId to sync with the Project Database. + TMap UAssetWithoutGuid; + //UAssets with invalid Guid and ShortId will use the AssetName to sync with the Project Database. + TMap UAssetWithoutShortId; + + UAssetDataSourceInformation CreateUAssetInfo(const UAssetDataSourceId& Id, const FAssetData& Asset); +public: + void ConstructItems(); + void GetAssetsInfo(FGuid ItemId, uint32 ShortId, FString Name, EWwiseItemType::Type& ItemType, FName& AssetName, TArray& Assets); + void GetOrphanAssets(TMap& OrphanAssets) const; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/WaapiDataSource.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/WaapiDataSource.cpp new file mode 100644 index 0000000..141e4e8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/WaapiDataSource.cpp @@ -0,0 +1,1169 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "WaapiDataSource.h" + +#include "AkSettings.h" +#include "AkSettingsPerUser.h" +#include "AkWaapiUtils.h" +#include "IAudiokineticTools.h" +#include "PackageTools.h" +#include "WwiseItemType.h" +#include "Async/Async.h" +#include "Dom/JsonObject.h" +#include "Misc/Paths.h" +#include "WaapiPicker/WwiseTreeItem.h" + +FWaapiDataSource::~FWaapiDataSource() +{ + TearDown(); +} + +bool FWaapiDataSource::Init() +{ + auto WaapiClient = FAkWaapiClient::Get(); + + if (!WaapiClient) + { + UE_LOG(LogAudiokineticTools, Log, TEXT("Failed to get the Waapi client.")); + return false; + } + + ProjectLoadedHandle = WaapiClient->OnProjectLoaded.AddRaw( + this, &FWaapiDataSource::OnProjectLoadedCallback); + + ConnectionLostHandle = WaapiClient->OnConnectionLost.AddRaw( + this, &FWaapiDataSource::OnConnectionLostCallback); + + ClientBeginDestroyHandle = WaapiClient->OnClientBeginDestroy.AddRaw( + this, &FWaapiDataSource::OnWaapiClientBeginDestroyCallback); + + SubscribeWaapiCallbacks(); + + return true; +} + +bool FWaapiDataSource::TearDown() +{ + auto WaapiClient = FAkWaapiClient::Get(); + + if (!WaapiClient) + { + UE_LOG(LogAudiokineticTools, Log, TEXT("Failed to get the Waapi client.")); + return false; + } + + if (ProjectLoadedHandle.IsValid()) + { + WaapiClient->OnProjectLoaded.Remove(ProjectLoadedHandle); + ProjectLoadedHandle.Reset(); + } + + if (ConnectionLostHandle.IsValid()) + { + WaapiClient->OnConnectionLost.Remove(ConnectionLostHandle); + ConnectionLostHandle.Reset(); + } + + RemoveClientCallbacks(); + + WaapiClient->OnClientBeginDestroy.Remove(ClientBeginDestroyHandle); + + return true; +} + +void FWaapiDataSource::ConstructTree(bool bShouldRefresh) +{ + if (IsProjectLoaded() != EWwiseConnectionStatus::Connected) + { + UE_LOG(LogAudiokineticTools, Log, TEXT("Failed to construct Waapi Tree. The Wwise project is not connected.")); + return; + } + { + FScopeLock AutoLock(&WaapiRootItemsLock); + RootItems.Empty(); + NodesByPath.Empty(); + } + + for (int i = EWwiseItemType::Event; i <= EWwiseItemType::LastWwiseBrowserType; ++i) + { + + FWwiseTreeItemPtr TreeRoot = ConstructTreeRoot(static_cast(i)); + { + FScopeLock AutoLock(&WaapiRootItemsLock); + RootItems.Add(TreeRoot); + NodesByPath.Add(TreeRoot->FolderPath, TreeRoot); + LoadChildren(TreeRoot); + } + } + + if(bShouldRefresh) + { + WaapiDataSourceRefreshed.ExecuteIfBound(); + } +} + +FWwiseTreeItemPtr FWaapiDataSource::ConstructTreeRoot(EWwiseItemType::Type Type) +{ + FGuid InItemGuid = FGuid(); + uint32 ShortId = 0; + TSharedPtr Result; + uint32_t ItemChildrenCount = 0; + FString Path = WwiseWaapiHelper::BACK_SLASH + EWwiseItemType::FolderNames[static_cast(Type)]; + + if (IsProjectLoaded() != EWwiseConnectionStatus::Connected) + { + UE_LOG(LogAudiokineticTools, Log, TEXT("Failed to construct Waapi Root Items. The Wwise project is not connected.")); + return nullptr; + } + + // Get the root item of the given hierarchy using its path + if (LoadWaapiInfo(WwiseWaapiHelper::PATH, Path, Result, {})) + { + const TSharedPtr ItemInfoObj = Result->GetArrayField(WwiseWaapiHelper::RETURN)[0]->AsObject(); + const FString ItemIdString = ItemInfoObj->GetStringField(WwiseWaapiHelper::ID); + Path = ItemInfoObj->GetStringField(WwiseWaapiHelper::PATH); + ItemChildrenCount = ItemInfoObj->GetNumberField(WwiseWaapiHelper::CHILDREN_COUNT); + FGuid::ParseExact(ItemIdString, EGuidFormats::DigitsWithHyphensInBraces, InItemGuid); + } + + else + { + UE_LOG(LogAudiokineticTools, Log, TEXT("Failed to get information for Waapi items at : %s"), *Path); + + if (Result && Result->GetStringField(TEXT("uri")) == TEXT("ak.wwise.locked")) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to get information for Waapi items. The item is locked.")); + } + } + + // Create the new tree item + FWwiseTreeItemPtr NewRootParent = MakeShared(EWwiseItemType::BrowserDisplayNames[static_cast(Type)], Path, nullptr, EWwiseItemType::Folder, InItemGuid); + NewRootParent->ShortId = ShortId; + NewRootParent->bWaapiRefExists = true; + NewRootParent->ChildCountInWwise = ItemChildrenCount; + + return NewRootParent; +} + +FWwiseTreeItemPtr FWaapiDataSource::ConstructWwiseTreeItem(const TSharedPtr& InItemInfoObj) +{ + static const FString ValidPaths[] = { + EWwiseItemType::FolderNames[EWwiseItemType::Event], + EWwiseItemType::FolderNames[EWwiseItemType::AuxBus], + EWwiseItemType::FolderNames[EWwiseItemType::ActorMixer], + EWwiseItemType::FolderNames[EWwiseItemType::GameParameter], + EWwiseItemType::FolderNames[EWwiseItemType::State], + EWwiseItemType::FolderNames[EWwiseItemType::Switch], + EWwiseItemType::FolderNames[EWwiseItemType::Trigger], + EWwiseItemType::FolderNames[EWwiseItemType::AcousticTexture], + EWwiseItemType::FolderNames[EWwiseItemType::EffectShareSet] + }; + + static auto IsValidPath = [](const FString& Input, const auto& Source) -> bool { + for (const auto& Item : Source) + { + if (Input.StartsWith(WwiseWaapiHelper::BACK_SLASH + Item)) + { + return true; + } + } + return false; + }; + + const FString ItemTypeString = InItemInfoObj->GetStringField(WwiseWaapiHelper::TYPE); + + auto ItemType = EWwiseItemType::FromString(ItemTypeString); + if (ItemType == EWwiseItemType::None) + { + return {}; + } + + const FString ItemPath = InItemInfoObj->GetStringField(WwiseWaapiHelper::PATH); + if (IsValidPath(ItemPath, ValidPaths)) + { + const FString ItemIdString = InItemInfoObj->GetStringField(WwiseWaapiHelper::ID); + FGuid InItemId = FGuid::NewGuid(); + FGuid::ParseExact(ItemIdString, EGuidFormats::DigitsWithHyphensInBraces, InItemId); + const FString ItemName = InItemInfoObj->GetStringField(WwiseWaapiHelper::NAME); + + if (ItemName.IsEmpty()) + { + return {}; + } + + const uint32_t ItemChildrenCount = InItemInfoObj->GetNumberField(WwiseWaapiHelper::CHILDREN_COUNT); + + if (ItemType == EWwiseItemType::StandaloneWorkUnit) + { + FString WorkUnitType; + if (InItemInfoObj->TryGetStringField(WwiseWaapiHelper::WORKUNIT_TYPE, WorkUnitType) && WorkUnitType == "FOLDER") + { + ItemType = EWwiseItemType::PhysicalFolder; + } + } + + FWwiseTreeItemPtr TreeItem = MakeShared(ItemName, ItemPath, nullptr, ItemType, InItemId); + TreeItem->WaapiName = ItemName; + TreeItem->bWaapiRefExists = true; + if (TreeItem->IsFolder()) + { + NodesByPath.Add(TreeItem->FolderPath, TreeItem); + } + + if ((ItemType != EWwiseItemType::Event) && (ItemType != EWwiseItemType::Sound)) + { + TreeItem->ChildCountInWwise = ItemChildrenCount; + } + return TreeItem; + } + + return {}; +} + +FWwiseTreeItemPtr FWaapiDataSource::ConstructWwiseTreeItem(const TSharedPtr& InJsonItem) +{ + return ConstructWwiseTreeItem(InJsonItem->AsObject()); +} + +FWwiseTreeItemPtr FWaapiDataSource::FindItemFromPath(const FWwiseTreeItemPtr& InParentItem, + const FString& InCurrentItemPath) +{ + return InParentItem->FindItemRecursive(InCurrentItemPath); +} + +FWwiseTreeItemPtr FWaapiDataSource::FindItemFromPath(const FString& InCurrentItemPath) +{ + auto FoundItem = NodesByPath.Find(InCurrentItemPath); + + if (FoundItem) + { + return *FoundItem; + } + + return nullptr; +} + +FWwiseTreeItemPtr FWaapiDataSource::FindOrConstructTreeItemFromJsonObject( + const TSharedPtr& ObjectJson) +{ + FString objectPath; + if (!ObjectJson->TryGetStringField(WwiseWaapiHelper::PATH, objectPath)) + { + return {}; + } + + FString stringId; + if (!ObjectJson->TryGetStringField(WwiseWaapiHelper::ID, stringId)) + { + return {}; + } + + FGuid id; + FGuid::ParseExact(stringId, EGuidFormats::DigitsWithHyphensInBraces, id); + + TArray pathParts; + objectPath.ParseIntoArray(pathParts, *WwiseWaapiHelper::BACK_SLASH); + + if (pathParts.Num() == 0) + { + return {}; + } + + TSharedPtr treeItem; + TArray>* children = &RootItems; + TArray> itemsToExpand; + FString folderPath; + + for (auto& part : pathParts) + { + folderPath += FString::Printf(TEXT("%s%s"), *WwiseWaapiHelper::BACK_SLASH, *part); + + bool found = false; + for (auto& item : *children) + { + if (item->ItemId == id) + { + treeItem = item; + break; + } + + if (item->FolderPath == folderPath) + { + if (!item->IsExpanded) + { + const FString itemIdStringField = item->ItemId.ToString(EGuidFormats::DigitsWithHyphensInBraces); + + TSharedPtr Result; + // Request data from Wwise UI using WAAPI and use them to create a Wwise tree item, getting the informations from a specific "ID". + if (!CallWaapiGetInfoFrom(WwiseWaapiHelper::ID, itemIdStringField, Result, { { WwiseWaapiHelper::SELECT, { WwiseWaapiHelper::CHILDREN }, {} } })) + { + // Folders do not have a specific "ID". Search with the "PATH" instead. + if (!CallWaapiGetInfoFrom(WwiseWaapiHelper::PATH, item->FolderPath, Result, { { WwiseWaapiHelper::SELECT, { WwiseWaapiHelper::CHILDREN }, {} } })) + { + UE_LOG(LogAudiokineticTools, Log, TEXT("Failed to get information from id : %s"), *itemIdStringField); + return {}; + } + } + itemsToExpand.Add(item); + } + treeItem = item; + children = treeItem->GetChildrenMutable(); + found = true; + } + } + + if (treeItem && treeItem->ItemId == id) + { + break; + } + + if (!found) + { + return {}; + } + } + + if (treeItem.IsValid() && treeItem->ItemId != id) + { + return {}; + } + + WwiseExpansionChange.ExecuteIfBound(itemsToExpand); + + return treeItem; +} + +void FWaapiDataSource::FindAndCreateItems(FWwiseTreeItemPtr CurrentItem) +{ + FString LastPathVisited = CurrentItem->FolderPath; + LastPathVisited.RemoveFromEnd(WwiseWaapiHelper::BACK_SLASH + CurrentItem->DisplayName); + FWwiseTreeItemPtr RootItem = GetRootItem(CurrentItem->FolderPath); + if (!RootItem || CurrentItem->FolderPath == RootItem->FolderPath) + { + return; + } + + if (LastPathVisited == RootItem->FolderPath) + { + RootItem->AddChild(CurrentItem); + return; + } + + FWwiseTreeItemPtr ParentItem = FindItemFromPath(RootItem, LastPathVisited); + if (ParentItem.IsValid()) + { + ParentItem->AddChild(CurrentItem); + } + + else + { + TSharedPtr Result; + // Request data from Wwise UI using WAAPI and use them to create a Wwise tree item, getting the informations from a specific "PATH". + if (LoadWaapiInfo(WwiseWaapiHelper::PATH, LastPathVisited, Result, {})) + { + // Recover the information from the Json object Result and use it to construct the tree item. + FWwiseTreeItemPtr NewRootItem = ConstructWwiseTreeItem(Result->GetArrayField(WwiseWaapiHelper::RETURN)[0]); + CurrentItem->Parent = NewRootItem; + NewRootItem->AddChild(CurrentItem); + FindAndCreateItems(NewRootItem); + } + else + { + UE_LOG(LogAudiokineticTools, Log, TEXT("Failed to get information from Waapi path : %s"), *LastPathVisited); + } + } +} + +FString FWaapiDataSource::GetItemWorkUnitPath(FWwiseTreeItemPtr InItem) +{ + auto WaapiClient = FAkWaapiClient::Get(); + if (!WaapiClient) + { + UE_LOG(LogAudiokineticTools, Log, TEXT("Unable to connect to localhost")); + return {}; + } + + TSharedRef Args = MakeShared(); + { + TSharedPtr From = MakeShared(); + From->SetArrayField(WwiseWaapiHelper::PATH, TArray> { MakeShared(InItem->FolderPath) }); + Args->SetObjectField(WwiseWaapiHelper::FROM, From); + } + + TSharedRef Options = MakeShared(); + Options->SetArrayField(WwiseWaapiHelper::RETURN, TArray> { MakeShared(WwiseWaapiHelper::FILEPATH) }); + +#if AK_SUPPORT_WAAPI + TSharedPtr outJsonResult; + if (!WaapiClient->Call(ak::wwise::core::object::get, Args, Options, outJsonResult)) + { + UE_LOG(LogAudiokineticTools, Log, TEXT("Call Failed to get %s's Work Unit Path"), *InItem->DisplayName); + return {}; + } +#endif + + FString Path = outJsonResult->GetArrayField(WwiseWaapiHelper::RETURN)[0]->AsObject()->GetStringField( + WwiseWaapiHelper::FILEPATH); + + return Path; +} + +FWwiseTreeItemPtr FWaapiDataSource::GetRootItem(EWwiseItemType::Type RootType) +{ + if (IsProjectLoaded() != EWwiseConnectionStatus::Connected) + { + UE_LOG(LogAudiokineticTools, Log, TEXT("Failed to get Waapi Root Items. The Wwise project is not connected.")); + return nullptr; + } + + check(RootType <= EWwiseItemType::LastWwiseBrowserType) + + if (RootType > RootItems.Num() - 1) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to get Waapi Root Items. Index out of range.")); + return nullptr; + } + + return RootItems[RootType]; +} + +FWwiseTreeItemPtr FWaapiDataSource::GetRootItem(const FString& InFullPath) +{ + for (int i = EWwiseItemType::Event; i <= EWwiseItemType::LastWwiseBrowserType; ++i) + { + if (InFullPath.StartsWith(RootItems[i]->FolderPath)) + { + return RootItems[i]; + } + } + + return {}; +} + +FWwiseTreeItemPtr FWaapiDataSource::LoadFilteredRootItem(EWwiseItemType::Type RootType, TSharedPtr CurrentFilter) +{ + + check(CurrentFilter.IsValid()) + + if (RootType > RootItems.Num() - 1) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to get Waapi Root Items. Index out of range.")); + return nullptr; + } + + FString CurrentFilterText = CurrentFilter->GetRawFilterText().ToString(); + FString Path = WwiseWaapiHelper::BACK_SLASH + EWwiseItemType::FolderNames[static_cast(RootType)]; + FScopeLock Autolock(&WaapiRootItemsLock); + for (int i = EWwiseItemType::Event; i <= EWwiseItemType::LastWwiseBrowserType; ++i) + { + RootItems[i]->EmptyChildren(); + } + TSharedPtr Result; + if (LoadWaapiInfo(WwiseWaapiHelper::PATH, Path, Result, + { + { WwiseWaapiHelper::SELECT , { WwiseWaapiHelper::DESCENDANTS }, {} }, + { WwiseWaapiHelper::WHERE, { WwiseWaapiHelper::NAMECONTAINS, CurrentFilterText}, {} } + })) + { + // Recover the information from the Json object Result and use it to construct the tree item. + TArray> SearchResultArray = Result->GetArrayField(WwiseWaapiHelper::RETURN); + if (SearchResultArray.Num()) + { + // The map contains each path and the correspondent object of the search result. + TMap < FString, FWwiseTreeItemPtr> SearchedResultTreeItem; + for (int i = 0; i < SearchResultArray.Num(); i++) + { + // Fill the map with the path-object elements. + FWwiseTreeItemPtr NewRootChild = ConstructWwiseTreeItem(SearchResultArray[i]); + if (NewRootChild.IsValid()) + { + FindAndCreateItems(NewRootChild); + } + } + } + } + else + { + UE_LOG(LogAudiokineticTools, Log, TEXT("Failed to get information from Waapi item search : %s"), *CurrentFilterText); + } + + return GetRootItem(RootType); +} + +bool FWaapiDataSource::LoadWaapiInfo(const FString& InFromField, const FString& InFromString, + TSharedPtr& OutJsonResult, const TArray& TransformFields) +{ + auto WaapiClient = FAkWaapiClient::Get(); + + if (!WaapiClient) + { + UE_LOG(LogAudiokineticTools, Log, TEXT("Unable to connect to localhost")); + return false; + } + +#if AK_SUPPORT_WAAPI + + // Construct the Json arguments from a specific id/path + TSharedRef Args = MakeShared(); + { + TSharedPtr From = MakeShared(); + From->SetArrayField(InFromField, TArray> {MakeShared(InFromString)}); + Args->SetObjectField(WwiseWaapiHelper::FROM, From); + } + + if (TransformFields.Num()) + { + TArray> Transform; + + for (const auto& TransformValue : TransformFields) + { + TSharedPtr InsideTransform = MakeShared(); + TArray> JsonArray; + for (auto TransformStringValueArg : TransformValue.valueStringArgs) + { + JsonArray.Add(MakeShared(TransformStringValueArg)); + } + for (auto TransformNumberValueArg : TransformValue.valueNumberArgs) + { + JsonArray.Add(MakeShared(TransformNumberValueArg)); + } + InsideTransform->SetArrayField(TransformValue.keyArg, JsonArray); + Transform.Add(MakeShared(InsideTransform)); + } + Args->SetArrayField(WwiseWaapiHelper::TRANSFORM, Transform); + } + + // Construct the Options Json object : Getting specific infos to construct the WwiseTreeItem "id - name - type - childrenCount - path - parent" + TSharedRef Options = MakeShared(); + Options->SetArrayField(WwiseWaapiHelper::RETURN, TArray> + { + MakeShared(WwiseWaapiHelper::ID), + MakeShared(WwiseWaapiHelper::NAME), + MakeShared(WwiseWaapiHelper::TYPE), + MakeShared(WwiseWaapiHelper::CHILDREN_COUNT), + MakeShared(WwiseWaapiHelper::PATH), + MakeShared(WwiseWaapiHelper::WORKUNIT_TYPE), + }); + + return WaapiClient->Call(ak::wwise::core::object::get, Args, Options, OutJsonResult); +#else + return false; +#endif +} + +bool FWaapiDataSource::IsTreeDirty() +{ + return false; +} + +void FWaapiDataSource::Tick(const double InCurrentTime, const float InDeltaTime) +{ +} + +int32 FWaapiDataSource::LoadChildren(const FGuid& InParentId, const FString& InParentPath, TArray& OutChildren) +{ + UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("Loading Children for Waapi item %s, id: %s"), *InParentPath, *InParentId.ToString()) + + FString InFromField; + FString InStringField; + + OutChildren = {}; + + if (InParentId.IsValid()) + { + InFromField = WwiseWaapiHelper::ID; + InStringField = InParentId.ToString(EGuidFormats::DigitsWithHyphensInBraces); + } + + else + { + InFromField = WwiseWaapiHelper::PATH; + InStringField = InParentPath; + } + + TSharedPtr Result; + + // Request data from Wwise UI using WAAPI and use them to create a Wwise tree item, getting the informations from a specific "ID". + if (!LoadWaapiInfo(WwiseWaapiHelper::PATH, InParentPath, Result, + { + { WwiseWaapiHelper::SELECT , { WwiseWaapiHelper::DESCENDANTS }, {} }, + { WwiseWaapiHelper::WHERE, { WwiseWaapiHelper::NAMECONTAINS, ""}, {}} + })) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to load Waapi information for %s."), *InStringField); + return 0; + } + + TArray> StructJsonArray = Result->GetArrayField(WwiseWaapiHelper::RETURN); + + if (StructJsonArray.Num() == 0) + { + return 0; + } + + FWwiseTreeItemPtr ExistingParent = nullptr; + if (auto ParentPtr = NodesByPath.Find(InParentPath)) + { + ExistingParent = *ParentPtr; + } + + if(!ExistingParent) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("No Parent found at %s."), *InParentPath) + return 0; + } + + ExistingParent->EmptyChildren(); + + for (int i = 0; i < StructJsonArray.Num(); i++) + { + FWwiseTreeItemPtr NewChild = ConstructWwiseTreeItem(StructJsonArray[i]); + if (NewChild) + { + FindAndCreateItems(NewChild); + } + } + + return OutChildren.Num(); +} + +int32 FWaapiDataSource::LoadChildren(FWwiseTreeItemPtr ParentTreeItem) +{ + if (!ParentTreeItem) + { + return 0; + } + + TArray OutChildren; + + LoadChildren(ParentTreeItem->ItemId, ParentTreeItem->FolderPath, OutChildren); + + return ParentTreeItem->GetChildren().Num(); +} + +int32 FWaapiDataSource::GetChildItemCount(const FWwiseTreeItemPtr& InParentItem) +{ + return InParentItem->ChildCountInWwise; +} + +FString FWaapiDataSource::LoadProjectName() +{ + auto WaapiClient = FAkWaapiClient::Get(); + + if (!WaapiClient) + { + return {}; + } + + TSharedRef Args = MakeShared(); + { + TSharedPtr OfType = MakeShared(); + OfType->SetArrayField(WwiseWaapiHelper::OF_TYPE, TArray> { MakeShared(WwiseWaapiHelper::PROJECT) }); + Args->SetObjectField(WwiseWaapiHelper::FROM, OfType); + } + + TSharedRef Options = MakeShared(); + { + Options->SetArrayField(WwiseWaapiHelper::RETURN, TArray> + { + MakeShared(WwiseWaapiHelper::NAME), + MakeShared(WwiseWaapiHelper::FILEPATH), + }); + } + +#if AK_SUPPORT_WAAPI + TSharedPtr outJsonResult; + if (WaapiClient->Call(ak::wwise::core::object::get, Args, Options, outJsonResult)) + { + // Recover the information from the Json object Result and use it to get the item id. + TArray> StructJsonArray = outJsonResult->GetArrayField(WwiseWaapiHelper::RETURN); + if (StructJsonArray.Num()) + { + FString Path = StructJsonArray[0]->AsObject()->GetStringField(WwiseWaapiHelper::FILEPATH); + return FPaths::GetCleanFilename(Path); + } + else + { + UE_LOG(LogAudiokineticTools, Log, TEXT("Unable to get the project name")); + return {}; + } + } + + return {}; +#endif +} + +void FWaapiDataSource::HandleFindWwiseItemInProjectExplorerCommandExecute( + const TArray& SelectedItems) const +{ + auto waapiClient = FAkWaapiClient::Get(); + if (!waapiClient) + { + UE_LOG(LogAudiokineticTools, Log, TEXT("Unable to connect to localhost")); + return; + } + + if (SelectedItems.Num() == 0) + return; + + TSharedRef Args = MakeShared(); + Args->SetStringField(WwiseWaapiHelper::COMMAND, WwiseWaapiHelper::FIND_IN_PROJECT_EXPLORER); + TArray> SelectedObjects; + for (auto SelectedItem : SelectedItems) + { + SelectedObjects.Add(MakeShared(SelectedItem->ItemId.ToString(EGuidFormats::DigitsWithHyphensInBraces))); + } + Args->SetArrayField(WwiseWaapiHelper::OBJECTS, SelectedObjects); + +#if AK_SUPPORT_WAAPI + TSharedPtr Result; + TSharedRef Options = MakeShared(); + if (!waapiClient->Call(ak::wwise::ui::commands::execute, Args, Options, Result)) + { + UE_LOG(LogAudiokineticTools, Log, TEXT("Call Failed")); + } +#endif +} + +void FWaapiDataSource::OnProjectLoadedCallback() +{ + SubscribeWaapiCallbacks(); + ConstructTree(true); +} + +void FWaapiDataSource::OnConnectionLostCallback() +{ + { + FScopeLock AutoLock(&WaapiRootItemsLock); + RootItems.Empty(); + NodesByPath.Empty(); + } + + WaapiDataSourceRefreshed.ExecuteIfBound(); + + UnsubscribeWaapiCallbacks(); +} + +void FWaapiDataSource::OnWaapiClientBeginDestroyCallback() +{ + auto waapiClient = FAkWaapiClient::Get(); + if (waapiClient == nullptr) + return; + + if (ProjectLoadedHandle.IsValid()) + { + waapiClient->OnProjectLoaded.Remove(ProjectLoadedHandle); + ProjectLoadedHandle.Reset(); + } + + if (ConnectionLostHandle.IsValid()) + { + waapiClient->OnConnectionLost.Remove(ConnectionLostHandle); + ConnectionLostHandle.Reset(); + } + + UnsubscribeWaapiCallbacks(); +} + +void FWaapiDataSource::OnWaapiRenamed(uint64_t Id, TSharedPtr Response) +{ + FString OldName; + if (Response->TryGetStringField(WwiseWaapiHelper::OLD_NAME, OldName) && OldName.IsEmpty()) + { + return; + } + + FString NewName; + if (Response->TryGetStringField(WwiseWaapiHelper::NEW_NAME, NewName) && NewName.IsEmpty()) + { + return; + } + + const TSharedPtr* ObjectJsonPtr = nullptr; + if (!Response->TryGetObjectField(WwiseWaapiHelper::OBJECT, ObjectJsonPtr)) + { + return; + } + + FString ObjectPath; + if (!ObjectJsonPtr->Get()->TryGetStringField(WwiseWaapiHelper::PATH, ObjectPath)) + { + return; + } + + FString OldPath(ObjectPath); + OldPath.RemoveFromEnd(WwiseWaapiHelper::BACK_SLASH + NewName); + auto TreeItem = FindItemFromPath(OldPath); + OldPath += WwiseWaapiHelper::BACK_SLASH + OldName; + NodesByPath.Remove(OldPath); + if(TreeItem) + { + for(auto& Child : TreeItem->GetChildren()) + { + if(Child->DisplayName == OldName) + { + Child->DisplayName = UPackageTools::SanitizePackageName(NewName); + Child->WaapiName = NewName; + break; + } + } + } + + WaapiDataSourceRefreshed.ExecuteIfBound(); +} + +void FWaapiDataSource::OnWaapiChildAdded(uint64_t Id, TSharedPtr Response) +{ + const TSharedPtr* ChildJsonPtr = nullptr; + if (!Response->TryGetObjectField(WwiseWaapiHelper::CHILD, ChildJsonPtr)) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to get Child Object field.")); + return; + } + + FString ChildName; + + if (!ChildJsonPtr->Get()->TryGetStringField(WwiseWaapiHelper::NAME, ChildName)) + { + return; + } + + FString ChildPath; + if (!ChildJsonPtr->Get()->TryGetStringField(WwiseWaapiHelper::PATH, ChildPath)) + { + return; + } + + const TSharedPtr* ParentJsonPtr = nullptr; + if (!Response->TryGetObjectField(WwiseWaapiHelper::PARENT, ParentJsonPtr)) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to get Parent Object field.")); + return; + } + + FString TreeItemParentPath; + + if (!ParentJsonPtr->Get()->TryGetStringField(WwiseWaapiHelper::PATH, TreeItemParentPath)) + { + return; + } + + bool bChildAdded = false; + if (auto ParentItem = NodesByPath.Find(TreeItemParentPath)) + { + if (!ParentItem->Get()->FindItemRecursive(ChildPath)) + { + auto ChildItem = ConstructWwiseTreeItem(*ChildJsonPtr); + if (ChildItem) + { + ParentItem->Get()->AddChild(ChildItem); + if(ChildItem->IsFolder()) + { + NodesByPath.Add(ChildItem->FolderPath, ChildItem); + } + bChildAdded = true; + } + } + } + + if(bChildAdded) + { + WaapiDataSourceRefreshed.ExecuteIfBound(); + } + +} + +void FWaapiDataSource::OnWaapiChildRemoved(uint64_t Id, TSharedPtr Response) +{ + const TSharedPtr* ChildJsonPtr = nullptr; + if (!Response->TryGetObjectField(WwiseWaapiHelper::CHILD, ChildJsonPtr)) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to get Child Object field.")); + return; + } + + const TSharedPtr* ParentJsonPtr = nullptr; + if (!Response->TryGetObjectField(WwiseWaapiHelper::PARENT, ParentJsonPtr)) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to get Parent Object field.")); + return; + } + + FString ItemIdString; + FString ItemName; + + if (!ChildJsonPtr->Get()->TryGetStringField(WwiseWaapiHelper::ID, ItemIdString)) + { + return; + } + + if (!ChildJsonPtr->Get()->TryGetStringField(WwiseWaapiHelper::NAME, ItemName)) + { + return; + } + + FString TreeItemParentPath; + + if (!ParentJsonPtr->Get()->TryGetStringField(WwiseWaapiHelper::PATH, TreeItemParentPath)) + { + return; + } + + // This seems to be the path to the removed item, and not its parent? + if (auto ParentTreeItem = NodesByPath.Find(TreeItemParentPath)) + { + FGuid ItemId = FGuid(ItemIdString); + NodesByPath.Remove(TreeItemParentPath / ItemName); + (*ParentTreeItem)->RemoveChild(ItemId); + } + + WaapiDataSourceRefreshed.ExecuteIfBound(); + +} + +void FWaapiDataSource::OnWwiseSelectionChanged(uint64_t Id, TSharedPtr Response) +{ + AsyncTask(ENamedThreads::GameThread, [this, Response]() + { + const UAkSettingsPerUser* AkSettingsPerUser = GetDefault(); + if (AkSettingsPerUser && AkSettingsPerUser->AutoSyncSelection) + { + const TArray>* objectsJsonArray = nullptr; + if (Response->TryGetArrayField(WwiseWaapiHelper::OBJECTS, objectsJsonArray)) + { + TArray> TreeItems; + for (auto JsonObject : *objectsJsonArray) + { + auto TreeItem = FindOrConstructTreeItemFromJsonObject(JsonObject->AsObject()); + if (TreeItem) + { + TreeItems.Add(TreeItem); + } + } + WwiseSelectionChange.ExecuteIfBound(TreeItems); + } + } + }); +} + +void FWaapiDataSource::SubscribeWaapiCallbacks() +{ + struct SubscriptionData + { + const char* Uri; + WampEventCallback Callback; + uint64* SubscriptionId; + }; + +#if AK_SUPPORT_WAAPI + const SubscriptionData Subscriptions[] = { + {ak::wwise::core::object::nameChanged, WampEventCallback::CreateRaw(this, &FWaapiDataSource::OnWaapiRenamed), &WaapiSubscriptionIds.Renamed}, + {ak::wwise::core::object::childAdded, WampEventCallback::CreateRaw(this, &FWaapiDataSource::OnWaapiChildAdded), &WaapiSubscriptionIds.ChildAdded}, + {ak::wwise::core::object::childRemoved, WampEventCallback::CreateRaw(this, &FWaapiDataSource::OnWaapiChildRemoved), &WaapiSubscriptionIds.ChildRemoved}, + {ak::wwise::ui::selectionChanged, WampEventCallback::CreateRaw(this, &FWaapiDataSource::OnWwiseSelectionChanged), &WaapiSubscriptionIds.SelectionChanged}, + }; +#endif + + auto waapiClient = FAkWaapiClient::Get(); + if (!waapiClient) + { + return; + } + + TSharedRef Options = MakeShared(); + Options->SetArrayField(WwiseWaapiHelper::RETURN, TArray> + { + MakeShared(WwiseWaapiHelper::ID), + MakeShared(WwiseWaapiHelper::NAME), + MakeShared(WwiseWaapiHelper::TYPE), + MakeShared(WwiseWaapiHelper::CHILDREN_COUNT), + MakeShared(WwiseWaapiHelper::PATH), + MakeShared(WwiseWaapiHelper::PARENT), + MakeShared(WwiseWaapiHelper::WORKUNIT_TYPE), + }); + + TSharedPtr Result; +#if AK_SUPPORT_WAAPI + for (auto& Subscription : Subscriptions) + { + if (*Subscription.SubscriptionId == 0) + { + waapiClient->Subscribe(Subscription.Uri, + Options, + Subscription.Callback, + *Subscription.SubscriptionId, + Result + ); + } + } +#endif +} + +EWwiseConnectionStatus FWaapiDataSource::IsProjectLoaded() +{ + if(UAkSettingsPerUser* AkUserSettings = GetMutableDefault()) + { + if(!AkUserSettings->bAutoConnectToWAAPI) + { + return EWwiseConnectionStatus::SettingDisabled; + } + } + if(FAkWaapiClient::IsProjectLoaded()) + { + return EWwiseConnectionStatus::Connected; + } + if(FAkWaapiClient::Get()->bIsWrongProjectLoaded) + { + return EWwiseConnectionStatus::WrongProjectOpened; + } + return EWwiseConnectionStatus::WwiseNotOpen; +} + +void FWaapiDataSource::SelectInProjectExplorer(TArray& InTreeItems) +{ + auto WaapiClient = FAkWaapiClient::Get(); + if (!WaapiClient) + { + UE_LOG(LogAudiokineticTools, Log, TEXT("Unable to connect to localhost")); + return; + } + + TSharedRef Args = MakeShared(); + Args->SetStringField(WwiseWaapiHelper::COMMAND, WwiseWaapiHelper::FIND_IN_PROJECT_EXPLORER); + TArray> SelectedObjects; + for (const auto TreeItem : InTreeItems) + { + SelectedObjects.Add(MakeShared(TreeItem->ItemId.ToString(EGuidFormats::DigitsWithHyphensInBraces))); + } + Args->SetArrayField(WwiseWaapiHelper::OBJECTS, SelectedObjects); + +#if AK_SUPPORT_WAAPI + TSharedPtr Result; + TSharedRef Options = MakeShared(); + if (!WaapiClient->Call(ak::wwise::ui::commands::execute, Args, Options, Result)) + { + UE_LOG(LogAudiokineticTools, Log, TEXT("Call Failed to Select Items in Project Explorer.")); + } +#endif + +} + +bool FWaapiDataSource::CallWaapiGetInfoFrom(const FString& inFromField, const FString& inFromString, TSharedPtr& outJsonResult, const TArray& TransformFields) +{ + auto waapiClient = FAkWaapiClient::Get(); + if (!waapiClient) + { + return false; + } +#if AK_SUPPORT_WAAPI + // Construct the arguments Json object : Getting infos "from - a specific id/path" + TSharedRef Args = MakeShared(); + { + TSharedPtr from = MakeShared(); + from->SetArrayField(inFromField, TArray> { MakeShared(inFromString) }); + Args->SetObjectField(WwiseWaapiHelper::FROM, from); + + // In case we would recover the children of the object that have the id : ID or the path : PATH, then we set isGetChildren to true. + + if (TransformFields.Num()) + { + TArray> transform; + + for (auto TransformValue : TransformFields) + { + TSharedPtr insideTransform = MakeShared(); + TArray> JsonArray; + for (auto TransformStringValueArg : TransformValue.valueStringArgs) + { + JsonArray.Add(MakeShared(TransformStringValueArg)); + } + for (auto TransformNumberValueArg : TransformValue.valueNumberArgs) + { + JsonArray.Add(MakeShared(TransformNumberValueArg)); + } + insideTransform->SetArrayField(TransformValue.keyArg, JsonArray); + transform.Add(MakeShared(insideTransform)); + } + Args->SetArrayField(WwiseWaapiHelper::TRANSFORM, transform); + } + } + + // Construct the Options Json object : Getting specific infos to construct the wwise tree item "id - name - type - childrenCount - path - parent" + TSharedRef Options = MakeShared(); + Options->SetArrayField(WwiseWaapiHelper::RETURN, TArray> + { + MakeShared(WwiseWaapiHelper::ID), + MakeShared(WwiseWaapiHelper::NAME), + MakeShared(WwiseWaapiHelper::TYPE), + MakeShared(WwiseWaapiHelper::CHILDREN_COUNT), + MakeShared(WwiseWaapiHelper::PATH), + MakeShared(WwiseWaapiHelper::WORKUNIT_TYPE), + }); + + // Request data from Wwise using WAAPI + + return waapiClient->Call(ak::wwise::core::object::get, Args, Options, outJsonResult); +#endif + return false; +} + +void FWaapiDataSource::UnsubscribeWaapiCallbacks() +{ + auto WaapiClient = FAkWaapiClient::Get(); + if (!WaapiClient) + { + return; + } + + auto DoUnsubscribe = [WaapiClient](uint64& subscriptionId) { + if (subscriptionId > 0) + { + TSharedPtr Result; + WaapiClient->Unsubscribe(subscriptionId, Result); + subscriptionId = 0; + } + }; + + DoUnsubscribe(WaapiSubscriptionIds.Renamed); + DoUnsubscribe(WaapiSubscriptionIds.ChildAdded); + DoUnsubscribe(WaapiSubscriptionIds.ChildRemoved); + DoUnsubscribe(WaapiSubscriptionIds.SelectionChanged); +} + +void FWaapiDataSource::RemoveClientCallbacks() +{ + auto WaapiClient = FAkWaapiClient::Get(); + if (WaapiClient == nullptr) + { + return; + } + + if (ProjectLoadedHandle.IsValid()) + { + WaapiClient->OnProjectLoaded.Remove(ProjectLoadedHandle); + ProjectLoadedHandle.Reset(); + } + + if (ConnectionLostHandle.IsValid()) + { + WaapiClient->OnConnectionLost.Remove(ConnectionLostHandle); + ConnectionLostHandle.Reset(); + } + + UnsubscribeWaapiCallbacks(); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/WaapiDataSource.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/WaapiDataSource.h new file mode 100644 index 0000000..c354f5c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/WaapiDataSource.h @@ -0,0 +1,171 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" + +#include "IWwiseBrowserDataSource.h" +#include "WwiseItemType.h" +#include "../WwiseBrowserForwards.h" +#include "WaapiPicker/SWaapiPicker.h" + +class FJsonValue; +class FJsonObject; + +DECLARE_DELEGATE_OneParam(FOnWaapiSelectionChange, const TArray>&) + +enum EWwiseConnectionStatus +{ + Connected, + SettingDisabled, + WrongProjectOpened, + WwiseNotOpen +}; + +struct WaapiTransformStringField +{ + const FString keyArg; + const TArray valueStringArgs; + const TArray valueNumberArgs; +}; + +struct WWiseWaapiItem +{ + FGuid Guid; + FName Name; + FName FullPath; +}; + +class FWaapiDataSource : IWwiseBrowserDataSource +{ +public : + + DECLARE_DELEGATE(FOnWaapiDataSourceRefreshed) + + // IWwiseBrowserDataSource + + virtual ~FWaapiDataSource() override; + + // Sets up the connection to WAAPI + virtual bool Init() override; + + virtual void ConstructTree(bool bShouldRefresh) override; + + // Constructs the top-level root for the given type, along with its direct children + virtual FWwiseTreeItemPtr ConstructTreeRoot(EWwiseItemType::Type Type) override; + + virtual int32 LoadChildren(const FGuid& InParentId, const FString& InParentPath, TArray& OutChildren) override; + + virtual int32 LoadChildren(FWwiseTreeItemPtr ParentTreeItem) override; + + virtual int32 GetChildItemCount(const FWwiseTreeItemPtr& InParentItem) override; + + virtual FWwiseTreeItemPtr GetRootItem(EWwiseItemType::Type RootType) override; + + FWwiseTreeItemPtr GetRootItem(const FString& InFullPath); + + + virtual FWwiseTreeItemPtr LoadFilteredRootItem(EWwiseItemType::Type RootType, TSharedPtr CurrentFilterText) override; + + // FWaapiDataSource + + bool TearDown(); + + FWwiseTreeItemPtr ConstructWwiseTreeItem(const TSharedPtr& InItemInfoObj); + + FWwiseTreeItemPtr ConstructWwiseTreeItem(const TSharedPtr& InJsonItem); + + FWwiseTreeItemPtr FindItemFromPath(const FWwiseTreeItemPtr& InParentItem, const FString& InCurrentItemPath); + + FWwiseTreeItemPtr FindItemFromPath(const FString& InCurrentItemPath); + + FWwiseTreeItemPtr FindOrConstructTreeItemFromJsonObject(const TSharedPtr& ObjectJson); + + void FindAndCreateItems(FWwiseTreeItemPtr CurrentItem); + + FString GetItemWorkUnitPath(FWwiseTreeItemPtr InItem); + + bool LoadWaapiInfo(const FString& InFromField, const FString& InFromString, TSharedPtr& OutJsonResult, const TArray& TransformFields); + + bool IsTreeDirty(); + + void Tick(const double InCurrentTime, const float InDeltaTime); + + FString LoadProjectName(); + + EWwiseConnectionStatus IsProjectLoaded(); + + void SelectInProjectExplorer(TArray& InTreeItems); + + /** + * Call WAAPI to get information about an object form the path or the id of the object (inFrom). + * + * @param inFromField The path or the id from which the data will be get. + * @param outJsonResult A JSON object that contains useful informations about the call process, gets the object infos or gets an error infos in case the call failed. + * @return A boolean to ensure that the call was successfully done. + */ + static bool CallWaapiGetInfoFrom(const FString& inFromField, const FString& inFromString, TSharedPtr& outJsonResult, const TArray& TransformFields); + + void HandleFindWwiseItemInProjectExplorerCommandExecute(const TArray& SelectedItems) const; + + FOnWaapiDataSourceRefreshed WaapiDataSourceRefreshed; + + FOnWaapiSelectionChange WwiseSelectionChange; + FOnWaapiSelectionChange WwiseExpansionChange; + +private: + + /** Root items, one for each type of Wwise object */ + FCriticalSection WaapiRootItemsLock; + TArray< FWwiseTreeItemPtr > RootItems; + + // Container paths along the Browser Tree + TMap NodesByPath; + + FDelegateHandle ProjectLoadedHandle; + FDelegateHandle ConnectionLostHandle; + FDelegateHandle ClientBeginDestroyHandle; + + // WAAPI Callbacks + + void OnProjectLoadedCallback(); + + void OnConnectionLostCallback(); + + void OnWaapiClientBeginDestroyCallback(); + + void OnWaapiRenamed(uint64_t Id, TSharedPtr Response); + void OnWaapiChildAdded(uint64_t Id, TSharedPtr Response); + void OnWaapiChildRemoved(uint64_t Id, TSharedPtr Response); + void OnWwiseSelectionChanged(uint64_t Id, TSharedPtr Response); + + void SubscribeWaapiCallbacks(); + + void UnsubscribeWaapiCallbacks(); + void RemoveClientCallbacks(); + + struct FWaapiSubscriptionIds + { + uint64 Renamed = 0; + uint64 ChildAdded = 0; + uint64 ChildRemoved = 0; + uint64 SelectionChanged = 0; + } WaapiSubscriptionIds; + + +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/WwiseBrowserDataSource.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/WwiseBrowserDataSource.cpp new file mode 100644 index 0000000..bc9e4a7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/WwiseBrowserDataSource.cpp @@ -0,0 +1,746 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "WwiseBrowserDataSource.h" + +#include "AkUnrealAssetDataHelper.h" +#include "AkWaapiUtils.h" +#include "WaapiDataSource.h" +#include "WwiseProjectDatabaseSource.h" +#include "Templates/SharedPointer.h" +#include "WaapiPicker/WwiseTreeItem.h" +#include "IAudiokineticTools.h" +#include "AssetManagement/AkAssetDatabase.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "Editor.h" + +bool FSoundBankStatusFilter::IsKeptInBrowser(FWwiseTreeItemPtr& Item) const +{ + if (AreFiltersOff()) + { + return true; + } + if (bFilters[NotInWwise] && Item->IsNotInWwiseOrSoundBank()) + { + return true; + } + + if (bFilters[DeletedInWwise] && Item->IsDeletedInWwise()) + { + return true; + } + + if (bFilters[NewInWwise] && Item->IsNewInWwise()) + { + return true; + } + + if (bFilters[RenamedInWwise] && Item->IsRenamedInWwise()) + { + return true; + } + + if (bFilters[MovedInWwise] && Item->IsMovedInWwise()) + { + return true; + } + + if (bFilters[UpToDate] && Item->IsSoundBankUpToDate()) + { + return true; + } + + return false; +} + +bool FSoundBankStatusFilter::AreFiltersOff() const +{ + for (auto filter : bFilters) + { + if (filter) + { + return false; + } + } + return true; +} + +bool FUAssetStatusFilter::IsKeptInBrowser(FWwiseTreeItemPtr& Item) const +{ + if (AreFiltersOff()) + { + return true; + } + if(bFilters[UAssetMissing] && Item->IsUAssetMissing()) + { + return true; + } + + if(bFilters[UAssetOrphaned] && Item->IsUAssetOrphaned()) + { + return true; + } + + if(bFilters[NotInSoundBankOrUnreal] && Item->IsNotInSoundBankOrUnreal()) + { + return true; + } + + if(bFilters[MultipleUAssets] && Item->HasMultipleUAssets()) + { + return true; + } + + if (bFilters[RenamedInSoundBank] && Item->IsRenamedInSoundBank()) + { + return true; + } + + if(bFilters[UAssetUpToDate] && Item->IsUAssetUpToDate()) + { + return true; + } + + return false; +} + +bool FUAssetStatusFilter::AreFiltersOff() const +{ + for (auto filter : bFilters) + { + if (filter) + { + return false; + } + } + return true; +} + +bool FWwiseTypeFilter::IsKeptInBrowser(FWwiseTreeItemPtr& Item) const +{ + if(AreFiltersOff()) + { + return true; + } + for(int i = 0; i < NumberOfWwiseTypes; i++) + { + if(bFilters[i] && Item->ItemType == GetExpectedType((EWwiseTypeFilter)i)) + { + return true; + } + } + return false; +} + +bool FWwiseTypeFilter::AreFiltersOff() const +{ + for(auto bFilter : bFilters) + { + if(bFilter) + { + return false; + } + } + return true; +} + +EWwiseItemType::Type FWwiseTypeFilter::GetExpectedType(EWwiseTypeFilter Filter) const +{ + switch(Filter) + { + case AcousticTexture: + return EWwiseItemType::AcousticTexture; + case Effects: + return EWwiseItemType::EffectShareSet; + case Events: + return EWwiseItemType::Event; + case GameParameters: + return EWwiseItemType::GameParameter; + case MasterMixerHierarchy: + return EWwiseItemType::Bus; + case State: + return EWwiseItemType::State; + case Switch: + return EWwiseItemType::Switch; + case Trigger: + return EWwiseItemType::Trigger; + default: + break; + } + return EWwiseItemType::None; +} + +FWwiseBrowserDataSource::FWwiseBrowserDataSource() +{ + WaapiDataSource = MakeUnique(); + ProjectDBDataSource = MakeUnique(); + UAssetDataSource = MakeUnique(); + + WaapiDataSource->Init(); + WaapiDataSource->WaapiDataSourceRefreshed.BindRaw(this, &FWwiseBrowserDataSource::OnWaapiDataSourceRefreshed); + WaapiDataSource->WwiseSelectionChange.BindRaw(this, &FWwiseBrowserDataSource::OnWwiseSelectionChange); + WaapiDataSource->WwiseExpansionChange.BindRaw(this, &FWwiseBrowserDataSource::OnWwiseExpansionChange); + + ProjectDBDataSource->Init(); + ProjectDBDataSource->ProjectDatabaseDataSourceRefreshed.BindRaw(this, &FWwiseBrowserDataSource::OnProjecDBDataSourceRefreshed); + + FAssetRegistryModule* AssetRegistryModule = &FModuleManager::LoadModuleChecked("AssetRegistry"); + + auto& AssetRegistry = AssetRegistryModule->Get(); + if (AssetRegistry.IsLoadingAssets()) + { + OnFilesLoaded = AssetRegistry.OnFilesLoaded().AddRaw(this, &FWwiseBrowserDataSource::OnFilesFullyLoaded); + } + else + { + SetupAssetCallbacks(); + } +} + +FWwiseBrowserDataSource::~FWwiseBrowserDataSource() +{ + WaapiDataSource->WaapiDataSourceRefreshed.Unbind(); + WaapiDataSource->WwiseSelectionChange.Unbind(); + WaapiDataSource->WwiseExpansionChange.Unbind(); + + ProjectDBDataSource->ProjectDatabaseDataSourceRefreshed.Unbind(); + + FAssetRegistryModule* AssetRegistryModule = &FModuleManager::LoadModuleChecked("AssetRegistry"); + + auto& AssetRegistry = AssetRegistryModule->Get(); + + AssetRegistry.OnAssetAdded().Remove(OnAssetAdded); + AssetRegistry.OnAssetRemoved().Remove(OnAssetRemoved); + AssetRegistry.OnAssetRenamed().Remove(OnAssetRenamed); + AssetRegistry.OnAssetUpdated().Remove(OnAssetUpdated); + if(PostEditorTickHandle.IsValid()) + { + GEditor->OnPostEditorTick().Remove(PostEditorTickHandle); + } +} + +void FWwiseBrowserDataSource::ConstructTree() +{ + UAssetDataSource->ConstructItems(); + ProjectDBDataSource->ConstructTree(false); + WaapiDataSource->ConstructTree(false); + MergeDataSources(false); +} + +bool FWwiseBrowserDataSource::AreFiltersOff() +{ + return WwiseTypeFilter.AreFiltersOff() && + UAssetStatusFilter.AreFiltersOff() && + SoundBankStatusFilter.AreFiltersOff(); +} + +void FWwiseBrowserDataSource::ApplyTextFilter(TSharedPtr FilterText) +{ + CurrentFilterText = FilterText; + ConstructTree(); + if (!AreFiltersOff()) + { + ApplyFilter(SoundBankStatusFilter, UAssetStatusFilter, WwiseTypeFilter); + } + +} + +void FWwiseBrowserDataSource::ApplyFilter(FSoundBankStatusFilter SoundBankStatus, + FUAssetStatusFilter UAssetStatus, + FWwiseTypeFilter WwiseType) +{ + SoundBankStatusFilter = SoundBankStatus; + UAssetStatusFilter = UAssetStatus; + WwiseTypeFilter = WwiseType; + + RootItems.Empty(); + for (auto& RootItem : RootItemsUnfiltered) + { + FWwiseTreeItemPtr RootItemFiltered; + ApplyFilter(RootItem, RootItemFiltered); + RootItems.Add(RootItemFiltered); + } +} + +void FWwiseBrowserDataSource::ApplyFilter(FWwiseTreeItemPtr Item, FWwiseTreeItemPtr& OutItem) +{ + OutItem = MakeShared(*Item.Get()); + OutItem->EmptyChildren(); + for (auto CurrItem : Item->GetChildren()) + { + if (!IsKeptInBrowser(CurrItem)) + { + continue; + } + if (CurrItem->IsFolder()) + { + FWwiseTreeItemPtr Children; + ApplyFilter(CurrItem, Children); + OutItem->AddChild(Children); + } + else + { + FWwiseTreeItemPtr Children = MakeShared(*CurrItem.Get()); + OutItem->AddChild(Children); + } + } +} + +void FWwiseBrowserDataSource::ClearFilter() +{ + CurrentFilterText = {}; +} + +bool FWwiseBrowserDataSource::IsKeptInBrowser(FWwiseTreeItemPtr Item) +{ + if(AreFiltersOff()) + { + return true; + } + + if (Item->IsFolder()) + { + for (auto Children : Item->GetChildren()) + { + if (IsKeptInBrowser(Children)) + { + return true; + } + } + return false; + } + + bool bWwiseFilterResult = true; + if (GetWaapiConnectionStatus() == EWwiseConnectionStatus::Connected) + { + bWwiseFilterResult = SoundBankStatusFilter.IsKeptInBrowser(Item); + } + + return bWwiseFilterResult && + UAssetStatusFilter.IsKeptInBrowser(Item) && + WwiseTypeFilter.IsKeptInBrowser(Item); +} + +FWwiseTreeItemPtr FWwiseBrowserDataSource::GetTreeRootForType(EWwiseItemType::Type ItemType, const FString& FilterText) +{ + FScopeLock AutoLock(&RootItemsLock); + + if (RootItemsUnfiltered.Num() < ItemType + 1) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to get Item Type. Index out of range.")); + return MakeShared("", "", nullptr, EWwiseItemType::Type::None, FGuid()); + } + if (AreFiltersOff()) + { + return RootItemsUnfiltered[ItemType]; + } + return RootItems[ItemType]; +} + +FText FWwiseBrowserDataSource::GetProjectName() +{ + if (ProjectDBDataSource.IsValid()) + { + return ProjectDBDataSource->GetProjectName(); + } + return {}; +} + +FText FWwiseBrowserDataSource::GetConnectedWwiseProjectName() +{ + return FText::FromString(WaapiDataSource->LoadProjectName()); +} + +int32 FWwiseBrowserDataSource::LoadChildren(FWwiseTreeItemPtr TreeItem, TArray& OutChildren) +{ + + UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("Loading children for %s"), *TreeItem->FolderPath ) + + TArray WaapiChildren; + TArray SoundBanksChildren; + + FWwiseTreeItemPtr WaapiParent = WaapiDataSource->FindItemFromPath(TreeItem->FolderPath); + FWwiseTreeItemPtr SoundBanksParent = ProjectDBDataSource->FindItemFromPath(TreeItem->FolderPath); + + WaapiDataSource->LoadChildren(WaapiParent); + ProjectDBDataSource->LoadChildren(SoundBanksParent); + + //In this case, it is an orphaned uasset. + if(!WaapiParent && !SoundBanksParent) + { + return TreeItem->GetChildren().Num(); + } + TreeItem->EmptyChildren(); + + if(SoundBanksParent || WaapiParent) + { + CreateUnifiedTree(SoundBanksParent, WaapiParent, TreeItem); + + TreeItem->SortChildren(); + + return TreeItem->GetChildren().Num(); + } + + return 0; +} + +void FWwiseBrowserDataSource::ClearEmptyChildren(FWwiseTreeItemPtr TreeItem) +{ + + TArray ChildrenToRemove; + + for (auto Child : *TreeItem->GetChildrenMutable()) + { + if (Child->IsOfType({EWwiseItemType::None})) + { + ChildrenToRemove.Add(Child); + } + } + + TreeItem->RemoveChildren(ChildrenToRemove); +} + +EWwiseConnectionStatus FWwiseBrowserDataSource::GetWaapiConnectionStatus() const +{ + if(!WaapiDataSource.IsValid()) + { + return EWwiseConnectionStatus::WwiseNotOpen; + } + return WaapiDataSource->IsProjectLoaded(); +} + +FString FWwiseBrowserDataSource::GetItemWorkUnitPath(FWwiseTreeItemPtr InTreeItem) +{ + if (WaapiDataSource.IsValid()) + { + return WaapiDataSource->GetItemWorkUnitPath(InTreeItem); + } + return {}; +} + +void FWwiseBrowserDataSource::SelectInWwiseProjectExplorer(TArray& InTreeItem) +{ + if (WaapiDataSource.IsValid()) + { + WaapiDataSource->SelectInProjectExplorer(InTreeItem); + } +} + +void FWwiseBrowserDataSource::HandleFindWwiseItemInProjectExplorerCommandExecute( + const TArray& SelectedItems) const +{ + WaapiDataSource->HandleFindWwiseItemInProjectExplorerCommandExecute(SelectedItems); +} + +void FWwiseBrowserDataSource::MergeDataSources(bool bGenerateUAssetsInfo) +{ + FWwiseTreeItemPtr WaapiRootItem; + FWwiseTreeItemPtr SoundBanksRootItem; + + if (bGenerateUAssetsInfo) + { + UAssetDataSource->ConstructItems(); + } + + { + FScopeLock AutoLock(&RootItemsLock); + RootItemsUnfiltered.Empty(); + } + + for (int i = EWwiseItemType::Event; i <= EWwiseItemType::LastWwiseBrowserType; ++i) + { + + if (!CurrentFilterText.IsValid() || CurrentFilterText->GetRawFilterText().IsEmpty()) + { + SoundBanksRootItem = ProjectDBDataSource->GetRootItem(static_cast(i)); + } + + else + { + SoundBanksRootItem = ProjectDBDataSource->LoadFilteredRootItem(static_cast(i), CurrentFilterText); + } + + FWwiseTreeItemPtr NewRootItem; + + if (SoundBanksRootItem) + { + NewRootItem = MakeShared(SoundBanksRootItem->DisplayName, SoundBanksRootItem->FolderPath, nullptr, SoundBanksRootItem->ItemType, SoundBanksRootItem->ItemId); + NewRootItem->WwiseItemRef = SoundBanksRootItem->WwiseItemRef; + NewRootItem->ShortId = SoundBanksRootItem->ShortId; + UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("Creating Tree for %s "), *SoundBanksRootItem->FolderPath) + } + + if (!CurrentFilterText.IsValid() || CurrentFilterText->GetRawFilterText().IsEmpty()) + { + WaapiRootItem = WaapiDataSource->GetRootItem(static_cast(i)); + } + + else + { + WaapiRootItem = WaapiDataSource->LoadFilteredRootItem(static_cast(i), CurrentFilterText); + } + + + if (WaapiRootItem) + { + if (!NewRootItem) + { + NewRootItem = MakeShared(WaapiRootItem->DisplayName, WaapiRootItem->FolderPath, nullptr, WaapiRootItem->ItemType, WaapiRootItem->ItemId); + NewRootItem->WwiseItemRef = WaapiRootItem->WwiseItemRef; + } + + NewRootItem->ItemId = WaapiRootItem->ItemId; + } + + if(SoundBanksRootItem || WaapiRootItem) + { + CreateUnifiedTree(SoundBanksRootItem, WaapiRootItem, NewRootItem); + UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("Merging Tree for %s "), *NewRootItem->FolderPath); + + FScopeLock AutoLock(&RootItemsLock); + RootItemsUnfiltered.Add(NewRootItem); + } + } + + // Add the Orphan UAssets + { + FScopeLock AutoLock(&RootItemsLock); + FWwiseTreeItemPtr RootItem = MakeShared(FString("Orphaned UAssets"), FString("\\Orphaned UAssets"), nullptr, EWwiseItemType::Folder, FGuid()); + TMap OrphanAssets; + UAssetDataSource->GetOrphanAssets(OrphanAssets); + for(auto& OrphanAsset : OrphanAssets) + { + //Check if they should be filtered out by the text + if (CurrentFilterText.IsValid() && !CurrentFilterText->GetRawFilterText().IsEmpty()) + { + if (!OrphanAsset.Value.Id.Name.ToString().Contains(CurrentFilterText->GetRawFilterText().ToString())) + { + continue; + } + } + FWwiseTreeItemPtr NewItem = MakeShared(OrphanAsset.Value.Id.Name.ToString(), FString(), RootItem, OrphanAsset.Value.Type, OrphanAsset.Key); + NewItem->Assets = OrphanAsset.Value.AssetsData; + NewItem->UAssetName = OrphanAsset.Value.Id.Name; + NewItem->ShortId = OrphanAsset.Value.Id.ShortId; + RootItem->AddChild(NewItem); + + } + RootItemsUnfiltered.Add(RootItem); + } + + + WwiseBrowserDataSourceRefreshed.ExecuteIfBound(); + +} + +void FWwiseBrowserDataSource::CreateUnifiedTree(const FWwiseTreeItemPtr& TreeItemRootSoundBank, const FWwiseTreeItemPtr& TreeItemRootWaapi, FWwiseTreeItemPtr& TreeItemRootDst) +{ + if (TreeItemRootSoundBank) + { + CreateProjectDBItem(TreeItemRootSoundBank, TreeItemRootDst); + } + if (TreeItemRootWaapi) + { + CreateWaapiItem(TreeItemRootWaapi, TreeItemRootDst); + } + +} + +void FWwiseBrowserDataSource::CreateProjectDBItem(const FWwiseTreeItemPtr& TreeItemRootSoundBank, FWwiseTreeItemPtr& TreeItemRootDst) +{ + FString DefaultAssetName = TreeItemRootSoundBank->GetDefaultAssetName(); + TreeItemRootDst = MakeShared(TreeItemRootSoundBank->DisplayName, TreeItemRootSoundBank->FolderPath, nullptr, TreeItemRootSoundBank->ItemType, TreeItemRootSoundBank->ItemId); + if(TreeItemRootSoundBank->ShouldDisplayInfo()) + { + TArray Assets; + EWwiseItemType::Type Type; + FName UAssetName; + UAssetDataSource->GetAssetsInfo(TreeItemRootSoundBank->ItemId, TreeItemRootSoundBank->ShortId, DefaultAssetName, Type, UAssetName, Assets); + TreeItemRootDst->Assets = Assets; + TreeItemRootDst->UAssetName = UAssetName; + } + TreeItemRootDst->WwiseItemRef = TreeItemRootSoundBank->WwiseItemRef; + TreeItemRootDst->ShortId = TreeItemRootSoundBank->ShortId; + //State Groups have a None State that does not appear in Wwise. Set the Ref to true to avoid "Deleted in Wwise". + if(TreeItemRootDst->ItemType == EWwiseItemType::State && TreeItemRootDst->DisplayName == FString("None")) + { + TreeItemRootDst->WaapiName = TreeItemRootDst->DisplayName; + TreeItemRootDst->bWaapiRefExists = true; + } + for (auto& Child : TreeItemRootSoundBank->GetChildren()) + { + FWwiseTreeItemPtr NewItem; + CreateProjectDBItem(Child, NewItem); + TreeItemRootDst->AddChild(NewItem); + } + TreeItemRootDst->SortChildren(); +} + +void FWwiseBrowserDataSource::CreateWaapiExclusiveItem(const FWwiseTreeItemPtr& WaapiItem, FWwiseTreeItemPtr& TreeItemRootDst) +{ + FWwiseTreeItemPtr CurrItem = MakeShared(WaapiItem->DisplayName, WaapiItem->FolderPath, nullptr, WaapiItem->ItemType, WaapiItem->ItemId); + if(WaapiItem->ShouldDisplayInfo()) + { + FString DefaultAssetName = WaapiItem->GetDefaultAssetName(); + TArray Assets; + FName AssetName; + EWwiseItemType::Type ItemType; + UAssetDataSource->GetAssetsInfo(WaapiItem->ItemId, WaapiItem->ShortId, DefaultAssetName, ItemType, AssetName, Assets); + CurrItem->UAssetName = AssetName; + CurrItem->Assets = Assets; + } + CurrItem->WaapiName = WaapiItem->DisplayName; + CurrItem->bWaapiRefExists = true; + CurrItem->ChildCountInWwise = WaapiItem->ChildCountInWwise; + TreeItemRootDst->AddChild(CurrItem); +} + +void FWwiseBrowserDataSource::CreateWaapiItem(const FWwiseTreeItemPtr& TreeItemRootWaapi, FWwiseTreeItemPtr& TreeItemRootDst) +{ + FWwiseTreeItemPtr NewItem; + for (const auto& WaapiItem : TreeItemRootWaapi->GetChildren()) + { + NewItem = TreeItemRootDst->GetChild(WaapiItem->ItemId, WaapiItem->ShortId, WaapiItem->DisplayName); + if (NewItem) + { + NewItem->WaapiName = WaapiItem->DisplayName; + NewItem->bWaapiRefExists = true; + continue; + } + if (WaapiItem->IsFolder()) + { + NewItem = MakeShared(WaapiItem->DisplayName, WaapiItem->FolderPath, nullptr, WaapiItem->ItemType, WaapiItem->ItemId); + TreeItemRootDst->AddChild(NewItem); + continue; + } + auto SoundBankItem = ProjectDBDataSource->FindItem(WaapiItem); + //The item was moved in Wwise + if (SoundBankItem) + { + auto Root = TreeItemRootDst->GetRoot(); + NewItem = Root->FindItemRecursive(WaapiItem); + if (NewItem) + { + NewItem->WaapiName = WaapiItem->DisplayName; + NewItem->bWaapiRefExists = true; + NewItem->bSameLocation = false; + } + } + else + { + CreateWaapiExclusiveItem(WaapiItem, TreeItemRootDst); + } + } + TreeItemRootDst->SortChildren(); + for (auto CurrItem : TreeItemRootDst->GetChildren()) + { + if (TreeItemRootWaapi) + { + if (FWwiseTreeItemPtr WaapiItem = TreeItemRootWaapi->GetChild(CurrItem->ItemId, CurrItem->ShortId, CurrItem->DisplayName)) + { + CreateWaapiItem(WaapiItem, CurrItem); + } + } + } +} + +void FWwiseBrowserDataSource::OnWaapiDataSourceRefreshed() +{ + MergeDataSources(false); +} + +void FWwiseBrowserDataSource::OnProjecDBDataSourceRefreshed() +{ + MergeDataSources(); +} + +void FWwiseBrowserDataSource::SetupAssetCallbacks() +{ + FAssetRegistryModule* AssetRegistryModule = &FModuleManager::LoadModuleChecked("AssetRegistry"); + + if (AssetRegistryModule) + { + auto& AssetRegistry = AssetRegistryModule->Get(); + OnAssetAdded = AssetRegistry.OnAssetAdded().AddRaw(this, &FWwiseBrowserDataSource::OnUAssetSourceRefresh); + OnAssetRemoved = AssetRegistry.OnAssetRemoved().AddRaw(this, &FWwiseBrowserDataSource::OnUAssetSourceRefresh); + OnAssetRenamed = AssetRegistry.OnAssetRenamed().AddRaw(this, &FWwiseBrowserDataSource::OnUAssetSourceRefresh); + OnAssetUpdated = AssetRegistry.OnAssetUpdated().AddRaw(this, &FWwiseBrowserDataSource::OnUAssetSourceRefresh); + } +} + +void FWwiseBrowserDataSource::OnFilesFullyLoaded() +{ + if (OnFilesLoaded.IsValid()) + { + FAssetRegistryModule* AssetRegistryModule = &FModuleManager::LoadModuleChecked("AssetRegistry"); + if (AssetRegistryModule) + { + auto& AssetRegistry = AssetRegistryModule->Get(); + AssetRegistry.OnFilesLoaded().Remove(OnFilesLoaded); + } + } + SetupAssetCallbacks(); + MergeDataSources(); +} + +void FWwiseBrowserDataSource::OnTimerTick(float DeltaSeconds) +{ + AssetUpdateTimer -= DeltaSeconds; + if(AssetUpdateTimer <= 0.f) + { + MergeDataSources(); + if(PostEditorTickHandle.IsValid()) + { + GEngine->OnPostEditorTick().Remove(PostEditorTickHandle); + PostEditorTickHandle.Reset(); + } + } +} + +void FWwiseBrowserDataSource::OnUAssetSourceRefresh(const FAssetData& AssetData) +{ + if(AkUnrealAssetDataHelper::IsAssetAkAudioType(AssetData) && !AkUnrealAssetDataHelper::IsAssetTransient(AssetData)) + { + if(!PostEditorTickHandle.IsValid()) + { + PostEditorTickHandle = GEngine->OnPostEditorTick().AddRaw(this, &FWwiseBrowserDataSource::OnTimerTick); + } + AssetUpdateTimer = AssetTimerRefresh; + } +} + +void FWwiseBrowserDataSource::OnUAssetSourceRefresh(const FAssetData& AssetData, const FString& OldPath) +{ + if (AkUnrealAssetDataHelper::IsAssetAkAudioType(AssetData) && !AkUnrealAssetDataHelper::IsAssetTransient(AssetData)) + { + if(!PostEditorTickHandle.IsValid()) + { + PostEditorTickHandle = GEngine->OnPostEditorTick().AddRaw(this, &FWwiseBrowserDataSource::OnTimerTick); + } + AssetUpdateTimer = AssetTimerRefresh; + } +} + +void FWwiseBrowserDataSource::OnWwiseSelectionChange(const TArray>& Items) +{ + WwiseSelectionChange.ExecuteIfBound(Items); +} + +void FWwiseBrowserDataSource::OnWwiseExpansionChange(const TArray>& Items) +{ + WwiseExpansionChange.ExecuteIfBound(Items); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/WwiseBrowserDataSource.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/WwiseBrowserDataSource.h new file mode 100644 index 0000000..e4df9e3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/WwiseBrowserDataSource.h @@ -0,0 +1,196 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "UAssetDataSource.h" + +#include "WaapiDataSource.h" +#include "../WwiseBrowserForwards.h" +#include "WwiseItemType.h" +#include "WwiseProjectDatabaseSource.h" +#include "WwiseBrowser/WwiseAssetDragDropOp.h" + +struct FWwiseTreeItem; + +enum ESoundBankStatusFilter +{ + NewInWwise, + DeletedInWwise, + RenamedInWwise, + NotInWwise, + MovedInWwise, + UpToDate, + NumberOfSoundBankStatus +}; + +enum EUAssetStatusFilter +{ + UAssetMissing, + NotInSoundBankOrUnreal, + UAssetOrphaned, + RenamedInSoundBank, + MultipleUAssets, + UAssetUpToDate, + NumberOfUAssetStatus +}; + +enum EWwiseTypeFilter +{ + AcousticTexture, + Effects, + Events, + GameParameters, + MasterMixerHierarchy, + State, + Switch, + Trigger, + NumberOfWwiseTypes +}; + +struct FSoundBankStatusFilter +{ + bool bFilters[NumberOfSoundBankStatus] = { false }; + bool IsKeptInBrowser(FWwiseTreeItemPtr& Item) const; + bool AreFiltersOff() const; +}; + +struct FUAssetStatusFilter +{ + bool bFilters[NumberOfUAssetStatus] = { false }; + bool IsKeptInBrowser(FWwiseTreeItemPtr& Item) const; + bool AreFiltersOff() const; +}; + +struct FWwiseTypeFilter +{ + bool bFilters[NumberOfWwiseTypes] = { false }; + bool IsKeptInBrowser(FWwiseTreeItemPtr& Item) const; + bool AreFiltersOff() const; +private: + EWwiseItemType::Type GetExpectedType(EWwiseTypeFilter Filter) const; +}; + +class FWwiseBrowserDataSource +{ +public: + DECLARE_DELEGATE(FOnWwiseBrowserDataSourceRefreshed) + + FWwiseBrowserDataSource(); + + ~FWwiseBrowserDataSource(); + + void ConstructTree(); + bool AreFiltersOff(); + + void ApplyTextFilter(TSharedPtr FilterText); + + void ApplyFilter(FSoundBankStatusFilter SoundBankStatusFilter, + FUAssetStatusFilter UAssetStatusFilter, + FWwiseTypeFilter WwiseTypeFilter); + + void ApplyFilter(FWwiseTreeItemPtr Item, FWwiseTreeItemPtr& OutItem); + + void ClearFilter(); + + bool IsKeptInBrowser(FWwiseTreeItemPtr Item); + + FWwiseTreeItemPtr GetTreeRootForType(EWwiseItemType::Type ItemType, const FString& FilterText={}); + + FText GetProjectName(); + + FText GetConnectedWwiseProjectName(); + + int32 LoadChildren(FWwiseTreeItemPtr TreeItem, TArray& OutChildren); + + // Clean placeholder children + void ClearEmptyChildren(FWwiseTreeItemPtr TreeItem); + + EWwiseConnectionStatus GetWaapiConnectionStatus() const; + + FString GetItemWorkUnitPath(FWwiseTreeItemPtr InTreeItem); + + void SelectInWwiseProjectExplorer(TArray& InTreeItem); + + FOnWwiseBrowserDataSourceRefreshed WwiseBrowserDataSourceRefreshed; + + FOnWaapiSelectionChange WwiseSelectionChange; + FOnWaapiSelectionChange WwiseExpansionChange; + + FDelegateHandle PostEditorTickHandle; + + void HandleFindWwiseItemInProjectExplorerCommandExecute(const TArray& SelectedItems) const; + + void CreateProjectDBItem(const FWwiseTreeItemPtr& TreeItemRootSoundBank, FWwiseTreeItemPtr& TreeItemRootDst); + void CreateWaapiExclusiveItem(const FWwiseTreeItemPtr& WaapiItem, FWwiseTreeItemPtr& TreeItemRootDst); + void CreateWaapiItem(const FWwiseTreeItemPtr& TreeItemRootWwise, FWwiseTreeItemPtr& TreeItemRootDst); +private: + + void MergeDataSources(bool bGenerateUAssetsInfo = true); + + bool bIsDirty = false; + + float AssetUpdateTimer = 0.f; + + const float AssetTimerRefresh = 0.1f; + + TUniquePtr WaapiDataSource; + + TUniquePtr ProjectDBDataSource; + + TUniquePtr UAssetDataSource; + + /** Root items, one for each type of Wwise object */ + FCriticalSection RootItemsLock; + TArray< FWwiseTreeItemPtr > RootItems; + + TArray< FWwiseTreeItemPtr > RootItemsUnfiltered; + + TSharedPtr CurrentFilterText; + + FSoundBankStatusFilter SoundBankStatusFilter; + + FUAssetStatusFilter UAssetStatusFilter; + + FWwiseTypeFilter WwiseTypeFilter; + + // Merges TreeItemRootSrc and TreeItemRootDst, with the resulting tree in TreeItemRootDst. New TreeItems will be created if they do not exist in TreeItemRootDst + void CreateUnifiedTree(const FWwiseTreeItemPtr& TreeItemRootSoundBank, const FWwiseTreeItemPtr& TreeItemRootWaapi, FWwiseTreeItemPtr& TreeItemRootDst); + + void OnWaapiDataSourceRefreshed(); + + void OnProjecDBDataSourceRefreshed(); + + void SetupAssetCallbacks(); + void OnFilesFullyLoaded(); + void OnTimerTick(float DeltaSeconds); + + void OnUAssetSourceRefresh(const FAssetData& RemovedAssetData); + + void OnUAssetSourceRefresh(const FAssetData& RemovedAssetData, const FString& OldPath); + + void OnWwiseSelectionChange(const TArray>& Items); + void OnWwiseExpansionChange(const TArray>& Items); + + FDelegateHandle OnAssetAdded; + FDelegateHandle OnAssetRemoved; + FDelegateHandle OnAssetRenamed; + FDelegateHandle OnAssetUpdated; + FDelegateHandle OnFilesLoaded; +}; + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/WwiseProjectDatabaseSource.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/WwiseProjectDatabaseSource.cpp new file mode 100644 index 0000000..b1ef477 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/WwiseProjectDatabaseSource.cpp @@ -0,0 +1,636 @@ +#include "WwiseProjectDatabaseSource.h" + +#include "IAudiokineticTools.h" +#include "AssetManagement/AkAssetDatabase.h" +#include "Async/Async.h" +#include "WaapiPicker/WwiseTreeItem.h" +#include "Wwise/WwiseProjectDatabase.h" +#include "Wwise/WwiseProjectDatabaseDelegates.h" +#include "Wwise/Metadata/WwiseMetadataProjectInfo.h" + +FWwiseProjectDatabaseDataSource::~FWwiseProjectDatabaseDataSource() +{ + if (OnDatabaseUpdateCompleteHandle.IsValid()) + { + FWwiseProjectDatabaseDelegates::Get().GetOnDatabaseUpdateCompletedDelegate().Remove(OnDatabaseUpdateCompleteHandle); + OnDatabaseUpdateCompleteHandle.Reset(); + } +} + +FText FWwiseProjectDatabaseDataSource::GetProjectName() +{ + auto* ProjectDatabase = FWwiseProjectDatabase::Get(); + if (UNLIKELY(!ProjectDatabase)) + { + return {}; + } + + const FWwiseDataStructureScopeLock DataStructure(*ProjectDatabase); + const FWwiseRefPlatform Platform = DataStructure.GetPlatform(ProjectDatabase->GetCurrentPlatform()); + + if (Platform.IsValid()) + { + if (const FWwiseMetadataProjectInfo* ProjectInfo = Platform.ProjectInfo.GetProjectInfo()) + { + return FText::FromName(ProjectInfo->Project.Name); + } + } + return {}; +} + +bool FWwiseProjectDatabaseDataSource::Init() +{ + + OnDatabaseUpdateCompleteHandle = FWwiseProjectDatabaseDelegates::Get().GetOnDatabaseUpdateCompletedDelegate().AddLambda([this] + { + AsyncTask(ENamedThreads::Type::GameThread, [this] + { + this->ConstructTree(true); + }); + }); + + return true; +} + +void FWwiseProjectDatabaseDataSource::ConstructTree(bool bShouldRefresh) +{ + UE_LOG(LogAudiokineticTools, Log, TEXT("Rebuilding tree for Wwise Browser")); + + FWwiseProjectDatabase* ProjectDatabase = FWwiseProjectDatabase::Get(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("ConstructTree: ProjectDatabase not loaded")); + return; + } + + AllValidTreeItemsByGuid.Empty(); + + { + FScopeLock AutoLock(&RootItemsLock); + RootItems.Empty(); + RootItems.SetNum(EWwiseItemType::LastWwiseBrowserType + 1); + } + + NodesByPath.Empty(); + + { + const FWwiseDataStructureScopeLock DataStructure(*ProjectDatabase); + if (DataStructure.GetCurrentPlatformData() == nullptr) + { + return; + } + const WwiseEventGlobalIdsMap& Events = DataStructure.GetEvents(); + const WwiseBusGlobalIdsMap& Busses = DataStructure.GetBusses(); + const WwiseAuxBusGlobalIdsMap& AuxBusses = DataStructure.GetAuxBusses(); + const WwiseAcousticTextureGlobalIdsMap& AcousticTextures = DataStructure.GetAcousticTextures(); + const WwiseStateGroupGlobalIdsMap& StateGroups = DataStructure.GetStateGroups(); + const WwiseStateGlobalIdsMap& States = DataStructure.GetStates(); + const WwiseSwitchGlobalIdsMap& Switches = DataStructure.GetSwitches(); + const WwiseGameParameterGlobalIdsMap& GameParameters = DataStructure.GetGameParameters(); + const WwiseTriggerGlobalIdsMap& Triggers = DataStructure.GetTriggers(); + const WwisePluginShareSetGlobalIdsMap& EffectShareSets = DataStructure.GetPluginShareSets(); + const WwiseSwitchGroupGlobalIdsMap SwitchGroups = DataStructure.GetSwitchGroups(); + + BuildEvents(Events); + BuildBusses(Busses); + BuildAuxBusses(AuxBusses); + BuildAcousticTextures(AcousticTextures); + BuildStateGroups(StateGroups); + BuildStates(States); + BuildSwitchGroups(SwitchGroups); + BuildSwitches(Switches); + BuildGameParameters(GameParameters); + BuildTriggers(Triggers); + BuildEffectShareSets(EffectShareSets); + } + + if(bShouldRefresh) + { + ProjectDatabaseDataSourceRefreshed.ExecuteIfBound(); + } +} + +FWwiseTreeItemPtr FWwiseProjectDatabaseDataSource::ConstructTreeRoot(EWwiseItemType::Type Type) +{ + ConstructTree(false); + + { + FScopeLock AutoLock(&RootItemsLock); + return RootItems[Type]; + } +} + +int32 FWwiseProjectDatabaseDataSource::LoadChildren( + const FGuid& InParentId, const FString& InParentPath, TArray& OutChildren) +{ + FWwiseTreeItemPtr* TreeItem = NodesByPath.Find(InParentPath); + + if (TreeItem && TreeItem->IsValid()) + { + OutChildren = TreeItem->Get()->GetChildren(); + } + + else + { + OutChildren = {}; + } + + return OutChildren.Num(); +} + +int32 FWwiseProjectDatabaseDataSource::LoadChildren(FWwiseTreeItemPtr InParentItem) +{ + if (!InParentItem) + { + return 0; + } + + + // For now, we have to construct the whole tree at once, so this can't be individually loaded. So we return what we have. + return InParentItem->GetChildren().Num(); +} + +int32 FWwiseProjectDatabaseDataSource::GetChildItemCount(const FWwiseTreeItemPtr& InParentItem) +{ + return InParentItem->GetChildren().Num(); +} + +FWwiseTreeItemPtr FWwiseProjectDatabaseDataSource::GetRootItem(EWwiseItemType::Type RootType) +{ + check(RootType <= EWwiseItemType::LastWwiseBrowserType) + + if (RootType > RootItems.Num() - 1) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to get the Project Database Root Items. Index out of range.")); + return nullptr; + } + + return RootItems[RootType]; +} + +FWwiseTreeItemPtr FWwiseProjectDatabaseDataSource::LoadFilteredRootItem(EWwiseItemType::Type ItemType, + TSharedPtr CurrentFilter) +{ + check(CurrentFilter.IsValid()) + + FWwiseTreeItemPtr CurrentTreeRootItem; + + { + FScopeLock AutoLock(&RootItemsLock); + if (ItemType > RootItems.Num()) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to get the Project Database Root Items. Index out of range.")); + return nullptr; + } + + CurrentTreeRootItem = RootItems[ItemType]; + FString CurrentFilterText = CurrentFilter->GetRawFilterText().ToString(); + + if (!CurrentFilterText.IsEmpty() && CurrentTreeRootItem.IsValid()) + { + FWwiseTreeItemPtr FilteredTreeRootItem = MakeShared(EWwiseItemType::BrowserDisplayNames[ItemType], + CurrentTreeRootItem->FolderPath, nullptr, EWwiseItemType::Folder, FGuid(CurrentTreeRootItem->ItemId)); + FilteredTreeRootItem->ChildCountInWwise = CurrentTreeRootItem->ChildCountInWwise; + + if (!OldFilterText.IsEmpty() && CurrentFilterText.StartsWith(OldFilterText)) + { + CopyTree(CurrentTreeRootItem, FilteredTreeRootItem); + } + + else + { + CopyTree(RootItems[ItemType], FilteredTreeRootItem); + } + + FilterTree(FilteredTreeRootItem, CurrentFilter); + OldFilterText = CurrentFilterText; + return FilteredTreeRootItem; + } + + if (RootItems[ItemType]) + { + return RootItems[ItemType]; + } + + return nullptr; + } +} + +FWwiseTreeItemPtr FWwiseProjectDatabaseDataSource::FindItemFromPath(const FString& InCurrentItemPath) +{ + FWwiseTreeItemPtr* FoundItem = NodesByPath.Find(InCurrentItemPath); + if (FoundItem) + { + return *FoundItem; + } + + return nullptr; +} + +FWwiseTreeItemPtr FWwiseProjectDatabaseDataSource::FindItem(const FWwiseTreeItemPtr InItem) +{ + FWwiseTreeItemPtr* FoundItem = AllValidTreeItemsByGuid.Find(InItem->ItemId); + if (FoundItem) + { + return *FoundItem; + } + + for(auto TreeItem : AllValidTreeItemsByGuid) + { + if (TreeItem.Value->ShortId == InItem->ShortId && InItem->ShortId > 0) + { + return TreeItem.Value; + } + if(TreeItem.Value->DisplayName == InItem->DisplayName) + { + return TreeItem.Value; + } + } + return nullptr; +} + +void FWwiseProjectDatabaseDataSource::BuildEvents(const WwiseEventGlobalIdsMap& Events) +{ + const auto& FolderItem = MakeShared(EWwiseItemType::EventsBrowserName, TEXT("\\") + EWwiseItemType::FolderNames[EWwiseItemType::Event], nullptr, EWwiseItemType::Folder, FGuid()); + NodesByPath.Add(FolderItem->FolderPath, FolderItem); + + { + FScopeLock AutoLock(&RootItemsLock); + RootItems[EWwiseItemType::Event] = FolderItem; + } + + for (const auto& Event : Events) + { + const auto& WwiseItem = Event.Value.GetEvent(); + UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("Event Name: %s"), *WwiseItem->Name.ToString()); + + FWwiseMetadataBasicReference EventRef = FWwiseMetadataBasicReference(WwiseItem->Id, WwiseItem->Name, WwiseItem->ObjectPath, WwiseItem->GUID); + if (!BuildFolderHierarchy(EventRef, EWwiseItemType::Event, FolderItem)) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to place %s in the Wwise Browser"), *WwiseItem->ObjectPath.ToString()); + } + } + + FolderItem->ChildCountInWwise = FolderItem->GetChildren().Num(); + +} + +void FWwiseProjectDatabaseDataSource::BuildBusses(const WwiseBusGlobalIdsMap& Busses) +{ + const auto& FolderItem = MakeShared(EWwiseItemType::BussesBrowserName, TEXT("\\") + EWwiseItemType::FolderNames[EWwiseItemType::AuxBus], nullptr, EWwiseItemType::Folder, FGuid()); + NodesByPath.Add(FolderItem->FolderPath, FolderItem); + + { + FScopeLock AutoLock(&RootItemsLock); + RootItems[EWwiseItemType::AuxBus] = FolderItem; + } + + for (auto& Bus : Busses) + { + const auto& WwiseItem = Bus.Value.GetBus(); + UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("Bus Name: %s"), *WwiseItem->Name.ToString()); + + if (!BuildFolderHierarchy(*WwiseItem, EWwiseItemType::Bus, FolderItem)) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to place %s in the Wwise Browser"), *WwiseItem->ObjectPath.ToString()); + } + } + + FolderItem->ChildCountInWwise = FolderItem->GetChildren().Num(); +} + +void FWwiseProjectDatabaseDataSource::BuildAuxBusses(const WwiseAuxBusGlobalIdsMap& AuxBusses) +{ + + FWwiseTreeItemPtr FolderItem; + + { + FScopeLock AutoLock(&RootItemsLock); + FolderItem = RootItems[EWwiseItemType::AuxBus]; + } + + for (const auto& AuxBus : AuxBusses) + { + const auto& WwiseItem = AuxBus.Value.GetAuxBus(); + UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("Aux Bus Name: %s"), *WwiseItem->Name.ToString()); + + if (!BuildFolderHierarchy(*WwiseItem, EWwiseItemType::AuxBus, FolderItem)) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to place %s in the Wwise Browser"), *WwiseItem->ObjectPath.ToString()); + } + } +} + +void FWwiseProjectDatabaseDataSource::BuildAcousticTextures(const WwiseAcousticTextureGlobalIdsMap& AcousticTextures) +{ + const auto& FolderItem = MakeShared(EWwiseItemType::AcousticTexturesBrowserName, TEXT("\\") + EWwiseItemType::FolderNames[EWwiseItemType::AcousticTexture], nullptr, EWwiseItemType::Folder, FGuid()); + NodesByPath.Add(FolderItem->FolderPath, FolderItem); + + { + FScopeLock AutoLock(&RootItemsLock); + RootItems[EWwiseItemType::AcousticTexture] = FolderItem; + } + + for (const auto& AcousticTexture : AcousticTextures) + { + const FWwiseMetadataAcousticTexture* WwiseItem = AcousticTexture.Value.GetAcousticTexture(); + UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("Acoustic Texture Name: %s"), *WwiseItem->Name.ToString()); + + if (!BuildFolderHierarchy(*WwiseItem, EWwiseItemType::AcousticTexture, FolderItem)) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to place %s in the Wwise Browser"), *WwiseItem->ObjectPath.ToString()); + } + } + + FolderItem->ChildCountInWwise = FolderItem->GetChildren().Num(); +} + +void FWwiseProjectDatabaseDataSource::BuildStateGroups(const WwiseStateGroupGlobalIdsMap& StateGroups) +{ + const auto& FolderItem = MakeShared(EWwiseItemType::StatesBrowserName, TEXT("\\") + EWwiseItemType::FolderNames[EWwiseItemType::State], nullptr, EWwiseItemType::Folder, FGuid()); + NodesByPath.Add(FolderItem->FolderPath, FolderItem); + + { + FScopeLock AutoLock(&RootItemsLock); + RootItems[EWwiseItemType::State] = FolderItem; + } + + for (const auto& StateGroup : StateGroups) + { + const auto& WwiseItem = StateGroup.Value.GetStateGroup(); + UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("State Group Name: %s"), *WwiseItem->Name.ToString()); + + if (!BuildFolderHierarchy(*WwiseItem, EWwiseItemType::StateGroup, FolderItem)) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to place %s in the Wwise Browser"), *WwiseItem->ObjectPath.ToString()); + } + } + + FolderItem->ChildCountInWwise = FolderItem->GetChildren().Num(); +} + +void FWwiseProjectDatabaseDataSource::BuildStates(const WwiseStateGlobalIdsMap& States) +{ + FWwiseTreeItemPtr StateGroupFolderItem; + + { + FScopeLock AutoLock(&RootItemsLock); + StateGroupFolderItem = RootItems[EWwiseItemType::State]; + } + + for (const auto& State : States) + { + const auto& WwiseItem = State.Value.GetState(); + + if (!BuildFolderHierarchy(*WwiseItem, EWwiseItemType::State, StateGroupFolderItem)) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to place %s in the Wwise Browser"), *WwiseItem->ObjectPath.ToString()); + } + } +} + +void FWwiseProjectDatabaseDataSource::BuildSwitchGroups(const WwiseSwitchGroupGlobalIdsMap& SwitchGroups) +{ + const auto& FolderItem = MakeShared(EWwiseItemType::SwitchesBrowserName, TEXT("\\") + EWwiseItemType::FolderNames[EWwiseItemType::Switch], nullptr, EWwiseItemType::Folder, FGuid()); + NodesByPath.Add(FolderItem->FolderPath, FolderItem); + + { + FScopeLock AutoLock(&RootItemsLock); + RootItems[EWwiseItemType::Switch] = FolderItem; + } + + for (const auto& SwitchGroup : SwitchGroups) + { + const auto& WwiseItem = SwitchGroup.Value.GetSwitchGroup(); + UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("Switch Group Name: %s"), *WwiseItem->Name.ToString()); + + if (!BuildFolderHierarchy(*WwiseItem, EWwiseItemType::SwitchGroup, FolderItem)) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to place %s in the Wwise Browser"), *WwiseItem->ObjectPath.ToString()); + } + } + + FolderItem->ChildCountInWwise = FolderItem->GetChildren().Num(); +} + +void FWwiseProjectDatabaseDataSource::BuildSwitches(const WwiseSwitchGlobalIdsMap& Switches) +{ + FWwiseTreeItemPtr SwitchGroupFolderItem; + + { + FScopeLock AutoLock(&RootItemsLock); + SwitchGroupFolderItem = RootItems[EWwiseItemType::Switch]; + } + + for (const auto& Switch : Switches) + { + const auto& WwiseItem = Switch.Value.GetSwitch(); + + if (!BuildFolderHierarchy(*WwiseItem, EWwiseItemType::Switch, SwitchGroupFolderItem)) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to place %s in the Wwise Browser"), *WwiseItem->ObjectPath.ToString()); + } + } +} + +void FWwiseProjectDatabaseDataSource::BuildGameParameters(const WwiseGameParameterGlobalIdsMap& GameParameters) +{ + const auto& FolderItem = MakeShared(EWwiseItemType::GameParametersBrowserName, TEXT("\\") + EWwiseItemType::FolderNames[EWwiseItemType::GameParameter], nullptr, EWwiseItemType::Folder, FGuid()); + NodesByPath.Add(FolderItem->FolderPath, FolderItem); + + { + FScopeLock AutoLock(&RootItemsLock); + RootItems[EWwiseItemType::GameParameter] = FolderItem; + } + + for (const auto& GameParameter : GameParameters) + { + const FWwiseMetadataGameParameter* WwiseItem = GameParameter.Value.GetGameParameter(); + UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("GameParameter Name: %s"), *WwiseItem->Name.ToString()); + + if (!BuildFolderHierarchy(*WwiseItem, EWwiseItemType::GameParameter, FolderItem)) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to place %s in the Wwise Browser"), *WwiseItem->ObjectPath.ToString()); + } + } + + FolderItem->ChildCountInWwise = FolderItem->GetChildren().Num(); +} + +void FWwiseProjectDatabaseDataSource::BuildTriggers(const WwiseTriggerGlobalIdsMap& Triggers) +{ + const auto& FolderItem = MakeShared(EWwiseItemType::TriggersBrowserName, TEXT("\\") + EWwiseItemType::FolderNames[EWwiseItemType::Trigger], nullptr, EWwiseItemType::Folder, FGuid()); + NodesByPath.Add(FolderItem->FolderPath, FolderItem); + + { + FScopeLock AutoLock(&RootItemsLock); + RootItems[EWwiseItemType::Trigger] = FolderItem; + } + + for (const auto& Trigger : Triggers) + { + const auto& WwiseItem = Trigger.Value.GetTrigger(); + UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("Trigger Name: %s"), *WwiseItem->Name.ToString()); + + if (!BuildFolderHierarchy(*WwiseItem, EWwiseItemType::Trigger, FolderItem)) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to place %s in the Wwise Browser"), *WwiseItem->ObjectPath.ToString()); + } + } + + FolderItem->ChildCountInWwise = FolderItem->GetChildren().Num(); +} + +void FWwiseProjectDatabaseDataSource::BuildEffectShareSets(const WwisePluginShareSetGlobalIdsMap& EffectShareSets) +{ + const auto& FolderItem = MakeShared(EWwiseItemType::ShareSetsBrowserName, TEXT("\\") + EWwiseItemType::FolderNames[EWwiseItemType::EffectShareSet], nullptr, EWwiseItemType::Folder, FGuid()); + NodesByPath.Add(FolderItem->FolderPath, FolderItem); + + { + FScopeLock AutoLock(&RootItemsLock); + RootItems[EWwiseItemType::EffectShareSet] = FolderItem; + } + + for (const auto& EffectShareSet : EffectShareSets) + { + const auto& WwiseItem = EffectShareSet.Value.GetPlugin(); + UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("ShareSet Name: %s"), *WwiseItem->Name.ToString()); + + if (!BuildFolderHierarchy(*WwiseItem, EWwiseItemType::EffectShareSet, FolderItem)) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to place %s in the Wwise Browser"), *WwiseItem->ObjectPath.ToString()); + } + } + + FolderItem->ChildCountInWwise = FolderItem->GetChildren().Num(); +} + +bool FWwiseProjectDatabaseDataSource::ParseTreePath(const FString& ObjectPath, WwiseItemTreePath& OutItemPath) +{ + TArray SwitchPartsArray; + ObjectPath.ParseIntoArray(SwitchPartsArray, TEXT("\\")); + + if (SwitchPartsArray.Num() < 3) + { + UE_LOG(LogAudiokineticTools, Warning, TEXT("Failed to determine object hierarchy from path: %s"), *ObjectPath); + return false; + } + + OutItemPath.HierarchyName = SwitchPartsArray[0]; + OutItemPath.RootFolder = SwitchPartsArray[1]; + OutItemPath.ItemName = SwitchPartsArray[SwitchPartsArray.Num() - 1]; + + for (int i = 2; i < SwitchPartsArray.Num() - 1; ++i) + { + OutItemPath.IntermediateFolders.Add(SwitchPartsArray[i]); + } + + return true; +} + +bool FWwiseProjectDatabaseDataSource::IsContainer(EWwiseItemType::Type ItemType) const +{ + return ItemType == EWwiseItemType::Bus || ItemType == EWwiseItemType::SwitchGroup || ItemType == + EWwiseItemType::StateGroup || ItemType == EWwiseItemType::AuxBus || ItemType == EWwiseItemType::MotionBus; +} + +bool FWwiseProjectDatabaseDataSource::BuildFolderHierarchy( + const FWwiseMetadataBasicReference& WwiseItem, EWwiseItemType::Type + ItemType, const FWwiseTreeItemPtr CurrentRootFolder) +{ + const FString ItemPath = WwiseItem.ObjectPath.ToString(); + WwiseItemTreePath TreePath; + FWwiseTreeItemPtr ParentItem = CurrentRootFolder; + + if (ParseTreePath(ItemPath, TreePath)) + { + FString CurrentPath = "\\" + TreePath.HierarchyName; + TArray AllFolders = TArray{ TreePath.RootFolder }; + AllFolders.Append(TreePath.IntermediateFolders); + + for (FString FolderName : AllFolders) + { + CurrentPath.Append("\\").Append(FolderName); + + FWwiseTreeItemPtr FolderItem; + FWwiseTreeItemPtr* FolderItemPtr = NodesByPath.Find(CurrentPath); + + if (!FolderItemPtr) + { + FolderItem = MakeShared(FolderName, CurrentPath, ParentItem, + EWwiseItemType::Folder, FGuid()); + + NodesByPath.Add(CurrentPath, FolderItem); + ParentItem->AddChild(FolderItem); + ParentItem->ChildCountInWwise = ParentItem->GetChildren().Num(); + } + else + { + FolderItem = *FolderItemPtr; + } + + ParentItem = FolderItem; + } + + if (!AllValidTreeItemsByGuid.Find(WwiseItem.GUID)) + { + if(auto TreeItemPtr = NodesByPath.Find(WwiseItem.ObjectPath.ToString())) + { + auto TreeItem = *TreeItemPtr; + TreeItem->ItemType = ItemType; + TreeItem->ItemId = WwiseItem.GUID; + TreeItem->ShortId = WwiseItem.Id; + } + else + { + const auto& NewWwiseTreeItem = MakeShared(WwiseItem, ParentItem, ItemType); + NewWwiseTreeItem->ShortId = WwiseItem.Id; + ParentItem->AddChild(NewWwiseTreeItem); + AllValidTreeItemsByGuid.Add(NewWwiseTreeItem->ItemId, NewWwiseTreeItem); + + if (IsContainer(ItemType)) + { + NodesByPath.Add(WwiseItem.ObjectPath.ToString(), NewWwiseTreeItem); + } + } + } + + return true; + } + return false; +} + +void FWwiseProjectDatabaseDataSource::CopyTree(FWwiseTreeItemPtr SourceTreeItem, FWwiseTreeItemPtr DestTreeItem) +{ + for (auto& CurrItem: SourceTreeItem->GetChildren()) + { + FWwiseTreeItemPtr NewItem = MakeShared(CurrItem->DisplayName, CurrItem->FolderPath, CurrItem->Parent.Pin(), CurrItem->ItemType, CurrItem->ItemId); + NewItem->WwiseItemRef = CurrItem->WwiseItemRef; + DestTreeItem->AddChild(NewItem); + DestTreeItem->ChildCountInWwise = SourceTreeItem->ChildCountInWwise; + + CopyTree(CurrItem, NewItem); + } +} + +void FWwiseProjectDatabaseDataSource::FilterTree(FWwiseTreeItemPtr TreeItem, TSharedPtr SearchFilter) +{ + TArray ItemsToRemove; + for (auto& CurrItem: TreeItem->GetChildren()) + { + FilterTree(CurrItem, SearchFilter); + + if (!SearchFilter->PassesFilter(CurrItem->DisplayName) && CurrItem->GetChildren().Num() == 0) + { + ItemsToRemove.Add(CurrItem); + } + } + + for (int32 i = 0; i < ItemsToRemove.Num(); i++) + { + TreeItem->RemoveChild(ItemsToRemove[i]); + } + + TreeItem->ChildCountInWwise = TreeItem->GetChildren().Num(); +} + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/WwiseProjectDatabaseSource.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/WwiseProjectDatabaseSource.h new file mode 100644 index 0000000..411174a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/DataSource/WwiseProjectDatabaseSource.h @@ -0,0 +1,82 @@ +#pragma once + +#include "IWwiseBrowserDataSource.h" +#include "Misc/TextFilter.h" +#include "Wwise/Ref/WwiseRefCollections.h" + +struct FWwiseMetadataBasicReference; + +struct WwiseItemTreePath +{ + FString HierarchyName; + FString RootFolder; + TArray IntermediateFolders; + FString ItemName; +}; + +class FWwiseProjectDatabaseDataSource : IWwiseBrowserDataSource +{ +public: + DECLARE_DELEGATE(FOnWwiseProjectDatasbaseDataSourceRefreshed) + + virtual ~FWwiseProjectDatabaseDataSource() override; + + FText GetProjectName(); + + // IWwiseBrowserDataSource + + virtual bool Init() override; + virtual void ConstructTree(bool bShouldRefresh) override; + virtual FWwiseTreeItemPtr ConstructTreeRoot(EWwiseItemType::Type Type) override; + virtual int32 LoadChildren(const FGuid& InParentId, const FString& InParentPath, TArray& OutChildren) override; + virtual int32 LoadChildren(FWwiseTreeItemPtr InParentItem) override; + virtual int32 GetChildItemCount(const FWwiseTreeItemPtr& InParentItem) override; + virtual FWwiseTreeItemPtr GetRootItem(EWwiseItemType::Type RootType) override; + virtual FWwiseTreeItemPtr LoadFilteredRootItem(EWwiseItemType::Type ItemType, TSharedPtr CurrentFilter) override; + + FWwiseTreeItemPtr FindItemFromPath(const FString& InCurrentItemPath); + FWwiseTreeItemPtr FindItem(const FWwiseTreeItemPtr InItem); + + FOnWwiseProjectDatasbaseDataSourceRefreshed ProjectDatabaseDataSourceRefreshed; + +private: + + + void BuildEvents(const WwiseEventGlobalIdsMap& Events); + void BuildBusses(const WwiseBusGlobalIdsMap& Busses); + void BuildAuxBusses(const WwiseAuxBusGlobalIdsMap& AuxBusses); + void BuildAcousticTextures(const WwiseAcousticTextureGlobalIdsMap& AcousticTextures); + void BuildStateGroups(const WwiseStateGroupGlobalIdsMap& StateGroups); + void BuildStates(const WwiseStateGlobalIdsMap& States); + void BuildSwitchGroups(const WwiseSwitchGroupGlobalIdsMap& SwitchGroups); + void BuildSwitches(const WwiseSwitchGlobalIdsMap& Switches); + void BuildGameParameters(const WwiseGameParameterGlobalIdsMap& GameParameters); + void BuildTriggers(const WwiseTriggerGlobalIdsMap& Triggers); + void BuildEffectShareSets(const WwisePluginShareSetGlobalIdsMap& EffectShareSets); + bool ParseTreePath(const FString& ObjectPath, WwiseItemTreePath& OutItemPath); + + bool BuildFolderHierarchy(const FWwiseMetadataBasicReference& WwiseItem, EWwiseItemType::Type ItemType, + const FWwiseTreeItemPtr + CurrentRootFolder); + + void CopyTree(FWwiseTreeItemPtr SourceTreeItem, FWwiseTreeItemPtr DestTreeItem); + + void FilterTree(FWwiseTreeItemPtr TreeItem, TSharedPtr SearchFilter); + + bool IsContainer(EWwiseItemType::Type ItemType) const; + + /** Root items, one for each type of Wwise object */ + FCriticalSection RootItemsLock; + TArray< FWwiseTreeItemPtr > RootItems; + + // Map of all tree items + TMap AllValidTreeItemsByGuid; + + // Container paths along the Browser Tree + TMap NodesByPath; + + // Allows some optimization if we have already applied a search + FString OldFilterText; + + FDelegateHandle OnDatabaseUpdateCompleteHandle; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/IWwiseBrowserColumn.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/IWwiseBrowserColumn.h new file mode 100644 index 0000000..3953b77 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/IWwiseBrowserColumn.h @@ -0,0 +1,36 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "WwiseBrowserForwards.h" +#include "Widgets/SWidget.h" +#include "Widgets/Views/SHeaderRow.h" +#include "Widgets/Views/STableRow.h" + +class IWwiseBrowserColumn : public TSharedFromThis< IWwiseBrowserColumn > +{ +public: + virtual ~IWwiseBrowserColumn() {} + + virtual FName GetColumnId() = 0; + + virtual const TSharedRef< SWidget > ConstructRowWidget(FWwiseTreeItemPtr TreeItem, const STableRow& Row) = 0; + + virtual SHeaderRow::FColumn::FArguments ConstructHeaderRowColumn() = 0; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/SWwiseBrowser.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/SWwiseBrowser.cpp new file mode 100644 index 0000000..09ec935 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/SWwiseBrowser.cpp @@ -0,0 +1,1516 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*------------------------------------------------------------------------------------ + SWwiseBrowser.cpp +------------------------------------------------------------------------------------*/ + +#include "SWwiseBrowser.h" + +#include "IDesktopPlatform.h" +#include "ISettingsModule.h" +#include "Async/Async.h" +#include "Developer/DesktopPlatform/Public/DesktopPlatformModule.h" +#include "DragAndDrop/AssetDragDropOp.h" +#include "Editor/UnrealEd/Public/EditorDirectories.h" +#include "Framework/Application/SlateApplication.h" +#include "Framework/Commands/GenericCommands.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Misc/MessageDialog.h" +#include "Misc/ScopedSlowTask.h" +#include "Widgets/Images/SLayeredImage.h" +#include "Widgets/Input/SHyperlink.h" +#include "Widgets/Input/SComboButton.h" +#include "Widgets/Input/SSearchBox.h" +#include "Widgets/Input/SComboButton.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/Layout/SSpacer.h" + +#include "AkAudioBankGenerationHelpers.h" +#include "AkAudioModule.h" +#include "AkAudioStyle.h" +#include "AkSettings.h" +#include "AkSettingsPerUser.h" +#include "AkUnrealHelper.h" +#include "IAudiokineticTools.h" +#include "AssetManagement/AkAssetDatabase.h" +#include "Wwise/WwiseProjectDatabase.h" +#include "Wwise/Metadata/WwiseMetadataProjectInfo.h" +#include "WwiseBrowser/WwiseAssetDragDropOp.h" +#include "WwiseBrowser/WwiseBrowserHelpers.h" +#include "WwiseBrowser/WwiseBrowserViewCommands.h" + +#include "SWwiseBrowserTreeView.h" +#include "SoundBankStatusColumn.h" +#include "WwiseBrowserTreeColumn.h" +#include "WwiseStatusColumn.h" +#include "WwiseUEAssetStatusColumn.h" +#include "DataSource/WwiseBrowserDataSource.h" +#include "AkWaapiUMG/Components/AkBoolPropertyToControlCustomization.h" + +#define LOCTEXT_NAMESPACE "AkAudio" + +const FName SWwiseBrowser::WwiseBrowserTabName = "WwiseBrowser"; + +namespace SWwiseBrowser_Helper +{ + const FName DirectoryWatcherModuleName = "DirectoryWatcher"; +} + +namespace FilterMenuTitles +{ + FText GetMenuTitle(ESoundBankStatusFilter Filter) + { + switch (Filter) + { + case NewInWwise: + return LOCTEXT("NewInWwise", "New In Wwise"); + case DeletedInWwise: + return LOCTEXT("DeletedInWwise", "Deleted In Wwise"); + case RenamedInWwise: + return LOCTEXT("RenamedInWwise", "Renamed In Wwise"); + case NotInWwise: + return LOCTEXT("NotInWwiseOrSoundBank", "Not In Wwise or SoundBank"); + case MovedInWwise: + return LOCTEXT("MovedInWwise", "Moved In Wwise"); + case UpToDate: + return LOCTEXT("SoundBankUpToDate", "SoundBank Up To Date"); + } + return FText(); + } + + FText GetMenuTooltip(ESoundBankStatusFilter Filter) + { + switch (Filter) + { + case NewInWwise: + return LOCTEXT("NewInWwise_Tooltip", "Filter the New in Wwise Items."); + case DeletedInWwise: + return LOCTEXT("DeletedInWwise_Tooltip", "Filter the Deleted Items in Wwise."); + case RenamedInWwise: + return LOCTEXT("RenamedInWwise_Tooltip", "Filter the Renamed Items in Wwise."); + case NotInWwise: + return LOCTEXT("NotInWwiseOrSoundBank_Tooltip", "Filter the Items not in Wwise or in SoundBank."); + case MovedInWwise: + return LOCTEXT("MovedInWwise_Tooltip", "Filter the Items in different locations in Wwise and in SoundBank."); + case UpToDate: + return LOCTEXT("SoundBankUpToDate_Tooltip", "Filter the Up to date Wwise Items."); + } + return FText(); + } + + FText GetMenuTitle(EUAssetStatusFilter Filter) + { + switch (Filter) + { + case UAssetMissing: + return LOCTEXT("UAssetMissing", "UAsset Missing"); + case NotInSoundBankOrUnreal: + return LOCTEXT("NotInSoundBankOrUnreal", "Not in SoundBank or Unreal"); + case UAssetOrphaned: + return LOCTEXT("UAssetOrphaned", "UAsset Orphaned"); + case MultipleUAssets: + return LOCTEXT("UAssetMultiple", "Multiple UAssets"); + case RenamedInSoundBank: + return LOCTEXT("RenamedInSoundBank", "Renamed In SoundBank"); + case UAssetUpToDate: + return LOCTEXT("UAssetUpToDate", "UAsset Up to Date"); + } + return FText(); + } + + FText GetMenuTooltip(EUAssetStatusFilter Filter) + { + switch (Filter) + { + case UAssetMissing: + return LOCTEXT("UAssetMissing_Tooltip", "Filter the Missing UAssets."); + case NotInSoundBankOrUnreal: + return LOCTEXT("NotInSoundBankOrUnreal_Tooltip", "Filter the UAssets not in SoundBanks or Unreal."); + case UAssetOrphaned: + return LOCTEXT("UAssetOrphaned_Tooltip", "Filter UAssets with no counterpart in Wwise."); + case MultipleUAssets: + return LOCTEXT("UAssetMultiple_Tooltip", "Filter Wwise items with multiple UAssets."); + case RenamedInSoundBank: + return LOCTEXT("UAssetOrphaned_Tooltip", "Filter UAssets with different names in the SoundBank."); + case UAssetUpToDate: + return LOCTEXT("UAssetUpToDate_Tooltip", "Filter UAssets that are up to date with Wwise."); + } + return FText(); + } + + FText GetMenuTitle(EWwiseTypeFilter Filter) + { + switch (Filter) + { + case AcousticTexture: + return LOCTEXT("AcousticTexture", "Acoustic Texture"); + case Effects: + return LOCTEXT("Effects", "Effects"); + case Events: + return LOCTEXT("Events", "Events"); + case GameParameters: + return LOCTEXT("GameParameters", "GameParameters"); + case MasterMixerHierarchy: + return LOCTEXT("MasterMixerHierarchy", "Master Mixer Hierarchy (Bus)"); + case State: + return LOCTEXT("States", "States"); + case Switch: + return LOCTEXT("Switches", "Switches"); + case Trigger: + return LOCTEXT("Triggers", "Triggers"); + } + return FText(); + } + + FText GetMenuTooltip(EWwiseTypeFilter Filter) + { + switch (Filter) + { + case AcousticTexture: + return LOCTEXT("AcousticTexture_Tooltip", "Filter Wwise Acoustic Texture Types."); + case Effects: + return LOCTEXT("Effects_Tooltip", "Filter Wwise Effect Types."); + case Events: + return LOCTEXT("Events_Tooltip", "Filter Wwise Event Types."); + case GameParameters: + return LOCTEXT("GameParameters_Tooltip", "Filter Wwise Game Parameter Types."); + case MasterMixerHierarchy: + return LOCTEXT("MasterMixerHierarchy_Tooltip", "Filter Wwise Bus Types."); + case State: + return LOCTEXT("State_Tooltip", "Filter Wwise State Types."); + case Switch: + return LOCTEXT("Switch_Tooltip", "Filter Wwise Switch Types."); + case Trigger: + return LOCTEXT("Trigger_Tooltip", "Filter Wwise Trigger Types."); + } + return FText(); + } +} + +SWwiseBrowser::SWwiseBrowser(): CommandList(MakeShared()) +{ + AllowTreeViewDelegates = true; + DataSource = MakeUnique(); + Transport = MakeUnique(); + + DataSource->WwiseBrowserDataSourceRefreshed.BindLambda([this]() + { + AsyncTask(ENamedThreads::Type::GameThread, [this] + { + this->ConstructTree(); + }); + }); + + DataSource->WwiseSelectionChange.BindRaw(this, &SWwiseBrowser::UpdateWaapiSelection); + DataSource->WwiseExpansionChange.BindRaw(this, &SWwiseBrowser::ExpandItems); +} + +void SWwiseBrowser::CreateWwiseBrowserCommands() +{ + const FWwiseBrowserViewCommands& Commands = FWwiseBrowserViewCommands::Get(); + FUICommandList& ActionList = *CommandList; + // Action to start playing an event from the Wwise Browser. + ActionList.MapAction( + Commands.RequestPlayWwiseItem, + FExecuteAction::CreateSP(this, &SWwiseBrowser::HandlePlayWwiseItemCommandExecute), + FCanExecuteAction::CreateSP(this, &SWwiseBrowser::HandlePlayOrStopWwiseItemCanExecute)); + + // Action to stop all playing Wwise item (event). + ActionList.MapAction( + Commands.RequestStopAllWwiseItem, + FExecuteAction::CreateSP(this, &SWwiseBrowser::HandleStopAllWwiseItemCommandExecute)); + + // Action to explore an item (workunit) in the containing folder. + ActionList.MapAction( + Commands.RequestExploreWwiseItem, + FExecuteAction::CreateSP(this, &SWwiseBrowser::HandleExploreWwiseItemCommandExecute), + FCanExecuteAction::CreateSP(this, &SWwiseBrowser::HandleExploreWwiseItemCanExecute)); + + // Action find an item in the Wwise Project Explorer + ActionList.MapAction( + Commands.RequestFindInProjectExplorerWwisetem, + FExecuteAction::CreateSP(this, &SWwiseBrowser::HandleFindInProjectExplorerWwiseItemCommandExecute), + FCanExecuteAction::CreateSP(this, &SWwiseBrowser::HandleFindInProjectExplorerWwiseItemCanExecute)); + + // Action to find the Unreal asset in the Content Browser that maps to this item + ActionList.MapAction( + Commands.RequestFindInContentBrowser, + FExecuteAction::CreateSP(this, &SWwiseBrowser::HandleFindInContentBrowserCommandExecute), + FCanExecuteAction::CreateSP(this, &SWwiseBrowser::HandleFindInContentBrowserCanExecute)); + + // Action to refresh the Browser + ActionList.MapAction( + Commands.RequestRefreshWwiseBrowser, + FExecuteAction::CreateSP(this, &SWwiseBrowser::HandleRefreshWwiseBrowserCommandExecute)); + + // Action for importing the selected items from the Wwise Browser. + ActionList.MapAction( + Commands.RequestImportWwiseItem, + FExecuteAction::CreateSP(this, &SWwiseBrowser::HandleImportWwiseItemCommandExecute)); +} + +TSharedPtr SWwiseBrowser::MakeWwiseBrowserContextMenu() +{ + const FWwiseBrowserViewCommands& Commands = FWwiseBrowserViewCommands::Get(); + + // Build up the menu + FMenuBuilder MenuBuilder(true, CommandList); + { + MenuBuilder.BeginSection("WwiseBrowserTransport", LOCTEXT("MenuHeader", "WwiseBrowser")); + { + MenuBuilder.AddMenuEntry(Commands.RequestPlayWwiseItem); + MenuBuilder.AddMenuEntry(Commands.RequestStopAllWwiseItem); + } + MenuBuilder.EndSection(); + MenuBuilder.BeginSection("WwiseBrowserFindOptions", LOCTEXT("ExploreMenuHeader", "Explore")); + { + MenuBuilder.AddMenuEntry(Commands.RequestFindInProjectExplorerWwisetem); + MenuBuilder.AddMenuEntry(Commands.RequestFindInContentBrowser); + MenuBuilder.AddMenuEntry(Commands.RequestExploreWwiseItem); + } + MenuBuilder.EndSection(); + MenuBuilder.BeginSection("WwiseBrowserRefreshAll", LOCTEXT("RefreshHeader", "Refresh")); + { + MenuBuilder.AddMenuEntry(Commands.RequestRefreshWwiseBrowser); + } + MenuBuilder.EndSection(); + MenuBuilder.BeginSection("WwiseBrowserImport", LOCTEXT("ImportHeader", "Import")); + { + MenuBuilder.AddMenuEntry(Commands.RequestImportWwiseItem); + } + MenuBuilder.EndSection(); + + } + return MenuBuilder.MakeWidget(); +} + +void SWwiseBrowser::HandlePlayWwiseItemCommandExecute() +{ + TArray SelectedItems = TreeViewPtr->GetSelectedItems(); + int32 TransportID = -1; + for (auto WwiseTreeItem : SelectedItems) + { + bool bPlaying = Transport->IsPlaying(WwiseTreeItem->ItemId); + + if (WwiseTreeItem->bWaapiRefExists) + { + TransportID = Transport->FindOrAdd(WwiseTreeItem->ItemId); + } + if(TransportID >= 0 && !bPlaying) + { + Transport->TogglePlay(TransportID); + } + if(bPlaying) + { + Transport->Remove(WwiseTreeItem->ItemId); + } + } +} + +bool SWwiseBrowser::HandlePlayOrStopWwiseItemCanExecute() +{ + if (IsWaapiAvailable() != EWwiseConnectionStatus::Connected) + { + return false; + } + + TArray SelectedItems = TreeViewPtr->GetSelectedItems(); + if (SelectedItems.Num() == 0) + { + return false; + } + + for (auto SelectedItem: SelectedItems) + { + + if (!SelectedItem->bWaapiRefExists) + { + return false; + } + + if (SelectedItem->IsNotOfType({ EWwiseItemType::Event, EWwiseItemType::Sound, EWwiseItemType::BlendContainer, EWwiseItemType::SwitchContainer, EWwiseItemType::RandomSequenceContainer })) + { + return false; + } + } + + return true; +} + +void SWwiseBrowser::HandleStopWwiseItemCommandExecute() +{ + TArray SelectedItems = TreeViewPtr->GetSelectedItems(); + int32 TransportID = -1; + for (auto WwiseTreeItem : SelectedItems) + { + if (WwiseTreeItem->bWaapiRefExists) + { + TransportID = Transport->FindOrAdd(WwiseTreeItem->ItemId); + } + Transport->Stop(TransportID); + } +} + +void SWwiseBrowser::HandleStopAllWwiseItemCommandExecute() +{ + Transport->StopAndDestroyAll(); +} + +void SWwiseBrowser::HandleExploreWwiseItemCommandExecute() +{ + FWwiseTreeItemPtr SelectedItem = TreeViewPtr->GetSelectedItems()[0]; + FString WorkUnitPath = DataSource->GetItemWorkUnitPath(SelectedItem); + FPlatformProcess::ExploreFolder(*WorkUnitPath); +} + +bool SWwiseBrowser::HandleExploreWwiseItemCanExecute() +{ + return TreeViewPtr->GetSelectedItems().Num() == 1 && IsWaapiAvailable() == EWwiseConnectionStatus::Connected; +} + +void SWwiseBrowser::HandleFindInProjectExplorerWwiseItemCommandExecute() +{ + TArray SelectedItems = TreeViewPtr->GetSelectedItems(); + + DataSource->SelectInWwiseProjectExplorer(SelectedItems); +} + +bool SWwiseBrowser::HandleFindInProjectExplorerWwiseItemCanExecute() +{ + return TreeViewPtr->GetSelectedItems().Num() >= 1 && IsWaapiAvailable() == EWwiseConnectionStatus::Connected; +} + +bool SWwiseBrowser::HandleFindInContentBrowserCanExecute() +{ + TArray SelectedItems = TreeViewPtr->GetSelectedItems(); + + for (const auto& Asset : SelectedItems) + { + if (Asset->IsOfType({ EWwiseItemType::Event, + EWwiseItemType::Bus, + EWwiseItemType::AuxBus, + EWwiseItemType::AcousticTexture, + EWwiseItemType::State, + EWwiseItemType::Switch, + EWwiseItemType::GameParameter, + EWwiseItemType::Trigger, + EWwiseItemType::EffectShareSet + })) + { + return true; + } + } + + return false; +} + +void SWwiseBrowser::HandleFindInContentBrowserCommandExecute() +{ + TArray SelectedItems = TreeViewPtr->GetSelectedItems(); + TArray AssetsToSync; + + for (const auto& Asset : SelectedItems) + { + AssetsToSync.Append(Asset->Assets); + } + + if (AssetsToSync.Num()) + { + GEditor->SyncBrowserToObjects(AssetsToSync); + } +} + +void SWwiseBrowser::HandleRefreshWwiseBrowserCommandExecute() +{ + DataSource->ConstructTree(); +} + +SWwiseBrowser::~SWwiseBrowser() +{ + RootItems.Empty(); + SearchBoxFilter->OnChanged().RemoveAll(this); + DataSource->WwiseSelectionChange.Unbind(); + DataSource->WwiseExpansionChange.Unbind(); +} + +void SWwiseBrowser::Construct(const FArguments& InArgs) +{ + + FGenericCommands::Register(); + FWwiseBrowserViewCommands::Register(); + CreateWwiseBrowserCommands(); + + SearchBoxFilter = MakeShareable(new StringFilter(StringFilter::FItemToStringArray::CreateSP(this, &SWwiseBrowser::PopulateSearchStrings))); + SearchBoxFilter->OnChanged().AddSP(this, &SWwiseBrowser::OnTextFilterUpdated); + + HeaderRowWidget = + SNew(SHeaderRow) + .CanSelectGeneratedColumn(true); + +#if UE_5_0_OR_LATER + TSharedPtr FilterImage = SNew(SLayeredImage) + .Image(FAkAppStyle::Get().GetBrush("Icons.Filter")) + .ColorAndOpacity(FSlateColor::UseForeground()); +#if UE_5_1_OR_LATER + // Badge the filter icon if there are filters active + FilterImage->AddLayer(TAttribute(this, &SWwiseBrowser::GetFilterBadgeIcon)); +#endif +#endif + + SetupColumns(*HeaderRowWidget); + + ChildSlot + [ + SNew(SBorder) + .Padding(4) + .BorderImage(FAkAppStyle::Get().GetBrush("ToolPanel.GroupBorder")) + [ + SNew(SOverlay) + + // Browser + + SOverlay::Slot() + .VAlign(VAlign_Fill) + [ + SNew(SVerticalBox) + + // Search + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 1, 0, 3) + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .AutoWidth() + [ + InArgs._SearchContent.Widget + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Left) + [ + SNew(SComboButton) +#if UE_5_0_OR_LATER + .ComboButtonStyle(FAkAppStyle::Get(), "SimpleComboButton") +#else + .ComboButtonStyle(FEditorStyle::Get(), "GenericFilters.ComboButtonStyle") + .ForegroundColor(FLinearColor::White) +#endif + .ToolTipText(LOCTEXT("Browser_AddFilterToolTip", "Add filters to the Wwise Browser.")) + .OnGetMenuContent(this, &SWwiseBrowser::MakeAddFilterMenu) + .ButtonContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ +#if UE_5_0_OR_LATER + FilterImage.ToSharedRef() +#else + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "GenericFilters.TextStyle") + .Font(FEditorStyle::Get().GetFontStyle("FontAwesome.9")) + .Text(FText::FromString(FString(TEXT("\xf0b0"))) /*fa-filter*/) +#endif + ] + ] + ] + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SSearchBox) + .HintText(LOCTEXT("WwiseBrowserSearchTooltip", "Search Wwise Item")) + .OnTextChanged(this, &SWwiseBrowser::OnSearchBoxChanged) + .SelectAllTextWhenFocused(false) + .DelayChangeNotificationsWhileTyping(true) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .Padding(FMargin(5.0f,0.0f,30.0f,0.0f)) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("WwiseBrowserRefresh", "Refresh the Wwise Browser")) + .OnClicked(this, &SWwiseBrowser::OnRefreshClicked) + [ + SNew(SImage) + .Image(FAkAppStyle::Get().GetBrush("Icons.Refresh")) + ] + ] + ] + + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton) + .Text(LOCTEXT("AkBrowserGenerate", "Generate SoundBanks...")) + .OnClicked(this, &SWwiseBrowser::OnGenerateSoundBanksClicked) + ] + + ] + // Separator + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 0, 0, 1) + [ + SNew(SSeparator) + .Visibility((InArgs._ShowSeparator) ? EVisibility::Visible : EVisibility::Collapsed) + ] + + // Tree + + SVerticalBox::Slot() + .FillHeight(1.f) + [ + SAssignNew(TreeViewPtr, SWwiseBrowserTreeView, StaticCastSharedRef(AsShared())) + .TreeItemsSource(&RootItems).Visibility(this, &SWwiseBrowser::IsWarningNotVisible) + .OnGenerateRow(this, &SWwiseBrowser::GenerateRow) + .ItemHeight(18) + .SelectionMode(InArgs._SelectionMode) + .OnSelectionChanged(this, &SWwiseBrowser::TreeSelectionChanged) + .OnExpansionChanged(this, &SWwiseBrowser::TreeExpansionChanged) + .OnGetChildren(this, &SWwiseBrowser::GetChildrenForTree) + .OnContextMenuOpening(this, &SWwiseBrowser::MakeWwiseBrowserContextMenu) + .ClearSelectionOnClick(false) + .HeaderRow(HeaderRowWidget) + .OnMouseButtonDoubleClick(this, &SWwiseBrowser::OnTreeItemDoubleClicked) + ] + + //Footer + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .VAlign(VAlign_Bottom) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SBorder) + .BorderImage(FCoreStyle::Get().GetBrush("ToolPanel.GroupBorder")) + .Padding(1.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(STextBlock) + .Text(this, &SWwiseBrowser::GetConnectionStatusText) + .Visibility(this, &SWwiseBrowser::IsWarningNotVisible) + ] + + SHorizontalBox::Slot() + [ + SNew(STextBlock) + .Text(this, &SWwiseBrowser::GetSoundBanksLocationText) + ] + ] + ] + ] + ] +] + + // Empty Browser + + SOverlay::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .AutoHeight() + [ + SNew(STextBlock) + .Visibility(this, &SWwiseBrowser::IsWarningVisible) + .AutoWrapText(true) + .Justification(ETextJustify::Center) + .Text(this, &SWwiseBrowser::GetWarningText) + ] + + SVerticalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .AutoHeight() + [ + SNew(SButton) + .Text(LOCTEXT("AkBrowserOpenSettings", "Open Wwise Integration Settings")) + .Visibility(this, &SWwiseBrowser::IsWarningVisible) + .OnClicked(this, &SWwiseBrowser::OnOpenSettingsClicked) + ] + + SVerticalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .AutoHeight() + ] + ] + ]; + + InitialParse(); +} + +TSharedRef SWwiseBrowser::MakeAddFilterMenu() +{ + FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/false, nullptr); + + MenuBuilder.BeginSection("SoundBankStatusFilters", LOCTEXT("SoundBankStatusFilters", "SoundBank Status Filters")); + { + for (int i = 0; i < NumberOfSoundBankStatus; i++) + { + ESoundBankStatusFilter Status = (ESoundBankStatusFilter)i; + MenuBuilder.AddMenuEntry( + FilterMenuTitles::GetMenuTitle(Status), + FilterMenuTitles::GetMenuTooltip(Status), + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &SWwiseBrowser::SoundBankFilterExecute, Status), + FCanExecuteAction::CreateLambda([] { return true; }), + FIsActionChecked::CreateSP(this, &SWwiseBrowser::SoundBankFilterIsChecked, Status)), + NAME_None, + EUserInterfaceActionType::ToggleButton + ); + } + + + MenuBuilder.AddMenuEntry( + LOCTEXT("SoundBankNotUpToDate", "SoundBank Not Up to Date"), + LOCTEXT("SoundBankNotUpToDate_Tooltip", "Activate all filters of this section that is not \"SoundBank Up to Date\""), + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &SWwiseBrowser::SoundBankNotUpToDateExecute)), + NAME_None, + EUserInterfaceActionType::Button + ); + } + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("UAssetStatusFilters", LOCTEXT("UAssetStatusFilters", "UAsset Status Filters")); + { + for(int i = 0; i < NumberOfUAssetStatus; i++) + { + EUAssetStatusFilter Status = (EUAssetStatusFilter)i; + MenuBuilder.AddMenuEntry( + FilterMenuTitles::GetMenuTitle(Status), + FilterMenuTitles::GetMenuTooltip(Status), + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &SWwiseBrowser::UAssetFilterExecute, Status), + FCanExecuteAction::CreateLambda([] { return true; }), + FIsActionChecked::CreateSP(this, &SWwiseBrowser::UAssetFilterIsChecked, Status)), + NAME_None, + EUserInterfaceActionType::ToggleButton + ); + } + + MenuBuilder.AddMenuEntry( + LOCTEXT("UAssetNotUpToDate", "UAsset Not Up to Date"), + LOCTEXT("UAssetNotUpToDate_Tooltip", "Activate all filters of this section that is not \"UAsset Up to Date\""), + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &SWwiseBrowser::UAssetNotUpToDateExecute)), + NAME_None, + EUserInterfaceActionType::Button + ); + } + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("TypeFilters", LOCTEXT("TypeFilters", "Type Filters")); + { + for (int i = 0; i < NumberOfWwiseTypes; i++) + { + EWwiseTypeFilter Type = (EWwiseTypeFilter)i; + MenuBuilder.AddMenuEntry( + FilterMenuTitles::GetMenuTitle(Type), + FilterMenuTitles::GetMenuTooltip(Type), + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &SWwiseBrowser::WwiseTypeFilterExecute, Type), + FCanExecuteAction::CreateLambda([] { return true; }), + FIsActionChecked::CreateSP(this, &SWwiseBrowser::WwiseTypeFilterIsChecked, Type)), + NAME_None, + EUserInterfaceActionType::ToggleButton + ); + } + } + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("RemoveFilters", LOCTEXT("RemoveFilters", "Remove Filters")); + { + MenuBuilder.AddMenuEntry( + LOCTEXT("Remove_Title", "Remove All Filters"), + LOCTEXT("Remove_Tooltip", "Removes all selected filters."), + FSlateIcon(), + FUIAction(FExecuteAction::CreateSP(this, &SWwiseBrowser::RemoveFiltersExecute), + FCanExecuteAction::CreateLambda([] { return true; })), + NAME_None, + EUserInterfaceActionType::Button + ); + } + + return MenuBuilder.MakeWidget(); +} + +const FSlateBrush* SWwiseBrowser::GetFilterBadgeIcon() const +{ + if (!SoundBankStatusFilter.AreFiltersOff()) + { + return FAkAppStyle::Get().GetBrush("Icons.BadgeModified"); + } + if (!UAssetStatusFilter.AreFiltersOff()) + { + return FAkAppStyle::Get().GetBrush("Icons.BadgeModified"); + } + if (!WwiseTypeFilter.AreFiltersOff()) + { + return FAkAppStyle::Get().GetBrush("Icons.BadgeModified"); + } + return nullptr; +} + +void SWwiseBrowser::SoundBankFilterExecute(ESoundBankStatusFilter Filter) +{ + SoundBankStatusFilter.bFilters[Filter] = !SoundBankStatusFilter.bFilters[Filter]; + OnFilterUpdated(); +} + +bool SWwiseBrowser::SoundBankFilterIsChecked(ESoundBankStatusFilter Filter) +{ + return SoundBankStatusFilter.bFilters[Filter]; +} + +void SWwiseBrowser::UAssetFilterExecute(EUAssetStatusFilter Filter) +{ + UAssetStatusFilter.bFilters[Filter] = !UAssetStatusFilter.bFilters[Filter]; + OnFilterUpdated(); +} + +bool SWwiseBrowser::UAssetFilterIsChecked(EUAssetStatusFilter Filter) +{ + return UAssetStatusFilter.bFilters[Filter]; +} + +void SWwiseBrowser::WwiseTypeFilterExecute(EWwiseTypeFilter Filter) +{ + WwiseTypeFilter.bFilters[Filter] = !WwiseTypeFilter.bFilters[Filter]; + OnFilterUpdated(); +} + +bool SWwiseBrowser::WwiseTypeFilterIsChecked(EWwiseTypeFilter Filter) +{ + return WwiseTypeFilter.bFilters[Filter]; +} + +void SWwiseBrowser::RemoveFiltersExecute() +{ + bool bShouldUpdate = false; + + for (auto& bFilter : WwiseTypeFilter.bFilters) + { + if(bFilter) + { + bShouldUpdate = true; + bFilter = false; + } + } + + for(auto& bFilter : SoundBankStatusFilter.bFilters) + { + if (bFilter) + { + bShouldUpdate = true; + bFilter = false; + } + } + + for(auto& bFilter : UAssetStatusFilter.bFilters) + { + if (bFilter) + { + bShouldUpdate = true; + bFilter = false; + } + } + + if(bShouldUpdate) + { + OnFilterUpdated(); + } +} + +void SWwiseBrowser::SoundBankNotUpToDateExecute() +{ + bool bShouldUpdate = false; + for (int i = 0; i < ESoundBankStatusFilter::UpToDate; i++) + { + if (!SoundBankStatusFilter.bFilters[i]) + { + SoundBankStatusFilter.bFilters[i] = true; + bShouldUpdate = true; + } + } + + if (SoundBankStatusFilter.bFilters[ESoundBankStatusFilter::UpToDate]) + { + SoundBankStatusFilter.bFilters[ESoundBankStatusFilter::UpToDate] = false; + bShouldUpdate = true; + } + + if (bShouldUpdate) + { + OnFilterUpdated(); + } +} + +void SWwiseBrowser::UAssetNotUpToDateExecute() +{ + bool bShouldUpdate = false; + for (int i = 0; i < EUAssetStatusFilter::UAssetUpToDate; i++) + { + if (!UAssetStatusFilter.bFilters[i]) + { + UAssetStatusFilter.bFilters[i] = true; + bShouldUpdate = true; + } + } + + if (UAssetStatusFilter.bFilters[EUAssetStatusFilter::UAssetUpToDate]) + { + UAssetStatusFilter.bFilters[EUAssetStatusFilter::UAssetUpToDate] = false; + bShouldUpdate = true; + } + + if (bShouldUpdate) + { + OnFilterUpdated(); + } +} + + +void SWwiseBrowser::ForceRefresh() +{ + if(!GetProjectName().IsEmpty()) + { + DataSource->ConstructTree(); + } +} + +void SWwiseBrowser::InitialParse() +{ + if(!GetProjectName().IsEmpty()) + { + DataSource->ConstructTree(); + TreeViewPtr->RequestTreeRefresh(); + ExpandFirstLevel(); + } +} + +FText SWwiseBrowser::GetProjectName() const +{ + return DataSource->GetProjectName(); +} + +FText SWwiseBrowser::GetConnectedWwiseProjectName() const +{ + return DataSource->GetConnectedWwiseProjectName(); +} + +EWwiseConnectionStatus SWwiseBrowser::IsWaapiAvailable() const +{ + return DataSource->GetWaapiConnectionStatus(); +} + +EVisibility SWwiseBrowser::IsWarningVisible() const +{ + // Also need to check if WAAPI available + return GetProjectName().IsEmpty() ? EVisibility::Visible : EVisibility::Hidden; +} + +EVisibility SWwiseBrowser::IsWarningNotVisible() const +{ + return GetProjectName().IsEmpty() ? EVisibility::Hidden : EVisibility::Visible; +} + +EVisibility SWwiseBrowser::IsItemPlaying(FGuid ItemId) const +{ + return Transport->IsPlaying(ItemId) ? EVisibility::Visible : EVisibility::Hidden; +} + +FText SWwiseBrowser::GetWarningText() const +{ + if (auto AkSettings = GetMutableDefault()) + { + if (AkSettings->GeneratedSoundBanksFolder.Path.IsEmpty()) + { + const FText WarningText = LOCTEXT("BrowserSoundBanksFolderEmpty", "Generated SoundBanks Folder in Wwise Integration settings is empty.\nThis folder should match the \"Root Output Path\" in the Wwise Project's SoundBanks settings."); + return WarningText; + } + } + + FString soundBankDirectory = AkUnrealHelper::GetSoundBankDirectory(); + const FText WarningText = FText::FormatOrdered(LOCTEXT("BrowserMissingSoundBanks", "SoundBank metadata was not found at path specified by \"Generated SoundBanks Folder\" setting: {0}.\nThis folder should match the \"Root Output Path\" in the Wwise Project's SoundBanks settings.\nEnsure the folders match, and that SoundBanks and JSON metadata are generated.\nPress the \"Refresh\" button to re-parse the generated metadata."), FText::FromString(soundBankDirectory)); + return WarningText; +} + +FText SWwiseBrowser::GetConnectionStatusText() const +{ + switch(IsWaapiAvailable()) + { + case Connected: + return FText::Format(LOCTEXT("WwiseConnected", "Connected to Wwise Project: {0}"), GetProjectName()); + case SettingDisabled: + return LOCTEXT("WwiseSettingDisabled", "Not Connected to Wwise. Only objects on disk shown. Enable \"Auto Connect to Waapi\" in the Wwise User Settings to see the Wwise project in the Browser."); + case WrongProjectOpened: + return LOCTEXT("WwiseWrongProject", "Not Connected to Wwise. Only objects on disk shown. The wrong Wwise project is opened."); + case WwiseNotOpen: + default: + return LOCTEXT("WwiseNotConnected", "Not Connected to Wwise. Only objects on disk shown. Open Wwise to see the Wwise project in the Browser."); + } +} + +FText SWwiseBrowser::GetSoundBanksLocationText() const +{ + return FText::Format(LOCTEXT("RootOutputPath", "Root Output Path: {0}"), FText::FromString(AkUnrealHelper::GetSoundBankDirectory())); +} + +FReply SWwiseBrowser::OnOpenSettingsClicked() +{ + FModuleManager::LoadModuleChecked("Settings").ShowViewer(FName("Project"), FName("Wwise"), FName("Integration")); + return FReply::Handled(); +} + +FReply SWwiseBrowser::OnRefreshClicked() +{ + if (FModuleManager::Get().IsModuleLoaded("AudiokineticTools")) + { + UE_LOG(LogAudiokineticTools, Verbose, TEXT("SWwiseBrowser::OnRefreshClicked: Reloading project data.")); + FModuleManager::Get().GetModuleChecked(FName("AudiokineticTools")).RefreshWwiseProject(); + } + ForceRefresh(); + return FReply::Handled(); +} + +FReply SWwiseBrowser::OnGenerateSoundBanksClicked() +{ + UE_LOG(LogAudiokineticTools, Verbose, TEXT("SWwiseBrowser::OnGenerateSoundBanksClicked: Opening Generate SoundBanks Window.")); + AkAudioBankGenerationHelper::CreateGenerateSoundDataWindow(); + return FReply::Handled(); +} + +void SWwiseBrowser::OnTreeItemDoubleClicked(FWwiseTreeItemPtr TreeItem) +{ + if (TreeItem->IsBrowserType()) + { + HandleFindInContentBrowserCommandExecute(); + } + + else if (TreeItem->IsFolder() && TreeItem->GetChildren().Num() > 0) + { + ExpandItem(TreeItem, !TreeViewPtr->IsItemExpanded(TreeItem)); + } +} + +void SWwiseBrowser::ConstructTree() +{ + + UE_LOG(LogAudiokineticTools, Verbose, TEXT("SWwiseBrowser::ConstructTree: Rebuilding Wwise Browser Tree")); + + RootItems.Empty(EWwiseItemType::LastWwiseBrowserType - EWwiseItemType::Event + 1); + + for (int i = EWwiseItemType::Event; i <= EWwiseItemType::LastWwiseBrowserType + 1; ++i) + { + FWwiseTreeItemPtr NewRoot = DataSource->GetTreeRootForType(static_cast(i)); + + RootItems.Add(NewRoot); + } + + RestoreTreeExpansion(RootItems); + TreeViewPtr->RequestTreeRefresh(); +} + +void SWwiseBrowser::ExpandFirstLevel() +{ + // Expand root items and first-level work units. + for(int32 i = 0; i < RootItems.Num(); i++) + { + ExpandItem(RootItems[i], true); + } +} + +void SWwiseBrowser::ExpandParents(FWwiseTreeItemPtr Item) +{ + if(Item->Parent.IsValid()) + { + ExpandParents(Item->Parent.Pin()); + ExpandItem(Item->Parent.Pin(), true); + } +} + +TSharedRef SWwiseBrowser::GenerateRow( FWwiseTreeItemPtr TreeItem, const TSharedRef& OwnerTable ) +{ + check(TreeItem.IsValid()); + + TSharedRef NewRow = SNew(SWwiseBrowserTreeRow, TreeViewPtr.ToSharedRef(), SharedThis(this)). + Item(TreeItem); + + TreeItem->TreeRow = NewRow; + + return NewRow; +} + +void SWwiseBrowser::GetChildrenForTree(FWwiseTreeItemPtr TreeItem, TArray< FWwiseTreeItemPtr >& OutChildren) +{ + if (TreeItem) + { + if(TreeItem->ChildCountInWwise) + { + if (!LastExpandedPaths.Contains(TreeItem->FolderPath)) + { + DataSource->ClearEmptyChildren(TreeItem); + // We add a placeholder item if the children exist, but are not loaded (e.g. for WAAPI). This should never be visible + if (!TreeItem->GetChildren().Num()) + { + FWwiseTreeItemPtr EmptyTreeItem = MakeShared(FString::Format(TEXT("Expansion placeholder for {0} "), { TreeItem->FolderPath }), "", nullptr, EWwiseItemType::None, FGuid()); + EmptyTreeItem->Parent = TreeItem; + TreeItem->AddChild(EmptyTreeItem); + } + } + else + { + ExpandItem(TreeItem, true); + } + } + + OutChildren = TreeItem->GetChildren(); + } +} + +FReply SWwiseBrowser::OnDragDetected(const FGeometry& Geometry, const FPointerEvent& MouseEvent) +{ + return DoDragDetected(MouseEvent, TreeViewPtr->GetSelectedItems()); +} + +FReply SWwiseBrowser::DoDragDetected(const FPointerEvent& MouseEvent, const TArray& SelectedItems) +{ + if (!MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton)) + { + return FReply::Unhandled(); + } + + if (SelectedItems.Num() == 0) + { + return FReply::Unhandled(); + } + + UE_LOG(LogAudiokineticTools, Verbose, TEXT("SWwiseBrowser::OnDragDetected: User drag operation started.")); + + auto AkSettings = GetMutableDefault(); + const FString DefaultPath = AkSettings->DefaultAssetCreationPath; + + TArray DragAssets; + TSet SeenGuids; + bool bAllItemsCanBeCreated = true; + for (auto& WwiseTreeItem : SelectedItems) + { + if (!WwiseTreeItem->ItemId.IsValid() && !WwiseTreeItem->IsFolder()) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Cannot drag selected Wwise asset: %s does not have a valid ID"), *(WwiseTreeItem->FolderPath)); + continue; + } + + if(!WwiseBrowserHelpers::CanCreateAsset(WwiseTreeItem)) + { + bAllItemsCanBeCreated = false; + } + + WwiseBrowserHelpers::FindOrCreateAssetsRecursive(WwiseTreeItem,DragAssets,SeenGuids, WwiseBrowserHelpers::EAssetCreationMode::Transient); + } + + if (DragAssets.Num() == 0 && bAllItemsCanBeCreated) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to find or create Wwise asset '%s'%s in Browser operation"), + *(SelectedItems[0]->FolderPath), SelectedItems.Num() > 1 ? TEXT(" and others"): TEXT("")); + return FReply::Unhandled(); + } + + return FReply::Handled().BeginDragDrop(FWwiseAssetDragDropOp::New(DragAssets)); +} + +void SWwiseBrowser::ImportWwiseAssets(const TArray& SelectedItems, const FString& PackagePath) +{ + UE_LOG(LogAudiokineticTools, Verbose, TEXT("SWwiseBrowser::ImportWwiseAssets: Creating %d assets in %s."), SelectedItems.Num(), *PackagePath); + TArray AssetsToImport; + TSet SeenGuids; + for(auto WwiseTreeItem: SelectedItems) + { + WwiseBrowserHelpers::FindOrCreateAssetsRecursive(WwiseTreeItem, AssetsToImport, SeenGuids, + WwiseBrowserHelpers::EAssetCreationMode::InPackage, PackagePath); + + } + + WwiseBrowserHelpers::SaveSelectedAssets(AssetsToImport, PackagePath, + WwiseBrowserHelpers::EAssetCreationMode::InPackage, WwiseBrowserHelpers::EAssetDuplicationMode::DoDuplication); +} + +FReply SWwiseBrowser::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) +{ + const FKey KeyPressed = InKeyEvent.GetKey(); + + if (KeyPressed == EKeys::SpaceBar) + { + // Play the wwise item. + if (HandlePlayOrStopWwiseItemCanExecute()) + { + HandlePlayWwiseItemCommandExecute(); + return FReply::Handled(); + } + } + + else if (KeyPressed == EKeys::F5) + { // Populates the Wwise Browser. + HandleRefreshWwiseBrowserCommandExecute(); + return FReply::Handled(); + } + + else if ((KeyPressed == EKeys::One) && InKeyEvent.IsControlDown() && InKeyEvent.IsShiftDown()) + { + // Finds the specified object in the Project Explorer (Sync Group 1). + if (HandleFindInProjectExplorerWwiseItemCanExecute()) + { + HandleFindInProjectExplorerWwiseItemCommandExecute(); + return FReply::Handled(); + } + } + + return FReply::Unhandled(); +} + +void SWwiseBrowser::PopulateSearchStrings(const FString& FolderName, OUT TArray< FString >& OutSearchStrings) const +{ + OutSearchStrings.Add(FolderName); +} + +void SWwiseBrowser::OnSearchBoxChanged(const FText& InSearchText) +{ + SearchBoxFilter->SetRawFilterText(InSearchText); +} + +void SWwiseBrowser::SetItemVisibility(FWwiseTreeItemPtr Item, bool IsVisible) +{ + if (Item.IsValid()) + { + if (IsVisible) + { + // Propagate visibility to parents. + SetItemVisibility(Item->Parent.Pin(), IsVisible); + } + Item->IsVisible = IsVisible; + if (Item->TreeRow.IsValid()) + { + TSharedRef wid = Item->TreeRow.Pin()->AsWidget(); + wid->SetVisibility(IsVisible ? EVisibility::Visible : EVisibility::Collapsed); + } + } +} + +void SWwiseBrowser::SaveCurrentTreeExpansion() +{ + TSet ExpandedItemSet; + TreeViewPtr->GetExpandedItems(ExpandedItemSet); + + LastExpandedPaths.Empty(); + + for (const auto& Item : ExpandedItemSet) + { + if (Item.IsValid()) + { + // Keep track of the last paths that we broadcasted for expansion reasons when filtering + LastExpandedPaths.Add(Item->FolderPath); + } + } +} + +void SWwiseBrowser::RestoreTreeExpansion(const TArray< FWwiseTreeItemPtr >& Items) +{ + + for(int i = 0; i < Items.Num(); i++) + { + if (!Items[i]) + { + continue; + } + + if(!DataSource->IsKeptInBrowser(Items[i])) + { + continue; + } + // If filtering, everything should be expanded? + + if (LastExpandedPaths.Contains(Items[i]->FolderPath) || IsFiltering()) + { + ExpandItem(Items[i], true); + } + + RestoreTreeExpansion(Items[i]->GetChildren()); + } +} + +void SWwiseBrowser::TreeSelectionChanged( FWwiseTreeItemPtr TreeItem, ESelectInfo::Type /*SelectInfo*/ ) +{ + if (AllowTreeViewDelegates) + { + const TArray SelectedItems = TreeViewPtr->GetSelectedItems(); + + LastSelectedPaths.Empty(); + for (int32 ItemIdx = 0; ItemIdx < SelectedItems.Num(); ++ItemIdx) + { + const FWwiseTreeItemPtr Item = SelectedItems[ItemIdx]; + if (Item.IsValid()) + { + LastSelectedPaths.Add(Item->FolderPath); + } + } + + const UAkSettingsPerUser* AkSettingsPerUser = GetDefault(); + if (AkSettingsPerUser && AkSettingsPerUser->AutoSyncSelection) + { + DataSource->HandleFindWwiseItemInProjectExplorerCommandExecute(SelectedItems); + } + } +} + +void SWwiseBrowser::TreeExpansionChanged( FWwiseTreeItemPtr TreeItem, bool bIsExpanded ) +{ + auto Items = TreeViewPtr->GetSelectedItems(); + if (bIsExpanded) + { + // If we have a filter applied, it is assumed that everything is loaded + if (!IsFiltering()) + { + TArray OutChildren; + } + } + + // If the item is not expanded we don't need to request the server to get any information(the children are hidden). + else + { + LastExpandedPaths.Remove(TreeItem->FolderPath); + return; + } + + LastExpandedPaths.Add(TreeItem->FolderPath); + + TreeViewPtr->SetSelectedItems(Items); +} + +void SWwiseBrowser::ExpandItem(FWwiseTreeItemPtr TreeItem, bool bShouldExpand) +{ + TreeViewPtr->SetItemExpansion(TreeItem, bShouldExpand); + TreeItem->IsExpanded = bShouldExpand; +} + +void SWwiseBrowser::ExpandItem(FWwiseTreeItemPtr WaapiTreeItem) +{ + if(auto TreeItem = GetTreeItemFromWaapiItem(WaapiTreeItem)) + { + LastExpandedPaths.Add(TreeItem->FolderPath); + ExpandItem(TreeItem, true); + } +} + +bool SWwiseBrowser::IsItemExpanded(FWwiseTreeItemPtr TreeItem) +{ + return TreeViewPtr->IsItemExpanded(TreeItem); +} + +void SWwiseBrowser::GetTreeItemsFromWaapi(const TArray>& WaapiTreeItems, TArray>& TreeItems) +{ + for(auto& WaapiTreeItem : WaapiTreeItems) + { + if(auto TreeItem = GetTreeItemFromWaapiItem(WaapiTreeItem)) + { + TreeItems.Add(TreeItem); + } + } +} + +FWwiseTreeItemPtr SWwiseBrowser::GetTreeItemFromWaapiItem(FWwiseTreeItemPtr WaapiTreeItem) +{ + for (auto& RootItem : RootItems) + { + if (auto ResultTreeItem = RootItem->FindItemRecursive(WaapiTreeItem)) + { + return ResultTreeItem; + } + if (auto ResultTreeItem = RootItem->FindItemRecursive(WaapiTreeItem->FolderPath)) + { + return ResultTreeItem; + } + } + return FWwiseTreeItemPtr(); +} + +void SWwiseBrowser::ExpandItems(const TArray& Items) +{ + for(auto& Item : Items) + { + ExpandItem(Item); + } +} + +void SWwiseBrowser::UpdateWaapiSelection(const TArray>& WaapiTreeItems) +{ + TArray> TreeItems; + GetTreeItemsFromWaapi(WaapiTreeItems, TreeItems); + TreeViewPtr->RequestTreeRefresh(); + if (TreeItems.Num() > 0) + { + + TreeViewPtr->ClearSelection(); + TreeViewPtr->SetSelectedItems(TreeItems); + TreeViewPtr->RequestScrollIntoView(TreeItems[0]); + } + AllowTreeViewDelegates = true; +} + +void SWwiseBrowser::HandleImportWwiseItemCommandExecute() const +{ + // If not prompting individual files, prompt the user to select a target directory. + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); + if (DesktopPlatform) + { + FString LastWwiseImportPath = FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_IMPORT); + FString FolderName; + const FString Title = NSLOCTEXT("UnrealEd", "ChooseADirectory", "Choose A Directory").ToString(); + const bool bFolderSelected = DesktopPlatform->OpenDirectoryDialog( + FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), + Title, + LastWwiseImportPath, + FolderName + ); + + if (bFolderSelected) + { + if (!FPaths::IsUnderDirectory(FolderName, FPaths::ProjectContentDir())) + { + + const FText FailReason = FText::FormatOrdered(LOCTEXT("CannotImportWwiseItem", "Cannot import into {0}. Folder must be in content directory."), FText::FromString(FolderName)); + FMessageDialog::Open(EAppMsgType::Ok, FailReason); + UE_LOG(LogAudiokineticTools, Error, TEXT("%s"), *FailReason.ToString()); + return; + } + FPaths::MakePathRelativeTo(FolderName, *FPaths::ProjectContentDir()); + FString PackagePath = TEXT("/Game"); + if (!FolderName.IsEmpty()) + { + PackagePath = PackagePath / FolderName; + } + FEditorDirectories::Get().SetLastDirectory(ELastDirectory::GENERIC_IMPORT, PackagePath); + ImportWwiseAssets(TreeViewPtr->GetSelectedItems(), PackagePath); + } + } +} + +void SWwiseBrowser::SetupColumns(SHeaderRow& HeaderRow) +{ + + Columns.Empty(); + HeaderRow.ClearColumns(); + + auto WwiseObjectsColumn = MakeShared(*this); + auto WwiseStatusColumn = MakeShared(*this); + auto WwiseUEAssetStatusColumn = MakeShared(); + auto SoundBankStatusColumn = MakeShared(); + + auto WwiseObjectArgs = WwiseObjectsColumn->ConstructHeaderRowColumn(); + auto WwiseStatusArgs = WwiseStatusColumn->ConstructHeaderRowColumn(); + auto WwiseUEAssetStatusArgs = WwiseUEAssetStatusColumn->ConstructHeaderRowColumn(); + auto SoundBankStatusArgs = SoundBankStatusColumn->ConstructHeaderRowColumn(); + + Columns.Add(WwiseObjectArgs._ColumnId, WwiseObjectsColumn); + Columns.Add(WwiseStatusArgs._ColumnId, WwiseStatusColumn); + Columns.Add(WwiseUEAssetStatusArgs._ColumnId, WwiseUEAssetStatusColumn); + Columns.Add(SoundBankStatusArgs._ColumnId, SoundBankStatusColumn); + + HeaderRowWidget->AddColumn(WwiseObjectArgs); + HeaderRowWidget->AddColumn(WwiseStatusArgs); + HeaderRowWidget->AddColumn(WwiseUEAssetStatusArgs); + HeaderRowWidget->AddColumn(SoundBankStatusArgs); +} + +void SWwiseBrowser::OnTextFilterUpdated() +{ + if (GetFilterText().IsEmpty()) + { + bIsFilterApplied = false; + } + + DataSource->ApplyTextFilter(SearchBoxFilter); +} + +void SWwiseBrowser::OnFilterUpdated() +{ + if (RootItems.Num() == 0) + { + return; + } + + ApplyFilter(); + TreeViewPtr->RequestTreeRefresh(); +} + +void SWwiseBrowser::ApplyFilter() +{ + AllowTreeViewDelegates = false; + + if (!bIsFilterApplied) + { + SaveCurrentTreeExpansion(); + } + + DataSource->ApplyFilter(SoundBankStatusFilter, UAssetStatusFilter, WwiseTypeFilter); + + RootItems.Empty(); + for (int i = EWwiseItemType::Event; i <= EWwiseItemType::LastWwiseBrowserType + 1; ++i) + { + FWwiseTreeItemPtr NewRoot = DataSource->GetTreeRootForType(static_cast(i)); + + RootItems.Add(NewRoot); + } + + bIsFilterApplied = true; + AllowTreeViewDelegates = true; +} + +bool SWwiseBrowser::IsFiltering() +{ + return !GetFilterText().IsEmpty(); +} + +FText SWwiseBrowser::GetFilterText() +{ + return SearchBoxFilter.IsValid() ? SearchBoxFilter->GetRawFilterText(): FText(); +} + +FString SWwiseBrowser::GetFilterString() +{ + return GetFilterText().ToString(); +} + +TAttribute SWwiseBrowser::GetFilterHighlightText() const +{ + return SearchBoxFilter.IsValid() ? SearchBoxFilter->GetRawFilterText(): FText(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/SWwiseBrowser.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/SWwiseBrowser.h new file mode 100644 index 0000000..e2d5304 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/SWwiseBrowser.h @@ -0,0 +1,265 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "WaapiPlaybackTransport.h" +#include "DataSource/WwiseBrowserDataSource.h" +#include "DataSource/WaapiDataSource.h" +#include "Misc/TextFilter.h" +#include "Widgets/SCompoundWidget.h" +#include "Styling/SlateBrush.h" +#include "Framework/Views/ITypedTableView.h" +#include "WwiseBrowser/IWwiseBrowserColumn.h" + +class FUICommandList; +class STableViewBase; +class ITableRow; +class SHeaderRow; +class FWwiseBrowserDataSource; +class SWwiseBrowserTreeView; +using StringFilter = TTextFilter; + +class WwiseBrowserBuilderVisitor; +class FWwiseBrowserDataLoader; + +class SWwiseBrowser : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS( SWwiseBrowser ) + : _FocusSearchBoxWhenOpened(false) + , _ShowTreeTitle(true) + , _ShowSearchBar(true) + , _ShowSeparator(true) + , _AllowContextMenu(true) + , _SelectionMode( ESelectionMode::Multi ) + {} + + /** Content displayed to the left of the search bar */ + SLATE_NAMED_SLOT( FArguments, SearchContent ) + + /** If true, the search box will be focus the frame after construction */ + SLATE_ARGUMENT( bool, FocusSearchBoxWhenOpened ) + + /** If true, The tree title will be displayed */ + SLATE_ARGUMENT( bool, ShowTreeTitle ) + + /** If true, The tree search bar will be displayed */ + SLATE_ARGUMENT( bool, ShowSearchBar ) + + /** If true, The tree search bar separator be displayed */ + SLATE_ARGUMENT( bool, ShowSeparator ) + + /** If false, the context menu will be suppressed */ + SLATE_ARGUMENT( bool, AllowContextMenu ) + + /** The selection mode for the tree view */ + SLATE_ARGUMENT( ESelectionMode::Type, SelectionMode ) + SLATE_END_ARGS( ) + + AUDIOKINETICTOOLS_API void Construct(const FArguments& InArgs); + SWwiseBrowser(void); + ~SWwiseBrowser(); + + AUDIOKINETICTOOLS_API static const FName WwiseBrowserTabName; + + AUDIOKINETICTOOLS_API TSharedRef MakeAddFilterMenu(); + + void SoundBankFilterExecute(ESoundBankStatusFilter Filter); + bool SoundBankFilterIsChecked(ESoundBankStatusFilter Filter); + void UAssetFilterExecute(EUAssetStatusFilter Filter); + bool UAssetFilterIsChecked(EUAssetStatusFilter Filter); + void WwiseTypeFilterExecute(EWwiseTypeFilter Filter); + bool WwiseTypeFilterIsChecked(EWwiseTypeFilter Filter); + void SoundBankNotUpToDateExecute(); + void RemoveFiltersExecute(); + void UAssetNotUpToDateExecute(); + + AUDIOKINETICTOOLS_API void ForceRefresh(); + + AUDIOKINETICTOOLS_API void InitialParse(); + + static FReply DoDragDetected(const FPointerEvent& MouseEvent, const TArray& SelectedItems); + + TAttribute GetFilterHighlightText() const; + + static void ImportWwiseAssets(const TArray& SelectedItems, const FString& PackagePath); + + /** Get the columns to be displayed in this outliner */ + const TMap>& GetColumns() const + { + return Columns; + } + + virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; + + EVisibility IsItemPlaying(FGuid ItemId) const; + + void ExpandItem(FWwiseTreeItemPtr TreeItem, bool bShouldExpand); + + void ExpandItem(FWwiseTreeItemPtr TreeItem); + + bool IsItemExpanded(FWwiseTreeItemPtr TreeItem); + + void UpdateWaapiSelection(const TArray>& WaapiTreeItems); + + EWwiseConnectionStatus IsWaapiAvailable() const; + + /** Prevent the selection changed callback from running when filtering */ + bool AllowTreeViewDelegates; + +private: + + /** The new tree view widget */ + TSharedPtr TreeViewPtr; + + /** The header row of the browser tree view */ + TSharedPtr< SHeaderRow > HeaderRowWidget; + + /** Filter for the search box */ + TSharedPtr SearchBoxFilter; + + /** True when the tree is current being filtered */ + bool bIsFilterApplied; + + FSoundBankStatusFilter SoundBankStatusFilter; + + FUAssetStatusFilter UAssetStatusFilter; + + FWwiseTypeFilter WwiseTypeFilter; + + const FSlateBrush* GetFilterBadgeIcon() const; + + void OnTextFilterUpdated(); + void OnFilterUpdated(); + void ApplyFilter(); + bool IsFiltering(); + FText GetFilterText(); + FString GetFilterString(); + + /** Root items */ + TArray< FWwiseTreeItemPtr > RootItems; + + /** Remember the selected items. Useful when filtering to preserve selection status. */ + TSet< FString > LastSelectedPaths; + + /** Remember the expanded items. Useful when filtering to preserve expansion status. */ + TSet< FString > LastExpandedPaths; + + //Open the Wwise Integration settings + FReply OnOpenSettingsClicked(); + + /** Ran when the Refresh button is clicked. Parses the Wwise project (using the WwiseWwuParser) and populates the window. */ + FReply OnRefreshClicked(); + + FReply OnGenerateSoundBanksClicked(); + + void OnTreeItemDoubleClicked(FWwiseTreeItemPtr TreeItem); + + /** Populates the browser window only (does not parse the Wwise project) */ + void ConstructTree(); + + /** Generate a row in the tree view */ + TSharedRef GenerateRow( FWwiseTreeItemPtr TreeItem, const TSharedRef& OwnerTable ); + + /** Get the children of a specific tree element */ + void GetChildrenForTree( FWwiseTreeItemPtr TreeItem, TArray< FWwiseTreeItemPtr >& OutChildren ); + + /** Commands handled by this widget */ + TSharedRef CommandList; + + /** Handle Drag & Drop */ + virtual FReply OnDragDetected(const FGeometry& Geometry, const FPointerEvent& MouseEvent) override; + + void ExpandFirstLevel(); + void ExpandParents(FWwiseTreeItemPtr Item); + + FText GetProjectName() const; + FText GetConnectedWwiseProjectName() const; + + EVisibility IsWarningVisible() const; + EVisibility IsWarningNotVisible() const; + FText GetWarningText() const; + + FText GetConnectionStatusText() const; + FText GetSoundBanksLocationText() const; + + /** Used by the search filter */ + void PopulateSearchStrings( const FString& FolderName, OUT TArray< FString >& OutSearchStrings ) const; + void OnSearchBoxChanged( const FText& InSearchText ); + void SetItemVisibility(FWwiseTreeItemPtr Item, bool IsVisible); + void SaveCurrentTreeExpansion(); + void RestoreTreeExpansion(const TArray< FWwiseTreeItemPtr >& Items); + + /** Handler for tree view selection changes */ + void TreeSelectionChanged( FWwiseTreeItemPtr TreeItem, ESelectInfo::Type SelectInfo ); + + /** Handler for tree view expansion changes */ + void TreeExpansionChanged( FWwiseTreeItemPtr TreeItem, bool bIsExpanded ); + + /** Builds the command list for the context menu on Wwise Browser items. */ + void CreateWwiseBrowserCommands(); + + /** Callback for creating a context menu for the Wwise items list. */ + TSharedPtr MakeWwiseBrowserContextMenu(); + + /** Callback to execute the play command from the context menu. Only available with a WAAPI connection. */ + void HandlePlayWwiseItemCommandExecute(); + bool HandlePlayOrStopWwiseItemCanExecute(); + + /** Callback to execute the stop command from the context menu. Only available with a WAAPI connection. */ + void HandleStopWwiseItemCommandExecute(); + + /** Callback to execute the stop all command from the context menu. Only available with a WAAPI connection. */ + void HandleStopAllWwiseItemCommandExecute(); + + /** Finds the selected item's containing workunit in Explorer. Only available with a WAAPI connection */ + void HandleExploreWwiseItemCommandExecute(); + bool HandleExploreWwiseItemCanExecute(); + + /** Finds the selected item in the Wwise Project Explorer. Only available with a WAAPI connection */ + void HandleFindInProjectExplorerWwiseItemCommandExecute(); + bool HandleFindInProjectExplorerWwiseItemCanExecute(); + + /** Finds the selected item in the Content browser. */ + void HandleFindInContentBrowserCommandExecute(); + bool HandleFindInContentBrowserCanExecute(); + + void HandleRefreshWwiseBrowserCommandExecute(); + + /** Callback to import a Wwise item into the project's Contents*/ + void HandleImportWwiseItemCommandExecute() const; + + /** Set up the columns required for this outliner */ + void SetupColumns(SHeaderRow& HeaderRow); + + void GetTreeItemsFromWaapi(const TArray>& WaapiTreeItems, TArray>& TreeItems); + + FWwiseTreeItemPtr GetTreeItemFromWaapiItem(FWwiseTreeItemPtr WaapiTreeItem); + + void ExpandItems(const TArray< FWwiseTreeItemPtr >& Items); + +private: + /** Map of columns that are shown on the Browser. */ + TMap> Columns; + + /** DataSource responsible for loading all of the Browser's data through WAAPI, ProjectDB, Unreal assets */ + TUniquePtr DataSource; + + /** Transport for handling playback of Wwise Items*/ + TUniquePtr Transport; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/SWwiseBrowserTreeView.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/SWwiseBrowserTreeView.cpp new file mode 100644 index 0000000..291ad1b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/SWwiseBrowserTreeView.cpp @@ -0,0 +1,204 @@ +/******************************************************************************* +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. +*******************************************************************************/ + + +#include "SWwiseBrowserTreeView.h" +#include "AkAudioStyle.h" +#include "AkSettings.h" +#include "IAudiokineticTools.h" +#include "SWwiseBrowser.h" +#include "SlateOptMacros.h" +#include "WwiseBrowser/WwiseAssetDragDropOp.h" + +#if UE_5_0_OR_LATER +#include "AkUEFeatures.h" +#endif + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION + +FReply HandleOnDragDetected(const FGeometry& Geometry, const FPointerEvent& MouseEvent, TWeakPtr Table) +{ + if (!MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton)) + { + return FReply::Unhandled(); + } + + auto TablePtr = Table.Pin(); + + TArray SelectedItems; + if (TablePtr.IsValid() && MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton)) + { + SelectedItems = TablePtr->GetSelectedItems(); + + if (SelectedItems.Num() == 0) + { + return FReply::Unhandled(); + } + } + + UE_LOG(LogAudiokineticTools, Verbose, TEXT("SWwiseBrowser::OnDragDetected: User drag operation started.")); + + auto AkSettings = GetMutableDefault(); + const FString DefaultPath = AkSettings->DefaultAssetCreationPath; + + TArray DragAssets; + TSet SeenGuids; + + bool bAllItemsCanBeCreated = true; + for (auto& WwiseTreeItem : SelectedItems) + { + if (!WwiseTreeItem->ItemId.IsValid() && !WwiseTreeItem->IsFolder()) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Cannot drag selected Wwise asset: %s does not have a valid ID"), *(WwiseTreeItem->FolderPath)); + continue; + } + + if (!WwiseBrowserHelpers::CanCreateAsset(WwiseTreeItem)) + { + bAllItemsCanBeCreated = false; + } + + WwiseBrowserHelpers::FindOrCreateAssetsRecursive(WwiseTreeItem, DragAssets,SeenGuids, WwiseBrowserHelpers::EAssetCreationMode::Transient); + } + + if (DragAssets.Num() == 0 && bAllItemsCanBeCreated) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to find or create Wwise asset '%s'%s in Browser operation"), + *(SelectedItems[0]->FolderPath), SelectedItems.Num() > 1 ? TEXT(" and others"): TEXT("")); + return FReply::Unhandled(); + } + + return FReply::Handled().BeginDragDrop(FWwiseAssetDragDropOp::New(DragAssets)); +} + +void SWwiseBrowserTreeView::Construct(const FArguments& InArgs, TSharedRef Owner) +{ + WwiseBrowserWeak = Owner; + STreeView::Construct(InArgs); +} + +void SWwiseBrowserTreeView::SetSelectedItems(TArray> Items) +{ + SelectedItems.Reset(); + for(auto Item : Items) + { + SelectedItems.Add(Item); + } + + if (OnSelectionChanged.IsBound() && SelectedItems.Num() > 0) + { + NullableItemType SelectedItem = (*typename TItemSet::TIterator(SelectedItems)); + + OnSelectionChanged.ExecuteIfBound(SelectedItem, ESelectInfo::Direct); + } +} + +TSharedRef SWwiseBrowserTreeRow::GenerateWidgetForColumn(const FName& InColumnId) +{ + auto ItemPtr = Item.Pin(); + if (!ItemPtr.IsValid()) + { + return SNullWidget::NullWidget; + } + + auto WwiseBrowser = WwiseBrowserWeak.Pin(); + check(WwiseBrowser.IsValid()); + + // Create the widget for this item + TSharedRef NewItemWidget = SNullWidget::NullWidget; + + auto Column = WwiseBrowser->GetColumns().FindRef(InColumnId); + if (Column.IsValid()) + { + NewItemWidget = Column->ConstructRowWidget(ItemPtr.ToSharedRef(), *this); + } + + if( InColumnId == WwiseBrowserHelpers::WwiseBrowserColumnId) + { + // The first column gets the tree expansion arrow for this row + return SNew(SBox) + .MinDesiredHeight(20) + [ + SNew( SHorizontalBox ) + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(6, 0, 0, 0) + [ + SNew(SImage) +#if UE_5_0_OR_LATER + .Image(FAkAppStyle::Get().GetBrush("ClassIcon.AmbientSound")) +#else + .Image(FEditorStyle::Get().GetBrush("Sequencer.Tracks.Audio")) +#endif + .Visibility_Raw(WwiseBrowser.Get(), &SWwiseBrowser::IsItemPlaying, ItemPtr->ItemId) + ] + + +SHorizontalBox::Slot() + .AutoWidth() + .Padding(6, 0, 0, 0) + [ + SNew( SExpanderArrow, SharedThis(this) ).IndentAmount(12) + ] + + +SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + NewItemWidget + ] + ]; + } + else + { + // Other columns just get widget content -- no expansion arrow needed + return NewItemWidget; + } +} + +void SWwiseBrowserTreeRow::Construct( + const FArguments& InArgs, + const TSharedRef& BrowserTreeView, + TSharedRef WwiseBrowser) +{ + Item = InArgs._Item->AsShared(); + WwiseBrowserWeak = WwiseBrowser; + + auto Args = FSuperRowType::FArguments(); + + Args.OnDragDetected_Static(HandleOnDragDetected, TWeakPtr(BrowserTreeView)); + + auto ItemPtr = Item.Pin(); + auto WwiseBrowserPtr = WwiseBrowserWeak.Pin(); + + SetVisibility(ItemPtr->IsVisible ? EVisibility::Visible : EVisibility::Collapsed); + + if (!WwiseBrowserPtr.IsValid()) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Wwise Browser is invalid.")); + return; + } + + if (!ItemPtr.IsValid()) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Wwise Item is invalid.")); + return; + } + + SMultiColumnTableRow::Construct(Args, BrowserTreeView); +} + +END_SLATE_FUNCTION_BUILD_OPTIMIZATION diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/SWwiseBrowserTreeView.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/SWwiseBrowserTreeView.h new file mode 100644 index 0000000..915d577 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/SWwiseBrowserTreeView.h @@ -0,0 +1,67 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "WaapiPicker/WwiseTreeItem.h" +#include "Widgets/Views/STreeView.h" +#include "WwiseBrowserForwards.h" + +/** + * + */ +class SWwiseBrowserTreeView : public STreeView< FWwiseTreeItemPtr > +{ +public: + + /** Constructs this widget with InArgs */ + void Construct(const FArguments& InArgs, TSharedRef Owner); + + /** Weak reference to the browser widget that owns this list */ + TWeakPtr WwiseBrowserWeak; + + void SetSelectedItems(TArray> Items); +}; + +/** Widget that represents a row in the browser's tree control. Generates widgets for each column on demand. */ +class SWwiseBrowserTreeRow + : public SMultiColumnTableRow< FWwiseTreeItemPtr > +{ +public: + virtual TSharedRef GenerateWidgetForColumn(const FName& InColumnId) override; + + /** The item associated with this row of data */ + TWeakPtr Item; + + SLATE_BEGIN_ARGS(SWwiseBrowserTreeRow) {} + + /** The list item for this row */ + SLATE_ARGUMENT( FWwiseTreeItemPtr, Item ) + + SLATE_END_ARGS() + + /** Construct function for this widget */ + void Construct(const FArguments& InArgs, const + TSharedRef& BrowserTreeView, TSharedRef + WwiseBrowser); + + /** Weak reference to the browser widget that owns our list */ +private: + TWeakPtr WwiseBrowserWeak; + +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/SoundBankStatusColumn.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/SoundBankStatusColumn.cpp new file mode 100644 index 0000000..b37d162 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/SoundBankStatusColumn.cpp @@ -0,0 +1,84 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "SoundBankStatusColumn.h" + +#include "WwiseBrowserHelpers.h" +#include "Widgets/SWidget.h" +#include "Widgets/Text/STextBlock.h" + +#define LOCTEXT_NAMESPACE "AkAudio" + +FText FSoundBankStatusColumn::GetDisplayedName(FWwiseTreeItemPtr TreeItem) +{ + if(!TreeItem->ShouldDisplayInfo()) + { + return LOCTEXT("WwiseBrowserEmpty", ""); + } + if(TreeItem->IsUAssetUpToDate()) + { + return LOCTEXT("UAssetStatusUpToDate", "UAsset Up to Date"); + } + else if(TreeItem->IsRenamedInSoundBank()) + { + return LOCTEXT("UAssetStatusRename", "Renamed in SoundBank"); + } + else if(TreeItem->IsUAssetOrphaned()) + { + return LOCTEXT("UAssetStatusOrphaned", "UAsset Orphaned"); + } + else if(TreeItem->HasMultipleUAssets()) + { + return LOCTEXT("UAssetStatusMultiple", "Multiple UAssets"); + } + else if(TreeItem->IsUAssetMissing()) + { + return LOCTEXT("UAssetStatusMissing", "UAsset Missing"); + } + return LOCTEXT("UAssetStatusNotInSoundBank", "Not in SoundBank or Unreal"); +} + +FName FSoundBankStatusColumn::GetColumnId() +{ + return FName("SoundBankStatus"); +} + +const TSharedRef FSoundBankStatusColumn::ConstructRowWidget(FWwiseTreeItemPtr TreeItem, + const STableRow& Row) +{ + return SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth(). + VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(GetDisplayedName(TreeItem)) + .ColorAndOpacity(WwiseBrowserHelpers::GetTextColor(TreeItem->IsUAssetUpToDate())) + ]; +} + +SHeaderRow::FColumn::FArguments FSoundBankStatusColumn::ConstructHeaderRowColumn() +{ + auto UEStatusColumnHeader = SHeaderRow::Column(GetColumnId()); + TAttribute UEStatusLabel; + UEStatusLabel.Set(FText::FromString("SoundBanks vs UAssets")); + UEStatusColumnHeader.DefaultLabel(UEStatusLabel); + UEStatusColumnHeader.DefaultTooltip(LOCTEXT("SoundBankColumn_Tooltip", "The status of the UAssets compared to the information in the Generated SoundBanks.")); + return UEStatusColumnHeader; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/SoundBankStatusColumn.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/SoundBankStatusColumn.h new file mode 100644 index 0000000..088699d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/SoundBankStatusColumn.h @@ -0,0 +1,34 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "WwiseBrowserForwards.h" +#include "IWwiseBrowserColumn.h" + +class FSoundBankStatusColumn : public IWwiseBrowserColumn +{ + FText GetDisplayedName(FWwiseTreeItemPtr TreeItem); +public: + virtual FName GetColumnId() override; + + virtual const TSharedRef ConstructRowWidget(FWwiseTreeItemPtr TreeItem, + const STableRow& Row) override; + + virtual SHeaderRow::FColumn::FArguments ConstructHeaderRowColumn() override; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseAssetDragDropOp.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseAssetDragDropOp.cpp new file mode 100644 index 0000000..9aa2953 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseAssetDragDropOp.cpp @@ -0,0 +1,219 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "WwiseBrowser/WwiseAssetDragDropOp.h" + +#include "AkAudioType.h" +#include "AkSettings.h" +#include "AkUEFeatures.h" +#include "AkUnrealHelper.h" +#include "AssetTools/Public/AssetToolsModule.h" +#include "AssetManagement/AkAssetDatabase.h" +#include "ContentBrowserModule.h" +#include "FileHelpers.h" +#include "Misc/Paths.h" +#include "UnrealEd/Public/ObjectTools.h" + +#define LOCTEXT_NAMESPACE "AkAudio" + +TSharedRef FWwiseAssetDragDropOp::New(TArray InAssetData, UActorFactory* ActorFactory) +{ + return New(MoveTemp(InAssetData), TArray(), ActorFactory); +} + +TSharedRef FWwiseAssetDragDropOp::New(TArray InAssetData, TArray InAssetPaths, UActorFactory* ActorFactory) +{ + TArray NewAssets; + for (WwiseBrowserHelpers::WwiseBrowserAssetPayload AssetResult : InAssetData) + { + if (AssetResult.ExistingAssets.Num() > 0) + { + NewAssets.Add(AssetResult.ExistingAssets[0]); + } + else + { + NewAssets.Add(AssetResult.CreatedAsset); + } + } + TSharedRef ParentOperation = FAssetDragDropOp::New(NewAssets, InAssetPaths, ActorFactory); + + FWwiseAssetDragDropOp* RawPointer = new FWwiseAssetDragDropOp(); + TSharedRef Operation = MakeShareable(RawPointer); + // ugly hack since FAssetDragDropOp data is private + static_cast(RawPointer)->operator=(ParentOperation.Get()); + + FAssetViewDragAndDropExtender::FOnDropDelegate DropDelegate = FAssetViewDragAndDropExtender::FOnDropDelegate::CreateRaw(RawPointer, &FWwiseAssetDragDropOp::OnAssetViewDrop); + FAssetViewDragAndDropExtender::FOnDragOverDelegate DragOverDelegate = FAssetViewDragAndDropExtender::FOnDragOverDelegate::CreateRaw(RawPointer, &FWwiseAssetDragDropOp::OnAssetViewDragOver); + Operation->Extender = new FAssetViewDragAndDropExtender(DropDelegate, DragOverDelegate); + + FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked("ContentBrowser"); + TArray& AssetViewDragAndDropExtenders = ContentBrowserModule.GetAssetViewDragAndDropExtenders(); + AssetViewDragAndDropExtenders.Add(*(Operation->Extender)); + + Operation->WwiseAssetsToDrop = InAssetData; + + return Operation; +} + +FWwiseAssetDragDropOp::~FWwiseAssetDragDropOp() +{ + FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked("ContentBrowser"); + TArray& AssetViewDragAndDropExtenders = ContentBrowserModule.GetAssetViewDragAndDropExtenders(); + for (int32 i = 0; i < AssetViewDragAndDropExtenders.Num(); i++) + { + if (AssetViewDragAndDropExtenders[i].OnDropDelegate.IsBoundToObject(this)) + { + AssetViewDragAndDropExtenders.RemoveAt(i); + } + } + + delete Extender; +} + +bool FWwiseAssetDragDropOp::OnAssetViewDrop(const FAssetViewDragAndDropExtender::FPayload& Payload) +{ + if (!Payload.DragDropOp->IsOfType()) + { + SetCanDrop(false); + return false; + } + + if (CanDrop) + { + bDroppedOnContentBrowser =true; + if (Payload.PackagePaths.Num() <= 0) + { + return false; + } + + const auto AssetDragDrop = static_cast(Payload.DragDropOp.Get()); + auto PackagePath = Payload.PackagePaths[0].ToString(); + + // UE5 adds "/All" to all game content folder paths, but CreateAsset doesn't like it + PackagePath.RemoveFromStart(TEXT("/All")); + + // UE5 adds "/All/Plugins" to all plugin content folder paths, but CreateAsset doesn't like it + PackagePath.RemoveFromStart(TEXT("/Plugins")); + AssetViewDropTargetPackagePath = PackagePath; + } + + return CanDrop; +} + +bool FWwiseAssetDragDropOp::OnAssetViewDragOver(const FAssetViewDragAndDropExtender::FPayload& Payload) +{ + if (!Payload.DragDropOp->IsOfType()) + { + SetCanDrop(false); + return false; + } + SetCanDrop(true); + return true; +} + + +void FWwiseAssetDragDropOp::SaveAssets() +{ + FString DefaultPath = FPaths::ProjectContentDir(); + auto AkSettings = GetDefault(); + if (LIKELY(AkSettings)) + { + DefaultPath = AkSettings->DefaultAssetCreationPath; + } + + FString TargetRootPackagePath = DefaultPath; + WwiseBrowserHelpers::EAssetDuplicationMode DuplicationMode = WwiseBrowserHelpers::EAssetDuplicationMode::NoDuplication; + if (bDroppedOnContentBrowser) + { + TargetRootPackagePath = AssetViewDropTargetPackagePath; + DuplicationMode = WwiseBrowserHelpers::EAssetDuplicationMode::DoDuplication; + } + + WwiseBrowserHelpers::SaveSelectedAssets(WwiseAssetsToDrop, TargetRootPackagePath, WwiseBrowserHelpers::EAssetCreationMode::Transient, DuplicationMode); +} + +void FWwiseAssetDragDropOp::DeleteAssets() +{ + TArray AssetsToDelete; + for (WwiseBrowserHelpers::WwiseBrowserAssetPayload& AssetResult : WwiseAssetsToDrop) + { + if (AssetResult.CreatedAsset.IsValid()) + { + AssetsToDelete.Add(AssetResult.CreatedAsset); + } + } + + if (AssetsToDelete.Num() > 0) + { + ObjectTools::DeleteAssets(AssetsToDelete, false); + } +} + +void FWwiseAssetDragDropOp::OnDrop(bool bDropWasHandled, const FPointerEvent& MouseEvent) +{ + if (!bDropWasHandled) + { + DeleteAssets(); + return; + } + + SaveAssets(); +} + +void FWwiseAssetDragDropOp::SetCanDrop(const bool InCanDrop) +{ + CanDrop = InCanDrop; + SetTooltipText(); + + if (InCanDrop) + { + MouseCursor = EMouseCursor::GrabHandClosed; + SetToolTip(GetTooltipText(), FAkAppStyle::Get().GetBrush(TEXT("Graph.ConnectorFeedback.Ok"))); + } + else + { + MouseCursor = EMouseCursor::SlashedCircle; + SetToolTip(GetTooltipText(), FAkAppStyle::Get().GetBrush(TEXT("Graph.ConnectorFeedback.Error"))); + } +} + +void FWwiseAssetDragDropOp::SetTooltipText() +{ + if (CanDrop) + { + auto& assets = GetAssets(); + FString Text = assets.Num() ? assets[0].AssetName.ToString() : TEXT(""); + + if (assets.Num() > 1) + { + Text += TEXT(" "); + Text += FString::Printf(TEXT("and %d other(s)"), assets.Num() - 1); + } + CurrentHoverText = FText::FromString(Text); + } + else + { + CurrentHoverText = LOCTEXT("OnDragAkEventsOverFolder_CannotDrop", "Wwise assets can only be dropped in the right folder"); + } +} + +FText FWwiseAssetDragDropOp::GetTooltipText() const +{ + return CurrentHoverText; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseBrowserForwards.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseBrowserForwards.h new file mode 100644 index 0000000..64ec4ce --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseBrowserForwards.h @@ -0,0 +1,27 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" + +class SWwiseBrowser; +struct FWwiseTreeItem; + +typedef TSharedPtr FWwiseTreeItemPtr; +typedef TSharedRef FWwiseTreeItemRef; + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseBrowserHelpers.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseBrowserHelpers.cpp new file mode 100644 index 0000000..2379067 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseBrowserHelpers.cpp @@ -0,0 +1,452 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "WwiseBrowser/WwiseBrowserHelpers.h" + +#include "AkUEFeatures.h" +#include "AkAcousticTexture.h" +#include "AkUEFeatures.h" +#include "AkAudioEvent.h" +#include "AkAuxBus.h" +#include "AkRtpc.h" +#include "AkStateValue.h" +#include "AkSwitchValue.h" +#include "AkTrigger.h" +#include "AkEffectShareSet.h" +#include "AkInitBank.h" +#include "AkUnrealHelper.h" +#include "AudiokineticTools/Public/AkAssetFactories.h" +#include "AudiokineticTools/Private/AssetManagement/AkAssetDatabase.h" +#include "IAudiokineticTools.h" + +#include "AssetRegistry/AssetRegistryModule.h" +#include "AssetTools/Public/AssetToolsModule.h" +#include "Async/Async.h" +#include "Engine/ObjectReferencer.h" +#include "FileHelpers.h" +#include "ObjectTools.h" +#include "PackageTools.h" + +#define LOCTEXT_NAMESPACE "AkAudio" + + +EWwiseItemType::Type WwiseBrowserHelpers::GetTypeFromClass(UClass* Class) +{ + if (Class == UAkAudioEvent::StaticClass()) + { + return EWwiseItemType::Event; + } + if (Class == UAkAcousticTexture::StaticClass()) + { + return EWwiseItemType::AcousticTexture; + } + if (Class == UAkRtpc::StaticClass()) + { + return EWwiseItemType::GameParameter; + } + if (Class == UAkStateValue::StaticClass()) + { + return EWwiseItemType::State; + } + if (Class == UAkSwitchValue::StaticClass()) + { + return EWwiseItemType::Switch; + } + if (Class == UAkTrigger::StaticClass()) + { + return EWwiseItemType::Trigger; + } + if (Class == UAkEffectShareSet::StaticClass()) + { + return EWwiseItemType::EffectShareSet; + } + if (Class == UAkAuxBus::StaticClass()) + { + return EWwiseItemType::AuxBus; + } + if(Class == UAkInitBank::StaticClass()) + { + return EWwiseItemType::InitBank; + } + return EWwiseItemType::None; +} + +void WwiseBrowserHelpers::FindOrCreateAssetsRecursive(const FWwiseTreeItemPtr& WwiseTreeItem, + TArray& InOutBrowserAssetPayloads, TSet& InOutKnownGuids, + const EAssetCreationMode AssetCreationMode, const FString& PackagePath, const FString& CurrentRelativePath) +{ + FString Name; + UClass* WwiseAssetClass = nullptr; + FString CurrentRelativePackagePath = PackagePath / CurrentRelativePath; + + if (WwiseTreeItem->ItemId.IsValid() && InOutKnownGuids.Contains(WwiseTreeItem->ItemId)) + { + return; + } + + if (WwiseTreeItem->ItemType == EWwiseItemType::Event) + { + Name = WwiseTreeItem->DisplayName; + WwiseAssetClass = UAkAudioEvent::StaticClass(); + } + if (WwiseTreeItem->ItemType == EWwiseItemType::AcousticTexture) + { + Name = WwiseTreeItem->DisplayName; + WwiseAssetClass = UAkAcousticTexture::StaticClass(); + } + else if (WwiseTreeItem->ItemType == EWwiseItemType::AuxBus) + { + Name = WwiseTreeItem->DisplayName; + WwiseAssetClass = UAkAuxBus::StaticClass(); + for (FWwiseTreeItemPtr Child : WwiseTreeItem->GetChildren()) + { + FString NewRelativePath = CurrentRelativePath / WwiseTreeItem->DisplayName; + FindOrCreateAssetsRecursive(Child, InOutBrowserAssetPayloads, InOutKnownGuids, AssetCreationMode, PackagePath, NewRelativePath); + } + } + else if (WwiseTreeItem->ItemType == EWwiseItemType::GameParameter) + { + Name = WwiseTreeItem->DisplayName; + WwiseAssetClass = UAkRtpc::StaticClass(); + } + else if (WwiseTreeItem->ItemType == EWwiseItemType::State) + { + Name = WwiseTreeItem->GetSwitchAssetName(); + WwiseAssetClass = UAkStateValue::StaticClass(); + } + else if (WwiseTreeItem->ItemType == EWwiseItemType::Switch) + { + Name = WwiseTreeItem->GetSwitchAssetName(); + WwiseAssetClass = UAkSwitchValue::StaticClass(); + } + else if (WwiseTreeItem->ItemType == EWwiseItemType::Trigger) + { + Name = WwiseTreeItem->DisplayName; + WwiseAssetClass = UAkTrigger::StaticClass(); + } + else if (WwiseTreeItem->ItemType == EWwiseItemType::EffectShareSet) + { + Name = WwiseTreeItem->DisplayName; + WwiseAssetClass = UAkEffectShareSet::StaticClass(); + } + else if (WwiseTreeItem->IsFolder()) + { + //Add object to prevent Drag and Drop in the world. + WwiseAssetClass = UAkDragDropBlocker::StaticClass(); + Name = WwiseTreeItem->DisplayName; + for (FWwiseTreeItemPtr Child : WwiseTreeItem->GetChildren()) + { + FString NewRelativePath = CurrentRelativePath / Name; + FindOrCreateAssetsRecursive(Child, InOutBrowserAssetPayloads, InOutKnownGuids, AssetCreationMode, PackagePath, NewRelativePath); + } + } + + if (WwiseAssetClass) + { + TArray SearchResults; + WwiseBrowserAssetPayload Payload; + AkAssetDatabase::Get().FindAssetsByGuidAndClass(WwiseTreeItem->ItemId, WwiseAssetClass, Payload.ExistingAssets); + if (Payload.ExistingAssets.Num() == 0) + { + Payload.CreatedAsset = CreateBrowserAsset(Name, WwiseTreeItem, WwiseAssetClass, AssetCreationMode, PackagePath / CurrentRelativePath); + } + + Payload.Name = UPackageTools::SanitizePackageName(Name); + Payload.RelativePackagePath = UPackageTools::SanitizePackageName(CurrentRelativePath); + Payload.WwiseObjectGuid = WwiseTreeItem->ItemId; + InOutBrowserAssetPayloads.Add(Payload); + InOutKnownGuids.Add(WwiseTreeItem->ItemId); + } +} + + +FAssetData WwiseBrowserHelpers::CreateBrowserAsset(const FString& AssetName, const FWwiseTreeItemPtr& WwiseTreeItem, UClass* AssetClass, const EAssetCreationMode AssetCreationMode, const FString& PackagePath) +{ + //We shouldn't call NewObject outside of the game thread + if (!IsInGameThread()) + { + AsyncTask(ENamedThreads::GameThread, [AssetName, WwiseTreeItem, AssetClass, AssetCreationMode, PackagePath] + { + CreateBrowserAssetTask(AssetName, WwiseTreeItem, AssetClass, AssetCreationMode, PackagePath); + }); + + // Spoof the FAssetData for the asset that will be created asynchronously + const FString SanitizedName = UPackageTools::SanitizePackageName(AssetName); + //Folder asset are always created in the transient package + if (AssetCreationMode == EAssetCreationMode::Transient || WwiseTreeItem->IsFolder()) + { + FString AssetPackage = UPackageTools::SanitizePackageName(GetTransientPackage()->GetPathName() / AssetClass->GetName()); +#if UE_5_1_OR_LATER + return FAssetData(FName(AssetPackage), FName(GetTransientPackage()->GetPathName()), FName(*SanitizedName), AssetClass->GetClassPathName()); +#else + return FAssetData(FName(AssetPackage), FName(GetTransientPackage()->GetPathName()), FName(*SanitizedName), AssetClass->GetFName()); +#endif + } + else if (AssetCreationMode == EAssetCreationMode::InPackage) + { + FString AssetPackage = UPackageTools::SanitizePackageName(PackagePath / AssetName); +#if UE_5_1_OR_LATER + return FAssetData(FName(AssetPackage), FName(PackagePath), FName(*SanitizedName), AssetClass->GetClassPathName()); +#else + return FAssetData(FName(AssetPackage), FName(PackagePath), FName(*SanitizedName), AssetClass->GetFName()); +#endif + } + } + return CreateBrowserAssetTask(AssetName, WwiseTreeItem, AssetClass, AssetCreationMode, PackagePath); +} + + +FAssetData WwiseBrowserHelpers::CreateBrowserAssetTask(const FString& AssetName, const FWwiseTreeItemPtr& WwiseTreeItem, UClass* AssetClass, const EAssetCreationMode AssetCreationMode, const FString& PackagePath) +{ + if (!ensureMsgf(IsInGameThread(), TEXT("WwiseBrowserHelpers::CreateBrowserAsset : Not in the Game thread. Assets will not be created."))) + { + return {}; + } + //Folder asset are always created in the transient package + if (AssetCreationMode == EAssetCreationMode::Transient || WwiseTreeItem->IsFolder()) + { + return CreateTransientAsset(AssetName, WwiseTreeItem, AssetClass); + } + else //if (AssetCreationMode == EAssetCreationMode::InPackage) + { + return CreateAssetInPackage(AssetName, WwiseTreeItem, PackagePath, AssetClass); + } +} + +FAssetData WwiseBrowserHelpers::CreateTransientAsset(const FString& AssetName, const FWwiseTreeItemPtr& WwiseTreeItem, UClass* AssetClass) +{ + //Create a sub-package in transient to avoid asset name collisions for different wwise object type + const FString PackageName = UPackageTools::SanitizePackageName(GetTransientPackage()->GetPathName() / AssetClass->GetName()); + UPackage* Pkg = CreatePackage(*PackageName); + + UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("Wwise Browser: Creating new temporary %s asset for Drag operation in '%s' in '%s'."), *AssetClass->GetName(), *AssetName, *PackageName); + return CreateAsset(AssetName, WwiseTreeItem, AssetClass, Pkg); +} + +FAssetData WwiseBrowserHelpers::CreateAssetInPackage(const FString& AssetName, const FWwiseTreeItemPtr& WwiseTreeItem, const FString& PackagePath, UClass* AssetClass) +{ + const FString PackageName = UPackageTools::SanitizePackageName(PackagePath / AssetName); + UPackage* Pkg = CreatePackage(*PackageName); + + UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("Wwise Browser: Creating new %s asset '%s' in '%s'."), *AssetClass->GetName(), *AssetName, *PackageName); + return CreateAsset(AssetName, WwiseTreeItem, AssetClass, Pkg); +} + +FAssetData WwiseBrowserHelpers::CreateAsset(const FString& AssetName, const FWwiseTreeItemPtr& WwiseTreeItem, UClass* AssetClass, UPackage* Pkg) +{ + // Verify the asset class + if (!ensureMsgf(AssetClass, TEXT("The new asset '%s' wasn't created due to a problem finding the appropriate class for the new asset."))) + { + return nullptr; + } + + const auto Factory = GetAssetFactory(WwiseTreeItem); + const FString SanitizedName = UPackageTools::SanitizePackageName(AssetName); + UObject* NewObj = nullptr; + EObjectFlags Flags = RF_Public | RF_Transactional | RF_Standalone; + if (Factory) + { + NewObj = Factory->FactoryCreateNew(AssetClass, Pkg, FName(*SanitizedName), Flags, nullptr, GWarn); + } + else if (AssetClass) + { + NewObj = NewObject(Pkg, AssetClass, FName(*SanitizedName), Flags); + } + return FAssetData(NewObj); +} + +void WwiseBrowserHelpers::SaveSelectedAssets(TArray Assets, const FString& RootPackagePath, const EAssetCreationMode AssetCreationMode, const EAssetDuplicationMode AssetDuplicationMode) +{ + //It is probably dangerous to manipulate UObjects (rename/delete/duplicate) outside of the game thread + if (!IsInGameThread()) + { + AsyncTask(ENamedThreads::GameThread, [Assets, RootPackagePath, AssetCreationMode, AssetDuplicationMode] + { + SaveSelectedAssetsTask(Assets, RootPackagePath, AssetCreationMode, AssetDuplicationMode); + }); + + return; + } + SaveSelectedAssetsTask(Assets, RootPackagePath, AssetCreationMode, AssetDuplicationMode); +} + +void WwiseBrowserHelpers::SaveSelectedAssetsTask(TArray Assets, const FString& RootPackagePath, const EAssetCreationMode AssetCreationMode, const EAssetDuplicationMode AssetDuplicationMode) +{ + if (!ensureMsgf(IsInGameThread(), TEXT("WwiseBrowserHelpers::SaveSelectedAssets : Not in the Game thread. Assets will not be saved or moved."))) + { + return; + } + auto AssetToolsModule = &FModuleManager::LoadModuleChecked("AssetTools"); + TArray AssetsToRename; + TArray AssetsToDelete; + TArray RenamedTransientAssets; + TArray PackagesToSave; + + for (WwiseBrowserAssetPayload& AssetResult : Assets) + { + const bool bPreExisting = AssetResult.ExistingAssets.Num() > 0; + FString Path = AssetResult.RelativePackagePath; + FString PackagePath = RootPackagePath; + if (!Path.IsEmpty()) + { + PackagePath = UPackageTools::SanitizePackageName(PackagePath / Path); + } + FString NewAssetPath = PackagePath / AssetResult.Name; + + if (bPreExisting) + { + if (AssetDuplicationMode == EAssetDuplicationMode::NoDuplication) + { + continue; + } + //Make sure none of the existing assets would be overwritten by the new asset we want to duplicate + bool bMatch = false; + for (auto ExistingAsset : AssetResult.ExistingAssets) + { + if (ExistingAsset.PackageName.ToString() == NewAssetPath) + { + bMatch = true; + break; + } + } + if (bMatch) + { + //Asset already exists, nothing to do + continue; + } + + UE_LOG(LogAudiokineticTools, Log, TEXT("Wwise Browser: Duplicating existing asset '%s' into '%s'."), *AssetResult.ExistingAssets[0].GetFullName(), *PackagePath); + auto NewAsset = AssetToolsModule->Get().DuplicateAsset(AssetResult.Name, PackagePath, AssetResult.ExistingAssets[0].GetAsset()); + if (NewAsset) + { + PackagesToSave.Add(NewAsset->GetPackage()); + } + } + else + { + UObject* NewAsset = AssetResult.CreatedAsset.GetAsset(); + if (IsValid(NewAsset)) + { + if (NewAsset->IsA()) + { + AssetsToDelete.Add(AssetResult.CreatedAsset); + continue; + } + } + if (AssetCreationMode == EAssetCreationMode::Transient) + { + //Drag/Drop assets are created in transient package, and we need to move them to the drop location (or the default folder) + FAssetRenameData NewAssetRenameData(NewAsset, PackagePath, AssetResult.Name); + AssetsToRename.Add(NewAssetRenameData); + RenamedTransientAssets.Add(AssetResult.CreatedAsset); + UE_LOG(LogAudiokineticTools, Verbose, TEXT("Wwise Browser: Temporary asset '%s' will be moved to '%s'."), *NewAsset->GetFullName(), *PackagePath); + } + else if (AssetCreationMode == EAssetCreationMode::InPackage) + { + UE_LOG(LogAudiokineticTools, Verbose, TEXT("Wwise Browser: Saving new asset '%s'."), *NewAsset->GetFullName()); + //Assets were created in the destination package but we still need to save them + PackagesToSave.Add(NewAsset->GetPackage()); + } + } + } + if (AssetsToRename.Num() > 0) + { + bool bRenameSuccess = AssetToolsModule->Get().RenameAssets(AssetsToRename); + + //We really don't want to leave assets hanging around in the transient package + if (!bRenameSuccess && AssetCreationMode == EAssetCreationMode::Transient) + { + TArray ObjectsToDelete; + for (auto Asset : RenamedTransientAssets) + { + if (auto TransientObject = Asset.GetAsset()) + { + ObjectsToDelete.Add(TransientObject); + UE_LOG(LogAudiokineticTools, Warning, TEXT("Wwise Browser: Failed to rename temporary asset '%s' created in Drag/Drop, it will be deleted."), *Asset.GetFullName()); + } + } + ObjectTools::ForceDeleteObjects(ObjectsToDelete); + } + } + if (AssetsToDelete.Num() > 0) + { + UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("Wwise Browser: Deleting '%d' temporary packages."), PackagesToSave.Num()); + ObjectTools::DeleteAssets(AssetsToDelete, false); + } + if (PackagesToSave.Num() > 0) + { + UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("Wwise Browser: Saving '%d' new packages."), PackagesToSave.Num()); + UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); + } +} + +UAkAssetFactory* WwiseBrowserHelpers::GetAssetFactory(const FWwiseTreeItemPtr& WwiseTreeItem) +{ + UFactory* Factory = nullptr; + switch (WwiseTreeItem->ItemType) + { + case EWwiseItemType::Event: + Factory = UAkAudioEventFactory::StaticClass()->GetDefaultObject(); + break; + case EWwiseItemType::AcousticTexture: + Factory = UAkAcousticTextureFactory::StaticClass()->GetDefaultObject(); + break; + case EWwiseItemType::AuxBus: + Factory = UAkAuxBusFactory::StaticClass()->GetDefaultObject(); + break; + case EWwiseItemType::GameParameter: + Factory = UAkRtpcFactory::StaticClass()->GetDefaultObject(); + break; + case EWwiseItemType::Switch: + Factory = UAkSwitchValueFactory::StaticClass()->GetDefaultObject(); + break; + case EWwiseItemType::State: + Factory = UAkStateValueFactory::StaticClass()->GetDefaultObject(); + break; + case EWwiseItemType::Trigger: + Factory = UAkTriggerFactory::StaticClass()->GetDefaultObject(); + break; + case EWwiseItemType::EffectShareSet: + Factory = UAkEffectShareSetFactory::StaticClass()->GetDefaultObject(); + break; + default: + return nullptr; + } + if (Factory) + { + if (auto AkAssetFactory = Cast(Factory)) + { + AkAssetFactory->AssetID = WwiseTreeItem->ItemId; + AkAssetFactory->WwiseObjectName = WwiseTreeItem->DisplayName; + return AkAssetFactory; + } + } + return nullptr; +} + +bool WwiseBrowserHelpers::CanCreateAsset(const FWwiseTreeItemPtr& Item) +{ + return !Item->IsOfType({ EWwiseItemType::Sound }); +} + +FLinearColor WwiseBrowserHelpers::GetTextColor(bool bUpToDate) +{ + FColor Color; + return bUpToDate ? FLinearColor::Gray : FLinearColor(1.f, 0.33f, 0); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseBrowserHelpers.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseBrowserHelpers.h new file mode 100644 index 0000000..46efd19 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseBrowserHelpers.h @@ -0,0 +1,107 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "WwiseBrowserForwards.h" +#include "Templates/SharedPointer.h" +#include "AssetRegistry/AssetData.h" +#include "WaapiPicker/WwiseTreeItem.h" + +#include "WwiseBrowserHelpers.generated.h" + +class UAkAssetFactory; +class UObject; +class FString; + +UCLASS() +class UAkDragDropBlocker : public UObject +{ + GENERATED_BODY() +}; + +namespace WwiseBrowserHelpers +{ + struct WwiseBrowserAssetPayload + { + FAssetData CreatedAsset; + TArray ExistingAssets; + FString RelativePackagePath; + FString Name; + FGuid WwiseObjectGuid; + }; + + enum EAssetCreationMode + { + Transient, + InPackage + }; + + enum EAssetDuplicationMode + { + DoDuplication, + NoDuplication + }; + + /** + * Recursively iterates over a WwiseTreeItem and its children + * Found assets are added to the ExistingAssets array in the payload + * If no assets are found, a new asset is created and the payload's CreatedAsset field is set + * + * @param WwiseTreeItem Current WwiseTreeItem we want to create assets from + * @param InOutBrowserAssetPayloads The recursively filled array of WwiseBrowserAssetPayloads + * @param InOutKnownGuids List of encountered Guids so the same asset payload is not created twice + * This can happen when both an asset and its parent are selected in the browser + * @param AssetCreationMode Create assets in the transient package or directly in the content directory + * @param PackagePath Root path where the assets should be saved when AssetCreationMode is 'InPackage' + * @param CurrentRelativePath Recursively built path of the assets relative to the root tree item + */ + void FindOrCreateAssetsRecursive( + const FWwiseTreeItemPtr& WwiseTreeItem, + TArray& InOutBrowserAssetPayloads, + TSet& InOutKnownGuids, + const EAssetCreationMode AssetCreationMode, + const FString& PackagePath = "", + const FString& CurrentRelativePath = "" + ); + + FAssetData CreateBrowserAsset(const FString& AssetName, const FWwiseTreeItemPtr& WwiseTreeItem, UClass* AssetClass, const EAssetCreationMode AssetCreationMode, const FString& PackagePath); + FAssetData CreateBrowserAssetTask(const FString& AssetName, const FWwiseTreeItemPtr& WwiseTreeItem, UClass* AssetClass, const EAssetCreationMode AssetCreationMode, const FString& PackagePath); + + FAssetData CreateTransientAsset(const ::FString& AssetName, const FWwiseTreeItemPtr& WwiseTreeItem, UClass* AssetClass); + FAssetData CreateAssetInPackage(const ::FString& AssetName, const FWwiseTreeItemPtr& WwiseTreeItem, const FString& PackagePath, UClass* AssetClass); + FAssetData CreateAsset(const ::FString& AssetName, const FWwiseTreeItemPtr& WwiseTreeItem, UClass* AssetClass, UPackage* Pkg); + + /** + * Save, rename, duplicate assets produced by FindOrCreateAssetsRecursive + * + * @param Assets Array of WwiseBrowserAssetPayloads created to handle + * @param RootPackagePath Root path where renamed and duplicated assets will be copied to + * @param AssetCreationMode Must match the AssetCreationMode passed to FindOrCreateAssetsRecursive + * @param AssetDuplicationMode Whether existing assets should be duplicated into a new asset placed relative to RootPackagePath + */ + void SaveSelectedAssets(const TArray Assets, const FString& RootPackagePath, const EAssetCreationMode AssetCreationMode, const EAssetDuplicationMode AssetDuplicationMode); + void SaveSelectedAssetsTask(const TArray Assets, const FString& RootPackagePath, const EAssetCreationMode AssetCreationMode, const EAssetDuplicationMode AssetDuplicationMode); + + UAkAssetFactory* GetAssetFactory(const FWwiseTreeItemPtr& WwiseTreeItem); + + EWwiseItemType::Type GetTypeFromClass(UClass* Class); + bool CanCreateAsset(const FWwiseTreeItemPtr& Item); + FLinearColor GetTextColor(bool UpToDate); + + const FName WwiseBrowserColumnId = TEXT("WwiseItem"); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseBrowserTreeColumn.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseBrowserTreeColumn.cpp new file mode 100644 index 0000000..c36b470 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseBrowserTreeColumn.cpp @@ -0,0 +1,75 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "WwiseBrowserTreeColumn.h" + +#include "AkAudioStyle.h" +#include "SWwiseBrowser.h" +#include "WwiseBrowserHelpers.h" +#include "Widgets/SWidget.h" +#include "WaapiPicker/WwiseTreeItem.h" + +#define LOCTEXT_NAMESPACE "AkAudio" + +FWwiseBrowserTreeColumn::FWwiseBrowserTreeColumn(SWwiseBrowser& WwiseBrowser) + : WwiseBrowserWeak( StaticCastSharedRef(WwiseBrowser.AsShared()) ) +{ +} + +FName FWwiseBrowserTreeColumn::GetColumnId() +{ + return FName("WwiseObjects"); +} + +const TSharedRef FWwiseBrowserTreeColumn::ConstructRowWidget(FWwiseTreeItemPtr TreeItem, + const STableRow& Row) +{ + auto WwiseBrowser = WwiseBrowserWeak.Pin(); + + bool bItemUpToDate = WwiseBrowser->IsWaapiAvailable() == EWwiseConnectionStatus::Connected ? TreeItem->IsItemUpToDate() : TreeItem->IsUAssetUpToDate(); + + return SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0, 1, 2, 1) + .VAlign(VAlign_Center) + [ + SNew(SImage) + .Image(FAkAudioStyle::GetBrush(TreeItem->ItemType)) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString(TreeItem->DisplayName)) + .ColorAndOpacity(WwiseBrowserHelpers::GetTextColor(bItemUpToDate || !TreeItem->ShouldDisplayInfo())) + .HighlightText(WwiseBrowser->GetFilterHighlightText()) + ]; +} + +SHeaderRow::FColumn::FArguments FWwiseBrowserTreeColumn::ConstructHeaderRowColumn() +{ + auto NameColumnHeader = SHeaderRow::Column(GetColumnId()); + NameColumnHeader.ColumnId(WwiseBrowserHelpers::WwiseBrowserColumnId); + TAttribute Label; + Label.Set(FText::FromString("Name")); + NameColumnHeader.DefaultLabel(Label); + NameColumnHeader.DefaultTooltip(LOCTEXT("WwiseBrowserColumn_Tooltip", "The names of the items in their hierarchy. The hierarchy uses the information from the Generated SoundBanks first.")); + return NameColumnHeader; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseBrowserTreeColumn.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseBrowserTreeColumn.h new file mode 100644 index 0000000..9edc522 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseBrowserTreeColumn.h @@ -0,0 +1,43 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "SWwiseBrowser.h" +#include "WwiseBrowserForwards.h" +#include "IWwiseBrowserColumn.h" + +class FWwiseBrowserTreeColumn : public IWwiseBrowserColumn +{ +public: + + FWwiseBrowserTreeColumn(SWwiseBrowser& WwiseBrowser); + + virtual ~FWwiseBrowserTreeColumn() {} + + virtual FName GetColumnId() override; + + virtual const TSharedRef ConstructRowWidget(FWwiseTreeItemPtr TreeItem, + const STableRow& Row) override; + + virtual SHeaderRow::FColumn::FArguments ConstructHeaderRowColumn() override; + +private: + /** Weak reference to the outliner widget that owns our list */ + TWeakPtr< SWwiseBrowser > WwiseBrowserWeak; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseBrowserViewCommands.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseBrowserViewCommands.cpp new file mode 100644 index 0000000..0540423 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseBrowserViewCommands.cpp @@ -0,0 +1,18 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "WwiseBrowserViewCommands.h" \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseBrowserViewCommands.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseBrowserViewCommands.h new file mode 100644 index 0000000..38f800f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseBrowserViewCommands.h @@ -0,0 +1,82 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "Framework/Commands/Commands.h" +#include "AkAudioStyle.h" + +#define LOCTEXT_NAMESPACE "WwiseBrowserViewCommands" + + +class FWwiseBrowserViewCommands : public TCommands +{ + +public: + + /** FWwiseBrowserViewCommands Constructor */ + FWwiseBrowserViewCommands() : TCommands + ( + "WwiseBrowserViewCommand", // Context name for fast lookup + NSLOCTEXT("Contexts", "WwiseBrowserViewCommand", "Wwise Browser Command"), // Localized context name for displaying + NAME_None, // Parent + FAkAudioStyle::GetStyleSetName() // Icon Style Set + ) + { + } + + /** + * Initialize the commands + */ + virtual void RegisterCommands() override + { + UI_COMMAND(RequestPlayWwiseItem, "Play/Stop", "Plays or stops the selected items.", EUserInterfaceActionType::Button, FInputChord(EKeys::SpaceBar)); + UI_COMMAND(RequestStopAllWwiseItem, "Stop All", "Stop all playing events", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(RequestExploreWwiseItem, "Show in Folder", "Finds this item on disk.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(RequestFindInProjectExplorerWwisetem, "Find in the Wwise Project Explorer", "Finds the specified object in the Project Explorer (Sync Group 1).", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control | EModifierKey::Shift, EKeys::One)); + UI_COMMAND(RequestFindInContentBrowser, "Find in Content Browser ", "Locates the corresponding item inside the Content Browser", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(RequestRefreshWwiseBrowser, "Refresh All", "Populates the Wwise Browser.", EUserInterfaceActionType::Button, FInputChord(EKeys::F5)); + UI_COMMAND(RequestImportWwiseItem, "Import Selected Assets", "Imports the selected assets from the Wwise Browser.", EUserInterfaceActionType::Button, FInputChord()); + } + +public: + /** Requests a play action on a Wwise items */ + TSharedPtr< FUICommandInfo > RequestPlayWwiseItem; + + /** Requests a stop playing on all Wwise items */ + TSharedPtr< FUICommandInfo > RequestStopAllWwiseItem; + + /** Requests an explore action on the Item (locates its workunit in the file browser) */ + TSharedPtr< FUICommandInfo > RequestExploreWwiseItem; + + /** Requests a Find in the Wwise Project Explorer action on the Item */ + TSharedPtr< FUICommandInfo > RequestFindInProjectExplorerWwisetem; + + /** Requests a refresh on the Wwise Browser */ + TSharedPtr< FUICommandInfo > RequestRefreshWwiseBrowser; + + /** Requests a Find in Content Browser action on the Item */ + TSharedPtr< FUICommandInfo > RequestFindInContentBrowser; + + /** Imports the selected asset into the project's Contents */ + TSharedPtr< FUICommandInfo > RequestImportWwiseItem; + +}; + + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseObjectsColumn.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseObjectsColumn.h new file mode 100644 index 0000000..a944d50 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseObjectsColumn.h @@ -0,0 +1,33 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "WwiseBrowserForwards.h" +#include "IWwiseBrowserColumn.h" + +class FWwiseBrowserTreeColumn : public IWwiseBrowserColumn +{ +public: + virtual FName GetColumnId() override; + + virtual const TSharedRef ConstructRowWidget(FWwiseTreeItemPtr TreeItem, + const STableRow& Row) override; + + virtual SHeaderRow::FColumn::FArguments ConstructHeaderRowColumn() override; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseStatusColumn.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseStatusColumn.cpp new file mode 100644 index 0000000..1ca4cf0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseStatusColumn.cpp @@ -0,0 +1,91 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "WwiseStatusColumn.h" + +#include "WwiseBrowserForwards.h" +#include "WwiseBrowserHelpers.h" +#include "DataSource/WaapiDataSource.h" +#include "Widgets/SWidget.h" + +#define LOCTEXT_NAMESPACE "AkAudio" + +FWwiseStatusColumn::FWwiseStatusColumn(SWwiseBrowser& WwiseBrowser) + : WwiseBrowserWeak(StaticCastSharedRef(WwiseBrowser.AsShared())) +{ +} + +FText FWwiseStatusColumn::GetDisplayedName(FWwiseTreeItemPtr TreeItem) +{ + if(!TreeItem->ShouldDisplayInfo() || WwiseBrowserWeak.Pin()->IsWaapiAvailable() != EWwiseConnectionStatus::Connected) + { + return LOCTEXT("WwiseBrowserEmpty", ""); + } + if(TreeItem->IsSoundBankUpToDate()) + { + return LOCTEXT("WwiseStatusColumnUpToDate", "SoundBank Up to Date"); + } + else if (TreeItem->IsRenamedInWwise()) + { + return LOCTEXT("WwiseStatusColumnRenamed", "Renamed in Wwise"); + } + else if(TreeItem->IsNewInWwise()) + { + return LOCTEXT("WwiseStatusColumnNewInWwise", "New In Wwise"); + } + else if(TreeItem->IsDeletedInWwise()) + { + return LOCTEXT("WwiseStatusColumnNewInWwise", "Deleted In Wwise"); + } + else if (TreeItem->IsMovedInWwise()) + { + return LOCTEXT("WwiseStatusColumnMovedInWwise", "Moved In Wwise"); + } + return LOCTEXT("WwiseStatusColumnNotInWwise", "Not in Wwise or SoundBank"); +} + +FName FWwiseStatusColumn::GetColumnId() +{ + return FName("WwiseStatus"); +} + +const TSharedRef FWwiseStatusColumn::ConstructRowWidget(FWwiseTreeItemPtr TreeItem, + const STableRow& Row) +{ + + return SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth(). + VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(GetDisplayedName(TreeItem)) + .ColorAndOpacity((WwiseBrowserWeak.Pin()->IsWaapiAvailable() != EWwiseConnectionStatus::Connected) ? FLinearColor::Gray : WwiseBrowserHelpers::GetTextColor(TreeItem->IsSoundBankUpToDate())) + ]; +} + +SHeaderRow::FColumn::FArguments FWwiseStatusColumn::ConstructHeaderRowColumn() +{ + SHeaderRow::FColumn::FArguments WwiseStatusColumnHeader = SHeaderRow::Column(GetColumnId()); + TAttribute StatusLabel; + StatusLabel.Set(FText::FromString("Wwise Project vs SoundBanks")); + WwiseStatusColumnHeader.DefaultLabel(StatusLabel); + WwiseStatusColumnHeader.DefaultTooltip(LOCTEXT("WwiseColumn_Tooltip", "The status of the Generated SoundBanks compared to the information in the Wwise Project. The Wwise Project must be open for this column to display information.")); + return WwiseStatusColumnHeader; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseStatusColumn.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseStatusColumn.h new file mode 100644 index 0000000..ed90d0c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseStatusColumn.h @@ -0,0 +1,43 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "WwiseBrowserForwards.h" +#include "SWwiseBrowser.h" +#include "IWwiseBrowserColumn.h" + +class FWwiseStatusColumn : public IWwiseBrowserColumn +{ + FText GetDisplayedName(FWwiseTreeItemPtr TreeItem); +public: + + FWwiseStatusColumn(SWwiseBrowser& WwiseBrowser); + + virtual ~FWwiseStatusColumn() {} + + virtual FName GetColumnId() override; + + virtual const TSharedRef ConstructRowWidget(FWwiseTreeItemPtr TreeItem, + const STableRow& Row) override; + + virtual SHeaderRow::FColumn::FArguments ConstructHeaderRowColumn() override; +private: + /** Weak reference to the outliner widget that owns our list */ + TWeakPtr< SWwiseBrowser > WwiseBrowserWeak; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseUEAssetStatusColumn.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseUEAssetStatusColumn.cpp new file mode 100644 index 0000000..8cb5ad1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseUEAssetStatusColumn.cpp @@ -0,0 +1,81 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "WwiseUEAssetStatusColumn.h" + +#include "AkAudioStyle.h" +#include "WwiseBrowserForwards.h" +#include "WwiseBrowserHelpers.h" +#include "Widgets/SWidget.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Text/STextBlock.h" + +#define LOCTEXT_NAMESPACE "AkAudio" + +FText FWwiseUEAssetStatusColumn::GetDisplayedName(FWwiseTreeItemPtr TreeItem) +{ + if(!TreeItem->UEAssetExists() || !TreeItem->ShouldDisplayInfo()) + { + return LOCTEXT("WwiseBrowserEmpty", ""); + } + if(TreeItem->HasUniqueUAsset()) + { + return FText::FromName(TreeItem->UAssetName); + } + return FText::Format(LOCTEXT("UEAssetsColumnMultiple", "Multiple UAssets ({0})"), TreeItem->Assets.Num()); +} + +FName FWwiseUEAssetStatusColumn::GetColumnId() +{ + return FName("UEStatus"); +} + +const TSharedRef FWwiseUEAssetStatusColumn::ConstructRowWidget(FWwiseTreeItemPtr TreeItem, + const STableRow& Row) +{ + return SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0, 1, 2, 1) + .VAlign(VAlign_Center) + [ + SNew(SImage) + .Image(FAkAudioStyle::GetBrush(TreeItem->ItemType)) + .Visibility((!TreeItem->HasUniqueUAsset() || TreeItem->IsFolder()) ? EVisibility::Collapsed : EVisibility::Visible) + ] + + + SHorizontalBox::Slot() + .AutoWidth(). + VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(GetDisplayedName(TreeItem)) + ]; +} + +SHeaderRow::FColumn::FArguments FWwiseUEAssetStatusColumn::ConstructHeaderRowColumn() +{ + auto UEStatusColumnHeader = SHeaderRow::Column(GetColumnId()); + TAttribute UEStatusLabel; + UEStatusLabel.Set(FText::FromString("Wwise UAssets Status")); + UEStatusColumnHeader.DefaultLabel(UEStatusLabel); + UEStatusColumnHeader.DefaultTooltip(LOCTEXT("UAssetColumn_Tooltip", "The number of UAssets that exist for this Item.")); + return UEStatusColumnHeader; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseUEAssetStatusColumn.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseUEAssetStatusColumn.h new file mode 100644 index 0000000..aaebbea --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseBrowser/WwiseUEAssetStatusColumn.h @@ -0,0 +1,35 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "WwiseBrowserForwards.h" +#include "IWwiseBrowserColumn.h" +#include "Widgets/SWidget.h" + +class FWwiseUEAssetStatusColumn : public IWwiseBrowserColumn +{ + FText GetDisplayedName(FWwiseTreeItemPtr TreeItem); +public: + virtual FName GetColumnId() override; + + virtual const TSharedRef ConstructRowWidget(FWwiseTreeItemPtr TreeItem, + const STableRow& Row) override; + + virtual SHeaderRow::FColumn::FArguments ConstructHeaderRowColumn() override; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseProject/AcousticTextureParamLookup.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseProject/AcousticTextureParamLookup.cpp new file mode 100644 index 0000000..b225a57 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseProject/AcousticTextureParamLookup.cpp @@ -0,0 +1,98 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AcousticTextureParamLookup.h" +#include "AkSettings.h" +#include "AkAcousticTexture.h" +#include "AkUnrealHelper.h" +#include "AssetManagement/AkAssetDatabase.h" +#include "IAudiokineticTools.h" +#include "Wwise/WwiseProjectDatabase.h" + +void AkAcousticTextureParamLookup::LoadAllTextures() +{ + FWwiseProjectDatabase* ProjectDatabase = FWwiseProjectDatabase::Get(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("LoadAllTextures: ProjectDatabase not loaded")); + return; + } + + const FWwiseDataStructureScopeLock DataStructure(*ProjectDatabase); + const auto& AcousticTextures = DataStructure.GetAcousticTextures(); + + if (AcousticTextures.Num() == 0) + { + return; + } + + UAkSettings* AkSettings = GetMutableDefault(); + auto& AkAssetDatabase = AkAssetDatabase::Get(); + + if (UNLIKELY(!AkSettings)) + { + UE_LOG(LogAudiokineticTools, Error, TEXT("No AkSettings while loading Acoustic Textures")); + return; + } + + for (auto& AcousticTexture : AcousticTextures) + { + const FString& TextureName = AcousticTexture.Value.AcousticTextureName().ToString(); + float AbsorptionLow = AcousticTexture.Value.GetAcousticTexture()->AbsorptionLow; + float AbsorptionMidLow = AcousticTexture.Value.GetAcousticTexture()->AbsorptionMidLow; + float AbsorptionMidHigh = AcousticTexture.Value.GetAcousticTexture()->AbsorptionMidHigh; + float AbsorptionHigh = AcousticTexture.Value.GetAcousticTexture()->AbsorptionHigh; + + uint32 TextureShortID = 0; + FAssetData Texture; + FGuid Id = AcousticTexture.Value.AcousticTextureGuid(); + if (LIKELY(AkAssetDatabase.FindFirstAsset(Id, Texture))) + { + const auto AcousticTextureAsset = Cast(Texture.GetAsset()); + if (LIKELY(AcousticTextureAsset)) + { + TextureShortID = AcousticTextureAsset->GetShortID(); + + UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("Properties for texture %s (%" PRIu32 "): Absorption High: %.0f%%, MidHigh: %.0f%%, MidLow: %.0f%%, Low: %.0f%%"), + *TextureName, TextureShortID, AbsorptionHigh, AbsorptionMidHigh, AbsorptionMidLow, AbsorptionLow); + } + else + { + UE_LOG(LogAudiokineticTools, Error, TEXT("Invalid AkAcousticTexture for GUID %s (%s)"), *Id.ToString(), *TextureName); + } + } + else + { + UE_LOG(LogAudiokineticTools, Log, TEXT("Properties for texture %s (No AkAcousticTexture): Absorption High: %.0f%%, MidHigh: %.0f%%, MidLow: %.0f%%, Low: %.0f%%"), + *TextureName, AbsorptionHigh, AbsorptionMidHigh, AbsorptionMidLow, AbsorptionLow); + } + + const FVector4 AbsorptionValues = FVector4(AbsorptionLow, AbsorptionMidLow, AbsorptionMidHigh, AbsorptionHigh) / 100.0f; + + AkSettings->SetAcousticTextureParams(Id,{AbsorptionValues, TextureShortID}); + } +} + +void AkAcousticTextureParamLookup::UpdateParamsMap() const +{ + UAkSettings* AkSettings = GetMutableDefault(); + if (AkSettings != nullptr) + { + AkSettings->ClearTextureParamsMap(); + LoadAllTextures(); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseProject/AcousticTextureParamLookup.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseProject/AcousticTextureParamLookup.h new file mode 100644 index 0000000..5c0b85b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseProject/AcousticTextureParamLookup.h @@ -0,0 +1,25 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +class AkAcousticTextureParamLookup +{ +public: + static void LoadAllTextures(); + void UpdateParamsMap() const; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseProject/WwiseSoundBankInfoCache.cpp b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseProject/WwiseSoundBankInfoCache.cpp new file mode 100644 index 0000000..e297181 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseProject/WwiseSoundBankInfoCache.cpp @@ -0,0 +1,232 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "WwiseSoundBankInfoCache.h" + +#include "AssetManagement/AkAssetDatabase.h" +#include "WwiseDefines.h" +#include "AkUnrealHelper.h" +#include "HAL/FileManager.h" +#if UE_5_0_OR_LATER +#include "HAL/PlatformFileManager.h" +#else +#include "HAL/PlatformFilemanager.h" +#endif +#include "Misc/Paths.h" +#include "AssetManagement/WwiseProjectInfo.h" + +namespace WwiseSoundBankInfoCacheHelpers +{ + constexpr auto CacheVersion = 1; + constexpr auto SFXLanguageID = 393239870; +} + +bool WwiseSoundBankInfoCache::Load(const FString& Path) +{ + WwiseProjectInfo wwiseProjectInfo; + wwiseProjectInfo.Parse(); + + for (auto& platform : wwiseProjectInfo.GetSupportedPlatforms()) + { + platformNameToGuidMap.Add(platform.Name, platform.ID); + } + + auto& platformFile = FPlatformFileManager::Get().GetPlatformFile(); + TUniquePtr ar(IFileManager::Get().CreateFileReader(*Path)); + + uint32 readVersion = 0; + *ar << readVersion; + + if (readVersion != WwiseSoundBankInfoCacheHelpers::CacheVersion) + { + return false; + } + + uint32 flagsHash = 0; + *ar << flagsHash; + + FGuid currentPlatform; + uint32 currentLanguage = 0; + CacheType currentCacheType = static_cast(0); + + SerializeState serializeState = SerializeState::None; + *ar << serializeState; + + while (serializeState != SerializeState::EndOfData) + { + switch (serializeState) + { + case SerializeState::None: + case SerializeState::EndOfData: + break; + case SerializeState::Platform: + { + readGuid(*ar, currentPlatform); + break; + } + case SerializeState::Language: + { + *ar << currentLanguage; + break; + } + case SerializeState::BankInfo: + { + FString name; + readString(*ar, name); + + auto key = CacheKey(name, currentPlatform, currentLanguage); + + BankInfo& info = bankInfoMap.Emplace(key); + *ar << info; + break; + } + case SerializeState::InfoFile: + { + FString name; + readString(*ar, name); + + FileInfo info; + *ar << info; + break; + } + case SerializeState::InfoFileType: + { + *ar << currentCacheType; + break; + } + } + + *ar << serializeState; + } + + return true; +} + +void WwiseSoundBankInfoCache::readString(FArchive& Ar, FString& Value) +{ + uint32 stringSize = 0; + Ar << stringSize; + + TArray rawString; + rawString.SetNumUninitialized(stringSize); + + Ar.Serialize(rawString.GetData(), stringSize); + + FUTF8ToTCHAR utf8(reinterpret_cast(rawString.GetData()), stringSize); + Value = FString(utf8.Length(), utf8.Get()); +} + +void WwiseSoundBankInfoCache::readGuid(FArchive& Ar, FGuid& Id) +{ + uint32 data1; + uint16 data2; + uint16 data3; + uint8 data4[8]; + + Ar << data1; + Ar << data2; + Ar << data3; + Ar.Serialize(data4, sizeof(data4)); + + Id.A = data1; + Id.B = data3 | (data2 << 16); + Id.C = data4[3] | (data4[2] << 8) | (data4[1] << 16) | (data4[0] << 24); + Id.D = data4[7] | (data4[6] << 8) | (data4[5] << 16) | (data4[4] << 24); +} + +void WwiseSoundBankInfoCache::readBool(FArchive& Ar, bool& Value) +{ + uint8 temp = 0; + Ar.Serialize(&temp, 1); + Value = temp > 0; +} + +bool WwiseSoundBankInfoCache::IsSoundBankUpToUpdate(const FGuid& Id, const FString& Platform, const FString& Language, const uint32 Hash) const +{ + FString bankName = AkUnrealHelper::GuidToBankName(Id); + + FGuid platformGuid; + + if (auto platformIt = platformNameToGuidMap.Find(Platform)) + { + platformGuid = *platformIt; + } + + uint32 languageId = WwiseSoundBankInfoCacheHelpers::SFXLanguageID; + + if (Language.Len() > 0) + { + AK::FNVHash32 hash; + FTCHARToUTF8 utf8(*Language.ToLower()); + languageId = hash.Compute(utf8.Get(), utf8.Length()); + } + + CacheKey key(bankName, platformGuid, languageId); + + if (auto* cacheEntry = bankInfoMap.Find(key)) + { + return cacheEntry->Hash == Hash; + } + + return false; +} + +FArchive& operator<<(FArchive& Ar, WwiseSoundBankInfoCache::FileInfo& Value) +{ + Ar << Value.Hash; + Ar << Value.Timestamp; + WwiseSoundBankInfoCache::readBool(Ar, Value.Updated); + return Ar; +} + +FArchive& operator<<(FArchive& Ar, WwiseSoundBankInfoCache::BankInfo& Value) +{ + Ar << static_cast(Value); + Ar << Value.Stats; + WwiseSoundBankInfoCache::readBool(Ar, Value.IsTemporary); + + return Ar; +} + +FArchive& operator<<(FArchive& Ar, WwiseSoundBankInfoCache::MemoryStats& Value) +{ + Ar << Value.Timestamp; + Ar << Value.DataSize; + Ar << Value.FileSize; + Ar << Value.DecodedSize; + Ar << Value.SFXPreFetchSize; + Ar << Value.SFXInMemorySize; + Ar << Value.SFXMissingFiles; + Ar << Value.MusicPreFetchSize; + Ar << Value.MusicInMemorySize; + Ar << Value.MusicMissingFiles; + Ar << Value.VoicePreFetchSize; + Ar << Value.VoiceInMemorySize; + Ar << Value.VoiceMissingFiles; + Ar << Value.ReplacedFiles; + + return Ar; +} + +uint32 GetTypeHash(const WwiseSoundBankInfoCache::CacheKey& Key) +{ + AK::FNVHash32 hash; + hash.Compute(*Key.Name, sizeof(TCHAR) * Key.Name.Len()); + hash.Compute(&Key.Platform, sizeof(FGuid)); + hash.Compute(&Key.Language, sizeof(uint32)); + return hash.Get(); +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseProject/WwiseSoundBankInfoCache.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseProject/WwiseSoundBankInfoCache.h new file mode 100644 index 0000000..e45c71b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Private/WwiseProject/WwiseSoundBankInfoCache.h @@ -0,0 +1,123 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "ISoundBankInfoCache.h" +#include "Containers/Map.h" +#include "Misc/Guid.h" + +class FArchive; + +class WwiseSoundBankInfoCache : public ISoundBankInfoCache +{ +public: + bool Load(const FString& Path); + + bool IsSoundBankUpToUpdate(const FGuid& Id, const FString& Platform, const FString& Language, const uint32 Hash) const override; + +private: + void readString(FArchive& ar, FString& Value); + void readGuid(FArchive& Ar, FGuid& Value); + static void readBool(FArchive& Ar, bool& Value); + +private: + enum class CacheType : uint8 + { + XML, + JSON, + Bank, + Count + }; + + enum class SerializeState : uint8 + { + None, + Platform, + Language, + BankInfo, + InfoFile, + InfoFileType, + EndOfData + }; + + struct FileInfo + { + uint32 Hash = 0; + int64 Timestamp = 0; + bool Updated = false; + }; + + struct MemoryStats + { + int64 Timestamp; + uint32 DataSize; + uint32 FileSize; + uint32 DecodedSize; + uint32 SFXPreFetchSize; + uint32 SFXInMemorySize; + uint32 SFXMissingFiles; + uint32 MusicPreFetchSize; + uint32 MusicInMemorySize; + uint32 MusicMissingFiles; + uint32 VoicePreFetchSize; + uint32 VoiceInMemorySize; + uint32 VoiceMissingFiles; + uint32 ReplacedFiles; + }; + + struct BankInfo : public FileInfo + { + MemoryStats Stats; + bool IsTemporary = false; + }; + + struct CacheKey + { + CacheKey() = default; + CacheKey(const FString& InName, const FGuid& InPlatform, uint32 InLanguage) + : Name(InName), Platform(InPlatform), Language(InLanguage) + {} + + + FString Name; + FGuid Platform; + uint32 Language; + + bool operator==(const CacheKey& Right) const + { + return Name == Right.Name + && Platform == Right.Platform + && Language == Right.Language + ; + } + }; + + friend FArchive& operator<<(FArchive& Ar, WwiseSoundBankInfoCache::FileInfo& Value); + friend FArchive& operator<<(FArchive& Ar, WwiseSoundBankInfoCache::BankInfo& Value); + friend FArchive& operator<<(FArchive& Ar, WwiseSoundBankInfoCache::MemoryStats& Value); + friend uint32 GetTypeHash(const CacheKey& Key); + +private: + TMap platformNameToGuidMap; + TMap bankInfoMap; +}; + +FArchive& operator<<(FArchive& Ar, WwiseSoundBankInfoCache::FileInfo& Value); +FArchive& operator<<(FArchive& Ar, WwiseSoundBankInfoCache::BankInfo& Value); +FArchive& operator<<(FArchive& Ar, WwiseSoundBankInfoCache::MemoryStats& Value); +uint32 GetTypeHash(const WwiseSoundBankInfoCache::CacheKey& Key); \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Public/AkAssetFactories.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Public/AkAssetFactories.h new file mode 100644 index 0000000..1a245e8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Public/AkAssetFactories.h @@ -0,0 +1,126 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Factories/Factory.h" +#include "AkAssetFactories.generated.h" + +UCLASS(hidecategories = Object) +class AUDIOKINETICTOOLS_API UAkAssetFactory : public UFactory +{ + GENERATED_BODY() + +public: + FGuid AssetID; + uint32 ShortID; + FString WwiseObjectName; +}; + +UCLASS(hidecategories = Object) +class AUDIOKINETICTOOLS_API UAkAcousticTextureFactory : public UAkAssetFactory +{ + GENERATED_BODY() + +public: + UAkAcousticTextureFactory(const class FObjectInitializer& ObjectInitializer); + + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; + virtual bool CanCreateNew() const override; +}; + +UCLASS(hidecategories = Object) +class AUDIOKINETICTOOLS_API UAkAudioEventFactory : public UAkAssetFactory +{ + GENERATED_BODY() + +public: + UAkAudioEventFactory(const class FObjectInitializer& ObjectInitializer); + + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; + virtual bool CanCreateNew() const override; +}; + +UCLASS(hidecategories = Object) +class AUDIOKINETICTOOLS_API UAkAuxBusFactory : public UAkAssetFactory +{ + GENERATED_BODY() + +public: + UAkAuxBusFactory(const class FObjectInitializer& ObjectInitializer); + + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; + virtual bool CanCreateNew() const override; +}; + +UCLASS(hidecategories = Object) +class AUDIOKINETICTOOLS_API UAkRtpcFactory : public UAkAssetFactory +{ + GENERATED_BODY() + +public: + UAkRtpcFactory(const class FObjectInitializer& ObjectInitializer); + + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; + virtual bool CanCreateNew() const override; +}; + +UCLASS(hidecategories = Object) +class AUDIOKINETICTOOLS_API UAkTriggerFactory : public UAkAssetFactory +{ + GENERATED_BODY() + +public: + UAkTriggerFactory(const class FObjectInitializer& ObjectInitializer); + + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; + virtual bool CanCreateNew() const override; +}; + +// mlarouche - For now Switch and State factory are only used in drag & drop +UCLASS(hidecategories = Object) +class AUDIOKINETICTOOLS_API UAkStateValueFactory : public UAkAssetFactory +{ + GENERATED_BODY() + +public: + UAkStateValueFactory(const class FObjectInitializer& ObjectInitializer); + + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; +}; + +UCLASS(hidecategories = Object) +class AUDIOKINETICTOOLS_API UAkSwitchValueFactory : public UAkAssetFactory +{ + GENERATED_BODY() + +public: + UAkSwitchValueFactory(const class FObjectInitializer& ObjectInitializer); + + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; +}; + +UCLASS(hidecategories = Object) +class AUDIOKINETICTOOLS_API UAkEffectShareSetFactory : public UAkAssetFactory +{ + GENERATED_BODY() + +public: + UAkEffectShareSetFactory(const class FObjectInitializer& ObjectInitializer); + + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Public/AkAudioBankGenerationHelpers.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Public/AkAudioBankGenerationHelpers.h new file mode 100644 index 0000000..17bcdcf --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Public/AkAudioBankGenerationHelpers.h @@ -0,0 +1,50 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Misc/EnumClassFlags.h" +#include "Containers/UnrealString.h" + +class UAkAudioBank; + +namespace AkAudioBankGenerationHelper +{ + /** + * Get path to the WwiseConsole application + */ + FString GetWwiseConsoleApplicationPath(); + + /** + * Function to create the Generate SoundBanks window + * + * @param pSoundBanks List of SoundBanks to be pre-selected + * @paramin_bShouldSaveWwiseProject Whether the Wwise project should be saved or not + */ + void CreateGenerateSoundDataWindow(bool ProjectSave = false); + + enum class AkSoundDataClearFlags + { + None = 0, + SoundBankInfoCache = 1 << 0, + MediaCache = 1 << 1, + }; + + ENUM_CLASS_FLAGS(AkSoundDataClearFlags) + + void DoClearSoundData(AkSoundDataClearFlags ClearFlags); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Public/AkUnrealAssetDataHelper.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Public/AkUnrealAssetDataHelper.h new file mode 100644 index 0000000..81e33a7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Public/AkUnrealAssetDataHelper.h @@ -0,0 +1,60 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkUEFeatures.h" +#include "WwiseItemType.h" +#include "AssetRegistry/AssetData.h" + +class FWwiseAnyRef; + +namespace AkUnrealAssetDataHelper +{ + AUDIOKINETICTOOLS_API bool IsSameType(const FAssetData& AssetData, EWwiseItemType::Type ItemType); + + AUDIOKINETICTOOLS_API FName GetUClassName(EWwiseItemType::Type ItemType); + + // Gets the AssetClass prior to UE 5.1, otherwise the AssetClassPath + AUDIOKINETICTOOLS_API FName GetAssetClassName(const FAssetData& AssetData); + + AUDIOKINETICTOOLS_API bool IsAssetAkAudioType(const FAssetData& AssetData); + + AUDIOKINETICTOOLS_API bool IsAssetTransient(const FAssetData& AssetData); + + // Sets the AssetClass prior to UE 5.1, otherwise the AssetClassPath + AUDIOKINETICTOOLS_API void SetAssetClassName(FAssetData& AssetData, UClass* Class); + + AUDIOKINETICTOOLS_API FString GetAssetDefaultPackagePath(const FAssetData& AssetData); + + AUDIOKINETICTOOLS_API FString GetAssetDefaultPackagePath(const FWwiseAnyRef* WwiseRef); + + AUDIOKINETICTOOLS_API FName GetAssetDefaultName(const FAssetData& AssetData); + + AUDIOKINETICTOOLS_API FName GetAssetDefaultName(const FWwiseAnyRef* WwiseRef); + + template + bool AssetOfType(const FAssetData& AssetData) + { +#if UE_5_1_OR_LATER + return AssetData.AssetClassPath == T::StaticClass()->GetClassPathName(); +#else + return AssetData.AssetClass == T::StaticClass()->GetFName(); +#endif + } + +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Public/AudiokineticToolsModule.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Public/AudiokineticToolsModule.h new file mode 100644 index 0000000..5236b19 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Public/AudiokineticToolsModule.h @@ -0,0 +1,205 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +/*============================================================================= + AudiokineticToolsModule.h +=============================================================================*/ +#include "AudiokineticToolsPrivatePCH.h" +#include "AkAcousticPortal.h" +#include "AkAudioBankGenerationHelpers.h" +#include "AkComponent.h" +#include "AkSettings.h" +#include "AkSettingsPerUser.h" +#include "AssetManagement/AkAssetMigrationManager.h" +#include "AssetManagement/AkGenerateSoundBanksTask.h" +#include "AssetManagement/GeneratedSoundBanksDirectoryWatcher.h" +#include "AssetManagement/WwiseProjectInfo.h" +#include "AssetToolsModule.h" +#include "Factories/AkAssetTypeActions.h" +#include "IAudiokineticTools.h" + +#include "WwiseBrowser/SWwiseBrowser.h" +#include "Wwise/WwiseProjectDatabase.h" +#include "GeneratedSoundBanksWarning.h" +#include "ReloadPopup.h" + +#include "ComponentAssetBroker.h" +#include "Developer/ToolMenus/Public/ToolMenu.h" +#include "Editor/LevelEditor/Public/LevelEditor.h" +#include "ISequencerModule.h" +#include "ISettingsModule.h" +#include "Internationalization/Culture.h" +#include "Modules/ModuleManager.h" +#include "Sequencer/MovieSceneAkAudioRTPCTrackEditor.h" +#include "UnrealEd/Public/EditorBuildUtils.h" +#include "Widgets/Docking/SDockTab.h" +#include "Widgets/Input/SHyperlink.h" + +#define LOCTEXT_NAMESPACE "AkAudio" +DEFINE_LOG_CATEGORY(LogAudiokineticTools); + +namespace +{ + struct WwiseLanguageToUnrealCulture + { + const TCHAR* WwiseLanguage; + const TCHAR* UnrealCulture; + }; + + // This list come from the fixed list of languages that were used before Wwise 2017.1 + const WwiseLanguageToUnrealCulture WwiseLanguageToUnrealCultureList[] = { + {TEXT("Arabic"), TEXT("ar")}, + {TEXT("Bulgarian"), TEXT("bg")}, + {TEXT("Chinese(HK)"), TEXT("zh-HK")}, + {TEXT("Chinese(Malaysia)"), TEXT("zh")}, + {TEXT("Chinese(PRC)"), TEXT("zh-CN")}, + {TEXT("Chinese(Taiwan)"), TEXT("zh-TW")}, + {TEXT("Czech"), TEXT("cs")}, + {TEXT("Danish"), TEXT("da")}, + {TEXT("English(Australia)"), TEXT("en-AU")}, + {TEXT("English(Canada)"), TEXT("en-CA")}, + {TEXT("English(US)"), TEXT("en-US")}, + {TEXT("English(UK)"), TEXT("en-GB")}, + {TEXT("Finnish"), TEXT("fi")}, + {TEXT("French(Canada)"), TEXT("fr-CA")}, + {TEXT("French(France)"), TEXT("fr-FR")}, + {TEXT("German"), TEXT("de")}, + {TEXT("Greek"), TEXT("el")}, + {TEXT("Hebrew"), TEXT("he")}, + {TEXT("Hungarian"), TEXT("hu")}, + {TEXT("Indonesian"), TEXT("id")}, + {TEXT("Italian"), TEXT("it")}, + {TEXT("Japanese"), TEXT("ja")}, + {TEXT("Korean"), TEXT("ko")}, + {TEXT("Norwegian "), TEXT("no")}, + {TEXT("Polish"), TEXT("pl")}, + {TEXT("Portuguese(Brazil)"), TEXT("pt-BR")}, + {TEXT("Portuguese(Portugal)"), TEXT("pt-PT")}, + {TEXT("Romanian"), TEXT("ro")}, + {TEXT("Russian"), TEXT("ru")}, + {TEXT("Slovenian"), TEXT("sl")}, + {TEXT("Spanish(Mexico)"), TEXT("es-MX")}, + {TEXT("Spanish(Spain)"), TEXT("es-ES")}, + {TEXT("Swedish"), TEXT("sv")}, + {TEXT("Thai"), TEXT("th")}, + {TEXT("Turkish"), TEXT("tr")}, + {TEXT("Ukrainian"), TEXT("uk")}, + {TEXT("Vietnamese"), TEXT("vi")}, + }; +} + +struct SettingsRegistrationStruct +{ + SettingsRegistrationStruct(UClass* SettingsClass, const FName& SectionName, const FText& DisplayName, const FText& Description) + : SettingsClass(SettingsClass), SectionName(SectionName), DisplayName(DisplayName), Description(Description) + {} + + void Register(ISettingsModule* SettingsModule) const + { + SettingsModule->RegisterSettings("Project", "Wwise", SectionName, DisplayName, Description, SettingsObject()); + } + + void Unregister(ISettingsModule* SettingsModule) const + { + SettingsModule->UnregisterSettings("Project", "Wwise", SectionName); + } + +private: + UClass* SettingsClass; + const FName SectionName; + const FText DisplayName; + const FText Description; + + UObject* SettingsObject() const { return SettingsClass->GetDefaultObject(); } +}; + +class FAudiokineticToolsModule : public IAudiokineticTools +{ +public: + /** + * Creates a new WwiseBrowser tab. + * + * @param SpawnTabArgs The arguments for the tab to spawn. + * @return The spawned tab. + */ + TSharedRef CreateWwiseBrowserTab(const FSpawnTabArgs& SpawnTabArgs); + void RefreshWwiseProject() override; + void OpenOnlineHelp(); + static void ToggleVisualizeRoomsAndPortals(); + static bool IsVisualizeRoomsAndPortalsEnabled(); + static ECheckBoxState GetVisualizeRoomsAndPortalsCheckBoxState(); + static void ToggleShowReverbInfo(); + static bool IsReverbInfoEnabled(); + static ECheckBoxState GetReverbInfoCheckBoxState(); + + void CreateAkViewportCommands(); + void RegisterWwiseMenus(); + void UpdateUnrealCultureToWwiseCultureMap(const WwiseProjectInfo& wwiseProjectInfo); + void VerifyGeneratedSoundBanksPath(UAkSettings* AkSettings, UAkSettingsPerUser* AkSettingsPerUser); + void OnAssetRegistryFilesLoaded(); + + virtual void StartupModule() override; + virtual void OnAkAudioInit(); + void OnSoundBanksFolderChanged(); + void BeginPIE(bool bIsSimulating); + + void DisplayGeneratedSoundBanksWarning(); + void AssetReloadPrompt(); + void OpenAssetReloadPopup(); + static void ParseGeneratedSoundBankData(); + virtual void ShutdownModule() override; + static EEditorBuildResult BuildAkEventData(UWorld* world, FName name); + + static FAudiokineticToolsModule* AudiokineticToolsModuleInstance; + + void RefreshAndUpdateTextureParams(); + +private: + static TMap& GetWwisePlatformNameToSettingsRegistrationMap(); + TSet RegisteredSettingsNames; + void RegisterSettings(); + void UnregisterSettings(); + + /** Ak-specific viewport actions */ + FUIAction ToggleVizRoomsPortalsAction; + FUIAction ToggleReverbInfoAction; + + TArray> AkAssetTypeActionsArray; + TSharedPtr MainMenuExtender; + FLevelEditorModule::FLevelEditorMenuExtender LevelViewportToolbarBuildMenuExtenderAk; + FDelegateHandle LevelViewportToolbarBuildMenuExtenderAkHandle; + FDelegateHandle OnAssetRegistryFilesLoadedHandle; + FDelegateHandle RTPCTrackEditorHandle; + FDelegateHandle EventTrackEditorHandle; + + /** Allow to create an AkComponent when Drag & Drop of an AkEvent */ + TSharedPtr AkEventBroker; + + FDoEditorBuildDelegate buildDelegate; + AkAssetMigrationManager AssetMigrationManager; + GeneratedSoundBanksDirectoryWatcher SoundBanksDirectoryWatcher; + FDelegateHandle OnDatabaseUpdateCompleteHandle; + FDelegateHandle OnDatabaseUpdateTextureHandle; + + FReloadPopup ReloadPopup = FReloadPopup(); + FGeneratedSoundBanksWarning GeneratedSoundBanksWarning = FGeneratedSoundBanksWarning(); +}; + +IMPLEMENT_MODULE(FAudiokineticToolsModule, AudiokineticTools); + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Public/IAudiokineticTools.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Public/IAudiokineticTools.h new file mode 100644 index 0000000..94d7554 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Public/IAudiokineticTools.h @@ -0,0 +1,35 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +/*============================================================================= + IAudiokineticTools.h: +=============================================================================*/ +#pragma once + +#include "Modules/ModuleInterface.h" +#include "Stats/Stats.h" + +AUDIOKINETICTOOLS_API DECLARE_LOG_CATEGORY_EXTERN(LogAudiokineticTools, Log, All); + +/** + * The public interface of the AudiokineticTools module + */ +class IAudiokineticTools : public IModuleInterface +{ +public: + virtual void RefreshWwiseProject() {} +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Public/WaapiPlaybackTransport.h b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Public/WaapiPlaybackTransport.h new file mode 100644 index 0000000..faf5231 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/AudiokineticTools/Public/WaapiPlaybackTransport.h @@ -0,0 +1,51 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#include "CoreMinimal.h" +#include "Dom/JsonObject.h" + +class WaapiPlaybackTransport +{ + struct TransportInfo + { + int32 TransportID; + uint64 SubscriptionID; + + TransportInfo(int32 transID, uint64 subsID) : TransportID(transID), SubscriptionID(subsID) {} + }; + +public: + ~WaapiPlaybackTransport(); + + int32 FindOrAdd(const FGuid& InItemID); + void Remove(const FGuid& InItemID); + void TogglePlay(int32 InTransportID); + void Stop(int32 InTransportID); + bool IsPlaying(const FGuid& InItemID); + void StopAndDestroyAll(); + +private: + uint64 SubscribeToStateChanged(int32 TransportId); + + void OnStateChanged(TSharedPtr InUEJsonObject); + + /** Remember the played items. Useful to play/stop and event. */ + TMap TransportItems; + FCriticalSection TransportItemsLock; + +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/Wwise/Private/Wwise/Stats/Wwise.cpp b/Pawn_Unreal/Plugins/Wwise/Source/Wwise/Private/Wwise/Stats/Wwise.cpp new file mode 100644 index 0000000..b660453 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/Wwise/Private/Wwise/Stats/Wwise.cpp @@ -0,0 +1,20 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Stats/Wwise.h" + +DEFINE_LOG_CATEGORY(LogWwise); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/Wwise/Private/Wwise/WwiseModule.cpp b/Pawn_Unreal/Plugins/Wwise/Source/Wwise/Private/Wwise/WwiseModule.cpp new file mode 100644 index 0000000..d7105d0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/Wwise/Private/Wwise/WwiseModule.cpp @@ -0,0 +1,97 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Modules/ModuleManager.h" +#include "Wwise/Stats/Wwise.h" + +#if !UE_SERVER +#include "Misc/CoreDelegates.h" +#include "Misc/ConfigCacheIni.h" +#include "WwiseDefines.h" + +#if UE_5_1_OR_LATER +#include "Wwise/WwiseAudioLinkRuntimeModule.h" +#if WITH_EDITOR +#include "Wwise/WwiseAudioLinkEditorModule.h" +#endif +#endif +#endif + +class FWwiseModule : public IModuleInterface +{ +#if !UE_SERVER + +#if UE_5_1_OR_LATER + TUniquePtr WwiseAudioLinkRuntimeModule; +#if WITH_EDITOR + TUniquePtr WwiseAudioLinkEditorModule; +#endif +#endif + +public: + virtual void StartupModule() override + { + { + SCOPED_WWISE_EVENT(TEXT("StartupModule: AkAudio")); + UE_LOG(LogWwise, Log, TEXT("WwiseModule: Loading AkAudio")); + FModuleManager::Get().LoadModule(TEXT("AkAudio")); + FCoreDelegates::OnPostEngineInit.AddRaw(this, &FWwiseModule::OnPostEngineInit); + } + + // Loading optional modules + bool bAkAudioMixerEnabled = false; + GConfig->GetBool(TEXT("/Script/AkAudio.AkSettings"), TEXT("bAkAudioMixerEnabled"), bAkAudioMixerEnabled, GGameIni); + if (bAkAudioMixerEnabled) + { + SCOPED_WWISE_EVENT(TEXT("StartupModule: AkAudioMixer")); + UE_LOG(LogWwise, Log, TEXT("WwiseModule: Loading AkAudioMixer")); + FModuleManager::Get().LoadModule(TEXT("AkAudioMixer")); + } + + bool bWwiseAudioLinkEnabled = false; + GConfig->GetBool(TEXT("/Script/AkAudio.AkSettings"), TEXT("bWwiseAudioLinkEnabled"), bWwiseAudioLinkEnabled, GGameIni); + if (bWwiseAudioLinkEnabled) + { +#if UE_5_1_OR_LATER + { + SCOPED_WWISE_EVENT(TEXT("StartupModule: WwiseAudioLink Runtime")); + UE_LOG(LogWwise, Log, TEXT("WwiseModule: Loading WwiseAudioLink")); + WwiseAudioLinkRuntimeModule = MakeUnique(); + } +#if WITH_EDITOR + { + SCOPED_WWISE_EVENT(TEXT("StartupModule: WwiseAudioLink Editor")); + WwiseAudioLinkEditorModule = MakeUnique(); + } +#endif +#else + UE_LOG(LogWwise, Error, TEXT("WwiseModule: AudioLink is not available in Unreal versions prior to 5.1. Ignoring.")); +#endif + } + } + + void OnPostEngineInit() + { + SCOPED_WWISE_EVENT(TEXT("OnPostEngineInit")); +#if WITH_EDITOR + { + SCOPED_WWISE_EVENT(TEXT("OnPostEngineInit: AudiokineticTools")); + UE_LOG(LogWwise, Log, TEXT("WwiseModule: Loading AudiokineticTools")); + FModuleManager::Get().LoadModule(TEXT("AudiokineticTools")); + } +#endif + } + + virtual void ShutdownModule() override + { +#if UE_5_1_OR_LATER +#if WITH_EDITOR + WwiseAudioLinkEditorModule.Reset(); +#endif + WwiseAudioLinkRuntimeModule.Reset(); +#endif + } + +#endif +}; + +IMPLEMENT_MODULE(FWwiseModule, Wwise); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/Wwise/Public/Wwise/Stats/Wwise.h b/Pawn_Unreal/Plugins/Wwise/Source/Wwise/Public/Wwise/Stats/Wwise.h new file mode 100644 index 0000000..ec7af16 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/Wwise/Public/Wwise/Stats/Wwise.h @@ -0,0 +1,25 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Stats/NamedEvents.h" + +WWISE_API DECLARE_LOG_CATEGORY_EXTERN(LogWwise, Log, All); + +#define SCOPED_WWISE_EVENT(Text) SCOPED_WWISE_NAMED_EVENT(TEXT("Wwise"), Text) +#define SCOPED_WWISE_EVENT_F(Format, ...) SCOPED_WWISE_NAMED_EVENT_F(TEXT("Wwise"), Format, __VA_ARGS__) diff --git a/Pawn_Unreal/Plugins/Wwise/Source/Wwise/Wwise.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/Wwise/Wwise.Build.cs new file mode 100644 index 0000000..10c65ae --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/Wwise/Wwise.Build.cs @@ -0,0 +1,70 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using System.IO; +using UnrealBuildTool; + +public class Wwise : ModuleRules +{ + public Wwise(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + WwiseHelper.AddDependencies(this, Target); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine" + } + ); + + + if (Target.bBuildEditor) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "UnrealEd", + "AudiokineticTools" + } + ); + } + + WwiseAudioLinkEditor.Apply(this, Target); + WwiseAudioLinkRuntime.Apply(this, Target); + } + + public void AddOptionalModule(string Module, bool AddPublic = true) + { +#if UE_5_0_OR_LATER + ConditionalAddModuleDirectory( + EpicGames.Core.DirectoryReference.Combine(new EpicGames.Core.DirectoryReference(ModuleDirectory), "..", Module)); +#else + ConditionalAddModuleDirectory( + Tools.DotNETCommon.DirectoryReference.Combine(new Tools.DotNETCommon.DirectoryReference(ModuleDirectory), "..", Module)); +#endif + ExternalDependencies.Add(Path.Combine(ModuleDirectory, "..", Module, Module + "_OptionalModule.Build.cs")); + if (AddPublic) + { + PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "..", Module, "Public")); + } + PrivateIncludePaths.Add(Path.Combine(ModuleDirectory, "..", Module, "Private")); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/Wwise/WwiseHelper.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/Wwise/WwiseHelper.Build.cs new file mode 100644 index 0000000..6f54fef --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/Wwise/WwiseHelper.Build.cs @@ -0,0 +1,42 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; + +public struct WwiseHelper +{ + public static void AddDependencies(ModuleRules Module, ReadOnlyTargetRules Target) + { + Module.PublicDependencyModuleNames.AddRange( + new string[] + { + "AkAudio", + "WwiseSoundEngine" + } + ); + + // Optional modules + Module.PrivateDependencyModuleNames.AddRange( + new string[] + { + "AkAudioMixer", + "WwiseConcurrency", + "WwiseSimpleExternalSource" + } + ); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkEditor/Private/Wwise/AudioLink/WwiseAudioLinkSettingsFactory.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkEditor/Private/Wwise/AudioLink/WwiseAudioLinkSettingsFactory.cpp new file mode 100644 index 0000000..8ef4085 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkEditor/Private/Wwise/AudioLink/WwiseAudioLinkSettingsFactory.cpp @@ -0,0 +1,76 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/AudioLink/WwiseAudioLinkSettingsFactory.h" +#include "Wwise/AudioLink/WwiseAudioLinkSettings.h" +#include "AssetTypeCategories.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Object.h" + +#define LOCTEXT_NAMESPACE "AssetTypeActions" + +FText FAssetTypeActions_WwiseAudioLinkSettings::GetName() const +{ + return LOCTEXT("AssetTypeActions_WwiseAudioLinkSettings", "Wwise AudioLink Settings"); +} + +FColor FAssetTypeActions_WwiseAudioLinkSettings::GetTypeColor() const +{ + return FColor(100, 100, 100); +} + +const TArray& FAssetTypeActions_WwiseAudioLinkSettings::GetSubMenus() const +{ + static const TArray SubMenus + { + LOCTEXT("AssetAudioLinkSubMenu", "AudioLink") + }; + + return SubMenus; +} + +UClass* FAssetTypeActions_WwiseAudioLinkSettings::GetSupportedClass() const +{ + return UWwiseAudioLinkSettings::StaticClass(); +} + +uint32 FAssetTypeActions_WwiseAudioLinkSettings::GetCategories() +{ + return EAssetTypeCategories::Sounds; +} + +UWwiseAudioLinkSettingsFactory::UWwiseAudioLinkSettingsFactory(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + SupportedClass = UWwiseAudioLinkSettings::StaticClass(); + + bCreateNew = true; + bEditorImport = true; + bEditAfterNew = true; +} + +UObject* UWwiseAudioLinkSettingsFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, + FFeedbackContext* Warn) +{ + return NewObject(InParent, Name, Flags); +} + +uint32 UWwiseAudioLinkSettingsFactory::GetMenuCategories() const +{ + return EAssetTypeCategories::Sounds; +} +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkEditor/Private/Wwise/AudioLink/WwiseAudioLinkSettingsFactory.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkEditor/Private/Wwise/AudioLink/WwiseAudioLinkSettingsFactory.h new file mode 100644 index 0000000..0ef8ab3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkEditor/Private/Wwise/AudioLink/WwiseAudioLinkSettingsFactory.h @@ -0,0 +1,46 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Factories/Factory.h" +#include "AssetTypeActions_Base.h" +#include "AssetToolsModule.h" + +#include "WwiseAudioLinkSettingsFactory.generated.h" + +class FAssetTypeActions_WwiseAudioLinkSettings : public FAssetTypeActions_Base +{ +public: + virtual FText GetName() const override; + virtual FColor GetTypeColor() const override; + virtual const TArray& GetSubMenus() const override; + virtual UClass* GetSupportedClass() const override; + virtual uint32 GetCategories() override; +}; + +UCLASS(hidecategories = Object, MinimalAPI) +class UWwiseAudioLinkSettingsFactory : public UFactory +{ + GENERATED_UCLASS_BODY() + + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, + FFeedbackContext* Warn) override; + + virtual uint32 GetMenuCategories() const override; +}; + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkEditor/Private/Wwise/WwiseAudioLinkEditorModule.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkEditor/Private/Wwise/WwiseAudioLinkEditorModule.h new file mode 100644 index 0000000..f85e439 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkEditor/Private/Wwise/WwiseAudioLinkEditorModule.h @@ -0,0 +1,52 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/AudioLink/WwiseAudioLinkSettingsFactory.h" +#include "Wwise/AudioLink/WwiseAudioLinkSettings.h" + +#include "ISettingsModule.h" + +class FWwiseAudioLinkEditorModule +{ +public: + FWwiseAudioLinkEditorModule() + { + // Register asset types + IAssetTools& AssetTools = FAssetToolsModule::GetModule().Get(); + { + AssetActions = MakeShared(); + AssetTools.RegisterAssetTypeActions(AssetActions.ToSharedRef()); + + if (ISettingsModule* SettingsModule = FModuleManager::Get().GetModulePtr("Settings")) + { + SettingsModule->RegisterSettings("Project", "Wwise", "Wwise AudioLink", NSLOCTEXT("WwiseAudioLink", "Wwise AudioLink", "Wwise AudioLink"), + NSLOCTEXT("WwiseAudioLink", "Configure AudioLink for Wwise Settings", "Configure AudioLink for Wwise Settings"), GetMutableDefault()); + } + } + } + ~FWwiseAudioLinkEditorModule() + { + if (FAssetToolsModule::IsModuleLoaded()) + { + IAssetTools& AssetTools = FAssetToolsModule::GetModule().Get(); + AssetTools.UnregisterAssetTypeActions(AssetActions.ToSharedRef()); + } + AssetActions.Reset(); + } +private: + TSharedPtr AssetActions; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkEditor/WwiseAudioLinkEditor_OptionalModule.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkEditor/WwiseAudioLinkEditor_OptionalModule.Build.cs new file mode 100644 index 0000000..cd6d4dc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkEditor/WwiseAudioLinkEditor_OptionalModule.Build.cs @@ -0,0 +1,31 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; + +public struct WwiseAudioLinkEditor +{ + public static void Apply(Wwise WwiseModule, ReadOnlyTargetRules Target) + { +#if UE_5_1_OR_LATER + if (Target.bBuildEditor) + { + WwiseModule.AddOptionalModule("WwiseAudioLinkEditor", false); + } +#endif + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLink.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLink.h new file mode 100644 index 0000000..c6f26d1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLink.h @@ -0,0 +1,42 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "IAudioLink.h" +#include "IBufferedAudioOutput.h" +#include "Wwise/AudioLink/WwiseAudioLinkInputClient.h" + +struct FWwiseAudioLink : IAudioLink +{ + FSharedBufferedOutputPtr ProducerSP; // <- Circular buffer (submix/source) that listening for new buffers from unreal. + FSharedWwiseAudioLinkInputClientPtr ConsumerSP; // <- Wwise input client + + FWwiseAudioLink(const FSharedBufferedOutputPtr& InProducerSP, const FSharedWwiseAudioLinkInputClientPtr& InConsumerSP) + : ProducerSP(InProducerSP) + , ConsumerSP(InConsumerSP) + {} + + virtual ~FWwiseAudioLink() override + { + if (ConsumerSP.IsValid()) + { + ConsumerSP->Stop(); + } + } +}; + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkComponent.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkComponent.cpp new file mode 100644 index 0000000..3b6de72 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkComponent.cpp @@ -0,0 +1,146 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/AudioLink/WwiseAudioLinkComponent.h" +#include "Wwise/AudioLink/WwiseAudioLinkFactory.h" +#include "Components/AudioComponent.h" + +void UWwiseAudioLinkComponent::CreateLink() +{ + if (!Settings) + { + Settings = GetMutableDefault(); + } + + IAudioLinkFactory* Factory = IAudioLinkFactory::FindFactory(FWwiseAudioLinkFactory::GetFactoryNameStatic()); + if (ensure(Factory)) + { + IAudioLinkFactory::FAudioLinkSourceCreateArgs CreateArgs; + CreateArgs.OwningComponent = this; + CreateArgs.AudioComponent = AudioComponent; + CreateArgs.Settings = Settings; + LinkInstance = Factory->CreateSourceAudioLink(CreateArgs); + } +} + +void UWwiseAudioLinkComponent::CreateAudioComponent() +{ + if (!AudioComponent) + { + // Create the audio component which will be used to play the procedural sound wave + AudioComponent = NewObject(this); + + if (!AudioComponent->GetAttachParent() && !AudioComponent->IsAttachedTo(this)) + { + AActor* Owner = GetOwner(); + + // If the media component has no owner or the owner doesn't have a world + if (!Owner || !Owner->GetWorld()) + { + // Attempt to retrieve the component's world and register the audio component with it + // This ensures that the component plays on the correct world in cases where there isn't an owner + if (UWorld* World = GetWorld()) + { + AudioComponent->RegisterComponentWithWorld(World); + AudioComponent->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + } + else + { + AudioComponent->SetupAttachment(this); + } + } + else + { + AudioComponent->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + AudioComponent->RegisterComponent(); + } + } + } + + if (AudioComponent) + { + AudioComponent->bAutoActivate = false; + AudioComponent->bStopWhenOwnerDestroyed = true; + AudioComponent->bShouldRemainActiveIfDropped = true; + AudioComponent->Mobility = EComponentMobility::Movable; + +#if WITH_EDITORONLY_DATA + AudioComponent->bVisualizeComponent = false; +#endif + } +} + +void UWwiseAudioLinkComponent::OnRegister() +{ + Super::OnRegister(); + + CreateAudioComponent(); + + if (ensure(AudioComponent)) + { + check(LinkInstance == nullptr); + CreateLink(); + } +} + +void UWwiseAudioLinkComponent::OnUnregister() +{ + LinkInstance.Reset(); + AudioComponent = nullptr; + + Super::OnUnregister(); +} + +void UWwiseAudioLinkComponent::SetLinkSound(USoundBase* InSound) +{ + Sound = InSound; + + if (AudioComponent) + { + AudioComponent->SetSound(InSound); + } +} + +void UWwiseAudioLinkComponent::PlayLink(float StartTime) +{ + if (AudioComponent) + { + // Set the audio component's sound to be our procedural sound wave + AudioComponent->SetSound(Sound); + AudioComponent->Play(StartTime); + + SetActiveFlag(AudioComponent->IsActive()); + } +} + +void UWwiseAudioLinkComponent::StopLink() +{ + if (IsActive()) + { + if (AudioComponent) + { + AudioComponent->Stop(); + } + + SetActiveFlag(false); + } +} + +bool UWwiseAudioLinkComponent::IsLinkPlaying() const +{ + return AudioComponent && AudioComponent->IsPlaying(); +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkFactory.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkFactory.cpp new file mode 100644 index 0000000..b1d4045 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkFactory.cpp @@ -0,0 +1,276 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/AudioLink/WwiseAudioLinkFactory.h" + +#include "Wwise/AudioLink/WwiseAudioLink.h" +#include "Wwise/AudioLink/WwiseAudioLinkInputClient.h" +#include "Wwise/AudioLink/WwiseAudioLinkSettings.h" +#include "Wwise/AudioLink/WwiseAudioLinkComponent.h" +#include "Wwise/AudioLink/WwiseAudioLinkSourcePushed.h" +#include "Wwise/AudioLink/WwiseAudioLinkSynchronizer.h" + +#include "Wwise/Stats/AudioLink.h" + +#include "AkAudioModule.h" +#include "AkAudioInputManager.h" +#include "AkAudioEvent.h" +#include "Wwise/WwiseSoundEngineModule.h" +#include "Wwise/API/WwiseSoundEngineAPI.h" + +#include "Async/Async.h" +#include "Components/AudioComponent.h" +#include "Sound/SoundSubmix.h" +#include "Templates/SharedPointer.h" + +#include + +bool FWwiseAudioLinkFactory::bHasSubmix = false; + +bool FWwiseAudioLinkFactory::IsWwiseInit() +{ + if (IWwiseSoundEngineModule::IsAvailable()) + { + IWwiseSoundEngineAPI* WwiseLLSE = IWwiseSoundEngineModule::SoundEngine; + if (WwiseLLSE) + { + return WwiseLLSE->IsInitialized(); + } + } + return false; +} + +TUniquePtr FWwiseAudioLinkFactory::CreateSubmixAudioLink(const FAudioLinkSubmixCreateArgs& InArgs) +{ + if (!IAkAudioModule::IsAvailable()) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkFactory::CreateSubmixAudioLink: No AkAudio module.")); + return {}; + } + if (!IsWwiseInit()) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkFactory::CreateSubmixAudioLink: AkAudio not initialized.")); + return {}; + } + + if (!InArgs.Settings.IsValid()) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkFactory::CreateSubmixAudioLink: Invalid WwiseAudioLinkSettings.")); + return {}; + } + + if (!InArgs.Submix.IsValid()) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkFactory::CreateSubmixAudioLink: Invalid Submix.")); + return {}; + } + + bHasSubmix = true; + + UE_LOG(LogWwiseAudioLink, Verbose, TEXT("FWwiseAudioLinkFactory: Creating AudioLink %s for Submix %s."), *InArgs.Settings->GetName(), *InArgs.Submix->GetName()); + + // Downcast to Wwise Settings Proxy. + const FSharedAudioLinkSettingProxyWwisePtr WwiseSettingsSP = InArgs.Settings->GetCastProxy(); + + // This is a best guess, as we don't know the format yet, so assume mono. + const int32 DefaultSizeOfBuffer = WwiseSettingsSP->GetReceivingBufferSizeInFrames(); + + // Make buffer listener first, which is our producer. + FSubmixBufferListenerCreateParams SubmixListenerCreateArgs; + SubmixListenerCreateArgs.SizeOfBufferInFrames = DefaultSizeOfBuffer; + SubmixListenerCreateArgs.bShouldZeroBuffer = WwiseSettingsSP->ShouldClearBufferOnReceipt(); + FSharedBufferedOutputPtr ProducerSP = CreateSubmixBufferListener(SubmixListenerCreateArgs); + + // Create Wwise input client, which is the consumer in the link. + // This take a Weak Reference to the Producer. + FSharedWwiseAudioLinkInputClientPtr ConsumerSP = MakeShared( + ProducerSP, InArgs.Settings->GetProxy(), InArgs.Submix->GetFName()); + + // Setup a delegate to establish the link when we know the format. + ProducerSP->SetFormatKnownDelegate(IBufferedAudioOutput::FOnFormatKnown::CreateLambda( + [ProducerSP, ConsumerSP, WwiseSettingsSP](const IBufferedAudioOutput::FBufferFormat& InFormat) + { + int32 BufferSizeInSamples = WwiseSettingsSP->GetReceivingBufferSizeInFrames() * InFormat.NumChannels; + int32 ReserveSizeInSamples = (float)BufferSizeInSamples * WwiseSettingsSP->GetProducerConsumerBufferRatio(); + int32 SilenceToAddToFirstBuffer = FMath::Min((float)BufferSizeInSamples * WwiseSettingsSP->GetInitialSilenceFillRatio(), ReserveSizeInSamples); + + // Set circular buffer ahead of first buffer. + ProducerSP->Reserve(ReserveSizeInSamples, SilenceToAddToFirstBuffer); + + ConsumerSP->Stop(); + + UE_LOG(LogWwiseAudioLink, VeryVerbose, TEXT("FWwiseAudioLinkFactory:CreateSubmixAudioLink: Starting Wwise consumer.")); + ConsumerSP->Start(); + })); + + UE_LOG(LogWwiseAudioLink, VeryVerbose, TEXT("FWwiseAudioLinkFactory:CreateSubmixAudioLink: Starting Unreal producer.")); + ProducerSP->Start(InArgs.Device); + + return MakeUnique(ProducerSP, ConsumerSP); +} + +TUniquePtr FWwiseAudioLinkFactory::CreateSourceAudioLink(const FAudioLinkSourceCreateArgs& InArgs) +{ + if (!IAkAudioModule::IsAvailable()) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkFactory::CreateSourceAudioLink: No AkAudio module.")); + return {}; + } + if (!IsWwiseInit()) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkFactory::CreateSourceAudioLink: AkAudio not initialized.")); + return {}; + } + + if (!FAkAudioDevice::Get()) + { + UE_LOG(LogWwiseAudioLink, Warning, TEXT("FWwiseAudioLinkFactory::CreateSourceAudioLink: No AkAudioDevice available.")); + return {}; + } + + if (!InArgs.Settings.IsValid()) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkFactory::CreateSourceAudioLink: Invalid WwiseAudioLinkSettings.")); + return {}; + } + + if (!InArgs.OwningComponent.IsValid()) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkFactory::CreateSourceAudioLink: Invalid Owning Component.")); + return {}; + } + + if (!InArgs.AudioComponent.IsValid()) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkFactory::CreateSourceAudioLink: Invalid Audio Component.")); + return {}; + } + + const UWorld* World = InArgs.OwningComponent->GetWorld(); + if (UNLIKELY(!IsValid(World))) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkFactory::CreateSourceAudioLink: Invalid World in Owning Component.")); + return {}; + } + + const FAudioDeviceHandle Handle = World->GetAudioDevice(); + if (UNLIKELY(!Handle.IsValid())) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkFactory::CreateSourceAudioLink: Invalid AudioDevice in World.")); + return {}; + } + + UE_LOG(LogWwiseAudioLink, Verbose, TEXT("FWwiseAudioLinkFactory: Creating AudioLink %s for Audio Compoment %s (Owning=%s; World=%s; AudioDeviceID=%" PRIu32 ")"), + *InArgs.Settings->GetName(), *InArgs.AudioComponent->GetName(), *InArgs.OwningComponent->GetName(), *World->GetName(), Handle.GetDeviceID()); + + // Downcast to Wwise Settings Proxy. + const FSharedAudioLinkSettingProxyWwisePtr WwiseSettingsSP = InArgs.Settings->GetCastProxy(); + + // This is a best guess, as we don't know the format yet, so assume mono. + const int32 DefaultSizeOfBuffer = WwiseSettingsSP->GetReceivingBufferSizeInFrames(); + + // Create the Unreal Source Listener, this is our Producer. + FSourceBufferListenerCreateParams SourceBufferCreateArgs; + SourceBufferCreateArgs.SizeOfBufferInFrames = DefaultSizeOfBuffer; + SourceBufferCreateArgs.bShouldZeroBuffer = WwiseSettingsSP->ShouldClearBufferOnReceipt(); + SourceBufferCreateArgs.OwningComponent = InArgs.OwningComponent; + SourceBufferCreateArgs.AudioComponent = InArgs.AudioComponent; + FSharedBufferedOutputPtr ProducerSP = CreateSourceBufferListener(SourceBufferCreateArgs); + + static const FName UnknownOwner(TEXT("Unknown")); + FName OwnerName = InArgs.OwningComponent.IsValid() ? InArgs.OwningComponent->GetFName() : UnknownOwner; + + // Create the Wwise input object that will start receiving buffers from us. + auto ConsumerSP = MakeShared(ProducerSP, WwiseSettingsSP, OwnerName); + + ProducerSP->SetFormatKnownDelegate(IBufferedAudioOutput::FOnFormatKnown::CreateLambda( + [ProducerSP, ConsumerSP, WwiseSettingsSP, OwningComponent = InArgs.OwningComponent](const IBufferedAudioOutput::FBufferFormat& InFormat) + { + const int32 BufferSizeInSamples = WwiseSettingsSP->GetReceivingBufferSizeInFrames() * InFormat.NumChannels; + const int32 ReserveSizeInSamples = (float)BufferSizeInSamples * WwiseSettingsSP->GetProducerConsumerBufferRatio(); + const int32 SilenceToAddToFirstBuffer = FMath::Min((float)BufferSizeInSamples * WwiseSettingsSP->GetInitialSilenceFillRatio(), ReserveSizeInSamples); + + // Set circular buffer ahead of first buffer. + ProducerSP->Reserve(ReserveSizeInSamples, SilenceToAddToFirstBuffer); + + AsyncTask(ENamedThreads::GameThread, [ConsumerSP,OwningComponent]() + { + if (OwningComponent.IsValid()) + { + ConsumerSP->Stop(); + + UE_LOG(LogWwiseAudioLink, VeryVerbose, TEXT("FWwiseAudioLinkFactory:CreateSourceAudioLink: Starting Wwise consumer.")); + ConsumerSP->Start(Cast(OwningComponent.Get())); + } + }); + })); + + ProducerSP->SetBufferStreamEndDelegate(IBufferedAudioOutput::FOnBufferStreamEnd::CreateLambda( + [ConsumerSP](const IBufferedAudioOutput::FBufferStreamEnd& InBufferStreamEndParams) + { + UE_LOG(LogWwiseAudioLink, VeryVerbose, TEXT("FWwiseAudioLinkFactory:CreateSourceAudioLink: Stopping Wwise consumer.")); + ConsumerSP->Stop(); + })); + + UE_LOG(LogWwiseAudioLink, VeryVerbose, TEXT("FWwiseAudioLinkFactory:CreateSourceAudioLink: Starting Unreal producer.")); + ProducerSP->Start(Handle.GetAudioDevice()); + + return MakeUnique(ProducerSP, ConsumerSP); +} + +FName FWwiseAudioLinkFactory::GetFactoryNameStatic() +{ + static const FName FactoryName(TEXT("Wwise")); + return FactoryName; +} + +IAudioLinkFactory::FAudioLinkSynchronizerSharedPtr FWwiseAudioLinkFactory::CreateSynchronizerAudioLink() +{ + UE_LOG(LogWwiseAudioLink, VeryVerbose, TEXT("FWwiseAudioLinkFactory: Creating AudioLink Synchronizer")); + + auto SynchronizerSP = MakeShared(); + SynchronizerSP->Bind(); + return SynchronizerSP; +} + +IAudioLinkFactory::FAudioLinkSourcePushedSharedPtr FWwiseAudioLinkFactory::CreateSourcePushedAudioLink(const FAudioLinkSourcePushedCreateArgs& InArgs) +{ + if (!IAkAudioModule::IsAvailable()) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkFactory::CreateSourcePushedAudioLink: No AkAudio module.")); + return {}; + } + if (!IsWwiseInit()) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkFactory::CreateSourcePushedAudioLink: AkAudio not initialized.")); + return {}; + } + + UE_LOG(LogWwiseAudioLink, VeryVerbose, TEXT("FWwiseAudioLinkFactory: Creating AudioLink SourcePushed")); + + return MakeShared(InArgs,this); +} + +FName FWwiseAudioLinkFactory::GetFactoryName() const +{ + return GetFactoryNameStatic(); +} + +TSubclassOf FWwiseAudioLinkFactory::GetSettingsClass() const +{ + return UWwiseAudioLinkSettings::StaticClass(); +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkFactory.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkFactory.h new file mode 100644 index 0000000..31ef5f1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkFactory.h @@ -0,0 +1,43 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "IAudioLink.h" +#include "IAudioLinkFactory.h" +#include "Templates/SharedPointer.h" + +class FWwiseAudioLinkFactory : public IAudioLinkFactory +{ +public: + FWwiseAudioLinkFactory() = default; + virtual ~FWwiseAudioLinkFactory() override = default; + + static FName GetFactoryNameStatic(); + static bool bHasSubmix; + +protected: + static bool IsWwiseInit(); + + // IAudioLinkFactory + FName GetFactoryName() const override; + TSubclassOf GetSettingsClass() const override; + TUniquePtr CreateSubmixAudioLink(const FAudioLinkSubmixCreateArgs&) override; + TUniquePtr CreateSourceAudioLink(const FAudioLinkSourceCreateArgs&) override; + FAudioLinkSourcePushedSharedPtr CreateSourcePushedAudioLink(const FAudioLinkSourcePushedCreateArgs&) override; + FAudioLinkSynchronizerSharedPtr CreateSynchronizerAudioLink() override; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkInputClient.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkInputClient.cpp new file mode 100644 index 0000000..9371d43 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkInputClient.cpp @@ -0,0 +1,488 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/AudioLink/WwiseAudioLinkInputClient.h" +#include "Wwise/AudioLink/WwiseAudioLinkSettings.h" +#include "Wwise/AudioLink/WwiseAudioLinkComponent.h" +#include "Wwise/AudioLink/WwiseAudioLinkFactory.h" +#include "Wwise/AudioLink/WwiseAudioLinkSynchronizer.h" + +#include "AkAudioInputManager.h" +#include "AkAudioEvent.h" +#include "AkAudioDevice.h" +#include "AkUnrealHelper.h" +#include "Wwise/API/WwiseSoundEngineAPI.h" +#include "Wwise/Stats/AudioLink.h" +#include "Wwise/Stats/Global.h" + +#include "AudioDevice.h" +#include "Async/Async.h" +#include "HAL/PlatformMisc.h" + +#include + +FWwiseAudioLinkInputClient::FWwiseAudioLinkInputClient( + const FSharedBufferedOutputPtr& InToConsumeFrom, + const UAudioLinkSettingsAbstract::FSharedSettingsProxyPtr& InSettingsProxy, + FName InNameOfProducingSource ) + : WeakProducer{ InToConsumeFrom } + , SettingsProxy{ InSettingsProxy } + , ProducerName{ InNameOfProducingSource } +{ + Register(InNameOfProducingSource); +} + +FWwiseAudioLinkInputClient::~FWwiseAudioLinkInputClient() +{ + Unregister(); +} + +void FWwiseAudioLinkInputClient::Start(UWwiseAudioLinkComponent* InAkComponent) +{ + SCOPED_NAMED_EVENT(WwiseAudioLink_StartComponent, FColor::Red); + check(IsInGameThread()); + + if (UNLIKELY(ObjectId == AK_INVALID_AUDIO_OBJECT_ID)) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkInputClient::Start: Unregistered Object (this=%" PRIu64 " %s)"), this, *ProducerName.ToString()); + return; + } + + if (UNLIKELY(PlayId != AK_INVALID_PLAYING_ID)) + { + UE_LOG(LogWwiseAudioLink, Log, TEXT("FWwiseAudioLinkInputClient::Start: Reusing an already playing %" PRIu32 " ObjectID %" PRIu64 " (%s). Stopping previous instance."), PlayId.load(), this, *ProducerName.ToString()); + Stop(); + } + + if (UNLIKELY(!IsValid(InAkComponent))) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkInputClient::Start: Invalid component (this=%" PRIu64 " %s)"), this, *ProducerName.ToString()); + return; + } + + FWwiseAudioLinkSettingsProxy* Settings = static_cast(SettingsProxy.Get()); + if (UNLIKELY(!Settings)) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkInputClient::Start: Invalid Settings (this=%" PRIu64 " %s)"), this, *ProducerName.ToString()); + return; + } + + const auto StartEvent = Settings->GetStartEvent(); // Might not be loaded at this point, only the pointer should be valid. + + IsLoadedHandle = Settings->CallOnEventLoaded([this, StartEvent, InAkComponent]() mutable + { + if (UNLIKELY(!IsValid(InAkComponent))) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkInputClient::Start: Invalid component (this=%" PRIu64 " %s)"), this, *ProducerName.ToString()); + return; + } + + if (UNLIKELY(PlayId != AK_INVALID_PLAYING_ID)) + { + UE_LOG(LogWwiseAudioLink, Log, TEXT("FWwiseAudioLinkInputClient::Start (OnEventLoaded): Reusing an already playing %" PRIu32 " Component %" PRIu64 " (%s). Stopping previous instance."), PlayId.load(), InAkComponent->GetAkGameObjectID(), *InAkComponent->GetName()); + Stop(); + } + + const auto SelfSP = AsShared(); + + PlayId = FAkAudioInputManager::PostAudioInputEvent( + StartEvent.Get(), + InAkComponent, + FAkGlobalAudioInputDelegate::CreateSP(SelfSP, &FWwiseAudioLinkInputClient::GetSamples), + FAkGlobalAudioFormatDelegate::CreateSP(SelfSP, &FWwiseAudioLinkInputClient::GetFormat) + ); + UE_CLOG(UNLIKELY(PlayId.load() == AK_INVALID_PLAYING_ID), LogWwiseAudioLink, Error, + TEXT("FWwiseAudioLinkInputClient::Start: Error playing Component %" PRIu64 " (%s) in client %" PRIu64 " (%s)"), + InAkComponent->GetAkGameObjectID(), *InAkComponent->GetName(), ObjectId, *ProducerName.ToString()); + UE_CLOG(LIKELY(PlayId.load() != AK_INVALID_PLAYING_ID), LogWwiseAudioLink, Verbose, + TEXT("FWwiseAudioLinkInputClient::Start: Playing %" PRIu32 " Component %" PRIu64 " (%s) in client %" PRIu64 " (%s)"), + PlayId.load(), InAkComponent->GetAkGameObjectID(), *InAkComponent->GetName(), ObjectId, *ProducerName.ToString()); + }); +} + +void FWwiseAudioLinkInputClient::Start() +{ + SCOPED_NAMED_EVENT(WwiseAudioLink_Start, FColor::Red); + + if (UNLIKELY(ObjectId == AK_INVALID_AUDIO_OBJECT_ID)) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkInputClient::Start: Unregistered Object (this=%" PRIu64 " %s)"), this, *ProducerName.ToString()); + return; + } + + if (UNLIKELY(PlayId != AK_INVALID_PLAYING_ID)) + { + UE_LOG(LogWwiseAudioLink, Log, TEXT("FWwiseAudioLinkInputClient::Start: Reusing an already playing %" PRIu32 " ObjectID %" PRIu64 " (%s). Stopping previous instance."), PlayId.load(), this, *ProducerName.ToString()); + Stop(); + } + + FWwiseAudioLinkSettingsProxy* Settings = static_cast(SettingsProxy.Get()); + if (UNLIKELY(!Settings)) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkInputClient::Start: Invalid Settings (this=%" PRIu64 " %s)"), this, *ProducerName.ToString()); + return; + } + + const auto StartEvent = Settings->GetStartEvent(); // Might not be loaded at this point, only the pointer should be valid. + + IsLoadedHandle = Settings->CallOnEventLoaded([this, StartEvent]() mutable + { + if (UNLIKELY(!StartEvent || !IsValid(StartEvent.Get()))) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkInputClient::Start: Invalid StartEvent (this=%" PRIu64 " %s)"), this, *ProducerName.ToString()); + return; + } + + if (UNLIKELY(PlayId != AK_INVALID_PLAYING_ID)) + { + UE_LOG(LogWwiseAudioLink, Log, TEXT("FWwiseAudioLinkInputClient::Start (OnEventLoaded): Reusing an already playing %" PRIu32 " ObjectID %" PRIu64 " (%s). Stopping previous instance."), PlayId.load(), this, *ProducerName.ToString()); + Stop(); + } + + const auto SelfSP = AsShared(); + + PlayId = FAkAudioInputManager::PostAudioInputEvent( + StartEvent.Get(), + ObjectId, + FAkGlobalAudioInputDelegate::CreateSP(SelfSP, &FWwiseAudioLinkInputClient::GetSamples), + FAkGlobalAudioFormatDelegate::CreateSP(SelfSP, &FWwiseAudioLinkInputClient::GetFormat) + ); + + UE_CLOG(UNLIKELY(PlayId.load() == AK_INVALID_PLAYING_ID), LogWwiseAudioLink, Error, + TEXT("FWwiseAudioLinkInputClient::Start: Error playing Event '%s' in client %" PRIu64 " (%s)"), + *StartEvent->GetName(), ObjectId, *ProducerName.ToString()); + UE_CLOG(LIKELY(PlayId.load() != AK_INVALID_PLAYING_ID), LogWwiseAudioLink, Verbose, + TEXT("FWwiseAudioLinkInputClient::Start: Playing %" PRIu32 " Event '%s' in client %" PRIu64 " (%s)"), + PlayId.load(), *StartEvent->GetName(), ObjectId, *ProducerName.ToString()); + }); +} + +void FWwiseAudioLinkInputClient::Stop() +{ + SCOPED_NAMED_EVENT(WwiseAudioLink_Stop, FColor::Red); + + if (IsLoadedHandle.IsValid()) + { + FWwiseAudioLinkSettingsProxy* Settings = static_cast(SettingsProxy.Get()); + if (UNLIKELY(!Settings)) + { + UE_LOG(LogWwiseAudioLink, Log, TEXT("FWwiseAudioLinkInputClient::Stop: Invalid Settings (this=%" PRIu64 " %s)"), this, *ProducerName.ToString()); + } + else + { + Settings->UnregisterEventLoadedDelegate(IsLoadedHandle); + } + + IsLoadedHandle.Reset(); + } + + if (PlayId.load() != AK_INVALID_PLAYING_ID) + { + FAkAudioDevice* AudioDevice = FAkAudioDevice::Get(); + if (UNLIKELY(!AudioDevice)) + { + UE_LOG(LogWwiseAudioLink, Log, TEXT("FWwiseAudioLinkInputClient::Stop: Invalid AudioDevice (PlayId=%" PRIu32 ", this=%" PRIu64 " %s)"), PlayId.load(), this, *ProducerName.ToString()); + } + else + { + UE_LOG(LogWwiseAudioLink, Verbose, TEXT("FWwiseAudioLinkInputClient::Stop: PlayId=%" PRIu32 ", ObjectID=%" PRIu64 " %s"), PlayId.load(), ObjectId, *ProducerName.ToString()); + AudioDevice->StopPlayingID(PlayId); + } + + PlayId = AK_INVALID_PLAYING_ID; + } +} + +void FWwiseAudioLinkInputClient::Register(const FName& InNameOfProducingSource) +{ + const auto Name = InNameOfProducingSource.GetPlainNameString(); + + if (UNLIKELY(!SettingsProxy.IsValid())) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkInputClient::Register: Invalid settings registering %" PRIu64 " %s."), ObjectId, this, *Name); + return; + } + + IWwiseSoundEngineAPI* SoundEngine = IWwiseSoundEngineAPI::Get(); + if(UNLIKELY(!SoundEngine)) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkInputClient::Register: No Wwise SoundEngine registering %" PRIu64 " %s."), this, *Name); + return; + } + + if (UNLIKELY(ObjectId != AK_INVALID_AUDIO_OBJECT_ID)) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkInputClient::Register: ObjectId %" PRIu64 " already registered for %" PRIu64 " %s."), ObjectId, this, *Name); + return; + } + ObjectId = reinterpret_cast(this); + + AKRESULT Result; +#ifdef AK_OPTIMIZED + Result = SoundEngine->RegisterGameObj(ObjectId); +#else + if (Name.Len() > 0) + { + Result = SoundEngine->RegisterGameObj(ObjectId, TCHAR_TO_ANSI(*Name)); + } + else + { + Result = SoundEngine->RegisterGameObj(ObjectId); + } +#endif + UE_CLOG(LIKELY(Result == AK_Success), LogWwiseAudioLink, VeryVerbose, TEXT("FWwiseAudioLinkInputClient::Register: Registered Object ID %" PRIu64 " (%s)"), ObjectId, *Name); + UE_CLOG(UNLIKELY(Result != AK_Success), LogWwiseAudioLink, Warning, TEXT("FWwiseAudioLinkInputClient::Register: Error registering Object ID %" PRIu64 " (%s): (%" PRIu32 ") %s"), ObjectId, *Name, Result, AkUnrealHelper::GetResultString(Result)); + + // Sanity checks +#ifndef AK_OPTIMIZED + AsyncTask(ENamedThreads::GameThread, [] + { + const auto AudioDeviceManager = FAudioDeviceManager::Get(); + if (UNLIKELY(!AudioDeviceManager)) + { + UE_LOG(LogWwiseAudioLink, Warning, TEXT("FWwiseAudioLinkInputClient::Register: No AudioDeviceManager at registration.")); + return; + } + const auto AudioDevice = AudioDeviceManager->GetActiveAudioDevice(); + if (UNLIKELY(!AudioDevice)) + { + UE_LOG(LogWwiseAudioLink, Warning, TEXT("FWwiseAudioLinkInputClient::Register: No active AudioDevice at registration.")); + return; + } + + UE_CLOG(UNLIKELY(AudioDevice->GetMaxChannels() == 0), + LogWwiseHints, Warning, TEXT("WwiseAudioLink: The current AudioDevice %" PRIu32 " has 0 MaxChannel. Consider setting AudioMaxChannels to a sensible value in the Engine config file's TargetSettings for your platform."), + AudioDevice->DeviceID); + + UE_CLOG(!FWwiseAudioLinkFactory::bHasSubmix, + LogWwiseHints, Warning, TEXT("WwiseAudioLink: No initial submix got routed to AudioLink. Consider creating custom versions of global submixes in Project Settings Audio, and Enable Audio Link in their advanced settings.")); + }); +#endif +} + +void FWwiseAudioLinkInputClient::Unregister() +{ + IWwiseSoundEngineAPI* SoundEngine = IWwiseSoundEngineAPI::Get(); + if(UNLIKELY(!SoundEngine)) + { + UE_LOG(LogWwiseAudioLink, Log, TEXT("FWwiseAudioLinkInputClient::Unregister: No Wwise SoundEngine unregistering %" PRIu64 " (this=%" PRIu64 " %s)."), ObjectId, this, *ProducerName.ToString()); + return; + } + if (UNLIKELY(ObjectId == AK_INVALID_AUDIO_OBJECT_ID)) + { + UE_LOG(LogWwiseAudioLink, Log, TEXT("FWwiseAudioLinkInputClient::Unregister: Unregistering an unregistered object (this=%" PRIu64 " %s)"), this, *ProducerName.ToString()); + return; + } + + const auto Result = SoundEngine->UnregisterGameObj(ObjectId); + UE_CLOG(LIKELY(Result == AK_Success), LogWwiseAudioLink, VeryVerbose, TEXT("FWwiseAudioLinkInputClient::Unregister: Unregistered Object ID %" PRIu64 " (%s)"), ObjectId, *ProducerName.ToString()); + UE_CLOG(UNLIKELY(Result != AK_Success), LogWwiseAudioLink, Warning, TEXT("FWwiseAudioLinkInputClient::Unregister: Error Unregistering Object ID %" PRIu64 " (%s): (%" PRIu32 ") %s"), ObjectId, *ProducerName.ToString(), Result, AkUnrealHelper::GetResultString(Result)); + ObjectId = AK_INVALID_AUDIO_OBJECT_ID; +} + +/** Called from the Consumer Thread, at Game-tick rate */ +void FWwiseAudioLinkInputClient::UpdateWorldState(const FWorldState& InParams) +{ + IWwiseSoundEngineAPI* SoundEngine = IWwiseSoundEngineAPI::Get(); + if(UNLIKELY(!SoundEngine)) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkInputClient::Register: No Wwise SoundEngine.")); + return; + } + + UE_LOG(LogWwiseAudioLinkLowLevel, VeryVerbose, TEXT("FWwiseAudioLinkInputClient::UpdateWorldState, PlayId=%d, Name=%s, This=0x%p"), + PlayId.load(), *ProducerName.GetPlainNameString(), this ) + + const FTransform& T = InParams.WorldTransform; + AkSoundPosition Pos; + const FQuat& Q = T.GetRotation(); + FAkAudioDevice::FVectorsToAKWorldTransform( + T.GetLocation(), + Q.GetForwardVector(), + Q.GetUpVector(), + Pos + ); + SoundEngine->SetPosition(ObjectId, Pos); +} + +/** Called from the Wwise Renderer Thread */ +bool FWwiseAudioLinkInputClient::GetSamples(uint32 InNumChannels, uint32 InNumFrames, float** InChannelBuffers) +{ + SCOPED_NAMED_EVENT(WwiseAudioLink_GetSamples, FColor::Red); + + FSharedBufferedOutputPtr StrongBufferProducer{ WeakProducer.Pin() }; + if (!StrongBufferProducer.IsValid()) + { + // Return false, to indicate no more data. + return false; + } + + if (UNLIKELY(UnrealFormat.NumChannels == 0)) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkInputClient::GetSamples: UnrealFormat's NumSamples == 0")); + return false; + } + + if (UNLIKELY(UnrealFormat.NumSamplesPerSec == 0)) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkInputClient::GetSamples: UnrealFormat's NumSamplesPerSec == 0")); + return false; + } + + int32 NumFramesPopped = 0; + bool bMoreDataRemaining = false; + + // Always zero the buffer before we start. + for (uint32 Channel = 0; Channel < InNumChannels; ++Channel) + { + FMemory::Memzero(InChannelBuffers[Channel], InNumFrames * sizeof(float)); + } + + if (UnrealFormat.NumChannels == 1) + { + // Pop the data directly onto Wwise buffers. + // Keep record if the Producer has told us there's no more data. + bMoreDataRemaining = StrongBufferProducer->PopBuffer(InChannelBuffers[0], InNumFrames, NumFramesPopped); + } + else + { + // Make sure we have enough space in our temp buffer + int32 NumInterleavedSamplesNeeded = InNumFrames * UnrealFormat.NumChannels; + if (NumInterleavedSamplesNeeded > InterleavedBuffer.Num()) + { + InterleavedBuffer.SetNumUninitialized( + NumInterleavedSamplesNeeded, + true // bAllowShrinking + ); + } + + // Pop the data onto an intermediate buffer for deinterleaving. + // Keep record if the Producer has told us there's no more data. + int32 NumInterleavedSamplesPopped = 0; + bMoreDataRemaining = StrongBufferProducer->PopBuffer(InterleavedBuffer.GetData(), NumInterleavedSamplesNeeded, NumInterleavedSamplesPopped); + NumFramesPopped = NumInterleavedSamplesPopped / UnrealFormat.NumChannels; + + if (NumFramesPopped > 0) + { + // De-interleave. + switch (UnrealFormat.NumChannels) + { + // Stereo. + case 2: + { + Audio::BufferDeinterleave2ChannelFast(InterleavedBuffer.GetData(), InChannelBuffers[0], InChannelBuffers[1], NumFramesPopped); + break; + } + + // Generic version. + default: + { + const float* InterleavedPtr = InterleavedBuffer.GetData(); + for (int32 Frame = 0; Frame < NumFramesPopped; ++Frame) + { + for (int32 Channel = 0; Channel < UnrealFormat.NumChannels; ++Channel) + { + InChannelBuffers[Channel][Frame] = *InterleavedPtr++; + } + } + break; + } + } + } + } + + const int32 UnwrittenFrames = InNumFrames - NumFramesPopped; + + UE_LOG(LogWwiseAudioLinkLowLevel, VeryVerbose, TEXT("FWwiseAudioLinkInputClient::GetSamples() (post-pop), SamplesPopped=%d, SamplesNeeded=%d, UnwrittenFrames=%d, This=0x%p"), + NumFramesPopped * UnrealFormat.NumChannels, InNumFrames * UnrealFormat.NumChannels, UnwrittenFrames, this); + + // We are permissive in Editor (debugging, more elements in single-threads), however, it still is an audio glitch, so we log all of them outside the editor. +#if WITH_EDITOR + static constexpr int32 NumStarvedBuffersBeforeLogging = 10; + static constexpr int32 NumStarvedBuffersBeforeForciblyClosing = 100; +#else + static constexpr int32 NumStarvedBuffersBeforeLogging = 1; + static constexpr int32 NumStarvedBuffersBeforeForciblyClosing = 10; +#endif + + if (UnwrittenFrames > 0) + { + NumStarvedBuffersInARow++; + + UE_CLOG(NumStarvedBuffersInARow == NumStarvedBuffersBeforeLogging, + LogWwiseAudioLink, Log, TEXT("FWwiseAudioLinkInputClient: Starving input object, PlayID=%u, Needed=%d, Read=%d, StarvedCount=%d, This=0x%p"), + PlayId.load(), InNumFrames, NumFramesPopped, NumStarvedBuffersInARow, this); + + if (NumStarvedBuffersInARow == NumStarvedBuffersBeforeForciblyClosing) + { + if (!IsEngineExitRequested()) + { +#if WITH_EDITOR + UE_LOG(LogWwiseAudioLink, Verbose, TEXT("FWwiseAudioLinkInputClient: Forcibly closing defunct AudioLink for object, PlayID=%u, StarvedCount=%d, This=0x%p"), + PlayId.load(), NumStarvedBuffersInARow, this); +#else + UE_LOG(LogWwiseAudioLink, Warning, TEXT("FWwiseAudioLinkInputClient: Forcibly closing defunct AudioLink for object, PlayID=%u, StarvedCount=%d, This=0x%p"), + PlayId.load(), NumStarvedBuffersInARow, this); +#endif + } + bMoreDataRemaining = false; + } + } + else + { + UE_CLOG(NumStarvedBuffersInARow > NumStarvedBuffersBeforeLogging, + LogWwiseAudioLink, Verbose, TEXT("FWwiseAudioLinkInputClient: Got input from previously starving input object, PlayID=%u, Needed=%d, Read=%d, StarvedCount=%d, This=0x%p"), + PlayId.load(), InNumFrames, NumFramesPopped, NumStarvedBuffersInARow, this); + + NumStarvedBuffersInARow = 0; + } + + // Tell Wwise if this is the last buffer which will stop if it is. + return bMoreDataRemaining; +} + +/** Called from the Wwise Renderer Thread */ +void FWwiseAudioLinkInputClient::GetFormat(AkAudioFormat& io_AudioFormat) +{ + SCOPED_NAMED_EVENT(WwiseAudioLink_GetFormat, FColor::Red); + + // Ensure we're still listening to a sub mix that exists. + FSharedBufferedOutputPtr StrongPtr{ WeakProducer.Pin() }; + if (!StrongPtr.IsValid()) + { + return; + } + + // Cache the format from the Consumer. + // Ensure the format is known at this point. + ensure(StrongPtr->GetFormat(UnrealFormat)); + + AkChannelConfig ChannelConfig( + UnrealFormat.NumChannels, // Num Channels + AK::ChannelMaskFromNumChannels(UnrealFormat.NumChannels) // Channel mask + ); + + // Tell Wwise our format. + io_AudioFormat.SetAll( + UnrealFormat.NumSamplesPerSec, // Number of samples per second + ChannelConfig, // Channel configuration (above). + 32, // Number of bits per sample + 4, // Block alignment + AK_FLOAT, // Data sample format (Float or Integer) + AK_NONINTERLEAVED // Interleaved type (NOTE: Interleaved does not work with float, per the docs, sad times). + ); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkInputClient.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkInputClient.h new file mode 100644 index 0000000..fb1f5c1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkInputClient.h @@ -0,0 +1,72 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "IAudioLink.h" +#include "IAudioLinkFactory.h" +#include "Templates/SharedPointer.h" +#include "IBufferedAudioOutput.h" + +#include "AK/SoundEngine/Common/AkTypes.h" +#include "AkAudioDevice.h" +#include "DSP/BufferVectorOperations.h" + +class UWwiseAudioLinkComponent; + +class FWwiseAudioLinkInputClient : public TSharedFromThis +{ +public: + FWwiseAudioLinkInputClient(const FSharedBufferedOutputPtr& InToConsumeFrom, const UAudioLinkSettingsAbstract::FSharedSettingsProxyPtr& InSettingsProxy, FName InNameOfProducingSource={}); + virtual ~FWwiseAudioLinkInputClient(); + + void Start(UWwiseAudioLinkComponent* InComponent); + void Start(); + void Stop(); + + struct FWorldState + { + FTransform WorldTransform; + }; + // Called from Consumer thread at game tick rate. + void UpdateWorldState(const FWorldState&); + +private: + void Register(const FName& InNameOfProducingSource); + void Unregister(); + + + // Called from WWise thread. + bool GetSamples(uint32 InNumChannels, uint32 InNumFrames, float** InChannelBuffers); + + // Called from WWise thread. + void GetFormat(AkAudioFormat& io_AudioFormat); + + Audio::AlignedFloatBuffer InterleavedBuffer; + TWeakObjectPtr UnsafeAttachment; + FWeakBufferedOutputPtr WeakProducer; + UAudioLinkSettingsAbstract::FSharedSettingsProxyPtr SettingsProxy; + IBufferedAudioOutput::FBufferFormat UnrealFormat; + + std::atomic PlayId = AK_INVALID_PLAYING_ID; + AkGameObjectID ObjectId = AK_INVALID_AUDIO_OBJECT_ID; + FName ProducerName; + int32 NumStarvedBuffersInARow = 0; + FDelegateHandle IsLoadedHandle; +}; + +using FSharedWwiseAudioLinkInputClientPtr = TSharedPtr; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkSettings.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkSettings.cpp new file mode 100644 index 0000000..fcde264 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkSettings.cpp @@ -0,0 +1,188 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/AudioLink/WwiseAudioLinkSettings.h" +#include "Wwise/AudioLink/WwiseAudioLinkInputClient.h" +#include "Wwise/AudioLink/WwiseAudioLinkFactory.h" +#include "Wwise/Stats/AudioLink.h" + +#include "AkAudioEvent.h" +#include "InitializationSettings/AkInitializationSettings.h" +#include "Engine/StreamableManager.h" +#include "Engine/AssetManager.h" +#include "Async/Async.h" + +FWwiseAudioLinkSettingsProxy::FWwiseAudioLinkSettingsProxy(const UWwiseAudioLinkSettings& InSettings) +{ + Update(InSettings); +} + +void FWwiseAudioLinkSettingsProxy::Update(const UWwiseAudioLinkSettings& InSettings) +{ + InSettings.RequestLoad(); + ReceivingBufferSizeInFrames = InSettings.GetReceivingBufferSizeInFrames(); + bShouldZeroBuffer = InSettings.bShouldClearBufferOnReceipt; + ProducerToConsumerBufferRatio = InSettings.ProducerToConsumerBufferRatio; + InitialSilenceFillRatio = InSettings.InitialSilenceFillRatio; + StartEvent = InSettings.StartEvent; +} + +FDelegateHandle FWwiseAudioLinkSettingsProxy::CallOnEventLoaded(TFunction&& InCallback) +{ + { + FScopeLock Lock(&CriticalSection); + if (!bIsEventDataLoaded) + { + UE_LOG(LogWwiseAudioLink, VeryVerbose, TEXT("FWwiseAudioLinkSettingsProxy::CallOnEventLoaded: Event not loaded. Wait for load before calling callback.")); + return OnEventLoadedDelegate.Add(FSimpleMulticastDelegate::FDelegate::CreateLambda(MoveTemp(InCallback))); + } + } + + AsyncTask(ENamedThreads::GameThread, [InCallback = MoveTemp(InCallback)] + { + InCallback(); + }); + return FDelegateHandle(); +} + +void FWwiseAudioLinkSettingsProxy::UnregisterEventLoadedDelegate(const FDelegateHandle& InDelegate) +{ + FScopeLock Lock(&CriticalSection); + OnEventLoadedDelegate.Remove(InDelegate); +} + +void FWwiseAudioLinkSettingsProxy::NotifyEventDataLoaded() +{ + { + FScopeLock Lock(&CriticalSection); + bIsEventDataLoaded = true; + } + OnEventLoadedDelegate.Broadcast(); +} + +#if WITH_EDITOR +void FWwiseAudioLinkSettingsProxy::RefreshFromSettings(UAudioLinkSettingsAbstract* InSettings, FPropertyChangedEvent&) +{ + Update(*CastChecked(InSettings)); +} +#endif //WITH_EDITOR + +FName UWwiseAudioLinkSettings::GetFactoryName() const +{ + return FWwiseAudioLinkFactory::GetFactoryNameStatic(); +} + +bool UWwiseAudioLinkSettings::GetSettingsFromWwise(FWwiseSettings& OutSettings) const +{ + if (const UAkInitializationSettings* InitializationSettings = FAkPlatform::GetInitializationSettings()) + { + OutSettings.NumSamplesPerBuffer = InitializationSettings->CommonSettings.SamplesPerFrame; +#if PLATFORM_WINDOWS + OutSettings.NumSamplesPerSecond = InitializationSettings->CommonSettings.SampleRate; +#else //PLATFORM_WINDOWS + OutSettings.NumSamplesPerSecond = 48000; +#endif //PLATFORM_WINDOWS + return true; + } + return false; +} + +void UWwiseAudioLinkSettings::PostLoad() +{ + RequestLoad(); + Super::PostLoad(); +} + +void UWwiseAudioLinkSettings::RequestLoad() const +{ + if (bLoadRequested) + { + return; + } + bLoadRequested = true; + AsyncTask(ENamedThreads::GameThread, [WeakThis = MakeWeakObjectPtr(const_cast(this))]() + { + if (UNLIKELY(!WeakThis.IsValid())) + { + UE_LOG(LogWwiseAudioLink, Warning, TEXT("UWwiseAudioLinkSettings::RequestLoad: Invalid weak pointer.")); + return; + } + + UE_LOG(LogWwiseAudioLink, VeryVerbose, TEXT("UWwiseAudioLinkSettings::RequestLoad: Loading Settings '%s' StartEvent '%s'"), *WeakThis->GetName(), *WeakThis->StartEvent.ToSoftObjectPath().GetAssetName()); + FStreamableManager& StreamableManager = UAssetManager::GetStreamableManager(); + const FStreamableDelegate Delegate = FStreamableDelegate::CreateUObject(WeakThis.Get(), &UWwiseAudioLinkSettings::OnLoadCompleteCallback); + WeakThis->LoadingHandle = StreamableManager.RequestAsyncLoad( + WeakThis->StartEvent.ToSoftObjectPath(), + Delegate, + FStreamableManager::AsyncLoadHighPriority, + true // Managed active handle + ); + }); +} + +void UWwiseAudioLinkSettings::OnLoadCompleteCallback() +{ + TArray LoadedAssets; + LoadingHandle->GetLoadedAssets(LoadedAssets); + if (UNLIKELY(LoadedAssets.Num() == 0)) + { + UE_LOG(LogWwiseAudioLink, Warning, TEXT("UWwiseAudioLinkSettings::OnLoadCompleteCallback: Could not load asset for Settings '%s' StartEvent '%s'"), *GetName(), *StartEvent.ToSoftObjectPath().GetAssetName()); + } + else if (UNLIKELY(LoadedAssets.Num() > 1)) + { + UE_LOG(LogWwiseAudioLink, Warning, TEXT("UWwiseAudioLinkSettings::OnLoadCompleteCallback: Loaded more than one (%d) asset for Settings '%s' StartEvent '%s'. Using first one."), LoadedAssets.Num(), *GetName(), *StartEvent.ToSoftObjectPath().GetAssetName()); + StartEventResolved = CastChecked(LoadedAssets[0]); + } + else + { + UE_LOG(LogWwiseAudioLink, Verbose, TEXT("UWwiseAudioLinkSettings::OnLoadCompleteCallback: Asset loaded for Settings '%s' StartEvent '%s'"), *GetName(), *StartEvent.ToSoftObjectPath().GetAssetName()); + StartEventResolved = CastChecked(LoadedAssets[0]); + } + + if (IsValid(StartEventResolved)) + { + StartEventResolved->AddToRoot(); + } + + GetCastProxy()->NotifyEventDataLoaded(); + LoadingHandle.Reset(); +} + +void UWwiseAudioLinkSettings::FinishDestroy() +{ + if (IsValid(StartEventResolved)) + { + StartEventResolved->RemoveFromRoot(); + } + + Super::FinishDestroy(); +} + +int32 UWwiseAudioLinkSettings::GetReceivingBufferSizeInFrames() const +{ + // Ask Wwise for it's setting for buffer size, which is per platform. + FWwiseSettings SettingsFromWwise; + if (GetSettingsFromWwise(SettingsFromWwise)) + { + return SettingsFromWwise.NumSamplesPerBuffer; + } + + static const int32 SensibleDefaultSize = 1024; + UE_LOG(LogWwiseAudioLink, Warning, TEXT("UWwiseAudioLinkSettings::GetReceivingBufferSizeInFrames: Failed to get Wwise settings for buffer sizes, so using a default of '%d'"), SensibleDefaultSize); + return SensibleDefaultSize; +} + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkSourcePushed.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkSourcePushed.cpp new file mode 100644 index 0000000..fdd47ec --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkSourcePushed.cpp @@ -0,0 +1,113 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/AudioLink/WwiseAudioLinkSourcePushed.h" +#include "Wwise/AudioLink/WwiseAudioLinkSettings.h" +#include "Wwise/Stats/AudioLink.h" +#include "AkAudioEvent.h" + +FWwiseAudioLinkSourcePushed::FWwiseAudioLinkSourcePushed(const IAudioLinkFactory::FAudioLinkSourcePushedCreateArgs& InArgs, IAudioLinkFactory* InFactory) + : CreateArgs(InArgs) +{ + const FWwiseAudioLinkSettingsProxy* WwiseSettings = static_cast(InArgs.Settings.Get()); + + IAudioLinkFactory::FPushedBufferListenerCreateParams Params; + Params.SizeOfBufferInFrames = InArgs.NumFramesPerBuffer; + Params.bShouldZeroBuffer = WwiseSettings->ShouldClearBufferOnReceipt(); + + ProducerSP = InFactory->CreatePushableBufferListener(Params); + ConsumerSP = MakeShared(ProducerSP, InArgs.Settings, InArgs.OwnerName); + + int32 BufferSizeInSamples = WwiseSettings->GetReceivingBufferSizeInFrames() * InArgs.NumChannels; + int32 ReserveSizeInSamples = (float)BufferSizeInSamples * WwiseSettings->GetProducerConsumerBufferRatio(); + int32 SilenceToAddToFirstBuffer = FMath::Min((float)BufferSizeInSamples * WwiseSettings->GetInitialSilenceFillRatio(), ReserveSizeInSamples); + + // Set circular buffer ahead of first buffer. + ProducerSP->Reserve(ReserveSizeInSamples, SilenceToAddToFirstBuffer); + + UE_LOG(LogWwiseAudioLinkLowLevel, Verbose, + TEXT("FWwiseAudioLinkSourcePushed::Ctor() Name=%s, Producer=0x%p, Consumer=0x%p, p2c%%=%2.2f, PlayEvent=%s, TotalFramesForSource=%d, This=0x%p"), + *InArgs.OwnerName.GetPlainNameString(), ProducerSP.Get(), + ConsumerSP.Get(), WwiseSettings->GetProducerConsumerBufferRatio(), *WwiseSettings->GetStartEvent()->GetName(), CreateArgs.TotalNumFramesInSource, this); +} +FWwiseAudioLinkSourcePushed::~FWwiseAudioLinkSourcePushed() +{ + UE_LOG(LogWwiseAudioLinkLowLevel, Verbose, + TEXT("FWwiseAudioLinkSourcePushed::Dtor() Name=%s, Producer=0x%p, Consumer=0x%p, RecievedFrames=%d/%d, This=0x%p"), + *CreateArgs.OwnerName.GetPlainNameString(), ProducerSP.Get(), ConsumerSP.Get(), NumFramesReceivedSoFar, CreateArgs.TotalNumFramesInSource,this); + + ConsumerSP->Stop(); +} +void FWwiseAudioLinkSourcePushed::OnNewBuffer(const FOnNewBufferParams& InArgs) +{ + UE_LOG(LogWwiseAudioLinkLowLevel, VeryVerbose, + TEXT("FWwiseAudioLinkSourcePushed::OnNewBuffer() Name=%s, Producer=0x%p, Consumer=0x%p, SourceID=%d, RecievedFrames=%d/%d, This=0x%p"), + *CreateArgs.OwnerName.GetPlainNameString(), ProducerSP.Get(), ConsumerSP.Get(), SourceId, NumFramesReceivedSoFar, CreateArgs.TotalNumFramesInSource, + this); + + NumFramesReceivedSoFar += CreateArgs.NumFramesPerBuffer; + + if (SourceId == INDEX_NONE) + { + SourceId = InArgs.SourceId; + ProducerSP->Start(nullptr /*AudioDevice*/); + ConsumerSP->Start(); + } + check(SourceId == InArgs.SourceId); + + IPushableAudioOutput* Pushable = ProducerSP->GetPushableInterface(); + if (ensure(Pushable)) + { + IPushableAudioOutput::FOnNewBufferParams Params; + Params.AudioData = InArgs.Buffer.GetData(); + Params.NumSamples = InArgs.Buffer.Num(); + Params.Id = InArgs.SourceId; + Params.NumChannels = CreateArgs.NumChannels; + Params.SampleRate = CreateArgs.SampleRate; + Pushable->PushNewBuffer(Params); + } +} + +void FWwiseAudioLinkSourcePushed::OnSourceDone(const int32 InSourceId) +{ + UE_LOG(LogWwiseAudioLinkLowLevel, Verbose, + TEXT("FWwiseAudioLinkSourcePushed::OnSourceDone() Name=%s, Producer=0x%p, Consumer=0x%p, RecievedFrames=%d/%d, This=0x%p"), + *CreateArgs.OwnerName.GetPlainNameString(), ProducerSP.Get(), ConsumerSP.Get(), NumFramesReceivedSoFar, CreateArgs.TotalNumFramesInSource, this); + + check(SourceId == InSourceId); + IPushableAudioOutput* Pushable = ProducerSP->GetPushableInterface(); + if (ensure(Pushable)) + { + Pushable->LastBuffer(SourceId); + } + SourceId = INDEX_NONE; +} + +void FWwiseAudioLinkSourcePushed::OnSourceReleased(const int32 InSourceId) +{ + UE_LOG(LogWwiseAudioLinkLowLevel, Verbose, + TEXT("FWwiseAudioLinkSourcePushed::OnSourceReleased() Name=%s, Producer=0x%p, Consumer=0x%p, RecievedFrames=%d/%d, This=0x%p"), + *CreateArgs.OwnerName.GetPlainNameString(), ProducerSP.Get(), ConsumerSP.Get(), NumFramesReceivedSoFar, CreateArgs.TotalNumFramesInSource,this); +} + +// Called by the AudioThread, not the AudioRenderThread +void FWwiseAudioLinkSourcePushed::OnUpdateWorldState(const FOnUpdateWorldStateParams& InParams) +{ + FWwiseAudioLinkInputClient::FWorldState UpdateParams; + UpdateParams.WorldTransform = InParams.WorldTransform; + ConsumerSP->UpdateWorldState(UpdateParams); +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkSourcePushed.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkSourcePushed.h new file mode 100644 index 0000000..994d6c9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkSourcePushed.h @@ -0,0 +1,38 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#include "Wwise/AudioLink/WwiseAudioLinkInputClient.h" + +#include "IAudioLink.h" +#include "IBufferedAudioOutput.h" + +struct FWwiseAudioLinkSourcePushed : IAudioLinkSourcePushed +{ + int32 SourceId = INDEX_NONE; + int32 NumFramesReceivedSoFar = INDEX_NONE; + FSharedBufferedOutputPtr ProducerSP; + FSharedWwiseAudioLinkInputClientPtr ConsumerSP; + IAudioLinkFactory::FAudioLinkSourcePushedCreateArgs CreateArgs; + + FWwiseAudioLinkSourcePushed(const IAudioLinkFactory::FAudioLinkSourcePushedCreateArgs& InArgs, IAudioLinkFactory* InFactory); + virtual ~FWwiseAudioLinkSourcePushed() override; + void OnNewBuffer(const FOnNewBufferParams& InArgs) override; + void OnSourceDone(const int32 InSourceId) override; + void OnSourceReleased(const int32 InSourceId) override; + void OnUpdateWorldState(const FOnUpdateWorldStateParams& InParams) override; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkSynchronizer.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkSynchronizer.cpp new file mode 100644 index 0000000..cc014d6 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkSynchronizer.cpp @@ -0,0 +1,168 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/AudioLink/WwiseAudioLinkSynchronizer.h" + +#include "AkAudioDevice.h" +#include "Wwise/WwiseGlobalCallbacks.h" +#include "Wwise/WwiseSoundEngineModule.h" +#include "Wwise/API/WwiseSoundEngineAPI.h" +#include "Wwise/Stats/AudioLink.h" + +FWwiseAudioLinkSynchronizer::FWwiseAudioLinkSynchronizer() +{} + +FWwiseAudioLinkSynchronizer::~FWwiseAudioLinkSynchronizer() +{ + Unbind(); +} + +void FWwiseAudioLinkSynchronizer::ExecuteBeginRender(AK::IAkGlobalPluginContext* InContext) +{ + FOnRenderParams Params; + Params.BufferTickID = InContext->GetBufferTick(); + Params.NumFrames = InContext->GetMaxBufferLength(); + OnBeginRender.Broadcast(Params); +} + +void FWwiseAudioLinkSynchronizer::ExecuteEndRender(AK::IAkGlobalPluginContext* InContext) +{ + FOnRenderParams Params; + Params.BufferTickID = InContext->GetBufferTick(); + Params.NumFrames = InContext->GetMaxBufferLength(); + OnEndRender.Broadcast(Params); +} + +void FWwiseAudioLinkSynchronizer::ExecuteOpenStream() +{ + UE_LOG(LogWwiseAudioLink, Verbose, TEXT("FWwiseAudioLinkSynchronizer::ExecuteOpenStream: Opening stream between Unreal and Wwise.")); + + auto* SoundEngine = IWwiseSoundEngineModule::SoundEngine; + if (UNLIKELY(!SoundEngine)) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkSynchronizer::ExecuteOpenStream: No Sound Engine.")); + return; + } + + AkAudioSettings Settings; + SoundEngine->GetAudioSettings(Settings); + const AkChannelConfig SinkConfig = SoundEngine->GetSpeakerConfiguration(); + + FOnOpenStreamParams Params; + Params.NumChannels = SinkConfig.uNumChannels; + Params.SampleRate = Settings.uNumSamplesPerSecond; + Params.NumFrames = Settings.uNumSamplesPerFrame; + Params.Name = TEXT("AudioLink for Wwise"); + OnOpenStream.Broadcast(Params); +} + +void FWwiseAudioLinkSynchronizer::ExecuteCloseStream() +{ + UE_LOG(LogWwiseAudioLink, Verbose, TEXT("FWwiseAudioLinkSynchronizer::ExecuteCloseStream: Closing stream between Unreal and Wwise.")); + + OnCloseStream.Broadcast(); +} + +void FWwiseAudioLinkSynchronizer::Bind() +{ + if (UNLIKELY(bIsBound)) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkSynchronizer::Bind: Binding an already bound SoundEngine.")); + return; + } + + auto* Callbacks = FWwiseGlobalCallbacks::Get(); + if (UNLIKELY(!Callbacks)) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkSynchronizer::Bind: No Callbacks.")); + return; + } + + auto* SoundEngine = IWwiseSoundEngineModule::SoundEngine; + if (UNLIKELY(!SoundEngine)) + { + UE_LOG(LogWwiseAudioLink, Error, TEXT("FWwiseAudioLinkSynchronizer::ExecuteOpenStream: No Sound Engine.")); + return; + } + + UE_LOG(LogWwiseAudioLink, Verbose, TEXT("FWwiseAudioLinkSynchronizer::Bind: Binding SoundEngine.")); + Callbacks->BeginRenderSync([WeakThis = AsWeak()](AK::IAkGlobalPluginContext* InContext) mutable + { + auto This = WeakThis.Pin(); + if (UNLIKELY(!This.IsValid() || !This->bIsBound)) + { + return EWwiseDeferredAsyncResult::Done; + } + + This->ExecuteBeginRender(InContext); + return EWwiseDeferredAsyncResult::KeepRunning; + }); + + Callbacks->EndRenderSync([WeakThis = AsWeak()](AK::IAkGlobalPluginContext* InContext) mutable + { + auto This = WeakThis.Pin(); + if (UNLIKELY(!This.IsValid() || !This->bIsBound)) + { + return EWwiseDeferredAsyncResult::Done; + } + + This->ExecuteEndRender(InContext); + return EWwiseDeferredAsyncResult::KeepRunning; + }); + + if (SoundEngine->IsInitialized()) + { + ExecuteOpenStream(); + } + Callbacks->InitAsync([WeakThis = AsWeak()]() mutable + { + auto This = WeakThis.Pin(); + if (UNLIKELY(!This.IsValid() || !This->bIsBound)) + { + return EWwiseDeferredAsyncResult::Done; + } + + This->ExecuteOpenStream(); + return EWwiseDeferredAsyncResult::KeepRunning; + }); + + Callbacks->TermAsync([WeakThis = AsWeak()]() mutable + { + auto This = WeakThis.Pin(); + if (UNLIKELY(!This.IsValid() || !This->bIsBound)) + { + return EWwiseDeferredAsyncResult::Done; + } + + This->ExecuteCloseStream(); + return EWwiseDeferredAsyncResult::KeepRunning; + }); + bIsBound = true; +} + +void FWwiseAudioLinkSynchronizer::Unbind() +{ + if (UNLIKELY(!bIsBound)) + { + return; + } + + UE_LOG(LogWwiseAudioLink, Verbose, TEXT("FWwiseAudioLinkSynchronizer::Bind: Unbinding SoundEngine.")); + bIsBound = false; + + ExecuteCloseStream(); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkSynchronizer.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkSynchronizer.h new file mode 100644 index 0000000..9256583 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/AudioLink/WwiseAudioLinkSynchronizer.h @@ -0,0 +1,70 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "IAudioLink.h" +#include "Misc/ScopeRWLock.h" +#include "Templates/SharedPointer.h" + +#include "AkInclude.h" + +struct FWwiseAudioLinkSynchronizer : IAudioLinkSynchronizer, TSharedFromThis +{ + FRWLock RwLock; + + // Registered with AudioLink + FOnSuspend OnSuspend; + FOnResume OnResume; + FOnOpenStream OnOpenStream; + FOnCloseStream OnCloseStream; + FOnBeginRender OnBeginRender; + FOnEndRender OnEndRender; + + bool bIsBound { false }; + + FWwiseAudioLinkSynchronizer(); + ~FWwiseAudioLinkSynchronizer() override; + + void ExecuteBeginRender(AK::IAkGlobalPluginContext* InContext); + void ExecuteEndRender(AK::IAkGlobalPluginContext* InContext); + void ExecuteOpenStream(); + void ExecuteCloseStream(); + void Bind(); + void Unbind(); + + #define MAKE_DELEGATE_FUNC(X)\ + FDelegateHandle Register##X##Delegate(const FOn##X::FDelegate& InDelegate) override\ + {\ + FWriteScopeLock WriteLock(RwLock);\ + return On##X.Add(InDelegate);\ + }\ + bool Remove##X##Delegate(const FDelegateHandle& InHandle) override\ + {\ + FWriteScopeLock WriteLock(RwLock);\ + return On##X.Remove(InHandle);\ + } + + MAKE_DELEGATE_FUNC(Suspend) + MAKE_DELEGATE_FUNC(Resume) + MAKE_DELEGATE_FUNC(OpenStream) + MAKE_DELEGATE_FUNC(CloseStream) + MAKE_DELEGATE_FUNC(BeginRender) + MAKE_DELEGATE_FUNC(EndRender) + + #undef MAKE_DELEGATE_FUNC +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/Stats/AudioLink.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/Stats/AudioLink.cpp new file mode 100644 index 0000000..868acb9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/Stats/AudioLink.cpp @@ -0,0 +1,21 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Stats/AudioLink.h" + +DEFINE_LOG_CATEGORY(LogWwiseAudioLink); +DEFINE_LOG_CATEGORY(LogWwiseAudioLinkLowLevel); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/WwiseAudioLinkRuntimeModule.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/WwiseAudioLinkRuntimeModule.h new file mode 100644 index 0000000..791f65c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Private/Wwise/WwiseAudioLinkRuntimeModule.h @@ -0,0 +1,34 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/AudioLink/WwiseAudioLinkFactory.h" + +class FWwiseAudioLinkRuntimeModule +{ +public: + // Begin IModuleInterface + FWwiseAudioLinkRuntimeModule() + { + Factory = MakeUnique(); + } + ~FWwiseAudioLinkRuntimeModule() + { + Factory.Reset(); + } +private: + TUniquePtr Factory; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Public/Wwise/AudioLink/WwiseAudioLinkComponent.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Public/Wwise/AudioLink/WwiseAudioLinkComponent.h new file mode 100644 index 0000000..dd4a482 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Public/Wwise/AudioLink/WwiseAudioLinkComponent.h @@ -0,0 +1,63 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/AudioLink/WwiseAudioLinkSettings.h" +#include "AkComponent.h" +#include "IAudioLink.h" +#include "IAudioLinkBlueprintInterface.h" + +class UAudioComponent; + +#include "WwiseAudioLinkComponent.generated.h" + +UCLASS(ClassGroup = (Audio, Common), HideCategories = (Object, ActorComponent, Physics, Rendering, Mobility, LOD), ShowCategories = Trigger, meta = (BlueprintSpawnableComponent)) +class UWwiseAudioLinkComponent : public UAkComponent, public IAudioLinkBlueprintInterface +{ + GENERATED_BODY() + +public: + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AudioLink") + TObjectPtr Settings; + + /** The sound to be played */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Sound) + TObjectPtr Sound; + +protected: + //~ Begin IAudioLinkInterface + virtual void SetLinkSound(USoundBase* NewSound) override; + virtual void PlayLink(float StartTime = 0.0f) override; + virtual void StopLink() override; + virtual bool IsLinkPlaying() const override; + //~ End IAudioLinkInterface + + //~ Begin ActorComponent Interface. + virtual void OnRegister() override; + virtual void OnUnregister() override; + //~ End ActorComponent Interface. + + /** Stop sound when owner is destroyed */ + void CreateAudioComponent(); + + UPROPERTY(Transient) + TObjectPtr AudioComponent; + + void CreateLink(); + TUniquePtr LinkInstance; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Public/Wwise/AudioLink/WwiseAudioLinkSettings.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Public/Wwise/AudioLink/WwiseAudioLinkSettings.h new file mode 100644 index 0000000..4703a47 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Public/Wwise/AudioLink/WwiseAudioLinkSettings.h @@ -0,0 +1,121 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AudioLinkSettingsAbstract.h" +#include "Engine/StreamableManager.h" +#include "WwiseAudioLinkSettings.generated.h" + +class UWwiseAudioLinkSettings; +class UAkAudioEvent; + +class FWwiseAudioLinkSettingsProxy : public IAudioLinkSettingsProxy +{ +public: + FWwiseAudioLinkSettingsProxy(const UWwiseAudioLinkSettings&); + virtual ~FWwiseAudioLinkSettingsProxy() override = default; + + const TSoftObjectPtr& GetStartEvent() const { return StartEvent; } + int32 GetReceivingBufferSizeInFrames() const { return ReceivingBufferSizeInFrames; } + bool ShouldClearBufferOnReceipt() const { return bShouldZeroBuffer; } + float GetProducerConsumerBufferRatio() const { return ProducerToConsumerBufferRatio; } + float GetInitialSilenceFillRatio() const { return InitialSilenceFillRatio; } + + void Update(const UWwiseAudioLinkSettings&); + + FDelegateHandle CallOnEventLoaded(TFunction&& InCallback); + void UnregisterEventLoadedDelegate(const FDelegateHandle& InDelegate); + +protected: + void NotifyEventDataLoaded(); + +private: +#if WITH_EDITOR + void RefreshFromSettings(UAudioLinkSettingsAbstract* InSettings, FPropertyChangedEvent& InPropertyChangedEvent) override; +#endif //WITH_EDITOR + + FSimpleMulticastDelegate OnEventLoadedDelegate; + FCriticalSection CriticalSection; + friend class UWwiseAudioLinkSettings; + + TSoftObjectPtr StartEvent; + + int32 ReceivingBufferSizeInFrames; + bool bShouldZeroBuffer = false; + bool bIsEventDataLoaded = false; + float ProducerToConsumerBufferRatio = 2.0f; + float InitialSilenceFillRatio = 1.0f; +}; + +using FSharedAudioLinkSettingProxyWwisePtr = TSharedPtr; + +UCLASS(config = Game, defaultconfig) +class WWISE_API UWwiseAudioLinkSettings : public UAudioLinkSettingsAbstract +{ + GENERATED_BODY() + +public: + UPROPERTY(Config, EditAnywhere, Category = "Wwise|AudioLink") + TSoftObjectPtr StartEvent; + + /** If this is set, the receiving code will clear the buffer after it's read, so it's not rendered by Unreal. Only applies if running both renderers at once. */ + UPROPERTY(Config, EditAnywhere, Category = "Wwise|AudioLink") + bool bShouldClearBufferOnReceipt = true; + + /** This is the ratio of producer to consumer buffer size, 2.0 means its twice as big as the consumer buffer. */ + UPROPERTY(Config, EditAnywhere, Category = "Wwise|AudioLink") + float ProducerToConsumerBufferRatio = 2.0f; + + /** Ratio of initial buffer to fill with silence ahead of consumption. This can resolve starvation at the cost of added latency. */ + UPROPERTY(Config, EditAnywhere, Category = "Wwise|AudioLink") + float InitialSilenceFillRatio = 1.0f; + + void RequestLoad() const; + +protected: + mutable bool bLoadRequested = false; + TSharedPtr LoadingHandle; + + /** Once the SoftObjectReference has been resolved, attach the reference here so it's owned. */ + UPROPERTY(Transient) + TObjectPtr StartEventResolved; + + void PostLoad() override; + void OnLoadCompleteCallback(); + void FinishDestroy() override; + + friend class FWwiseAudioLinkSettingsProxy; + + int32 GetReceivingBufferSizeInFrames() const; + + UAudioLinkSettingsAbstract::FSharedSettingsProxyPtr MakeProxy() const override + { + return UAudioLinkSettingsAbstract::FSharedSettingsProxyPtr(new FWwiseAudioLinkSettingsProxy{ *this }); + } + + FName GetFactoryName() const override; + + struct FWwiseSettings + { + int32 NumSamplesPerBuffer = 0; + int32 NumSamplesPerSecond = 0; + }; + + bool GetSettingsFromWwise(FWwiseSettings& OutSettings) const; +}; + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Public/Wwise/Stats/AudioLink.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Public/Wwise/Stats/AudioLink.h new file mode 100644 index 0000000..fd59a62 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/Public/Wwise/Stats/AudioLink.h @@ -0,0 +1,24 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Stats/Stats.h" +#include "Logging/LogMacros.h" + +WWISE_API DECLARE_LOG_CATEGORY_EXTERN(LogWwiseAudioLink, Log, All); +WWISE_API DECLARE_LOG_CATEGORY_EXTERN(LogWwiseAudioLinkLowLevel, Log, Log); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/WwiseAudioLinkRuntime_OptionalModule.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/WwiseAudioLinkRuntime_OptionalModule.Build.cs new file mode 100644 index 0000000..1cbd11b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseAudioLinkRuntime/WwiseAudioLinkRuntime_OptionalModule.Build.cs @@ -0,0 +1,36 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; + +public struct WwiseAudioLinkRuntime +{ + public static void Apply(Wwise WwiseModule, ReadOnlyTargetRules Target) + { +#if UE_5_1_OR_LATER + WwiseModule.AddOptionalModule("WwiseAudioLinkRuntime"); + WwiseModule.PrivateDependencyModuleNames.AddRange( + new string[] + { + "AudioLinkCore", + "AudioLinkEngine", + "SignalProcessing" + } + ); +#endif + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Private/Wwise/Stats/Concurrency.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Private/Wwise/Stats/Concurrency.cpp new file mode 100644 index 0000000..a606721 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Private/Wwise/Stats/Concurrency.cpp @@ -0,0 +1,33 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Stats/Concurrency.h" + +DEFINE_STAT(STAT_WwiseConcurrencyGameThreadWait); +DEFINE_STAT(STAT_WwiseConcurrencyWait); +DEFINE_STAT(STAT_WwiseConcurrencyAsync); +DEFINE_STAT(STAT_WwiseConcurrencySync); +DEFINE_STAT(STAT_WwiseConcurrencyGameThread); + +DEFINE_STAT(STAT_WwiseExecutionQueues); +DEFINE_STAT(STAT_WwiseExecutionQueueAsyncCalls); +DEFINE_STAT(STAT_WwiseExecutionQueueAsyncWaitCalls); + +DEFINE_STAT(STAT_WwiseFutures); +DEFINE_STAT(STAT_WwiseFuturesWithEvent); + +DEFINE_LOG_CATEGORY(LogWwiseConcurrency); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Private/Wwise/WwiseConcurrencyModuleImpl.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Private/Wwise/WwiseConcurrencyModuleImpl.cpp new file mode 100644 index 0000000..4102e31 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Private/Wwise/WwiseConcurrencyModuleImpl.cpp @@ -0,0 +1,163 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseConcurrencyModuleImpl.h" + +#include "Wwise/WwiseGlobalCallbacks.h" +#include "Wwise/Stats/Concurrency.h" + +IMPLEMENT_MODULE(FWwiseConcurrencyModule, WwiseConcurrency) + +FWwiseConcurrencyModule::FWwiseConcurrencyModule() +{ +} + +void FWwiseConcurrencyModule::StartupModule() +{ + UE_LOG(LogWwiseConcurrency, Display, TEXT("Initializing default Concurrency.")); + ExecutionQueueLock.WriteLock(); + if (!ExecutionQueueThreadPool) + { + InitializeExecutionQueueThreadPool(); + } + ExecutionQueueLock.WriteUnlock(); + + GlobalCallbacksLock.WriteLock(); + if (!GlobalCallbacks) + { + InitializeGlobalCallbacks(); + } + GlobalCallbacksLock.WriteUnlock(); + + IWwiseConcurrencyModule::StartupModule(); +} + +void FWwiseConcurrencyModule::ShutdownModule() +{ + UE_LOG(LogWwiseConcurrency, Display, TEXT("Shutting down default Concurrency.")); + GlobalCallbacksLock.WriteLock(); + TerminateGlobalCallbacks(); + GlobalCallbacksLock.WriteUnlock(); + + ExecutionQueueLock.WriteLock(); + TerminateExecutionQueueThreadPool(); + ExecutionQueueLock.WriteUnlock(); + + IWwiseConcurrencyModule::ShutdownModule(); +} + +FQueuedThreadPool* FWwiseConcurrencyModule::GetExecutionQueueThreadPool() +{ + ExecutionQueueLock.ReadLock(); + if (LIKELY(ExecutionQueueThreadPool)) + { + ExecutionQueueLock.ReadUnlock(); + return ExecutionQueueThreadPool; + } + + ExecutionQueueLock.ReadUnlock(); + ExecutionQueueLock.WriteLock(); + if (UNLIKELY(ExecutionQueueThreadPool)) + { + ExecutionQueueLock.WriteUnlock(); + return ExecutionQueueThreadPool; + } + + InitializeExecutionQueueThreadPool(); + ExecutionQueueLock.WriteUnlock(); + return ExecutionQueueThreadPool; +} + +FWwiseGlobalCallbacks* FWwiseConcurrencyModule::GetGlobalCallbacks() +{ + GlobalCallbacksLock.ReadLock(); + if (LIKELY(GlobalCallbacks)) + { + GlobalCallbacksLock.ReadUnlock(); + return GlobalCallbacks; + } + + GlobalCallbacksLock.ReadUnlock(); + GlobalCallbacksLock.WriteLock(); + if (UNLIKELY(GlobalCallbacks)) + { + GlobalCallbacksLock.WriteUnlock(); + return GlobalCallbacks; + } + + InitializeGlobalCallbacks(); + GlobalCallbacksLock.WriteUnlock(); + return GlobalCallbacks; +} + +int32 FWwiseConcurrencyModule::NumberOfExecutionQueueThreadsToSpawn() +{ + if (UNLIKELY(!FPlatformProcess::SupportsMultithreading())) + { + return 1; + } + + static constexpr int32 ClampMin = 2; + static constexpr int32 ClampMax = 8; + + // Unreal Platform gives a curated value based on the possible number of cores available. Some platforms have 3, some have 13. + // In our case, we don't want that many threads, don't want to tax systems, but don't want the app to wait for us. + // A square root gives a good correlation for most, and for everyone else, it's possible to override this class. + // 4 cores = 2 threads, 9 cores = 3 threads, 16 cores = 4 threads. + const auto PlatformWorkersToSpawn = FPlatformMisc::NumberOfWorkerThreadsToSpawn(); + const auto WorkersToSpawn = FMath::Sqrt(static_cast(PlatformWorkersToSpawn)); + return FMath::Min(ClampMax, FMath::Max(ClampMin, static_cast(WorkersToSpawn))); +} + +void FWwiseConcurrencyModule::InitializeExecutionQueueThreadPool() +{ + static constexpr int32 StackSize = 128 * 1024; + + ExecutionQueueThreadPool = FQueuedThreadPool::Allocate(); + const int32 NumThreadsInThreadPool = NumberOfExecutionQueueThreadsToSpawn(); + verify(ExecutionQueueThreadPool->Create(NumThreadsInThreadPool, StackSize, TPri_Normal, TEXT("Wwise ExecutionQueue Pool"))); +} + +void FWwiseConcurrencyModule::InitializeGlobalCallbacks() +{ + if (!ExecutionQueueThreadPool) + { + InitializeExecutionQueueThreadPool(); + } + GlobalCallbacks = new FWwiseGlobalCallbacks; + // GlobalCallbacks->Initialize(); Initialization requires memory allocation that is only available in AkInitializationSettings. +} + +void FWwiseConcurrencyModule::TerminateExecutionQueueThreadPool() +{ + if (ExecutionQueueThreadPool) + { + ExecutionQueueThreadPool->Destroy(); + ExecutionQueueThreadPool = nullptr; + } +} + +void FWwiseConcurrencyModule::TerminateGlobalCallbacks() +{ + if (GlobalCallbacks) + { + GlobalCallbacks->Terminate(); + delete GlobalCallbacks; + GlobalCallbacks = nullptr; + } +} + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Private/Wwise/WwiseDeferredQueue.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Private/Wwise/WwiseDeferredQueue.cpp new file mode 100644 index 0000000..13b7a7a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Private/Wwise/WwiseDeferredQueue.cpp @@ -0,0 +1,240 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseDeferredQueue.h" + +#include "Async/Async.h" + +#include "Wwise/Stats/AsyncStats.h" +#include "Wwise/Stats/Concurrency.h" + +FWwiseDeferredQueue::FWwiseDeferredQueue() +{ +} + +FWwiseDeferredQueue::~FWwiseDeferredQueue() +{ + bClosing = true; + if (!IsEmpty()) + { + Wait(); + + UE_CLOG(UNLIKELY(!IsEmpty()), LogWwiseConcurrency, Error, TEXT("Still operations in queue while deleting Deferred Queue")); + } +} + +void FWwiseDeferredQueue::AsyncDefer(FFunction&& InFunction) +{ + if (!bClosing) + { + AsyncOpQueue.Enqueue(MoveTemp(InFunction)); + } +} + +void FWwiseDeferredQueue::SyncDefer(FSyncFunction&& InFunction) +{ + if (!bClosing) + { + SyncOpQueue.Enqueue(MoveTemp(InFunction)); + } +} + +void FWwiseDeferredQueue::GameDefer(FFunction&& InFunction) +{ + if (!bClosing) + { + GameOpQueue.Enqueue(MoveTemp(InFunction)); + } +} + +void FWwiseDeferredQueue::Run(AK::IAkGlobalPluginContext* InContext) +{ + SCOPED_WWISECONCURRENCY_EVENT_4(TEXT("FWwiseDeferredQueue::Run")); + FWwiseAsyncCycleCounter OpCycleCounter(GET_STATID(STAT_WwiseConcurrencySync)); + + UE_CLOG(UNLIKELY(Context), LogWwiseConcurrency, Error, TEXT("Executing two Run() at the same time.")); + Context = InContext; + + if (!AsyncOpQueue.IsEmpty()) + { + AsyncExecutionQueue.Async([this]() mutable + { + AsyncExec(); + }); + } + + if (!GameOpQueue.IsEmpty() || OnGameRun.IsBound()) + { + GameThreadExec(); + } + + if (!SyncOpQueue.IsEmpty() || OnSyncRunTS.IsBound()) + { + SyncExec(); + } + OnSyncRunTS.Broadcast(Context); + + Context = nullptr; +} + +void FWwiseDeferredQueue::Wait() +{ + const bool bIsInGameThread = IsInGameThread(); + SCOPED_WWISECONCURRENCY_EVENT_4(bIsInGameThread ? TEXT("FWwiseDeferredQueue::Wait GameThread") : TEXT("FWwiseDeferredQueue::Wait")); + CONDITIONAL_SCOPE_CYCLE_COUNTER(STAT_WwiseConcurrencyGameThreadWait, bIsInGameThread); + CONDITIONAL_SCOPE_CYCLE_COUNTER(STAT_WwiseConcurrencyWait, !bIsInGameThread); + + if (!AsyncOpQueue.IsEmpty()) + { + AsyncExecutionQueue.AsyncWait([this]() mutable + { + AsyncExec(); + }); + } + if (!GameOpQueue.IsEmpty()) + { + FEventRef Done; + if (bIsInGameThread) + { + const bool bNeedToStartLoop = GameThreadExecuting.IncrementExchange() == 0; + + GameOpQueue.Enqueue([this, &Done]() mutable + { + Done->Trigger(); + GameThreadExecuting.DecrementExchange(); + return EWwiseDeferredAsyncResult::Done; + }); + + if (bNeedToStartLoop) + { + FFunction Func; + while (GameThreadExecuting.Load() > 0 && GameOpQueue.Dequeue(Func)) + { + if (Func() == EWwiseDeferredAsyncResult::KeepRunning) + { + GameDefer(MoveTemp(Func)); + } + } + } + } + else + { + GameOpQueue.Enqueue([&Done]() mutable + { + Done->Trigger(); + return EWwiseDeferredAsyncResult::Done; + }); + GameThreadExec(); + } + Done->Wait(); + } + if (!SyncOpQueue.IsEmpty()) + { + FWwiseAsyncCycleCounter OpCycleCounter(GET_STATID(STAT_WwiseConcurrencySync)); + + SyncExec(); + } +} + +void FWwiseDeferredQueue::AsyncExec() +{ + SCOPED_WWISECONCURRENCY_EVENT_4(TEXT("FWwiseDeferredQueue::AsyncExec")); + SCOPE_CYCLE_COUNTER(STAT_WwiseConcurrencyAsync); + + bool bDone = false; + AsyncOpQueue.Enqueue([&bDone]() mutable + { + bDone = true; + return EWwiseDeferredAsyncResult::Done; + }); + + while (!bDone) + { + FFunction Func; + const bool bResult = AsyncOpQueue.Dequeue(Func); + if (UNLIKELY(!bResult)) + { + UE_LOG(LogWwiseConcurrency, Error, TEXT("FWwiseDeferredQueue: No Result dequeuing Async Deferred Queue")); + break; + } + if (Func() == EWwiseDeferredAsyncResult::KeepRunning) + { + AsyncDefer(MoveTemp(Func)); + } + } +} + +void FWwiseDeferredQueue::SyncExec() +{ + SyncOpQueue.Enqueue([this](AK::IAkGlobalPluginContext*) mutable + { + bSyncThreadDone = true; + return EWwiseDeferredAsyncResult::Done; + }); + + SyncExecLoop(); +} + +void FWwiseDeferredQueue::SyncExecLoop() +{ + FSyncFunction Func; + while (!bSyncThreadDone && SyncOpQueue.Dequeue(Func)) + { + if (Func(Context) == EWwiseDeferredAsyncResult::KeepRunning) + { + SyncDefer(MoveTemp(Func)); + } + } + OnSyncRunTS.Broadcast(Context); + bSyncThreadDone = false; +} + +void FWwiseDeferredQueue::GameThreadExec() +{ + const bool bNeedToStartLoop = GameThreadExecuting.IncrementExchange() == 0; + + GameOpQueue.Enqueue([this]() mutable + { + GameThreadExecuting.DecrementExchange(); + return EWwiseDeferredAsyncResult::Done; + }); + + if (bNeedToStartLoop) + { + GameThreadExecLoop(); + } +} + +void FWwiseDeferredQueue::GameThreadExecLoop() +{ + AsyncTask(ENamedThreads::GameThread, [this]() mutable + { + SCOPED_WWISECONCURRENCY_EVENT_4(TEXT("FWwiseDeferredQueue::GameThreadExecLoop")); + SCOPE_CYCLE_COUNTER(STAT_WwiseConcurrencyGameThread); + + FFunction Func; + while (GameThreadExecuting.Load() > 0 && GameOpQueue.Dequeue(Func)) + { + if (Func() == EWwiseDeferredAsyncResult::KeepRunning) + { + GameDefer(MoveTemp(Func)); + } + } + + OnGameRun.Broadcast(); + }); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Private/Wwise/WwiseExecutionQueue.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Private/Wwise/WwiseExecutionQueue.cpp new file mode 100644 index 0000000..aa53036 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Private/Wwise/WwiseExecutionQueue.cpp @@ -0,0 +1,414 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseExecutionQueue.h" +#include "Async/TaskGraphInterfaces.h" +#include "HAL/Event.h" +#include "Misc/IQueuedWork.h" +#include "Wwise/WwiseConcurrencyModule.h" +#include "Wwise/Stats/AsyncStats.h" +#include "Wwise/Stats/Concurrency.h" + +#include + +static constexpr const auto DEBUG_WWISEEXECUTIONQUEUE{ false }; + +#if defined(WITH_LOW_LEVEL_TESTS) && WITH_LOW_LEVEL_TESTS || defined(WITH_AUTOMATION_TESTS) || (WITH_DEV_AUTOMATION_TESTS || WITH_PERF_AUTOMATION_TESTS) +bool FWwiseExecutionQueue::Test::bMockEngineDeletion{ false }; +bool FWwiseExecutionQueue::Test::bMockEngineDeleted{ false }; +#endif + +class FWwiseExecutionQueue::ExecutionQueuePoolTask + : public IQueuedWork +{ +public: + ExecutionQueuePoolTask(TUniqueFunction&& InFunction) + : Function(MoveTemp(InFunction)) + { } + +public: + virtual void DoThreadedWork() override + { + Function(); + delete this; + } + + virtual void Abandon() override + { + } + +private: + TUniqueFunction Function; +}; + + +FWwiseExecutionQueue::FWwiseExecutionQueue(ENamedThreads::Type InNamedThread) : + NamedThread(InNamedThread), + ThreadPool(nullptr), + bOwnedPool(false) +{ + ASYNC_INC_DWORD_STAT(STAT_WwiseExecutionQueues); + UE_CLOG(DEBUG_WWISEEXECUTIONQUEUE, LogWwiseConcurrency, VeryVerbose, TEXT("FWwiseExecutionQueue (%p): Creating in named thread %d"), this, (int)InNamedThread); +} + +FWwiseExecutionQueue::FWwiseExecutionQueue(FQueuedThreadPool* InThreadPool) : + NamedThread(ENamedThreads::UnusedAnchor), + ThreadPool(InThreadPool ? InThreadPool : GetDefaultThreadPool()), + bOwnedPool(false) +{ + ASYNC_INC_DWORD_STAT(STAT_WwiseExecutionQueues); + UE_CLOG(DEBUG_WWISEEXECUTIONQUEUE, LogWwiseConcurrency, VeryVerbose, TEXT("FWwiseExecutionQueue (%p): Creating in thread pool %p"), this, InThreadPool); +} + +FWwiseExecutionQueue::FWwiseExecutionQueue(const TCHAR* InThreadName, EThreadPriority InThreadPriority, int32 InStackSize) : + NamedThread(ENamedThreads::UnusedAnchor), + ThreadPool(FQueuedThreadPool::Allocate()), + bOwnedPool(true) +{ + ASYNC_INC_DWORD_STAT(STAT_WwiseExecutionQueues); + UE_CLOG(DEBUG_WWISEEXECUTIONQUEUE, LogWwiseConcurrency, VeryVerbose, TEXT("FWwiseExecutionQueue (%p): Creating new thread pool %p named %s"), this, ThreadPool, InThreadName); + verify(ThreadPool->Create(1, InStackSize, InThreadPriority, InThreadName)); +} + +FWwiseExecutionQueue::~FWwiseExecutionQueue() +{ + UE_CLOG(UNLIKELY(bDeleteOnceClosed && WorkerState.Load(EMemoryOrder::SequentiallyConsistent) != EWorkerState::Closed), LogWwiseConcurrency, Fatal, TEXT("Deleting FWwiseExectionQueue twice!")); + + Close(); + if (bOwnedPool) + { + delete ThreadPool; + } + ASYNC_DEC_DWORD_STAT(STAT_WwiseExecutionQueues); + UE_CLOG(DEBUG_WWISEEXECUTIONQUEUE, LogWwiseConcurrency, VeryVerbose, TEXT("FWwiseExecutionQueue (%p): Deleted Execution Queue"), this); +} + +void FWwiseExecutionQueue::Async(FBasicFunction&& InFunction) +{ + TrySetRunningWorkerToAddOp(); + if (UNLIKELY(!OpQueue.Enqueue(MoveTemp(InFunction)))) + { + ASYNC_INC_DWORD_STAT(STAT_WwiseExecutionQueueAsyncCalls); + InFunction(); + return; + } + StartWorkerIfNeeded(); +} + +void FWwiseExecutionQueue::AsyncAlways(FBasicFunction&& InFunction) +{ + Async([CallerThreadId = FPlatformTLS::GetCurrentThreadId(), InFunction = MoveTemp(InFunction)]() mutable + { + if (CallerThreadId == FPlatformTLS::GetCurrentThreadId()) + { + FFunctionGraphTask::CreateAndDispatchWhenReady(MoveTemp(InFunction)); + } + else + { + InFunction(); + } + }); +} + +void FWwiseExecutionQueue::AsyncWait(FBasicFunction&& InFunction) +{ + SCOPED_WWISECONCURRENCY_EVENT_4(TEXT("FWwiseExecutionQueue::AsyncWait")); + TrySetRunningWorkerToAddOp(); + FEventRef Event(EEventMode::ManualReset); + if (UNLIKELY(!OpQueue.Enqueue([&Event, &InFunction] { + ASYNC_INC_DWORD_STAT(STAT_WwiseExecutionQueueAsyncWaitCalls); + InFunction(); + Event->Trigger(); + }))) + { + InFunction(); + return; + } + StartWorkerIfNeeded(); + Event->Wait(); +} + +void FWwiseExecutionQueue::Close() +{ + UE_CLOG(DEBUG_WWISEEXECUTIONQUEUE, LogWwiseConcurrency, VeryVerbose, TEXT("FWwiseExecutionQueue::Close (%p): Closing"), this); + auto State = WorkerState.Load(EMemoryOrder::Relaxed); + if (State != EWorkerState::Closing && State != EWorkerState::Closed) + { + AsyncWait([this] + { + TrySetRunningWorkerToClosing(); + }); + State = WorkerState.Load(EMemoryOrder::Relaxed); + } + if (State != EWorkerState::Closed) + { + SCOPED_WWISECONCURRENCY_EVENT_4(TEXT("FWwiseExecutionQueue::Close Waiting")); + UE_CLOG(DEBUG_WWISEEXECUTIONQUEUE, LogWwiseConcurrency, VeryVerbose, TEXT("FWwiseExecutionQueue::Close (%p): Waiting for Closed"), this); + while (State != EWorkerState::Closed) + { + FPlatformProcess::Sleep(0.01); + State = WorkerState.Load(EMemoryOrder::Relaxed); + } + } + UE_CLOG(DEBUG_WWISEEXECUTIONQUEUE, LogWwiseConcurrency, VeryVerbose, TEXT("FWwiseExecutionQueue::Close (%p): Done Closing"), this); +} + +void FWwiseExecutionQueue::CloseAndDelete() +{ + UE_CLOG(DEBUG_WWISEEXECUTIONQUEUE, LogWwiseConcurrency, VeryVerbose, TEXT("FWwiseExecutionQueue::Close (%p): Closing and Request Deleting"), this); + bDeleteOnceClosed = true; + auto State = WorkerState.Load(EMemoryOrder::Relaxed); + Async([this] + { + TrySetRunningWorkerToClosing(); + }); +} + +bool FWwiseExecutionQueue::IsBeingClosed() const +{ + const auto State = WorkerState.Load(EMemoryOrder::Relaxed); + return UNLIKELY(State == EWorkerState::Closed || State == EWorkerState::Closing); +} + +bool FWwiseExecutionQueue::IsClosed() const +{ + const auto State = WorkerState.Load(EMemoryOrder::Relaxed); + return State == EWorkerState::Closed; +} + +FQueuedThreadPool* FWwiseExecutionQueue::GetDefaultThreadPool() +{ + auto* ConcurrencyModule = IWwiseConcurrencyModule::GetModule(); + if (UNLIKELY(!ConcurrencyModule)) + { + return nullptr; + } + return ConcurrencyModule->GetExecutionQueueThreadPool(); +} + +void FWwiseExecutionQueue::StartWorkerIfNeeded() +{ + if (TrySetStoppedWorkerToRunning()) + { + if (UNLIKELY(!IWwiseConcurrencyModule::GetModule() || Test::bMockEngineDeletion || Test::bMockEngineDeleted)) + { + if (UNLIKELY(!FTaskGraphInterface::IsRunning() || Test::bMockEngineDeleted)) + { + UE_LOG(LogWwiseConcurrency, VeryVerbose, TEXT("FWwiseExecutionQueue::StartWorkerIfNeeded (%p): No Task Graph. Do tasks now"), this); + Work(); + } + else + { + UE_LOG(LogWwiseConcurrency, VeryVerbose, TEXT("FWwiseExecutionQueue::StartWorkerIfNeeded (%p): Concurrency is not available. Starting new worker thread on Task Graph"), this); + FFunctionGraphTask::CreateAndDispatchWhenReady([this] + { + Work(); + }); + } + } + else if (ThreadPool) + { + UE_CLOG(DEBUG_WWISEEXECUTIONQUEUE, LogWwiseConcurrency, VeryVerbose, TEXT("FWwiseExecutionQueue::StartWorkerIfNeeded (%p): Starting new worker thread on thread pool %p"), this, ThreadPool); + ThreadPool->AddQueuedWork(new ExecutionQueuePoolTask([this] + { + Work(); + })); + } + else + { + UE_CLOG(DEBUG_WWISEEXECUTIONQUEUE, LogWwiseConcurrency, VeryVerbose, TEXT("FWwiseExecutionQueue::StartWorkerIfNeeded (%p): Starting new worker thread on named thread %d"), this, (int)NamedThread); + FFunctionGraphTask::CreateAndDispatchWhenReady([this] + { + Work(); + }, TStatId{}, nullptr, NamedThread); + } + } +} + +void FWwiseExecutionQueue::Work() +{ + SCOPED_WWISECONCURRENCY_EVENT_4(TEXT("FWwiseExecutionQueue::Work")); + UE_LOG(LogWwiseConcurrency, VeryVerbose, TEXT("FWwiseExecutionQueue::Work (%p): Started work on %" PRIi32), this, FPlatformTLS::GetCurrentThreadId()); + + do + { + ProcessWork(); + } + while (!StopWorkerIfDone()); +} + +bool FWwiseExecutionQueue::StopWorkerIfDone() +{ + if (!OpQueue.IsEmpty()) + { + TrySetAddOpWorkerToRunning(); + return false; + } + + const auto CurrentThreadId = FPlatformTLS::GetCurrentThreadId(); + + if (LIKELY(TrySetAddOpWorkerToRunning())) + { + // We have a new operation in the queue. + if (LIKELY(!OpQueue.IsEmpty())) + { + // Keep on chugging... + return false; + } + + if (UNLIKELY(!IWwiseConcurrencyModule::GetModule())) + { + UE_CLOG(DEBUG_WWISEEXECUTIONQUEUE, LogWwiseConcurrency, VeryVerbose, TEXT("FWwiseExecutionQueue::StartWorkerIfNeeded (%p): Concurrency is not available. Starting new worker thread on Task Graph to replace %" PRIi32), this, CurrentThreadId); + FFunctionGraphTask::CreateAndDispatchWhenReady([this] + { + Work(); + }); + } + else if (ThreadPool) + { + UE_CLOG(DEBUG_WWISEEXECUTIONQUEUE, LogWwiseConcurrency, VeryVerbose, TEXT("FWwiseExecutionQueue::StartWorkerIfNeeded (%p): Starting new worker thread on thread pool %p to replace %" PRIi32), this, ThreadPool, CurrentThreadId); + ThreadPool->AddQueuedWork(new ExecutionQueuePoolTask([this] + { + Work(); + })); + } + else + { + UE_CLOG(DEBUG_WWISEEXECUTIONQUEUE, LogWwiseConcurrency, VeryVerbose, TEXT("FWwiseExecutionQueue::StartWorkerIfNeeded (%p): Starting new task on named thread %d to replace %" PRIi32), this, (int)NamedThread, CurrentThreadId); + FFunctionGraphTask::CreateAndDispatchWhenReady([this] + { + Work(); + }, TStatId{}, nullptr, NamedThread); + } + return true; // This precise worker thread is done, we tagged teamed it to a new one. + } + + const auto bDeleteOnceClosedCopy = this->bDeleteOnceClosed; + + if (LIKELY(TrySetRunningWorkerToStopped())) + { + UE_CLOG(DEBUG_WWISEEXECUTIONQUEUE, LogWwiseConcurrency, VeryVerbose, TEXT("FWwiseExecutionQueue::StopWorkerIfDone (%p): Stopped worker on thread %" PRIi32 ), this, CurrentThreadId); + // We don't have any more operations queued. Done. + // Don't execute operations here, as the Execution Queue might be deleted here. + return true; + } + else if (LIKELY(TrySetClosingWorkerToClosed())) + { + // We were exiting and we don't have operations anymore. Immediately return, as our worker is not valid at this point. + // Don't do any operations here! + + if (bDeleteOnceClosedCopy) // We use a copy since the deletion might've already occurred + { + FFunctionGraphTask::CreateAndDispatchWhenReady([this] + { + SCOPED_WWISECONCURRENCY_EVENT_4(TEXT("FWwiseExecutionQueue::StopWorkerIfDone DeleteOnceClosed")); + UE_LOG(LogWwiseConcurrency, VeryVerbose, TEXT("FWwiseExecutionQueue::StopWorkerIfDone (%p): Auto deleting on Any Thread"), this); + delete this; + }, TStatId{}, nullptr, ENamedThreads::GameThread); + } + return true; + } + else if (LIKELY(WorkerState.Load() == EWorkerState::Closed)) + { + // We were already closed, and we had some extra operations to do somehow. + return true; + } + else if (LIKELY(WorkerState.Load() == EWorkerState::Stopped)) + { + UE_LOG(LogWwiseConcurrency, Error, TEXT("FWwiseExecutionQueue::StopWorkerIfDone (%p): Worker on thread %" PRIi32 " is stopped, but we haven't stopped it ourselves."), this, CurrentThreadId); + return true; + } + else + { + // Reiterate because we got changed midway + return false; + } +} + +void FWwiseExecutionQueue::ProcessWork() +{ + FBasicFunction Op; + if (OpQueue.Dequeue(Op)) + { + UE_CLOG(DEBUG_WWISEEXECUTIONQUEUE, LogWwiseConcurrency, VeryVerbose, TEXT("FWwiseExecutionQueue (%p): Executing async operation"), this); + Op(); + } +} + +bool FWwiseExecutionQueue::TrySetStoppedWorkerToRunning() +{ + return TryStateUpdate(EWorkerState::Stopped, EWorkerState::Running); + +} + +bool FWwiseExecutionQueue::TrySetRunningWorkerToStopped() +{ + return TryStateUpdate(EWorkerState::Running, EWorkerState::Stopped); +} + +bool FWwiseExecutionQueue::TrySetRunningWorkerToAddOp() +{ + return TryStateUpdate(EWorkerState::Running, EWorkerState::AddOp); +} + +bool FWwiseExecutionQueue::TrySetAddOpWorkerToRunning() +{ + return TryStateUpdate(EWorkerState::AddOp, EWorkerState::Running); +} + +bool FWwiseExecutionQueue::TrySetRunningWorkerToClosing() +{ + return TryStateUpdate(EWorkerState::Running, EWorkerState::Closing) + || TryStateUpdate(EWorkerState::AddOp, EWorkerState::Closing) + || TryStateUpdate(EWorkerState::Stopped, EWorkerState::Closing); + // Warning: Try not to do operations past this method returning "true". There's a slight chance "this" might be deleted! +} + +bool FWwiseExecutionQueue::TrySetClosingWorkerToClosed() +{ + return TryStateUpdate(EWorkerState::Closing, EWorkerState::Closed); + // Warning: NEVER do operations past this method returning "true". "this" is probably deleted! +} + +const TCHAR* FWwiseExecutionQueue::StateName(EWorkerState State) +{ + switch (State) + { + case EWorkerState::Stopped: return TEXT("Stopped"); + case EWorkerState::Running: return TEXT("Running"); + case EWorkerState::AddOp: return TEXT("AddOp"); + case EWorkerState::Closing: return TEXT("Closing"); + case EWorkerState::Closed: return TEXT("Closed"); + default: return TEXT("UNKNOWN"); + } +} + +bool FWwiseExecutionQueue::TryStateUpdate(EWorkerState NeededState, EWorkerState WantedState) +{ + EWorkerState PreviousState = NeededState; + bool bResult = WorkerState.CompareExchange(PreviousState, WantedState); + bResult = bResult && PreviousState == NeededState; + + UE_CLOG(DEBUG_WWISEEXECUTIONQUEUE, LogWwiseConcurrency, VeryVerbose, TEXT("FWwiseExecutionQueue (%p): %s %s [%s -> %s] %s %s [%" PRIi32 "]"), this, + StateName(PreviousState), + bResult ? TEXT("==>") : TEXT("XX>"), + StateName(NeededState), StateName(WantedState), + bResult ? TEXT("==>") : TEXT("XX>"), + bResult ? StateName(WantedState) : StateName(PreviousState), + FPlatformTLS::GetCurrentThreadId()); + return bResult; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Private/Wwise/WwiseGlobalCallbacks.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Private/Wwise/WwiseGlobalCallbacks.cpp new file mode 100644 index 0000000..e874131 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Private/Wwise/WwiseGlobalCallbacks.cpp @@ -0,0 +1,606 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseGlobalCallbacks.h" +#include "Wwise/API/WwiseSoundEngineAPI.h" +#include "Wwise/Stats/Concurrency.h" + +#include "AkUnrealHelper.h" + +FWwiseGlobalCallbacks::~FWwiseGlobalCallbacks() +{ + if (UNLIKELY(bInitialized)) + { + UE_LOG(LogWwiseConcurrency, Error, TEXT("GlobalCallbacks not terminated at destruction.")); + } +} + +bool FWwiseGlobalCallbacks::Initialize() +{ + if (UNLIKELY(bInitialized)) + { + UE_LOG(LogWwiseConcurrency, Fatal, TEXT("Global Callbacks already initialized")); + return false; + } + + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) + { + UE_LOG(LogWwiseConcurrency, Fatal, TEXT("Could not implement callbacks.")); + return false; + } + bInitialized = true; + + bool bResult = true; + AKRESULT Result; + Result = SoundEngine->RegisterGlobalCallback(&FWwiseGlobalCallbacks::OnRegisterCallbackStatic, AkGlobalCallbackLocation_Register, (void*)this); + UE_CLOG(Result != AK_Success, LogWwiseConcurrency, Error, TEXT("Cannot Register `Register` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + bResult = bResult && (Result == AK_Success); + + Result = SoundEngine->RegisterGlobalCallback(&FWwiseGlobalCallbacks::OnBeginCallbackStatic, AkGlobalCallbackLocation_Begin, (void*)this); + UE_CLOG(Result != AK_Success, LogWwiseConcurrency, Error, TEXT("Cannot Register `Begin` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + bResult = bResult && (Result == AK_Success); + + Result = SoundEngine->RegisterGlobalCallback(&FWwiseGlobalCallbacks::OnPreProcessMessageQueueForRenderCallbackStatic, AkGlobalCallbackLocation_PreProcessMessageQueueForRender, (void*)this); + UE_CLOG(Result != AK_Success, LogWwiseConcurrency, Error, TEXT("Cannot Register `PreProcessMessageQueueForRender` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + bResult = bResult && (Result == AK_Success); + + Result = SoundEngine->RegisterGlobalCallback(&FWwiseGlobalCallbacks::OnPostMessagesProcessedCallbackStatic, AkGlobalCallbackLocation_PostMessagesProcessed, (void*)this); + UE_CLOG(Result != AK_Success, LogWwiseConcurrency, Error, TEXT("Cannot Register `PostMessagesProcessed` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + bResult = bResult && (Result == AK_Success); + + Result = SoundEngine->RegisterGlobalCallback(&FWwiseGlobalCallbacks::OnBeginRenderCallbackStatic, AkGlobalCallbackLocation_BeginRender, (void*)this); + UE_CLOG(Result != AK_Success, LogWwiseConcurrency, Error, TEXT("Cannot Register `BeginRender` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + bResult = bResult && (Result == AK_Success); + + Result = SoundEngine->RegisterGlobalCallback(&FWwiseGlobalCallbacks::OnEndRenderCallbackStatic, AkGlobalCallbackLocation_EndRender, (void*)this); + UE_CLOG(Result != AK_Success, LogWwiseConcurrency, Error, TEXT("Cannot Register `EndRender` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + bResult = bResult && (Result == AK_Success); + + Result = SoundEngine->RegisterGlobalCallback(&FWwiseGlobalCallbacks::OnEndCallbackStatic, AkGlobalCallbackLocation_End, (void*)this); + UE_CLOG(Result != AK_Success, LogWwiseConcurrency, Error, TEXT("Cannot Register `End` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + bResult = bResult && (Result == AK_Success); + + Result = SoundEngine->RegisterGlobalCallback(&FWwiseGlobalCallbacks::OnTermCallbackStatic, AkGlobalCallbackLocation_Term, (void*)this); + UE_CLOG(Result != AK_Success, LogWwiseConcurrency, Error, TEXT("Cannot Register `Term` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + bResult = bResult && (Result == AK_Success); + + Result = SoundEngine->RegisterGlobalCallback(&FWwiseGlobalCallbacks::OnMonitorCallbackStatic, AkGlobalCallbackLocation_Monitor, (void*)this); + UE_CLOG(Result != AK_Success, LogWwiseConcurrency, Error, TEXT("Cannot Register `Monitor` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + bResult = bResult && (Result == AK_Success); + + Result = SoundEngine->RegisterGlobalCallback(&FWwiseGlobalCallbacks::OnMonitorRecapCallbackStatic, AkGlobalCallbackLocation_MonitorRecap, (void*)this); + UE_CLOG(Result != AK_Success, LogWwiseConcurrency, Error, TEXT("Cannot Register `MonitorRecap` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + bResult = bResult && (Result == AK_Success); + + Result = SoundEngine->RegisterGlobalCallback(&FWwiseGlobalCallbacks::OnInitCallbackStatic, AkGlobalCallbackLocation_Init, (void*)this); + UE_CLOG(Result != AK_Success, LogWwiseConcurrency, Error, TEXT("Cannot Register `Init` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + bResult = bResult && (Result == AK_Success); + + Result = SoundEngine->RegisterGlobalCallback(&FWwiseGlobalCallbacks::OnSuspendCallbackStatic, AkGlobalCallbackLocation_Suspend, (void*)this); + UE_CLOG(Result != AK_Success, LogWwiseConcurrency, Error, TEXT("Cannot Register `Suspend` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + bResult = bResult && (Result == AK_Success); + + Result = SoundEngine->RegisterGlobalCallback(&FWwiseGlobalCallbacks::OnWakeupFromSuspendCallbackStatic, AkGlobalCallbackLocation_WakeupFromSuspend, (void*)this); + UE_CLOG(Result != AK_Success, LogWwiseConcurrency, Error, TEXT("Cannot Register `WakeupFromSuspend` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + bResult = bResult && (Result == AK_Success); + + return bResult; +} + +void FWwiseGlobalCallbacks::Terminate() +{ + if (!bInitialized) + { + return; + } + + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) + { + return; + } + bInitialized = false; + + AKRESULT Result; + Result = SoundEngine->UnregisterGlobalCallback(&FWwiseGlobalCallbacks::OnRegisterCallbackStatic, AkGlobalCallbackLocation_Register); + UE_CLOG(Result != AK_Success && Result != AK_InvalidParameter, LogWwiseConcurrency, Verbose, TEXT("Cannot Unregister `Register` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + + Result = SoundEngine->UnregisterGlobalCallback(&FWwiseGlobalCallbacks::OnBeginCallbackStatic, AkGlobalCallbackLocation_Begin); + UE_CLOG(Result != AK_Success && Result != AK_InvalidParameter, LogWwiseConcurrency, Verbose, TEXT("Cannot Unregister `Begin` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + + Result = SoundEngine->UnregisterGlobalCallback(&FWwiseGlobalCallbacks::OnPreProcessMessageQueueForRenderCallbackStatic, AkGlobalCallbackLocation_PreProcessMessageQueueForRender); + UE_CLOG(Result != AK_Success && Result != AK_InvalidParameter, LogWwiseConcurrency, Verbose, TEXT("Cannot Unregister `PreProcessMessageQueueForRender` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + + Result = SoundEngine->UnregisterGlobalCallback(&FWwiseGlobalCallbacks::OnPostMessagesProcessedCallbackStatic, AkGlobalCallbackLocation_PostMessagesProcessed); + UE_CLOG(Result != AK_Success && Result != AK_InvalidParameter, LogWwiseConcurrency, Verbose, TEXT("Cannot Unregister `PostMessagesProcessed` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + + Result = SoundEngine->UnregisterGlobalCallback(&FWwiseGlobalCallbacks::OnBeginRenderCallbackStatic, AkGlobalCallbackLocation_BeginRender); + UE_CLOG(Result != AK_Success && Result != AK_InvalidParameter, LogWwiseConcurrency, Verbose, TEXT("Cannot Unregister `BeginRender` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + + Result = SoundEngine->UnregisterGlobalCallback(&FWwiseGlobalCallbacks::OnEndRenderCallbackStatic, AkGlobalCallbackLocation_EndRender); + UE_CLOG(Result != AK_Success && Result != AK_InvalidParameter, LogWwiseConcurrency, Verbose, TEXT("Cannot Unregister `EndRender` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + + Result = SoundEngine->UnregisterGlobalCallback(&FWwiseGlobalCallbacks::OnEndCallbackStatic, AkGlobalCallbackLocation_End); + UE_CLOG(Result != AK_Success && Result != AK_InvalidParameter, LogWwiseConcurrency, Verbose, TEXT("Cannot Unregister `End` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + + Result = SoundEngine->UnregisterGlobalCallback(&FWwiseGlobalCallbacks::OnTermCallbackStatic, AkGlobalCallbackLocation_Term); + UE_CLOG(Result != AK_Success && Result != AK_InvalidParameter, LogWwiseConcurrency, Verbose, TEXT("Cannot Unregister `Term` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + + Result = SoundEngine->UnregisterGlobalCallback(&FWwiseGlobalCallbacks::OnMonitorCallbackStatic, AkGlobalCallbackLocation_Monitor); + UE_CLOG(Result != AK_Success && Result != AK_InvalidParameter, LogWwiseConcurrency, Verbose, TEXT("Cannot Unregister `Monitor` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + + Result = SoundEngine->UnregisterGlobalCallback(&FWwiseGlobalCallbacks::OnMonitorRecapCallbackStatic, AkGlobalCallbackLocation_MonitorRecap); + UE_CLOG(Result != AK_Success && Result != AK_InvalidParameter, LogWwiseConcurrency, Verbose, TEXT("Cannot Unregister `MonitorRecap` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + + Result = SoundEngine->UnregisterGlobalCallback(&FWwiseGlobalCallbacks::OnInitCallbackStatic, AkGlobalCallbackLocation_Init); + UE_CLOG(Result != AK_Success && Result != AK_InvalidParameter, LogWwiseConcurrency, Verbose, TEXT("Cannot Unregister `Init` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + + Result = SoundEngine->UnregisterGlobalCallback(&FWwiseGlobalCallbacks::OnSuspendCallbackStatic, AkGlobalCallbackLocation_Suspend); + UE_CLOG(Result != AK_Success && Result != AK_InvalidParameter, LogWwiseConcurrency, Verbose, TEXT("Cannot Unregister `Suspend` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); + + Result = SoundEngine->UnregisterGlobalCallback(&FWwiseGlobalCallbacks::OnWakeupFromSuspendCallbackStatic, AkGlobalCallbackLocation_WakeupFromSuspend); + UE_CLOG(Result != AK_Success && Result != AK_InvalidParameter, LogWwiseConcurrency, Verbose, TEXT("Cannot Unregister `WakeupFromSuspend` Callback: %d (%s)"), Result, AkUnrealHelper::GetResultString(Result)); +} + +void FWwiseGlobalCallbacks::RegisterSync(FSyncFunction&& InFunction) +{ + RegisterQueue.SyncDefer(MoveTemp(InFunction)); +} + +void FWwiseGlobalCallbacks::RegisterCompletion(FCompletionPromise&& Promise) +{ + RegisterAsync([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + return EWwiseDeferredAsyncResult::Done; + }); +} + +void FWwiseGlobalCallbacks::WaitForRegister() +{ + SCOPED_WWISECONCURRENCY_EVENT_4(TEXT("FWwiseGlobalCallbacks::WaitForRegister")); + FEventRef Event; + RegisterAsync([&Event]() {Event->Trigger(); return EWwiseDeferredAsyncResult::Done; }); + Event->Wait(); + Event->Reset(); +} + +void FWwiseGlobalCallbacks::BeginSync(FSyncFunction&& InFunction) +{ + BeginQueue.SyncDefer(MoveTemp(InFunction)); +} + +void FWwiseGlobalCallbacks::BeginCompletion(FCompletionPromise&& Promise) +{ + BeginAsync([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + return EWwiseDeferredAsyncResult::Done; + }); +} + +void FWwiseGlobalCallbacks::WaitForBegin() +{ + SCOPED_WWISECONCURRENCY_EVENT_4(TEXT("FWwiseGlobalCallbacks::WaitForBegin")); + FEventRef Event; + BeginAsync([&Event]() {Event->Trigger(); return EWwiseDeferredAsyncResult::Done; }); + Event->Wait(); + Event->Reset(); +} + +void FWwiseGlobalCallbacks::PreProcessMessageQueueForRenderSync(FSyncFunction&& InFunction) +{ + PreProcessMessageQueueForRenderQueue.SyncDefer(MoveTemp(InFunction)); +} + +void FWwiseGlobalCallbacks::PreProcessMessageQueueForRenderCompletion(FCompletionPromise&& Promise) +{ + PreProcessMessageQueueForRenderAsync([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + return EWwiseDeferredAsyncResult::Done; + }); +} + +void FWwiseGlobalCallbacks::WaitForPreProcessMessageQueueForRender() +{ + SCOPED_WWISECONCURRENCY_EVENT_4(TEXT("FWwiseGlobalCallbacks::WaitForPreProcessMessageQueueForRender")); + FEventRef Event; + PreProcessMessageQueueForRenderAsync([&Event]() {Event->Trigger(); return EWwiseDeferredAsyncResult::Done; }); + Event->Wait(); + Event->Reset(); +} + +void FWwiseGlobalCallbacks::PostMessagesProcessedSync(FSyncFunction&& InFunction) +{ + PostMessagesProcessedQueue.SyncDefer(MoveTemp(InFunction)); +} + +void FWwiseGlobalCallbacks::PostMessagesProcessedCompletion(FCompletionPromise&& Promise) +{ + PostMessagesProcessedAsync([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + return EWwiseDeferredAsyncResult::Done; + }); +} + +void FWwiseGlobalCallbacks::WaitForPostMessagesProcessed() +{ + SCOPED_WWISECONCURRENCY_EVENT_4(TEXT("FWwiseGlobalCallbacks::WaitForPostMessagesProcessed")); + FEventRef Event; + PostMessagesProcessedAsync([&Event]() {Event->Trigger(); return EWwiseDeferredAsyncResult::Done; }); + Event->Wait(); + Event->Reset(); +} + +void FWwiseGlobalCallbacks::BeginRenderSync(FSyncFunction&& InFunction) +{ + BeginRenderQueue.SyncDefer(MoveTemp(InFunction)); +} + +void FWwiseGlobalCallbacks::BeginRenderCompletion(FCompletionPromise&& Promise) +{ + BeginRenderAsync([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + return EWwiseDeferredAsyncResult::Done; + }); +} + +void FWwiseGlobalCallbacks::WaitForBeginRender() +{ + SCOPED_WWISECONCURRENCY_EVENT_4(TEXT("FWwiseGlobalCallbacks::WaitForBeginRender")); + FEventRef Event; + BeginRenderAsync([&Event]() {Event->Trigger(); return EWwiseDeferredAsyncResult::Done; }); + Event->Wait(); + Event->Reset(); +} + +void FWwiseGlobalCallbacks::EndRenderSync(FSyncFunction&& InFunction) +{ + EndRenderQueue.SyncDefer(MoveTemp(InFunction)); +} + +void FWwiseGlobalCallbacks::EndRenderCompletion(FCompletionPromise&& Promise) +{ + EndRenderAsync([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + return EWwiseDeferredAsyncResult::Done; + }); +} + +void FWwiseGlobalCallbacks::WaitForEndRender() +{ + SCOPED_WWISECONCURRENCY_EVENT_4(TEXT("FWwiseGlobalCallbacks::WaitForEndRender")); + FEventRef Event; + EndRenderAsync([&Event]() {Event->Trigger(); return EWwiseDeferredAsyncResult::Done; }); + Event->Wait(); + Event->Reset(); +} + +void FWwiseGlobalCallbacks::EndSync(FSyncFunction&& InFunction) +{ + EndQueue.SyncDefer(MoveTemp(InFunction)); +} + +void FWwiseGlobalCallbacks::EndCompletion(FCompletionPromise&& Promise, int Count) +{ + if (Count <= 0) + { + return Promise.EmplaceValue(); + } + + EndAsync([Promise = MoveTemp(Promise), Count = Count - 1]() mutable + { + auto* WwiseGlobalCallbacks = FWwiseGlobalCallbacks::Get(); + WwiseGlobalCallbacks->EndCompletion(MoveTemp(Promise), Count); + return EWwiseDeferredAsyncResult::Done; + }); +} + +void FWwiseGlobalCallbacks::WaitForEnd() +{ + SCOPED_WWISECONCURRENCY_EVENT_4(TEXT("FWwiseGlobalCallbacks::WaitForEnd")); + FEventRef Event; + EndAsync([&Event]() {Event->Trigger(); return EWwiseDeferredAsyncResult::Done; }); + Event->Wait(); + Event->Reset(); +} + +void FWwiseGlobalCallbacks::TermSync(FSyncFunction&& InFunction) +{ + TermQueue.SyncDefer(MoveTemp(InFunction)); +} + +void FWwiseGlobalCallbacks::TermCompletion(FCompletionPromise&& Promise) +{ + TermAsync([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + return EWwiseDeferredAsyncResult::Done; + }); +} + +void FWwiseGlobalCallbacks::WaitForTerm() +{ + SCOPED_WWISECONCURRENCY_EVENT_4(TEXT("FWwiseGlobalCallbacks::WaitForTerm")); + FEventRef Event; + TermAsync([&Event]() {Event->Trigger(); return EWwiseDeferredAsyncResult::Done; }); + Event->Wait(); + Event->Reset(); +} + +void FWwiseGlobalCallbacks::MonitorSync(FSyncFunction&& InFunction) +{ + MonitorQueue.SyncDefer(MoveTemp(InFunction)); +} + +void FWwiseGlobalCallbacks::MonitorCompletion(FCompletionPromise&& Promise) +{ + MonitorAsync([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + return EWwiseDeferredAsyncResult::Done; + }); +} + +void FWwiseGlobalCallbacks::WaitForMonitor() +{ + SCOPED_WWISECONCURRENCY_EVENT_4(TEXT("FWwiseGlobalCallbacks::WaitForMonitor")); + FEventRef Event; + MonitorAsync([&Event]() {Event->Trigger(); return EWwiseDeferredAsyncResult::Done; }); + Event->Wait(); + Event->Reset(); +} + +void FWwiseGlobalCallbacks::MonitorRecapSync(FSyncFunction&& InFunction) +{ + MonitorRecapQueue.SyncDefer(MoveTemp(InFunction)); +} + +void FWwiseGlobalCallbacks::MonitorRecapCompletion(FCompletionPromise&& Promise) +{ + MonitorRecapAsync([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + return EWwiseDeferredAsyncResult::Done; + }); +} + +void FWwiseGlobalCallbacks::WaitForMonitorRecap() +{ + SCOPED_WWISECONCURRENCY_EVENT_4(TEXT("FWwiseGlobalCallbacks::WaitForMonitorRecap")); + FEventRef Event; + MonitorRecapAsync([&Event]() {Event->Trigger(); return EWwiseDeferredAsyncResult::Done; }); + Event->Wait(); + Event->Reset(); +} + +void FWwiseGlobalCallbacks::InitSync(FSyncFunction&& InFunction) +{ + InitQueue.SyncDefer(MoveTemp(InFunction)); +} + +void FWwiseGlobalCallbacks::InitCompletion(FCompletionPromise&& Promise) +{ + InitAsync([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + return EWwiseDeferredAsyncResult::Done; + }); +} + +void FWwiseGlobalCallbacks::WaitForInit() +{ + SCOPED_WWISECONCURRENCY_EVENT_4(TEXT("FWwiseGlobalCallbacks::WaitForInit")); + FEventRef Event; + InitAsync([&Event]() {Event->Trigger(); return EWwiseDeferredAsyncResult::Done; }); + Event->Wait(); + Event->Reset(); +} + +void FWwiseGlobalCallbacks::SuspendSync(FSyncFunction&& InFunction) +{ + SuspendQueue.SyncDefer(MoveTemp(InFunction)); +} + +void FWwiseGlobalCallbacks::SuspendCompletion(FCompletionPromise&& Promise) +{ + SuspendAsync([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + return EWwiseDeferredAsyncResult::Done; + }); +} + +void FWwiseGlobalCallbacks::WaitForSuspend() +{ + SCOPED_WWISECONCURRENCY_EVENT_4(TEXT("FWwiseGlobalCallbacks::WaitForSuspend")); + FEventRef Event; + SuspendAsync([&Event]() {Event->Trigger(); return EWwiseDeferredAsyncResult::Done; }); + Event->Wait(); + Event->Reset(); +} + +void FWwiseGlobalCallbacks::WakeupFromSuspendSync(FSyncFunction&& InFunction) +{ + WakeupFromSuspendQueue.SyncDefer(MoveTemp(InFunction)); +} + +void FWwiseGlobalCallbacks::WakeupFromSuspendCompletion(FCompletionPromise&& Promise) +{ + WakeupFromSuspendAsync([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + return EWwiseDeferredAsyncResult::Done; + }); +} + +void FWwiseGlobalCallbacks::WaitForWakeupFromSuspend() +{ + SCOPED_WWISECONCURRENCY_EVENT_4(TEXT("FWwiseGlobalCallbacks::WaitForWakeupFromSuspend")); + FEventRef Event; + WakeupFromSuspendAsync([&Event]() {Event->Trigger(); return EWwiseDeferredAsyncResult::Done; }); + Event->Wait(); + Event->Reset(); +} + +void FWwiseGlobalCallbacks::OnRegisterCallback(AK::IAkGlobalPluginContext* in_pContext) +{ + RegisterQueue.Run(in_pContext); +} + +void FWwiseGlobalCallbacks::OnBeginCallback(AK::IAkGlobalPluginContext* in_pContext) +{ + BeginQueue.Run(in_pContext); +} + +void FWwiseGlobalCallbacks::OnPreProcessMessageQueueForRenderCallback(AK::IAkGlobalPluginContext* in_pContext) +{ + PreProcessMessageQueueForRenderQueue.Run(in_pContext); +} + +void FWwiseGlobalCallbacks::OnPostMessagesProcessedCallback(AK::IAkGlobalPluginContext* in_pContext) +{ + PostMessagesProcessedQueue.Run(in_pContext); +} + +void FWwiseGlobalCallbacks::OnBeginRenderCallback(AK::IAkGlobalPluginContext* in_pContext) +{ + BeginRenderQueue.Run(in_pContext); +} + +void FWwiseGlobalCallbacks::OnEndRenderCallback(AK::IAkGlobalPluginContext* in_pContext) +{ + EndRenderQueue.Run(in_pContext); +} + +void FWwiseGlobalCallbacks::OnEndCallback(AK::IAkGlobalPluginContext* in_pContext) +{ + EndQueue.Run(in_pContext); +} + +void FWwiseGlobalCallbacks::OnTermCallback(AK::IAkGlobalPluginContext* in_pContext) +{ + TermQueue.Run(in_pContext); +} + +void FWwiseGlobalCallbacks::OnMonitorCallback(AK::IAkGlobalPluginContext* in_pContext) +{ + MonitorQueue.Run(in_pContext); +} + +void FWwiseGlobalCallbacks::OnMonitorRecapCallback(AK::IAkGlobalPluginContext* in_pContext) +{ + MonitorRecapQueue.Run(in_pContext); +} + +void FWwiseGlobalCallbacks::OnInitCallback(AK::IAkGlobalPluginContext* in_pContext) +{ + InitQueue.Run(in_pContext); +} + +void FWwiseGlobalCallbacks::OnSuspendCallback(AK::IAkGlobalPluginContext* in_pContext) +{ + SuspendQueue.Run(in_pContext); +} + +void FWwiseGlobalCallbacks::OnWakeupFromSuspendCallback(AK::IAkGlobalPluginContext* in_pContext) +{ + WakeupFromSuspendQueue.Run(in_pContext); +} + +void FWwiseGlobalCallbacks::OnRegisterCallbackStatic(AK::IAkGlobalPluginContext* in_pContext, + AkGlobalCallbackLocation in_eLocation, void* in_pCookie) +{ + static_cast(in_pCookie)->OnRegisterCallback(in_pContext); +} + +void FWwiseGlobalCallbacks::OnBeginCallbackStatic(AK::IAkGlobalPluginContext* in_pContext, + AkGlobalCallbackLocation in_eLocation, void* in_pCookie) +{ + static_cast(in_pCookie)->OnBeginCallback(in_pContext); +} + +void FWwiseGlobalCallbacks::OnPreProcessMessageQueueForRenderCallbackStatic(AK::IAkGlobalPluginContext* in_pContext, + AkGlobalCallbackLocation in_eLocation, void* in_pCookie) +{ + static_cast(in_pCookie)->OnPreProcessMessageQueueForRenderCallback(in_pContext); +} + +void FWwiseGlobalCallbacks::OnPostMessagesProcessedCallbackStatic(AK::IAkGlobalPluginContext* in_pContext, + AkGlobalCallbackLocation in_eLocation, void* in_pCookie) +{ + static_cast(in_pCookie)->OnPostMessagesProcessedCallback(in_pContext); +} + +void FWwiseGlobalCallbacks::OnBeginRenderCallbackStatic(AK::IAkGlobalPluginContext* in_pContext, + AkGlobalCallbackLocation in_eLocation, void* in_pCookie) +{ + static_cast(in_pCookie)->OnBeginRenderCallback(in_pContext); +} + +void FWwiseGlobalCallbacks::OnEndRenderCallbackStatic(AK::IAkGlobalPluginContext* in_pContext, + AkGlobalCallbackLocation in_eLocation, void* in_pCookie) +{ + static_cast(in_pCookie)->OnEndRenderCallback(in_pContext); +} + +void FWwiseGlobalCallbacks::OnEndCallbackStatic(AK::IAkGlobalPluginContext* in_pContext, + AkGlobalCallbackLocation in_eLocation, void* in_pCookie) +{ + static_cast(in_pCookie)->OnEndCallback(in_pContext); +} + +void FWwiseGlobalCallbacks::OnTermCallbackStatic(AK::IAkGlobalPluginContext* in_pContext, + AkGlobalCallbackLocation in_eLocation, void* in_pCookie) +{ + static_cast(in_pCookie)->OnTermCallback(in_pContext); +} + +void FWwiseGlobalCallbacks::OnMonitorCallbackStatic(AK::IAkGlobalPluginContext* in_pContext, + AkGlobalCallbackLocation in_eLocation, void* in_pCookie) +{ + static_cast(in_pCookie)->OnMonitorCallback(in_pContext); +} + +void FWwiseGlobalCallbacks::OnMonitorRecapCallbackStatic(AK::IAkGlobalPluginContext* in_pContext, + AkGlobalCallbackLocation in_eLocation, void* in_pCookie) +{ + static_cast(in_pCookie)->OnMonitorRecapCallback(in_pContext); +} + +void FWwiseGlobalCallbacks::OnInitCallbackStatic(AK::IAkGlobalPluginContext* in_pContext, + AkGlobalCallbackLocation in_eLocation, void* in_pCookie) +{ + static_cast(in_pCookie)->OnInitCallback(in_pContext); +} + +void FWwiseGlobalCallbacks::OnSuspendCallbackStatic(AK::IAkGlobalPluginContext* in_pContext, + AkGlobalCallbackLocation in_eLocation, void* in_pCookie) +{ + static_cast(in_pCookie)->OnSuspendCallback(in_pContext); +} + +void FWwiseGlobalCallbacks::OnWakeupFromSuspendCallbackStatic(AK::IAkGlobalPluginContext* in_pContext, + AkGlobalCallbackLocation in_eLocation, void* in_pCookie) +{ + static_cast(in_pCookie)->OnWakeupFromSuspendCallback(in_pContext); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Public/Wwise/Stats/Concurrency.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Public/Wwise/Stats/Concurrency.h new file mode 100644 index 0000000..5b6119c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Public/Wwise/Stats/Concurrency.h @@ -0,0 +1,43 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Stats/Stats.h" +#include "Wwise/Stats/NamedEvents.h" + +DECLARE_STATS_GROUP(TEXT("Concurrency"), STATGROUP_WwiseConcurrency, STATCAT_Wwise); + +DECLARE_CYCLE_STAT_EXTERN(TEXT("Waiting for completion in Game Thread"), STAT_WwiseConcurrencyGameThreadWait, STATGROUP_WwiseConcurrency, WWISECONCURRENCY_API); +DECLARE_CYCLE_STAT_EXTERN(TEXT("Waiting for completion"), STAT_WwiseConcurrencyWait, STATGROUP_WwiseConcurrency, WWISECONCURRENCY_API); +DECLARE_CYCLE_STAT_EXTERN(TEXT("Executing asynchronous op"), STAT_WwiseConcurrencyAsync, STATGROUP_WwiseConcurrency, WWISECONCURRENCY_API); +DECLARE_CYCLE_STAT_EXTERN(TEXT("Executing blocking synchronous op"), STAT_WwiseConcurrencySync, STATGROUP_WwiseConcurrency, WWISECONCURRENCY_API); +DECLARE_CYCLE_STAT_EXTERN(TEXT("Executing op in Game Thread"), STAT_WwiseConcurrencyGameThread, STATGROUP_WwiseConcurrency, WWISECONCURRENCY_API); + +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Number of ExecutionQueues"), STAT_WwiseExecutionQueues, STATGROUP_WwiseConcurrency, WWISECONCURRENCY_API); +DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("ExecutionQueue Async calls"), STAT_WwiseExecutionQueueAsyncCalls, STATGROUP_WwiseConcurrency, WWISECONCURRENCY_API); +DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("ExecutionQueue AsyncWait calls"), STAT_WwiseExecutionQueueAsyncWaitCalls, STATGROUP_WwiseConcurrency, WWISECONCURRENCY_API); + +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Number of Futures"), STAT_WwiseFutures, STATGROUP_WwiseConcurrency, WWISECONCURRENCY_API); +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Number of Futures with Events"), STAT_WwiseFuturesWithEvent, STATGROUP_WwiseConcurrency, WWISECONCURRENCY_API); + +WWISECONCURRENCY_API DECLARE_LOG_CATEGORY_EXTERN(LogWwiseConcurrency, Log, All); + +#define SCOPED_WWISECONCURRENCY_EVENT(Text) SCOPED_WWISE_NAMED_EVENT(TEXT("WwiseConcurrency"), Text) +#define SCOPED_WWISECONCURRENCY_EVENT_4(Text) SCOPED_WWISE_NAMED_EVENT_4(TEXT("WwiseConcurrency"), Text) +#define SCOPED_WWISECONCURRENCY_EVENT_F(Format, ...) SCOPED_WWISE_NAMED_EVENT_F(TEXT("WwiseConcurrency"), Format, __VA_ARGS__) +#define SCOPED_WWISECONCURRENCY_EVENT_F_4(Format, ...) SCOPED_WWISE_NAMED_EVENT_F_4(TEXT("WwiseConcurrency"), Format, __VA_ARGS__) diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Public/Wwise/WwiseConcurrencyModule.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Public/Wwise/WwiseConcurrencyModule.h new file mode 100644 index 0000000..2cffaab --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Public/Wwise/WwiseConcurrencyModule.h @@ -0,0 +1,91 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Modules/ModuleManager.h" +#include "Misc/ConfigCacheIni.h" + +#include "AkInclude.h" + +class FQueuedThreadPool; +class FWwiseGlobalCallbacks; + +class IWwiseConcurrencyModule : public IModuleInterface +{ +public: + static FName GetModuleName() + { + static const FName ModuleName = GetModuleNameFromConfig(); + return ModuleName; + } + + /** + * Checks to see if this module is loaded and ready. + * + * @return True if the module is loaded and ready to use + */ + static bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded(GetModuleName()); + } + + static IWwiseConcurrencyModule* GetModule() + { + const auto ModuleName = GetModuleName(); + if (ModuleName.IsNone()) + { + return nullptr; + } + + FModuleManager& ModuleManager = FModuleManager::Get(); + IWwiseConcurrencyModule* Result = ModuleManager.GetModulePtr(ModuleName); + if (UNLIKELY(!Result)) + { + if (UNLIKELY(IsEngineExitRequested())) + { + UE_LOG(LogLoad, Verbose, TEXT("Skipping reloading missing WwiseConcurrency module: Exiting.")); + } + else if (UNLIKELY(!IsInGameThread())) + { + UE_LOG(LogLoad, Warning, TEXT("Skipping loading missing WwiseConcurrency module: Not in game thread")); + } + else + { + UE_LOG(LogLoad, Log, TEXT("Loading WwiseConcurrency module: %s"), *ModuleName.GetPlainNameString()); + Result = ModuleManager.LoadModulePtr(ModuleName); + if (UNLIKELY(!Result)) + { + UE_LOG(LogLoad, Fatal, TEXT("Could not load WwiseConcurrency module: %s not found"), *ModuleName.GetPlainNameString()); + } + } + } + + return Result; + } + + virtual FQueuedThreadPool* GetExecutionQueueThreadPool() = 0; + virtual FWwiseGlobalCallbacks* GetGlobalCallbacks() = 0; + +private: + static inline FName GetModuleNameFromConfig() + { + FString ModuleName = TEXT("WwiseConcurrency"); + GConfig->GetString(TEXT("Audio"), TEXT("WwiseConcurrencyModuleName"), ModuleName, GEngineIni); + return FName(ModuleName); + } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Public/Wwise/WwiseConcurrencyModuleImpl.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Public/Wwise/WwiseConcurrencyModuleImpl.h new file mode 100644 index 0000000..cfa229d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Public/Wwise/WwiseConcurrencyModuleImpl.h @@ -0,0 +1,45 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseConcurrencyModule.h" + +class WWISECONCURRENCY_API FWwiseConcurrencyModule : public IWwiseConcurrencyModule +{ +public: + FWwiseConcurrencyModule(); + + void StartupModule() override; + void ShutdownModule() override; + + FQueuedThreadPool* GetExecutionQueueThreadPool() override; + FWwiseGlobalCallbacks* GetGlobalCallbacks() override; + +protected: + FRWLock ExecutionQueueLock; + FQueuedThreadPool* ExecutionQueueThreadPool = nullptr; + + FRWLock GlobalCallbacksLock; + FWwiseGlobalCallbacks* GlobalCallbacks = nullptr; + + virtual int32 NumberOfExecutionQueueThreadsToSpawn(); + virtual void InitializeExecutionQueueThreadPool(); + virtual void InitializeGlobalCallbacks(); + virtual void TerminateExecutionQueueThreadPool(); + virtual void TerminateGlobalCallbacks(); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Public/Wwise/WwiseDeferredQueue.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Public/Wwise/WwiseDeferredQueue.h new file mode 100644 index 0000000..89f956c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Public/Wwise/WwiseDeferredQueue.h @@ -0,0 +1,119 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseExecutionQueue.h" + +#include "WwiseDefines.h" + +namespace AK +{ + class IAkGlobalPluginContext; +} + +enum class WWISECONCURRENCY_API EWwiseDeferredAsyncResult +{ + /** + * @brief The Deferred Queue task is now done executing. Stop executing it. + */ + Done, + + /** + * @brief The Deferred Queue task is recurrent. Execute it again at next request. + */ + KeepRunning +}; + +struct WWISECONCURRENCY_API FWwiseDeferredQueue +{ + using FFunction = TUniqueFunction; + using FSyncFunction = TUniqueFunction; +#if UE_5_1_OR_LATER + DECLARE_TS_MULTICAST_DELEGATE_OneParam(FThreadSafeDelegate, AK::IAkGlobalPluginContext*); +#else + DECLARE_MULTICAST_DELEGATE_OneParam(FThreadSafeDelegate, AK::IAkGlobalPluginContext*); +#endif + DECLARE_MULTICAST_DELEGATE(FGameThreadDelegate); + + FWwiseDeferredQueue(); + ~FWwiseDeferredQueue(); + + /** + * @brief Defer execution of an operation on the next Run() or Wait() call, where a task is started, and the function is run asynchronously. + * @param InFunction The function to execute + * + * For most uses, there is no time sensitivity to a deferred operation. Every single deferred Async operation are run sequentially, however, + * they might be executed at the same time than the Sync and Game operations. Also, these will probably be called in a non-game thread, so you are + * responsible to properly bound your code if this causes issues. + * + * The return value of the passed function should be "Done" if it was properly executed, whether there was an error or not. The return value + * should be "KeepRunning" if it should be deferred for the next callback loop. + */ + void AsyncDefer(FFunction&& InFunction); + + /** + * @brief Defer execution of an operation on the next Run() or Wait() call, where the function is immediately called in the Run() or Wait() thread. + * @param InFunction The function to execute, synchronous to the Run() thread. + * + * @warning Since this function will be called synchronously to the Run() call, if the running thread is time-sensitive, it can cause glitches + * or hitches. In most cases, you should use DeferAsync or DeferGame. + * + * This is required for time-sensitive issues, such as waiting for an operation that absolutely needs to be done before the Run() thread gains + * control back. + */ + void SyncDefer(FSyncFunction&& InFunction); + + /** + * @brief Defer execution of an operation on the next Run() or Wait() call, where the function is subsequently run on the Game Thread. + * @param InFunction The function to execute + * + * For most game uses, there is no time sensitivity to a deferred operation, but you might want to notify game objects when an event occurs. + * + * Every single deferred Game operation are run sequentially, however, they might be executed at the same time than the other Async and Sync + * operations. + */ + void GameDefer(FFunction&& InFunction); + + + void Run(AK::IAkGlobalPluginContext* InContext); + void Wait(); + bool IsEmpty() const { return AsyncOpQueue.IsEmpty() && SyncOpQueue.IsEmpty() && GameOpQueue.IsEmpty(); } + + FGameThreadDelegate OnGameRun; + FThreadSafeDelegate OnSyncRunTS; + +protected: + FWwiseExecutionQueue AsyncExecutionQueue; + + using FOps = TQueue; + using FSyncOps = TQueue; + FOps AsyncOpQueue; + FSyncOps SyncOpQueue; + FOps GameOpQueue; + TAtomic GameThreadExecuting {0}; + bool bSyncThreadDone = false; + bool bClosing = false; + AK::IAkGlobalPluginContext* Context { nullptr }; + +private: + void AsyncExec(); + void SyncExec(); + void SyncExecLoop(); + void GameThreadExec(); + void GameThreadExecLoop(); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Public/Wwise/WwiseExecutionQueue.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Public/Wwise/WwiseExecutionQueue.h new file mode 100644 index 0000000..b127df3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Public/Wwise/WwiseExecutionQueue.h @@ -0,0 +1,164 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Async/TaskGraphInterfaces.h" +#include "Containers/Queue.h" +#include "Misc/DateTime.h" +#include "Misc/QueuedThreadPool.h" + +/** + * @brief Asynchronous sequential execution queue. + * + * The goal of the Execution Queue is to call asynchronous operations sequentially. In effect, + * this creates the equivalent of a GCD's Dispatch Queue, where operations are known to be executed + * one after the other, but asynchronously. +*/ +struct WWISECONCURRENCY_API FWwiseExecutionQueue +{ + const ENamedThreads::Type NamedThread; + FQueuedThreadPool * const ThreadPool; + const bool bOwnedPool; + + using FBasicFunction = TUniqueFunction; + + /** + * @brief Starts a new Execution Queue running in a particular Named Thread + * @param InNamedThread The Named Thread that will run the Execution Queue + */ + FWwiseExecutionQueue(ENamedThreads::Type InNamedThread); + + /** + * @brief Starts a new Execution Queue running in a particular Thread Pool + * @param InThreadPool The Thread Pool that will run the Execution Queue. Using nullptr will run it in the + * Wwise Execution Queue's default thread pool. + */ + FWwiseExecutionQueue(FQueuedThreadPool* InThreadPool = nullptr); + + /** + * @brief Starts a new Execution Queue running in a new, owned Thread Pool (with 1 thread) exclusive to this Execution Queue. + * @param InThreadName Thread Name + * @param InThreadPriority Thread Priority of the new Thread Pool + * @param InStackSize Stack size for new Thread Pool + */ + FWwiseExecutionQueue(const TCHAR* InThreadName, EThreadPriority InThreadPriority = EThreadPriority::TPri_Normal, int32 InStackSize = 128 * 1024); + + /** + * @brief Destructor for Execution Queue. + * + * This will lock the calling thread until the Execution Queue is actually closed. + * + * If the Execution Queue can be deleted while executing its own Async operation, you need to call CloseAndDelete() instead + * of deleting the Execution Queue yourself. + */ + ~FWwiseExecutionQueue(); + + /** + * @brief Calls a function asynchronously in the Execution Queue. + * @param InFunction The function to be called. + * + * Async usually calls the passed function asynchronously. However, in deletion instances or exiting instances, this + * will be called synchronously. If you absolutely need this call to be done asynchronously (it might not be called), + * you should call AsyncAlways instead. + */ + void Async(FBasicFunction&& InFunction); + /** + * @brief Calls a function asynchronously in the Execution Queue. If no Execution Queue is available, it will be called + * on any Task Graph thread. + * @param InFunction The function to be called. + * + * Execute the function asynchronously, or on the Task Graph, but never synchronously. + */ + void AsyncAlways(FBasicFunction&& InFunction); + + /** + * @brief Calls a function asynchronously in the Execution Queue, and then wait for it to be called. + * @param InFunction The function to be called. + */ + void AsyncWait(FBasicFunction&& InFunction); + + + /** + * @brief Permanently closes the Execution Queue, ensuring every Async operations are done processing before closing. + * + * This gets called by the destructor. This will lock the calling thread until the Execution Queue is actually closed. + */ + void Close(); + + /** + * @brief Permanently closes the Execution Queue once it's done processing asynchronously. Then, deletes the Execution Queue pointer. + * + * This is the way to destroy an Execution Queue whose destruction can be achieved through one of its own function. For example, + * an object that async a structure cleanup, and then removes itself once it's possible to delete it. + */ + void CloseAndDelete(); + + bool IsBeingClosed() const; + bool IsClosed() const; + + static FQueuedThreadPool* GetDefaultThreadPool(); + +private: + class ExecutionQueuePoolTask; + + enum class EWorkerState + { + Stopped, ///< Idle + Running, ///< There is a thread owner executing operations + AddOp, ///< While a thread is running, a producer is adding operations + Closing, ///< While a thread is running, the last producer is asking to permanently close the Execution Queue + Closed ///< Execution Queue is permanently closed. It can be deleted. + }; + TAtomic WorkerState{ EWorkerState::Stopped }; + bool bDeleteOnceClosed{ false }; + + using FOpQueue = TQueue; + FOpQueue OpQueue; + + void StartWorkerIfNeeded(); + void Work(); + bool StopWorkerIfDone(); + void ProcessWork(); + bool TrySetStoppedWorkerToRunning(); + bool TrySetRunningWorkerToStopped(); + bool TrySetRunningWorkerToAddOp(); + bool TrySetAddOpWorkerToRunning(); + bool TrySetRunningWorkerToClosing(); + bool TrySetClosingWorkerToClosed(); + + static const TCHAR* StateName(EWorkerState State); + FORCEINLINE bool TryStateUpdate(EWorkerState NeededState, EWorkerState WantedState); + +private: + FWwiseExecutionQueue(const FWwiseExecutionQueue& Rhs) = delete; + FWwiseExecutionQueue(FWwiseExecutionQueue&& Rhs) = delete; + FWwiseExecutionQueue& operator=(const FWwiseExecutionQueue& Rhs) = delete; + FWwiseExecutionQueue& operator=(FWwiseExecutionQueue&& Rhs) = delete; + +public: + struct WWISECONCURRENCY_API Test + { +#if defined(WITH_LOW_LEVEL_TESTS) && WITH_LOW_LEVEL_TESTS || defined(WITH_AUTOMATION_TESTS) || (WITH_DEV_AUTOMATION_TESTS || WITH_PERF_AUTOMATION_TESTS) + static bool bMockEngineDeletion; + static bool bMockEngineDeleted; +#else + static constexpr const auto bMockEngineDeletion{ false }; + static constexpr const auto bMockEngineDeleted{ false }; +#endif + }; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Public/Wwise/WwiseFuture.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Public/Wwise/WwiseFuture.h new file mode 100644 index 0000000..6cf8265 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Public/Wwise/WwiseFuture.h @@ -0,0 +1,1383 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreTypes.h" +#include "Misc/AssertionMacros.h" +#include "Templates/UnrealTemplate.h" +#include "Templates/Function.h" +#include "Misc/Timespan.h" +#include "Templates/SharedPointer.h" +#include "Misc/DateTime.h" +#include "HAL/Event.h" +#include "HAL/PlatformProcess.h" +#include "Misc/ScopeLock.h" + +#include "Wwise/Stats/Concurrency.h" +#include "Wwise/Stats/AsyncStats.h" + +// +// WwiseFuture is a replica of Unreal's Async/Future.h code, with optimizations on the FutureState to reduce the +// amount of FCriticalSection and FEvent to the bare minimum. Most of Wwise Integrations code uses the "Then" paradigm +// of Futures/Promises instead of the Wait paradigm. Even when waiting a result, it is usually ready for usage. +// +// This means there is a very strong chance a Future doesn't need to retrieve an Event from its pool of resources. +// +// Since some platforms have set amount of hardware synchronization primitites, it's preferable to reduce the number of +// them to a minimum. Although they are useful for actually waiting for a longer operation to finish in a particular +// thread, even then, there will usually be one lengthy operation, and all the subsequent operations will be done +// instantaneously without a lock. +// + + + +/** + * Base class for the internal state of asynchronous return values (futures). + */ +class FWwiseFutureState +{ +public: + + /** Default constructor. */ + FWwiseFutureState() + : CompletionCallback(nullptr) + , CompletionEvent(nullptr) + , Complete(false) + { + ASYNC_INC_DWORD_STAT(STAT_WwiseFutures); + } + + /** + * Create and initialize a new instance with a callback. + * + * @param InCompletionCallback A function that is called when the state is completed. + */ + FWwiseFutureState(TUniqueFunction&& InCompletionCallback) + : CompletionCallback(InCompletionCallback ? new TUniqueFunction(MoveTemp(InCompletionCallback)) : nullptr) + , CompletionEvent(nullptr) + , Complete(false) + { + ASYNC_INC_DWORD_STAT(STAT_WwiseFutures); + } + + /** Destructor. */ + ~FWwiseFutureState() + { + ASYNC_DEC_DWORD_STAT(STAT_WwiseFutures); + if (CompletionCallback) + { + delete CompletionCallback; + CompletionCallback = nullptr; + } + if (CompletionEvent) + { + FPlatformProcess::ReturnSynchEventToPool(CompletionEvent); + CompletionEvent = nullptr; + ASYNC_DEC_DWORD_STAT(STAT_WwiseFuturesWithEvent); + } + } + +public: + + /** + * Checks whether the asynchronous result has been set. + * + * @return true if the result has been set, false otherwise. + * @see WaitFor + */ + bool IsComplete() const + { + return Complete.Load(EMemoryOrder::SequentiallyConsistent); + } + + /** + * Blocks the calling thread until the future result is available. + * + * @param Duration The maximum time span to wait for the future result. + * @return true if the result is available, false otherwise. + * @see IsComplete + */ + bool WaitFor(const FTimespan& Duration) const + { + if (IsComplete()) + { + return true; + } + + SCOPED_WWISECONCURRENCY_EVENT_4(TEXT("FWwiseFutureState::WaitFor")); + FEvent* CompletionEventToUse = CompletionEvent.Load(EMemoryOrder::SequentiallyConsistent); + if (!CompletionEventToUse) + { + auto* NewCompletionEvent = FPlatformProcess::GetSynchEventFromPool(true); + if (CompletionEvent.CompareExchange(CompletionEventToUse, NewCompletionEvent)) + { + CompletionEventToUse = NewCompletionEvent; + ASYNC_INC_DWORD_STAT(STAT_WwiseFuturesWithEvent); + } + else + { + FPlatformProcess::ReturnSynchEventToPool(NewCompletionEvent); + } + + if (IsComplete()) + { + return true; + } + } + + const bool bIsInGameThread = IsInGameThread(); + CONDITIONAL_SCOPE_CYCLE_COUNTER(STAT_WwiseConcurrencyGameThreadWait, bIsInGameThread); + CONDITIONAL_SCOPE_CYCLE_COUNTER(STAT_WwiseConcurrencyWait, !bIsInGameThread); + if (CompletionEventToUse->Wait(Duration)) + { + return true; + } + + return false; + } + + /** + * Set a continuation to be called on completion of the promise + * @param Continuation + */ + void SetContinuation(TUniqueFunction&& Continuation) + { + if (IsComplete()) + { + if (Continuation) + { + Continuation(); + } + return; + } + + // Store the Copy to the CompletionCallback + auto Copy = Continuation ? new TUniqueFunction(MoveTemp(Continuation)) : nullptr; + auto OldCopy = CompletionCallback.Exchange(Copy); + check(!OldCopy); // We can only execute one continuation per WwiseFuture. + + if (!IsComplete()) + { + return; + } + + // We are already complete. See if we need to execute ourselves. + Copy = CompletionCallback.Exchange(nullptr); + if (Copy) + { + (*Copy)(); + delete Copy; + } + } + +protected: + + /** Notifies any waiting threads that the result is available. */ + void MarkComplete() + { + Complete.Store(true, EMemoryOrder::SequentiallyConsistent); + + FEvent* Event = CompletionEvent.Load(EMemoryOrder::SequentiallyConsistent); + auto* Continuation = CompletionCallback.Exchange(nullptr); + + if (Event) + { + Event->Trigger(); + } + if (Continuation) + { + (*Continuation)(); + delete Continuation; + } + } + +private: + /** An optional callback function that is executed the state is completed. */ + TAtomic< TUniqueFunction* > CompletionCallback; + + /** Holds an event signaling that the result is available. */ + mutable TAtomic< FEvent* > CompletionEvent; + + /** Whether the asynchronous result is available. */ + TAtomic Complete; +}; + + +/** + * Implements the internal state of asynchronous return values (futures). + */ +template +class TWwiseFutureState + : public FWwiseFutureState +{ +public: + + /** Default constructor. */ + TWwiseFutureState() + : FWwiseFutureState() + { } + + ~TWwiseFutureState() + { + if (IsComplete()) + { + DestructItem(Result.GetTypedPtr()); + } + } + + /** + * Create and initialize a new instance with a callback. + * + * @param CompletionCallback A function that is called when the state is completed. + */ + TWwiseFutureState(TUniqueFunction&& CompletionCallback) + : FWwiseFutureState(MoveTemp(CompletionCallback)) + { } + +public: + + /** + * Gets the result (will block the calling thread until the result is available). + * + * @return The result value. + * @see EmplaceResult + */ + const InternalResultType& GetResult() const + { + while (!IsComplete()) + { + WaitFor(FTimespan::MaxValue()); + } + + return *Result.GetTypedPtr(); + } + + /** + * Sets the result and notifies any waiting threads. + * + * @param InResult The result to set. + * @see GetResult + */ + template + void EmplaceResult(ArgTypes&&... Args) + { + check(!IsComplete()); + new(Result.GetTypedPtr()) InternalResultType(Forward(Args)...); + MarkComplete(); + } + +private: + + /** Holds the asynchronous result. */ + TTypeCompatibleBytes Result; +}; + +/* TWwiseFuture +*****************************************************************************/ + +/** + * Abstract base template for futures and shared futures. + */ +template +class TWwiseFutureBase +{ +public: + + /** + * Checks whether this future object has its value set. + * + * @return true if this future has a shared state and the value has been set, false otherwise. + * @see IsValid + */ + bool IsReady() const + { + return State.IsValid() ? State->IsComplete() : false; + } + + /** + * Checks whether this future object has a valid state. + * + * @return true if the state is valid, false otherwise. + * @see IsReady + */ + bool IsValid() const + { + return State.IsValid(); + } + + /** + * Blocks the calling thread until the future result is available. + * + * Note that this method may block forever if the result is never set. Use + * the WaitFor or WaitUntil methods to specify a maximum timeout for the wait. + * + * @see WaitFor, WaitUntil + */ + void Wait() const + { + if (State.IsValid()) + { + while (!WaitFor(FTimespan::MaxValue())); + } + } + + /** + * Blocks the calling thread until the future result is available or the specified duration is exceeded. + * + * @param Duration The maximum time span to wait for the future result. + * @return true if the result is available, false otherwise. + * @see Wait, WaitUntil + */ + bool WaitFor(const FTimespan& Duration) const + { + return State.IsValid() ? State->WaitFor(Duration) : false; + } + + /** + * Blocks the calling thread until the future result is available or the specified time is hit. + * + * @param Time The time until to wait for the future result (in UTC). + * @return true if the result is available, false otherwise. + * @see Wait, WaitUntil + */ + bool WaitUntil(const FDateTime& Time) const + { + return WaitFor(Time - FDateTime::UtcNow()); + } + +protected: + + typedef TSharedPtr, ESPMode::ThreadSafe> StateType; + + /** Default constructor. */ + TWwiseFutureBase() { } + + /** + * Creates and initializes a new instance. + * + * @param InState The shared state to initialize with. + */ + TWwiseFutureBase(const StateType& InState) + : State(InState) + { } + + /** + * Protected move construction + */ + TWwiseFutureBase(TWwiseFutureBase&&) = default; + + /** + * Protected move assignment + */ + TWwiseFutureBase& operator=(TWwiseFutureBase&&) = default; + + /** + * Protected copy construction + */ + TWwiseFutureBase(const TWwiseFutureBase&) = default; + + /** + * Protected copy assignment + */ + TWwiseFutureBase& operator=(const TWwiseFutureBase&) = default; + + /** Protected destructor. */ + ~TWwiseFutureBase() { } + +protected: + + /** + * Gets the shared state object. + * + * @return The shared state object. + */ + const StateType& GetState() const + { + // if you hit this assertion then your future has an invalid state. + // this happens if you have an uninitialized future or if you moved + // it to another instance. + check(State.IsValid()); + + return State; + } + + /** + * Set a completion callback that will be called once the future completes + * or immediately if already completed + * + * @param Continuation a continuation taking an argument of type TWwiseFuture + * @return nothing at the moment but could return another future to allow future chaining + */ + template + auto Then(Func Continuation); + + /** + * Convenience wrapper for Then that + * set a completion callback that will be called once the future completes + * or immediately if already completed + * @param Continuation a continuation taking an argument of type InternalResultType + * @return nothing at the moment but could return another future to allow future chaining + */ + template + auto Next(Func Continuation); + + /** + * Reset the future. + * Reseting a future removes any continuation from its shared state and invalidates it. + * Useful for discarding yet to be completed future cleanly. + */ + void Reset() + { + if (IsValid()) + { + this->State->SetContinuation(nullptr); + this->State.Reset(); + } + } + +private: + + /** Holds the future's state. */ + StateType State; +}; + + +template class TWwiseSharedFuture; + +/** + * Template for unshared futures. + */ +template +class TWwiseFuture + : public TWwiseFutureBase +{ + typedef TWwiseFutureBase BaseType; + +public: + + /** Default constructor. */ + TWwiseFuture() { } + + /** + * Creates and initializes a new instance. + * + * @param InState The shared state to initialize with. + */ + TWwiseFuture(const typename BaseType::StateType& InState) + : BaseType(InState) + { } + + /** + * Move constructor. + */ + TWwiseFuture(TWwiseFuture&&) = default; + + /** + * Move assignment operator. + */ + TWwiseFuture& operator=(TWwiseFuture&& Other) = default; + + /** Destructor. */ + ~TWwiseFuture() { } + +public: + + /** + * Gets the future's result. + * + * @return The result. + */ + ResultType Get() const + { + return this->GetState()->GetResult(); + } + + /** + * Moves this future's state into a shared future. + * + * @return The shared future object. + */ + TWwiseSharedFuture Share() + { + return TWwiseSharedFuture(MoveTemp(*this)); + } + + /** + * Expose Then functionality + * @see TWwiseFutureBase + */ + using BaseType::Then; + + /** + * Expose Next functionality + * @see TWwiseFutureBase + */ + using BaseType::Next; + + /** + * Expose Reset functionality + * @see TWwiseFutureBase + */ + using BaseType::Reset; + +private: + + /** Hidden copy constructor (futures cannot be copied). */ + TWwiseFuture(const TWwiseFuture&); + + /** Hidden copy assignment (futures cannot be copied). */ + TWwiseFuture& operator=(const TWwiseFuture&); +}; + + +/** + * Template for unshared futures (specialization for reference types). + */ +template +class TWwiseFuture + : public TWwiseFutureBase +{ + typedef TWwiseFutureBase BaseType; + +public: + + /** Default constructor. */ + TWwiseFuture() { } + + /** + * Creates and initializes a new instance. + * + * @param InState The shared state to initialize with. + */ + TWwiseFuture(const typename BaseType::StateType& InState) + : BaseType(InState) + { } + + /** + * Move constructor. + */ + TWwiseFuture(TWwiseFuture&&) = default; + + /** + * Move assignment operator. + */ + TWwiseFuture& operator=(TWwiseFuture&& Other) = default; + + /** Destructor. */ + ~TWwiseFuture() { } + +public: + + /** + * Gets the future's result. + * + * @return The result. + */ + ResultType& Get() const + { + return *this->GetState()->GetResult(); + } + + /** + * Moves this future's state into a shared future. + * + * @return The shared future object. + */ + TWwiseSharedFuture Share() + { + return TWwiseSharedFuture(MoveTemp(*this)); + } + + /** + * Expose Then functionality + * @see TWwiseFutureBase + */ + using BaseType::Then; + + /** + * Expose Next functionality + * @see TWwiseFutureBase + */ + using BaseType::Next; + + /** + * Expose Reset functionality + * @see TWwiseFutureBase + */ + using BaseType::Reset; + +private: + + /** Hidden copy constructor (futures cannot be copied). */ + TWwiseFuture(const TWwiseFuture&); + + /** Hidden copy assignment (futures cannot be copied). */ + TWwiseFuture& operator=(const TWwiseFuture&); +}; + + +/** + * Template for unshared futures (specialization for void). + */ +template<> +class TWwiseFuture + : public TWwiseFutureBase +{ + typedef TWwiseFutureBase BaseType; + +public: + + /** Default constructor. */ + TWwiseFuture() { } + + /** + * Creates and initializes a new instance. + * + * @param InState The shared state to initialize with. + */ + TWwiseFuture(const BaseType::StateType& InState) + : BaseType(InState) + { } + + /** + * Move constructor. + */ + TWwiseFuture(TWwiseFuture&&) = default; + + /** + * Move assignment operator. + */ + TWwiseFuture& operator=(TWwiseFuture&& Other) = default; + + /** Destructor. */ + ~TWwiseFuture() { } + +public: + + /** + * Gets the future's result. + * + * @return The result. + */ + void Get() const + { + GetState()->GetResult(); + } + + /** + * Moves this future's state into a shared future. + * + * @return The shared future object. + */ + TWwiseSharedFuture Share(); + + /** + * Expose Then functionality + * @see TWwiseFutureBase + */ + using BaseType::Then; + + /** + * Expose Next functionality + * @see TWwiseFutureBase + */ + using BaseType::Next; + + /** + * Expose Reset functionality + * @see TWwiseFutureBase + */ + using BaseType::Reset; + +private: + + /** Hidden copy constructor (futures cannot be copied). */ + TWwiseFuture(const TWwiseFuture&); + + /** Hidden copy assignment (futures cannot be copied). */ + TWwiseFuture& operator=(const TWwiseFuture&); +}; + +/* TWwiseSharedFuture +*****************************************************************************/ + +/** + * Template for shared futures. + */ +template +class TWwiseSharedFuture + : public TWwiseFutureBase +{ + typedef TWwiseFutureBase BaseType; + +public: + + /** Default constructor. */ + TWwiseSharedFuture() { } + + /** + * Creates and initializes a new instance. + * + * @param InState The shared state to initialize from. + */ + TWwiseSharedFuture(const typename BaseType::StateType& InState) + : BaseType(InState) + { } + + /** + * Creates and initializes a new instances from a future object. + * + * @param Future The future object to initialize from. + */ + TWwiseSharedFuture(TWwiseFuture&& Future) + : BaseType(MoveTemp(Future)) + { } + + /** + * Copy constructor. + */ + TWwiseSharedFuture(const TWwiseSharedFuture&) = default; + + /** + * Copy assignment operator. + */ + TWwiseSharedFuture& operator=(const TWwiseSharedFuture& Other) = default; + + /** + * Move constructor. + */ + TWwiseSharedFuture(TWwiseSharedFuture&&) = default; + + /** + * Move assignment operator. + */ + TWwiseSharedFuture& operator=(TWwiseSharedFuture&& Other) = default; + + /** Destructor. */ + ~TWwiseSharedFuture() { } + +public: + + /** + * Gets the future's result. + * + * @return The result. + */ + ResultType Get() const + { + return this->GetState()->GetResult(); + } +}; + + +/** + * Template for shared futures (specialization for reference types). + */ +template +class TWwiseSharedFuture + : public TWwiseFutureBase +{ + typedef TWwiseFutureBase BaseType; + +public: + + /** Default constructor. */ + TWwiseSharedFuture() { } + + /** + * Creates and initializes a new instance. + * + * @param InState The shared state to initialize from. + */ + TWwiseSharedFuture(const typename BaseType::StateType& InState) + : BaseType(InState) + { } + + /** + * Creates and initializes a new instances from a future object. + * + * @param Future The future object to initialize from. + */ + TWwiseSharedFuture(TWwiseFuture&& Future) + : BaseType(MoveTemp(Future)) + { } + + /** + * Copy constructor. + */ + TWwiseSharedFuture(const TWwiseSharedFuture&) = default; + + /** Copy assignment operator. */ + TWwiseSharedFuture& operator=(const TWwiseSharedFuture& Other) = default; + + /** + * Move constructor. + */ + TWwiseSharedFuture(TWwiseSharedFuture&&) = default; + + /** + * Move assignment operator. + */ + TWwiseSharedFuture& operator=(TWwiseSharedFuture&& Other) = default; + + /** Destructor. */ + ~TWwiseSharedFuture() { } + +public: + + /** + * Gets the future's result. + * + * @return The result. + */ + ResultType& Get() const + { + return *this->GetState()->GetResult(); + } +}; + + +/** + * Template for shared futures (specialization for void). + */ +template<> +class TWwiseSharedFuture + : public TWwiseFutureBase +{ + typedef TWwiseFutureBase BaseType; + +public: + + /** Default constructor. */ + TWwiseSharedFuture() { } + + /** + * Creates and initializes a new instance from shared state. + * + * @param InState The shared state to initialize from. + */ + TWwiseSharedFuture(const BaseType::StateType& InState) + : BaseType(InState) + { } + + /** + * Creates and initializes a new instances from a future object. + * + * @param Future The future object to initialize from. + */ + TWwiseSharedFuture(TWwiseFuture&& Future) + : BaseType(MoveTemp(Future)) + { } + + /** + * Copy constructor. + */ + TWwiseSharedFuture(const TWwiseSharedFuture&) = default; + + /** + * Copy assignment operator. + */ + TWwiseSharedFuture& operator=(const TWwiseSharedFuture& Other) = default; + + /** + * Move constructor. + */ + TWwiseSharedFuture(TWwiseSharedFuture&&) = default; + + /** + * Move assignment operator. + */ + TWwiseSharedFuture& operator=(TWwiseSharedFuture&& Other) = default; + + /** Destructor. */ + ~TWwiseSharedFuture() { } + +public: + + /** + * Gets the future's result. + * + * @return The result. + */ + void Get() const + { + GetState()->GetResult(); + } +}; + + +inline TWwiseSharedFuture TWwiseFuture::Share() +{ + return TWwiseSharedFuture(MoveTemp(*this)); +} + + +/* TWwisePromise +*****************************************************************************/ + +template +class TWwisePromiseBase + : FNoncopyable +{ + typedef TSharedPtr, ESPMode::ThreadSafe> StateType; + +public: + + /** Default constructor. */ + TWwisePromiseBase() + : State(MakeShared, ESPMode::ThreadSafe>()) + { } + + /** + * Move constructor. + * + * @param Other The promise holding the shared state to move. + */ + TWwisePromiseBase(TWwisePromiseBase&& Other) + : State(MoveTemp(Other.State)) + { + Other.State.Reset(); + } + + /** + * Create and initialize a new instance with a callback. + * + * @param CompletionCallback A function that is called when the future state is completed. + */ + TWwisePromiseBase(TUniqueFunction&& CompletionCallback) + : State(MakeShared, ESPMode::ThreadSafe>(MoveTemp(CompletionCallback))) + { } + +public: + + /** Move assignment operator. */ + TWwisePromiseBase& operator=(TWwisePromiseBase&& Other) + { + State = Other.State; + Other.State.Reset(); + return *this; + } + +protected: + + /** Destructor. */ + ~TWwisePromiseBase() + { + if (State.IsValid()) + { + // if you hit this assertion then your promise never had its result + // value set. broken promises are considered programming errors. + check(State->IsComplete()); + + State.Reset(); + } + } + + /** + * Gets the shared state object. + * + * @return The shared state object. + */ + const StateType& GetState() + { + // if you hit this assertion then your promise has an invalid state. + // this happens if you move the promise to another instance. + check(State.IsValid()); + + return State; + } + +private: + + /** Holds the shared state object. */ + StateType State; +}; + + +/** + * Template for promises. + */ +template +class TWwisePromise + : public TWwisePromiseBase +{ +public: + + typedef TWwisePromiseBase BaseType; + + /** Default constructor (creates a new shared state). */ + TWwisePromise() + : BaseType() + , FutureRetrieved(false) + { } + + /** + * Move constructor. + * + * @param Other The promise holding the shared state to move. + */ + TWwisePromise(TWwisePromise&& Other) + : BaseType(MoveTemp(Other)) + , FutureRetrieved(MoveTemp(Other.FutureRetrieved)) + { } + + /** + * Create and initialize a new instance with a callback. + * + * @param CompletionCallback A function that is called when the future state is completed. + */ + TWwisePromise(TUniqueFunction&& CompletionCallback) + : BaseType(MoveTemp(CompletionCallback)) + , FutureRetrieved(false) + { } + +public: + + /** + * Move assignment operator. + * + * @param Other The promise holding the shared state to move. + */ + TWwisePromise& operator=(TWwisePromise&& Other) + { + BaseType::operator=(MoveTemp(Other)); + FutureRetrieved = MoveTemp(Other.FutureRetrieved); + + return *this; + } + +public: + + /** + * Gets a TWwiseFuture object associated with the shared state of this promise. + * + * @return The TWwiseFuture object. + */ + TWwiseFuture GetFuture() + { + check(!FutureRetrieved); + FutureRetrieved = true; + + return TWwiseFuture(this->GetState()); + } + + /** + * Sets the promised result. + * + * The result must be set only once. An assertion will + * be triggered if this method is called a second time. + * + * @param Result The result value to set. + */ + FORCEINLINE void SetValue(const ResultType& Result) + { + EmplaceValue(Result); + } + + /** + * Sets the promised result (from rvalue). + * + * The result must be set only once. An assertion will + * be triggered if this method is called a second time. + * + * @param Result The result value to set. + */ + FORCEINLINE void SetValue(ResultType&& Result) + { + EmplaceValue(MoveTemp(Result)); + } + + /** + * Sets the promised result. + * + * The result must be set only once. An assertion will + * be triggered if this method is called a second time. + * + * @param Result The result value to set. + */ + template + void EmplaceValue(ArgTypes&&... Args) + { + this->GetState()->EmplaceResult(Forward(Args)...); + } + +private: + + /** Whether a future has already been retrieved from this promise. */ + bool FutureRetrieved; +}; + + +/** + * Template for promises (specialization for reference types). + */ +template +class TWwisePromise + : public TWwisePromiseBase +{ + typedef TWwisePromiseBase BaseType; + +public: + + /** Default constructor (creates a new shared state). */ + TWwisePromise() + : BaseType() + , FutureRetrieved(false) + { } + + /** + * Move constructor. + * + * @param Other The promise holding the shared state to move. + */ + TWwisePromise(TWwisePromise&& Other) + : BaseType(MoveTemp(Other)) + , FutureRetrieved(MoveTemp(Other.FutureRetrieved)) + { } + + /** + * Create and initialize a new instance with a callback. + * + * @param CompletionCallback A function that is called when the future state is completed. + */ + TWwisePromise(TUniqueFunction&& CompletionCallback) + : BaseType(MoveTemp(CompletionCallback)) + , FutureRetrieved(false) + { } + +public: + + /** + * Move assignment operator. + * + * @param Other The promise holding the shared state to move. + */ + TWwisePromise& operator=(TWwisePromise&& Other) + { + BaseType::operator=(MoveTemp(Other)); + FutureRetrieved = MoveTemp(Other.FutureRetrieved); + + return this; + } + +public: + + /** + * Gets a TWwiseFuture object associated with the shared state of this promise. + * + * @return The TWwiseFuture object. + */ + TWwiseFuture GetFuture() + { + check(!FutureRetrieved); + FutureRetrieved = true; + + return TWwiseFuture(this->GetState()); + } + + /** + * Sets the promised result. + * + * The result must be set only once. An assertion will + * be triggered if this method is called a second time. + * + * @param Result The result value to set. + */ + FORCEINLINE void SetValue(ResultType& Result) + { + EmplaceValue(Result); + } + + /** + * Sets the promised result. + * + * The result must be set only once. An assertion will + * be triggered if this method is called a second time. + * + * @param Result The result value to set. + */ + void EmplaceValue(ResultType& Result) + { + this->GetState()->EmplaceResult(&Result); + } + +private: + + /** Whether a future has already been retrieved from this promise. */ + bool FutureRetrieved; +}; + + +/** + * Template for promises (specialization for void results). + */ +template<> +class TWwisePromise + : public TWwisePromiseBase +{ + typedef TWwisePromiseBase BaseType; + +public: + + /** Default constructor (creates a new shared state). */ + TWwisePromise() + : BaseType() + , FutureRetrieved(false) + { } + + /** + * Move constructor. + * + * @param Other The promise holding the shared state to move. + */ + TWwisePromise(TWwisePromise&& Other) + : BaseType(MoveTemp(Other)) + , FutureRetrieved(false) + { } + + /** + * Create and initialize a new instance with a callback. + * + * @param CompletionCallback A function that is called when the future state is completed. + */ + TWwisePromise(TUniqueFunction&& CompletionCallback) + : BaseType(MoveTemp(CompletionCallback)) + , FutureRetrieved(false) + { } + +public: + + /** + * Move assignment operator. + * + * @param Other The promise holding the shared state to move. + */ + TWwisePromise& operator=(TWwisePromise&& Other) + { + BaseType::operator=(MoveTemp(Other)); + FutureRetrieved = MoveTemp(Other.FutureRetrieved); + + return *this; + } + +public: + + /** + * Gets a TWwiseFuture object associated with the shared state of this promise. + * + * @return The TWwiseFuture object. + */ + TWwiseFuture GetFuture() + { + check(!FutureRetrieved); + FutureRetrieved = true; + + return TWwiseFuture(GetState()); + } + + /** + * Sets the promised result. + * + * The result must be set only once. An assertion will + * be triggered if this method is called a second time. + */ + FORCEINLINE void SetValue() + { + EmplaceValue(); + } + + /** + * Sets the promised result. + * + * The result must be set only once. An assertion will + * be triggered if this method is called a second time. + * + * @param Result The result value to set. + */ + void EmplaceValue() + { + this->GetState()->EmplaceResult(0); + } + +private: + + /** Whether a future has already been retrieved from this promise. */ + bool FutureRetrieved; +}; + +/* TWwiseFuture::Then +*****************************************************************************/ + +namespace FutureDetail +{ + /** + * Template for setting a promise value from a continuation. + */ + template + inline void SetPromiseValue(TWwisePromise& Promise, Func& Function, TWwiseFuture&& Param) + { + Promise.SetValue(Function(MoveTemp(Param))); + } + template + inline void SetPromiseValue(TWwisePromise& Promise, Func& Function, TWwiseFuture&& Param) + { + Function(MoveTemp(Param)); + Promise.SetValue(); + } +} + +// Then implementation +template +template +auto TWwiseFutureBase::Then(Func Continuation) //-> TWwiseFuture())))> +{ + check(IsValid()); + using ReturnValue = decltype(Continuation(MoveTemp(TWwiseFuture()))); + + TWwisePromise Promise; + TWwiseFuture FutureResult = Promise.GetFuture(); + TUniqueFunction Callback = [PromiseCapture = MoveTemp(Promise), ContinuationCapture = MoveTemp(Continuation), StateCapture = this->State]() mutable + { + FutureDetail::SetPromiseValue(PromiseCapture, ContinuationCapture, TWwiseFuture(MoveTemp(StateCapture))); + }; + + // This invalidate this future. + StateType MovedState = MoveTemp(this->State); + MovedState->SetContinuation(MoveTemp(Callback)); + return FutureResult; +} + +// Next implementation +template +template +auto TWwiseFutureBase::Next(Func Continuation) //-> TWwiseFuture +{ + return this->Then([Continuation = MoveTemp(Continuation)](TWwiseFuture Self) mutable + { + return Continuation(Self.Get()); + }); +} + +/** Helper to create and immediately fulfill a promise */ +template +TWwisePromise MakeFulfilledWwisePromise(ArgTypes&&... Args) +{ + TWwisePromise Promise; + Promise.EmplaceValue(Forward(Args)...); + return Promise; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Public/Wwise/WwiseGlobalCallbacks.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Public/Wwise/WwiseGlobalCallbacks.h new file mode 100644 index 0000000..73bcc86 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Public/Wwise/WwiseGlobalCallbacks.h @@ -0,0 +1,268 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseDeferredQueue.h" +#include "Wwise/WwiseConcurrencyModule.h" + +#include "AkInclude.h" + +#include "Async/Future.h" + +class WWISECONCURRENCY_API FWwiseGlobalCallbacks +{ +public: + using FCompletionPromise = TPromise; + using FAsyncFunction = FWwiseDeferredQueue::FFunction; + using FGameFunction = FWwiseDeferredQueue::FFunction; + using FSyncFunction = FWwiseDeferredQueue::FSyncFunction; + using FGameThreadDelegate = FWwiseDeferredQueue::FGameThreadDelegate; + using FThreadSafeDelegate = FWwiseDeferredQueue::FThreadSafeDelegate; + +protected: + bool bInitialized = false; + FWwiseDeferredQueue RegisterQueue; + FWwiseDeferredQueue BeginQueue; + FWwiseDeferredQueue PreProcessMessageQueueForRenderQueue; + FWwiseDeferredQueue PostMessagesProcessedQueue; + FWwiseDeferredQueue BeginRenderQueue; + FWwiseDeferredQueue EndRenderQueue; + FWwiseDeferredQueue EndQueue; + FWwiseDeferredQueue TermQueue; + FWwiseDeferredQueue MonitorQueue; + FWwiseDeferredQueue MonitorRecapQueue; + FWwiseDeferredQueue InitQueue; + FWwiseDeferredQueue SuspendQueue; + FWwiseDeferredQueue WakeupFromSuspendQueue; + +public: + static FWwiseGlobalCallbacks* Get() + { + const auto ConcurrencyModule = IWwiseConcurrencyModule::GetModule(); + if (UNLIKELY(!ConcurrencyModule)) + { + return nullptr; + } + return ConcurrencyModule->GetGlobalCallbacks(); + } + + virtual ~FWwiseGlobalCallbacks(); + + virtual bool Initialize(); + virtual void Terminate(); + bool IsInitialized() const { return bInitialized; } + + // AkGlobalCallbackLocation_Register: Right after successful registration of callback/plugin. Typically used by plugins along with AkGlobalCallbackLocation_Term for allocating memory for the lifetime of the sound engine. + void RegisterAsync(FAsyncFunction&& InFunction) { RegisterQueue.AsyncDefer(MoveTemp(InFunction)); } + void RegisterGame(FGameFunction&& InFunction) { RegisterQueue.GameDefer(MoveTemp(InFunction)); } + void RegisterSync(FSyncFunction&& InFunction); + void RegisterCompletion(FCompletionPromise&& Promise); + void WaitForRegister(); + FGameThreadDelegate& OnRegister { RegisterQueue.OnGameRun }; + FThreadSafeDelegate& OnRegisterTS { RegisterQueue.OnSyncRunTS }; + + // AkGlobalCallbackLocation_Begin: Start of audio processing. The number of frames about to be rendered depends on the sink/end-point and can be zero. + void BeginAsync(FAsyncFunction&& InFunction) { BeginQueue.AsyncDefer(MoveTemp(InFunction)); } + void BeginGame(FGameFunction&& InFunction) { BeginQueue.GameDefer(MoveTemp(InFunction)); } + void BeginSync(FSyncFunction&& InFunction); + void BeginCompletion(FCompletionPromise&& Promise); + void WaitForBegin(); + FGameThreadDelegate& OnBegin { BeginQueue.OnGameRun }; + FThreadSafeDelegate& OnBeginTS { BeginQueue.OnSyncRunTS }; + + // AkGlobalCallbackLocation_PreProcessMessageQueueForRender: Start of frame rendering, before having processed game messages. + void PreProcessMessageQueueForRenderAsync(FAsyncFunction&& InFunction) { PreProcessMessageQueueForRenderQueue.AsyncDefer(MoveTemp(InFunction)); } + void PreProcessMessageQueueForRenderGame(FGameFunction&& InFunction) { PreProcessMessageQueueForRenderQueue.GameDefer(MoveTemp(InFunction)); } + void PreProcessMessageQueueForRenderSync(FSyncFunction&& InFunction); + void PreProcessMessageQueueForRenderCompletion(FCompletionPromise&& Promise); + void WaitForPreProcessMessageQueueForRender(); + FGameThreadDelegate& OnPreProcessMessageQueueForRender { PreProcessMessageQueueForRenderQueue.OnGameRun }; + FThreadSafeDelegate& OnPreProcessMessageQueueForRenderTS { PreProcessMessageQueueForRenderQueue.OnSyncRunTS }; + + // AkGlobalCallbackLocation_PostMessagesProcessed: After one or more messages have been processed, but before updating game object and listener positions internally. + void PostMessagesProcessedAsync(FAsyncFunction&& InFunction) { PostMessagesProcessedQueue.AsyncDefer(MoveTemp(InFunction)); } + void PostMessagesProcessedGame(FGameFunction&& InFunction) { PostMessagesProcessedQueue.GameDefer(MoveTemp(InFunction)); } + void PostMessagesProcessedSync(FSyncFunction&& InFunction); + void PostMessagesProcessedCompletion(FCompletionPromise&& Promise); + void WaitForPostMessagesProcessed(); + FGameThreadDelegate& OnPostMessagesProcessed { PostMessagesProcessedQueue.OnGameRun }; + FThreadSafeDelegate& OnPostMessagesProcessedTS { PostMessagesProcessedQueue.OnSyncRunTS }; + + // AkGlobalCallbackLocation_BeginRender: Start of frame rendering, after having processed game messages. + void BeginRenderAsync(FAsyncFunction&& InFunction) { BeginRenderQueue.AsyncDefer(MoveTemp(InFunction)); } + void BeginRenderGame(FGameFunction&& InFunction) { BeginRenderQueue.GameDefer(MoveTemp(InFunction)); } + void BeginRenderSync(FSyncFunction&& InFunction); + void BeginRenderCompletion(FCompletionPromise&& Promise); + void WaitForBeginRender(); + FGameThreadDelegate& OnBeginRender { BeginRenderQueue.OnGameRun }; + FThreadSafeDelegate& OnBeginRenderTS { BeginRenderQueue.OnSyncRunTS }; + + // AkGlobalCallbackLocation_EndRender: End of frame rendering. + void EndRenderAsync(FAsyncFunction&& InFunction) { EndRenderQueue.AsyncDefer(MoveTemp(InFunction)); } + void EndRenderGame(FGameFunction&& InFunction) { EndRenderQueue.GameDefer(MoveTemp(InFunction)); } + void EndRenderSync(FSyncFunction&& InFunction); + void EndRenderCompletion(FCompletionPromise&& Promise); + void WaitForEndRender(); + FGameThreadDelegate& OnEndRender { EndRenderQueue.OnGameRun }; + FThreadSafeDelegate& OnEndRenderTS { EndRenderQueue.OnSyncRunTS }; + + // AkGlobalCallbackLocation_End: End of audio processing. + void EndAsync(FAsyncFunction&& InFunction) { EndQueue.AsyncDefer(MoveTemp(InFunction)); } + void EndGame(FGameFunction&& InFunction) { EndQueue.GameDefer(MoveTemp(InFunction)); } + void EndSync(FSyncFunction&& InFunction); + void EndCompletion(FCompletionPromise&& Promise, int Count = 1); + void WaitForEnd(); + FGameThreadDelegate& OnEnd { EndQueue.OnGameRun }; + FThreadSafeDelegate& OnEndTS { EndQueue.OnSyncRunTS }; + + // AkGlobalCallbackLocation_Term: Sound engine termination. + void TermAsync(FAsyncFunction&& InFunction) { TermQueue.AsyncDefer(MoveTemp(InFunction)); } + void TermGame(FGameFunction&& InFunction) { TermQueue.GameDefer(MoveTemp(InFunction)); } + void TermSync(FSyncFunction&& InFunction); + void TermCompletion(FCompletionPromise&& Promise); + void WaitForTerm(); + FGameThreadDelegate& OnTerm { TermQueue.OnGameRun }; + FThreadSafeDelegate& OnTermTS { TermQueue.OnSyncRunTS }; + + // AkGlobalCallbackLocation_Monitor: Send monitor data + void MonitorAsync(FAsyncFunction&& InFunction) { MonitorQueue.AsyncDefer(MoveTemp(InFunction)); } + void MonitorGame(FGameFunction&& InFunction) { MonitorQueue.GameDefer(MoveTemp(InFunction)); } + void MonitorSync(FSyncFunction&& InFunction); + void MonitorCompletion(FCompletionPromise&& Promise); + void WaitForMonitor(); + FGameThreadDelegate& OnMonitor { MonitorQueue.OnGameRun }; + FThreadSafeDelegate& OnMonitorTS { MonitorQueue.OnSyncRunTS }; + + // AkGlobalCallbackLocation_MonitorRecap: Send monitor data connection to recap. + void MonitorRecapAsync(FAsyncFunction&& InFunction) { MonitorRecapQueue.AsyncDefer(MoveTemp(InFunction)); } + void MonitorRecapGame(FGameFunction&& InFunction) { MonitorRecapQueue.GameDefer(MoveTemp(InFunction)); } + void MonitorRecapSync(FSyncFunction&& InFunction); + void MonitorRecapCompletion(FCompletionPromise&& Promise); + void WaitForMonitorRecap(); + FGameThreadDelegate& OnMonitorRecap { MonitorRecapQueue.OnGameRun }; + FThreadSafeDelegate& OnMonitorRecapTS { MonitorRecapQueue.OnSyncRunTS }; + + // AkGlobalCallbackLocation_Init: Sound engine initialization. + void InitAsync(FAsyncFunction&& InFunction) { InitQueue.AsyncDefer(MoveTemp(InFunction)); } + void InitGame(FGameFunction&& InFunction) { InitQueue.GameDefer(MoveTemp(InFunction)); } + void InitSync(FSyncFunction&& InFunction); + void InitCompletion(FCompletionPromise&& Promise); + void WaitForInit(); + FGameThreadDelegate& OnInit { InitQueue.OnGameRun }; + FThreadSafeDelegate& OnInitTS { InitQueue.OnSyncRunTS }; + + // AkGlobalCallbackLocation_Suspend: Sound engine suspension through AK::SoundEngine::Suspend + void SuspendAsync(FAsyncFunction&& InFunction) { SuspendQueue.AsyncDefer(MoveTemp(InFunction)); } + void SuspendGame(FGameFunction&& InFunction) { SuspendQueue.GameDefer(MoveTemp(InFunction)); } + void SuspendSync(FSyncFunction&& InFunction); + void SuspendCompletion(FCompletionPromise&& Promise); + void WaitForSuspend(); + FGameThreadDelegate& OnSuspend { SuspendQueue.OnGameRun }; + FThreadSafeDelegate& OnSuspendTS { SuspendQueue.OnSyncRunTS }; + + // AkGlobalCallbackLocation_WakeupFromSuspend: Sound engine awakening through AK::SoundEngine::WakeupFromSuspend + void WakeupFromSuspendAsync(FAsyncFunction&& InFunction) { WakeupFromSuspendQueue.AsyncDefer(MoveTemp(InFunction)); } + void WakeupFromSuspendGame(FGameFunction&& InFunction) { WakeupFromSuspendQueue.GameDefer(MoveTemp(InFunction)); } + void WakeupFromSuspendSync(FSyncFunction&& InFunction); + void WakeupFromSuspendCompletion(FCompletionPromise&& Promise); + void WaitForWakeupFromSuspend(); + FGameThreadDelegate& OnWakeupFromSuspend { WakeupFromSuspendQueue.OnGameRun }; + FThreadSafeDelegate& OnWakeupFromSuspendTS { WakeupFromSuspendQueue.OnSyncRunTS }; + +protected: + virtual void OnRegisterCallback(AK::IAkGlobalPluginContext* in_pContext); + virtual void OnBeginCallback(AK::IAkGlobalPluginContext* in_pContext); + virtual void OnPreProcessMessageQueueForRenderCallback(AK::IAkGlobalPluginContext* in_pContext); + virtual void OnPostMessagesProcessedCallback(AK::IAkGlobalPluginContext* in_pContext); + virtual void OnBeginRenderCallback(AK::IAkGlobalPluginContext* in_pContext); + virtual void OnEndRenderCallback(AK::IAkGlobalPluginContext* in_pContext); + virtual void OnEndCallback(AK::IAkGlobalPluginContext* in_pContext); + virtual void OnTermCallback(AK::IAkGlobalPluginContext* in_pContext); + virtual void OnMonitorCallback(AK::IAkGlobalPluginContext* in_pContext); + virtual void OnMonitorRecapCallback(AK::IAkGlobalPluginContext* in_pContext); + virtual void OnInitCallback(AK::IAkGlobalPluginContext* in_pContext); + virtual void OnSuspendCallback(AK::IAkGlobalPluginContext* in_pContext); + virtual void OnWakeupFromSuspendCallback(AK::IAkGlobalPluginContext* in_pContext); + +private: + static void OnRegisterCallbackStatic( + AK::IAkGlobalPluginContext * in_pContext, ///< Engine context. + AkGlobalCallbackLocation in_eLocation, ///< Location where this callback is fired. + void * in_pCookie ///< User cookie passed to AK::SoundEngine::RegisterGlobalCallback(). + ); + static void OnBeginCallbackStatic( + AK::IAkGlobalPluginContext * in_pContext, ///< Engine context. + AkGlobalCallbackLocation in_eLocation, ///< Location where this callback is fired. + void * in_pCookie ///< User cookie passed to AK::SoundEngine::RegisterGlobalCallback(). + ); + static void OnPreProcessMessageQueueForRenderCallbackStatic( + AK::IAkGlobalPluginContext * in_pContext, ///< Engine context. + AkGlobalCallbackLocation in_eLocation, ///< Location where this callback is fired. + void * in_pCookie ///< User cookie passed to AK::SoundEngine::RegisterGlobalCallback(). + ); + static void OnPostMessagesProcessedCallbackStatic( + AK::IAkGlobalPluginContext * in_pContext, ///< Engine context. + AkGlobalCallbackLocation in_eLocation, ///< Location where this callback is fired. + void * in_pCookie ///< User cookie passed to AK::SoundEngine::RegisterGlobalCallback(). + ); + static void OnBeginRenderCallbackStatic( + AK::IAkGlobalPluginContext * in_pContext, ///< Engine context. + AkGlobalCallbackLocation in_eLocation, ///< Location where this callback is fired. + void * in_pCookie ///< User cookie passed to AK::SoundEngine::RegisterGlobalCallback(). + ); + static void OnEndRenderCallbackStatic( + AK::IAkGlobalPluginContext * in_pContext, ///< Engine context. + AkGlobalCallbackLocation in_eLocation, ///< Location where this callback is fired. + void * in_pCookie ///< User cookie passed to AK::SoundEngine::RegisterGlobalCallback(). + ); + static void OnEndCallbackStatic( + AK::IAkGlobalPluginContext * in_pContext, ///< Engine context. + AkGlobalCallbackLocation in_eLocation, ///< Location where this callback is fired. + void * in_pCookie ///< User cookie passed to AK::SoundEngine::RegisterGlobalCallback(). + ); + static void OnTermCallbackStatic( + AK::IAkGlobalPluginContext * in_pContext, ///< Engine context. + AkGlobalCallbackLocation in_eLocation, ///< Location where this callback is fired. + void * in_pCookie ///< User cookie passed to AK::SoundEngine::RegisterGlobalCallback(). + ); + static void OnMonitorCallbackStatic( + AK::IAkGlobalPluginContext * in_pContext, ///< Engine context. + AkGlobalCallbackLocation in_eLocation, ///< Location where this callback is fired. + void * in_pCookie ///< User cookie passed to AK::SoundEngine::RegisterGlobalCallback(). + ); + static void OnMonitorRecapCallbackStatic( + AK::IAkGlobalPluginContext * in_pContext, ///< Engine context. + AkGlobalCallbackLocation in_eLocation, ///< Location where this callback is fired. + void * in_pCookie ///< User cookie passed to AK::SoundEngine::RegisterGlobalCallback(). + ); + static void OnInitCallbackStatic( + AK::IAkGlobalPluginContext * in_pContext, ///< Engine context. + AkGlobalCallbackLocation in_eLocation, ///< Location where this callback is fired. + void * in_pCookie ///< User cookie passed to AK::SoundEngine::RegisterGlobalCallback(). + ); + static void OnSuspendCallbackStatic( + AK::IAkGlobalPluginContext * in_pContext, ///< Engine context. + AkGlobalCallbackLocation in_eLocation, ///< Location where this callback is fired. + void * in_pCookie ///< User cookie passed to AK::SoundEngine::RegisterGlobalCallback(). + ); + static void OnWakeupFromSuspendCallbackStatic( + AK::IAkGlobalPluginContext * in_pContext, ///< Engine context. + AkGlobalCallbackLocation in_eLocation, ///< Location where this callback is fired. + void * in_pCookie ///< User cookie passed to AK::SoundEngine::RegisterGlobalCallback(). + ); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Tests/ExecutionQueueTests.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Tests/ExecutionQueueTests.cpp new file mode 100644 index 0000000..c7b9e6f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/Tests/ExecutionQueueTests.cpp @@ -0,0 +1,319 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseUnitTests.h" + +#if WWISE_UNIT_TESTS +#include "Wwise/WwiseExecutionQueue.h" +#include + +TEST_CASE("Audio::Wwise::Concurrency::ExecutionQueue_Smoke", "[ApplicationContextMask][SmokeFilter]") +{ + SECTION("Static") + { + static_assert(std::is_constructible::value); + static_assert(std::is_constructible::value); + static_assert(std::is_constructible::value); + static_assert(std::is_constructible::value); + static_assert(std::is_constructible::value); + static_assert(std::is_constructible::value); + static_assert(!std::is_copy_constructible::value); + static_assert(!std::is_move_constructible::value); + static_assert(!std::is_copy_assignable::value); + static_assert(!std::is_move_assignable::value); + } + + SECTION("Instantiation") + { + FWwiseExecutionQueue NoParam; + FWwiseExecutionQueue NamedThread(ENamedThreads::AnyThread); + FWwiseExecutionQueue Pool(GThreadPool); + FWwiseExecutionQueue NewPool(TEXT("New Execution Queue")); + + CHECK(NoParam.ThreadPool); + CHECK(NoParam.NamedThread == ENamedThreads::UnusedAnchor); + CHECK(NoParam.ThreadPool == FWwiseExecutionQueue::GetDefaultThreadPool()); + + CHECK_FALSE(NamedThread.ThreadPool); + CHECK_FALSE(NamedThread.NamedThread == ENamedThreads::UnusedAnchor); + + CHECK(Pool.NamedThread == ENamedThreads::UnusedAnchor); + CHECK_FALSE(Pool.bOwnedPool); + CHECK(Pool.ThreadPool); + + CHECK(NewPool.NamedThread == ENamedThreads::UnusedAnchor); + CHECK(NewPool.bOwnedPool); + CHECK(NewPool.ThreadPool); + CHECK(NewPool.ThreadPool && NewPool.ThreadPool->GetNumThreads() == 1); + } + + SECTION("Async At Destructor") + { + constexpr const int LoopCount = 10; + std::atomic Value{ 0 }; + { + FWwiseExecutionQueue ExecutionQueue; + for (int i = 0; i < LoopCount; ++i) + { + ExecutionQueue.Async([&Value] + { + ++Value; + }); + } + } + CHECK(Value.load() == LoopCount); + } + + SECTION("AsyncWait") + { + constexpr const int LoopCount = 10; + std::atomic Value{ 0 }; + { + const auto CurrentThreadId = FPlatformTLS::GetCurrentThreadId(); + FWwiseExecutionQueue ExecutionQueue; + for (int i = 0; i < LoopCount; ++i) + { + ExecutionQueue.AsyncWait([&Value, CurrentThreadId] + { + CHECK_FALSE(CurrentThreadId == FPlatformTLS::GetCurrentThreadId()); + ++Value; + }); + } + CHECK(Value.load() == LoopCount); + } + } + + SECTION("Async in order") + { + constexpr const int LoopCount = 10; + std::atomic Value{ 0 }; + { + FWwiseExecutionQueue ExecutionQueue; + for (int i = 0; i < LoopCount; ++i) + { + ExecutionQueue.Async([&Value, ShouldBe = i] + { + CHECK(Value++ == ShouldBe); + }); + } + } + CHECK(Value.load() == LoopCount); + } + + SECTION("Async at exit") + { + constexpr const int LoopCount = 10; + std::atomic Value{ 0 }; + { + FWwiseExecutionQueue::Test::bMockEngineDeletion = true; + FWwiseExecutionQueue ExecutionQueue; + for (int i = 0; i < LoopCount; ++i) + { + ExecutionQueue.Async([&Value, ShouldBe = i] + { + CHECK(Value++ == ShouldBe); + }); + } + } + FWwiseExecutionQueue::Test::bMockEngineDeletion = false; + CHECK(Value.load() == LoopCount); + } + + SECTION("Async after exit") + { + constexpr const int LoopCount = 10; + std::atomic Value{ 0 }; + { + const auto CurrentThreadId = FPlatformTLS::GetCurrentThreadId(); + FWwiseExecutionQueue::Test::bMockEngineDeleted = true; + FWwiseExecutionQueue ExecutionQueue; + for (int i = 0; i < LoopCount; ++i) + { + ExecutionQueue.Async([&Value, ShouldBe = i, CurrentThreadId] + { + CHECK(CurrentThreadId == FPlatformTLS::GetCurrentThreadId()); + CHECK(Value++ == ShouldBe); + }); + } + } + FWwiseExecutionQueue::Test::bMockEngineDeleted = false; + CHECK(Value.load() == LoopCount); + } +} + +TEST_CASE("Audio::Wwise::Concurrency::ExecutionQueue_Perf", "[ApplicationContextMask][PerfFilter]") +{ + SECTION("AsyncAddingOpPerf") + { + constexpr const int LoopCount = 1000000; + constexpr const int ExpectedUS = 600000; + std::atomic Value{ 0 }; + + { + FWwiseExecutionQueue ExecutionQueue; + + FDateTime StartTime = FDateTime::UtcNow(); + for (int i = 0; i < LoopCount; ++i) + { + ExecutionQueue.Async([&Value] + { + ++Value; + }); + } + FTimespan Duration = FDateTime::UtcNow() - StartTime; + WWISETEST_LOG("AsyncAddingOpPerf %dus < %dus", (int)Duration.GetTotalMicroseconds(), ExpectedUS); + CHECK(Duration.GetTotalMicroseconds() < ExpectedUS); + } + CHECK(Value.load() == LoopCount); + } + + SECTION("AsyncExecutionPerf") + { + constexpr const int LoopCount = 1000000; + constexpr const int ExpectedUS = 200000; + std::atomic Value{ 0 }; + + FDateTime StartTime; + { + FWwiseExecutionQueue ExecutionQueue; + + ExecutionQueue.AsyncWait([&ExecutionQueue, LoopCount, &Value] + { + for (int i = 0; i < LoopCount; ++i) + { + ExecutionQueue.Async([&Value] + { + ++Value; + }); + } + }); + StartTime = FDateTime::UtcNow(); + } + FTimespan Duration = FDateTime::UtcNow() - StartTime; + WWISETEST_LOG("AsyncExecutionPerf %dus < %dus", (int)Duration.GetTotalMicroseconds(), ExpectedUS); + CHECK(Value.load() == LoopCount); + CHECK(Duration.GetTotalMicroseconds() < ExpectedUS); + } +} + +TEST_CASE("Audio::Wwise::Concurrency::ExecutionQueue_Stress", "[ApplicationContextMask][StressFilter]") +{ + SECTION("AsyncStress") + { + constexpr const int LoopCount = 2000000; + constexpr const int MainLoopCount = 100; + constexpr const int SubLoopCount = 100; + constexpr const int FinalLoopCount = LoopCount / MainLoopCount / SubLoopCount; + + constexpr const int ExpectedUS = 1500000; + + FDateTime StartTime = FDateTime::UtcNow(); + { + FWwiseExecutionQueue MainExecutionQueue; + FWwiseExecutionQueue SubExecutionQueue; + FWwiseExecutionQueue DeletionExecutionQueue; + + for (int i = 0; i < MainLoopCount; ++i) + { + MainExecutionQueue.Async([&SubExecutionQueue, &DeletionExecutionQueue, SubLoopCount, FinalLoopCount] + { + for (int i = 0; i < SubLoopCount; ++i) + { + SubExecutionQueue.Async([&DeletionExecutionQueue, FinalLoopCount] + { + auto ExecutionQueue = new FWwiseExecutionQueue; + for (int i = 0; i < FinalLoopCount; ++i) + { + ExecutionQueue->Async([] + { + }); + } + DeletionExecutionQueue.Async([ExecutionQueue] + { + delete ExecutionQueue; + }); + }); + } + }); + } + } + FTimespan Duration = FDateTime::UtcNow() - StartTime; + WWISETEST_LOG("AsyncAddingOpPerf %dus < %dus", (int)Duration.GetTotalMicroseconds(), ExpectedUS); + CHECK(Duration.GetTotalMicroseconds() < ExpectedUS); + } + + SECTION("AsyncStress at Exit") + { + constexpr const int LoopCount = 100000; + constexpr const int MainLoopCount = 100; + constexpr const int SubLoopCount = 100; + constexpr const int FinalLoopCount = LoopCount / MainLoopCount / SubLoopCount; + + constexpr const int ExpectedUS = 3000000; + + FDateTime StartTime = FDateTime::UtcNow(); + { + FWwiseExecutionQueue MainExecutionQueue; + FWwiseExecutionQueue SubExecutionQueue; + FWwiseExecutionQueue DeletionExecutionQueue; + + for (int i = 0; i < MainLoopCount; ++i) + { + MainExecutionQueue.Async([&SubExecutionQueue, &DeletionExecutionQueue, SubLoopCount, FinalLoopCount] + { + for (int i = 0; i < SubLoopCount; ++i) + { + SubExecutionQueue.Async([&DeletionExecutionQueue, FinalLoopCount] + { + auto ExecutionQueue = new FWwiseExecutionQueue; + for (int i = 0; i < FinalLoopCount; ++i) + { + ExecutionQueue->Async([] + { + }); + } + DeletionExecutionQueue.Async([ExecutionQueue] + { + delete ExecutionQueue; + }); + }); + } + }); + + if (i == MainLoopCount / 3) + { + MainExecutionQueue.Async([] + { + FWwiseExecutionQueue::Test::bMockEngineDeletion = true; + }); + } + } + MainExecutionQueue.Async([] + { + FWwiseExecutionQueue::Test::bMockEngineDeleted = true; + }); + } + FWwiseExecutionQueue::Test::bMockEngineDeletion = false; + FWwiseExecutionQueue::Test::bMockEngineDeleted = false; + + FTimespan Duration = FDateTime::UtcNow() - StartTime; + WWISETEST_LOG("AsyncAddingOpPerf %dus < %dus", (int)Duration.GetTotalMicroseconds(), ExpectedUS); + CHECK(Duration.GetTotalMicroseconds() < ExpectedUS); + } +} + +#endif // WWISE_UNIT_TESTS \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/WwiseConcurrency.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/WwiseConcurrency.Build.cs new file mode 100644 index 0000000..a7be16a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseConcurrency/WwiseConcurrency.Build.cs @@ -0,0 +1,36 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; + +public class WwiseConcurrency : ModuleRules +{ + public WwiseConcurrency(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange(new string[] { + "Core", + "CoreUObject", + "Engine", + "WwiseSoundEngine" + }); + + PrivateDependencyModuleNames.AddRange(new string[] { + }); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/CookedData/WwiseExternalSourceCookedData.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/CookedData/WwiseExternalSourceCookedData.cpp new file mode 100644 index 0000000..4815764 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/CookedData/WwiseExternalSourceCookedData.cpp @@ -0,0 +1,50 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/CookedData/WwiseExternalSourceCookedData.h" + +#include "Wwise/Stats/FileHandler.h" +#include + +FWwiseExternalSourceCookedData::FWwiseExternalSourceCookedData(): + Cookie(0), + DebugName() +{} + +void FWwiseExternalSourceCookedData::Serialize(FArchive& Ar) +{ + UStruct* Struct = StaticStruct(); + UE_CLOG(UNLIKELY(!Struct), LogWwiseFileHandler, Fatal, TEXT("ExternalSourceCookedData Serialize: No StaticStruct.")); + + if (Ar.WantBinaryPropertySerialization()) + { + UE_CLOG(Ar.IsSaving(), LogWwiseFileHandler, VeryVerbose, TEXT("Serializing to binary archive %s ExternalSourceCookedData %" PRIu32 " %s"), *Ar.GetArchiveName(), Cookie, *DebugName.ToString()); + Struct->SerializeBin(Ar, this); + UE_CLOG(Ar.IsLoading(), LogWwiseFileHandler, VeryVerbose, TEXT("Serializing from binary archive %s ExternalSourceCookedData %" PRIu32 " %s"), *Ar.GetArchiveName(), Cookie, *DebugName.ToString()); + } + else + { + UE_CLOG(Ar.IsSaving(), LogWwiseFileHandler, VeryVerbose, TEXT("Serializing to tagged archive %s ExternalSourceCookedData %" PRIu32 " %s"), *Ar.GetArchiveName(), Cookie, *DebugName.ToString()); + Struct->SerializeTaggedProperties(Ar, reinterpret_cast(this), Struct, nullptr); + UE_CLOG(Ar.IsLoading(), LogWwiseFileHandler, VeryVerbose, TEXT("Serializing from tagged archive %s ExternalSourceCookedData %" PRIu32 " %s"), *Ar.GetArchiveName(), Cookie, *DebugName.ToString()); + } +} + +FString FWwiseExternalSourceCookedData::GetDebugString() const +{ + return FString::Printf(TEXT("ExternalSource %s (%" PRIu32 ")"), *DebugName.ToString(), Cookie); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/CookedData/WwiseLanguageCookedData.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/CookedData/WwiseLanguageCookedData.cpp new file mode 100644 index 0000000..e57064e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/CookedData/WwiseLanguageCookedData.cpp @@ -0,0 +1,55 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/CookedData/WwiseLanguageCookedData.h" + +#include "Wwise/WwiseFileHandlerModule.h" +#include "Wwise/Stats/FileHandler.h" +#include + +const FWwiseLanguageCookedData FWwiseLanguageCookedData::Sfx(0, TEXT("SFX"), EWwiseLanguageRequirement::SFX); + +FWwiseLanguageCookedData::FWwiseLanguageCookedData(): + LanguageId(0), + LanguageName(), + LanguageRequirement(EWwiseLanguageRequirement::IsOptional) +{} + +FWwiseLanguageCookedData::FWwiseLanguageCookedData(int32 LangId, const FName& LangName, EWwiseLanguageRequirement LangRequirement): + LanguageId(LangId), + LanguageName(LangName), + LanguageRequirement(LangRequirement) +{} + +void FWwiseLanguageCookedData::Serialize(FArchive& Ar) +{ + UStruct* Struct = StaticStruct(); + UE_CLOG(UNLIKELY(!Struct), LogWwiseFileHandler, Fatal, TEXT("LanguageCookedData Serialize: No StaticStruct.")); + + if (Ar.WantBinaryPropertySerialization()) + { + UE_CLOG(Ar.IsSaving(), LogWwiseFileHandler, VeryVerbose, TEXT("Serializing to binary archive %s LanguageCookedData %" PRIu32 " %s"), *Ar.GetArchiveName(), LanguageId, *LanguageName.ToString()); + Struct->SerializeBin(Ar, this); + UE_CLOG(Ar.IsLoading(), LogWwiseFileHandler, VeryVerbose, TEXT("Serializing from binary archive %s LanguageCookedData %" PRIu32 " %s"), *Ar.GetArchiveName(), LanguageId, *LanguageName.ToString()); + } + else + { + UE_CLOG(Ar.IsSaving(), LogWwiseFileHandler, VeryVerbose, TEXT("Serializing to tagged archive %s LanguageCookedData %" PRIu32 " %s"), *Ar.GetArchiveName(), LanguageId, *LanguageName.ToString()); + Struct->SerializeTaggedProperties(Ar, reinterpret_cast(this), Struct, nullptr); + UE_CLOG(Ar.IsLoading(), LogWwiseFileHandler, VeryVerbose, TEXT("Serializing from tagged archive %s LanguageCookedData %" PRIu32 " %s"), *Ar.GetArchiveName(), LanguageId, *LanguageName.ToString()); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/CookedData/WwiseMediaCookedData.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/CookedData/WwiseMediaCookedData.cpp new file mode 100644 index 0000000..06c7da7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/CookedData/WwiseMediaCookedData.cpp @@ -0,0 +1,57 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/CookedData/WwiseMediaCookedData.h" + +#include "Wwise/Stats/FileHandler.h" +#include + +FWwiseMediaCookedData::FWwiseMediaCookedData(): + MediaId(0), + MediaPathName(), + PrefetchSize(0), + MemoryAlignment(0), + bDeviceMemory(false), + bStreaming(false), + DebugName() +{} + +void FWwiseMediaCookedData::Serialize(FArchive& Ar) +{ + UStruct* Struct = StaticStruct(); + UE_CLOG(UNLIKELY(!Struct), LogWwiseFileHandler, Fatal, TEXT("MediaCookedData Serialize: No StaticStruct.")); + + if (Ar.WantBinaryPropertySerialization()) + { + UE_CLOG(Ar.IsSaving(), LogWwiseFileHandler, VeryVerbose, TEXT("Serializing to binary archive %s MediaCookedData %" PRIu32 " %s"), *Ar.GetArchiveName(), MediaId, *DebugName.ToString()); + Struct->SerializeBin(Ar, this); + UE_CLOG(Ar.IsLoading(), LogWwiseFileHandler, VeryVerbose, TEXT("Serializing from binary archive %s MediaCookedData %" PRIu32 " %s"), *Ar.GetArchiveName(), MediaId, *DebugName.ToString()); + } + else + { + UE_CLOG(Ar.IsSaving(), LogWwiseFileHandler, VeryVerbose, TEXT("Serializing to tagged archive %s MediaCookedData %" PRIu32 " %s"), *Ar.GetArchiveName(), MediaId, *DebugName.ToString()); + Struct->SerializeTaggedProperties(Ar, reinterpret_cast(this), Struct, nullptr); + UE_CLOG(Ar.IsLoading(), LogWwiseFileHandler, VeryVerbose, TEXT("Serializing from tagged archive %s MediaCookedData %" PRIu32 " %s"), *Ar.GetArchiveName(), MediaId, *DebugName.ToString()); + } +} + +FString FWwiseMediaCookedData::GetDebugString() const +{ + return FString::Printf(TEXT("Media %s (%" PRIu32 ") @ %s (p:%" PRIi32 " ma:%" PRIi32 " %sdm %ss)"), + *DebugName.ToString(), MediaId, *MediaPathName.ToString(), PrefetchSize, MemoryAlignment, + bDeviceMemory ? TEXT("") : TEXT("!"), bStreaming ? TEXT("") : TEXT("!")); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/CookedData/WwiseSoundBankCookedData.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/CookedData/WwiseSoundBankCookedData.cpp new file mode 100644 index 0000000..74f2ea4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/CookedData/WwiseSoundBankCookedData.cpp @@ -0,0 +1,58 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/CookedData/WwiseSoundBankCookedData.h" + +#include "Wwise/Stats/FileHandler.h" +#include + +FWwiseSoundBankCookedData::FWwiseSoundBankCookedData() : + SoundBankId(0), + SoundBankPathName(), + MemoryAlignment(0), + bDeviceMemory(false), + bContainsMedia(false), + SoundBankType(EWwiseSoundBankType::User), + DebugName() +{} + +void FWwiseSoundBankCookedData::Serialize(FArchive& Ar) +{ + UStruct* Struct = StaticStruct(); + UE_CLOG(UNLIKELY(!Struct), LogWwiseFileHandler, Fatal, TEXT("SoundBankCookedData Serialize: No StaticStruct.")); + + if (Ar.WantBinaryPropertySerialization()) + { + UE_CLOG(Ar.IsSaving(), LogWwiseFileHandler, VeryVerbose, TEXT("Serializing to binary archive %s SoundBankCookedData %" PRIu32 " %s"), *Ar.GetArchiveName(), SoundBankId, *DebugName.ToString()); + Struct->SerializeBin(Ar, this); + UE_CLOG(Ar.IsLoading(), LogWwiseFileHandler, VeryVerbose, TEXT("Serializing from binary archive %s SoundBankCookedData %" PRIu32 " %s"), *Ar.GetArchiveName(), SoundBankId, *DebugName.ToString()); + } + else + { + UE_CLOG(Ar.IsSaving(), LogWwiseFileHandler, VeryVerbose, TEXT("Serializing to tagged archive %s SoundBankCookedData %" PRIu32 " %s"), *Ar.GetArchiveName(), SoundBankId, *DebugName.ToString()); + Struct->SerializeTaggedProperties(Ar, reinterpret_cast(this), Struct, nullptr); + UE_CLOG(Ar.IsLoading(), LogWwiseFileHandler, VeryVerbose, TEXT("Serializing from tagged archive %s SoundBankCookedData %" PRIu32 " %s"), *Ar.GetArchiveName(), SoundBankId, *DebugName.ToString()); + } +} + +FString FWwiseSoundBankCookedData::GetDebugString() const +{ + return FString::Printf(TEXT("SoundBank %s (%" PRIu32 ") @ %s (ma:%" PRIi32 " %sdm %smedia %suser)"), + *DebugName.ToString(), SoundBankId, *SoundBankPathName.ToString(), MemoryAlignment, + bDeviceMemory ? TEXT("") : TEXT("!"), bContainsMedia ? TEXT("") : TEXT("!"), + SoundBankType == EWwiseSoundBankType::User ? TEXT("") : TEXT("!")); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/Stats/FileHandler.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/Stats/FileHandler.cpp new file mode 100644 index 0000000..c26b930 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/Stats/FileHandler.cpp @@ -0,0 +1,60 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Stats/FileHandler.h" + +DEFINE_STAT(STAT_WwiseFileHandlerMemoryAllocated); +DEFINE_STAT(STAT_WwiseFileHandlerMemoryMapped); + +#if AK_SUPPORT_DEVICE_MEMORY +DEFINE_STAT(STAT_WwiseFileHandlerDeviceMemoryAllocated); +#endif + +DEFINE_STAT(STAT_WwiseFileHandlerCreatedExternalSourceStates); +DEFINE_STAT(STAT_WwiseFileHandlerKnownExternalSourceMedia); +DEFINE_STAT(STAT_WwiseFileHandlerLoadedExternalSourceMedia); +DEFINE_STAT(STAT_WwiseFileHandlerKnownMedia); +DEFINE_STAT(STAT_WwiseFileHandlerLoadedMedia); +DEFINE_STAT(STAT_WwiseFileHandlerKnownSoundBanks); +DEFINE_STAT(STAT_WwiseFileHandlerLoadedSoundBanks); + +DEFINE_STAT(STAT_WwiseFileHandlerPrefetchMemoryAllocated); + +DEFINE_STAT(STAT_WwiseFileHandlerTotalErrorCount); +DEFINE_STAT(STAT_WwiseFileHandlerStateOperationsBeingProcessed); +DEFINE_STAT(STAT_WwiseFileHandlerStateOperationLatency); + +DEFINE_STAT(STAT_WwiseFileHandlerStreamingKB); +DEFINE_STAT(STAT_WwiseFileHandlerOpenedStreams); +DEFINE_STAT(STAT_WwiseFileHandlerBatchedRequests); +DEFINE_STAT(STAT_WwiseFileHandlerPendingRequests); +DEFINE_STAT(STAT_WwiseFileHandlerTotalRequests); +DEFINE_STAT(STAT_WwiseFileHandlerTotalStreamedMB); + +DEFINE_STAT(STAT_WwiseFileHandlerAsyncDeleteRequest); +DEFINE_STAT(STAT_WwiseFileHandlerIORequestLatency); +DEFINE_STAT(STAT_WwiseFileHandlerFileOperationLatency); +DEFINE_STAT(STAT_WwiseFileHandlerSoundEngineCallbackLatency); + +DEFINE_STAT(STAT_WwiseFileHandlerCriticalPriority); +DEFINE_STAT(STAT_WwiseFileHandlerHighPriority); +DEFINE_STAT(STAT_WwiseFileHandlerNormalPriority); +DEFINE_STAT(STAT_WwiseFileHandlerBelowNormalPriority); +DEFINE_STAT(STAT_WwiseFileHandlerLowPriority); +DEFINE_STAT(STAT_WwiseFileHandlerBackgroundPriority); + +DEFINE_LOG_CATEGORY(LogWwiseFileHandler); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseExternalSourceFileState.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseExternalSourceFileState.cpp new file mode 100644 index 0000000..158a3d9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseExternalSourceFileState.cpp @@ -0,0 +1,306 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseExternalSourceFileState.h" +#include "Wwise/WwiseExternalSourceManager.h" +#include "Wwise/WwiseFileCache.h" +#include "Wwise/WwiseStreamingManagerHooks.h" +#include "Wwise/API/WwiseSoundEngineAPI.h" +#include "Async/MappedFileHandle.h" + +#include + +FWwiseExternalSourceFileState::FWwiseExternalSourceFileState(uint32 InMemoryAlignment, bool bInDeviceMemory, + uint32 InMediaId, const FName& InMediaPathName, const FName& InRootPath, int32 InCodecId) : + AkExternalSourceInfo(), + MemoryAlignment(InMemoryAlignment), + bDeviceMemory(bInDeviceMemory), + MediaId(InMediaId), + MediaPathName(InMediaPathName), + RootPath(InRootPath), + PlayCount(0) +{ + idCodec = InCodecId; + INC_DWORD_STAT(STAT_WwiseFileHandlerKnownExternalSourceMedia); +} + +FWwiseExternalSourceFileState::~FWwiseExternalSourceFileState() +{ + DEC_DWORD_STAT(STAT_WwiseFileHandlerKnownExternalSourceMedia); +} + +bool FWwiseExternalSourceFileState::GetExternalSourceInfo(AkExternalSourceInfo& OutInfo) +{ + OutInfo = static_cast(*this); + return szFile != nullptr || pInMemory != nullptr || idFile != 0; +} + +void FWwiseExternalSourceFileState::IncrementPlayCount() +{ + ++PlayCount; +} + + +bool FWwiseExternalSourceFileState::DecrementPlayCount() +{ + const auto NewPlayCount = PlayCount.DecrementExchange() - 1; + if (PlayCount < 0) + { + PlayCount.Store(0); + UE_LOG(LogWwiseFileHandler, Warning, TEXT("FWwiseExternalSourceFileState: Play count went below zero for media %" PRIu32 " (%s)"), + MediaId, *MediaPathName.ToString()); + } + return NewPlayCount == 0; +} + +FWwiseInMemoryExternalSourceFileState::FWwiseInMemoryExternalSourceFileState(uint32 InMemoryAlignment, bool bInDeviceMemory, + uint32 InMediaId, const FName& InMediaPathName, const FName& InRootPath, int32 InCodecId) : + FWwiseExternalSourceFileState(InMemoryAlignment, bInDeviceMemory, InMediaId, InMediaPathName, InRootPath, InCodecId), + Ptr(nullptr), + MappedHandle(nullptr), + MappedRegion(nullptr) +{ +#if WITH_EDITOR + if (bDeviceMemory) + { + UE_LOG(LogWwiseFileHandler, Warning, TEXT("FWwiseExternalSourceFileState: Loading External Source Media with DeviceMemory=true while in in editor. Expect to see \"No Device Memory\" errors in the log.")); + } +#endif +} + +void FWwiseInMemoryExternalSourceFileState::OpenFile(FOpenFileCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseInMemoryExternalSourceFileState::OpenFile")); + if (UNLIKELY(uiMemorySize || pInMemory)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseInMemoryExternalSourceFileState::OpenFile %" PRIu32 " (%s): Seems to be already opened."), MediaId, *MediaPathName.ToString()); + return OpenFileFailed(MoveTemp(InCallback)); + } + + const auto FullPathName = RootPath.ToString() / MediaPathName.ToString(); + int64 FileSize = 0; + if (LIKELY(GetFileToPtr(const_cast(reinterpret_cast(pInMemory)), FileSize, FullPathName, bDeviceMemory, MemoryAlignment, true))) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseInMemoryExternalSourceFileState::OpenFile %" PRIu32 " (%s)"), MediaId, *MediaPathName.ToString()); + uiMemorySize = FileSize; + return OpenFileSucceeded(MoveTemp(InCallback)); + } + else + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseInMemoryExternalSourceFileState::OpenFile %" PRIu32 ": Failed to open In-Memory External Source (%s)."), MediaId, *FullPathName); + pInMemory = nullptr; + FileSize = 0; + return OpenFileFailed(MoveTemp(InCallback)); + } +} + +void FWwiseInMemoryExternalSourceFileState::LoadInSoundEngine(FLoadInSoundEngineCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseInMemoryExternalSourceFileState::LoadInSoundEngine")); + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseInMemoryExternalSourceFileState::LoadInSoundEngine %" PRIu32 " (%s)"), MediaId, *MediaPathName.ToString()); + INC_DWORD_STAT(STAT_WwiseFileHandlerLoadedExternalSourceMedia); + LoadInSoundEngineSucceeded(MoveTemp(InCallback)); +} + +void FWwiseInMemoryExternalSourceFileState::UnloadFromSoundEngine(FUnloadFromSoundEngineCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseInMemoryExternalSourceFileState::UnloadFromSoundEngine")); + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseInMemoryExternalSourceFileState::UnloadFromSoundEngine %" PRIu32 " (%s)"), MediaId, *MediaPathName.ToString()); + DEC_DWORD_STAT(STAT_WwiseFileHandlerLoadedExternalSourceMedia); + UnloadFromSoundEngineDone(MoveTemp(InCallback)); +} + +void FWwiseInMemoryExternalSourceFileState::CloseFile(FCloseFileCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseInMemoryExternalSourceFileState::CloseFile")); + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseInMemoryExternalSourceFileState::CloseFile %" PRIu32 " (%s)"), MediaId, *MediaPathName.ToString()); + DeallocateMemory(const_cast(reinterpret_cast(pInMemory)), uiMemorySize, bDeviceMemory, MemoryAlignment, true); + pInMemory = nullptr; + uiMemorySize = 0; + CloseFileDone(MoveTemp(InCallback)); +} + +FWwiseStreamedExternalSourceFileState::FWwiseStreamedExternalSourceFileState(uint32 InMemoryAlignment, bool bInDeviceMemory, + uint32 InPrefetchSize, uint32 InStreamingGranularity, + uint32 InMediaId, const FName& InMediaPathName, const FName& InRootPath, int32 InCodecId) : + FWwiseExternalSourceFileState(InMemoryAlignment, bInDeviceMemory, InMediaId, InMediaPathName, InRootPath, InCodecId), + PrefetchSize(InPrefetchSize), + StreamingGranularity(InStreamingGranularity), + StreamedFile(nullptr) +{ + sourceID = InMediaId; + pMediaMemory = nullptr; + uMediaSize = 0; + + idFile = InMediaId; +} + +void FWwiseStreamedExternalSourceFileState::CloseStreaming() +{ + auto* ExternalSourceManager = IWwiseExternalSourceManager::Get(); + if (UNLIKELY(!ExternalSourceManager)) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("FWwiseStreamedExternalSourceFileState::CloseStreaming %" PRIu32 " (%s): Closing without an ExternalSourceManager."), MediaId, *MediaPathName.ToString()); + return; + } + ExternalSourceManager->GetStreamingHooks().CloseStreaming(MediaId, *this); +} + +void FWwiseStreamedExternalSourceFileState::OpenFile(FOpenFileCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseStreamedExternalSourceFileState::OpenFile")); + if (UNLIKELY(iFileSize != 0 || StreamedFile)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseStreamedExternalSourceFileState::OpenFile %" PRIu32 " (%s): Stream seems to be already opened."), MediaId, *MediaPathName.ToString()); + return OpenFileFailed(MoveTemp(InCallback)); + } + + if (PrefetchSize == 0) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseStreamedExternalSourceFileState::OpenFile %" PRIu32 " (%s)"), MediaId, *MediaPathName.ToString()); + return OpenFileSucceeded(MoveTemp(InCallback)); + } + + // Process PrefetchSize and send as SetMedia + const auto FullPathName = RootPath.ToString() / MediaPathName.ToString(); + + auto PrefetchWithGranularity = PrefetchSize; + if (StreamingGranularity > 1) + { + auto PrefetchChunks = PrefetchSize / StreamingGranularity; + if (PrefetchSize % StreamingGranularity > 0) + { + PrefetchChunks += 1; + } + PrefetchWithGranularity = PrefetchChunks * StreamingGranularity; + } + + int64 FileSize = 0; + if (UNLIKELY(!GetFileToPtr(const_cast(pMediaMemory), FileSize, FullPathName, false, 0, false, PrefetchWithGranularity))) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseStreamedExternalSourceFileState::OpenFile %" PRIu32 " (%s): Failed to Read prefetch ExternalSource (%s)."), MediaId, *MediaPathName.ToString(), *FullPathName); + pMediaMemory = nullptr; + return OpenFileFailed(MoveTemp(InCallback)); + } + uMediaSize = FileSize; + + UE_CLOG(FileSize == PrefetchSize, LogWwiseFileHandler, Verbose, TEXT("FWwiseStreamedExternalSourceFileState::OpenFile %" PRIu32 " (%s): Prefetched %" PRIu32 " bytes."), MediaId, *MediaPathName.ToString(), uMediaSize); + UE_CLOG(PrefetchSize != PrefetchWithGranularity && PrefetchWithGranularity == FileSize, LogWwiseFileHandler, Verbose, TEXT("FWwiseStreamedExternalSourceFileState::OpenFile %" PRIu32 " (%s): Prefetched (%" PRIu32 ") -> %" PRIu32 " bytes."), MediaId, *MediaPathName.ToString(), PrefetchSize, PrefetchWithGranularity); + UE_CLOG(PrefetchWithGranularity != FileSize, LogWwiseFileHandler, Verbose, TEXT("FWwiseStreamedExternalSourceFileState::OpenFile %" PRIu32 " (%s): Prefetched (%" PRIu32 " -> %" PRIu32 ") -> %" PRIu32 " bytes."), MediaId, *MediaPathName.ToString(), PrefetchSize, PrefetchWithGranularity, uMediaSize); + INC_MEMORY_STAT_BY(STAT_WwiseFileHandlerPrefetchMemoryAllocated, uMediaSize); + return OpenFileSucceeded(MoveTemp(InCallback)); +} + +void FWwiseStreamedExternalSourceFileState::LoadInSoundEngine(FLoadInSoundEngineCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseStreamedExternalSourceFileState::LoadInSoundEngine")); + if (UNLIKELY(iFileSize != 0 || StreamedFile)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseStreamedExternalSourceFileState::LoadInSoundEngine %" PRIu32 " (%s): Stream seems to be already loaded."), MediaId, *MediaPathName.ToString()); + return LoadInSoundEngineFailed(MoveTemp(InCallback)); + } + + FWwiseFileCache* FileCache = FWwiseFileCache::Get(); + if (UNLIKELY(!FileCache)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseStreamedExternalSourceFileState::LoadInSoundEngine %" PRIu32 " (%s): WwiseFileCache not available."), MediaId, *MediaPathName.ToString()); + return LoadInSoundEngineFailed(MoveTemp(InCallback)); + } + + const auto FullPathName = RootPath.ToString() / MediaPathName.ToString(); + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseStreamedExternalSourceFileState::LoadInSoundEngine %" PRIu32 " (%s): Opening file"), MediaId, *MediaPathName.ToString()); + FileCache->CreateFileCacheHandle(StreamedFile, FullPathName, [this, Callback = MoveTemp(InCallback)](bool bResult) mutable + { + if (UNLIKELY(!bResult)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseStreamedExternalSourceFileState::LoadInSoundEngine %" PRIu32 ": Failed to load Streaming ExternalSource (%s)."), MediaId, *MediaPathName.ToString()); + FFunctionGraphTask::CreateAndDispatchWhenReady([StreamedFile = StreamedFile] + { + delete StreamedFile; + }); + StreamedFile = nullptr; + return LoadInSoundEngineFailed(MoveTemp(Callback)); + } + + iFileSize = StreamedFile->GetFileSize(); + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseStreamedExternalSourceFileState::LoadInSoundEngine %" PRIu32 " (%s)"), MediaId, *MediaPathName.ToString()); + INC_DWORD_STAT(STAT_WwiseFileHandlerLoadedExternalSourceMedia); + return LoadInSoundEngineSucceeded(MoveTemp(Callback)); + }); +} + +void FWwiseStreamedExternalSourceFileState::UnloadFromSoundEngine(FUnloadFromSoundEngineCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseStreamedExternalSourceFileState::UnloadFromSoundEngine")); + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseStreamedExternalSourceFileState::UnloadFromSoundEngine %" PRIu32 " (%s)"), MediaId, *MediaPathName.ToString()); + + const auto* StreamedFileToDelete = StreamedFile; + StreamedFile = nullptr; + iFileSize = 0; + + delete StreamedFileToDelete; + + DEC_DWORD_STAT(STAT_WwiseFileHandlerLoadedExternalSourceMedia); + UnloadFromSoundEngineDone(MoveTemp(InCallback)); +} + +void FWwiseStreamedExternalSourceFileState::CloseFile(FCloseFileCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseStreamedExternalSourceFileState::CloseFile")); + if (pMediaMemory == nullptr) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseStreamedExternalSourceFileState::CloseFile %" PRIu32 " (%s)"), MediaId, *MediaPathName.ToString()); + return CloseFileDone(MoveTemp(InCallback)); + } + + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseStreamedExternalSourceFileState::CloseFile %" PRIu32 " (%s): Unloaded prefetch."), MediaId, *MediaPathName.ToString()); + DeallocateMemory(pMediaMemory, uMediaSize, bDeviceMemory, MemoryAlignment, true); + DEC_MEMORY_STAT_BY(STAT_WwiseFileHandlerPrefetchMemoryAllocated, uMediaSize); + pMediaMemory = nullptr; + uMediaSize = 0; + return CloseFileDone(MoveTemp(InCallback)); +} + +bool FWwiseStreamedExternalSourceFileState::CanProcessFileOp() const +{ + if (UNLIKELY(State != EState::Loaded)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseStreamedExternalSourceFileState::CanProcessFileOp %" PRIu32 " (%s): IO Hook asked for a file operation, but state is not ready."), MediaId, *MediaPathName.ToString()); + return false; + } + return true; +} + +AKRESULT FWwiseStreamedExternalSourceFileState::ProcessRead(AkFileDesc& InFileDesc, const AkIoHeuristics& InHeuristics, AkAsyncIOTransferInfo& OutTransferInfo, FWwiseAkFileOperationDone&& InFileOpDoneCallback) +{ + if (pMediaMemory && OutTransferInfo.uFilePosition + OutTransferInfo.uRequestedSize <= uMediaSize) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseStreamedExternalSourceFileState::ProcessRead: Reading prefetch %" PRIu32 " bytes @ %" PRIu64 " in file %" PRIu32 " (%s)"), + OutTransferInfo.uRequestedSize, OutTransferInfo.uFilePosition, MediaId, *MediaPathName.ToString()); + FMemory::Memcpy(OutTransferInfo.pBuffer, pMediaMemory + OutTransferInfo.uFilePosition, OutTransferInfo.uRequestedSize); + SCOPED_WWISEFILEHANDLER_EVENT_4(TEXT("FWwiseStreamedExternalSourceFileState::ProcessRead Callback")); + InFileOpDoneCallback(&OutTransferInfo, AK_Success); + return AK_Success; + } + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseStreamedExternalSourceFileState::ProcessRead: Reading %" PRIu32 " bytes @ %" PRIu64 " in file %" PRIu32 " (%s)"), + OutTransferInfo.uRequestedSize, OutTransferInfo.uFilePosition, MediaId, *MediaPathName.ToString()); + + StreamedFile->ReadAkData(InHeuristics, OutTransferInfo, MoveTemp(InFileOpDoneCallback)); + return AK_Success; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseExternalSourceManagerImpl.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseExternalSourceManagerImpl.cpp new file mode 100644 index 0000000..48b6372 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseExternalSourceManagerImpl.cpp @@ -0,0 +1,394 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseExternalSourceManagerImpl.h" +#include "Wwise/API/WwiseSoundEngineAPI.h" +#include "Wwise/Stats/AsyncStats.h" +#include "Wwise/Stats/FileHandler.h" + +#include + +#include "Wwise/WwiseExternalSourceFileState.h" + +FWwiseExternalSourceState::FWwiseExternalSourceState(const FWwiseExternalSourceCookedData& InCookedData) : + FWwiseExternalSourceCookedData(InCookedData), + LoadCount(0) +{ + INC_DWORD_STAT(STAT_WwiseFileHandlerCreatedExternalSourceStates); +} + +FWwiseExternalSourceState::~FWwiseExternalSourceState() +{ + DEC_DWORD_STAT(STAT_WwiseFileHandlerCreatedExternalSourceStates); +} + +void FWwiseExternalSourceState::IncrementLoadCount() +{ + const auto NewLoadCount = LoadCount.IncrementExchange() + 1; + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("ExternalSource State %" PRIu32 " (%s): ++LoadCount=%d"), Cookie, *DebugName.ToString(), NewLoadCount); +} + +bool FWwiseExternalSourceState::DecrementLoadCount() +{ + const auto NewLoadCount = LoadCount.DecrementExchange() - 1; + const bool bResult = (NewLoadCount == 0); + if (bResult) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("ExternalSource State %" PRIu32 " (%s): --LoadCount=%d. Deleting."), Cookie, *DebugName.ToString(), NewLoadCount); + } + else + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("ExternalSource State %" PRIu32 " (%s): --LoadCount=%d"), Cookie, *DebugName.ToString(), NewLoadCount); + } + return bResult; +} + + +FWwiseExternalSourceManagerImpl::FWwiseExternalSourceManagerImpl() : + StreamingGranularity(0) +{ +} + +FWwiseExternalSourceManagerImpl::~FWwiseExternalSourceManagerImpl() +{ +} + +void FWwiseExternalSourceManagerImpl::LoadExternalSource( + const FWwiseExternalSourceCookedData& InExternalSourceCookedData, const FName& InRootPath, + const FWwiseLanguageCookedData& InLanguage, FLoadExternalSourceCallback&& InCallback) +{ + FileHandlerExecutionQueue.Async([this, InExternalSourceCookedData, InRootPath, InLanguage, InCallback = MoveTemp(InCallback)]() mutable + { + LoadExternalSourceImpl(InExternalSourceCookedData, InRootPath, InLanguage, MoveTemp(InCallback)); + }); +} + +void FWwiseExternalSourceManagerImpl::UnloadExternalSource( + const FWwiseExternalSourceCookedData& InExternalSourceCookedData, const FName& InRootPath, + const FWwiseLanguageCookedData& InLanguage, FUnloadExternalSourceCallback&& InCallback) +{ + FileHandlerExecutionQueue.Async([this, InExternalSourceCookedData, InRootPath, InLanguage, InCallback = MoveTemp(InCallback)]() mutable + { + UnloadExternalSourceImpl(InExternalSourceCookedData, InRootPath, InLanguage, MoveTemp(InCallback)); + }); +} + +void FWwiseExternalSourceManagerImpl::SetGranularity(AkUInt32 InStreamingGranularity) +{ + StreamingGranularity = InStreamingGranularity; +} + +TArray FWwiseExternalSourceManagerImpl::PrepareExternalSourceInfos(TArray& OutInfo, + const TArray + && + InCookedData) +{ + SCOPED_WWISEFILEHANDLER_EVENT_2(TEXT("FWwiseExternalSourceManagerImpl::PrepareExternalSourceInfos")); + if (InCookedData.Num() == 0) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("PrepareExternalSourceInfos: No External Sources to process")); + return {}; + } + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("GetExternalSourceInfos: Preparing %d external sources."), InCookedData.Num()); + TArray Result; + OutInfo.Reset(); + OutInfo.Reserve(InCookedData.Num()); + Result.Reserve(InCookedData.Num()); + { + FRWScopeLock Lock(CookieToMediaLock, FRWScopeLockType::SLT_ReadOnly); + for (const auto& Data : InCookedData) + { + AkExternalSourceInfo Info; + const auto MediaId = PrepareExternalSourceInfo(Info, Data); + if (LIKELY(MediaId != AK_INVALID_UNIQUE_ID)) + { + OutInfo.Add(MoveTemp(Info)); + Result.Add(MediaId); + } + } + } + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("GetExternalSourceInfos: Successfuly retrieved requested %d of %d external sources."), OutInfo.Num(), InCookedData.Num()); + return Result; +} + +#if WITH_EDITORONLY_DATA +void FWwiseExternalSourceManagerImpl::Cook(FWwiseResourceCooker& InResourceCooker, const FWwiseExternalSourceCookedData& InCookedData, + TFunctionRef WriteAdditionalFile, + const FWwiseSharedPlatformId& InPlatform, const FWwiseSharedLanguageId& InLanguage) +{ + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseExternalSourceManagerImpl::Cook: External Source manager needs to be overridden.")); +} +#endif + +void FWwiseExternalSourceManagerImpl::LoadExternalSourceImpl( + const FWwiseExternalSourceCookedData& InExternalSourceCookedData, const FName& InRootPath, const FWwiseLanguageCookedData& InLanguage, + FLoadExternalSourceCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_2(TEXT("FWwiseExternalSourceManagerImpl::LoadExternalSourceImpl")); + FWwiseExternalSourceStateSharedPtr State; + if (const auto* StatePtr = ExternalSourceStatesById.Find(InExternalSourceCookedData.Cookie)) + { + State = *StatePtr; + State->IncrementLoadCount(); + } + else + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("Creating new State for %s %" PRIu32), GetManagingTypeName(), InExternalSourceCookedData.Cookie); + State = CreateExternalSourceState(InExternalSourceCookedData, InRootPath); + if (UNLIKELY(!State.IsValid())) + { + SCOPED_WWISEFILEHANDLER_EVENT_4(TEXT("FWwiseExternalSourceManagerImpl::LoadExternalSourceImpl Callback")); + InCallback(false); + return; + } + else + { + State->IncrementLoadCount(); + ExternalSourceStatesById.Add(InExternalSourceCookedData.Cookie, State); + } + } + LoadExternalSourceMedia(InExternalSourceCookedData.Cookie, InExternalSourceCookedData.DebugName, InRootPath, MoveTemp(InCallback)); +} + +void FWwiseExternalSourceManagerImpl::UnloadExternalSourceImpl( + const FWwiseExternalSourceCookedData& InExternalSourceCookedData, const FName& InRootPath, const FWwiseLanguageCookedData& InLanguage, + FUnloadExternalSourceCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_2(TEXT("FWwiseExternalSourceManagerImpl::UnloadExternalSourceImpl")); + FWwiseExternalSourceStateSharedPtr State; + if (const auto* StatePtr = ExternalSourceStatesById.Find(InExternalSourceCookedData.Cookie)) + { + State = *StatePtr; + } + + if (UNLIKELY(!State.IsValid())) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("ExternalSource %" PRIu32 " (%s): Unloading an unknown External Source"), InExternalSourceCookedData.Cookie, *InExternalSourceCookedData.DebugName.ToString()); + SCOPED_WWISEFILEHANDLER_EVENT_4(TEXT("FWwiseExternalSourceManagerImpl::UnloadExternalSourceImpl Callback")); + InCallback(); + } + else + { + FWwiseExternalSourceState* ExternalSourceState = State.Get(); + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("ExternalSource %" PRIu32 " (%s): Closing State instance"), InExternalSourceCookedData.Cookie, *InExternalSourceCookedData.DebugName.ToString()); + if (CloseExternalSourceState(*State) && InExternalSourceCookedData.Cookie != 0) + { + ExternalSourceStatesById.Remove(InExternalSourceCookedData.Cookie); + State.Reset(); + } + if (LIKELY(InExternalSourceCookedData.Cookie != 0)) + { + UnloadExternalSourceMedia(InExternalSourceCookedData.Cookie, InExternalSourceCookedData.DebugName, InRootPath, MoveTemp(InCallback)); + } + else + { + SCOPED_WWISEFILEHANDLER_EVENT_4(TEXT("FWwiseExternalSourceManagerImpl::UnloadExternalSourceImpl Callback")); + InCallback(); + } + } +} + +FWwiseExternalSourceStateSharedPtr FWwiseExternalSourceManagerImpl::CreateExternalSourceState( + const FWwiseExternalSourceCookedData& InExternalSourceCookedData, const FName& InRootPath) +{ + return FWwiseExternalSourceStateSharedPtr(new FWwiseExternalSourceState(InExternalSourceCookedData)); +} + +bool FWwiseExternalSourceManagerImpl::CloseExternalSourceState(FWwiseExternalSourceState& InExternalSourceState) +{ + return InExternalSourceState.DecrementLoadCount(); +} + + +void FWwiseExternalSourceManagerImpl::LoadExternalSourceMedia(const uint32 InExternalSourceCookie, + const FName& InExternalSourceName, const FName& InRootPath, FLoadExternalSourceCallback&& InCallback) +{ + UE_LOG(LogWwiseFileHandler, Error, TEXT("External Source manager needs to be overridden.")); + InCallback(false); +} + +void FWwiseExternalSourceManagerImpl::UnloadExternalSourceMedia(const uint32 InExternalSourceCookie, + const FName& InExternalSourceName, const FName& InRootPath, FUnloadExternalSourceCallback&& InCallback) +{ + UE_LOG(LogWwiseFileHandler, Error, TEXT("External Source manager needs to be overridden.")); + InCallback(); +} + +uint32 FWwiseExternalSourceManagerImpl::PrepareExternalSourceInfo(AkExternalSourceInfo& OutInfo, + const FWwiseExternalSourceCookedData& InCookedData) +{ + const auto* ExternalSourceFileStatePtr = CookieToMedia.Find(InCookedData.Cookie); + if (UNLIKELY(!ExternalSourceFileStatePtr)) + { + UE_LOG(LogWwiseFileHandler, Warning, TEXT("PrepareExternalSourceInfo %" PRIu32 " (%s): CookieToMedia not defined"), InCookedData.Cookie, *InCookedData.DebugName.ToString()); + return AK_INVALID_UNIQUE_ID; + } + auto* ExternalSourceFileState = *ExternalSourceFileStatePtr; + + if (UNLIKELY(!ExternalSourceFileState->GetExternalSourceInfo(OutInfo))) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("Getting external source %" PRIu32 " (%s): AkExternalSourceInfo not initialized"), InCookedData.Cookie, *InCookedData.DebugName.ToString()); + return AK_INVALID_UNIQUE_ID; + } + + ExternalSourceFileState->IncrementPlayCount(); + + OutInfo.iExternalSrcCookie = InCookedData.Cookie; + UE_CLOG(OutInfo.idFile != 0, LogWwiseFileHandler, VeryVerbose, TEXT("Getting external source %" PRIu32 " (%s): Using file %" PRIu32), InCookedData.Cookie, *InCookedData.DebugName.ToString(), OutInfo.idFile); + UE_CLOG(OutInfo.idFile == 0, LogWwiseFileHandler, VeryVerbose, TEXT("Getting external source %" PRIu32 " (%s): Using memory file"), InCookedData.Cookie, *InCookedData.DebugName.ToString()); + return ExternalSourceFileState->MediaId; +} + +void FWwiseExternalSourceManagerImpl::BindPlayingIdToExternalSources(const uint32 InPlayingId, + const TArray& InMediaIds) +{ + if (InMediaIds.Num() == 0) + { + return; + } + + SCOPED_WWISEFILEHANDLER_EVENT_2(TEXT("FWwiseExternalSourceManagerImpl::BindPlayingIdToExternalSources")); + if (UNLIKELY(InPlayingId == AK_INVALID_PLAYING_ID)) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("BindPlayingIdToExternalSources: Failed PostEvent. Unpreparing %d Media."), InMediaIds.Num()); + + FRWScopeLock Lock(CookieToMediaLock, FRWScopeLockType::SLT_ReadOnly); + for (const auto MediaId : InMediaIds) + { + FWwiseFileStateSharedPtr State; + { + FRWScopeLock StateLock(FileStatesByIdLock, FRWScopeLockType::SLT_ReadOnly); + const auto* StatePtr = FileStatesById.Find(MediaId); + if (UNLIKELY(!StatePtr || !StatePtr->IsValid())) + { + UE_LOG(LogWwiseFileHandler, Warning, TEXT("BindPlayingIdToExternalSources: Getting external source media state %" PRIu32 " failed to decrement after failed PostEvent."), MediaId); + continue; + } + State = *StatePtr; + } + auto* ExternalSourceFileState = State->GetStateAs(); + if (UNLIKELY(!ExternalSourceFileState)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("BindPlayingIdToExternalSources: Getting external source media %" PRIu32 ": Could not cast to ExternalSourceState"), MediaId); + continue; + } + + FileHandlerExecutionQueue.Async([this, MediaId, ExternalSourceFileState]() mutable + { + // This type is safe as long as we don't decrement its usage + if (ExternalSourceFileState->DecrementPlayCount() && ExternalSourceFileState->CanDelete()) + { + OnDeleteState(MediaId, *ExternalSourceFileState, EWwiseFileStateOperationOrigin::Loading, []{}); + } + }); + } + } + else + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("BindPlayingIdToExternalSources: Binding %d ExtSrc Media to Playing ID %" PRIu32 "."), InMediaIds.Num(), InPlayingId); + for (const auto MediaId : InMediaIds) + { + PlayingIdToMediaIds.AddUnique(InPlayingId, MediaId); + } + } +} + +void FWwiseExternalSourceManagerImpl::OnEndOfEvent(const uint32 InPlayingId) +{ + if (!PlayingIdToMediaIds.Contains(InPlayingId)) + { + return; + } + + SCOPED_WWISEFILEHANDLER_EVENT_2(TEXT("FWwiseExternalSourceManagerImpl::OnEndOfEvent")); + TArray MediaIds; + PlayingIdToMediaIds.MultiFind(InPlayingId, MediaIds); + PlayingIdToMediaIds.Remove(InPlayingId); + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("OnEndOfEvent: Unbinding %d ExtSrc Media from Playing ID %" PRIu32 "."), MediaIds.Num(), InPlayingId); + + FRWScopeLock Lock(CookieToMediaLock, FRWScopeLockType::SLT_ReadOnly); + + for (const auto MediaId : MediaIds) + { + FWwiseFileStateSharedPtr State; + { + FRWScopeLock StateLock(FileStatesByIdLock, FRWScopeLockType::SLT_ReadOnly); + const auto* StatePtr = FileStatesById.Find(MediaId); + if (UNLIKELY(!StatePtr || !StatePtr->IsValid())) + { + UE_LOG(LogWwiseFileHandler, Warning, TEXT("OnEndOfEvent: Getting external source media state %" PRIu32 " failed to decrement after failed PostEvent."), MediaId); + continue; + } + State = *StatePtr; + } + auto* ExternalSourceFileState = State->GetStateAs(); + if (UNLIKELY(!ExternalSourceFileState)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("OnEndOfEvent: Getting external source media %" PRIu32 ": Could not cast to ExternalSourceState"), MediaId); + continue; + } + FileHandlerExecutionQueue.Async([this, MediaId, ExternalSourceFileState]() mutable + { + // This type is safe as long as we don't decrement its usage + if (ExternalSourceFileState->DecrementPlayCount() && ExternalSourceFileState->CanDelete()) + { + OnDeleteState(MediaId, *ExternalSourceFileState, EWwiseFileStateOperationOrigin::Loading, []{}); + } + }); + } +} + +void FWwiseExternalSourceManagerImpl::OnDeleteState(uint32 InShortId, FWwiseFileState& InFileState, + EWwiseFileStateOperationOrigin InOperationOrigin, FDecrementStateCallback&& InCallback) +{ + if (InFileState.CanDelete()) + { + FRWScopeLock Lock(CookieToMediaLock, FRWScopeLockType::SLT_Write); + TArray CookiesToRemove; + for (const auto Item : CookieToMedia) + { + if (Item.Value == &InFileState) + { + CookiesToRemove.Add(Item.Key); + } + } + for (const auto Cookie : CookiesToRemove) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("Removing Cookie %" PRIu32 " binding to media %" PRIu32 "."), Cookie, InFileState.GetShortId()); + CookieToMedia.Remove(Cookie); + } + } + FWwiseFileHandlerBase::OnDeleteState(InShortId, InFileState, InOperationOrigin, MoveTemp(InCallback)); +} + +void FWwiseExternalSourceManagerImpl::SetExternalSourceMediaById(const FName& ExternalSourceName, const int32 MediaId) +{ + UE_LOG(LogWwiseFileHandler, Error, TEXT("External Source manager needs to be overridden.")); +} + +void FWwiseExternalSourceManagerImpl::SetExternalSourceMediaByName(const FName& ExternalSourceName, + const FName& MediaName) +{ + UE_LOG(LogWwiseFileHandler, Error, TEXT("External Source manager needs to be overridden.")); +} + +void FWwiseExternalSourceManagerImpl::SetExternalSourceMediaWithIds(const int32 ExternalSourceCookie, + const int32 MediaId) +{ + UE_LOG(LogWwiseFileHandler, Error, TEXT("External Source manager needs to be overridden.")); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseExternalSourceStatics.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseExternalSourceStatics.cpp new file mode 100644 index 0000000..2d5048d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseExternalSourceStatics.cpp @@ -0,0 +1,43 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseExternalSourceStatics.h" +#include "Wwise/WwiseExternalSourceManager.h" + +void UWwiseExternalSourceStatics::SetExternalSourceMediaById(const FString& ExternalSourceName, const int32 MediaId) +{ + if (auto ExternalSourceManager = IWwiseExternalSourceManager::Get()) + { + ExternalSourceManager->SetExternalSourceMediaById(FName(ExternalSourceName), MediaId); + } +} + +void UWwiseExternalSourceStatics::SetExternalSourceMediaByName(const FString& ExternalSourceName, const FString& MediaName) +{ + if (auto ExternalSourceManager = IWwiseExternalSourceManager::Get()) + { + ExternalSourceManager->SetExternalSourceMediaByName(FName(ExternalSourceName), FName(MediaName)); + } +} + +void UWwiseExternalSourceStatics::SetExternalSourceMediaWithIds(const FAkUniqueID ExternalSourceCookie, const int32 MediaId) +{ + if (auto ExternalSourceManager = IWwiseExternalSourceManager::Get()) + { + ExternalSourceManager->SetExternalSourceMediaWithIds(ExternalSourceCookie.UInt32Value, MediaId); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseFileCache.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseFileCache.cpp new file mode 100644 index 0000000..a73361b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseFileCache.cpp @@ -0,0 +1,330 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseFileCache.h" + +#include "Wwise/WwiseExecutionQueue.h" +#include "Wwise/WwiseFileHandlerModule.h" +#include "Wwise/Stats/AsyncStats.h" +#include "Wwise/Stats/FileHandler.h" +#include "WwiseDefines.h" + +#include "Async/Async.h" +#include "Async/AsyncFileHandle.h" +#if UE_5_0_OR_LATER +#include "HAL/PlatformFileManager.h" +#else +#include "HAL/PlatformFilemanager.h" +#endif + +#include + +FWwiseFileCache* FWwiseFileCache::Get() +{ + if (auto* Module = IWwiseFileHandlerModule::GetModule()) + { + if (auto* FileCache = Module->GetFileCache()) + { + return FileCache; + } + } + return nullptr; +} + +FWwiseFileCache::FWwiseFileCache() +{ +} + +FWwiseFileCache::~FWwiseFileCache() +{ +} + +void FWwiseFileCache::CreateFileCacheHandle( + FWwiseFileCacheHandle*& OutHandle, + const FString& Pathname, + FWwiseFileOperationDone&& OnDone) +{ + OutHandle = new FWwiseFileCacheHandle(Pathname); + if (UNLIKELY(!OutHandle)) + { + OnDone(false); + } + OutHandle->Open(MoveTemp(OnDone)); +} + +FWwiseFileCacheHandle::FWwiseFileCacheHandle(const FString& InPathname) : + Pathname { InPathname }, + FileHandle { nullptr }, + FileSize { 0 }, + InitializationStat { nullptr } +{ +} + +FWwiseFileCacheHandle::~FWwiseFileCacheHandle() +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseFileCacheHandle::~FWwiseFileCacheHandle")); + + const auto* FileHandleToDestroy = FileHandle; FileHandle = nullptr; + + if (UNLIKELY(RequestsInFlight.Load() > 0)) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseFileCacheHandle: Closing %s with %" PRIi32 " operations left to process."), *Pathname, RequestsInFlight.Load()); + auto* CanDestroyEvent = FPlatformProcess::GetSynchEventFromPool(false); + CanDestroy.Store(CanDestroyEvent, EMemoryOrder::SequentiallyConsistent); + while (RequestsInFlight.Load(EMemoryOrder::SequentiallyConsistent) > 0) + { + CanDestroyEvent->Wait(FTimespan::FromMilliseconds(1)); + } + CanDestroy.Store(nullptr); + FFunctionGraphTask::CreateAndDispatchWhenReady([CanDestroyEvent] + { + FPlatformProcess::ReturnSynchEventToPool(CanDestroyEvent); + }); + } + + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseFileCacheHandle: Closing %s."), *Pathname); + delete FileHandleToDestroy; + DEC_DWORD_STAT(STAT_WwiseFileHandlerOpenedStreams); +} + +void FWwiseFileCacheHandle::Open(FWwiseFileOperationDone&& OnDone) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseFileCacheHandle::Open")); + check(!InitializationStat); + check(!FileHandle); + + InitializationStat = new FWwiseAsyncCycleCounter(GET_STATID(STAT_WwiseFileHandlerFileOperationLatency)); + InitializationDone = MoveTemp(OnDone); + + FWwiseAsyncCycleCounter Stat(GET_STATID(STAT_WwiseFileHandlerFileOperationLatency)); + + const auto FileCache = FWwiseFileCache::Get(); + if (UNLIKELY(!FileCache)) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseFileCacheHandle: FileCache not available while opening %s."), *Pathname); + delete InitializationStat; InitializationStat = nullptr; + CallDone(false, MoveTemp(InitializationDone)); + return; + } + + ++RequestsInFlight; + FileCache->OpenQueue.Async([this, OnDone = MoveTemp(OnDone)]() mutable + { + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseFileCacheHandle::Open Async")); + check(!FileHandle); + + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseFileCacheHandle: Opening %s."), *Pathname); + IAsyncReadFileHandle* CurrentFileHandle; + ASYNC_INC_DWORD_STAT(STAT_WwiseFileHandlerOpenedStreams); + { + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseFileCacheHandle::Open OpenAsyncRead")); + CurrentFileHandle = FileHandle = FPlatformFileManager::Get().GetPlatformFile().OpenAsyncRead(*Pathname); + } + if (UNLIKELY(!CurrentFileHandle)) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseFileCacheHandle: OpenAsyncRead %s failed instantiating."), *Pathname); + delete InitializationStat; InitializationStat = nullptr; + CallDone(false, MoveTemp(InitializationDone)); + RemoveRequestInFlight(); + return; + } + + FAsyncFileCallBack SizeCallbackFunction = [this](bool bWasCancelled, IAsyncReadRequest* Request) mutable + { + OnSizeRequestDone(bWasCancelled, Request); + }; + IAsyncReadRequest* Request; + { + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseFileCacheHandle::Open SizeRequest")); + // ++RequestsInFlight; already done + Request = CurrentFileHandle->SizeRequest(&SizeCallbackFunction); + } + if (UNLIKELY(!Request)) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseFileCacheHandle: SizeRequest %s failed instantiating."), *Pathname); + delete InitializationStat; InitializationStat = nullptr; + CallDone(false, MoveTemp(InitializationDone)); + RemoveRequestInFlight(); + } + }); +} + +void FWwiseFileCacheHandle::OnSizeRequestDone(bool bWasCancelled, IAsyncReadRequest* Request) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseFileCacheHandle::OnSizeRequestDone")); + FileSize = Request->GetSizeResults(); + + const bool bSizeOpSuccess = LIKELY(FileSize > 0); + + UE_CLOG(!bSizeOpSuccess, LogWwiseFileHandler, Log, TEXT("FWwiseFileCacheHandle: Streamed file \"%s\" could not be opened."), *Pathname); + UE_CLOG(bSizeOpSuccess, LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseFileCacheHandle: Initializing %s succeeded."), *Pathname); + delete InitializationStat; InitializationStat = nullptr; + CallDone(bSizeOpSuccess, MoveTemp(InitializationDone)); + DeleteRequest(Request); +} + +void FWwiseFileCacheHandle::CallDone(bool bResult, FWwiseFileOperationDone&& OnDone) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseFileCacheHandle::CallDone Callback")); + OnDone(bResult); +} + +void FWwiseFileCacheHandle::RemoveRequestInFlight() +{ + auto* CanDestroyEvent = CanDestroy.Load(EMemoryOrder::SequentiallyConsistent); + --RequestsInFlight; + if (CanDestroyEvent) + { + CanDestroyEvent->Trigger(); + } +} + +void FWwiseFileCacheHandle::DeleteRequest(IAsyncReadRequest* Request) +{ + if (!Request || Request->PollCompletion()) + { + SCOPED_WWISEFILEHANDLER_EVENT_4(TEXT("FWwiseFileCacheHandle::DeleteRequest")); + delete Request; + RemoveRequestInFlight(); + } + else + { + const auto FileCache = FWwiseFileCache::Get(); + if (LIKELY(FileCache)) + { + FileCache->DeleteRequestQueue.AsyncAlways([this, Request]() mutable + { + DeleteRequest(Request); + }); + } + else + { + FFunctionGraphTask::CreateAndDispatchWhenReady([this, Request]() mutable + { + DeleteRequest(Request); + }); + } + } +}; + +void FWwiseFileCacheHandle::ReadData(uint8* OutBuffer, int64 Offset, int64 BytesToRead, + EAsyncIOPriorityAndFlags Priority, FWwiseFileOperationDone&& OnDone) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseFileCacheHandle::ReadData")); + FWwiseAsyncCycleCounter Stat(GET_STATID(STAT_WwiseFileHandlerFileOperationLatency)); + ++RequestsInFlight; + + IAsyncReadFileHandle* CurrentFileHandle = FileHandle; + if (UNLIKELY(!CurrentFileHandle)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseFileCacheHandle::ReadData: Trying to read in file %s while it was not properly initialized."), *Pathname); + OnReadDataDone(false, MoveTemp(OnDone)); + return; + } + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseFileCacheHandle::ReadData: %" PRIi64 "@%" PRIi64 " in %s"), BytesToRead, Offset, *Pathname); + FAsyncFileCallBack ReadCallbackFunction = [this, OnDone = new FWwiseFileOperationDone(MoveTemp(OnDone)), BytesToRead, Stat = MoveTemp(Stat)](bool bWasCancelled, IAsyncReadRequest* Request) mutable + { + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseFileCacheHandle::ReadData Callback")); + if (!bWasCancelled && Request) // Do not add Request->GetReadResults() since it will break subsequent results retrievals. + { + ASYNC_INC_FLOAT_STAT_BY(STAT_WwiseFileHandlerTotalStreamedMB, static_cast(BytesToRead) / 1024 / 1024); + } + OnReadDataDone(bWasCancelled, Request, MoveTemp(*OnDone)); + delete OnDone; + DeleteRequest(Request); + }; + ASYNC_INC_FLOAT_STAT_BY(STAT_WwiseFileHandlerStreamingKB, static_cast(BytesToRead) / 1024); + check(BytesToRead > 0); + +#if STATS + switch (Priority & EAsyncIOPriorityAndFlags::AIOP_PRIORITY_MASK) + { + case EAsyncIOPriorityAndFlags::AIOP_CriticalPath: INC_DWORD_STAT(STAT_WwiseFileHandlerCriticalPriority); break; + case EAsyncIOPriorityAndFlags::AIOP_High: INC_DWORD_STAT(STAT_WwiseFileHandlerHighPriority); break; + case EAsyncIOPriorityAndFlags::AIOP_BelowNormal: INC_DWORD_STAT(STAT_WwiseFileHandlerBelowNormalPriority); break; + case EAsyncIOPriorityAndFlags::AIOP_Low: INC_DWORD_STAT(STAT_WwiseFileHandlerLowPriority); break; + case EAsyncIOPriorityAndFlags::AIOP_MIN: INC_DWORD_STAT(STAT_WwiseFileHandlerBackgroundPriority); break; + + default: + case EAsyncIOPriorityAndFlags::AIOP_Normal: INC_DWORD_STAT(STAT_WwiseFileHandlerNormalPriority); break; + } +#endif + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseFileCacheHandle::ReadData Async ReadRequest")); + const auto* Request = CurrentFileHandle->ReadRequest(Offset, BytesToRead, Priority, &ReadCallbackFunction, OutBuffer); + if (UNLIKELY(!Request)) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseFileCacheHandle::ReadData: ReadRequest %s failed instantiating."), *Pathname); + ReadCallbackFunction(true, nullptr); + } +} + +void FWwiseFileCacheHandle::ReadAkData(uint8* OutBuffer, int64 Offset, int64 BytesToRead, int8 AkPriority, FWwiseFileOperationDone&& OnDone) +{ + // Wwise priority is what we expect our priority to be. Audio will skip if "our normal" is not met. + constexpr const auto bHigherAudioPriority = true; + + EAsyncIOPriorityAndFlags Priority; + if (LIKELY(AkPriority == AK_DEFAULT_PRIORITY)) + { + Priority = bHigherAudioPriority ? AIOP_High : AIOP_Normal; + } + else if (AkPriority <= AK_MIN_PRIORITY) + { + Priority = bHigherAudioPriority ? AIOP_BelowNormal : AIOP_Low; + } + else if (AkPriority >= AK_MAX_PRIORITY) + { + Priority = AIOP_CriticalPath; + } + else if (AkPriority < AK_DEFAULT_PRIORITY) + { + Priority = bHigherAudioPriority ? AIOP_Normal : AIOP_Low; + } + else + { + Priority = bHigherAudioPriority ? AIOP_CriticalPath : AIOP_High; + } + ReadData(OutBuffer, Offset, BytesToRead, Priority, MoveTemp(OnDone)); +} + +void FWwiseFileCacheHandle::ReadAkData(const AkIoHeuristics& Heuristics, AkAsyncIOTransferInfo& TransferInfo, + FWwiseAkFileOperationDone&& Callback) +{ + ReadAkData( + static_cast(TransferInfo.pBuffer), + static_cast(TransferInfo.uFilePosition), + static_cast(TransferInfo.uRequestedSize), + Heuristics.priority, + [TransferInfo = &TransferInfo, FileOpDoneCallback = MoveTemp(Callback)](bool bResult) + { + FileOpDoneCallback(TransferInfo, bResult ? AK_Success : AK_UnknownFileError); + }); +} + + +void FWwiseFileCacheHandle::OnReadDataDone(bool bWasCancelled, IAsyncReadRequest* Request, + FWwiseFileOperationDone&& OnDone) +{ + OnReadDataDone(!bWasCancelled && Request && Request->GetReadResults(), MoveTemp(OnDone)); +} + +void FWwiseFileCacheHandle::OnReadDataDone(bool bResult, FWwiseFileOperationDone&& OnDone) +{ + --RequestsInFlight; + CallDone(bResult, MoveTemp(OnDone)); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseFileHandlerBase.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseFileHandlerBase.cpp new file mode 100644 index 0000000..1069ab9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseFileHandlerBase.cpp @@ -0,0 +1,239 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseFileHandlerBase.h" +#include "Wwise/WwiseIOHook.h" +#include "Wwise/WwiseStreamableFileStateInfo.h" + +#include "Wwise/Stats/FileHandler.h" +#include "Wwise/Stats/AsyncStats.h" + +#include "Misc/ScopeRWLock.h" + +#include + + +FWwiseFileHandlerBase::FWwiseFileHandlerBase() +{ +} + +FWwiseFileHandlerBase::~FWwiseFileHandlerBase() +{ + FRWScopeLock StateLock(FileStatesByIdLock, FRWScopeLockType::SLT_Write); + if (UNLIKELY(FileStatesById.Num() > 0)) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("Closing FileHandler with remaining %" PRIu32 " FileStates. Leaking."), FileStatesById.Num()); + for (auto& State : FileStatesById) + { + // Locking in memory the file states + new FWwiseFileStateSharedPtr(State.Value); + } + } +} + +void FWwiseFileHandlerBase::OpenStreaming(AkAsyncFileOpenData* io_pOpenData) +{ + FWwiseAsyncCycleCounter OpCycleCounter(GET_STATID(STAT_WwiseFileHandlerIORequestLatency)); + + IncrementFileStateUseAsync(io_pOpenData->fileID, EWwiseFileStateOperationOrigin::Streaming, + [ManagingTypeName = GetManagingTypeName(), io_pOpenData] + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("Trying to open streaming for unknown %s %" PRIu32), ManagingTypeName, io_pOpenData->fileID); + return FWwiseFileStateSharedPtr{}; + }, + [this, io_pOpenData, OpCycleCounter = MoveTemp(OpCycleCounter)](const FWwiseFileStateSharedPtr& InFileState, bool bInResult) mutable + { + OpCycleCounter.Stop(); + + SCOPED_WWISEFILEHANDLER_EVENT_F_3(TEXT("FWwiseFileHandlerBase::OpenStreaming %s Async"), GetManagingTypeName()); + AKRESULT Result = GetOpenStreamingResult(io_pOpenData); + if (Result == AK_Success && UNLIKELY(!bInResult)) + { + Result = AK_UnknownFileError; + } + + if (LIKELY(Result == AK_Success)) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("Succeeded opening %" PRIu32 " for streaming"), io_pOpenData->fileID); + io_pOpenData->pCallback(io_pOpenData, Result); + } + else + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("Failed opening %" PRIu32 " for streaming. Doing callback later."), io_pOpenData->fileID); + FFunctionGraphTask::CreateAndDispatchWhenReady([io_pOpenData, Result] + { + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseFileHandlerBase::OpenStreaming Failure Async")); + io_pOpenData->pCallback(io_pOpenData, Result); + }); + } + }); +} + +AKRESULT FWwiseFileHandlerBase::GetOpenStreamingResult(AkAsyncFileOpenData* io_pOpenData) +{ + FWwiseAsyncCycleCounter OpCycleCounter(GET_STATID(STAT_WwiseFileHandlerIORequestLatency)); + + FWwiseFileStateSharedPtr State; + { + FRWScopeLock StateLock(FileStatesByIdLock, FRWScopeLockType::SLT_ReadOnly); + const auto* StatePtr = FileStatesById.Find(io_pOpenData->fileID); + if (UNLIKELY(!StatePtr || !StatePtr->IsValid())) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("Could not open file %" PRIu32 " for streaming: File wasn't initialized prior to OpenStreaming."), io_pOpenData->fileID); + return AK_FileNotFound; + } + State = *StatePtr; + } + + AKRESULT Result = AK_Success; + if (auto* StreamableFileStateInfo = State->GetStreamableFileStateInfo()) + { + io_pOpenData->pFileDesc = StreamableFileStateInfo->GetFileDesc(); + } + else + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("Could not open file %" PRIu32 " for streaming: Could not get AkFileDesc."), io_pOpenData->fileID); + Result = AK_UnknownFileError; + } + return Result; +} + +void FWwiseFileHandlerBase::CloseStreaming(uint32 InShortId, FWwiseFileState& InFileState) +{ + return DecrementFileStateUseAsync(InShortId, &InFileState, EWwiseFileStateOperationOrigin::Streaming, []{}); +} + +void FWwiseFileHandlerBase::IncrementFileStateUseAsync(uint32 InShortId, EWwiseFileStateOperationOrigin InOperationOrigin, + FCreateStateFunction&& InCreate, FIncrementStateCallback&& InCallback) +{ + FileHandlerExecutionQueue.Async([this, InShortId, InOperationOrigin, InCreate = MoveTemp(InCreate), InCallback = MoveTemp(InCallback)]() mutable + { + IncrementFileStateUse(InShortId, InOperationOrigin, MoveTemp(InCreate), MoveTemp(InCallback)); + }); +} + +void FWwiseFileHandlerBase::DecrementFileStateUseAsync(uint32 InShortId, FWwiseFileState* InFileState, EWwiseFileStateOperationOrigin InOperationOrigin, FDecrementStateCallback&& InCallback) +{ + FileHandlerExecutionQueue.Async([this, InShortId, InFileState, InOperationOrigin, InCallback = MoveTemp(InCallback)]() mutable + { + DecrementFileStateUse(InShortId, InFileState, InOperationOrigin, MoveTemp(InCallback)); + }); +} + +void FWwiseFileHandlerBase::IncrementFileStateUse(uint32 InShortId, EWwiseFileStateOperationOrigin InOperationOrigin, FCreateStateFunction&& InCreate, FIncrementStateCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_F_3(TEXT("FWwiseFileHandlerBase::IncrementFileStateUse %s"), GetManagingTypeName()); + FWwiseFileStateSharedPtr State; + { + FRWScopeLock StateLock(FileStatesByIdLock, FRWScopeLockType::SLT_ReadOnly); + if (const auto* StatePtr = FileStatesById.Find(InShortId)) + { + State = *StatePtr; + } + } + + if (!State.IsValid()) + { + FRWScopeLock StateLock(FileStatesByIdLock, FRWScopeLockType::SLT_Write); + if (const auto* StatePtr = FileStatesById.Find(InShortId)) + { + State = *StatePtr; + } + else + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("Creating new State for %s %" PRIu32), GetManagingTypeName(), InShortId); + State = InCreate(); + if (LIKELY(State.IsValid())) + { + FileStatesById.Add(InShortId, State); + } + } + } + + if (UNLIKELY(!State.IsValid())) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("Trying to increment invalid state for %s %" PRIu32), GetManagingTypeName(), InShortId); + SCOPED_WWISEFILEHANDLER_EVENT_4(TEXT("FWwiseFileHandlerBase::IncrementFileStateUse Callback")); + InCallback(State, false); + } + else + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("Incrementing State for %s %" PRIu32), GetManagingTypeName(), InShortId); + State->IncrementCountAsync(InOperationOrigin, [State, InCallback = MoveTemp(InCallback)](bool bInResult) + { + SCOPED_WWISEFILEHANDLER_EVENT_4(TEXT("FWwiseFileHandlerBase::IncrementFileStateUse Callback")); + InCallback(State, bInResult); + }); + } +} + +void FWwiseFileHandlerBase::DecrementFileStateUse(uint32 InShortId, FWwiseFileState* InFileState, EWwiseFileStateOperationOrigin InOperationOrigin, FDecrementStateCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_F_3(TEXT("FWwiseFileHandlerBase::DecrementFileStateUse %s"), GetManagingTypeName()); + if (!InFileState) + { + const FWwiseFileStateSharedPtr* StatePtr; + { + FRWScopeLock StateLock(FileStatesByIdLock, FRWScopeLockType::SLT_ReadOnly); + StatePtr = FileStatesById.Find(InShortId); + if (LIKELY(StatePtr && StatePtr->IsValid())) + { + InFileState = StatePtr->Get(); + } + } + if (UNLIKELY(!StatePtr || !StatePtr->IsValid())) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("Could not find state for for %s %" PRIu32), GetManagingTypeName(), InShortId); + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileHandlerBase::DecrementFileStateUse %s Callback"), GetManagingTypeName()); + InCallback(); + return; + } + } + + InFileState->DecrementCountAsync(InOperationOrigin, [this, InShortId, InFileState, InOperationOrigin](FDecrementStateCallback&& InCallback) mutable + { + // File state deletion request + FileHandlerExecutionQueue.Async([this, InShortId, InFileState, InOperationOrigin, InCallback = MoveTemp(InCallback)]() mutable + { + OnDeleteState(InShortId, *InFileState, InOperationOrigin, MoveTemp(InCallback)); + }); + }, MoveTemp(InCallback)); +} + +void FWwiseFileHandlerBase::OnDeleteState(uint32 InShortId, FWwiseFileState& InFileState, EWwiseFileStateOperationOrigin InOperationOrigin, FDecrementStateCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_F_3(TEXT("FWwiseFileHandlerBase::OnDeleteState %s"), GetManagingTypeName()); + { + FRWScopeLock StateLock(FileStatesByIdLock, FRWScopeLockType::SLT_Write); + if (!InFileState.CanDelete()) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("OnDeleteState %s %" PRIu32 ": Cannot delete State. Probably re-loaded between deletion request and now."), + GetManagingTypeName(), InShortId); + } + else + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("OnDeleteState %s %" PRIu32 ": Deleting."), GetManagingTypeName(), InShortId); + const auto RemovalCount = FileStatesById.Remove(InShortId); // WARNING: This will very probably delete InFileState reference. Do not use the File State from that point! + + UE_CLOG(RemovalCount != 1, LogWwiseFileHandler, Error, TEXT("Removing a state for %s %" PRIu32 ", ended up deleting %" PRIi32 " states."), + GetManagingTypeName(), InShortId, RemovalCount); + } + } + + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileHandlerBase::OnDeleteState %s Callback"), GetManagingTypeName()); + InCallback(); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseFileHandlerModuleImpl.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseFileHandlerModuleImpl.cpp new file mode 100644 index 0000000..a1138b1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseFileHandlerModuleImpl.cpp @@ -0,0 +1,171 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseFileHandlerModuleImpl.h" +#include "Wwise/WwiseSoundBankManagerImpl.h" +#include "Wwise/WwiseExternalSourceManagerImpl.h" +#include "Wwise/WwiseFileCache.h" +#include "Wwise/WwiseMediaManagerImpl.h" +#include "Wwise/WwiseIOHookImpl.h" +#include "Wwise/Stats/FileHandler.h" + +IMPLEMENT_MODULE(FWwiseFileHandlerModule, WwiseFileHandler) + +FWwiseFileHandlerModule::FWwiseFileHandlerModule() +{ +} + +IWwiseSoundBankManager* FWwiseFileHandlerModule::GetSoundBankManager() +{ + Lock.ReadLock(); + if (LIKELY(SoundBankManager)) + { + Lock.ReadUnlock(); + } + else + { + Lock.ReadUnlock(); + Lock.WriteLock(); + if (LIKELY(!SoundBankManager)) + { + UE_LOG(LogWwiseFileHandler, Display, TEXT("Initializing default SoundBank Manager.")); + SoundBankManager.Reset(InstantiateSoundBankManager()); + } + Lock.WriteUnlock(); + } + return SoundBankManager.Get(); +} + +IWwiseExternalSourceManager* FWwiseFileHandlerModule::GetExternalSourceManager() +{ + Lock.ReadLock(); + if (LIKELY(ExternalSourceManager)) + { + Lock.ReadUnlock(); + } + else + { + Lock.ReadUnlock(); + Lock.WriteLock(); + if (LIKELY(!ExternalSourceManager)) + { + UE_LOG(LogWwiseFileHandler, Display, TEXT("Initializing default External Source Manager.")); + ExternalSourceManager.Reset(InstantiateExternalSourceManager()); + } + Lock.WriteUnlock(); + } + return ExternalSourceManager.Get(); +} + +IWwiseMediaManager* FWwiseFileHandlerModule::GetMediaManager() +{ + Lock.ReadLock(); + if (LIKELY(MediaManager)) + { + Lock.ReadUnlock(); + } + else + { + Lock.ReadUnlock(); + Lock.WriteLock(); + if (LIKELY(!MediaManager)) + { + UE_LOG(LogWwiseFileHandler, Display, TEXT("Initializing default Media Manager.")); + MediaManager.Reset(InstantiateMediaManager()); + } + Lock.WriteUnlock(); + } + return MediaManager.Get(); +} + +FWwiseFileCache* FWwiseFileHandlerModule::GetFileCache() +{ + Lock.ReadLock(); + if (LIKELY(FileCache)) + { + Lock.ReadUnlock(); + } + else + { + Lock.ReadUnlock(); + Lock.WriteLock(); + if (LIKELY(!FileCache)) + { + UE_LOG(LogWwiseFileHandler, Display, TEXT("Initializing default File Cache.")); + FileCache.Reset(InstantiateFileCache()); + } + Lock.WriteUnlock(); + } + return FileCache.Get(); +} + +FWwiseIOHook* FWwiseFileHandlerModule::InstantiateIOHook() +{ + return new FWwiseIOHookImpl; +} + +IWwiseSoundBankManager* FWwiseFileHandlerModule::InstantiateSoundBankManager() +{ + return new FWwiseSoundBankManagerImpl; +} + +IWwiseExternalSourceManager* FWwiseFileHandlerModule::InstantiateExternalSourceManager() +{ + return new FWwiseExternalSourceManagerImpl; +} + +IWwiseMediaManager* FWwiseFileHandlerModule::InstantiateMediaManager() +{ + return new FWwiseMediaManagerImpl; +} + +FWwiseFileCache* FWwiseFileHandlerModule::InstantiateFileCache() +{ + return new FWwiseFileCache; +} + +void FWwiseFileHandlerModule::StartupModule() +{ + IWwiseFileHandlerModule::StartupModule(); +} + +void FWwiseFileHandlerModule::ShutdownModule() +{ + Lock.WriteLock(); + if (SoundBankManager.IsValid()) + { + UE_LOG(LogWwiseFileHandler, Display, TEXT("Shutting down SoundBank Manager.")); + SoundBankManager.Reset(); + } + if (ExternalSourceManager.IsValid()) + { + UE_LOG(LogWwiseFileHandler, Display, TEXT("Shutting down External Source Manager.")); + ExternalSourceManager.Reset(); + } + if (MediaManager.IsValid()) + { + UE_LOG(LogWwiseFileHandler, Display, TEXT("Shutting down Media Manager.")); + MediaManager.Reset(); + } + if (FileCache.IsValid()) + { + UE_LOG(LogWwiseFileHandler, Display, TEXT("Shutting down File Cache.")); + FileCache.Reset(); + } + Lock.WriteUnlock(); + IWwiseFileHandlerModule::ShutdownModule(); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseFileState.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseFileState.cpp new file mode 100644 index 0000000..35ee7c4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseFileState.cpp @@ -0,0 +1,796 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseFileState.h" +#include "Wwise/WwiseGlobalCallbacks.h" +#include "Wwise/Stats/AsyncStats.h" + +#include + +#include "Wwise/WwiseStreamableFileStateInfo.h" + +FWwiseFileState::~FWwiseFileState() +{ + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseFileState %p: Dtor"), this); + UE_CLOG(FileStateExecutionQueue, LogWwiseFileHandler, Error, TEXT("Closing the File State without closing the execution queue.")); + if (LoadCount > 0) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("Deleting FWwiseFileState %p with LoadCount still active!"), this); + } +} + +void FWwiseFileState::IncrementCountAsync(EWwiseFileStateOperationOrigin InOperationOrigin, + FIncrementCountCallback&& InCallback) +{ + FWwiseAsyncCycleCounter OpCycleCounter(GET_STATID(STAT_WwiseFileHandlerStateOperationLatency)); + FileStateExecutionQueue->Async([this, InOperationOrigin, OpCycleCounter = MoveTemp(OpCycleCounter), InCallback = MoveTemp(InCallback)]() mutable + { + INC_DWORD_STAT(STAT_WwiseFileHandlerStateOperationsBeingProcessed); + IncrementCount(InOperationOrigin, [OpCycleCounter = MoveTemp(OpCycleCounter), InCallback = MoveTemp(InCallback)](bool bInResult) mutable + { + OpCycleCounter.Stop(); + DEC_DWORD_STAT(STAT_WwiseFileHandlerStateOperationsBeingProcessed); + SCOPED_WWISEFILEHANDLER_EVENT_4(TEXT("FWwiseFileState::IncrementCountAsync Callback")); + InCallback(bInResult); + }); + }); +} + +void FWwiseFileState::DecrementCountAsync(EWwiseFileStateOperationOrigin InOperationOrigin, + FDeleteFileStateFunction&& InDeleteState, FDecrementCountCallback&& InCallback) +{ + FWwiseAsyncCycleCounter OpCycleCounter(GET_STATID(STAT_WwiseFileHandlerStateOperationLatency)); + + FileStateExecutionQueue->Async([this, InOperationOrigin, OpCycleCounter = MoveTemp(OpCycleCounter), InDeleteState = MoveTemp(InDeleteState), InCallback = MoveTemp(InCallback)]() mutable + { + INC_DWORD_STAT(STAT_WwiseFileHandlerStateOperationsBeingProcessed); + DecrementCount(InOperationOrigin, MoveTemp(InDeleteState), [OpCycleCounter = MoveTemp(OpCycleCounter), InCallback = MoveTemp(InCallback)]() mutable + { + OpCycleCounter.Stop(); + DEC_DWORD_STAT(STAT_WwiseFileHandlerStateOperationsBeingProcessed); + SCOPED_WWISEFILEHANDLER_EVENT_4(TEXT("FWwiseFileState::DecrementCountAsync Callback")); + InCallback(); + }); + }); +} + +bool FWwiseFileState::CanDelete() const +{ + return State == EState::Closed && LoadCount == 0; +} + +FWwiseFileState::FWwiseFileState(): + LoadCount(0), + StreamingCount(0), + State(EState::Closed) +{ + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseFileState %p: Ctor"), this); +} + +void FWwiseFileState::Term() +{ + if (UNLIKELY(!FileStateExecutionQueue)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseFileState::Term: %s file state %" PRIu32" already Term!"), GetManagingTypeName(), GetShortId()); + return; + } + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseFileState::Term %p: Term %s file state %" PRIu32"."), this, GetManagingTypeName(), GetShortId()); + UE_CLOG(!IsEngineExitRequested() && UNLIKELY(State != EState::Closed), LogWwiseFileHandler, Warning, TEXT("FWwiseFileState::Term %s State: Term unclosed file state %" PRIu32 ". Leaking."), GetManagingTypeName(), GetShortId()); + UE_CLOG(IsEngineExitRequested() && State != EState::Closed, LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseFileState::Term %s State: Term unclosed file state %" PRIu32 " at exit. Leaking."), GetManagingTypeName(), GetShortId()); + UE_CLOG(LoadCount != 0, LogWwiseFileHandler, Log, TEXT("FWwiseFileState::Term: %s file state %" PRIu32 " when there are still %d load count"), GetManagingTypeName(), GetShortId(), LoadCount); + + FileStateExecutionQueue->CloseAndDelete(); FileStateExecutionQueue = nullptr; +} + +void FWwiseFileState::IncrementCount(EWwiseFileStateOperationOrigin InOperationOrigin, + FIncrementCountCallback&& InCallback) +{ + IncrementLoadCount(InOperationOrigin); + + IncrementCountOpen(InOperationOrigin, MoveTemp(InCallback)); +} + +void FWwiseFileState::IncrementCountOpen(EWwiseFileStateOperationOrigin InOperationOrigin, + FIncrementCountCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_F_3(TEXT("FWwiseFileState::IncrementCountOpen %s"), GetManagingTypeName()); + if (State == EState::Closing) + { + // We are currently closing asynchronously. Meaning this is a lengthy operation. Wait until the next End global callback. + auto* GlobalCallbacks = FWwiseGlobalCallbacks::Get(); + if (UNLIKELY(!GlobalCallbacks)) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("IncrementCountOpen %s %" PRIu32 ": GlobalCallbacks unavailable. Giving up."), + GetManagingTypeName(), GetShortId()); + IncrementCountDone(InOperationOrigin, MoveTemp(InCallback)); // Skip all + return; + } + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("IncrementCountOpen %s %" PRIu32 ": Closing -> WillReopen"), + GetManagingTypeName(), GetShortId()); + State = EState::WillReopen; + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("IncrementCountOpen %s %" PRIu32 ": Waiting for deferred Closing file. Wait until End callback."), + GetManagingTypeName(), GetShortId()); + GlobalCallbacks->EndAsync([this, InOperationOrigin, InCallback = MoveTemp(InCallback)]() mutable + { + if (UNLIKELY(!FileStateExecutionQueue)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("%s file state %" PRIu32" already Term in IncrementCountOpen GlobalCallback!"), GetManagingTypeName(), GetShortId()); + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::IncrementCountOpen %s Callback"), GetManagingTypeName()); + InCallback(false); + return EWwiseDeferredAsyncResult::Done; + } + FileStateExecutionQueue->Async([this, InOperationOrigin, InCallback = MoveTemp(InCallback)]() mutable + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("IncrementCountOpen %s %" PRIu32 ": Retrying open"), + GetManagingTypeName(), GetShortId()); + IncrementCountOpen(InOperationOrigin, MoveTemp(InCallback)); // Call ourselves back + }); + return EWwiseDeferredAsyncResult::Done; + }); + return; + } + + if (State == EState::CanReopen) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("IncrementCountOpen %s %" PRIu32 ": CanReopen -> Closed (post-close)"), + GetManagingTypeName(), GetShortId()); + State = EState::Closed; + } + + if (State == EState::Opening) + { + // We are currently opening asynchronously. We must wait for that operation to be initially done, so we can keep on processing this. + auto* GlobalCallbacks = FWwiseGlobalCallbacks::Get(); + if (UNLIKELY(!GlobalCallbacks)) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("IncrementCountOpen %s %" PRIu32 ": GlobalCallbacks unavailable. Giving up."), + GetManagingTypeName(), GetShortId()); + IncrementCountDone(InOperationOrigin, MoveTemp(InCallback)); // Skip all + return; + } + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("IncrementCountOpen %s %" PRIu32 ": Waiting for deferred Opening file. Wait until End callback."), + GetManagingTypeName(), GetShortId()); + GlobalCallbacks->EndAsync([this, InOperationOrigin, InCallback = MoveTemp(InCallback)]() mutable + { + if (UNLIKELY(!FileStateExecutionQueue)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("%s file state %" PRIu32" already Term in IncrementCountOpen GlobalCallback!"), GetManagingTypeName(), GetShortId()); + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::IncrementCountOpen %s Callback"), GetManagingTypeName()); + InCallback(false); + return EWwiseDeferredAsyncResult::Done; + } + FileStateExecutionQueue->Async([this, InOperationOrigin, InCallback = MoveTemp(InCallback)]() mutable + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("IncrementCountOpen %s %" PRIu32 ": Retrying open"), + GetManagingTypeName(), GetShortId()); + IncrementCountOpen(InOperationOrigin, MoveTemp(InCallback)); // Call ourselves back + }); + return EWwiseDeferredAsyncResult::Done; + }); + return; + } + + if (!CanOpenFile()) + { + IncrementCountLoad(InOperationOrigin, MoveTemp(InCallback)); // Continue + return; + } + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("IncrementCountOpen %s %" PRIu32 ": Closed -> Opening"), + GetManagingTypeName(), GetShortId()); + State = EState::Opening; + + OpenFile([this, InOperationOrigin, InCallback = MoveTemp(InCallback)]() mutable + { + IncrementCountLoad(InOperationOrigin, MoveTemp(InCallback)); // Continue + }); +} + +void FWwiseFileState::IncrementCountLoad(EWwiseFileStateOperationOrigin InOperationOrigin, + FIncrementCountCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_F_3(TEXT("FWwiseFileState::IncrementCountLoad %s"), GetManagingTypeName()); + if (State == EState::Unloading) + { + // We are currently unloading asynchronously. Meaning this is a lengthy operation. Wait until the next End global callback. + auto* GlobalCallbacks = FWwiseGlobalCallbacks::Get(); + if (UNLIKELY(!GlobalCallbacks)) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("IncrementCountLoad %s %" PRIu32 ": GlobalCallbacks unavailable. Giving up."), + GetManagingTypeName(), GetShortId()); + IncrementCountDone(InOperationOrigin, MoveTemp(InCallback)); // Skip all + return; + } + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("IncrementCountLoad %s %" PRIu32 ": Unloading -> WillReload"), + GetManagingTypeName(), GetShortId()); + State = EState::WillReload; + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("IncrementCountLoad %s %" PRIu32 ": Waiting for deferred Unloading. Wait until End callback."), + GetManagingTypeName(), GetShortId()); + GlobalCallbacks->EndAsync([this, InOperationOrigin, InCallback = MoveTemp(InCallback)]() mutable + { + if (UNLIKELY(!FileStateExecutionQueue)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("%s file state %" PRIu32" already Term in IncrementCountLoad GlobalCallback!"), GetManagingTypeName(), GetShortId()); + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::IncrementCountLoad %s Callback"), GetManagingTypeName()); + InCallback(false); + return EWwiseDeferredAsyncResult::Done; + } + FileStateExecutionQueue->Async([this, InOperationOrigin, InCallback = MoveTemp(InCallback)]() mutable + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("IncrementCountLoad %s %" PRIu32 ": Retrying open"), + GetManagingTypeName(), GetShortId()); + IncrementCountOpen(InOperationOrigin, MoveTemp(InCallback)); // Restart the op from start + }); + return EWwiseDeferredAsyncResult::Done; + }); + return; + } + + if (State == EState::CanReload) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("IncrementCountLoad %s %" PRIu32 ": CanReload -> Opened (post-unload)"), + GetManagingTypeName(), GetShortId()); + State = EState::Opened; + } + + if (State == EState::Loading) + { + // We are currently loading asynchronously. We must wait for that operation to be initially done, so we can keep on processing this. + auto* GlobalCallbacks = FWwiseGlobalCallbacks::Get(); + if (UNLIKELY(!GlobalCallbacks)) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("IncrementCountLoad %s %" PRIu32 ": GlobalCallbacks unavailable. Giving up."), + GetManagingTypeName(), GetShortId()); + IncrementCountDone(InOperationOrigin, MoveTemp(InCallback)); // Skip all + return; + } + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("IncrementCountLoad %s %" PRIu32 ": Waiting for deferred Loading file. Wait until End callback."), + GetManagingTypeName(), GetShortId()); + GlobalCallbacks->EndAsync([this, InOperationOrigin, InCallback = MoveTemp(InCallback)]() mutable + { + if (UNLIKELY(!FileStateExecutionQueue)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("%s file state %" PRIu32" already Term in IncrementCountLoad GlobalCallback!"), GetManagingTypeName(), GetShortId()); + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::IncrementCountLoad %s Callback"), GetManagingTypeName()); + InCallback(false); + return EWwiseDeferredAsyncResult::Done; + } + FileStateExecutionQueue->Async([this, InOperationOrigin, InCallback = MoveTemp(InCallback)]() mutable + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("IncrementCountLoad %s %" PRIu32 ": Retrying load"), + GetManagingTypeName(), GetShortId()); + IncrementCountLoad(InOperationOrigin, MoveTemp(InCallback)); // Call ourselves back + }); + return EWwiseDeferredAsyncResult::Done; + }); + return; + } + + if (!CanLoadInSoundEngine()) + { + IncrementCountDone(InOperationOrigin, MoveTemp(InCallback)); // Continue + return; + } + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("IncrementCountOpen %s %" PRIu32 ": Opened -> Loading"), + GetManagingTypeName(), GetShortId()); + State = EState::Loading; + + LoadInSoundEngine([this, InOperationOrigin, InCallback = MoveTemp(InCallback)]() mutable + { + IncrementCountDone(InOperationOrigin, MoveTemp(InCallback)); // Continue + }); +} + +void FWwiseFileState::IncrementCountDone(EWwiseFileStateOperationOrigin InOperationOrigin, + FIncrementCountCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_F_3(TEXT("FWwiseFileState::IncrementCountDone %s"), GetManagingTypeName()); + bool bResult; + if (InOperationOrigin == EWwiseFileStateOperationOrigin::Streaming) + { + bResult = (State == EState::Loaded); + if (!bResult) + { + UE_LOG(LogWwiseFileHandler, Warning, TEXT("IncrementCountDone %s %" PRIu32 ": Could not load file for IO Hook streaming."), + GetManagingTypeName(), GetShortId()); + } + } + else + { + bResult = (State == EState::Loaded) + || (State == EState::Opened && !CanLoadInSoundEngine()) + || (State == EState::Closed && !CanOpenFile()); + UE_CLOG(UNLIKELY(!bResult), LogWwiseFileHandler, Warning, TEXT("IncrementCountDone %s %" PRIu32 ": Could not open file for asset loading."), + GetManagingTypeName(), GetShortId()); + } + + UE_CLOG(LIKELY(bResult), LogWwiseFileHandler, VeryVerbose, TEXT("IncrementCountDone %s %" PRIu32 ": Done incrementing."), + GetManagingTypeName(), GetShortId()); + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::IncrementCountDone %s Callback"), GetManagingTypeName()); + InCallback(bResult); +} + +void FWwiseFileState::DecrementCount(EWwiseFileStateOperationOrigin InOperationOrigin, + FDeleteFileStateFunction&& InDeleteState, FDecrementCountCallback&& InCallback) +{ + if (UNLIKELY(LoadCount == 0)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("DecrementCount %s %" PRIu32 ": File State is already closed."), GetManagingTypeName(), GetShortId()); + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::DecrementCount %s Callback"), GetManagingTypeName()); + InCallback(); + return; + } + + DecrementLoadCount(InOperationOrigin); + + DecrementCountUnload(InOperationOrigin, MoveTemp(InDeleteState), MoveTemp(InCallback)); +} + +void FWwiseFileState::DecrementCountUnload(EWwiseFileStateOperationOrigin InOperationOrigin, + FDeleteFileStateFunction&& InDeleteState, FDecrementCountCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_F_3(TEXT("FWwiseFileState::DecrementCountUnload %s"), GetManagingTypeName()); + if (State == EState::Unloading || State == EState::WillReload || State == EState::CanReload) + { + // We are currently unloading asynchronously. Meaning this is a lengthy operation. Wait until the next End global callback. + auto* GlobalCallbacks = FWwiseGlobalCallbacks::Get(); + if (UNLIKELY(!GlobalCallbacks)) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("DecrementCountUnload %s %" PRIu32 ": GlobalCallbacks unavailable. Giving up."), + GetManagingTypeName(), GetShortId()); + DecrementCountDone(InOperationOrigin, MoveTemp(InDeleteState), MoveTemp(InCallback)); // Skip all + return; + } + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("DecrementCountUnload %s %" PRIu32 ": UnloadFromSoundEngine deferred by another user. Wait until End callback."), + GetManagingTypeName(), GetShortId()); + GlobalCallbacks->EndAsync([this, InOperationOrigin, InDeleteState = MoveTemp(InDeleteState), InCallback = MoveTemp(InCallback)]() mutable + { + if (UNLIKELY(!FileStateExecutionQueue)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("%s file state %" PRIu32" already Term in DecrementCountUnload GlobalCallback!"), GetManagingTypeName(), GetShortId()); + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::DecrementCountUnload %s Callback"), GetManagingTypeName()); + InCallback(); + return EWwiseDeferredAsyncResult::Done; + } + FileStateExecutionQueue->Async([this, InOperationOrigin, InDeleteState = MoveTemp(InDeleteState), InCallback = MoveTemp(InCallback)]() mutable + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("DecrementCountUnload %s %" PRIu32 ": Retrying unload"), + GetManagingTypeName(), GetShortId()); + DecrementCountUnload(InOperationOrigin, MoveTemp(InDeleteState), MoveTemp(InCallback)); // Call ourselves back + }); + return EWwiseDeferredAsyncResult::Done; + }); + return; + } + + if (!CanUnloadFromSoundEngine()) + { + DecrementCountClose(InOperationOrigin, MoveTemp(InDeleteState), MoveTemp(InCallback)); // Continue + return; + } + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("DecrementCountUnload %s %" PRIu32 ": -> Unloading"), + GetManagingTypeName(), GetShortId()); + State = EState::Unloading; + + UnloadFromSoundEngine([this, InOperationOrigin, InDeleteState = MoveTemp(InDeleteState), InCallback = MoveTemp(InCallback)](EResult InDefer) mutable + { + if (LIKELY(InDefer == EResult::Done)) + { + DecrementCountClose(InOperationOrigin, MoveTemp(InDeleteState), MoveTemp(InCallback)); // Continue + return; + } + + auto* GlobalCallbacks = FWwiseGlobalCallbacks::Get(); + if (UNLIKELY(!GlobalCallbacks)) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("DecrementCountUnload %s %" PRIu32 ": GlobalCallbacks unavailable. Giving up."), + GetManagingTypeName(), GetShortId()); + DecrementCountDone(InOperationOrigin, MoveTemp(InDeleteState), MoveTemp(InCallback)); // Skip all + return; + } + + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("DecrementCountUnload %s %" PRIu32 ": UnloadFromSoundEngine deferred. Wait until End callback."), + GetManagingTypeName(), GetShortId()); + GlobalCallbacks->EndAsync([this, InOperationOrigin, InDeleteState = MoveTemp(InDeleteState), InCallback = MoveTemp(InCallback)]() mutable + { + if (UNLIKELY(!FileStateExecutionQueue)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("%s file state %" PRIu32" already Term in DecrementCountUnload UnloadFromSoundEngine GlobalCallback!"), GetManagingTypeName(), GetShortId()); + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::DecrementCountUnload %s Callback"), GetManagingTypeName()); + InCallback(); + return EWwiseDeferredAsyncResult::Done; + } + FileStateExecutionQueue->Async([this, InOperationOrigin, InDeleteState = MoveTemp(InDeleteState), InCallback = MoveTemp(InCallback)]() mutable + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("DecrementCountUnload %s %" PRIu32 ": Processing deferred Unload."), + GetManagingTypeName(), GetShortId()); + + if (UNLIKELY(State == EState::WillReload)) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("DecrementCountUnload %s %" PRIu32 ": Another user needs this to be kept loaded."), + GetManagingTypeName(), GetShortId()); + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("DecrementCountUnload %s %" PRIu32 ": WillReload -> Loaded"), + GetManagingTypeName(), GetShortId()); + State = EState::Loaded; + DecrementCountDone(InOperationOrigin, MoveTemp(InDeleteState), MoveTemp(InCallback)); // Skip all + } + else if (UNLIKELY(State != EState::Unloading)) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("DecrementCountUnload %s %" PRIu32 ": State got changed. Not unloading anymore."), + GetManagingTypeName(), GetShortId()); + DecrementCountClose(InOperationOrigin, MoveTemp(InDeleteState), MoveTemp(InCallback)); // Continue + } + else + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("DecrementCountUnload %s %" PRIu32 ": Unloading -> Loaded (retry)"), + GetManagingTypeName(), GetShortId()); + State = EState::Loaded; + DecrementCountUnload(InOperationOrigin, MoveTemp(InDeleteState), MoveTemp(InCallback)); // Call ourselves back + } + }); + return EWwiseDeferredAsyncResult::Done; + }); + }); +} + +void FWwiseFileState::DecrementCountClose(EWwiseFileStateOperationOrigin InOperationOrigin, + FDeleteFileStateFunction&& InDeleteState, FDecrementCountCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_F_3(TEXT("FWwiseFileState::DecrementCountClose %s"), GetManagingTypeName()); + if (State == EState::Closing || State == EState::WillReopen || State == EState::CanReopen) + { + // We are currently closing asynchronously. Meaning this is a lengthy operation. Wait until the next End global callback. + auto* GlobalCallbacks = FWwiseGlobalCallbacks::Get(); + if (UNLIKELY(!GlobalCallbacks)) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("DecrementCountClose %s %" PRIu32 ": GlobalCallbacks unavailable. Giving up."), + GetManagingTypeName(), GetShortId()); + DecrementCountDone(InOperationOrigin, MoveTemp(InDeleteState), MoveTemp(InCallback)); // Skip all + return; + } + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("DecrementCountClose %s %" PRIu32 ": CloseFile deferred by another user. Wait until End callback."), + GetManagingTypeName(), GetShortId()); + GlobalCallbacks->EndAsync([this, InOperationOrigin, InDeleteState = MoveTemp(InDeleteState), InCallback = MoveTemp(InCallback)]() mutable + { + if (UNLIKELY(!FileStateExecutionQueue)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("%s file state %" PRIu32" already Term in DecrementCountClose GlobalCallback!"), GetManagingTypeName(), GetShortId()); + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::DecrementCountUnload %s Callback"), GetManagingTypeName()); + InCallback(); + return EWwiseDeferredAsyncResult::Done; + } + FileStateExecutionQueue->Async([this, InOperationOrigin, InDeleteState = MoveTemp(InDeleteState), InCallback = MoveTemp(InCallback)]() mutable + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("DecrementCountClose %s %" PRIu32 ": Retrying close"), + GetManagingTypeName(), GetShortId()); + DecrementCountClose(InOperationOrigin, MoveTemp(InDeleteState), MoveTemp(InCallback)); // Call ourselves back + }); + return EWwiseDeferredAsyncResult::Done; + }); + return; + } + + if (!CanCloseFile()) + { + DecrementCountDone(InOperationOrigin, MoveTemp(InDeleteState), MoveTemp(InCallback)); // Continue + return; + } + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("DecrementCountClose %s %" PRIu32 ": -> Closing"), + GetManagingTypeName(), GetShortId()); + State = EState::Closing; + + CloseFile([this, InOperationOrigin, InDeleteState = MoveTemp(InDeleteState), InCallback = MoveTemp(InCallback)](EResult InDefer) mutable + { + if (LIKELY(InDefer == EResult::Done)) + { + DecrementCountDone(InOperationOrigin, MoveTemp(InDeleteState), MoveTemp(InCallback)); // Continue + return; + } + + auto* GlobalCallbacks = FWwiseGlobalCallbacks::Get(); + if (UNLIKELY(!GlobalCallbacks)) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("DecrementCountClose %s %" PRIu32 ": GlobalCallbacks unavailable. Giving up."), + GetManagingTypeName(), GetShortId()); + DecrementCountDone(InOperationOrigin, MoveTemp(InDeleteState), MoveTemp(InCallback)); // Skip all + return; + } + + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("DecrementCountClose %s %" PRIu32 ": CloseFile deferred. Wait until End callback."), + GetManagingTypeName(), GetShortId()); + GlobalCallbacks->EndAsync([this, InOperationOrigin, InDeleteState = MoveTemp(InDeleteState), InCallback = MoveTemp(InCallback)]() mutable + { + if (UNLIKELY(!FileStateExecutionQueue)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("%s file state %" PRIu32" already Term in DecrementCountClose CloseFile GlobalCallback!"), GetManagingTypeName(), GetShortId()); + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::DecrementCountUnload %s Callback"), GetManagingTypeName()); + InCallback(); + return EWwiseDeferredAsyncResult::Done; + } + FileStateExecutionQueue->Async([this, InOperationOrigin, InDeleteState = MoveTemp(InDeleteState), InCallback = MoveTemp(InCallback)]() mutable + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("DecrementCountClose %s %" PRIu32 ": Processing deferred Close."), + GetManagingTypeName(), GetShortId()); + + if (UNLIKELY(State == EState::WillReopen)) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("DecrementCountClose %s %" PRIu32 ": Another user needs this to be kept open."), + GetManagingTypeName(), GetShortId()); + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("DecrementCountClose %s %" PRIu32 ": WillReopen -> Opened"), + GetManagingTypeName(), GetShortId()); + State = EState::Opened; + DecrementCountDone(InOperationOrigin, MoveTemp(InDeleteState), MoveTemp(InCallback)); // Skip all + } + else if (UNLIKELY(State != EState::Closing)) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("DecrementCountClose %s %" PRIu32 ": State got changed. Not closing anymore."), + GetManagingTypeName(), GetShortId()); + DecrementCountClose(InOperationOrigin, MoveTemp(InDeleteState), MoveTemp(InCallback)); // Continue + } + else + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("DecrementCountClose %s %" PRIu32 ": Closing -> Opened (retry)"), + GetManagingTypeName(), GetShortId()); + State = EState::Opened; + DecrementCountClose(InOperationOrigin, MoveTemp(InDeleteState), MoveTemp(InCallback)); // Call ourselves back + } + }); + return EWwiseDeferredAsyncResult::Done; + }); + }); +} + +void FWwiseFileState::DecrementCountDone(EWwiseFileStateOperationOrigin InOperationOrigin, + FDeleteFileStateFunction&& InDeleteState, FDecrementCountCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_F_3(TEXT("FWwiseFileState::DecrementCountDone %s"), GetManagingTypeName()); + if (CanDelete()) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("DecrementCountDone %s %" PRIu32 ": Done decrementing. Deleting state."), + GetManagingTypeName(), GetShortId()); + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::DecrementCountDone %s Delete"), GetManagingTypeName()); + InDeleteState(MoveTemp(InCallback)); + } + else + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("DecrementCountDone %s %" PRIu32 ": Done decrementing."), + GetManagingTypeName(), GetShortId()); + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::DecrementCountDone %s Callback"), GetManagingTypeName()); + InCallback(); + } +} + +void FWwiseFileState::IncrementLoadCount(EWwiseFileStateOperationOrigin InOperationOrigin) +{ + const bool bIncrementStreamingCount = (InOperationOrigin == EWwiseFileStateOperationOrigin::Streaming); + + if (bIncrementStreamingCount) ++StreamingCount; + ++LoadCount; + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("State %s %" PRIu32 " LoadCount %d %sStreamingCount %d"), + GetManagingTypeName(), GetShortId(), LoadCount, bIncrementStreamingCount ? TEXT("++") : TEXT(""), StreamingCount); +} + +bool FWwiseFileState::CanOpenFile() const +{ + return State == EState::Closed && LoadCount > 0; +} + +void FWwiseFileState::OpenFileSucceeded(FOpenFileCallback&& InCallback) +{ + if (UNLIKELY(State != EState::Opening)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("Done opening %s %" PRIu32 " while not in Opening state"), GetManagingTypeName(), GetShortId()); + } + else + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("State %s %" PRIu32 " Opening -> Opened"), GetManagingTypeName(), GetShortId()); + State = EState::Opened; + } + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::OpenFileSucceeded %s Callback"), GetManagingTypeName()); + InCallback(); +} + +void FWwiseFileState::OpenFileFailed(FOpenFileCallback&& InCallback) +{ + INC_DWORD_STAT(STAT_WwiseFileHandlerTotalErrorCount); + if (UNLIKELY(State != EState::Opening)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("Failed opening %s %" PRIu32 " while not in Opening state"), GetManagingTypeName(), GetShortId()); + } + else + { + UE_LOG(LogWwiseFileHandler, Warning, TEXT("State %s %" PRIu32 " Opening Failed -> Closed"), GetManagingTypeName(), GetShortId()); + State = EState::Closed; + } + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::OpenFileFailed %s Callback"), GetManagingTypeName()); + InCallback(); +} + +bool FWwiseFileState::CanLoadInSoundEngine() const +{ + return State == EState::Opened && (!IsStreamedState() || StreamingCount > 0); +} + +void FWwiseFileState::LoadInSoundEngineSucceeded(FLoadInSoundEngineCallback&& InCallback) +{ + if (UNLIKELY(State != EState::Loading)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("Done loading %s %" PRIu32 " while not in Loading state"), GetManagingTypeName(), GetShortId()); + } + else + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("State %s %" PRIu32 " Loading -> Loaded"), GetManagingTypeName(), GetShortId()); + State = EState::Loaded; + } + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::LoadInSoundEngineSucceeded %s Callback"), GetManagingTypeName()); + InCallback(); +} + +void FWwiseFileState::LoadInSoundEngineFailed(FLoadInSoundEngineCallback&& InCallback) +{ + INC_DWORD_STAT(STAT_WwiseFileHandlerTotalErrorCount); + if (UNLIKELY(State != EState::Loading)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("Failed loading %s %" PRIu32 " while not in Loading state"), GetManagingTypeName(), GetShortId()); + } + else + { + UE_LOG(LogWwiseFileHandler, Warning, TEXT("State %s %" PRIu32 " Loading Failed -> Opened"), GetManagingTypeName(), GetShortId()); + State = EState::Opened; + } + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::LoadInSoundEngineFailed %s Callback"), GetManagingTypeName()); + InCallback(); +} + +void FWwiseFileState::DecrementLoadCount(EWwiseFileStateOperationOrigin InOperationOrigin) +{ + const bool bDecrementStreamingCount = (InOperationOrigin == EWwiseFileStateOperationOrigin::Streaming); + + if (bDecrementStreamingCount) --StreamingCount; + --LoadCount; + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("State %s %" PRIu32 " --LoadCount %d %sStreamingCount %d"), + GetManagingTypeName(), GetShortId(), LoadCount, bDecrementStreamingCount ? TEXT("--") : TEXT(""), StreamingCount); +} + +bool FWwiseFileState::CanUnloadFromSoundEngine() const +{ + return State == EState::Loaded && ((IsStreamedState() && StreamingCount == 0) || (!IsStreamedState() && LoadCount == 0)); +} + +void FWwiseFileState::UnloadFromSoundEngineDone(FUnloadFromSoundEngineCallback&& InCallback) +{ + if (UNLIKELY(State != EState::Unloading && State != EState::WillReload)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("Done unloading %s %" PRIu32 " while not in Unloading state"), GetManagingTypeName(), GetShortId()); + } + else if (LIKELY(State == EState::Unloading)) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("State %s %" PRIu32 " Unloading -> Opened"), GetManagingTypeName(), GetShortId()); + State = EState::Opened; + } + else + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("State %s %" PRIu32 " WillReload -> CanReload"), GetManagingTypeName(), GetShortId()); + State = EState::CanReload; + } + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::UnloadFromSoundEngineDone %s Callback"), GetManagingTypeName()); + InCallback(EResult::Done); +} + +void FWwiseFileState::UnloadFromSoundEngineToClosedFile(FUnloadFromSoundEngineCallback&& InCallback) +{ + if (UNLIKELY(State != EState::Unloading && State != EState::WillReload)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("Done unloading %s %" PRIu32 " while not in Unloading state"), GetManagingTypeName(), GetShortId()); + } + else if (LIKELY(State == EState::Unloading)) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("State %s %" PRIu32 " Unloading -...-> Closed"), GetManagingTypeName(), GetShortId()); + State = EState::Closed; + } + else + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("State %s %" PRIu32 " WillReload -> CanReload -> CanReopen"), GetManagingTypeName(), GetShortId()); + State = EState::CanReopen; + } + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::UnloadFromSoundEngineToClosedFile %s Callback"), GetManagingTypeName()); + InCallback(EResult::Done); +} + +void FWwiseFileState::UnloadFromSoundEngineDefer(FUnloadFromSoundEngineCallback&& InCallback) +{ + if (UNLIKELY(State != EState::Unloading && State != EState::WillReload)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("Deferring unloading %s %" PRIu32 " while not in Unloading state"), GetManagingTypeName(), GetShortId()); + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::UnloadFromSoundEngineDefer %s Callback"), GetManagingTypeName()); + InCallback(EResult::Done); + return; + } + if (UNLIKELY(State == EState::WillReload)) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("State %s %" PRIu32 " WillReload -> Loaded"), GetManagingTypeName(), GetShortId()); + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::UnloadFromSoundEngineDefer %s Callback"), GetManagingTypeName()); + State = EState::Loaded; + InCallback(EResult::Done); + return; + } + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("State %s %" PRIu32 " Deferring Unload"), GetManagingTypeName(), GetShortId()); + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::UnloadFromSoundEngineDefer %s Callback"), GetManagingTypeName()); + InCallback(EResult::Deferred); +} + +bool FWwiseFileState::CanCloseFile() const +{ + return State == EState::Opened && LoadCount == 0; +} + +void FWwiseFileState::CloseFileDone(FCloseFileCallback&& InCallback) +{ + if (UNLIKELY(State != EState::Closing && State != EState::WillReopen)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("Done closing %s %" PRIu32 " while not in Closing state"), GetManagingTypeName(), GetShortId()); + } + else if (LIKELY(State == EState::Closing)) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("State %s %" PRIu32 " Closing -> Closed"), GetManagingTypeName(), GetShortId()); + State = EState::Closed; + } + else + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("State %s %" PRIu32 " WillReopen -> CanReopen"), GetManagingTypeName(), GetShortId()); + State = EState::CanReopen; + } + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::CloseFileDone %s Callback"), GetManagingTypeName()); + InCallback(EResult::Done); +} + +void FWwiseFileState::CloseFileDefer(FCloseFileCallback&& InCallback) +{ + if (UNLIKELY(State != EState::Closing && State != EState::WillReopen)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("Deferring closing %s %" PRIu32 " while not in Closing state"), GetManagingTypeName(), GetShortId()); + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::CloseFileDefer %s Callback"), GetManagingTypeName()); + InCallback(EResult::Done); + return; + } + if (UNLIKELY(State == EState::WillReopen)) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("State %s %" PRIu32 " WillReopen -> Opened"), GetManagingTypeName(), GetShortId()); + State = EState::Opened; + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::CloseFileDefer %s Callback"), GetManagingTypeName()); + InCallback(EResult::Done); + return; + } + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("State %s %" PRIu32 " Deferring Close"), GetManagingTypeName(), GetShortId()); + SCOPED_WWISEFILEHANDLER_EVENT_F_4(TEXT("FWwiseFileState::CloseFileDefer %s Callback"), GetManagingTypeName()); + InCallback(EResult::Deferred); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseFileStateTools.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseFileStateTools.cpp new file mode 100644 index 0000000..d09d00f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseFileStateTools.cpp @@ -0,0 +1,217 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseFileStateTools.h" +#include "Wwise/Stats/AsyncStats.h" +#include "Wwise/Stats/FileHandler.h" + +#include "WwiseDefines.h" + +#include "Misc/Paths.h" +#include "Async/MappedFileHandle.h" +#include "Async/AsyncFileHandle.h" +#include "HAL/FileManager.h" +#if UE_5_0_OR_LATER +#include "HAL/PlatformFileManager.h" +#else +#include "HAL/PlatformFilemanager.h" +#endif + +#include + +uint8* FWwiseFileStateTools::AllocateMemory(int64 InMemorySize, bool bInDeviceMemory, int32 InMemoryAlignment, + bool bInEnforceMemoryRequirements) +{ + uint8* Result = nullptr; + + if (bInDeviceMemory && bInEnforceMemoryRequirements) + { +#if AK_SUPPORT_DEVICE_MEMORY + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("Allocating %" PRIi64 " (%" PRIi32 ") bytes in Device Memory"), InMemorySize, InMemoryAlignment); + Result = static_cast(AKPLATFORM::AllocDevice((size_t)InMemorySize, nullptr)); + if (Result) + { + INC_MEMORY_STAT_BY(STAT_WwiseFileHandlerDeviceMemoryAllocated, InMemorySize); + } + else + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("Could not allocate %" PRIi64 " (%" PRIi32 ") bytes in Device Memory"), InMemorySize, InMemoryAlignment); + } +#else + UE_LOG(LogWwiseFileHandler, Error, TEXT("No Device Memory, but trying to allocate %" PRIi64 " (%" PRIi32 ") bytes"), InMemorySize, InMemoryAlignment); + return AllocateMemory(InMemorySize, false, InMemoryAlignment, bInEnforceMemoryRequirements); +#endif + } + else + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("Allocating %" PRIi64 " (%" PRIi32 ") bytes in Unreal memory"), InMemorySize, InMemoryAlignment); + Result = static_cast(FMemory::Malloc(InMemorySize, bInEnforceMemoryRequirements ? InMemoryAlignment : 0)); + if (Result) + { + INC_MEMORY_STAT_BY(STAT_WwiseFileHandlerMemoryAllocated, InMemorySize); + } + else + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("Could not allocate %" PRIi64 " (%" PRIi32 ") bytes in Unreal memory"), InMemorySize, InMemoryAlignment); + } + } + return Result; +} + +void FWwiseFileStateTools::DeallocateMemory(const uint8* InMemoryPtr, int64 InMemorySize, bool bInDeviceMemory, + int32 InMemoryAlignment, bool bInEnforceMemoryRequirements) +{ + if (!InMemoryPtr) + { + return; + } + + if (bInDeviceMemory && bInEnforceMemoryRequirements) + { +#if AK_SUPPORT_DEVICE_MEMORY + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("Deallocating %" PRIi64 " (%" PRIi32 ") bytes in Device Memory"), InMemorySize, InMemoryAlignment); + DEC_MEMORY_STAT_BY(STAT_WwiseFileHandlerDeviceMemoryAllocated, InMemorySize); + AKPLATFORM::FreeDevice((void*)InMemoryPtr, InMemorySize, 0, true); +#else + UE_LOG(LogWwiseFileHandler, Error, TEXT("No Device Memory, but trying to deallocate %" PRIi64 " (%" PRIi32 ") bytes"), InMemorySize, InMemoryAlignment); + return DeallocateMemory(InMemoryPtr, InMemorySize, false, InMemoryAlignment, bInEnforceMemoryRequirements); + +#endif + } + else + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("Deallocating %" PRIi64 " (%" PRIi32 ") bytes in Unreal memory"), InMemorySize, InMemoryAlignment); + DEC_MEMORY_STAT_BY(STAT_WwiseFileHandlerMemoryAllocated, InMemorySize); + FMemory::Free(const_cast(InMemoryPtr)); + } +} + +bool FWwiseFileStateTools::GetMemoryMapped(IMappedFileHandle*& OutMappedHandle, IMappedFileRegion*& OutMappedRegion, + int64& OutSize, const FString& InFilePathname, int32 InMemoryAlignment) +{ + if (!GetMemoryMapped(OutMappedHandle, OutSize, InFilePathname, InMemoryAlignment)) + { + return false; + } + if (UNLIKELY(!GetMemoryMappedRegion(OutMappedRegion, *OutMappedHandle))) + { + UnmapHandle(*OutMappedHandle); + OutMappedHandle = nullptr; + return false; + } + return true; +} + +bool FWwiseFileStateTools::GetMemoryMapped(IMappedFileHandle*& OutMappedHandle, int64& OutSize, + const FString& InFilePathname, int32 InMemoryAlignment) +{ + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("Memory mapping %s"), *InFilePathname); + auto& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); + auto* Handle = PlatformFile.OpenMapped(*InFilePathname); + if (UNLIKELY(!Handle)) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("OpenMapped %s failed"), *InFilePathname); + return false; + } + + OutMappedHandle = Handle; + OutSize = Handle->GetFileSize(); + return true; +} + +bool FWwiseFileStateTools::GetMemoryMappedRegion(IMappedFileRegion*& OutMappedRegion, + IMappedFileHandle& InMappedHandle) +{ + auto* Region = InMappedHandle.MapRegion(0, MAX_int64, true); + if (UNLIKELY(!Region)) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("MapRegion failed")); + return false; + } + + INC_MEMORY_STAT_BY(STAT_WwiseFileHandlerMemoryMapped, Region->GetMappedSize()); + OutMappedRegion = Region; + return true; +} + +void FWwiseFileStateTools::UnmapRegion(IMappedFileRegion& InMappedRegion) +{ + DEC_MEMORY_STAT_BY(STAT_WwiseFileHandlerMemoryMapped, InMappedRegion.GetMappedSize()); + delete &InMappedRegion; +} + +void FWwiseFileStateTools::UnmapHandle(IMappedFileHandle& InMappedHandle) +{ + delete &InMappedHandle; +} + +bool FWwiseFileStateTools::GetFileToPtr(const uint8*& OutPtr, int64& OutSize, const FString& InFilePathname, + bool bInDeviceMemory, int32 InMemoryAlignment, bool bInEnforceMemoryRequirements, + int64 ReadFirstBytes) +{ + FScopedLoadingState ScopedLoadingState(*InFilePathname); + + FArchive* Reader = IFileManager::Get().CreateFileReader(*InFilePathname, 0); + if (!Reader) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("Could not get File Archive for %s"), *InFilePathname); + return false; + } + + int64 Size = Reader->TotalSize(); + if (UNLIKELY(!Size)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("Empty file %s"), *InFilePathname); + delete Reader; + return false; + } + if (ReadFirstBytes >= 0 && Size > ReadFirstBytes) + { + Size = ReadFirstBytes; + } + + if (UNLIKELY((InMemoryAlignment & (InMemoryAlignment - 1)) != 0)) + { + UE_LOG(LogWwiseFileHandler, Warning, TEXT("Invalid non-2^n Memory Alignment (%" PRIi32 ") while getting file %s. Resetting to 0."), InMemoryAlignment, *InFilePathname); + InMemoryAlignment = 0; + } + + uint8* Ptr = AllocateMemory(Size, bInDeviceMemory, InMemoryAlignment, bInEnforceMemoryRequirements); + if (UNLIKELY(!Ptr)) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("Could not Allocate memory for %s"), *InFilePathname); + delete Reader; + return false; + } + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("Getting a copy of full file %s (%" PRIi64 " bytes)"), *InFilePathname, Size); + + Reader->Serialize(Ptr, Size); + const bool Result = Reader->Close(); + + delete Reader; + if (!Result) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("Deserialization failed for file %s"), *InFilePathname); + DeallocateMemory(Ptr, Size, bInDeviceMemory, InMemoryAlignment, bInEnforceMemoryRequirements); + return false; + } + + OutPtr = Ptr; + OutSize = Size; + return true; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseIOHook.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseIOHook.cpp new file mode 100644 index 0000000..7a5c1e9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseIOHook.cpp @@ -0,0 +1,80 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseIOHook.h" +#include "Wwise/API/WwiseStreamMgrAPI.h" +#include "Wwise/Stats/FileHandler.h" +#include + +bool FWwiseIOHook::Init(const AkDeviceSettings& InDeviceSettings) +{ + auto* StreamMgr = IWwiseStreamMgrAPI::Get(); + if (UNLIKELY(!StreamMgr)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("IOHook: Could not retrieve StreamMgr while Init")); + return false; + } + + // If the Stream Manager's File Location Resolver was not set yet, set this object as the + // File Location Resolver (this I/O hook is also able to resolve file location). + if (LIKELY(!StreamMgr->GetFileLocationResolver())) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("IOHook: Setting File Location Resolver")); + StreamMgr->SetFileLocationResolver(GetLocationResolver()); + } + else + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("IOHook: Existing File Location Resolver. Not updating.")); + } + + // Create a device in the Stream Manager, specifying this as the hook. + StreamingDevice = StreamMgr->CreateDevice(InDeviceSettings, GetIOHook()); + UE_CLOG(UNLIKELY(StreamingDevice == AK_INVALID_DEVICE_ID), LogWwiseFileHandler, Error, TEXT("IOHook: CreateDevice failed.")); + UE_CLOG(LIKELY(StreamingDevice != AK_INVALID_DEVICE_ID), LogWwiseFileHandler, Verbose, TEXT("IOHook: CreateDevice = %" PRIu32), StreamingDevice); + return StreamingDevice != AK_INVALID_DEVICE_ID; +} + +void FWwiseIOHook::Term() +{ + auto* StreamMgr = IWwiseStreamMgrAPI::Get(); + if (UNLIKELY(!StreamMgr)) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("IOHook::Term Could not term StreamMgr")); + return; + } + + if (LIKELY(StreamMgr->GetFileLocationResolver() == GetLocationResolver())) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("IOHook::Term Resetting File Location Resolver")); + StreamMgr->SetFileLocationResolver(nullptr); + } + else + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("IOHook::Term Different File Location Resolver. Not setting to null.")); + } + + if (LIKELY(StreamingDevice != AK_INVALID_DEVICE_ID)) + { + StreamMgr->DestroyDevice(StreamingDevice); + StreamingDevice = AK_INVALID_DEVICE_ID; + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("IOHook::Term Device Destroyed.")); + } + else + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("IOHook::Term No device to destroy.")) + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseIOHookImpl.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseIOHookImpl.cpp new file mode 100644 index 0000000..677e397 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseIOHookImpl.cpp @@ -0,0 +1,471 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseIOHookImpl.h" + +#include "Wwise/WwiseFileHandlerBase.h" +#include "Wwise/WwiseWriteFileState.h" +#include "Wwise/WwiseSoundBankManager.h" +#include "Wwise/WwiseExternalSourceManager.h" +#include "Wwise/WwiseMediaManager.h" +#include "Wwise/WwiseStreamableFileHandler.h" + +#include "Wwise/Stats/AsyncStats.h" + +#include "WwiseDefines.h" +#include "AkUnrealHelper.h" + +#include "Async/Async.h" +#if UE_5_0_OR_LATER +#include "HAL/PlatformFileManager.h" +#else +#include "HAL/PlatformFilemanager.h" +#endif + +#include + +FWwiseIOHookImpl::FWwiseIOHookImpl() : + BatchExecutionQueue(TEXT("Wwise IO Hook Batch"), TPri_AboveNormal) // AboveNormal is equivalent to GIOThreadPool. +#ifndef AK_OPTIMIZED + , + CurrentDeviceData(0), + MaxDeviceData(0) +#endif +{ +} + +bool FWwiseIOHookImpl::Init(const AkDeviceSettings& InDeviceSettings) +{ + auto* ExternalSourceManager = IWwiseExternalSourceManager::Get(); + if (LIKELY(ExternalSourceManager)) + { + ExternalSourceManager->SetGranularity(InDeviceSettings.uGranularity); + } + auto* MediaManager = IWwiseMediaManager::Get(); + if (LIKELY(MediaManager)) + { + MediaManager->SetGranularity(InDeviceSettings.uGranularity); + } + auto* SoundBankManager = IWwiseSoundBankManager::Get(); + if (LIKELY(SoundBankManager)) + { + SoundBankManager->SetGranularity(InDeviceSettings.uGranularity); + } + return FWwiseDefaultIOHook::Init(InDeviceSettings); +} + +void FWwiseIOHookImpl::BatchOpen( + AkUInt32 in_uNumFiles, + AkAsyncFileOpenData** in_ppItems) +{ + SCOPED_WWISEFILEHANDLER_EVENT_2(TEXT("FWwiseIOHookImpl::BatchOpen")); + for (AkUInt32 i = 0; i < in_uNumFiles; i++) + { + AkAsyncFileOpenData* pOpenData = in_ppItems[i]; + + if (pOpenData) + { + Open(pOpenData); + } + } +} + + +AKRESULT FWwiseIOHookImpl::Open(AkAsyncFileOpenData* io_pOpenData) +{ + SCOPED_WWISEFILEHANDLER_EVENT_2(TEXT("FWwiseIOHookImpl::Open")); + + AKRESULT AkResult; + + const FString Filename(io_pOpenData->pszFileName); + + if (UNLIKELY(IsEngineExitRequested())) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("FWwiseIOHookImpl::Open: Ignoring opening file at Engine exit for File ID %" PRIu32), io_pOpenData->fileID); + + // Notify error to callback + if (io_pOpenData->pCallback) + io_pOpenData->pCallback(io_pOpenData, AK_NotInitialized); + + return AK_Success; + } + + if (io_pOpenData->eOpenMode == AK_OpenModeWrite || io_pOpenData->eOpenMode == AK_OpenModeWriteOvrwr) + { + AkResult = OpenFileForWrite(io_pOpenData); + // Notify result to callback + if (io_pOpenData->pCallback) + io_pOpenData->pCallback(io_pOpenData, AkResult); + + return AK_Success; + } + + if (io_pOpenData->eOpenMode != AK_OpenModeRead || !io_pOpenData->pFlags) + { + ASYNC_INC_DWORD_STAT(STAT_WwiseFileHandlerTotalErrorCount); + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseIOHookImpl::Open: Unsupported Open Mode for File ID %" PRIu32), io_pOpenData->fileID); + // Notify error to callback + if (io_pOpenData->pCallback) + io_pOpenData->pCallback(io_pOpenData, AK_NotImplemented); + + return AK_Success; + } + + IWwiseStreamingManagerHooks* StreamingHooks = GetStreamingHooks(*io_pOpenData->pFlags); + if (UNLIKELY(!StreamingHooks)) + { + ASYNC_INC_DWORD_STAT(STAT_WwiseFileHandlerTotalErrorCount); + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseIOHookImpl::Open: Unsupported Streaming for File ID %" PRIu32), io_pOpenData->fileID); + // Notify error to callback + if (io_pOpenData->pCallback) + io_pOpenData->pCallback(io_pOpenData, AK_NotInitialized); + + return AK_Success; + } + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseIOHookImpl::Open: Opening file for streaming: File ID %" PRIu32), io_pOpenData->fileID); + StreamingHooks->OpenStreaming(io_pOpenData); + + return AK_Success; +} + +AKRESULT FWwiseIOHookImpl::Read( + AkFileDesc& in_fileDesc, + const AkIoHeuristics& in_heuristics, + AkAsyncIOTransferInfo& io_transferInfo +) +{ + SCOPED_WWISEFILEHANDLER_EVENT_2(TEXT("FWwiseIOHookImpl::Read")); + FWwiseAsyncCycleCounter OpCycleCounter(GET_STATID(STAT_WwiseFileHandlerIORequestLatency)); + ASYNC_INC_DWORD_STAT(STAT_WwiseFileHandlerTotalRequests); + ASYNC_INC_DWORD_STAT(STAT_WwiseFileHandlerPendingRequests); + +#ifndef AK_OPTIMIZED + ++CurrentDeviceData; + if (CurrentDeviceData > MaxDeviceData) + { + MaxDeviceData.Store(CurrentDeviceData); + } +#endif + + auto* FileState = FWwiseStreamableFileStateInfo::GetFromFileDesc(in_fileDesc); + if (UNLIKELY(!FileState)) + { + UE_LOG(LogWwiseFileHandler, Warning, TEXT("FWwiseIOHookImpl::Read [%p]: Could not find File Descriptor"), AK_FILEHANDLE_TO_UINTPTR(in_fileDesc.hFile)); + ASYNC_INC_DWORD_STAT(STAT_WwiseFileHandlerTotalErrorCount); + ASYNC_DEC_DWORD_STAT(STAT_WwiseFileHandlerPendingRequests); +#ifndef AK_OPTIMIZED + --CurrentDeviceData; +#endif + return AK_IDNotFound; + } + + if (UNLIKELY(!FileState->CanProcessFileOp())) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseIOHookImpl::Read [%p]: FileState is not properly initialized for reading"), AK_FILEHANDLE_TO_UINTPTR(in_fileDesc.hFile)); + + ASYNC_INC_DWORD_STAT(STAT_WwiseFileHandlerTotalErrorCount); + ASYNC_DEC_DWORD_STAT(STAT_WwiseFileHandlerPendingRequests); +#ifndef AK_OPTIMIZED + --CurrentDeviceData; +#endif + return AK_UnknownFileError; + } + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseIOHookImpl::Read [%p]: Reading %" PRIu32 " bytes @ %" PRIu64 " - Priority %" PRIi8 " Deadline %f"), + AK_FILEHANDLE_TO_UINTPTR(in_fileDesc.hFile), io_transferInfo.uRequestedSize, io_transferInfo.uFilePosition, in_heuristics.priority, (double)in_heuristics.fDeadline); + const auto Result = FileState->ProcessRead(in_fileDesc, in_heuristics, io_transferInfo, + [this, OpCycleCounter = MoveTemp(OpCycleCounter), hFile = in_fileDesc.hFile] + (AkAsyncIOTransferInfo* InTransferInfo, AKRESULT InResult) mutable + { + ASYNC_DEC_DWORD_STAT(STAT_WwiseFileHandlerPendingRequests); + if (UNLIKELY(InResult != AK_Success)) + { + ASYNC_INC_DWORD_STAT(STAT_WwiseFileHandlerTotalErrorCount); + } + +#ifndef AK_OPTIMIZED + --CurrentDeviceData; +#endif + OpCycleCounter.Stop(); + + if (UNLIKELY(!InTransferInfo->pCallback)) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseIOHookImpl::Read [%p]: No callback reading data"), AK_FILEHANDLE_TO_UINTPTR(hFile)); + } + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseIOHookImpl::Read SoundEngine Callback")); + FWwiseAsyncCycleCounter CallbackCycleCounter(GET_STATID(STAT_WwiseFileHandlerSoundEngineCallbackLatency)); + InTransferInfo->pCallback(InTransferInfo, InResult); + }); + return Result; +} + +void FWwiseIOHookImpl::BatchRead( + AkUInt32 in_uNumTransfers, + BatchIoTransferItem* in_pTransferItems +) +{ + SCOPED_WWISEFILEHANDLER_EVENT_2(TEXT("FWwiseIOHookImpl::BatchRead")); + FWwiseAsyncCycleCounter OpCycleCounter(GET_STATID(STAT_WwiseFileHandlerIORequestLatency)); + ASYNC_INC_DWORD_STAT(STAT_WwiseFileHandlerBatchedRequests); + + BatchExecutionQueue.Async([this, TransferItems = TArray(in_pTransferItems, in_uNumTransfers)]() mutable + { + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseIOHookImpl::BatchRead Async")); + for (auto& TransferItem : TransferItems) + { + auto& FileDesc = *TransferItem.pFileDesc; + const auto& Heuristics = TransferItem.ioHeuristics; + auto& TransferInfo = *TransferItem.pTransferInfo; + + AKRESULT Result = Read(FileDesc, Heuristics, TransferInfo); + if (Result != AK_Success) + { + TransferInfo.pCallback(&TransferInfo, Result); + } + } + }); +} + +AKRESULT FWwiseIOHookImpl::Write( + AkFileDesc& in_fileDesc, + const AkIoHeuristics& in_heuristics, + AkAsyncIOTransferInfo& io_transferInfo +) +{ + SCOPED_WWISEFILEHANDLER_EVENT_2(TEXT("FWwiseIOHookImpl::Write")); + FWwiseAsyncCycleCounter OpCycleCounter(GET_STATID(STAT_WwiseFileHandlerIORequestLatency)); + ASYNC_INC_DWORD_STAT(STAT_WwiseFileHandlerTotalRequests); + ASYNC_INC_DWORD_STAT(STAT_WwiseFileHandlerPendingRequests); + +#ifndef AK_OPTIMIZED + ++CurrentDeviceData; + if (CurrentDeviceData > MaxDeviceData) + { + MaxDeviceData.Store(CurrentDeviceData); + } +#endif + + auto* FileState = FWwiseStreamableFileStateInfo::GetFromFileDesc(in_fileDesc); + if (!FileState) + { + UE_LOG(LogWwiseFileHandler, Warning, TEXT("FWwiseIOHookImpl::Write [%p]: Could not find File Descriptor"), AK_FILEHANDLE_TO_UINTPTR(in_fileDesc.hFile)); + ASYNC_INC_DWORD_STAT(STAT_WwiseFileHandlerTotalErrorCount); + ASYNC_DEC_DWORD_STAT(STAT_WwiseFileHandlerPendingRequests); + +#ifndef AK_OPTIMIZED + --CurrentDeviceData; +#endif + return AK_IDNotFound; + } + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseIOHookImpl::Write [%p]: Writing %" PRIu32 " bytes @ %" PRIu64), + AK_FILEHANDLE_TO_UINTPTR(in_fileDesc.hFile), io_transferInfo.uBufferSize, io_transferInfo.uFilePosition); + const auto Result = FileState->ProcessWrite(in_fileDesc, in_heuristics, io_transferInfo, + [this, OpCycleCounter = MoveTemp(OpCycleCounter), hFile = in_fileDesc.hFile] + (AkAsyncIOTransferInfo* InTransferInfo, AKRESULT InResult) mutable + { + ASYNC_DEC_DWORD_STAT(STAT_WwiseFileHandlerPendingRequests); + if (UNLIKELY(InResult != AK_Success)) + { + ASYNC_INC_DWORD_STAT(STAT_WwiseFileHandlerTotalErrorCount); + } + +#ifndef AK_OPTIMIZED + --CurrentDeviceData; +#endif + OpCycleCounter.Stop(); + + if (UNLIKELY(!InTransferInfo->pCallback)) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseIOHookImpl::Write [%p]: No callback reading data"), AK_FILEHANDLE_TO_UINTPTR(hFile)); + } + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseIOHookImpl::Write SoundEngine Callback")); + FWwiseAsyncCycleCounter CallbackCycleCounter(GET_STATID(STAT_WwiseFileHandlerSoundEngineCallbackLatency)); + InTransferInfo->pCallback(InTransferInfo, InResult); + }); + return Result; +} + +void FWwiseIOHookImpl::BatchWrite( + AkUInt32 in_uNumTransfers, + BatchIoTransferItem* in_pTransferItems +) +{ + SCOPED_WWISEFILEHANDLER_EVENT_2(TEXT("FWwiseIOHookImpl::BatchWrite")); + FWwiseAsyncCycleCounter OpCycleCounter(GET_STATID(STAT_WwiseFileHandlerIORequestLatency)); + ASYNC_INC_DWORD_STAT(STAT_WwiseFileHandlerBatchedRequests); + + BatchExecutionQueue.Async([this, TransferItems = TArray(in_pTransferItems, in_uNumTransfers)]() mutable + { + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseIOHookImpl::BatchWrite Async")); + for (auto& TransferItem : TransferItems) + { + auto& FileDesc = *TransferItem.pFileDesc; + const auto& Heuristics = TransferItem.ioHeuristics; + auto& TransferInfo = *TransferItem.pTransferInfo; + + const auto Result = Write(FileDesc, Heuristics, TransferInfo); + if (Result != AK_Success) + { + TransferInfo.pCallback(&TransferInfo, Result); + } + } + }); +} + +void FWwiseIOHookImpl::BatchCancel( + AkUInt32 in_uNumTransfers, + BatchIoTransferItem* in_pTransferItems, + bool** io_ppbCancelAllTransfersForThisFile +) +{ + SCOPED_WWISEFILEHANDLER_EVENT_2(TEXT("FWwiseIOHookImpl::BatchCancel")); + if (UNLIKELY(in_uNumTransfers == 0 || !in_pTransferItems)) + { + return; + } + for (AkUInt32 i = 0; i < in_uNumTransfers; ++i) + { + const auto& TransferItem = in_pTransferItems[i]; + UE_CLOG(TransferItem.pFileDesc != nullptr, + LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseIOHookImpl::BatchCancel [%p]: Cancelling transfer unsupported"), AK_FILEHANDLE_TO_UINTPTR(TransferItem.pFileDesc->hFile)); + } +} + +AKRESULT FWwiseIOHookImpl::Close(AkFileDesc* in_pFileDesc) +{ + SCOPED_WWISEFILEHANDLER_EVENT_2(TEXT("FWwiseIOHookImpl::Close")); + if (UNLIKELY(!in_pFileDesc)) + { + UE_LOG(LogWwiseFileHandler, Warning, TEXT("FWwiseIOHookImpl::Close: Closing null file.")); + ASYNC_INC_DWORD_STAT(STAT_WwiseFileHandlerTotalErrorCount); + return AK_IDNotFound; + } + + if (UNLIKELY(!IWwiseFileHandlerModule::IsAvailable())) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("FWwiseIOHookImpl::Close [%p]: Not closing file while engine is exiting."), AK_FILEHANDLE_TO_UINTPTR(in_pFileDesc->hFile)); + return AK_Success; + } + + auto* FileState = FWwiseStreamableFileStateInfo::GetFromFileDesc(*in_pFileDesc); + if (!FileState) + { + UE_LOG(LogWwiseFileHandler, Warning, TEXT("FWwiseIOHookImpl::Close [%p]: Could not find File Descriptor"), AK_FILEHANDLE_TO_UINTPTR(in_pFileDesc->hFile)); + ASYNC_INC_DWORD_STAT(STAT_WwiseFileHandlerTotalErrorCount); + return AK_IDNotFound; + } + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseIOHookImpl::Close [%p]: Closing streaming file"), AK_FILEHANDLE_TO_UINTPTR(in_pFileDesc->hFile)); + FileState->CloseStreaming(); + return AK_Success; +} + +// Returns the block size for the file or its storage device. +AkUInt32 FWwiseIOHookImpl::GetBlockSize(AkFileDesc& in_fileDesc) +{ + return 1; +} + +// Returns a description for the streaming device above this low-level hook. +void FWwiseIOHookImpl::GetDeviceDesc(AkDeviceDesc& out_deviceDesc) +{ +#if !defined(AK_OPTIMIZED) + // Deferred scheduler. + out_deviceDesc.deviceID = m_deviceID; + out_deviceDesc.bCanRead = true; + out_deviceDesc.bCanWrite = true; + AK_CHAR_TO_UTF16(out_deviceDesc.szDeviceName, "UnrealIODevice", AK_MONITOR_DEVICENAME_MAXLENGTH); + out_deviceDesc.szDeviceName[AK_MONITOR_DEVICENAME_MAXLENGTH - 1] = '\0'; + out_deviceDesc.uStringSize = (AkUInt32)AKPLATFORM::AkUtf16StrLen(out_deviceDesc.szDeviceName) + 1; +#endif +} + +// Returns custom profiling data: Current number of pending streaming requests +AkUInt32 FWwiseIOHookImpl::GetDeviceData() +{ +#ifndef AK_OPTIMIZED + AkUInt32 Result = MaxDeviceData; + MaxDeviceData.Store(CurrentDeviceData); +#else + AkUInt32 Result = 0; +#endif + return Result; +} + +IWwiseStreamingManagerHooks* FWwiseIOHookImpl::GetStreamingHooks(const AkFileSystemFlags& InFileSystemFlag) +{ + IWwiseStreamableFileHandler* WwiseStreamableFileHandler = nullptr; + if (InFileSystemFlag.uCompanyID == AKCOMPANYID_AUDIOKINETIC_EXTERNAL) + { + WwiseStreamableFileHandler = IWwiseExternalSourceManager::Get(); + } + else if (InFileSystemFlag.uCodecID == AKCODECID_BANK || InFileSystemFlag.uCodecID == AKCODECID_BANK_EVENT || InFileSystemFlag.uCodecID == AKCODECID_BANK_BUS) + { + WwiseStreamableFileHandler = IWwiseSoundBankManager::Get(); + } + else + { + WwiseStreamableFileHandler = IWwiseMediaManager::Get(); + } + + if (UNLIKELY(!WwiseStreamableFileHandler)) + { + return nullptr; + } + return &WwiseStreamableFileHandler->GetStreamingHooks(); +} + +AKRESULT FWwiseIOHookImpl::OpenFileForWrite(AkAsyncFileOpenData* io_pOpenData) +{ + const auto TargetDirectory = FPaths::ProjectSavedDir() / TEXT("Wwise"); + static bool TargetDirectoryExists = false; + if (!TargetDirectoryExists && !FPaths::DirectoryExists(TargetDirectory)) + { + TargetDirectoryExists = true; + if (!FPlatformFileManager::Get().GetPlatformFile().CreateDirectory(*TargetDirectory)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseIOHookImpl::OpenFileForWrite: Cannot create writable directory at %s"), *TargetDirectory); + ASYNC_INC_DWORD_STAT(STAT_WwiseFileHandlerTotalErrorCount); + return AK_NotImplemented; + } + } + const auto FullPath = FPaths::Combine(TargetDirectory, FString(io_pOpenData->pszFileName)); + + UE_LOG(LogWwiseFileHandler, Log, TEXT("FWwiseIOHookImpl::OpenFileForWrite: Opening file for writing: %s"), *FullPath); + const auto FileHandle = FPlatformFileManager::Get().GetPlatformFile().OpenWrite(*FullPath); + if (UNLIKELY(!FileHandle)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseIOHookImpl::OpenFileForWrite: Cannot open file %s for write."), *FullPath); + ASYNC_INC_DWORD_STAT(STAT_WwiseFileHandlerTotalErrorCount); + return AK_UnknownFileError; + } + + auto* WriteState = new FWwiseWriteFileState(FileHandle, FullPath); + if (WriteState) + { + io_pOpenData->pFileDesc = WriteState->GetFileDesc(); + if (io_pOpenData->pCallback) + { + io_pOpenData->pCallback(io_pOpenData, AK_Success); + } + return AK_Success; + } + + return AK_InsufficientMemory; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseMediaFileState.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseMediaFileState.cpp new file mode 100644 index 0000000..b013e3d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseMediaFileState.cpp @@ -0,0 +1,331 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseMediaFileState.h" +#include "Wwise/WwiseMediaManager.h" +#include "Wwise/WwiseStreamingManagerHooks.h" +#include "Wwise/API/WwiseSoundEngineAPI.h" +#include "Wwise/Stats/AsyncStats.h" +#include "AkUnrealHelper.h" +#include "Async/MappedFileHandle.h" + +#include + +FWwiseMediaFileState::FWwiseMediaFileState(const FWwiseMediaCookedData& InCookedData, const FString& InRootPath) : + FWwiseMediaCookedData(InCookedData), + RootPath(InRootPath) +{ + INC_DWORD_STAT(STAT_WwiseFileHandlerKnownMedia); +} + +FWwiseMediaFileState::~FWwiseMediaFileState() +{ + DEC_DWORD_STAT(STAT_WwiseFileHandlerKnownMedia); +} + +FWwiseInMemoryMediaFileState::FWwiseInMemoryMediaFileState(const FWwiseMediaCookedData& InCookedData, const FString& InRootPath) : + FWwiseMediaFileState(InCookedData, InRootPath) +{ + pMediaMemory = nullptr; + sourceID = MediaId; + uMediaSize = 0; +} + +void FWwiseInMemoryMediaFileState::OpenFile(FOpenFileCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseInMemoryMediaFileState::OpenFile")); + if (UNLIKELY(uMediaSize || pMediaMemory)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseInMemoryMediaFileState::OpenFile %" PRIu32 " (%s): Seems to be already opened."), MediaId, *DebugName.ToString()); + return OpenFileFailed(MoveTemp(InCallback)); + } + + const auto FullPathName = RootPath / MediaPathName.ToString(); + + int64 FileSize = 0; + if (LIKELY(GetFileToPtr(const_cast(pMediaMemory), FileSize, FullPathName, bDeviceMemory, MemoryAlignment, true))) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseInMemoryMediaFileState::OpenFile %" PRIu32 " (%s)"), MediaId, *DebugName.ToString()); + uMediaSize = FileSize; + return OpenFileSucceeded(MoveTemp(InCallback)); + } + else + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseInMemoryMediaFileState::OpenFile %" PRIu32 " (%s): Failed to open In-Memory Media (%s)."), MediaId, *DebugName.ToString(), *FullPathName); + pMediaMemory = nullptr; + FileSize = 0; + return OpenFileFailed(MoveTemp(InCallback)); + } +} + +void FWwiseInMemoryMediaFileState::LoadInSoundEngine(FLoadInSoundEngineCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseInMemoryMediaFileState::LoadInSoundEngine")); + if (UNLIKELY(!uMediaSize || !pMediaMemory)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseInMemoryMediaFileState::LoadInSoundEngine %" PRIu32 " (%s): No data, but supposed to be loaded."), MediaId, *DebugName.ToString()); + return LoadInSoundEngineFailed(MoveTemp(InCallback)); + } + + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("FWwiseInMemoryMediaFileState::LoadInSoundEngine %" PRIu32 " (%s): Failed loading media without a SoundEngine."), MediaId, *DebugName.ToString()); + return LoadInSoundEngineFailed(MoveTemp(InCallback)); + } + + const auto SetMediaResult = SoundEngine->SetMedia(this, 1); + if (LIKELY(SetMediaResult == AK_Success)) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseInMemoryMediaFileState::LoadInSoundEngine %" PRIu32 " (%s)"), MediaId, *DebugName.ToString()); + INC_DWORD_STAT(STAT_WwiseFileHandlerLoadedMedia); + return LoadInSoundEngineSucceeded(MoveTemp(InCallback)); + } + else + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseInMemoryMediaFileState::LoadInSoundEngine %" PRIu32 " (%s): Failed to load Media: %d (%s)."), MediaId, *DebugName.ToString(), SetMediaResult, AkUnrealHelper::GetResultString(SetMediaResult)); + return LoadInSoundEngineFailed(MoveTemp(InCallback)); + } +} + +void FWwiseInMemoryMediaFileState::UnloadFromSoundEngine(FUnloadFromSoundEngineCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseInMemoryMediaFileState::UnloadFromSoundEngine")); + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("FWwiseInMemoryMediaFileState::UnloadFromSoundEngine %" PRIu32 " (%s): Failed unloading media without a SoundEngine."), MediaId, *DebugName.ToString()); + return CloseFileDone(MoveTemp(InCallback)); + } + + const auto Result = SoundEngine->TryUnsetMedia(this, 1, nullptr); + if (UNLIKELY(Result == AK_ResourceInUse)) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseInMemoryMediaFileState::UnloadFromSoundEngine %" PRIu32 " (%s): Deferred."), MediaId, *DebugName.ToString()); + return UnloadFromSoundEngineDefer(MoveTemp(InCallback)); + } + else + { + UE_CLOG(UNLIKELY(Result != AK_Success), LogWwiseFileHandler, Error, TEXT("FWwiseInMemoryMediaFileState::UnloadFromSoundEngine %" PRIu32 " (%s): TryUnsetMedia failed: %d (%s)"), MediaId, *DebugName.ToString(), Result, AkUnrealHelper::GetResultString(Result)); + UE_CLOG(LIKELY(Result == AK_Success), LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseInMemoryMediaFileState::UnloadFromSoundEngine %" PRIu32 " (%s)"), MediaId, *DebugName.ToString()); + DEC_DWORD_STAT(STAT_WwiseFileHandlerLoadedMedia); + return UnloadFromSoundEngineDone(MoveTemp(InCallback)); + } +} + +void FWwiseInMemoryMediaFileState::CloseFile(FCloseFileCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseInMemoryMediaFileState::CloseFile")); + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseInMemoryMediaFileState::CloseFile %" PRIu32 " (%s)"), MediaId, *DebugName.ToString()); + DeallocateMemory(pMediaMemory, uMediaSize, bDeviceMemory, MemoryAlignment, true); + pMediaMemory = nullptr; + uMediaSize = 0; + CloseFileDone(MoveTemp(InCallback)); +} + +FWwiseStreamedMediaFileState::FWwiseStreamedMediaFileState(const FWwiseMediaCookedData& InCookedData, + const FString& InRootPath, uint32 InStreamingGranularity) : + FWwiseMediaFileState(InCookedData, InRootPath), + StreamingGranularity(InStreamingGranularity), + StreamedFile(nullptr) +{ + sourceID = InCookedData.MediaId; + pMediaMemory = nullptr; + uMediaSize = 0; +} + +void FWwiseStreamedMediaFileState::CloseStreaming() +{ + auto* MediaManager = IWwiseMediaManager::Get(); + if (UNLIKELY(!MediaManager)) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("FWwiseStreamedMediaFileState::CloseStreaming %" PRIu32 " (%s): Closing without a MediaManager."), MediaId, *DebugName.ToString()); + return; + } + MediaManager->GetStreamingHooks().CloseStreaming(MediaId, *this); +} + +void FWwiseStreamedMediaFileState::OpenFile(FOpenFileCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseStreamedMediaFileState::OpenFile")); + if (UNLIKELY(iFileSize != 0 || StreamedFile)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseStreamedMediaFileState::OpenFile %" PRIu32 " (%s): Stream seems to be already opened."), MediaId, *DebugName.ToString()); + return OpenFileFailed(MoveTemp(InCallback)); + } + + if (PrefetchSize == 0) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseStreamedMediaFileState::OpenFile %" PRIu32 " (%s)"), MediaId, *DebugName.ToString()); + return OpenFileSucceeded(MoveTemp(InCallback)); + } + + // Process PrefetchSize and send as SetMedia + const auto FullPathName = RootPath / MediaPathName.ToString(); + + int64 FileSize = 0; + if (UNLIKELY(!GetFileToPtr(const_cast(pMediaMemory), FileSize, FullPathName, bDeviceMemory, MemoryAlignment, true, PrefetchSize))) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseStreamedMediaFileState::OpenFile %" PRIu32 " (%s): Failed to Read prefetch media (%s)."), MediaId, *DebugName.ToString(), *FullPathName); + pMediaMemory = nullptr; + return OpenFileFailed(MoveTemp(InCallback)); + } + uMediaSize = FileSize; + + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("FWwiseStreamedMediaFileState::OpenFile %" PRIu32 " (%s): Failed prefetch media without a SoundEngine."), MediaId, *DebugName.ToString()); + DeallocateMemory(pMediaMemory, uMediaSize, bDeviceMemory, MemoryAlignment, true); + pMediaMemory = nullptr; + uMediaSize = 0; + return OpenFileFailed(MoveTemp(InCallback)); + } + + const auto SetMediaResult = SoundEngine->SetMedia(this, 1); + if (LIKELY(SetMediaResult == AK_Success)) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseStreamedMediaFileState::OpenFile %" PRIu32 " (%s): Prefetched %" PRIu32 " bytes."), MediaId, *DebugName.ToString(), uMediaSize); + INC_MEMORY_STAT_BY(STAT_WwiseFileHandlerPrefetchMemoryAllocated, uMediaSize); + INC_DWORD_STAT(STAT_WwiseFileHandlerLoadedMedia); + return OpenFileSucceeded(MoveTemp(InCallback)); + } + else + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseStreamedMediaFileState::OpenFile %" PRIu32 " (%s): Failed to prefetch media: %d (%s)."), MediaId, *DebugName.ToString(), SetMediaResult, AkUnrealHelper::GetResultString(SetMediaResult)); + DeallocateMemory(pMediaMemory, uMediaSize, bDeviceMemory, MemoryAlignment, true); + pMediaMemory = nullptr; + uMediaSize = 0; + return OpenFileFailed(MoveTemp(InCallback)); + } +} + +void FWwiseStreamedMediaFileState::LoadInSoundEngine(FLoadInSoundEngineCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseStreamedMediaFileState::LoadInSoundEngine")); + if (UNLIKELY(iFileSize != 0 || StreamedFile)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseStreamedMediaFileState::LoadInSoundEngine %" PRIu32 " (%s): Stream seems to be already loaded."), MediaId, *DebugName.ToString()); + return LoadInSoundEngineFailed(MoveTemp(InCallback)); + } + + FWwiseFileCache* FileCache = FWwiseFileCache::Get(); + if (UNLIKELY(!FileCache)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseStreamedMediaFileState::LoadInSoundEngine %" PRIu32 " (%s): WwiseFileCache not available."), MediaId, *DebugName.ToString()); + return LoadInSoundEngineFailed(MoveTemp(InCallback)); + } + + const auto FullPathName = RootPath / MediaPathName.ToString(); + + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseStreamedMediaFileState::LoadInSoundEngine %" PRIu32 " (%s): Opening file"), MediaId, *DebugName.ToString()); + FileCache->CreateFileCacheHandle(StreamedFile, FullPathName, [this, Callback = MoveTemp(InCallback)](bool bResult) mutable + { + if (UNLIKELY(!bResult)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseStreamedMediaFileState::LoadInSoundEngine %" PRIu32 ": Failed to load Streaming Media (%s)."), MediaId, *DebugName.ToString()); + FFunctionGraphTask::CreateAndDispatchWhenReady([StreamedFile=StreamedFile] + { + delete StreamedFile; + }); + StreamedFile = nullptr; + return LoadInSoundEngineFailed(MoveTemp(Callback)); + } + + iFileSize = StreamedFile->GetFileSize(); + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseStreamedMediaFileState::LoadInSoundEngine %" PRIu32 " (%s)"), MediaId, *DebugName.ToString()); + INC_DWORD_STAT(STAT_WwiseFileHandlerLoadedMedia); + return LoadInSoundEngineSucceeded(MoveTemp(Callback)); + }); +} + +void FWwiseStreamedMediaFileState::UnloadFromSoundEngine(FUnloadFromSoundEngineCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseStreamedMediaFileState::UnloadFromSoundEngine")); + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseStreamedMediaFileState::UnloadFromSoundEngine %" PRIu32 " (%s)"), MediaId, *DebugName.ToString()); + + const auto* StreamedFileToDelete = StreamedFile; + StreamedFile = nullptr; + iFileSize = 0; + + delete StreamedFileToDelete; + + DEC_DWORD_STAT(STAT_WwiseFileHandlerLoadedMedia); + UnloadFromSoundEngineDone(MoveTemp(InCallback)); +} + +void FWwiseStreamedMediaFileState::CloseFile(FCloseFileCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseStreamedMediaFileState::CloseFile")); + if (pMediaMemory == nullptr) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseStreamedMediaFileState::CloseFile %" PRIu32 " (%s)"), MediaId, *DebugName.ToString()); + return CloseFileDone(MoveTemp(InCallback)); + } + + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("FWwiseStreamedMediaFileState::CloseFile %" PRIu32 " (%s): Failed closing prefetch without a SoundEngine. Leaking."), MediaId, *DebugName.ToString()); + pMediaMemory = nullptr; + uMediaSize = 0; + return CloseFileDone(MoveTemp(InCallback)); + } + + const auto Result = SoundEngine->TryUnsetMedia(this, 1, nullptr); + if (UNLIKELY(Result == AK_ResourceInUse)) + { + return CloseFileDefer(MoveTemp(InCallback)); + } + else + { + if (LIKELY(Result == AK_Success)) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseStreamedMediaFileState::CloseFile %" PRIu32 " (%s): Unloaded prefetch."), MediaId, *DebugName.ToString()); + DeallocateMemory(pMediaMemory, uMediaSize, bDeviceMemory, MemoryAlignment, true); + DEC_MEMORY_STAT_BY(STAT_WwiseFileHandlerPrefetchMemoryAllocated, uMediaSize); + } + else + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseStreamedMediaFileState::CloseFile %" PRIu32 " (%s): TryUnsetMedia of prefetch failed: %d (%s). Leaking."), MediaId, *DebugName.ToString(), Result, AkUnrealHelper::GetResultString(Result)); + } + pMediaMemory = nullptr; + uMediaSize = 0; + return CloseFileDone(MoveTemp(InCallback)); + } +} + +bool FWwiseStreamedMediaFileState::CanProcessFileOp() const +{ + if (UNLIKELY(State != EState::Loaded)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseStreamedMediaFileState::CanProcessFileOp %" PRIu32 " (%s): IO Hook asked for a file operation, but state is not ready."), MediaId, *DebugName.ToString()); + return false; + } + return true; +} + +AKRESULT FWwiseStreamedMediaFileState::ProcessRead( + AkFileDesc& InFileDesc, const AkIoHeuristics& InHeuristics, + AkAsyncIOTransferInfo& OutTransferInfo, FWwiseAkFileOperationDone&& InFileOpDoneCallback) +{ + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseStreamedMediaFileState::ProcessRead: Reading %" PRIu32 " bytes @ %" PRIu64 " in file %" PRIu32 " (%s)"), + OutTransferInfo.uRequestedSize, OutTransferInfo.uFilePosition, MediaId, *DebugName.ToString()); + + StreamedFile->ReadAkData(InHeuristics, OutTransferInfo, MoveTemp(InFileOpDoneCallback)); + return AK_Success; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseMediaManagerImpl.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseMediaManagerImpl.cpp new file mode 100644 index 0000000..d5db211 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseMediaManagerImpl.cpp @@ -0,0 +1,62 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseMediaManagerImpl.h" +#include "Wwise/WwiseMediaFileState.h" +#include "Wwise/API/WwiseSoundEngineAPI.h" +#include "Async/Async.h" + +FWwiseMediaManagerImpl::FWwiseMediaManagerImpl() +{ +} + +FWwiseMediaManagerImpl::~FWwiseMediaManagerImpl() +{ +} + +void FWwiseMediaManagerImpl::LoadMedia(const FWwiseMediaCookedData& InMediaCookedData, const FString& InRootPath, FLoadMediaCallback&& InCallback) +{ + IncrementFileStateUseAsync(InMediaCookedData.MediaId, EWwiseFileStateOperationOrigin::Loading, [this, InMediaCookedData, InRootPath]() mutable + { + return CreateOp(InMediaCookedData, InRootPath); + }, [InCallback = MoveTemp(InCallback)](const FWwiseFileStateSharedPtr, bool bInResult) + { + InCallback(bInResult); + }); +} + +void FWwiseMediaManagerImpl::UnloadMedia(const FWwiseMediaCookedData& InMediaCookedData, const FString& InRootPath, FUnloadMediaCallback&& InCallback) +{ + DecrementFileStateUseAsync(InMediaCookedData.MediaId, nullptr, EWwiseFileStateOperationOrigin::Loading, MoveTemp(InCallback)); +} + +void FWwiseMediaManagerImpl::SetGranularity(AkUInt32 InStreamingGranularity) +{ + StreamingGranularity = InStreamingGranularity; +} + +FWwiseFileStateSharedPtr FWwiseMediaManagerImpl::CreateOp(const FWwiseMediaCookedData& InMediaCookedData, const FString& InRootPath) +{ + if (InMediaCookedData.bStreaming) + { + return FWwiseFileStateSharedPtr(new FWwiseStreamedMediaFileState(InMediaCookedData, InRootPath, StreamingGranularity)); + } + else + { + return FWwiseFileStateSharedPtr(new FWwiseInMemoryMediaFileState(InMediaCookedData, InRootPath)); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseSoundBankFileState.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseSoundBankFileState.cpp new file mode 100644 index 0000000..71df3da --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseSoundBankFileState.cpp @@ -0,0 +1,316 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseSoundBankFileState.h" +#include "Wwise/API/WwiseSoundEngineAPI.h" +#include "Wwise/Stats/AsyncStats.h" +#include "Wwise/Stats/FileHandler.h" +#include "AkUnrealHelper.h" +#include "Async/MappedFileHandle.h" +#include "Async/Async.h" + +#include + +FWwiseSoundBankFileState::FWwiseSoundBankFileState(const FWwiseSoundBankCookedData& InCookedData, const FString& InRootPath): + FWwiseSoundBankCookedData(InCookedData), + RootPath(InRootPath) +{ + INC_DWORD_STAT(STAT_WwiseFileHandlerKnownSoundBanks); +} + +FWwiseSoundBankFileState::~FWwiseSoundBankFileState() +{ + DEC_DWORD_STAT(STAT_WwiseFileHandlerKnownSoundBanks); +} + +FWwiseInMemorySoundBankFileState::FWwiseInMemorySoundBankFileState(const FWwiseSoundBankCookedData& InCookedData, const FString& InRootPath) : + FWwiseSoundBankFileState(InCookedData, InRootPath), + Ptr(nullptr), + FileSize(0), + MappedHandle(nullptr), + MappedRegion(nullptr) +{ +} + +bool FWwiseInMemorySoundBankFileState::LoadAsMemoryView() const +{ +#if WITH_EDITOR + return false; +#else + return bContainsMedia; +#endif +} + +void FWwiseInMemorySoundBankFileState::OpenFile(FOpenFileCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseInMemorySoundBankFileState::OpenFile")); + if (UNLIKELY(FileSize != 0 || Ptr)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseInMemorySoundBankFileState::OpenFile %" PRIu32 " (%s): Seems to be already opened."), SoundBankId, *DebugName.ToString()); + OpenFileFailed(MoveTemp(InCallback)); + return; + } + + const auto FullPathName = RootPath / SoundBankPathName.ToString(); + + const bool bCanTryMemoryMapping = !bContainsMedia || (!bDeviceMemory && !MemoryAlignment); + if (bCanTryMemoryMapping + && GetMemoryMapped(MappedHandle, MappedRegion, FileSize, FullPathName, 0)) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseInMemorySoundBankFileState::OpenFile %" PRIu32 " (%s): Loading Memory Mapped SoundBank as %s."), SoundBankId, *DebugName.ToString(), LoadAsMemoryView() ? TEXT("View") : TEXT("Copy")); + Ptr = MappedRegion->GetMappedPtr(); + FileSize = MappedRegion->GetMappedSize(); + OpenFileSucceeded(MoveTemp(InCallback)); + } + else if (LIKELY(GetFileToPtr(Ptr, FileSize, FullPathName, bDeviceMemory, MemoryAlignment, bContainsMedia))) + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseInMemorySoundBankFileState::OpenFile %" PRIu32 " (%s): Loading SoundBank as %s."), SoundBankId, *DebugName.ToString(), LoadAsMemoryView() ? TEXT("View") : TEXT("Copy")); + OpenFileSucceeded(MoveTemp(InCallback)); + } + else + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseInMemorySoundBankFileState::OpenFile %" PRIu32 " (%s): Failed to load SoundBank (%s)."), SoundBankId, *DebugName.ToString(), *FullPathName); + Ptr = nullptr; + FileSize = 0; + OpenFileFailed(MoveTemp(InCallback)); + } +} + +void FWwiseInMemorySoundBankFileState::LoadInSoundEngine(FLoadInSoundEngineCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseInMemorySoundBankFileState::LoadInSoundEngine")); + if (UNLIKELY(!FileSize || !Ptr)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseInMemorySoundBankFileState::LoadInSoundEngine %" PRIu32 " (%s): No data, but supposed to be loaded."), SoundBankId, *DebugName.ToString()); + LoadInSoundEngineFailed(MoveTemp(InCallback)); + return; + } + + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("FWwiseInMemorySoundBankFileState::LoadInSoundEngine %" PRIu32 " (%s): Failed loading without a SoundEngine."), SoundBankId, *DebugName.ToString()); + LoadInSoundEngineFailed(MoveTemp(InCallback)); + return; + } + + AkBankID LoadedSoundBankId; + AkBankType LoadedSoundBankType; + + BankLoadCookie* Cookie = new BankLoadCookie(this); + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseInMemorySoundBankFileState::LoadInSoundEngine %p: Cookie %p."), this, Cookie); + if (!Cookie) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseInMemorySoundBankFileState::LoadInSoundEngine %" PRIu32 " (%s): Failed to load SoundBank: Could not allocate cookie."), SoundBankId, *DebugName.ToString()); + LoadInSoundEngineFailed(MoveTemp(InCallback)); + return; + } + + Cookie->Callback = MoveTemp(InCallback); + const auto LoadResult = + LoadAsMemoryView() + ? SoundEngine->LoadBankMemoryView(Ptr, FileSize, &FWwiseInMemorySoundBankFileState::BankLoadCallback, Cookie, LoadedSoundBankId, LoadedSoundBankType) + : SoundEngine->LoadBankMemoryCopy(Ptr, FileSize, &FWwiseInMemorySoundBankFileState::BankLoadCallback, Cookie, LoadedSoundBankId, LoadedSoundBankType); + + UE_CLOG(UNLIKELY(LoadedSoundBankType != static_cast(SoundBankType)), LogWwiseFileHandler, Error, TEXT("FWwiseInMemorySoundBankFileState::LoadInSoundEngine %" PRIu32 " (%s): Incorrect SoundBank type: %" PRIu8 " expected %" PRIu8), SoundBankId, *DebugName.ToString(), (uint8)LoadedSoundBankType, (uint8)SoundBankType); + if(LoadResult != AK_Success) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseInMemorySoundBankFileState::LoadInSoundEngine %" PRIu32 " (%s): Failed to load SoundBank: %d (%s)."), SoundBankId, *DebugName.ToString(), LoadResult, AkUnrealHelper::GetResultString(LoadResult)); + auto Callback = MoveTemp(Cookie->Callback); + delete Cookie; + LoadInSoundEngineFailed(MoveTemp(Callback)); + return; + } +} + +void FWwiseInMemorySoundBankFileState::UnloadFromSoundEngine(FUnloadFromSoundEngineCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseInMemorySoundBankFileState::UnloadFromSoundEngine")); + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("FWwiseInMemorySoundBankFileState::UnloadFromSoundEngine %" PRIu32 " (%s): Failed unloading without a SoundEngine."), SoundBankId, *DebugName.ToString()); + return CloseFileDone(MoveTemp(InCallback)); + } + + BankUnloadCookie* Cookie = new BankUnloadCookie(this); + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseInMemorySoundBankFileState::UnloadFromSoundEngine %p: Cookie %p."), this, Cookie); + if(!Cookie) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("FWwiseInMemorySoundBankFileState::UnloadFromSoundEngine %" PRIu32 " (%s): Could not allocate cookie for unload operation."), SoundBankId, *DebugName.ToString()); + return CloseFileDone(MoveTemp(InCallback)); + } + + Cookie->Callback = MoveTemp(InCallback); + const auto Result = SoundEngine->UnloadBank(SoundBankId, Ptr, &FWwiseInMemorySoundBankFileState::BankUnloadCallback, Cookie, static_cast(SoundBankType)); + if(Result != AK_Success) + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("FWwiseInMemorySoundBankFileState::UnloadFromSoundEngine %" PRIu32 " (%s): Call to SoundEngine failed with result %s"), SoundBankId, *DebugName.ToString(), AkUnrealHelper::GetResultString(Result)); + auto Callback = MoveTemp(Cookie->Callback); + delete Cookie; + CloseFileDone(MoveTemp(Callback)); + return; + } +} + +bool FWwiseInMemorySoundBankFileState::CanCloseFile() const +{ + // LoadFromSoundEngine will copy and delete the pointer. If succeeded, we are already closed. + return (State == EState::Opened && Ptr == nullptr) || FWwiseSoundBankFileState::CanCloseFile(); +} + +void FWwiseInMemorySoundBankFileState::CloseFile(FCloseFileCallback&& InCallback) +{ + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseInMemorySoundBankFileState::CloseFile")); + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("FWwiseInMemorySoundBankFileState::CloseFile %" PRIu32 " (%s): Closing Memory Mapped SoundBank."), SoundBankId, *DebugName.ToString()); + if (MappedHandle) + { + if (LIKELY(MappedRegion)) + { + UnmapRegion(*MappedRegion); + } + UnmapHandle(*MappedHandle); + + MappedRegion = nullptr; + MappedHandle = nullptr; + } + else if (Ptr) + { + DeallocateMemory(Ptr, FileSize, bDeviceMemory, MemoryAlignment, bContainsMedia); + } + Ptr = nullptr; + FileSize = 0; + CloseFileDone(MoveTemp(InCallback)); +} + +void FWwiseInMemorySoundBankFileState::FreeMemoryIfNeeded() +{ + // We don't need the memory anymore if we copied it, whether the load succeeded or not. + if (!LoadAsMemoryView()) + { + if (MappedHandle) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseInMemorySoundBankFileState::FreeMemoryIfNeeded %" PRIu32 " (%s): Freeing Mapped Handle"), SoundBankId, *DebugName.ToString()); + if (LIKELY(MappedRegion)) + { + UnmapRegion(*MappedRegion); + } + UnmapHandle(*MappedHandle); + + MappedRegion = nullptr; + MappedHandle = nullptr; + } + else if (Ptr) + { + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseInMemorySoundBankFileState::FreeMemoryIfNeeded %" PRIu32 " (%s): Freeing Pointer"), SoundBankId, *DebugName.ToString()); + DeallocateMemory(Ptr, FileSize, bDeviceMemory, MemoryAlignment, bContainsMedia); + } + Ptr = nullptr; + FileSize = 0; + } +} + +void FWwiseInMemorySoundBankFileState::BankLoadCallback( + AkUInt32 InBankID, + const void* InMemoryBankPtr, + AKRESULT InLoadResult, + void* InCookie + ) +{ + SCOPED_WWISEFILEHANDLER_EVENT_4(TEXT("FWwiseInMemorySoundBankFileState::BankLoadCallback")); + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseInMemorySoundBankFileState::BankLoadCallback %p: Cookie %p."), ((BankLoadCookie*)InCookie)->BankFileState, InCookie); + if (!InCookie) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseInMemorySoundBankFileState::BankLoadCallback %" PRIu32 " (%s): Failed to load SoundBank: %d. Cookie given by SoundEngine is invalid."), InBankID, InLoadResult, AkUnrealHelper::GetResultString(InLoadResult)); + return; + } + + AsyncTask(ENamedThreads::AnyThread, [=]() { + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseInMemorySoundBankFileState::BankLoadCallback Async")); + BankLoadCookie Cookie((BankLoadCookie*)InCookie); + delete (BankLoadCookie*)InCookie; + if (!Cookie.BankFileState) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseInMemorySoundBankFileState::BankLoadCallback %" PRIu32 " (%s): Failed to load SoundBank: %d. Cookie given by SoundEngine is invalid."), InBankID, InLoadResult, AkUnrealHelper::GetResultString(InLoadResult)); + return; + } + + auto* BankFileState = Cookie.BankFileState; + if (LIKELY(InLoadResult == AK_Success)) + { + UE_CLOG(UNLIKELY(InBankID != BankFileState->SoundBankId), LogWwiseFileHandler, Error, TEXT("FWwiseInMemorySoundBankFileState::BankLoadCallback: Incorrect SoundBank loaded: %" PRIu32 " expected %" PRIu32 " (%s)"), InBankID, BankFileState->SoundBankId, *BankFileState->DebugName.ToString()); + + INC_DWORD_STAT(STAT_WwiseFileHandlerLoadedSoundBanks); + BankFileState->FreeMemoryIfNeeded(); + BankFileState->LoadInSoundEngineSucceeded(MoveTemp(Cookie.Callback)); + } + else + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseInMemorySoundBankFileState::BankLoadCallback %" PRIu32 " (%s): Failed to load SoundBank: %d (%s)."), InBankID, *BankFileState->DebugName.ToString(), InLoadResult, AkUnrealHelper::GetResultString(InLoadResult)); + BankFileState->FreeMemoryIfNeeded(); + BankFileState->LoadInSoundEngineFailed(MoveTemp(Cookie.Callback)); + } + }); +} + +void FWwiseInMemorySoundBankFileState::BankUnloadCallback( + AkUInt32 InBankID, + const void* InMemoryBankPtr, + AKRESULT InUnloadResult, + void* InCookie +) +{ + SCOPED_WWISEFILEHANDLER_EVENT_4(TEXT("FWwiseInMemorySoundBankFileState::BankUnloadCallback")); + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("FWwiseInMemorySoundBankFileState::BankUnloadCallback %p: Cookie %p."), ((BankUnloadCookie*)InCookie)->BankFileState, InCookie); + if (!InCookie) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseInMemorySoundBankFileState::BankUnloadCallback %" PRIu32 ": Cookie given by SoundEngine is invalid."), InBankID); + return; + } + + AsyncTask(ENamedThreads::AnyThread, [=]() { + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseInMemorySoundBankFileState::BankUnloadCallback Async")); + BankUnloadCookie Cookie((BankUnloadCookie*)InCookie); + delete (BankUnloadCookie*)InCookie; + + if (!Cookie.BankFileState) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseInMemorySoundBankFileState::BankUnloadCallback %" PRIu32 ": Cookie given by SoundEngine is invalid."), InBankID); + return; + } + + auto* BankFileState = Cookie.BankFileState; + if (UNLIKELY(InUnloadResult == AK_ResourceInUse)) + { + BankFileState->UnloadFromSoundEngineDefer(MoveTemp(Cookie.Callback)); + } + else + { + UE_CLOG(InUnloadResult != AK_Success, LogWwiseFileHandler, Error, TEXT("FWwiseInMemorySoundBankFileState::BankUnloadCallback %" PRIu32 " (%s): UnloadBank failed: %d (%s)"), InBankID, *BankFileState->DebugName.ToString(), InUnloadResult, AkUnrealHelper::GetResultString(InUnloadResult)); + DEC_DWORD_STAT(STAT_WwiseFileHandlerLoadedSoundBanks); + if (InMemoryBankPtr) + { + BankFileState->UnloadFromSoundEngineDone(MoveTemp(Cookie.Callback)); + } + else + { + BankFileState->UnloadFromSoundEngineToClosedFile(MoveTemp(Cookie.Callback)); + } + } + }); +} + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseSoundBankManagerImpl.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseSoundBankManagerImpl.cpp new file mode 100644 index 0000000..d2e0a4d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseSoundBankManagerImpl.cpp @@ -0,0 +1,54 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseSoundBankManagerImpl.h" +#include "Wwise/WwiseSoundBankFileState.h" + +FWwiseSoundBankManagerImpl::FWwiseSoundBankManagerImpl() : + StreamingGranularity(0) +{ +} + +FWwiseSoundBankManagerImpl::~FWwiseSoundBankManagerImpl() +{ +} + +void FWwiseSoundBankManagerImpl::LoadSoundBank(const FWwiseSoundBankCookedData& InSoundBankCookedData, const FString& InRootPath, FLoadSoundBankCallback&& InCallback) +{ + IncrementFileStateUseAsync(InSoundBankCookedData.SoundBankId, EWwiseFileStateOperationOrigin::Loading, [this, InSoundBankCookedData, InRootPath]() mutable + { + return CreateOp(InSoundBankCookedData, InRootPath); + }, [InCallback = MoveTemp(InCallback)](const FWwiseFileStateSharedPtr, bool bInResult) + { + InCallback(bInResult); + }); +} + +void FWwiseSoundBankManagerImpl::UnloadSoundBank(const FWwiseSoundBankCookedData& InSoundBankCookedData, const FString& InRootPath, FUnloadSoundBankCallback&& InCallback) +{ + DecrementFileStateUseAsync(InSoundBankCookedData.SoundBankId, nullptr, EWwiseFileStateOperationOrigin::Loading, MoveTemp(InCallback)); +} + +void FWwiseSoundBankManagerImpl::SetGranularity(AkUInt32 InStreamingGranularity) +{ + StreamingGranularity = InStreamingGranularity; +} + +FWwiseFileStateSharedPtr FWwiseSoundBankManagerImpl::CreateOp(const FWwiseSoundBankCookedData& InSoundBankCookedData, const FString& InRootPath) +{ + return FWwiseFileStateSharedPtr(new FWwiseInMemorySoundBankFileState(InSoundBankCookedData, InRootPath)); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseWriteFileState.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseWriteFileState.cpp new file mode 100644 index 0000000..bc9bd1c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Private/Wwise/WwiseWriteFileState.cpp @@ -0,0 +1,97 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseWriteFileState.h" +#include "Wwise/Stats/AsyncStats.h" +#include "Wwise/Stats/FileHandler.h" + +#include + +FWwiseWriteFileState::FWwiseWriteFileState(IFileHandle* InFileHandle, const FString& InFilePathName): + FileHandle(InFileHandle), + FilePathName(InFilePathName) +{ + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("Creating writable file %s"), *FilePathName); + ASYNC_INC_DWORD_STAT(STAT_WwiseFileHandlerOpenedStreams); +} + +void FWwiseWriteFileState::CloseStreaming() +{ + if (UNLIKELY(!FileStateExecutionQueue)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseWriteFileState::CloseStreaming: %s file state %" PRIu32" already Term!"), GetManagingTypeName(), GetShortId()); + } + else + { + FileStateExecutionQueue->AsyncWait([this] + { + UE_LOG(LogWwiseFileHandler, Verbose, TEXT("ProcessWrite: Closing file %s"), *FilePathName); + if (FileHandle) + { + delete FileHandle; + FileHandle = nullptr; + ASYNC_DEC_DWORD_STAT(STAT_WwiseFileHandlerOpenedStreams); + } + }); + } + delete this; +} + +bool FWwiseWriteFileState::CanProcessFileOp() const +{ + if (UNLIKELY(State != EState::Loaded)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("WriteFileState %s: IO Hook asked for a file operation, but state is not ready."), *FilePathName); + return false; + } + return true; +} + +AKRESULT FWwiseWriteFileState::ProcessWrite(AkFileDesc& InFileDesc, const AkIoHeuristics& InHeuristics, AkAsyncIOTransferInfo& OutTransferInfo, FWwiseAkFileOperationDone&& InFileOpDoneCallback) +{ + if (UNLIKELY(!FileStateExecutionQueue)) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("FWwiseWriteFileState::ProcessWrite: %s file state %" PRIu32" already Term!"), GetManagingTypeName(), GetShortId()); + InFileOpDoneCallback(&OutTransferInfo, AK_NotInitialized); + return AK_NotInitialized; + } + FileStateExecutionQueue->Async([this, InFileDesc, InHeuristics, OutTransferInfo, InFileOpDoneCallback = MoveTemp(InFileOpDoneCallback)]() mutable + { + SCOPED_WWISEFILEHANDLER_EVENT_3(TEXT("FWwiseWriteFileState::ProcessWrite")); + UE_LOG(LogWwiseFileHandler, VeryVerbose, TEXT("ProcessWrite: Writing %" PRIu32 " bytes @ %" PRIu64 " in file %s"), + OutTransferInfo.uBufferSize, OutTransferInfo.uFilePosition, *FilePathName); + + FileHandle->Seek(OutTransferInfo.uFilePosition); + + AKRESULT Result; + if (LIKELY(FileHandle->Write(static_cast(OutTransferInfo.pBuffer), OutTransferInfo.uBufferSize))) + { + Result = AK_Success; + } + else + { + UE_LOG(LogWwiseFileHandler, Log, TEXT("ProcessWrite: Failed writing %" PRIu32 " bytes @ %" PRIu64 " in file %s"), + OutTransferInfo.uBufferSize, OutTransferInfo.uFilePosition, *FilePathName); + + Result = AK_UnknownFileError; + } + InFileOpDoneCallback(&OutTransferInfo, Result); + return Result; + }); + + return AK_Success; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/CookedData/WwiseExternalSourceCookedData.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/CookedData/WwiseExternalSourceCookedData.h new file mode 100644 index 0000000..e0ba3a2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/CookedData/WwiseExternalSourceCookedData.h @@ -0,0 +1,53 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "WwiseExternalSourceCookedData.generated.h" + +USTRUCT(BlueprintType) +struct WWISEFILEHANDLER_API FWwiseExternalSourceCookedData +{ + GENERATED_BODY() + + /** + * @brief User-defined Cookie for the External Source + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + int32 Cookie = 0; + + /** + * @brief Optional debug name. Can be empty in release, contain the name, or the full path of the asset. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + FName DebugName; + + FWwiseExternalSourceCookedData(); + + void Serialize(FArchive& Ar); + + FString GetDebugString() const; +}; + +inline uint32 GetTypeHash(const FWwiseExternalSourceCookedData& InCookedData) +{ + return GetTypeHash(InCookedData.Cookie); +} +inline bool operator==(const FWwiseExternalSourceCookedData& InLhs, const FWwiseExternalSourceCookedData& InRhs) +{ + return InLhs.Cookie == InRhs.Cookie; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/CookedData/WwiseLanguageCookedData.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/CookedData/WwiseLanguageCookedData.h new file mode 100644 index 0000000..47a86ac --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/CookedData/WwiseLanguageCookedData.h @@ -0,0 +1,76 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "WwiseLanguageCookedData.generated.h" + +UENUM(BlueprintType) +enum class EWwiseLanguageRequirement : uint8 +{ + IsDefault, + IsOptional, + SFX +}; + +USTRUCT(BlueprintType) +struct WWISEFILEHANDLER_API FWwiseLanguageCookedData +{ + GENERATED_BODY() + + static const FWwiseLanguageCookedData Sfx; + + /** + * @brief Short ID for the Language + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + int32 LanguageId = 0; + + /** + * @brief Language name as set in Wwise + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + FName LanguageName; + + /** + * @brief Is this language the default in Wwise + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + EWwiseLanguageRequirement LanguageRequirement = EWwiseLanguageRequirement::SFX; + + FWwiseLanguageCookedData(); + FWwiseLanguageCookedData(int32 LangId, const FName& LangName, EWwiseLanguageRequirement LangRequirement); + + void Serialize(FArchive& Ar); + + FName GetLanguageName() const { return LanguageName; } + int32 GetLanguageId() const { return LanguageId; } +}; + +inline uint32 GetTypeHash(const FWwiseLanguageCookedData& InCookedData) +{ + return HashCombine(GetTypeHash(InCookedData.LanguageId), GetTypeHash(InCookedData.LanguageName)); +} +inline bool operator==(const FWwiseLanguageCookedData& InLhs, const FWwiseLanguageCookedData& InRhs) +{ + return InLhs.LanguageId == InRhs.LanguageId && InLhs.LanguageName == InRhs.LanguageName; +} +inline bool operator!=(const FWwiseLanguageCookedData& InLhs, const FWwiseLanguageCookedData& InRhs) +{ + return (InLhs.LanguageId != InRhs.LanguageId) || (InLhs.LanguageName != InRhs.LanguageName); +} + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/CookedData/WwiseMediaCookedData.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/CookedData/WwiseMediaCookedData.h new file mode 100644 index 0000000..81c8312 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/CookedData/WwiseMediaCookedData.h @@ -0,0 +1,83 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "WwiseMediaCookedData.generated.h" + +USTRUCT(BlueprintType) +struct WWISEFILEHANDLER_API FWwiseMediaCookedData +{ + GENERATED_BODY() + + /** + * @brief Short ID for the Media + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + int32 MediaId = 0; + + /** + * @brief Path name relative to the platform's root. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + FName MediaPathName; + + /** + * @brief How many bytes need to be retrieved at load-time. Only set if streaming. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + int32 PrefetchSize = 0; + + /** + * @brief Alignment required to load the asset on device. Can be 0 if no particular requirements. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + int32 MemoryAlignment = 0; + + /** + * @brief True if the asset needs to be loaded in a special memory zone on the device. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + bool bDeviceMemory = false; + + /** + * @brief True if the asset should not be fully loaded in memory at load time. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + bool bStreaming = false; + + /** + * @brief Optional debug name. Can be empty in release, contain the name, or the full path of the asset. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + FName DebugName; + + FWwiseMediaCookedData(); + + void Serialize(FArchive& Ar); + + FString GetDebugString() const; +}; + +inline uint32 GetTypeHash(const FWwiseMediaCookedData& InCookedData) +{ + return HashCombine(GetTypeHash(InCookedData.MediaId), GetTypeHash(InCookedData.MediaPathName)); +} +inline bool operator==(const FWwiseMediaCookedData& InLhs, const FWwiseMediaCookedData& InRhs) +{ + return InLhs.MediaId == InRhs.MediaId && InLhs.MediaPathName == InRhs.MediaPathName; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/CookedData/WwiseSoundBankCookedData.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/CookedData/WwiseSoundBankCookedData.h new file mode 100644 index 0000000..90b623c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/CookedData/WwiseSoundBankCookedData.h @@ -0,0 +1,102 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkInclude.h" + +#include "WwiseSoundBankCookedData.generated.h" + +UENUM(BlueprintType) +enum class EWwiseSoundBankType : uint8 +{ + User, // = AKCODECID_BANK, + Event = AKCODECID_BANK_EVENT, + Bus = AKCODECID_BANK_BUS +}; + +/** + * @brief Required data to load a SoundBank +*/ +USTRUCT(BlueprintType) +struct WWISEFILEHANDLER_API FWwiseSoundBankCookedData +{ + GENERATED_BODY() + + /** + * @brief Short ID for the SoundBank. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + int32 SoundBankId = 0; + + /** + * @brief Path name relative to the platform's root. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + FName SoundBankPathName; + + /** + * @brief Alignment required to load the SoundBank on device. Can be 0 if no particular requirements. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + int32 MemoryAlignment = 0; + + /** + * @brief True if the SoundBank needs to be loaded in a special memory zone on the device. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + bool bDeviceMemory = false; + + /** + * @brief True if the SoundBank contains media or media parts. False means a data-only SoundBank. + * + * Useful to load the SoundBank as a copy instead of keeping it Memory-mapped, as the SoundEngine will decode + * data from the SoundBank, and has no use for the file itself. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + bool bContainsMedia = false; + + /** + * @brief User-created SoundBank, Event Auto-defined SoundBank, or Bus Auto-defined SoundBank. + * + * Useful for loading by file name. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + EWwiseSoundBankType SoundBankType = EWwiseSoundBankType::User; + + /** + * @brief Optional debug name. Can be empty in release, contain the name, or the full path of the asset. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + FName DebugName; + + FWwiseSoundBankCookedData(); + + void Serialize(FArchive& Ar); + + FString GetDebugString() const; +}; + +inline uint32 GetTypeHash(const FWwiseSoundBankCookedData& InCookedData) +{ + return HashCombine(GetTypeHash(InCookedData.SoundBankId), GetTypeHash(InCookedData.SoundBankPathName)); +} + +inline bool operator==(const FWwiseSoundBankCookedData& InLhs, const FWwiseSoundBankCookedData& InRhs) +{ + return InLhs.SoundBankId == InRhs.SoundBankId && InLhs.SoundBankPathName == InRhs.SoundBankPathName; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/Stats/FileHandler.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/Stats/FileHandler.h new file mode 100644 index 0000000..7fb5f58 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/Stats/FileHandler.h @@ -0,0 +1,73 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Stats/Stats.h" +#include "Wwise/Stats/NamedEvents.h" + +DECLARE_STATS_GROUP(TEXT("File Handler"), STATGROUP_WwiseFileHandler, STATCAT_Wwise); + +DECLARE_MEMORY_STAT_EXTERN(TEXT("Memory Allocated"), STAT_WwiseFileHandlerMemoryAllocated, STATGROUP_WwiseFileHandler, WWISEFILEHANDLER_API); +DECLARE_MEMORY_STAT_EXTERN(TEXT("Memory Mapped"), STAT_WwiseFileHandlerMemoryMapped, STATGROUP_WwiseFileHandler, WWISEFILEHANDLER_API); +DECLARE_MEMORY_STAT_EXTERN(TEXT("Device Memory Allocated"), STAT_WwiseFileHandlerDeviceMemoryAllocated, STATGROUP_WwiseFileHandler, WWISEFILEHANDLER_API); + +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Created External Source States"), STAT_WwiseFileHandlerCreatedExternalSourceStates, STATGROUP_WwiseFileHandler, WWISEFILEHANDLER_API); +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Known External Source Media"), STAT_WwiseFileHandlerKnownExternalSourceMedia, STATGROUP_WwiseFileHandler, WWISEFILEHANDLER_API); +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Loaded External Source Media"), STAT_WwiseFileHandlerLoadedExternalSourceMedia, STATGROUP_WwiseFileHandler, WWISEFILEHANDLER_API); +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Known Media"), STAT_WwiseFileHandlerKnownMedia, STATGROUP_WwiseFileHandler, WWISEFILEHANDLER_API); +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Loaded Media"), STAT_WwiseFileHandlerLoadedMedia, STATGROUP_WwiseFileHandler, WWISEFILEHANDLER_API); +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Known SoundBanks"), STAT_WwiseFileHandlerKnownSoundBanks, STATGROUP_WwiseFileHandler, WWISEFILEHANDLER_API); +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Loaded SoundBanks"), STAT_WwiseFileHandlerLoadedSoundBanks, STATGROUP_WwiseFileHandler, WWISEFILEHANDLER_API); + +DECLARE_MEMORY_STAT_EXTERN(TEXT("Prefetch Memory Allocated"), STAT_WwiseFileHandlerPrefetchMemoryAllocated, STATGROUP_WwiseFileHandler, WWISEFILEHANDLER_API); + +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Total Error Count"), STAT_WwiseFileHandlerTotalErrorCount, STATGROUP_WwiseFileHandler, WWISEFILEHANDLER_API); +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("State Operations Being Processed"), STAT_WwiseFileHandlerStateOperationsBeingProcessed, STATGROUP_WwiseFileHandler, WWISEFILEHANDLER_API); +DECLARE_CYCLE_STAT_EXTERN(TEXT("State Operation Latency"), STAT_WwiseFileHandlerStateOperationLatency, STATGROUP_WwiseFileHandler, WWISEFILEHANDLER_API); + +DECLARE_STATS_GROUP(TEXT("File Handler - Low-level I/O"), STATGROUP_WwiseFileHandlerLowLevelIO, STATCAT_Wwise); + +DECLARE_FLOAT_COUNTER_STAT_EXTERN(TEXT("Streaming KB / Frame"), STAT_WwiseFileHandlerStreamingKB, STATGROUP_WwiseFileHandlerLowLevelIO, WWISEFILEHANDLER_API); +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Opened Streams"), STAT_WwiseFileHandlerOpenedStreams, STATGROUP_WwiseFileHandlerLowLevelIO, WWISEFILEHANDLER_API); +DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Batched Requests"), STAT_WwiseFileHandlerBatchedRequests, STATGROUP_WwiseFileHandlerLowLevelIO, WWISEFILEHANDLER_API); +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Pending Requests"), STAT_WwiseFileHandlerPendingRequests, STATGROUP_WwiseFileHandlerLowLevelIO, WWISEFILEHANDLER_API); +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Total Requests"), STAT_WwiseFileHandlerTotalRequests, STATGROUP_WwiseFileHandlerLowLevelIO, WWISEFILEHANDLER_API); +DECLARE_FLOAT_ACCUMULATOR_STAT_EXTERN(TEXT("Total Streaming MB"), STAT_WwiseFileHandlerTotalStreamedMB, STATGROUP_WwiseFileHandlerLowLevelIO, WWISEFILEHANDLER_API); + +DECLARE_CYCLE_STAT_EXTERN(TEXT("Async Delete Request"), STAT_WwiseFileHandlerAsyncDeleteRequest, STATGROUP_WwiseFileHandlerLowLevelIO, WWISEFILEHANDLER_API); +DECLARE_CYCLE_STAT_EXTERN(TEXT("IO Request Latency"), STAT_WwiseFileHandlerIORequestLatency, STATGROUP_WwiseFileHandlerLowLevelIO, WWISEFILEHANDLER_API); +DECLARE_CYCLE_STAT_EXTERN(TEXT("File Operation Latency"), STAT_WwiseFileHandlerFileOperationLatency, STATGROUP_WwiseFileHandlerLowLevelIO, WWISEFILEHANDLER_API); +DECLARE_CYCLE_STAT_EXTERN(TEXT("SoundEngine Callback Latency"), STAT_WwiseFileHandlerSoundEngineCallbackLatency, STATGROUP_WwiseFileHandlerLowLevelIO, WWISEFILEHANDLER_API); + +DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Stream Critical Priority"), STAT_WwiseFileHandlerCriticalPriority, STATGROUP_WwiseFileHandlerLowLevelIO, WWISEFILEHANDLER_API); +DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Stream High Priority"), STAT_WwiseFileHandlerHighPriority, STATGROUP_WwiseFileHandlerLowLevelIO, WWISEFILEHANDLER_API); +DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Stream Normal Priority"), STAT_WwiseFileHandlerNormalPriority, STATGROUP_WwiseFileHandlerLowLevelIO, WWISEFILEHANDLER_API); +DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Stream Below Normal Priority"), STAT_WwiseFileHandlerBelowNormalPriority, STATGROUP_WwiseFileHandlerLowLevelIO, WWISEFILEHANDLER_API); +DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Stream Low Priority"), STAT_WwiseFileHandlerLowPriority, STATGROUP_WwiseFileHandlerLowLevelIO, WWISEFILEHANDLER_API); +DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Stream Background Priority"), STAT_WwiseFileHandlerBackgroundPriority, STATGROUP_WwiseFileHandlerLowLevelIO, WWISEFILEHANDLER_API); + +WWISEFILEHANDLER_API DECLARE_LOG_CATEGORY_EXTERN(LogWwiseFileHandler, Log, All); + +#define SCOPED_WWISEFILEHANDLER_EVENT(Text) SCOPED_WWISE_NAMED_EVENT(TEXT("WwiseFileHandler"), Text) +#define SCOPED_WWISEFILEHANDLER_EVENT_2(Text) SCOPED_WWISE_NAMED_EVENT_2(TEXT("WwiseFileHandler"), Text) +#define SCOPED_WWISEFILEHANDLER_EVENT_3(Text) SCOPED_WWISE_NAMED_EVENT_3(TEXT("WwiseFileHandler"), Text) +#define SCOPED_WWISEFILEHANDLER_EVENT_4(Text) SCOPED_WWISE_NAMED_EVENT_4(TEXT("WwiseFileHandler"), Text) +#define SCOPED_WWISEFILEHANDLER_EVENT_F(Format, ...) SCOPED_WWISE_NAMED_EVENT_F(TEXT("WwiseFileHandler"), Format, __VA_ARGS__) +#define SCOPED_WWISEFILEHANDLER_EVENT_F_2(Format, ...) SCOPED_WWISE_NAMED_EVENT_F_2(TEXT("WwiseFileHandler"), Format, __VA_ARGS__) +#define SCOPED_WWISEFILEHANDLER_EVENT_F_3(Format, ...) SCOPED_WWISE_NAMED_EVENT_F_3(TEXT("WwiseFileHandler"), Format, __VA_ARGS__) +#define SCOPED_WWISEFILEHANDLER_EVENT_F_4(Format, ...) SCOPED_WWISE_NAMED_EVENT_F_4(TEXT("WwiseFileHandler"), Format, __VA_ARGS__) diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseExternalSourceFileState.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseExternalSourceFileState.h new file mode 100644 index 0000000..a57507f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseExternalSourceFileState.h @@ -0,0 +1,97 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseFileState.h" +#include "Wwise/WwiseStreamableFileStateInfo.h" + +class FWwiseFileCacheHandle; + +class WWISEFILEHANDLER_API FWwiseExternalSourceFileState : public FWwiseFileState, public AkExternalSourceInfo +{ +public: + const uint32 MemoryAlignment; + const bool bDeviceMemory; + + const uint32 MediaId; + const FName MediaPathName; + const FName RootPath; + + TAtomic PlayCount; + +protected: + FWwiseExternalSourceFileState(uint32 InMemoryAlignment, bool bInDeviceMemory, + uint32 InMediaId, const FName& InMediaPathName, const FName& InRootPath, int32 InCodecId); + ~FWwiseExternalSourceFileState() override; + +public: + bool CanDelete() const override { return FWwiseFileState::CanDelete() && PlayCount.Load() == 0; } + + virtual bool GetExternalSourceInfo(AkExternalSourceInfo& OutInfo); + void IncrementPlayCount(); + bool DecrementPlayCount(); + +protected: + bool CanUnloadFromSoundEngine() const override { return FWwiseFileState::CanUnloadFromSoundEngine() && PlayCount == 0; } + + const TCHAR* GetManagingTypeName() const override final { return TEXT("External Source"); } + uint32 GetShortId() const override final { return MediaId; } +}; + +class WWISEFILEHANDLER_API FWwiseInMemoryExternalSourceFileState : public FWwiseExternalSourceFileState +{ +public: + const uint8* Ptr; + IMappedFileHandle* MappedHandle; + IMappedFileRegion* MappedRegion; + + FWwiseInMemoryExternalSourceFileState(uint32 InMemoryAlignment, bool bInDeviceMemory, + uint32 InMediaId, const FName& InMediaPathName, const FName& InRootPath, int32 InCodecId); + ~FWwiseInMemoryExternalSourceFileState() override { Term(); } + + void OpenFile(FOpenFileCallback&& InCallback) override; + void LoadInSoundEngine(FLoadInSoundEngineCallback&& InCallback) override; + void UnloadFromSoundEngine(FUnloadFromSoundEngineCallback&& InCallback) override; + void CloseFile(FCloseFileCallback&& InCallback) override; +}; + +class WWISEFILEHANDLER_API FWwiseStreamedExternalSourceFileState : public FWwiseExternalSourceFileState, public FWwiseStreamableFileStateInfo, public AkSourceSettings +{ +public: + const uint32 PrefetchSize; + const uint32 StreamingGranularity; + + FWwiseFileCacheHandle* StreamedFile; + + FWwiseStreamedExternalSourceFileState(uint32 InMemoryAlignment, bool bInDeviceMemory, + uint32 InPrefetchSize, uint32 InStreamingGranularity, + uint32 InMediaId, const FName& InMediaPathName, const FName& InRootPath, int32 InCodecId); + ~FWwiseStreamedExternalSourceFileState() override { Term(); } + + void CloseStreaming() override; + FWwiseStreamableFileStateInfo* GetStreamableFileStateInfo() override { return this; } + const FWwiseStreamableFileStateInfo* GetStreamableFileStateInfo() const override { return this; } + + void OpenFile(FOpenFileCallback&& InCallback) override; + void LoadInSoundEngine(FLoadInSoundEngineCallback&& InCallback) override; + void UnloadFromSoundEngine(FUnloadFromSoundEngineCallback&& InCallback) override; + void CloseFile(FCloseFileCallback&& InCallback) override; + + bool CanProcessFileOp() const override; + AKRESULT ProcessRead(AkFileDesc& InFileDesc, const AkIoHeuristics& InHeuristics, AkAsyncIOTransferInfo& OutTransferInfo, FWwiseAkFileOperationDone&& InFileOpDoneCallback) override; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseExternalSourceManager.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseExternalSourceManager.h new file mode 100644 index 0000000..2152808 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseExternalSourceManager.h @@ -0,0 +1,70 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseFileHandlerModule.h" +#include "Wwise/WwiseStreamableFileHandler.h" +#include "Wwise/CookedData/WwiseLanguageCookedData.h" + +struct FWwiseSharedLanguageId; +struct AkExternalSourceInfo; +struct FWwiseExternalSourceCookedData; +class FWwiseResourceCooker; + +#if WITH_EDITORONLY_DATA +struct FWwiseSharedPlatformId; +#endif + +class IWwiseExternalSourceManager : public IWwiseStreamableFileHandler +{ +public: + inline static IWwiseExternalSourceManager* Get() + { + if (auto* Module = IWwiseFileHandlerModule::GetModule()) + { + return Module->GetExternalSourceManager(); + } + return nullptr; + } + + using FLoadExternalSourceCallback = TUniqueFunction; + using FUnloadExternalSourceCallback = TUniqueFunction; + + virtual void LoadExternalSource(const FWwiseExternalSourceCookedData& InExternalSourceCookedData, const FName& InRootPath, + const FWwiseLanguageCookedData& InLanguage, FLoadExternalSourceCallback&& InCallback) = 0; + virtual void UnloadExternalSource(const FWwiseExternalSourceCookedData& InExternalSourceCookedData, const FName& InRootPath, + const FWwiseLanguageCookedData& InLanguage, FUnloadExternalSourceCallback&& InCallback) = 0; + virtual void SetGranularity(AkUInt32 Uint32) = 0; + + virtual TArray PrepareExternalSourceInfos(TArray& OutInfo, + const TArray&& InCookedData) = 0; + virtual void BindPlayingIdToExternalSources(const uint32 InPlayingId, const TArray& InMediaIds) = 0; + virtual void OnEndOfEvent(const uint32 InPlayingID) = 0; + + virtual void SetExternalSourceMediaById(const FName& ExternalSourceName, const int32 MediaId) = 0; + virtual void SetExternalSourceMediaByName(const FName& ExternalSourceName, const FName& MediaName) = 0; + virtual void SetExternalSourceMediaWithIds(const int32 ExternalSourceCookie, const int32 MediaId) = 0; + + +#if WITH_EDITORONLY_DATA + virtual void Cook(FWwiseResourceCooker& InResourceCooker, const FWwiseExternalSourceCookedData& InCookedData, TFunctionRef WriteAdditionalFile, + const FWwiseSharedPlatformId& InPlatform, const FWwiseSharedLanguageId& InLanguage) = 0; +#endif + + virtual FString GetStagingDirectory() const { return TEXT("ExternalSources"); } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseExternalSourceManagerImpl.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseExternalSourceManagerImpl.h new file mode 100644 index 0000000..d908e47 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseExternalSourceManagerImpl.h @@ -0,0 +1,91 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#include "Wwise/WwiseExternalSourceManager.h" +#include "Wwise/CookedData/WwiseExternalSourceCookedData.h" +#include "Wwise/WwiseFileState.h" +#include "Wwise/WwiseFileHandlerBase.h" + +class FWwiseExternalSourceFileState; + +struct WWISEFILEHANDLER_API FWwiseExternalSourceState : public FWwiseExternalSourceCookedData +{ + FWwiseExternalSourceState(const FWwiseExternalSourceCookedData& InCookedData); + ~FWwiseExternalSourceState(); + + TAtomic LoadCount; + void IncrementLoadCount(); + bool DecrementLoadCount(); +}; +using FWwiseExternalSourceStateSharedPtr = TSharedPtr; + +class WWISEFILEHANDLER_API FWwiseExternalSourceManagerImpl : public IWwiseExternalSourceManager, public FWwiseFileHandlerBase +{ +public: + FWwiseExternalSourceManagerImpl(); + ~FWwiseExternalSourceManagerImpl(); + + virtual const TCHAR* GetManagingTypeName() const override { return TEXT("External Source"); } + virtual void LoadExternalSource(const FWwiseExternalSourceCookedData& InExternalSourceCookedData, const FName& InRootPath, + const FWwiseLanguageCookedData& InLanguage, FLoadExternalSourceCallback&& InCallback) override; + virtual void UnloadExternalSource(const FWwiseExternalSourceCookedData& InExternalSourceCookedData, const FName& InRootPath, + const FWwiseLanguageCookedData& InLanguage, FUnloadExternalSourceCallback&& InCallback) override; + virtual void SetGranularity(AkUInt32 InStreamingGranularity) override; + + IWwiseStreamingManagerHooks& GetStreamingHooks() override final { return *this; } + + virtual TArray PrepareExternalSourceInfos(TArray& OutInfo, + const TArray&& InCookedData) override; + virtual void BindPlayingIdToExternalSources(const uint32 InPlayingId, const TArray& InMediaIds) override; + virtual void OnEndOfEvent(const uint32 InPlayingID) override; + virtual void SetExternalSourceMediaById(const FName& ExternalSourceName, const int32 MediaId) override; + virtual void SetExternalSourceMediaByName(const FName& ExternalSourceName, const FName& MediaName) override; + virtual void SetExternalSourceMediaWithIds(const int32 ExternalSourceCookie, const int32 MediaId) override; + + +#if WITH_EDITORONLY_DATA + virtual void Cook(FWwiseResourceCooker& InResourceCooker, const FWwiseExternalSourceCookedData& InCookedData, + TFunctionRef WriteAdditionalFile, + const FWwiseSharedPlatformId& InPlatform, const FWwiseSharedLanguageId& InLanguage) override; +#endif + +protected: + /** + * @brief Lock on the Cookie to Media Table. Lock as "ReadOnly" for using the tables (Prepare), and as "Write" for modifying the tables. + */ + FRWLock CookieToMediaLock; + TMap CookieToMedia; + + TMultiMap PlayingIdToMediaIds; + + uint32 StreamingGranularity; + TMap ExternalSourceStatesById; + + virtual void LoadExternalSourceImpl(const FWwiseExternalSourceCookedData& InExternalSourceCookedData, const FName& InRootPath, + const FWwiseLanguageCookedData& InLanguage, FLoadExternalSourceCallback&& InCallback); + virtual void UnloadExternalSourceImpl(const FWwiseExternalSourceCookedData& InExternalSourceCookedData, const FName& InRootPath, + const FWwiseLanguageCookedData& InLanguage, FUnloadExternalSourceCallback&& InCallback); + virtual FWwiseExternalSourceStateSharedPtr CreateExternalSourceState(const FWwiseExternalSourceCookedData& InExternalSourceCookedData, const FName& InRootPath); + virtual bool CloseExternalSourceState(FWwiseExternalSourceState& InExternalSourceState); + + virtual void LoadExternalSourceMedia(const uint32 InExternalSourceCookie, const FName& InExternalSourceName, const FName& InRootPath, FLoadExternalSourceCallback&& InCallback); + virtual void UnloadExternalSourceMedia(const uint32 InExternalSourceCookie, const FName& InExternalSourceName, const FName& InRootPath, FUnloadExternalSourceCallback&& InCallback); + + virtual uint32 PrepareExternalSourceInfo(AkExternalSourceInfo& OutInfo, const FWwiseExternalSourceCookedData& InCookedData); + virtual void OnDeleteState(uint32 InShortId, FWwiseFileState& InFileState, EWwiseFileStateOperationOrigin InOperationOrigin, FDecrementStateCallback&& InCallback) override; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseExternalSourceStatics.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseExternalSourceStatics.h new file mode 100644 index 0000000..3bd84cf --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseExternalSourceStatics.h @@ -0,0 +1,40 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "AkUnrealHelper.h" + +#include "WwiseExternalSourceStatics.generated.h" + +UCLASS() +class WWISEFILEHANDLER_API UWwiseExternalSourceStatics : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, Category="WwiseExternalSources") + static void SetExternalSourceMediaById(const FString& ExternalSourceName, const int32 MediaId); + + UFUNCTION(BlueprintCallable, Category="WwiseExternalSources") + static void SetExternalSourceMediaByName(const FString& ExternalSourceName, const FString& MediaName); + + UFUNCTION(BlueprintCallable, Category="WwiseExternalSources") + static void SetExternalSourceMediaWithIds(const FAkUniqueID ExternalSourceCookie, const int32 MediaId); +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseFileCache.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseFileCache.h new file mode 100644 index 0000000..d441071 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseFileCache.h @@ -0,0 +1,92 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "GenericPlatform/GenericPlatformFile.h" +#include "AkInclude.h" + +#include "Wwise/WwiseExecutionQueue.h" + +class FWwiseAsyncCycleCounter; +class FWwiseFileCacheHandle; + +class IAsyncReadRequest; +class FQueuedThreadPool; + +using FWwiseFileOperationDone = TUniqueFunction; +using FWwiseAkFileOperationDone = TUniqueFunction; + +/** + * Wwise File Cache manager. + * + * This is a simple Wwise version of Unreal's complex FFileCache. + * + * WwiseFileHandler module already opens any file only once, so we don't need a global cache. + * + * Compared to Unreal's FFileCache, we want to process everything asynchronously, + * including file opening in the unlikely possibility the file is not accessible or present. + * This allows for a fully asynchronous process. + */ +class WWISEFILEHANDLER_API FWwiseFileCache +{ +public: + static FWwiseFileCache* Get(); + + FWwiseFileCache(); + virtual ~FWwiseFileCache(); + virtual void CreateFileCacheHandle(FWwiseFileCacheHandle*& OutHandle, const FString& Pathname, FWwiseFileOperationDone&& OnDone); + + FWwiseExecutionQueue OpenQueue; + FWwiseExecutionQueue DeleteRequestQueue; +}; + +class WWISEFILEHANDLER_API FWwiseFileCacheHandle +{ +public: + FWwiseFileCacheHandle(const FString& Pathname); + virtual ~FWwiseFileCacheHandle(); + + virtual void Open(FWwiseFileOperationDone&& OnDone); + + void DeleteRequest(IAsyncReadRequest* Request); + + virtual void ReadData(uint8* OutBuffer, int64 Offset, int64 BytesToRead, EAsyncIOPriorityAndFlags Priority, FWwiseFileOperationDone&& OnDone); + void ReadAkData(uint8* OutBuffer, int64 Offset, int64 BytesToRead, int8 AkPriority, FWwiseFileOperationDone&& OnDone); + void ReadAkData(const AkIoHeuristics& Heuristics, AkAsyncIOTransferInfo& TransferInfo, FWwiseAkFileOperationDone&& Callback); + + const FString& GetPathname() const { return Pathname; } + int64 GetFileSize() const { return FileSize; } + +protected: + FString Pathname; + + IAsyncReadFileHandle* FileHandle; + int64 FileSize; + + FWwiseFileOperationDone InitializationDone; + FWwiseAsyncCycleCounter* InitializationStat; + + TAtomic< FEvent* > CanDestroy{ nullptr }; + TAtomic RequestsInFlight { 0 }; + + void RemoveRequestInFlight(); + virtual void OnSizeRequestDone(bool bWasCancelled, IAsyncReadRequest* Request); + virtual void OnReadDataDone(bool bWasCancelled, IAsyncReadRequest* Request, FWwiseFileOperationDone&& OnDone); + virtual void OnReadDataDone(bool bResult, FWwiseFileOperationDone&& OnDone); + virtual void CallDone(bool bResult, FWwiseFileOperationDone&& OnDone); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseFileHandlerBase.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseFileHandlerBase.h new file mode 100644 index 0000000..9ac0ff6 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseFileHandlerBase.h @@ -0,0 +1,50 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseStreamingManagerHooks.h" +#include "Wwise/WwiseExecutionQueue.h" +#include "Wwise/WwiseFileState.h" + +class WWISEFILEHANDLER_API FWwiseFileHandlerBase : protected IWwiseStreamingManagerHooks +{ +protected: + FWwiseFileHandlerBase(); + ~FWwiseFileHandlerBase() override; + + void OpenStreaming(AkAsyncFileOpenData* io_pOpenData) override; + void CloseStreaming(uint32 InShortId, FWwiseFileState& InFileState) override; + AKRESULT GetOpenStreamingResult(AkAsyncFileOpenData* io_pOpenData); + + using FCreateStateFunction = TUniqueFunction; + using FIncrementStateCallback = TUniqueFunction; + using FDecrementStateCallback = TUniqueFunction; + void IncrementFileStateUseAsync(uint32 InShortId, EWwiseFileStateOperationOrigin InOperationOrigin, FCreateStateFunction&& InCreate, FIncrementStateCallback&& InCallback); + void DecrementFileStateUseAsync(uint32 InShortId, FWwiseFileState* InFileState, EWwiseFileStateOperationOrigin InOperationOrigin, FDecrementStateCallback&& InCallback); + + virtual void IncrementFileStateUse(uint32 InShortId, EWwiseFileStateOperationOrigin InOperationOrigin, FCreateStateFunction&& InCreate, FIncrementStateCallback&& InCallback); + virtual void DecrementFileStateUse(uint32 InShortId, FWwiseFileState* InFileState, EWwiseFileStateOperationOrigin InOperationOrigin, FDecrementStateCallback&& InCallback); + virtual void OnDeleteState(uint32 InShortId, FWwiseFileState& InFileState, EWwiseFileStateOperationOrigin InOperationOrigin, FDecrementStateCallback&& InCallback); + + virtual const TCHAR* GetManagingTypeName() const { return TEXT("UNKNOWN"); } + + FWwiseExecutionQueue FileHandlerExecutionQueue; + + FRWLock FileStatesByIdLock; + TMap FileStatesById; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseFileHandlerModule.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseFileHandlerModule.h new file mode 100644 index 0000000..c024734 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseFileHandlerModule.h @@ -0,0 +1,101 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Modules/ModuleManager.h" +#include "Misc/ConfigCacheIni.h" + +#include "AkInclude.h" + +class FWwiseFileCache; +class FWwiseIOHook; +class IWwiseSoundBankManager; +class IWwiseExternalSourceManager; +class IWwiseMediaManager; + +class IWwiseFileHandlerModule : public IModuleInterface +{ +public: + static FName GetModuleName() + { + static const FName ModuleName = GetModuleNameFromConfig(); + return ModuleName; + } + + /** + * Checks to see if this module is loaded and ready. + * + * @return True if the module is loaded and ready to use + */ + static bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded(GetModuleName()); + } + + static IWwiseFileHandlerModule* GetModule() + { + const auto ModuleName = GetModuleName(); + if (ModuleName.IsNone()) + { + return nullptr; + } + + FModuleManager& ModuleManager = FModuleManager::Get(); + IWwiseFileHandlerModule* Result = ModuleManager.GetModulePtr(ModuleName); + if (UNLIKELY(!Result)) + { + if (UNLIKELY(IsEngineExitRequested())) + { + UE_LOG(LogLoad, Verbose, TEXT("Skipping reloading missing WwiseFileHandler module: Exiting.")); + } + else if (UNLIKELY(!IsInGameThread())) + { + UE_LOG(LogLoad, Warning, TEXT("Skipping loading missing WwiseFileHandler module: Not in game thread")); + } + else + { + UE_LOG(LogLoad, Log, TEXT("Loading WwiseFileHandler module: %s"), *ModuleName.GetPlainNameString()); + Result = ModuleManager.LoadModulePtr(ModuleName); + if (UNLIKELY(!Result)) + { + UE_LOG(LogLoad, Fatal, TEXT("Could not load WwiseFileHandler module: %s not found"), *ModuleName.GetPlainNameString()); + } + } + } + + return Result; + } + + virtual IWwiseSoundBankManager* GetSoundBankManager() { return nullptr; } + virtual IWwiseExternalSourceManager* GetExternalSourceManager() { return nullptr; } + virtual IWwiseMediaManager* GetMediaManager() { return nullptr; } + virtual FWwiseFileCache* GetFileCache() { return nullptr; } + virtual FWwiseIOHook* InstantiateIOHook() { return nullptr; } + virtual IWwiseSoundBankManager* InstantiateSoundBankManager() { return nullptr; } + virtual IWwiseExternalSourceManager* InstantiateExternalSourceManager() { return nullptr; } + virtual IWwiseMediaManager* InstantiateMediaManager() { return nullptr; } + virtual FWwiseFileCache* InstantiateFileCache() { return nullptr; } + +private: + static inline FName GetModuleNameFromConfig() + { + FString ModuleName = TEXT("WwiseFileHandler"); + GConfig->GetString(TEXT("Audio"), TEXT("WwiseFileHandlerModuleName"), ModuleName, GEngineIni); + return FName(ModuleName); + } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseFileHandlerModuleImpl.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseFileHandlerModuleImpl.h new file mode 100644 index 0000000..75a32f4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseFileHandlerModuleImpl.h @@ -0,0 +1,50 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseFileHandlerModule.h" +#include "Wwise/WwiseFileCache.h" +#include "Wwise/WwiseExternalSourceManager.h" +#include "Wwise/WwiseMediaManager.h" +#include "Wwise/WwiseSoundBankManager.h" + +class WWISEFILEHANDLER_API FWwiseFileHandlerModule : public IWwiseFileHandlerModule +{ +public: + FWwiseFileHandlerModule(); + + IWwiseSoundBankManager* GetSoundBankManager() override; + IWwiseExternalSourceManager* GetExternalSourceManager() override; + IWwiseMediaManager* GetMediaManager() override; + FWwiseFileCache* GetFileCache() override; + FWwiseIOHook* InstantiateIOHook() override; + IWwiseSoundBankManager* InstantiateSoundBankManager() override; + IWwiseExternalSourceManager* InstantiateExternalSourceManager() override; + IWwiseMediaManager* InstantiateMediaManager() override; + FWwiseFileCache* InstantiateFileCache() override; + + void StartupModule() override; + void ShutdownModule() override; + +protected: + FRWLock Lock; + TUniquePtr ExternalSourceManager; + TUniquePtr MediaManager; + TUniquePtr SoundBankManager; + TUniquePtr FileCache; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseFileState.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseFileState.h new file mode 100644 index 0000000..cac55f3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseFileState.h @@ -0,0 +1,147 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseExecutionQueue.h" +#include "Wwise/WwiseFileStateTools.h" + +class FWwiseStreamableFileStateInfo; + +enum class WWISEFILEHANDLER_API EWwiseFileStateOperationOrigin +{ + Loading, + Streaming +}; + +class WWISEFILEHANDLER_API FWwiseFileState : protected FWwiseFileStateTools +{ +public: + ~FWwiseFileState() override; + + template + RequestedType* GetStateAs() + { +#if defined(WITH_RTTI) || defined(_CPPRTTI) || defined(__GXX_RTTI) + auto* Result = dynamic_cast(this); + checkf(Result, TEXT("Invalid Type Cast")); +#else + auto* Result = static_cast(this); +#endif + return Result; + } + + FWwiseExecutionQueue* FileStateExecutionQueue{ new FWwiseExecutionQueue }; + + int LoadCount{ 0 }; + int StreamingCount{ 0 }; + + enum class WWISEFILEHANDLER_API EState + { + Closed, + Opening, + Opened, + Loading, + Loaded, + Unloading, + Closing, + + WillReload, // While unloading, a task is waiting to load again + CanReload, // Equivalent to Opened, but won't Close + WillReopen, // While closing, a task is waiting to open again + CanReopen // Equivalent to Closed, but won't Delete + }; + EState State; + + enum class WWISEFILEHANDLER_API EResult + { + /** + * @brief The File State operation is completed. It doesn't tell whether the result is successful or not. + */ + Done, + + /** + * @brief The File State operation got deferred at a later time. + */ + Deferred + }; + + using FIncrementCountCallback = TUniqueFunction; + void IncrementCountAsync(EWwiseFileStateOperationOrigin InOperationOrigin, FIncrementCountCallback&& InCallback); + + using FDecrementCountCallback = TUniqueFunction; + using FDeleteFileStateFunction = TUniqueFunction; + void DecrementCountAsync(EWwiseFileStateOperationOrigin InOperationOrigin, FDeleteFileStateFunction&& InDeleteState, FDecrementCountCallback&& InCallback); + + virtual bool CanDelete() const; + virtual const TCHAR* GetManagingTypeName() const { return TEXT("Invalid"); } + virtual uint32 GetShortId() const { return 0; } + + virtual FWwiseStreamableFileStateInfo* GetStreamableFileStateInfo() { return nullptr; } + virtual const FWwiseStreamableFileStateInfo* GetStreamableFileStateInfo() const { return nullptr; } + bool IsStreamedState() const { return GetStreamableFileStateInfo() != nullptr; } + + FWwiseFileState(FWwiseFileState const&) = delete; + FWwiseFileState& operator=(FWwiseFileState const&) = delete; + FWwiseFileState(FWwiseFileState&&) = delete; + FWwiseFileState& operator=(FWwiseFileState&&) = delete; + +protected: + FWwiseFileState(); + void Term(); + + virtual void IncrementCount(EWwiseFileStateOperationOrigin InOperationOrigin, FIncrementCountCallback&& InCallback); + virtual void IncrementCountOpen(EWwiseFileStateOperationOrigin InOperationOrigin, FIncrementCountCallback&& InCallback); + virtual void IncrementCountLoad(EWwiseFileStateOperationOrigin InOperationOrigin, FIncrementCountCallback&& InCallback); + virtual void IncrementCountDone(EWwiseFileStateOperationOrigin InOperationOrigin, FIncrementCountCallback&& InCallback); + + virtual void DecrementCount(EWwiseFileStateOperationOrigin InOperationOrigin, FDeleteFileStateFunction&& InDeleteState, FDecrementCountCallback&& InCallback); + virtual void DecrementCountUnload(EWwiseFileStateOperationOrigin InOperationOrigin, FDeleteFileStateFunction&& InDeleteState, FDecrementCountCallback&& InCallback); + virtual void DecrementCountClose(EWwiseFileStateOperationOrigin InOperationOrigin, FDeleteFileStateFunction&& InDeleteState, FDecrementCountCallback&& InCallback); + virtual void DecrementCountDone(EWwiseFileStateOperationOrigin InOperationOrigin, FDeleteFileStateFunction&& InDeleteState, FDecrementCountCallback&& InCallback); + + using FOpenFileCallback = TUniqueFunction; + using FLoadInSoundEngineCallback = TUniqueFunction; + virtual void IncrementLoadCount(EWwiseFileStateOperationOrigin InOperationOrigin); + + virtual bool CanOpenFile() const; + virtual void OpenFile(FOpenFileCallback&& InCallback) { OpenFileFailed(MoveTemp(InCallback)); } + void OpenFileSucceeded(FOpenFileCallback&& InCallback); + void OpenFileFailed(FOpenFileCallback&& InCallback); + + virtual bool CanLoadInSoundEngine() const; + virtual void LoadInSoundEngine(FLoadInSoundEngineCallback&& InCallback) { LoadInSoundEngineFailed(MoveTemp(InCallback)); } + void LoadInSoundEngineSucceeded(FLoadInSoundEngineCallback&& InCallback); + void LoadInSoundEngineFailed(FLoadInSoundEngineCallback&& InCallback); + + using FUnloadFromSoundEngineCallback = TUniqueFunction; + using FCloseFileCallback = TUniqueFunction; + virtual void DecrementLoadCount(EWwiseFileStateOperationOrigin InOperationOrigin); + + virtual bool CanUnloadFromSoundEngine() const; + virtual void UnloadFromSoundEngine(FUnloadFromSoundEngineCallback&& InCallback) { UnloadFromSoundEngineDone(MoveTemp(InCallback)); } + void UnloadFromSoundEngineDone(FUnloadFromSoundEngineCallback&& InCallback); + void UnloadFromSoundEngineToClosedFile(FUnloadFromSoundEngineCallback&& InCallback); + void UnloadFromSoundEngineDefer(FUnloadFromSoundEngineCallback&& InCallback); + + virtual bool CanCloseFile() const; + virtual void CloseFile(FCloseFileCallback&& InCallback) { CloseFileDone(MoveTemp(InCallback)); } + void CloseFileDone(FCloseFileCallback&& InCallback); + void CloseFileDefer(FCloseFileCallback&& InCallback); +}; + +using FWwiseFileStateSharedPtr = TSharedPtr; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseFileStateTools.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseFileStateTools.h new file mode 100644 index 0000000..63fce10 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseFileStateTools.h @@ -0,0 +1,46 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkInclude.h" + +class FString; +class IMappedFileRegion; +class IMappedFileHandle; + +class WWISEFILEHANDLER_API FWwiseFileStateTools +{ +public: + virtual ~FWwiseFileStateTools() {} + +protected: + static uint8* AllocateMemory(int64 InMemorySize, bool bInDeviceMemory, int32 InMemoryAlignment, bool bInEnforceMemoryRequirements); + static void DeallocateMemory(const uint8* InMemoryPtr, int64 InMemorySize, bool bInDeviceMemory, int32 InMemoryAlignment, bool bInEnforceMemoryRequirements); + + static bool GetMemoryMapped(IMappedFileHandle*& OutMappedHandle, IMappedFileRegion*& OutMappedRegion, int64& OutSize, + const FString& InFilePathname, int32 InMemoryAlignment); + static bool GetMemoryMapped(IMappedFileHandle*& OutMappedHandle, int64& OutSize, + const FString& InFilePathname, int32 InMemoryAlignment); + static bool GetMemoryMappedRegion(IMappedFileRegion*& OutMappedRegion, IMappedFileHandle& InMappedHandle); + static void UnmapRegion(IMappedFileRegion& InMappedRegion); + static void UnmapHandle(IMappedFileHandle& InMappedHandle); + + static bool GetFileToPtr(const uint8*& OutPtr, int64& OutSize, + const FString& InFilePathname, bool bInDeviceMemory, int32 InMemoryAlignment, bool bInEnforceMemoryRequirements, + int64 ReadFirstBytes = -1); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseIOHook.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseIOHook.h new file mode 100644 index 0000000..acc761b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseIOHook.h @@ -0,0 +1,58 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseLowLevelIOHook.h" +#include "Wwise/WwiseFileLocationResolver.h" + +class WWISEFILEHANDLER_API FWwiseIOHook +{ +public: + virtual ~FWwiseIOHook() {} + + virtual bool Init(const AkDeviceSettings& InDeviceSettings); + virtual void Term(); + virtual AK::StreamMgr::IAkLowLevelIOHook* GetIOHook() = 0; + virtual AK::StreamMgr::IAkFileLocationResolver* GetLocationResolver() = 0; + +protected: + FWwiseIOHook() : + StreamingDevice(AK_INVALID_DEVICE_ID) + {} + + AkDeviceID StreamingDevice; +}; + +class WWISEFILEHANDLER_API FWwiseDefaultIOHook : + public FWwiseIOHook, + public FWwiseLowLevelIOHook, + public FWwiseFileLocationResolver +{ +public: + AK::StreamMgr::IAkLowLevelIOHook* GetIOHook() final + { + return this; + } + AK::StreamMgr::IAkFileLocationResolver* GetLocationResolver() final + { + return this; + } + +protected: + FWwiseDefaultIOHook() {} +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseIOHookImpl.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseIOHookImpl.h new file mode 100644 index 0000000..f3872f2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseIOHookImpl.h @@ -0,0 +1,122 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseIOHook.h" +#include "Wwise/WwiseExecutionQueue.h" + +#include "Templates/Atomic.h" + +class IWwiseStreamingManagerHooks; + +class WWISEFILEHANDLER_API FWwiseIOHookImpl : public FWwiseDefaultIOHook +{ +public: + FWwiseIOHookImpl(); + bool Init(const AkDeviceSettings& InDeviceSettings) override; + + /** + * Opens a file, asynchronously. + * + * @param io_pOpenData File open information (name, flags, etc). + * Also contain the callback to call when the open operation is completed, + * and the AkFileDesc to fill out. + */ + virtual void BatchOpen( + AkUInt32 in_uNumFiles, // Number of files to open + AkAsyncFileOpenData** in_ppItems // File open information (name, flags, etc) + // Also contain the callback to call when the open operation is completed, + // and the AkFileDesc to fill out. + ); + + /** + * @brief Reads multiple data from multiple files (asynchronous). + * + * @param in_uNumTransfers Number of transfers to process + * @param in_pTransferItems List of transfer items to process + */ + virtual void BatchRead( + AkUInt32 in_uNumTransfers, + BatchIoTransferItem* in_pTransferItems + ) override; + + /** + * @brief Write multiple data from multiple files (asynchronous). + * + * @param in_uNumTransfers Number of transfers to process + * @param in_pTransferItems List of transfer items to process + */ + virtual void BatchWrite( + AkUInt32 in_uNumTransfers, + BatchIoTransferItem* in_pTransferItems + ) override; + + /** + * @brief Cancel IO from multiple files (asynchronous). + * + * @param in_uNumTransfers Number of transfers to process + * @param in_pTransferItems List of transfer items to process + */ + virtual void BatchCancel( + AkUInt32 in_uNumTransfers, + BatchIoTransferItem* in_pTransferItems, + bool** io_ppbCancelAllTransfersForThisFile + ) override; + + /** + * Cleans up a file. + * + * @param in_fileDesc File descriptor. + * @return AK_Success if operation was successful, error code otherwise + */ + AKRESULT Close(AkFileDesc* in_pFileDesc) override; + + // Returns the block size for the file or its storage device. + AkUInt32 GetBlockSize(AkFileDesc& in_fileDesc) override; + + // Returns a description for the streaming device above this low-level hook. + void GetDeviceDesc(AkDeviceDesc& out_deviceDesc) override; + + // Returns custom profiling data: 1 if file opens are asynchronous, 0 otherwise. + AkUInt32 GetDeviceData() override; + +protected: + FWwiseExecutionQueue BatchExecutionQueue; + AkDeviceID m_deviceID = AK_INVALID_DEVICE_ID; + +#ifndef AK_OPTIMIZED + TAtomic CurrentDeviceData; + TAtomic MaxDeviceData; +#endif + + virtual IWwiseStreamingManagerHooks* GetStreamingHooks(const AkFileSystemFlags& InFileSystemFlag); + + virtual AKRESULT OpenFileForWrite(AkAsyncFileOpenData *io_pOpenData); + + virtual AKRESULT Open(AkAsyncFileOpenData* io_pOpenData); + + virtual AKRESULT Read( + AkFileDesc& in_fileDesc, + const AkIoHeuristics& in_heuristics, + AkAsyncIOTransferInfo& io_transferInfo); + + virtual AKRESULT Write( + AkFileDesc& in_fileDesc, + const AkIoHeuristics& in_heuristics, + AkAsyncIOTransferInfo& io_transferInfo); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseMediaFileState.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseMediaFileState.h new file mode 100644 index 0000000..7bf4b37 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseMediaFileState.h @@ -0,0 +1,71 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseFileState.h" +#include "Wwise/WwiseStreamableFileStateInfo.h" +#include "Wwise/CookedData/WwiseMediaCookedData.h" + +class WWISEFILEHANDLER_API FWwiseMediaFileState : public FWwiseFileState, public FWwiseMediaCookedData +{ +public: + const FString RootPath; + +protected: + FWwiseMediaFileState(const FWwiseMediaCookedData& InCookedData, const FString& InRootPath); + +public: + ~FWwiseMediaFileState() override; + const TCHAR* GetManagingTypeName() const override final { return TEXT("Media"); } + uint32 GetShortId() const override final { return MediaId; } +}; + +class WWISEFILEHANDLER_API FWwiseInMemoryMediaFileState : public FWwiseMediaFileState, public AkSourceSettings +{ +public: + FWwiseInMemoryMediaFileState(const FWwiseMediaCookedData& InCookedData, const FString& InRootPath); + ~FWwiseInMemoryMediaFileState() override { Term(); } + + void OpenFile(FOpenFileCallback&& InCallback) override; + void LoadInSoundEngine(FLoadInSoundEngineCallback&& InCallback) override; + void UnloadFromSoundEngine(FUnloadFromSoundEngineCallback&& InCallback) override; + void CloseFile(FCloseFileCallback&& InCallback) override; +}; + +class WWISEFILEHANDLER_API FWwiseStreamedMediaFileState : public FWwiseMediaFileState, protected FWwiseStreamableFileStateInfo, protected AkSourceSettings +{ +public: + const uint32 StreamingGranularity; + + FWwiseFileCacheHandle* StreamedFile; + + FWwiseStreamedMediaFileState(const FWwiseMediaCookedData& InCookedData, const FString& InRootPath, uint32 InStreamingGranularity); + ~FWwiseStreamedMediaFileState() override { Term(); } + + void CloseStreaming() override; + FWwiseStreamableFileStateInfo* GetStreamableFileStateInfo() override { return this; } + const FWwiseStreamableFileStateInfo* GetStreamableFileStateInfo() const override { return this; } + + void OpenFile(FOpenFileCallback&& InCallback) override; + void LoadInSoundEngine(FLoadInSoundEngineCallback&& InCallback) override; + void UnloadFromSoundEngine(FUnloadFromSoundEngineCallback&& InCallback) override; + void CloseFile(FCloseFileCallback&& InCallback) override; + + bool CanProcessFileOp() const override; + AKRESULT ProcessRead(AkFileDesc& InFileDesc, const AkIoHeuristics& InHeuristics, AkAsyncIOTransferInfo& OutTransferInfo, FWwiseAkFileOperationDone&& InFileOpDoneCallback) override; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseMediaManager.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseMediaManager.h new file mode 100644 index 0000000..e8098e7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseMediaManager.h @@ -0,0 +1,44 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkInclude.h" +#include "Wwise/WwiseFileHandlerModule.h" +#include "Wwise/WwiseStreamableFileHandler.h" + +struct FWwiseMediaCookedData; + +class IWwiseMediaManager : public IWwiseStreamableFileHandler +{ +public: + inline static IWwiseMediaManager* Get() + { + if (auto* Module = IWwiseFileHandlerModule::GetModule()) + { + return Module->GetMediaManager(); + } + return nullptr; + } + + using FLoadMediaCallback = TUniqueFunction; + using FUnloadMediaCallback = TUniqueFunction; + + virtual void LoadMedia(const FWwiseMediaCookedData& InMediaCookedData, const FString& InRootPath, FLoadMediaCallback&& InCallback) = 0; + virtual void UnloadMedia(const FWwiseMediaCookedData& InMediaCookedData, const FString& InRootPath, FUnloadMediaCallback&& InCallback) = 0; + virtual void SetGranularity(AkUInt32 Uint32) = 0; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseMediaManagerImpl.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseMediaManagerImpl.h new file mode 100644 index 0000000..1ba0e5a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseMediaManagerImpl.h @@ -0,0 +1,42 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkInclude.h" +#include "Wwise/WwiseMediaManager.h" +#include "Wwise/WwiseFileHandlerBase.h" + +class WWISEFILEHANDLER_API FWwiseMediaManagerImpl : public IWwiseMediaManager, public FWwiseFileHandlerBase +{ +public: + FWwiseMediaManagerImpl(); + ~FWwiseMediaManagerImpl() override; + + const TCHAR* GetManagingTypeName() const override { return TEXT("Media"); } + + void LoadMedia(const FWwiseMediaCookedData& InMediaCookedData, const FString& InRootPath, FLoadMediaCallback&& InCallback) override; + void UnloadMedia(const FWwiseMediaCookedData& InMediaCookedData, const FString& InRootPath, FUnloadMediaCallback&& InCallback) override; + void SetGranularity(AkUInt32 InStreamingGranularity) override; + + IWwiseStreamingManagerHooks& GetStreamingHooks() final { return *this; } + +protected: + uint32 StreamingGranularity; + + virtual FWwiseFileStateSharedPtr CreateOp(const FWwiseMediaCookedData& InMediaCookedData, const FString& InRootPath); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseSoundBankFileState.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseSoundBankFileState.h new file mode 100644 index 0000000..d40421d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseSoundBankFileState.h @@ -0,0 +1,112 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseSoundBankManager.h" +#include "Wwise/CookedData/WwiseSoundBankCookedData.h" +#include "Wwise/WwiseFileState.h" + +class WWISEFILEHANDLER_API FWwiseSoundBankFileState : public FWwiseFileState, public FWwiseSoundBankCookedData +{ +public: + const FString RootPath; + +protected: + FWwiseSoundBankFileState(const FWwiseSoundBankCookedData& InCookedData, const FString& InRootPath); + +public: + ~FWwiseSoundBankFileState() override; + + const TCHAR* GetManagingTypeName() const override final { return TEXT("SoundBank"); } + uint32 GetShortId() const override final { return SoundBankId; } +}; + +class WWISEFILEHANDLER_API FWwiseInMemorySoundBankFileState : public FWwiseSoundBankFileState +{ +public: + const uint8* Ptr; + int64 FileSize; + IMappedFileHandle* MappedHandle; + IMappedFileRegion* MappedRegion; + + FWwiseInMemorySoundBankFileState(const FWwiseSoundBankCookedData& InCookedData, const FString& InRootPath); + ~FWwiseInMemorySoundBankFileState() override { Term(); } + + bool LoadAsMemoryView() const; + + void OpenFile(FOpenFileCallback&& InCallback) override; + void LoadInSoundEngine(FLoadInSoundEngineCallback&& InCallback) override; + void UnloadFromSoundEngine(FUnloadFromSoundEngineCallback&& InCallback) override; + bool CanCloseFile() const override; + void CloseFile(FCloseFileCallback&& InCallback) override; + +private: + void FreeMemoryIfNeeded(); + + struct BankLoadCookie + { + FWwiseInMemorySoundBankFileState* BankFileState; + FLoadInSoundEngineCallback Callback; + + BankLoadCookie(FWwiseInMemorySoundBankFileState* InBankFileState) + : BankFileState(InBankFileState) + {} + + BankLoadCookie(BankLoadCookie* InOther) + { + if(InOther) + { + BankFileState = InOther->BankFileState; + Callback = MoveTemp(InOther->Callback); + } + } + }; + + struct BankUnloadCookie + { + FWwiseInMemorySoundBankFileState* BankFileState; + FUnloadFromSoundEngineCallback Callback; + + BankUnloadCookie(FWwiseInMemorySoundBankFileState* InBankFileState) + : BankFileState(InBankFileState) + {} + + BankUnloadCookie(BankUnloadCookie* InOther) + { + if (InOther) + { + BankFileState = InOther->BankFileState; + Callback = MoveTemp(InOther->Callback); + } + } + }; + + static void BankLoadCallback( + AkUInt32 InBankID, + const void* InMemoryBankPtr, + AKRESULT InLoadResult, + void* InCookie + ); + + static void BankUnloadCallback( + AkUInt32 InBankID, + const void* InMemoryBankPtr, + AKRESULT InUnloadResult, + void* InCookie + ); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseSoundBankManager.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseSoundBankManager.h new file mode 100644 index 0000000..be16e5c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseSoundBankManager.h @@ -0,0 +1,43 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseFileHandlerModule.h" +#include "Wwise/WwiseStreamableFileHandler.h" + +struct FWwiseSoundBankCookedData; + +class IWwiseSoundBankManager : public IWwiseStreamableFileHandler +{ +public: + inline static IWwiseSoundBankManager* Get() + { + if (auto* Module = IWwiseFileHandlerModule::GetModule()) + { + return Module->GetSoundBankManager(); + } + return nullptr; + } + + using FLoadSoundBankCallback = TUniqueFunction; + using FUnloadSoundBankCallback = TUniqueFunction; + + virtual void LoadSoundBank(const FWwiseSoundBankCookedData& InSoundBankCookedData, const FString& InRootPath, FLoadSoundBankCallback&& InCallback) = 0; + virtual void UnloadSoundBank(const FWwiseSoundBankCookedData& InSoundBankCookedData, const FString& InRootPath, FUnloadSoundBankCallback&& InCallback) = 0; + virtual void SetGranularity(AkUInt32 Uint32) = 0; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseSoundBankManagerImpl.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseSoundBankManagerImpl.h new file mode 100644 index 0000000..15578cb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseSoundBankManagerImpl.h @@ -0,0 +1,42 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseSoundBankManager.h" +#include "Wwise/CookedData/WwiseSoundBankCookedData.h" +#include "Wwise/WwiseFileState.h" +#include "Wwise/WwiseFileHandlerBase.h" + +class WWISEFILEHANDLER_API FWwiseSoundBankManagerImpl : public IWwiseSoundBankManager, public FWwiseFileHandlerBase +{ +public: + FWwiseSoundBankManagerImpl(); + ~FWwiseSoundBankManagerImpl() override; + + const TCHAR* GetManagingTypeName() const override { return TEXT("SoundBank"); } + void LoadSoundBank(const FWwiseSoundBankCookedData& InSoundBankCookedData, const FString& InRootPath, FLoadSoundBankCallback&& InCallback) override; + void UnloadSoundBank(const FWwiseSoundBankCookedData& InSoundBankCookedData, const FString& InRootPath, FUnloadSoundBankCallback&& InCallback) override; + void SetGranularity(AkUInt32 InStreamingGranularity) override; + + IWwiseStreamingManagerHooks& GetStreamingHooks() final { return *this; } + +protected: + uint32 StreamingGranularity; + + virtual FWwiseFileStateSharedPtr CreateOp(const FWwiseSoundBankCookedData& InSoundBankCookedData, const FString& InRootPath); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseStreamableFileHandler.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseStreamableFileHandler.h new file mode 100644 index 0000000..e9f4885 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseStreamableFileHandler.h @@ -0,0 +1,29 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +class IWwiseStreamingManagerHooks; + +class IWwiseStreamableFileHandler +{ +public: + virtual IWwiseStreamingManagerHooks& GetStreamingHooks() = 0; + +protected: + virtual ~IWwiseStreamableFileHandler() {} +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseStreamableFileStateInfo.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseStreamableFileStateInfo.h new file mode 100644 index 0000000..96a0b6c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseStreamableFileStateInfo.h @@ -0,0 +1,63 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseFileStateTools.h" +#include "Wwise/Stats/FileHandler.h" + +#include "AkInclude.h" +#include "WwiseDefines.h" +#include "Wwise/WwiseFileCache.h" + +class WWISEFILEHANDLER_API FWwiseStreamableFileStateInfo: protected AkFileDesc +{ +public: + static FWwiseStreamableFileStateInfo* GetFromFileDesc(AkFileDesc& InFileDesc) + { + return static_cast(static_cast(InFileDesc.pCustomParam)); + } + + AkFileDesc* GetFileDesc() + { + return this; + } + + virtual ~FWwiseStreamableFileStateInfo() {} + virtual bool CanProcessFileOp() const + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("Cannot process read on a non-streaming asset")); + return false; + } + virtual AKRESULT ProcessRead(AkFileDesc& InFileDesc, const AkIoHeuristics& InHeuristics, AkAsyncIOTransferInfo& OutTransferInfo, FWwiseAkFileOperationDone&& InFileOpDoneCallback) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("Cannot process read on a non-streaming asset")); + return AK_Fail; + } + virtual AKRESULT ProcessWrite(AkFileDesc& InFileDesc, const AkIoHeuristics& InHeuristics, AkAsyncIOTransferInfo& OutTransferInfo, FWwiseAkFileOperationDone&& InFileOpDoneCallback) + { + UE_LOG(LogWwiseFileHandler, Error, TEXT("Cannot process write on a non-writable asset")); + return AK_Fail; + } + virtual void CloseStreaming() {} + +protected: + FWwiseStreamableFileStateInfo() + { + FMemory::Memset(*static_cast(this), 0); + } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseStreamingManagerHooks.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseStreamingManagerHooks.h new file mode 100644 index 0000000..8a2b66e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseStreamingManagerHooks.h @@ -0,0 +1,33 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "AkInclude.h" + +class FWwiseFileState; + +class WWISEFILEHANDLER_API IWwiseStreamingManagerHooks +{ +public: + virtual void OpenStreaming(AkAsyncFileOpenData* io_pOpenData) = 0; + virtual void CloseStreaming(uint32 InShortId, FWwiseFileState& InFileState) = 0; + +protected: + virtual ~IWwiseStreamingManagerHooks() {} +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseWriteFileState.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseWriteFileState.h new file mode 100644 index 0000000..056e568 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/Public/Wwise/WwiseWriteFileState.h @@ -0,0 +1,45 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseFileState.h" +#include "Wwise/WwiseStreamableFileStateInfo.h" + +class WWISEFILEHANDLER_API FWwiseWriteFileState : public FWwiseFileState, protected FWwiseStreamableFileStateInfo +{ +public: + FWwiseWriteFileState(IFileHandle* InFileHandle, const FString& InFilePathName); + + void CloseStreaming() override; + + bool CanProcessFileOp() const override; + AKRESULT ProcessWrite(AkFileDesc& InFileDesc, const AkIoHeuristics& InHeuristics, AkAsyncIOTransferInfo& OutTransferInfo, FWwiseAkFileOperationDone&& InFileOpDoneCallback) override; + using FWwiseStreamableFileStateInfo::GetFileDesc; + +protected: + IFileHandle* FileHandle; + FString FilePathName; + + const TCHAR* GetManagingTypeName() const override { return TEXT("Write"); } + uint32 GetShortId() const override { return 0; } + + void OpenFile(FOpenFileCallback&& InCallback) override { OpenFileSucceeded(MoveTemp(InCallback)); } + void LoadInSoundEngine(FLoadInSoundEngineCallback&& InCallback) override { LoadInSoundEngineSucceeded(MoveTemp(InCallback)); } + void UnloadFromSoundEngine(FUnloadFromSoundEngineCallback&& InCallback) override { UnloadFromSoundEngineDone(MoveTemp(InCallback)); } + void CloseFile(FCloseFileCallback&& InCallback) override { CloseFileDone(MoveTemp(InCallback)); } +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/WwiseFileHandler.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/WwiseFileHandler.Build.cs new file mode 100644 index 0000000..3dd7156 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseFileHandler/WwiseFileHandler.Build.cs @@ -0,0 +1,37 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; + +public class WwiseFileHandler : ModuleRules +{ + public WwiseFileHandler(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange(new string[] { + "WwiseConcurrency", + "WwiseSoundEngine" + }); + + PrivateDependencyModuleNames.AddRange(new string[] { + "Core", + "CoreUObject", + "Engine" + }); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataAcousticTexture.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataAcousticTexture.cpp new file mode 100644 index 0000000..091b4de --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataAcousticTexture.cpp @@ -0,0 +1,37 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataAcousticTexture.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" + +FWwiseMetadataAcousticTexture::FWwiseMetadataAcousticTexture(FWwiseMetadataLoader& Loader) : + FWwiseMetadataBasicReference(Loader) +{ + Loader.GetPropertyArray(this, FloatProperties); + Loader.LogParsed(TEXT("AcousticTexture"), Id, Name); +} + +const TMap FWwiseMetadataAcousticTexture::FloatProperties = FWwiseMetadataAcousticTexture::FillFloatProperties(); +const TMap FWwiseMetadataAcousticTexture::FillFloatProperties() +{ + TMap Result; + Result.Add(FName(TEXT("AbsorptionLow")), offsetof(FWwiseMetadataAcousticTexture, AbsorptionLow)); + Result.Add(FName(TEXT("AbsorptionMidLow")), offsetof(FWwiseMetadataAcousticTexture, AbsorptionMidLow)); + Result.Add(FName(TEXT("AbsorptionMidHigh")), offsetof(FWwiseMetadataAcousticTexture, AbsorptionMidHigh)); + Result.Add(FName(TEXT("AbsorptionHigh")), offsetof(FWwiseMetadataAcousticTexture, AbsorptionHigh)); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataActionEntries.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataActionEntries.cpp new file mode 100644 index 0000000..262e51d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataActionEntries.cpp @@ -0,0 +1,43 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataActionEntries.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" + +FWwiseMetadataActionPostEventEntry::FWwiseMetadataActionPostEventEntry(FWwiseMetadataLoader& Loader) : + FWwiseMetadataBasicReference(Loader) +{ + Loader.LogParsed(TEXT("ActionPostEventEntry"), Id, Name); +} + +FWwiseMetadataActionSetStateEntry::FWwiseMetadataActionSetStateEntry(FWwiseMetadataLoader& Loader) : + FWwiseMetadataGroupValueReference(Loader) +{ + Loader.LogParsed(TEXT("ActionSetStateEntry"), Id, Name); +} + +FWwiseMetadataActionSetSwitchEntry::FWwiseMetadataActionSetSwitchEntry(FWwiseMetadataLoader& Loader) : + FWwiseMetadataGroupValueReference(Loader) +{ + Loader.LogParsed(TEXT("ActionSetSwitchEntry"), Id, Name); +} + +FWwiseMetadataActionTriggerEntry::FWwiseMetadataActionTriggerEntry(FWwiseMetadataLoader& Loader) : + FWwiseMetadataBasicReference(Loader) +{ + Loader.LogParsed(TEXT("ActionTriggerEntry"), Id, Name); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataBasicReference.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataBasicReference.cpp new file mode 100644 index 0000000..5c8f1ba --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataBasicReference.cpp @@ -0,0 +1,34 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataBasicReference.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" +#include "Wwise/Stats/ProjectDatabase.h" + +FWwiseMetadataBasicReference::FWwiseMetadataBasicReference() +{ + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Using default FWwiseMetadataBasicReference")); +} + +FWwiseMetadataBasicReference::FWwiseMetadataBasicReference(FWwiseMetadataLoader& Loader) : + Id(Loader.GetUint32(this, TEXT("Id"))), + Name(Loader.GetString(this, TEXT("Name"))), + ObjectPath(Loader.GetString(this, TEXT("ObjectPath"))), + GUID(Loader.GetGuid(this, TEXT("GUID"))) +{ + Loader.LogParsed(TEXT("BasicReference"), Id, Name); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataBus.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataBus.cpp new file mode 100644 index 0000000..15758a4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataBus.cpp @@ -0,0 +1,34 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataBus.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" +#include "Wwise/Metadata/WwiseMetadataPluginGroup.h" + +FWwiseMetadataBusReference::FWwiseMetadataBusReference(FWwiseMetadataLoader& Loader) : + FWwiseMetadataBasicReference(Loader) +{ + Loader.LogParsed(TEXT("BusReference"), Id, Name); +} + +FWwiseMetadataBus::FWwiseMetadataBus(FWwiseMetadataLoader& Loader) : + FWwiseMetadataBusReference(Loader), + PluginRefs(Loader.GetObjectPtr(this, TEXT("PluginRefs"))), + AuxBusRefs(Loader.GetArray(this, TEXT("AuxBusRefs"))) +{ + Loader.LogParsed(TEXT("Bus"), Id, Name); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataDialogue.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataDialogue.cpp new file mode 100644 index 0000000..d579e3e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataDialogue.cpp @@ -0,0 +1,38 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataDialogue.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" + +FWwiseMetadataDialogueEventReference::FWwiseMetadataDialogueEventReference(FWwiseMetadataLoader& Loader) : + FWwiseMetadataBasicReference(Loader) +{ + Loader.LogParsed(TEXT("DialogueEventReference"), Id, Name); +} + +FWwiseMetadataDialogueArgument::FWwiseMetadataDialogueArgument(FWwiseMetadataLoader& Loader) : + FWwiseMetadataBasicReference(Loader) +{ + Loader.LogParsed(TEXT("DialogueArgument"), Id, Name); +} + +FWwiseMetadataDialogueEvent::FWwiseMetadataDialogueEvent(FWwiseMetadataLoader& Loader) : + FWwiseMetadataDialogueEventReference(Loader), + Arguments(Loader.GetArray(this, TEXT("Arguments"))) +{ + Loader.LogParsed(TEXT("DialogueEvent"), Id, Name); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataEvent.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataEvent.cpp new file mode 100644 index 0000000..09d2df6 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataEvent.cpp @@ -0,0 +1,85 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataEvent.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" +#include "Wwise/Metadata/WwiseMetadataPluginGroup.h" +#include "Wwise/Stats/ProjectDatabase.h" + +FWwiseMetadataEventReference::FWwiseMetadataEventReference(FWwiseMetadataLoader& Loader) : + FWwiseMetadataBasicReference(Loader), + MaxAttenuation(Loader.GetUint32(this, TEXT("MaxAttenuation"), EWwiseRequiredMetadata::Optional)), + DurationType(DurationTypeFromString(Loader.GetString(this, TEXT("DurationType")))), + DurationMin(Loader.GetFloat(this, TEXT("DurationMin"), EWwiseRequiredMetadata::Optional)), + DurationMax(Loader.GetFloat(this, TEXT("DurationMax"), EWwiseRequiredMetadata::Optional)) +{ + IncLoadedSize(sizeof(EWwiseMetadataEventDurationType)); + Loader.LogParsed(TEXT("EventReference"), Id, Name); +} + +EWwiseMetadataEventDurationType FWwiseMetadataEventReference::DurationTypeFromString(const FName& TypeString) +{ + if (TypeString == "OneShot") + { + return EWwiseMetadataEventDurationType::OneShot; + } + else if (TypeString == "Infinite") + { + return EWwiseMetadataEventDurationType::Infinite; + } + else if (TypeString == "Mixed") + { + return EWwiseMetadataEventDurationType::Mixed; + } + else if (!(TypeString == "Unknown")) + { + UE_LOG(LogWwiseProjectDatabase, Warning, TEXT("FWwiseMetadataEventReference: Unknown DurationType: %s"), *TypeString.ToString()); + } + return EWwiseMetadataEventDurationType::Unknown; +} + +FWwiseMetadataEvent::FWwiseMetadataEvent(FWwiseMetadataLoader& Loader) : + FWwiseMetadataEventReference(Loader), + MediaRefs(Loader.GetArray(this, TEXT("MediaRefs"))), + ExternalSourceRefs(Loader.GetArray(this, TEXT("ExternalSourceRefs"))), + PluginRefs(Loader.GetObjectPtr(this, TEXT("PluginRefs"))), + AuxBusRefs(Loader.GetArray(this, TEXT("AuxBusRefs"))), + SwitchContainers(Loader.GetArray(this, TEXT("SwitchContainers"))), + ActionPostEvent(Loader.GetArray(this, TEXT("ActionPostEvent"))), + ActionSetState(Loader.GetArray(this, TEXT("ActionSetState"))), + ActionSetSwitch(Loader.GetArray(this, TEXT("ActionSetSwitch"))), + ActionTrigger(Loader.GetArray(this, TEXT("ActionTrigger"))) +{ + Loader.LogParsed(TEXT("Event"), Id, Name); +} + +bool FWwiseMetadataEvent::IsMandatory() const +{ + return + (ActionPostEvent.Num() > 0) + || (ActionSetState.Num() > 0) + || (ActionSetSwitch.Num() > 0) + || (ActionTrigger.Num() > 0) + || (AuxBusRefs.Num() > 0) + || (ExternalSourceRefs.Num() > 0) + || (MediaRefs.Num() > 0) + || (PluginRefs && ( + (PluginRefs->Custom.Num() > 0) + || (PluginRefs->ShareSets.Num() > 0) + || (PluginRefs->AudioDevices.Num() > 0))) + || (SwitchContainers.Num() == 0); +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataExternalSource.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataExternalSource.cpp new file mode 100644 index 0000000..2156869 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataExternalSource.cpp @@ -0,0 +1,34 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataExternalSource.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" + +FWwiseMetadataExternalSourceReference::FWwiseMetadataExternalSourceReference(FWwiseMetadataLoader& Loader) : + Cookie(Loader.GetUint32(this, TEXT("Cookie"))) +{ + Loader.LogParsed(TEXT("ExternalSourceReference"), Cookie); +} + +FWwiseMetadataExternalSource::FWwiseMetadataExternalSource(FWwiseMetadataLoader& Loader) : + FWwiseMetadataExternalSourceReference(Loader), + Name(Loader.GetString(this, TEXT("Name"))), + ObjectPath(Loader.GetString(this, TEXT("ObjectPath"))), + GUID(Loader.GetGuid(this, TEXT("GUID"))) +{ + Loader.LogParsed(TEXT("ExternalSource"), Cookie, Name); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataGameParameter.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataGameParameter.cpp new file mode 100644 index 0000000..a971f56 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataGameParameter.cpp @@ -0,0 +1,31 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataGameParameter.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" + +FWwiseMetadataGameParameterReference::FWwiseMetadataGameParameterReference(FWwiseMetadataLoader& Loader) : + Id(Loader.GetUint32(this, TEXT("Id"))) +{ + Loader.LogParsed(TEXT("GameParameterReference"), Id); +} + +FWwiseMetadataGameParameter::FWwiseMetadataGameParameter(FWwiseMetadataLoader& Loader) : + FWwiseMetadataBasicReference(Loader) +{ + Loader.LogParsed(TEXT("GameParameter"), Id, Name); +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataGroupValueReference.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataGroupValueReference.cpp new file mode 100644 index 0000000..16e1afc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataGroupValueReference.cpp @@ -0,0 +1,32 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataGroupValueReference.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" +#include "Wwise/Stats/ProjectDatabase.h" + +FWwiseMetadataGroupValueReference::FWwiseMetadataGroupValueReference() +{ + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Using default FWwiseMetadataGroupValueReference")); +} + +FWwiseMetadataGroupValueReference::FWwiseMetadataGroupValueReference(FWwiseMetadataLoader& Loader) : + FWwiseMetadataBasicReference(Loader), + GroupId(Loader.GetUint32(this, TEXT("GroupId"))) +{ + Loader.LogParsed(TEXT("GroupValueReference"), GroupId, Name); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataLanguage.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataLanguage.cpp new file mode 100644 index 0000000..628cd32 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataLanguage.cpp @@ -0,0 +1,35 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataLanguage.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" + +FWwiseMetadataLanguageAttributes::FWwiseMetadataLanguageAttributes(FWwiseMetadataLoader& Loader) : + Name(Loader.GetString(this, TEXT("Name"))), + Id(Loader.GetUint32(this, TEXT("Id"))), + GUID(Loader.GetGuid(this, TEXT("GUID"))), + bDefault(Loader.GetBool(this, TEXT("Default"), EWwiseRequiredMetadata::Optional)), + bUseAsStandIn(Loader.GetBool(this, TEXT("UseAsStandIn"), EWwiseRequiredMetadata::Optional)) +{ + Loader.LogParsed(TEXT("LanguageAttributes"), Id, Name); +} + +FWwiseMetadataLanguage::FWwiseMetadataLanguage(FWwiseMetadataLoader& Loader) : + FWwiseMetadataLanguageAttributes(Loader) +{ + Loader.LogParsed(TEXT("Language"), Id, Name); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataLoadable.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataLoadable.cpp new file mode 100644 index 0000000..d3b56db --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataLoadable.cpp @@ -0,0 +1,60 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataLoadable.h" +#include "Wwise/Stats/ProjectDatabase.h" + +#include "Dom/JsonObject.h" + +void FWwiseMetadataLoadable::AddRequestedValue(const FString& Type, const FString& Value) +{ + bool IsAlreadySet = false; + RequestedValues.Add(Value, &IsAlreadySet); + if (UNLIKELY(IsAlreadySet)) + { + UE_LOG(LogWwiseProjectDatabase, Fatal, TEXT("Trying to load the same %s field twice: %s"), *Type, *Value); + } +} + +void FWwiseMetadataLoadable::CheckRequestedValues(TSharedRef& JsonObject) +{ + TArray Keys; + JsonObject->Values.GetKeys(Keys); + auto Diff = TSet(Keys).Difference(RequestedValues); + for (const auto& Key : Diff) + { + UE_LOG(LogWwiseProjectDatabase, Warning, TEXT("Unknown Json field: %s"), *Key); + } +} + +void FWwiseMetadataLoadable::IncLoadedSize(size_t Size) +{ + INC_DWORD_STAT_BY(STAT_WwiseProjectDatabaseMemory, Size); + LoadedSize += Size; +} + +void FWwiseMetadataLoadable::DecLoadedSize(size_t Size) +{ + DEC_DWORD_STAT_BY(STAT_WwiseProjectDatabaseMemory, Size); + LoadedSize -= Size; +} + +void FWwiseMetadataLoadable::UnloadLoadedSize() +{ + DEC_DWORD_STAT_BY(STAT_WwiseProjectDatabaseMemory, LoadedSize); + LoadedSize = 0; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataLoader.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataLoader.cpp new file mode 100644 index 0000000..575013e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataLoader.cpp @@ -0,0 +1,173 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataLoader.h" + +#include "Wwise/Metadata/WwiseMetadataLoadable.h" +#include "Wwise/Stats/ProjectDatabase.h" + +#include "Dom/JsonObject.h" + +#include + +void FWwiseMetadataLoader::Fail(const TCHAR* FieldName) +{ + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not retrieve field %s"), FieldName); + bResult = false; +} + +void FWwiseMetadataLoader::LogParsed(const TCHAR* FieldName, const uint32 Id, const FName Name) +{ + if (bResult) + { + if (Id && !Name.IsNone()) + { + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("Parsed %s [%" PRIu32 "] %s"), FieldName, Id, *Name.ToString()); + } + else if (Id) + { + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("Parsed %s [%" PRIu32 "]"), FieldName, Id); + } + else if (!Name.IsNone()) + { + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("Parsed %s: %s"), FieldName, *Name.ToString()); + } + else + { + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("Parsed %s"), FieldName); + } + } + else + { + if (Id && !Name.IsNone()) + { + UE_LOG(LogWwiseProjectDatabase, Log, TEXT("... while parsing %s [%" PRIu32 "] %s"), FieldName, Id, *Name.ToString()); + } + else if (Id) + { + UE_LOG(LogWwiseProjectDatabase, Log, TEXT("... while parsing %s [%" PRIu32 "]"), FieldName, Id); + } + else if (!Name.IsNone()) + { + UE_LOG(LogWwiseProjectDatabase, Log, TEXT("... while parsing %s: %s"), FieldName, *Name.ToString()); + } + else + { + UE_LOG(LogWwiseProjectDatabase, Log, TEXT("... while parsing %s"), FieldName); + } + } +} + +bool FWwiseMetadataLoader::GetBool(FWwiseMetadataLoadable* Object, const FString& FieldName, EWwiseRequiredMetadata Required) +{ + check(Object); + Object->AddRequestedValue(TEXT("bool"), FieldName); + + bool Value = false; + + if (!JsonObject->TryGetBoolField(FieldName, Value) && Required == EWwiseRequiredMetadata::Mandatory) + { + Fail(*FieldName); + } + + Object->IncLoadedSize(sizeof(Value)); + return Value; +} + +float FWwiseMetadataLoader::GetFloat(FWwiseMetadataLoadable* Object, const FString& FieldName, EWwiseRequiredMetadata Required) +{ + check(Object); + Object->AddRequestedValue(TEXT("float"), FieldName); + + double Value{}; + + if (!JsonObject->TryGetNumberField(FieldName, Value) && Required == EWwiseRequiredMetadata::Mandatory) + { + Fail(*FieldName); + } + + Object->IncLoadedSize(sizeof(Value)); + return float(Value); +} + +FGuid FWwiseMetadataLoader::GetGuid(FWwiseMetadataLoadable* Object, const FString& FieldName, EWwiseRequiredMetadata Required) +{ + check(Object); + Object->AddRequestedValue(TEXT("guid"), FieldName); + + FGuid Value{}; + + FString ValueAsString; + if (!JsonObject->TryGetStringField(FieldName, ValueAsString)) + { + if (Required == EWwiseRequiredMetadata::Mandatory) + { + Fail(*FieldName); + } + } + else if (ValueAsString.Len() != 38) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Invalid GUID %s: %s"), *FieldName, *ValueAsString); + Fail(*FieldName); + } + else + { + if (!FGuid::ParseExact(ValueAsString, EGuidFormats::DigitsWithHyphensInBraces, Value)) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not decode GUID %s: %s"), *FieldName, *ValueAsString); + Fail(*FieldName); + } + } + + Object->IncLoadedSize(sizeof(Value)); + return Value; +} + +FName FWwiseMetadataLoader::GetString(FWwiseMetadataLoadable* Object, const FString& FieldName, EWwiseRequiredMetadata Required) +{ + check(Object); + Object->AddRequestedValue(TEXT("string"), FieldName); + + FString Value{}; + + if (!JsonObject->TryGetStringField(FieldName, Value) && Required == EWwiseRequiredMetadata::Mandatory) + { + Fail(*FieldName); + } + + Object->IncLoadedSize(sizeof(Value) + Value.GetAllocatedSize()); + return FName(Value); +} + +uint32 FWwiseMetadataLoader::GetUint32(FWwiseMetadataLoadable* Object, const FString& FieldName, EWwiseRequiredMetadata Required) +{ + check(Object); + Object->AddRequestedValue(TEXT("uint32"), FieldName); + + uint32 Value{}; + + if (!JsonObject->TryGetNumberField(FieldName, Value) && Required == EWwiseRequiredMetadata::Mandatory) + { + Fail(*FieldName); + } + + Object->IncLoadedSize(sizeof(Value)); + return Value; +} + + + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataLoader.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataLoader.h new file mode 100644 index 0000000..0a1dd61 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataLoader.h @@ -0,0 +1,226 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Dom/JsonObject.h" +#include "Wwise/Metadata/WwiseMetadataLoadable.h" +#include "Wwise/Metadata/WwiseMetadataGameParameter.h" + +enum class EWwiseRequiredMetadata +{ + Optional, + Mandatory +}; + +struct FWwiseMetadataLoader +{ + bool bResult; + const TSharedRef& JsonObject; + + FWwiseMetadataLoader(const TSharedRef& InJsonObject) : + bResult(true), + JsonObject(InJsonObject) + { + } + + void Fail(const TCHAR* FieldName); + void LogParsed(const TCHAR* FieldName, const uint32 Id = 0, const FName Name = FName()); + + bool GetBool(FWwiseMetadataLoadable* Object, const FString& FieldName, EWwiseRequiredMetadata Required = EWwiseRequiredMetadata::Mandatory); + float GetFloat(FWwiseMetadataLoadable* Object, const FString& FieldName, EWwiseRequiredMetadata Required = EWwiseRequiredMetadata::Mandatory); + FGuid GetGuid(FWwiseMetadataLoadable* Object, const FString& FieldName, EWwiseRequiredMetadata Required = EWwiseRequiredMetadata::Mandatory); + FName GetString(FWwiseMetadataLoadable* Object, const FString& FieldName, EWwiseRequiredMetadata Required = EWwiseRequiredMetadata::Mandatory); + uint32 GetUint32(FWwiseMetadataLoadable* Object, const FString& FieldName, EWwiseRequiredMetadata Required = EWwiseRequiredMetadata::Mandatory); + + template + T GetObject(FWwiseMetadataLoadable* Object, const FString& FieldName); + + template + T* GetObjectPtr(FWwiseMetadataLoadable* Object, const FString& FieldName); + + template + TArray GetArray(FWwiseMetadataLoadable* Object, const FString& FieldName); + + template + void GetPropertyArray(T* Object, const TMap& FloatProperties); +}; + +template +T FWwiseMetadataLoader::GetObject(FWwiseMetadataLoadable* Object, const FString& FieldName) +{ + check(Object); + Object->AddRequestedValue(TEXT("object"), FieldName); + + const TSharedPtr* InnerObject; + if (!JsonObject->TryGetObjectField(FieldName, InnerObject)) + { + Fail(*FieldName); + return T{}; + } + auto SharedRef(InnerObject->ToSharedRef()); + FWwiseMetadataLoader ObjectLoader(SharedRef); + T Result(ObjectLoader); + if (ObjectLoader.bResult) + { + Result.CheckRequestedValues(SharedRef); + } + else + { + bResult = false; + LogParsed(*FieldName); + } + + return Result; +} + + +template +T* FWwiseMetadataLoader::GetObjectPtr(FWwiseMetadataLoadable* Object, const FString& FieldName) +{ + check(Object); + Object->AddRequestedValue(TEXT("optional object"), FieldName); + + const TSharedPtr* InnerObject; + if (!JsonObject->TryGetObjectField(FieldName, InnerObject)) + { + return nullptr; + } + + auto SharedRef(InnerObject->ToSharedRef()); + FWwiseMetadataLoader ObjectLoader(SharedRef); + T* Result = new T(ObjectLoader); + if (ObjectLoader.bResult) + { + if (Result) + { + Result->CheckRequestedValues(SharedRef); + } + } + else + { + bResult = false; + LogParsed(*FieldName); + delete Result; + return nullptr; + } + + return Result; +} + +template +TArray FWwiseMetadataLoader::GetArray(FWwiseMetadataLoadable* Object, const FString& FieldName) +{ + check(Object); + Object->AddRequestedValue(TEXT("array"), FieldName); + + const TArray< TSharedPtr >* Array; + if (!JsonObject->TryGetArrayField(FieldName, Array)) + { + // No data. Not a fail, valid! + Object->IncLoadedSize(sizeof(TArray)); + return TArray{}; + } + + TArray Result; + Result.Empty(Array->Num()); + + for (auto& InnerObject : *Array) + { + const TSharedPtr* InnerJsonObjectPtr; + if (!InnerObject->TryGetObject(InnerJsonObjectPtr)) + { + LogParsed(*FieldName); + continue; + } + + auto SharedRef(InnerJsonObjectPtr->ToSharedRef()); + FWwiseMetadataLoader ArrayLoader(SharedRef); + T ResultObject(ArrayLoader); + + if (ArrayLoader.bResult) + { + ResultObject.CheckRequestedValues(SharedRef); + } + else + { + bResult = false; + ArrayLoader.LogParsed(*FieldName); + Result.Empty(); + break; + } + + Result.Add(MoveTemp(ResultObject)); + } + + Object->IncLoadedSize(sizeof(TArray)); + return Result; +} + +template +void FWwiseMetadataLoader::GetPropertyArray(T* Object, const TMap& FloatProperties) +{ + check(Object); + Object->AddRequestedValue(TEXT("propertyarray"), TEXT("Properties")); + + Object->IncLoadedSize(FloatProperties.Num() * sizeof(float)); + + const TArray< TSharedPtr >* Array; + if (!JsonObject->TryGetArrayField(TEXT("Properties"), Array)) + { + // No data. Not a fail, valid! + return; + } + + for (auto& InnerObject : *Array) + { + const TSharedPtr* InnerJsonObjectPtr; + if (!InnerObject->TryGetObject(InnerJsonObjectPtr)) + { + continue; + } + + const auto SharedRef(InnerJsonObjectPtr->ToSharedRef()); + FString Name; + if (!SharedRef->TryGetStringField(TEXT("Name"), Name)) + { + Fail(TEXT("Property::Name")); + continue; + } + FString Type; + if (!SharedRef->TryGetStringField(TEXT("Type"), Type) || Type != TEXT("Real32")) + { + Fail(TEXT("Property::Type")); + continue; + } + double Value; + if (!SharedRef->TryGetNumberField(TEXT("Value"), Value)) + { + Fail(TEXT("Property::Value")); + continue; + } + if (const auto* Property = FloatProperties.Find(FName(Name))) + { + *(float*)((intptr_t)Object + *Property) = Value; + } + else + { + Fail(*Name); + continue; + } + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataMedia.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataMedia.cpp new file mode 100644 index 0000000..e8320dc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataMedia.cpp @@ -0,0 +1,81 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataMedia.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" +#include "Wwise/Stats/ProjectDatabase.h" + +FWwiseMetadataMediaReference::FWwiseMetadataMediaReference(FWwiseMetadataLoader& Loader) : + Id(Loader.GetUint32(this, TEXT("Id"))) +{ + Loader.LogParsed(TEXT("MediaReference"), Id); +} + +FWwiseMetadataMediaAttributes::FWwiseMetadataMediaAttributes(FWwiseMetadataLoader& Loader) : + FWwiseMetadataMediaReference(Loader), + Language(Loader.GetString(this, TEXT("Language"))), + bStreaming(Loader.GetBool(this, TEXT("Streaming"))), + Location(LocationFromString(Loader.GetString(this, TEXT("Location")))), + bUsingReferenceLanguage(Loader.GetBool(this, TEXT("UsingReferenceLanguage"), EWwiseRequiredMetadata::Optional)), + Align(Loader.GetUint32(this, TEXT("Align"), EWwiseRequiredMetadata::Optional)), + bDeviceMemory(Loader.GetBool(this, TEXT("DeviceMemory"), EWwiseRequiredMetadata::Optional)) +{ + Loader.LogParsed(TEXT("MediaAttributes"), Id); +} + +EWwiseMetadataMediaLocation FWwiseMetadataMediaAttributes::LocationFromString(const FName& LocationString) +{ + if (LocationString == "Memory") + { + return EWwiseMetadataMediaLocation::Memory; + } + else if (LocationString == "Loose") + { + return EWwiseMetadataMediaLocation::Loose; + } + else if (LocationString == "OtherBank") + { + return EWwiseMetadataMediaLocation::OtherBank; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Warning, TEXT("FWwiseMetadataMediaAttributes: Unknown Location: %s"), *LocationString.ToString()); + return EWwiseMetadataMediaLocation::Unknown; + } +} + +FWwiseMetadataMedia::FWwiseMetadataMedia(FWwiseMetadataLoader& Loader) : + FWwiseMetadataMediaAttributes(Loader), + ShortName(Loader.GetString(this, TEXT("ShortName"))), + Path(Loader.GetString(this, TEXT("Path"), EWwiseRequiredMetadata::Optional)), + CachePath(Loader.GetString(this, TEXT("CachePath"), EWwiseRequiredMetadata::Optional)), + PrefetchSize(Loader.GetUint32(this, TEXT("PrefetchSize"), EWwiseRequiredMetadata::Optional)) +{ + if (UNLIKELY(Path.IsNone() && Location == EWwiseMetadataMediaLocation::Loose)) + { + Loader.Fail(TEXT("!Path+Location=Loose")); + } + else if (UNLIKELY(Path.IsNone() && Location == EWwiseMetadataMediaLocation::Memory && bStreaming)) + { + Loader.Fail(TEXT("!Path+Streaming")); + } + else if (UNLIKELY(!Path.IsNone() && Location == EWwiseMetadataMediaLocation::Memory && !bStreaming)) + { + Loader.Fail(TEXT("Path+Memory")); + } + Loader.LogParsed(TEXT("Media"), Id); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataPlatform.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataPlatform.cpp new file mode 100644 index 0000000..dd2cb48 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataPlatform.cpp @@ -0,0 +1,54 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataPlatform.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" +#include "Wwise/Stats/ProjectDatabase.h" + +FWwiseMetadataPlatformAttributes::FWwiseMetadataPlatformAttributes() +{ + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Using default FWwiseMetadataPlatformAttributes")); +} + +FWwiseMetadataPlatformAttributes::FWwiseMetadataPlatformAttributes(FWwiseMetadataLoader& Loader) : + Name(Loader.GetString(this, TEXT("Name"))), + BasePlatform(Loader.GetString(this, TEXT("BasePlatform"))), + Generator(Loader.GetString(this, TEXT("Generator"))) +{ + Loader.LogParsed(TEXT("PlatformAttributes"), 0, Name); +} + +FWwiseMetadataPlatformReference::FWwiseMetadataPlatformReference(FWwiseMetadataLoader& Loader) : + Name(Loader.GetString(this, TEXT("Name"))), + GUID(Loader.GetGuid(this, TEXT("GUID"))), + BasePlatform(Loader.GetString(this, TEXT("BasePlatform"))), + BasePlatformGUID(Loader.GetGuid(this, TEXT("BasePlatformGUID"))), + Path(Loader.GetString(this, TEXT("Path"))) +{ + Loader.LogParsed(TEXT("PlatformReference"), 0, Name); +} + +FWwiseMetadataPlatform::FWwiseMetadataPlatform() +{ + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Using default FWwiseMetadataPlatform")); +} + +FWwiseMetadataPlatform::FWwiseMetadataPlatform(FWwiseMetadataLoader& Loader) : + FWwiseMetadataPlatformAttributes(Loader) +{ + Loader.LogParsed(TEXT("Platform"), 0, Name); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataPlatformInfo.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataPlatformInfo.cpp new file mode 100644 index 0000000..5a22d34 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataPlatformInfo.cpp @@ -0,0 +1,29 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataPlatformInfo.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" + +FWwiseMetadataPlatformInfo::FWwiseMetadataPlatformInfo(FWwiseMetadataLoader& Loader) : + Platform(Loader.GetObject(this, TEXT("Platform"))), + RootPaths(Loader.GetObject(this, TEXT("RootPaths"))), + DefaultAlign(Loader.GetUint32(this, TEXT("DefaultAlign"))), + Settings(Loader.GetObject(this, TEXT("Settings"))), + FileHash(Loader.GetGuid(this, TEXT("FileHash"))) +{ + Loader.LogParsed(TEXT("PlatformInfo")); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataPlugin.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataPlugin.cpp new file mode 100644 index 0000000..5f8aea7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataPlugin.cpp @@ -0,0 +1,42 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataPlugin.h" +#include "Wwise/Metadata/WwiseMetadataPluginGroup.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" + +FWwiseMetadataPluginReference::FWwiseMetadataPluginReference(FWwiseMetadataLoader& Loader) : + Id(Loader.GetUint32(this, TEXT("Id"))) +{ + Loader.LogParsed(TEXT("PluginReference"), Id); +} + +FWwiseMetadataPluginAttributes::FWwiseMetadataPluginAttributes(FWwiseMetadataLoader& Loader) : + FWwiseMetadataBasicReference(Loader), + LibName(Loader.GetString(this, TEXT("LibName"))), + LibId(Loader.GetUint32(this, TEXT("LibId"))) +{ + Loader.LogParsed(TEXT("PluginAttributes"), Id, Name); +} + +FWwiseMetadataPlugin::FWwiseMetadataPlugin(FWwiseMetadataLoader& Loader) : + FWwiseMetadataPluginAttributes(Loader), + MediaRefs(Loader.GetArray(this, TEXT("MediaRefs"))), + PluginRefs(Loader.GetObjectPtr(this, TEXT("PluginRefs"))) +{ + Loader.LogParsed(TEXT("Plugin"), Id, Name); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataPluginGroup.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataPluginGroup.cpp new file mode 100644 index 0000000..2acc10d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataPluginGroup.cpp @@ -0,0 +1,35 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataPluginGroup.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" + +FWwiseMetadataPluginReferenceGroup::FWwiseMetadataPluginReferenceGroup(FWwiseMetadataLoader& Loader) : + Custom(Loader.GetArray(this, TEXT("Custom"))), + ShareSets(Loader.GetArray(this, TEXT("ShareSets"))), + AudioDevices(Loader.GetArray(this, TEXT("AudioDevices"))) +{ + Loader.LogParsed(TEXT("PluginReferenceGroup")); +} + +FWwiseMetadataPluginGroup::FWwiseMetadataPluginGroup(FWwiseMetadataLoader& Loader) : + Custom(Loader.GetArray(this, TEXT("Custom"))), + ShareSets(Loader.GetArray(this, TEXT("ShareSets"))), + AudioDevices(Loader.GetArray(this, TEXT("AudioDevices"))) +{ + Loader.LogParsed(TEXT("PluginGroup")); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataPluginInfo.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataPluginInfo.cpp new file mode 100644 index 0000000..865717d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataPluginInfo.cpp @@ -0,0 +1,34 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataPluginInfo.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" + +FWwiseMetadataPluginInfoAttributes::FWwiseMetadataPluginInfoAttributes(FWwiseMetadataLoader& Loader) : + Platform(Loader.GetString(this, TEXT("Platform"))), + BasePlatform(Loader.GetString(this, TEXT("BasePlatform"))) +{ + Loader.LogParsed(TEXT("PluginInfoAttributes")); +} + +FWwiseMetadataPluginInfo::FWwiseMetadataPluginInfo(FWwiseMetadataLoader& Loader) : + FWwiseMetadataPluginInfoAttributes(Loader), + PluginLibs(Loader.GetArray(this, TEXT("PluginLibs"))), + FileHash(Loader.GetGuid(this, TEXT("FileHash"))) +{ + Loader.LogParsed(TEXT("PluginInfo")); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataPluginLib.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataPluginLib.cpp new file mode 100644 index 0000000..836f7f4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataPluginLib.cpp @@ -0,0 +1,61 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataPluginLib.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" +#include "Wwise/Stats/ProjectDatabase.h" + +FWwiseMetadataPluginLibAttributes::FWwiseMetadataPluginLibAttributes(FWwiseMetadataLoader& Loader) : + LibName(Loader.GetString(this, TEXT("LibName"))), + LibId(Loader.GetUint32(this, TEXT("LibId"))), + Type(TypeFromString(Loader.GetString(this, TEXT("Type")))), + DLL(Loader.GetString(this, TEXT("DLL"), EWwiseRequiredMetadata::Optional)), + StaticLib(Loader.GetString(this, TEXT("StaticLib"), EWwiseRequiredMetadata::Optional)) +{ + Loader.LogParsed(TEXT("PluginLibAttributes"), LibId, LibName); +} + +EWwiseMetadataPluginLibType FWwiseMetadataPluginLibAttributes::TypeFromString(const FName& TypeString) +{ + if (TypeString == "Effect") + { + return EWwiseMetadataPluginLibType::Effect; + } + else if (TypeString == "Source") + { + return EWwiseMetadataPluginLibType::Source; + } + else if (TypeString == "AudioDevice") + { + return EWwiseMetadataPluginLibType::AudioDevice; + } + else if (TypeString == "Metadata") + { + return EWwiseMetadataPluginLibType::Metadata; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Warning, TEXT("FWwiseMetadataPluginLibAttributes: Unknown Type: %s"), *TypeString.ToString()); + return EWwiseMetadataPluginLibType::Unknown; + } +} + +FWwiseMetadataPluginLib::FWwiseMetadataPluginLib(FWwiseMetadataLoader& Loader) : + FWwiseMetadataPluginLibAttributes(Loader) +{ + Loader.LogParsed(TEXT("PluginLib"), LibId, LibName); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataProject.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataProject.cpp new file mode 100644 index 0000000..29a4051 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataProject.cpp @@ -0,0 +1,33 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataProject.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" +#include "Wwise/Stats/ProjectDatabase.h" + +FWwiseMetadataProject::FWwiseMetadataProject() +{ + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Using default Project")); +} + +FWwiseMetadataProject::FWwiseMetadataProject(FWwiseMetadataLoader& Loader) : + Name(Loader.GetString(this, TEXT("Name"))), + GUID(Loader.GetGuid(this, TEXT("GUID"))), + Generator(Loader.GetString(this, TEXT("Generator"))) +{ + Loader.LogParsed(TEXT("Project")); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataProjectInfo.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataProjectInfo.cpp new file mode 100644 index 0000000..0648cbb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataProjectInfo.cpp @@ -0,0 +1,32 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataProjectInfo.h" + +#include "Wwise/Metadata/WwiseMetadataLanguage.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" +#include "Wwise/Metadata/WwiseMetadataPlatform.h" + +FWwiseMetadataProjectInfo::FWwiseMetadataProjectInfo(FWwiseMetadataLoader& Loader) : + Project(Loader.GetObject(this, TEXT("Project"))), + CacheRoot(Loader.GetString(this, TEXT("CacheRoot"))), + Platforms(Loader.GetArray(this, TEXT("Platforms"))), + Languages(Loader.GetArray(this, TEXT("Languages"))), + FileHash(Loader.GetGuid(this, TEXT("FileHash"))) +{ + Loader.LogParsed(TEXT("ProjectInfo")); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataRootFile.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataRootFile.cpp new file mode 100644 index 0000000..b078410 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataRootFile.cpp @@ -0,0 +1,194 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataRootFile.h" + +#include "Async/AsyncWork.h" +#include "Misc/FileHelper.h" +#include "Serialization/JsonReader.h" +#include "Serialization/JsonSerializer.h" + +#include "Wwise/Metadata/WwiseMetadataPlatformInfo.h" +#include "Wwise/Metadata/WwiseMetadataPluginInfo.h" +#include "Wwise/Metadata/WwiseMetadataProjectInfo.h" +#include "Wwise/Metadata/WwiseMetadataSoundBanksInfo.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" +#include "Wwise/Stats/ProjectDatabase.h" + +#include "WwiseDefines.h" + +FWwiseMetadataRootFile::FWwiseMetadataRootFile(FWwiseMetadataLoader& Loader) : + PlatformInfo(Loader.GetObjectPtr(this, TEXT("PlatformInfo"))), + PluginInfo(Loader.GetObjectPtr(this, TEXT("PluginInfo"))), + ProjectInfo(Loader.GetObjectPtr(this, TEXT("ProjectInfo"))), + SoundBanksInfo(Loader.GetObjectPtr(this, TEXT("SoundBanksInfo"))) +{ + if (Loader.bResult && !PlatformInfo && !PluginInfo && !ProjectInfo && !SoundBanksInfo) + { + Loader.Fail(TEXT("FWwiseMetadataRootFile")); + } + IncLoadedSize(sizeof(FWwiseMetadataRootFile)); +} + +FWwiseMetadataRootFile::~FWwiseMetadataRootFile() +{ + if (PlatformInfo) + { + delete PluginInfo; + PluginInfo = nullptr; + } + if (PluginInfo) + { + delete PluginInfo; + PluginInfo = nullptr; + } + if (ProjectInfo) + { + delete PluginInfo; + PluginInfo = nullptr; + } + if (SoundBanksInfo) + { + delete SoundBanksInfo; + SoundBanksInfo = nullptr; + } +} + +class FWwiseAsyncLoadFileTask : public FNonAbandonableTask +{ + friend class FAsyncTask; + + WwiseMetadataSharedRootFilePtr& Output; + const FString& FilePath; + +public: + FWwiseAsyncLoadFileTask( + WwiseMetadataSharedRootFilePtr& OutputParam, + const FString& FilePathParam) : + Output(OutputParam), + FilePath(FilePathParam) + { + } + +protected: + void DoWork() + { + FString FileContents; + if (!FFileHelper::LoadFileToString(FileContents, *FilePath)) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Error while loading file %s to string"), *FilePath); + return; + } + + Output = FWwiseMetadataRootFile::LoadFile(MoveTemp(FileContents), *FilePath); + } + + FORCEINLINE TStatId GetStatId() const + { + RETURN_QUICK_DECLARE_CYCLE_STAT(FWwiseAsyncLoadFileTask, STATGROUP_WwiseProjectDatabase); + } +}; + +WwiseMetadataSharedRootFilePtr FWwiseMetadataRootFile::LoadFile(FString&& File, const FString& FilePath) +{ + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Parsing file in: %s"), *FilePath); + + auto JsonReader = TJsonReaderFactory<>::Create(MoveTemp(File)); + TSharedPtr RootJsonObject; + if (!FJsonSerializer::Deserialize(JsonReader, RootJsonObject)) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Error while decoding json")); + return {}; + } + + FWwiseMetadataLoader Loader(RootJsonObject.ToSharedRef()); + auto Result = MakeShared(Loader); + + if (!Loader.bResult) + { + Loader.LogParsed(TEXT("LoadFile"), 0, FName(FilePath)); + return {}; + } + + return Result; +} + +WwiseMetadataSharedRootFilePtr FWwiseMetadataRootFile::LoadFile(const FString& FilePath) +{ + FString FileContents; + if (!FFileHelper::LoadFileToString(FileContents, *FilePath)) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Error while loading file %s to string"), *FilePath); + return nullptr; + } + + return LoadFile(MoveTemp(FileContents), FilePath); +} + +WwiseMetadataFileMap FWwiseMetadataRootFile::LoadFiles(const TArray& FilePaths) +{ + WwiseMetadataFileMap Result; + for (const auto& FilePath : FilePaths) + { + Result.Add(FilePath, {}); + } + + TArray> Tasks; + Tasks.Empty(Result.Num()); + + for (auto& Elem : Result) + { + Tasks.Emplace(Elem.Value, Elem.Key); + } + + if (Result.Num() > 1) + { + // Create a temporary Thread Pool to fully load the file paths with a large Stack size. + // We typically use way less than that, but some functions are recursive, and Json parsing can be memory intensive. + const auto WorkersToSpawn = FMath::Min(FPlatformMisc::NumberOfWorkerThreadsToSpawn(), Result.Num()); + static constexpr int32 StackSize = 2 * 1024 * 1024; + const auto MetadataLoadingThreadPool = FQueuedThreadPool::Allocate(); + verify(MetadataLoadingThreadPool->Create(WorkersToSpawn, StackSize, TPri_BelowNormal, TEXT("Wwise ProjectDatabase Loading Pool"))); + + for (auto& Task : Tasks) + { + Task.StartBackgroundTask(MetadataLoadingThreadPool); + } + + for (auto& Task : Tasks) + { + Task.EnsureCompletion(); + } + + delete MetadataLoadingThreadPool; + } + else + { + // We have only one (or zero) task. Do it synchronously. + for (auto& Task : Tasks) + { + Task.StartSynchronousTask(); + } + + for (auto& Task : Tasks) + { + Task.EnsureCompletion(); + } + } + + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataRootPaths.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataRootPaths.cpp new file mode 100644 index 0000000..b97e7df --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataRootPaths.cpp @@ -0,0 +1,36 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataRootPaths.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" +#include "Wwise/WwiseProjectDatabaseModule.h" +#include "Wwise/Stats/ProjectDatabase.h" + +FWwiseMetadataRootPaths::FWwiseMetadataRootPaths() +{ + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Using default FWwiseMetadataRootPaths")); +} + +FWwiseMetadataRootPaths::FWwiseMetadataRootPaths(FWwiseMetadataLoader& Loader) : + ProjectRoot(Loader.GetString(this, TEXT("ProjectRoot"))), + SourceFilesRoot(Loader.GetString(this, TEXT("SourceFilesRoot"))), + SoundBanksRoot(Loader.GetString(this, TEXT("SoundBanksRoot"))), + ExternalSourcesInputFile(Loader.GetString(this, TEXT("ExternalSourcesInputFile"))), + ExternalSourcesOutputRoot(Loader.GetString(this, TEXT("ExternalSourcesOutputRoot"))) +{ + Loader.LogParsed(TEXT("RootPaths")); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataSettings.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataSettings.cpp new file mode 100644 index 0000000..5fedd3a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataSettings.cpp @@ -0,0 +1,65 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataSettings.h" +#include "Wwise/Stats/ProjectDatabase.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" +#include "Wwise/WwiseProjectDatabaseModule.h" + +FWwiseMetadataSettings::FWwiseMetadataSettings() : + bAutoSoundBankDefinition(false), + bCopyLooseStreamedMediaFiles(false), + bSubFoldersForGeneratedFiles(false), + bRemoveUnusedGeneratedFiles(false), + bSourceControlGeneratedFiles(false), + bGenerateHeaderFile(false), + bGenerateContentTxtFile(false), + bGenerateMetadataXML(false), + bGenerateMetadataJSON(false), + bGenerateAllBanksMetadata(false), + bGeneratePerBankMetadata(false), + bUseSoundBankNames(false), + bAllowExceedingMaxSize(false), + bMaxAttenuationInfo(false), + bEstimatedDurationInfo(false), + bPrintObjectGuid(false), + bPrintObjectPath(false) +{ + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Using default Settings")); +} + +FWwiseMetadataSettings::FWwiseMetadataSettings(FWwiseMetadataLoader& Loader) : + bAutoSoundBankDefinition(Loader.GetBool(this, TEXT("AutoSoundBankDefinition"))), + bCopyLooseStreamedMediaFiles(Loader.GetBool(this, TEXT("CopyLooseStreamedMediaFiles"))), + bSubFoldersForGeneratedFiles(Loader.GetBool(this, TEXT("SubFoldersForGeneratedFiles"))), + bRemoveUnusedGeneratedFiles(Loader.GetBool(this, TEXT("RemoveUnusedGeneratedFiles"))), + bSourceControlGeneratedFiles(Loader.GetBool(this, TEXT("SourceControlGeneratedFiles"))), + bGenerateHeaderFile(Loader.GetBool(this, TEXT("GenerateHeaderFile"))), + bGenerateContentTxtFile(Loader.GetBool(this, TEXT("GenerateContentTxtFile"))), + bGenerateMetadataXML(Loader.GetBool(this, TEXT("GenerateMetadataXML"))), + bGenerateMetadataJSON(Loader.GetBool(this, TEXT("GenerateMetadataJSON"))), + bGenerateAllBanksMetadata(Loader.GetBool(this, TEXT("GenerateAllBanksMetadata"))), + bGeneratePerBankMetadata(Loader.GetBool(this, TEXT("GeneratePerBankMetadata"))), + bUseSoundBankNames(Loader.GetBool(this, TEXT("UseSoundBankNames"))), + bAllowExceedingMaxSize(Loader.GetBool(this, TEXT("AllowExceedingMaxSize"))), + bMaxAttenuationInfo(Loader.GetBool(this, TEXT("MaxAttenuationInfo"))), + bEstimatedDurationInfo(Loader.GetBool(this, TEXT("EstimatedDurationInfo"))), + bPrintObjectGuid(Loader.GetBool(this, TEXT("PrintObjectGuid"))), + bPrintObjectPath(Loader.GetBool(this, TEXT("PrintObjectPath"))) +{ + Loader.LogParsed(TEXT("Settings")); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataSoundBank.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataSoundBank.cpp new file mode 100644 index 0000000..fdc3c13 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataSoundBank.cpp @@ -0,0 +1,123 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataSoundBank.h" +#include "Wwise/Stats/ProjectDatabase.h" +#include "Wwise/Metadata/WwiseMetadataCollections.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" +#include "Wwise/Metadata/WwiseMetadataPluginGroup.h" +#include "Wwise/Metadata/WwiseMetadataStateGroup.h" +#include "Wwise/Metadata/WwiseMetadataSwitchGroup.h" +#include "Wwise/WwiseProjectDatabaseModule.h" + +FWwiseMetadataSoundBankReference::FWwiseMetadataSoundBankReference(FWwiseMetadataLoader& Loader) : + Id(Loader.GetUint32(this, TEXT("Id"))), + GUID(Loader.GetGuid(this, TEXT("GUID"))), + Language(Loader.GetString(this, TEXT("Language"))) +{ + Loader.LogParsed(TEXT("SoundBankReference"), Id); +} + +FWwiseMetadataSoundBankAttributes::FWwiseMetadataSoundBankAttributes(FWwiseMetadataLoader& Loader) : + FWwiseMetadataSoundBankReference(Loader), + Align(Loader.GetUint32(this, TEXT("Align"), EWwiseRequiredMetadata::Optional)), + bDeviceMemory(Loader.GetBool(this, TEXT("DeviceMemory"), EWwiseRequiredMetadata::Optional)), + Hash(Loader.GetGuid(this, TEXT("Hash"))), + Type(TypeFromString(Loader.GetString(this, TEXT("Type")))) +{ + IncLoadedSize(sizeof(EMetadataSoundBankType)); + Loader.LogParsed(TEXT("SoundBankAttributes"), Id); +} + +EMetadataSoundBankType FWwiseMetadataSoundBankAttributes::TypeFromString(const FName& TypeString) +{ + if (TypeString == "User") + { + return EMetadataSoundBankType::User; + } + else if (TypeString == "Event") + { + return EMetadataSoundBankType::Event; + } + else if (TypeString == "Bus") + { + return EMetadataSoundBankType::Bus; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Warning, TEXT("FWwiseMetadataSoundBankAttributes: Unknown Type: %s"), *TypeString.ToString()); + return EMetadataSoundBankType::Unknown; + } +} + +FWwiseMetadataSoundBank::FWwiseMetadataSoundBank(FWwiseMetadataLoader& Loader) : + FWwiseMetadataSoundBankAttributes(Loader), + ObjectPath(Loader.GetString(this, TEXT("ObjectPath"))), + ShortName(Loader.GetString(this, TEXT("ShortName"))), + Path(Loader.GetString(this, TEXT("Path"))), + Media(Loader.GetArray(this, TEXT("Media"))), + Plugins(Loader.GetObjectPtr(this, TEXT("Plugins"))), + Events(Loader.GetArray(this, TEXT("Events"))), + DialogueEvents(Loader.GetArray(this, TEXT("DialogueEvents"))), + Busses(Loader.GetArray(this, TEXT("Busses"))), + AuxBusses(Loader.GetArray(this, TEXT("AuxBusses"))), + GameParameters(Loader.GetArray(this, TEXT("GameParameters"))), + StateGroups(Loader.GetArray(this, TEXT("StateGroups"))), + SwitchGroups(Loader.GetArray(this, TEXT("SwitchGroups"))), + Triggers(Loader.GetArray(this, TEXT("Triggers"))), + ExternalSources(Loader.GetArray(this, TEXT("ExternalSources"))), + AcousticTextures(Loader.GetArray(this, TEXT("AcousticTextures"))) +{ + bIsInitBank = ShortName == TEXT("Init"); + Loader.LogParsed(TEXT("SoundBank"), Id); +} + +TSet FWwiseMetadataSoundBank::GetAllDialogueArguments() const +{ + TSet Result; + for (const auto& DialogueEvent : DialogueEvents) + { + Result.Append(DialogueEvent.Arguments); + } + return Result; +} + +TSet FWwiseMetadataSoundBank::GetAllStates() const +{ + TSet Result; + for (const auto& StateGroup : StateGroups) + { + for (const auto& State : StateGroup.States) + { + Result.Emplace(WwiseMetadataStateWithGroup(StateGroup, State), nullptr); + } + } + return Result; +} + +TSet FWwiseMetadataSoundBank::GetAllSwitches() const +{ + TSet Result; + for (const auto& SwitchGroup : SwitchGroups) + { + for (const auto& Switch : SwitchGroup.Switches) + { + Result.Emplace(WwiseMetadataSwitchWithGroup(SwitchGroup, Switch), nullptr); + } + } + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataSoundBanksInfo.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataSoundBanksInfo.cpp new file mode 100644 index 0000000..fa1df80 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataSoundBanksInfo.cpp @@ -0,0 +1,48 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataSoundBanksInfo.h" +#include "Wwise/Metadata/WwiseMetadataRootPaths.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" + +FWwiseMetadataSoundBanksInfoAttributes::FWwiseMetadataSoundBanksInfoAttributes(FWwiseMetadataLoader& Loader): + Platform(Loader.GetString(this, TEXT("Platform"))), + BasePlatform(Loader.GetString(this, TEXT("BasePlatform"))), + SchemaVersion(Loader.GetUint32(this, TEXT("SchemaVersion"))), + SoundBankVersion(Loader.GetUint32(this, TEXT("SoundBankVersion"))) +{ + Loader.LogParsed(TEXT("SoundBanksInfoAttributes")); +} + +FWwiseMetadataSoundBanksInfo::FWwiseMetadataSoundBanksInfo(FWwiseMetadataLoader& Loader) : + FWwiseMetadataSoundBanksInfoAttributes(Loader), + RootPaths(Loader.GetObjectPtr(this, TEXT("RootPaths"))), + DialogueEvents(Loader.GetArray(this, TEXT("DialogueEvents"))), + SoundBanks(Loader.GetArray(this, TEXT("SoundBanks"))), + FileHash(Loader.GetGuid(this, TEXT("FileHash"))) +{ + Loader.LogParsed(TEXT("SoundBanksInfo")); +} + +FWwiseMetadataSoundBanksInfo::~FWwiseMetadataSoundBanksInfo() +{ + if (RootPaths) + { + delete RootPaths; + RootPaths = nullptr; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataState.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataState.cpp new file mode 100644 index 0000000..bcf7e2a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataState.cpp @@ -0,0 +1,25 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataState.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" + +FWwiseMetadataState::FWwiseMetadataState(FWwiseMetadataLoader& Loader) : + FWwiseMetadataBasicReference(Loader) +{ + Loader.LogParsed(TEXT("State"), Id, Name); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataStateGroup.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataStateGroup.cpp new file mode 100644 index 0000000..73fc75c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataStateGroup.cpp @@ -0,0 +1,26 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataStateGroup.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" + +FWwiseMetadataStateGroup::FWwiseMetadataStateGroup(FWwiseMetadataLoader& Loader) : + FWwiseMetadataBasicReference(Loader), + States(Loader.GetArray(this, TEXT("States"))) +{ + Loader.LogParsed(TEXT("StateGroup"), Id, Name); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataSwitch.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataSwitch.cpp new file mode 100644 index 0000000..e4a4972 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataSwitch.cpp @@ -0,0 +1,25 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataSwitch.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" + +FWwiseMetadataSwitch::FWwiseMetadataSwitch(FWwiseMetadataLoader& Loader) : + FWwiseMetadataBasicReference(Loader) +{ + Loader.LogParsed(TEXT("Switch"), Id, Name); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataSwitchContainer.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataSwitchContainer.cpp new file mode 100644 index 0000000..86251f0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataSwitchContainer.cpp @@ -0,0 +1,92 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataSwitchContainer.h" +#include "Wwise/Metadata/WwiseMetadataPluginGroup.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" + +FWwiseMetadataSwitchContainer::FWwiseMetadataSwitchContainer(FWwiseMetadataLoader& Loader) : + SwitchValue(Loader.GetObject(this, TEXT("SwitchValue"))), + MediaRefs(Loader.GetArray(this, TEXT("MediaRefs"))), + ExternalSourceRefs(Loader.GetArray(this, TEXT("ExternalSourceRefs"))), + PluginRefs(Loader.GetObjectPtr(this, TEXT("PluginRefs"))), + Children(Loader.GetArray(this, TEXT("Children"))) +{ + Loader.LogParsed(TEXT("SwitchContainer")); +} + +TSet FWwiseMetadataSwitchContainer::GetAllMedia() const +{ + TSet Result(MediaRefs); + for (const auto& Child : Children) + { + Result.Append(Child.GetAllMedia()); + } + return Result; +} + +TSet FWwiseMetadataSwitchContainer::GetAllExternalSources() const +{ + TSet Result(ExternalSourceRefs); + for (const auto& Child : Children) + { + Result.Append(Child.GetAllExternalSources()); + } + return Result; +} + +TSet FWwiseMetadataSwitchContainer::GetAllCustomPlugins() const +{ + if (!PluginRefs) + { + return {}; + } + TSet Result(PluginRefs->Custom); + for (const auto& Child : Children) + { + Result.Append(Child.GetAllCustomPlugins()); + } + return Result; +} + +TSet FWwiseMetadataSwitchContainer::GetAllPluginShareSets() const +{ + if (!PluginRefs) + { + return {}; + } + TSet Result(PluginRefs->ShareSets); + for (const auto& Child : Children) + { + Result.Append(Child.GetAllPluginShareSets()); + } + return Result; +} + +TSet FWwiseMetadataSwitchContainer::GetAllAudioDevices() const +{ + if (!PluginRefs) + { + return {}; + } + TSet Result(PluginRefs->AudioDevices); + for (const auto& Child : Children) + { + Result.Append(Child.GetAllAudioDevices()); + } + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataSwitchGroup.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataSwitchGroup.cpp new file mode 100644 index 0000000..afc403e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataSwitchGroup.cpp @@ -0,0 +1,27 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataSwitchGroup.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" + +FWwiseMetadataSwitchGroup::FWwiseMetadataSwitchGroup(FWwiseMetadataLoader& Loader) : + FWwiseMetadataBasicReference(Loader), + GameParameterRef(Loader.GetObjectPtr(this, TEXT("GameParameterRef"))), + Switches(Loader.GetArray(this, TEXT("Switches"))) +{ + Loader.LogParsed(TEXT("SwitchGroup"), Id, Name); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataSwitchValue.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataSwitchValue.cpp new file mode 100644 index 0000000..763e80f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataSwitchValue.cpp @@ -0,0 +1,58 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataSwitchValue.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" +#include "Wwise/Stats/ProjectDatabase.h" + +FWwiseMetadataSwitchValueAttributes::FWwiseMetadataSwitchValueAttributes() +{ +} + +FWwiseMetadataSwitchValueAttributes::FWwiseMetadataSwitchValueAttributes(FWwiseMetadataLoader& Loader) : + GroupType(GroupTypeFromString(Loader.GetString(this, TEXT("GroupType")))), + GroupId(Loader.GetUint32(this, TEXT("GroupId"))), + Id(Loader.GetUint32(this, TEXT("Id"))), + GUID(Loader.GetGuid(this, TEXT("GUID"))), + bDefault(Loader.GetBool(this, TEXT("Default"), EWwiseRequiredMetadata::Optional)) +{ + Loader.LogParsed(TEXT("SwitchValueAttributes")); +} + +EWwiseMetadataSwitchValueGroupType FWwiseMetadataSwitchValueAttributes::GroupTypeFromString(const FName& TypeString) +{ + if (TypeString == "Switch") + { + return EWwiseMetadataSwitchValueGroupType::Switch; + } + else if (TypeString == "State") + { + return EWwiseMetadataSwitchValueGroupType::State; + } + UE_LOG(LogWwiseProjectDatabase, Warning, TEXT("FWwiseMetadataSwitchValueAttributes: Unknown GroupType: %s"), *TypeString.ToString()); + return EWwiseMetadataSwitchValueGroupType::Unknown; +} + +FWwiseMetadataSwitchValue::FWwiseMetadataSwitchValue() +{ +} + +FWwiseMetadataSwitchValue::FWwiseMetadataSwitchValue(FWwiseMetadataLoader& Loader) : + FWwiseMetadataSwitchValueAttributes(Loader) +{ + Loader.LogParsed(TEXT("SwitchValue")); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataTrigger.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataTrigger.cpp new file mode 100644 index 0000000..51d9809 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Metadata/WwiseMetadataTrigger.cpp @@ -0,0 +1,25 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Metadata/WwiseMetadataTrigger.h" +#include "Wwise/Metadata/WwiseMetadataLoader.h" + +FWwiseMetadataTrigger::FWwiseMetadataTrigger(FWwiseMetadataLoader& Loader) : + FWwiseMetadataBasicReference(Loader) +{ + Loader.LogParsed(TEXT("Trigger"), Id, Name); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseAnyRef.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseAnyRef.cpp new file mode 100644 index 0000000..0076500 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseAnyRef.cpp @@ -0,0 +1,901 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseAnyRef.h" + +#include "Wwise/Metadata/WwiseMetadataLanguage.h" +#include "Wwise/Metadata/WwiseMetadataPlatform.h" +#include "Wwise/Metadata/WwiseMetadataPlugin.h" +#include "Wwise/Metadata/WwiseMetadataSoundBank.h" +#include "Wwise/Ref/WwiseRefAcousticTexture.h" +#include "Wwise/Ref/WwiseRefAudioDevice.h" +#include "Wwise/Ref/WwiseRefAuxBus.h" +#include "Wwise/Ref/WwiseRefBus.h" +#include "Wwise/Ref/WwiseRefCustomPlugin.h" +#include "Wwise/Ref/WwiseRefDialogueArgument.h" +#include "Wwise/Ref/WwiseRefExternalSource.h" +#include "Wwise/Ref/WwiseRefGameParameter.h" +#include "Wwise/Ref/WwiseRefLanguage.h" +#include "Wwise/Ref/WwiseRefMedia.h" +#include "Wwise/Ref/WwiseRefPlatform.h" +#include "Wwise/Ref/WwiseRefPluginLib.h" +#include "Wwise/Ref/WwiseRefPluginShareSet.h" +#include "Wwise/Ref/WwiseRefSoundBank.h" +#include "Wwise/Ref/WwiseRefState.h" +#include "Wwise/Ref/WwiseRefSwitch.h" +#include "Wwise/Ref/WwiseRefSwitchContainer.h" +#include "Wwise/Ref/WwiseRefTrigger.h" + +const FWwiseRefLanguage* FWwiseAnyRef::GetLanguageRef() const +{ + if (UNLIKELY(GetType() != EWwiseRefType::Language)) + { + return nullptr; + } + return static_cast(Ref.Get()); +} + +const FWwiseRefPlatform* FWwiseAnyRef::GetPlatformRef() const +{ + if (UNLIKELY(GetType() != EWwiseRefType::Platform)) + { + return nullptr; + } + return static_cast(Ref.Get()); +} + +const FWwiseRefPluginLib* FWwiseAnyRef::GetPluginLibRef() const +{ + if (UNLIKELY(GetType() != EWwiseRefType::PluginLib)) + { + return nullptr; + } + return static_cast(Ref.Get()); +} + +const FWwiseRefSoundBank* FWwiseAnyRef::GetSoundBankRef() const +{ + if (UNLIKELY(GetType() < EWwiseRefType::SoundBank || GetType() > EWwiseRefType::AcousticTexture)) + { + return nullptr; + } + return static_cast(Ref.Get()); +} + +const FWwiseRefMedia* FWwiseAnyRef::GetMediaRef() const +{ + if (UNLIKELY(GetType() != EWwiseRefType::Media)) + { + return nullptr; + } + return static_cast(Ref.Get()); +} + +const FWwiseRefCustomPlugin* FWwiseAnyRef::GetCustomPluginRef() const +{ + if (UNLIKELY(GetType() != EWwiseRefType::CustomPlugin)) + { + return nullptr; + } + return static_cast(Ref.Get()); +} + +const FWwiseRefPluginShareSet* FWwiseAnyRef::GetPluginShareSetRef() const +{ + if (UNLIKELY(GetType() != EWwiseRefType::PluginShareSet)) + { + return nullptr; + } + return static_cast(Ref.Get()); +} + +const FWwiseRefAudioDevice* FWwiseAnyRef::GetAudioDeviceRef() const +{ + if (UNLIKELY(GetType() != EWwiseRefType::AudioDevice)) + { + return nullptr; + } + return static_cast(Ref.Get()); +} + +const FWwiseRefEvent* FWwiseAnyRef::GetEventRef() const +{ + if (UNLIKELY(GetType() < EWwiseRefType::Event || GetType() > EWwiseRefType::SwitchContainer)) + { + return nullptr; + } + return static_cast(Ref.Get()); +} + +const FWwiseRefSwitchContainer* FWwiseAnyRef::GetSwitchContainerRef() const +{ + if (UNLIKELY(GetType() != EWwiseRefType::SwitchContainer)) + { + return nullptr; + } + return static_cast(Ref.Get()); +} + +const FWwiseRefDialogueEvent* FWwiseAnyRef::GetDialogueEventRef() const +{ + if (UNLIKELY(GetType() < EWwiseRefType::DialogueEvent || GetType() > EWwiseRefType::DialogueArgument)) + { + return nullptr; + } + return static_cast(Ref.Get()); +} + +const FWwiseRefDialogueArgument* FWwiseAnyRef::GetDialogueArgumentRef() const +{ + if (UNLIKELY(GetType() != EWwiseRefType::DialogueArgument)) + { + return nullptr; + } + return static_cast(Ref.Get()); +} + +const FWwiseRefBus* FWwiseAnyRef::GetBusRef() const +{ + if (UNLIKELY(GetType() != EWwiseRefType::Bus)) + { + return nullptr; + } + return static_cast(Ref.Get()); +} + +const FWwiseRefAuxBus* FWwiseAnyRef::GetAuxBusRef() const +{ + if (UNLIKELY(GetType() != EWwiseRefType::AuxBus)) + { + return nullptr; + } + return static_cast(Ref.Get()); +} + +const FWwiseRefGameParameter* FWwiseAnyRef::GetGameParameterRef() const +{ + if (UNLIKELY(GetType() != EWwiseRefType::GameParameter)) + { + return nullptr; + } + return static_cast(Ref.Get()); +} + +const FWwiseRefStateGroup* FWwiseAnyRef::GetStateGroupRef() const +{ + if (UNLIKELY(GetType() < EWwiseRefType::StateGroup || GetType() > EWwiseRefType::State)) + { + return nullptr; + } + return static_cast(Ref.Get()); +} + +const FWwiseRefState* FWwiseAnyRef::GetStateRef() const +{ + if (UNLIKELY(GetType() != EWwiseRefType::State)) + { + return nullptr; + } + return static_cast(Ref.Get()); +} + +const FWwiseRefSwitchGroup* FWwiseAnyRef::GetSwitchGroupRef() const +{ + if (UNLIKELY(GetType() < EWwiseRefType::SwitchGroup || GetType() > EWwiseRefType::Switch)) + { + return nullptr; + } + return static_cast(Ref.Get()); +} + +const FWwiseRefSwitch* FWwiseAnyRef::GetSwitchRef() const +{ + if (UNLIKELY(GetType() != EWwiseRefType::Switch)) + { + return nullptr; + } + return static_cast(Ref.Get()); +} + +const FWwiseRefTrigger* FWwiseAnyRef::GetTriggerRef() const +{ + if (UNLIKELY(GetType() != EWwiseRefType::Trigger)) + { + return nullptr; + } + return static_cast(Ref.Get()); +} + +const FWwiseRefExternalSource* FWwiseAnyRef::GetExternalSourceRef() const +{ + if (UNLIKELY(GetType() != EWwiseRefType::ExternalSource)) + { + return nullptr; + } + return static_cast(Ref.Get()); +} + +const FWwiseRefAcousticTexture* FWwiseAnyRef::GetAcousticTextureRef() const +{ + if (UNLIKELY(GetType() != EWwiseRefType::AcousticTexture)) + { + return nullptr; + } + return static_cast(Ref.Get()); +} + +const FWwiseMetadataLanguage* FWwiseAnyRef::GetLanguage() const +{ + const auto* LanguageRef = GetLanguageRef(); + if (UNLIKELY(!LanguageRef)) + { + return nullptr; + } + return LanguageRef->GetLanguage(); +} + +const FWwiseMetadataPlatform* FWwiseAnyRef::GetPlatform() const +{ + const auto* PlatformRef = GetPlatformRef(); + if (UNLIKELY(!PlatformRef)) + { + return nullptr; + } + return PlatformRef->GetPlatform(); +} + +const FWwiseMetadataPlatformReference* FWwiseAnyRef::GetPlatformReference() const +{ + const auto* PlatformRef = GetPlatformRef(); + if (UNLIKELY(!PlatformRef)) + { + return nullptr; + } + return PlatformRef->GetPlatformReference(); +} + +const FWwiseMetadataPluginLib* FWwiseAnyRef::GetPluginLib() const +{ + const auto* PluginLibRef = GetPluginLibRef(); + if (UNLIKELY(!PluginLibRef)) + { + return nullptr; + } + return PluginLibRef->GetPluginLib(); +} + +const FWwiseMetadataSoundBank* FWwiseAnyRef::GetSoundBank() const +{ + const auto* SoundBankRef = GetSoundBankRef(); + if (UNLIKELY(!SoundBankRef)) + { + return nullptr; + } + return SoundBankRef->GetSoundBank(); +} + +const FWwiseMetadataMedia* FWwiseAnyRef::GetMedia() const +{ + const auto* MediaRef = GetMediaRef(); + if (UNLIKELY(!MediaRef)) + { + return nullptr; + } + return MediaRef->GetMedia(); +} + +const FWwiseMetadataPlugin* FWwiseAnyRef::GetCustomPlugin() const +{ + const auto* CustomPluginRef = GetCustomPluginRef(); + if (UNLIKELY(!CustomPluginRef)) + { + return nullptr; + } + return CustomPluginRef->GetPlugin(); +} + +const FWwiseMetadataPlugin* FWwiseAnyRef::GetPluginShareSet() const +{ + const auto* PluginShareSetRef = GetPluginShareSetRef(); + if (UNLIKELY(!PluginShareSetRef)) + { + return nullptr; + } + return PluginShareSetRef->GetPlugin(); +} + +const FWwiseMetadataPlugin* FWwiseAnyRef::GetAudioDevice() const +{ + const auto* AudioDeviceRef = GetAudioDeviceRef(); + if (UNLIKELY(!AudioDeviceRef)) + { + return nullptr; + } + return AudioDeviceRef->GetPlugin(); +} + +const FWwiseMetadataEvent* FWwiseAnyRef::GetEvent() const +{ + const auto* EventRef = GetEventRef(); + if (UNLIKELY(!EventRef)) + { + return nullptr; + } + return EventRef->GetEvent(); +} + +const FWwiseMetadataSwitchContainer* FWwiseAnyRef::GetSwitchContainer() const +{ + const auto* SwitchContainerRef = GetSwitchContainerRef(); + if (UNLIKELY(!SwitchContainerRef)) + { + return nullptr; + } + return SwitchContainerRef->GetSwitchContainer(); +} + +const FWwiseMetadataDialogueEvent* FWwiseAnyRef::GetDialogueEvent() const +{ + const auto* DialogueEventRef = GetDialogueEventRef(); + if (UNLIKELY(!DialogueEventRef)) + { + return nullptr; + } + return DialogueEventRef->GetDialogueEvent(); +} + +const FWwiseMetadataDialogueArgument* FWwiseAnyRef::GetDialogueArgument() const +{ + const auto* DialogueArgumentRef = GetDialogueArgumentRef(); + if (UNLIKELY(!DialogueArgumentRef)) + { + return nullptr; + } + return DialogueArgumentRef->GetDialogueArgument(); +} + +const FWwiseMetadataBus* FWwiseAnyRef::GetBus() const +{ + const auto* BusRef = GetBusRef(); + if (UNLIKELY(!BusRef)) + { + return nullptr; + } + return BusRef->GetBus(); +} + +const FWwiseMetadataBus* FWwiseAnyRef::GetAuxBus() const +{ + const auto* AuxBusRef = GetAuxBusRef(); + if (UNLIKELY(!AuxBusRef)) + { + return nullptr; + } + return AuxBusRef->GetAuxBus(); +} + +const FWwiseMetadataGameParameter* FWwiseAnyRef::GetGameParameter() const +{ + const auto* GameParameterRef = GetGameParameterRef(); + if (UNLIKELY(!GameParameterRef)) + { + return nullptr; + } + return GameParameterRef->GetGameParameter(); +} + +const FWwiseMetadataStateGroup* FWwiseAnyRef::GetStateGroup() const +{ + const auto* StateGroupRef = GetStateGroupRef(); + if (UNLIKELY(!StateGroupRef)) + { + return nullptr; + } + return StateGroupRef->GetStateGroup(); +} + +const FWwiseMetadataState* FWwiseAnyRef::GetState() const +{ + const auto* StateRef = GetStateRef(); + if (UNLIKELY(!StateRef)) + { + return nullptr; + } + return StateRef->GetState(); +} + +const FWwiseMetadataSwitchGroup* FWwiseAnyRef::GetSwitchGroup() const +{ + const auto* SwitchGroupRef = GetSwitchGroupRef(); + if (UNLIKELY(!SwitchGroupRef)) + { + return nullptr; + } + return SwitchGroupRef->GetSwitchGroup(); +} + +const FWwiseMetadataSwitch* FWwiseAnyRef::GetSwitch() const +{ + const auto* SwitchRef = GetSwitchRef(); + if (UNLIKELY(!SwitchRef)) + { + return nullptr; + } + return SwitchRef->GetSwitch(); +} + +const FWwiseMetadataTrigger* FWwiseAnyRef::GetTrigger() const +{ + const auto* TriggerRef = GetTriggerRef(); + if (UNLIKELY(!TriggerRef)) + { + return nullptr; + } + return TriggerRef->GetTrigger(); +} + +const FWwiseMetadataExternalSource* FWwiseAnyRef::GetExternalSource() const +{ + const auto* ExternalSourceRef = GetExternalSourceRef(); + if (UNLIKELY(!ExternalSourceRef)) + { + return nullptr; + } + return ExternalSourceRef->GetExternalSource(); +} + +const FWwiseMetadataAcousticTexture* FWwiseAnyRef::GetAcousticTexture() const +{ + const auto* AcousticTextureRef = GetAcousticTextureRef(); + if (UNLIKELY(!AcousticTextureRef)) + { + return nullptr; + } + return AcousticTextureRef->GetAcousticTexture(); +} + +bool FWwiseAnyRef::GetRef(FWwiseRefLanguage& OutRef) const +{ + const auto* Result = GetLanguageRef(); + if (UNLIKELY(!Result)) + { + return false; + } + OutRef = *Result; + return true; +} + +bool FWwiseAnyRef::GetRef(FWwiseRefPlatform& OutRef) const +{ + const auto* Result = GetPlatformRef(); + if (UNLIKELY(!Result)) + { + return false; + } + OutRef = *Result; + return true; +} + +bool FWwiseAnyRef::GetRef(FWwiseRefPluginLib& OutRef) const +{ + const auto* Result = GetPluginLibRef(); + if (UNLIKELY(!Result)) + { + return false; + } + OutRef = *Result; + return true; +} + +bool FWwiseAnyRef::GetRef(FWwiseRefSoundBank& OutRef) const +{ + const auto* Result = GetSoundBankRef(); + if (UNLIKELY(!Result)) + { + return false; + } + OutRef = *Result; + return true; +} + +bool FWwiseAnyRef::GetRef(FWwiseRefMedia& OutRef) const +{ + const auto* Result = GetMediaRef(); + if (UNLIKELY(!Result)) + { + return false; + } + OutRef = *Result; + return true; +} + +bool FWwiseAnyRef::GetRef(FWwiseRefCustomPlugin& OutRef) const +{ + const auto* Result = GetCustomPluginRef(); + if (UNLIKELY(!Result)) + { + return false; + } + OutRef = *Result; + return true; +} + +bool FWwiseAnyRef::GetRef(FWwiseRefPluginShareSet& OutRef) const +{ + const auto* Result = GetPluginShareSetRef(); + if (UNLIKELY(!Result)) + { + return false; + } + OutRef = *Result; + return true; +} + +bool FWwiseAnyRef::GetRef(FWwiseRefAudioDevice& OutRef) const +{ + const auto* Result = GetAudioDeviceRef(); + if (UNLIKELY(!Result)) + { + return false; + } + OutRef = *Result; + return true; +} + +bool FWwiseAnyRef::GetRef(FWwiseRefEvent& OutRef) const +{ + const auto* Result = GetEventRef(); + if (UNLIKELY(!Result)) + { + return false; + } + OutRef = *Result; + return true; +} + +bool FWwiseAnyRef::GetRef(FWwiseRefSwitchContainer& OutRef) const +{ + const auto* Result = GetSwitchContainerRef(); + if (UNLIKELY(!Result)) + { + return false; + } + OutRef = *Result; + return true; +} + +bool FWwiseAnyRef::GetRef(FWwiseRefDialogueEvent& OutRef) const +{ + const auto* Result = GetDialogueEventRef(); + if (UNLIKELY(!Result)) + { + return false; + } + OutRef = *Result; + return true; +} + +bool FWwiseAnyRef::GetRef(FWwiseRefDialogueArgument& OutRef) const +{ + const auto* Result = GetDialogueArgumentRef(); + if (UNLIKELY(!Result)) + { + return false; + } + OutRef = *Result; + return true; +} + +bool FWwiseAnyRef::GetRef(FWwiseRefBus& OutRef) const +{ + const auto* Result = GetBusRef(); + if (UNLIKELY(!Result)) + { + return false; + } + OutRef = *Result; + return true; +} + +bool FWwiseAnyRef::GetRef(FWwiseRefAuxBus& OutRef) const +{ + const auto* Result = GetAuxBusRef(); + if (UNLIKELY(!Result)) + { + return false; + } + OutRef = *Result; + return true; +} + +bool FWwiseAnyRef::GetRef(FWwiseRefGameParameter& OutRef) const +{ + const auto* Result = GetGameParameterRef(); + if (UNLIKELY(!Result)) + { + return false; + } + OutRef = *Result; + return true; +} + +bool FWwiseAnyRef::GetRef(FWwiseRefStateGroup& OutRef) const +{ + const auto* Result = GetStateGroupRef(); + if (UNLIKELY(!Result)) + { + return false; + } + OutRef = *Result; + return true; +} + +bool FWwiseAnyRef::GetRef(FWwiseRefState& OutRef) const +{ + const auto* Result = GetStateRef(); + if (UNLIKELY(!Result)) + { + return false; + } + OutRef = *Result; + return true; +} + +bool FWwiseAnyRef::GetRef(FWwiseRefSwitchGroup& OutRef) const +{ + const auto* Result = GetSwitchGroupRef(); + if (UNLIKELY(!Result)) + { + return false; + } + OutRef = *Result; + return true; +} + +bool FWwiseAnyRef::GetRef(FWwiseRefSwitch& OutRef) const +{ + const auto* Result = GetSwitchRef(); + if (UNLIKELY(!Result)) + { + return false; + } + OutRef = *Result; + return true; +} + +bool FWwiseAnyRef::GetRef(FWwiseRefTrigger& OutRef) const +{ + const auto* Result = GetTriggerRef(); + if (UNLIKELY(!Result)) + { + return false; + } + OutRef = *Result; + return true; +} + +bool FWwiseAnyRef::GetRef(FWwiseRefExternalSource& OutRef) const +{ + const auto* Result = GetExternalSourceRef(); + if (UNLIKELY(!Result)) + { + return false; + } + OutRef = *Result; + return true; +} + +bool FWwiseAnyRef::GetRef(FWwiseRefAcousticTexture& OutRef) const +{ + const auto* Result = GetAcousticTextureRef(); + if (UNLIKELY(!Result)) + { + return false; + } + OutRef = *Result; + return true; +} + +FGuid FWwiseAnyRef::GetGuid(const EWwiseRefType* TypeOverride) const +{ + const auto Type = TypeOverride ? *TypeOverride : GetType(); + switch (Type) + { + case EWwiseRefType::RootFile: return {}; + case EWwiseRefType::ProjectInfo: return {}; + case EWwiseRefType::Language: return GetLanguage()->GUID; + case EWwiseRefType::PlatformInfo: return {}; + case EWwiseRefType::Platform: return GetPlatformReference()->GUID; + case EWwiseRefType::PluginInfo: return {}; + case EWwiseRefType::PluginLib: return {}; + case EWwiseRefType::SoundBanksInfo: return {}; + case EWwiseRefType::SoundBank: return GetSoundBank()->GUID; + case EWwiseRefType::Media: return {}; + case EWwiseRefType::CustomPlugin: return GetCustomPlugin()->GUID; + case EWwiseRefType::PluginShareSet: return GetPluginShareSet()->GUID; + case EWwiseRefType::AudioDevice: return GetAudioDevice()->GUID; + case EWwiseRefType::Event: return GetEvent()->GUID; + case EWwiseRefType::SwitchContainer: return {}; + case EWwiseRefType::DialogueEvent: return GetDialogueEvent()->GUID; + case EWwiseRefType::DialogueArgument: return GetDialogueArgument()->GUID; + case EWwiseRefType::Bus: return GetBus()->GUID; + case EWwiseRefType::AuxBus: return GetAuxBus()->GUID; + case EWwiseRefType::GameParameter: return GetGameParameter()->GUID; + case EWwiseRefType::StateGroup: return GetStateGroup()->GUID; + case EWwiseRefType::State: return GetState()->GUID; + case EWwiseRefType::SwitchGroup: return GetSwitchGroup()->GUID; + case EWwiseRefType::Switch: return GetSwitch()->GUID; + case EWwiseRefType::Trigger: return GetTrigger()->GUID; + case EWwiseRefType::ExternalSource: return GetExternalSource()->GUID; + case EWwiseRefType::AcousticTexture: return GetAcousticTexture()->GUID; + case EWwiseRefType::None: + default: + return {}; + } +} + +uint32 FWwiseAnyRef::GetGroupId(const EWwiseRefType* TypeOverride) const +{ + const auto Type = TypeOverride ? *TypeOverride : GetType(); + switch (Type) + { + case EWwiseRefType::RootFile: return 0; + case EWwiseRefType::ProjectInfo: return 0; + case EWwiseRefType::Language: return 0; + case EWwiseRefType::PlatformInfo: return 0; + case EWwiseRefType::Platform: return 0; + case EWwiseRefType::PluginInfo: return 0; + case EWwiseRefType::PluginLib: return 0; + case EWwiseRefType::SoundBanksInfo: return 0; + case EWwiseRefType::SoundBank: return 0; + case EWwiseRefType::Media: return 0; + case EWwiseRefType::CustomPlugin: return 0; + case EWwiseRefType::PluginShareSet: return 0; + case EWwiseRefType::AudioDevice: return 0; + case EWwiseRefType::Event: return 0; + case EWwiseRefType::SwitchContainer: return 0; + case EWwiseRefType::DialogueEvent: return 0; + case EWwiseRefType::DialogueArgument: return 0; + case EWwiseRefType::Bus: return 0; + case EWwiseRefType::AuxBus: return 0; + case EWwiseRefType::GameParameter: return 0; + case EWwiseRefType::StateGroup: return 0; + case EWwiseRefType::State: return GetStateGroup()->Id; + case EWwiseRefType::SwitchGroup: return 0; + case EWwiseRefType::Switch: return GetSwitchGroup()->Id; + case EWwiseRefType::Trigger: return 0; + case EWwiseRefType::ExternalSource: return 0; + case EWwiseRefType::AcousticTexture: return 0; + case EWwiseRefType::None: + default: + return 0; + } +} + +uint32 FWwiseAnyRef::GetId(const EWwiseRefType* TypeOverride) const +{ + const auto Type = TypeOverride ? *TypeOverride : GetType(); + switch (Type) + { + case EWwiseRefType::RootFile: return 0; + case EWwiseRefType::ProjectInfo: return 0; + case EWwiseRefType::Language: return GetLanguage()->Id; + case EWwiseRefType::PlatformInfo: return 0; + case EWwiseRefType::Platform: return 0; + case EWwiseRefType::PluginInfo: return 0; + case EWwiseRefType::PluginLib: return 0; + case EWwiseRefType::SoundBanksInfo: return 0; + case EWwiseRefType::SoundBank: return GetSoundBank()->Id; + case EWwiseRefType::Media: return GetMedia()->Id; + case EWwiseRefType::CustomPlugin: return GetCustomPlugin()->Id; + case EWwiseRefType::PluginShareSet: return GetPluginShareSet()->Id; + case EWwiseRefType::AudioDevice: return GetAudioDevice()->Id; + case EWwiseRefType::Event: return GetEvent()->Id; + case EWwiseRefType::SwitchContainer: return 0; + case EWwiseRefType::DialogueEvent: return GetDialogueEvent()->Id; + case EWwiseRefType::DialogueArgument: return GetDialogueArgument()->Id; + case EWwiseRefType::Bus: return GetBus()->Id; + case EWwiseRefType::AuxBus: return GetAuxBus()->Id; + case EWwiseRefType::GameParameter: return GetGameParameter()->Id; + case EWwiseRefType::StateGroup: return GetStateGroup()->Id; + case EWwiseRefType::State: return GetState()->Id; + case EWwiseRefType::SwitchGroup: return GetSwitchGroup()->Id; + case EWwiseRefType::Switch: return GetSwitch()->Id; + case EWwiseRefType::Trigger: return GetTrigger()->Id; + case EWwiseRefType::ExternalSource: return GetExternalSource()->Cookie; + case EWwiseRefType::AcousticTexture: return GetAcousticTexture()->Id; + case EWwiseRefType::None: + default: + return 0; + } +} + +FName FWwiseAnyRef::GetName(const EWwiseRefType* TypeOverride) const +{ + const auto Type = TypeOverride ? *TypeOverride : GetType(); + switch (Type) + { + case EWwiseRefType::RootFile: return {}; + case EWwiseRefType::ProjectInfo: return {}; + case EWwiseRefType::Language: return GetLanguage()->Name; + case EWwiseRefType::PlatformInfo: return {}; + case EWwiseRefType::Platform: return GetPlatformReference()->Name; + case EWwiseRefType::PluginInfo: return {}; + case EWwiseRefType::PluginLib: return {}; + case EWwiseRefType::SoundBanksInfo: return {}; + case EWwiseRefType::SoundBank: return GetSoundBank()->ShortName; + case EWwiseRefType::Media: return GetMedia()->ShortName; + case EWwiseRefType::CustomPlugin: return GetCustomPlugin()->Name; + case EWwiseRefType::PluginShareSet: return GetPluginShareSet()->Name; + case EWwiseRefType::AudioDevice: return GetAudioDevice()->Name; + case EWwiseRefType::Event: return GetEvent()->Name; + case EWwiseRefType::SwitchContainer: return {}; + case EWwiseRefType::DialogueEvent: return GetDialogueEvent()->Name; + case EWwiseRefType::DialogueArgument: return GetDialogueArgument()->Name; + case EWwiseRefType::Bus: return GetBus()->Name; + case EWwiseRefType::AuxBus: return GetAuxBus()->Name; + case EWwiseRefType::GameParameter: return GetGameParameter()->Name; + case EWwiseRefType::StateGroup: return GetStateGroup()->Name; + case EWwiseRefType::State: return GetState()->Name; + case EWwiseRefType::SwitchGroup: return GetSwitchGroup()->Name; + case EWwiseRefType::Switch: return GetSwitch()->Name; + case EWwiseRefType::Trigger: return GetTrigger()->Name; + case EWwiseRefType::ExternalSource: return GetExternalSource()->Name; + case EWwiseRefType::AcousticTexture: return GetAcousticTexture()->Name; + case EWwiseRefType::None: + default: + return {}; + } +} + +FName FWwiseAnyRef::GetObjectPath(const EWwiseRefType* TypeOverride) const +{ + const auto Type = TypeOverride ? *TypeOverride : GetType(); + switch (Type) + { + case EWwiseRefType::RootFile: return {}; + case EWwiseRefType::ProjectInfo: return {}; + case EWwiseRefType::Language: return {}; + case EWwiseRefType::PlatformInfo: return {}; + case EWwiseRefType::Platform: return {}; + case EWwiseRefType::PluginInfo: return {}; + case EWwiseRefType::PluginLib: return {}; + case EWwiseRefType::SoundBanksInfo: return {}; + case EWwiseRefType::SoundBank: return GetSoundBank()->ObjectPath; + case EWwiseRefType::Media: return {}; + case EWwiseRefType::CustomPlugin: return GetCustomPlugin()->ObjectPath; + case EWwiseRefType::PluginShareSet: return GetPluginShareSet()->ObjectPath; + case EWwiseRefType::AudioDevice: return GetAudioDevice()->ObjectPath; + case EWwiseRefType::Event: return GetEvent()->ObjectPath; + case EWwiseRefType::SwitchContainer: return {}; + case EWwiseRefType::DialogueEvent: return GetDialogueEvent()->ObjectPath; + case EWwiseRefType::DialogueArgument: return GetDialogueArgument()->ObjectPath; + case EWwiseRefType::Bus: return GetBus()->ObjectPath; + case EWwiseRefType::AuxBus: return GetAuxBus()->ObjectPath; + case EWwiseRefType::GameParameter: return GetGameParameter()->ObjectPath; + case EWwiseRefType::StateGroup: return GetStateGroup()->ObjectPath; + case EWwiseRefType::State: return GetState()->ObjectPath; + case EWwiseRefType::SwitchGroup: return GetSwitchGroup()->ObjectPath; + case EWwiseRefType::Switch: return GetSwitch()->ObjectPath; + case EWwiseRefType::Trigger: return GetTrigger()->ObjectPath; + case EWwiseRefType::ExternalSource: return GetExternalSource()->ObjectPath; + case EWwiseRefType::AcousticTexture: return GetAcousticTexture()->ObjectPath; + case EWwiseRefType::None: + default: + return {}; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefAcousticTexture.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefAcousticTexture.cpp new file mode 100644 index 0000000..8859c80 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefAcousticTexture.cpp @@ -0,0 +1,91 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefAcousticTexture.h" +#include "Wwise/WwiseProjectDatabaseModule.h" + +#include "Wwise/Metadata/WwiseMetadataAcousticTexture.h" +#include "Wwise/Metadata/WwiseMetadataSoundBank.h" +#include "Wwise/Stats/ProjectDatabase.h" + +const TCHAR* const FWwiseRefAcousticTexture::NAME = TEXT("AcousticTexture"); + +const FWwiseMetadataAcousticTexture* FWwiseRefAcousticTexture::GetAcousticTexture() const +{ + const auto* SoundBank = GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + return nullptr; + } + const auto& AcousticTextures = SoundBank->AcousticTextures; + if (AcousticTextures.IsValidIndex(AcousticTextureIndex)) + { + return &AcousticTextures[AcousticTextureIndex]; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get Acoustic Texture index #%zu"), AcousticTextureIndex); + return nullptr; + } +} + +uint32 FWwiseRefAcousticTexture::AcousticTextureId() const +{ + const auto* AcousticTexture = GetAcousticTexture(); + if (UNLIKELY(!AcousticTexture)) + { + return 0; + } + return AcousticTexture->Id; +} + +FGuid FWwiseRefAcousticTexture::AcousticTextureGuid() const +{ + const auto* AcousticTexture = GetAcousticTexture(); + if (UNLIKELY(!AcousticTexture)) + { + return {}; + } + return AcousticTexture->GUID; +} + +FName FWwiseRefAcousticTexture::AcousticTextureName() const +{ + const auto* AcousticTexture = GetAcousticTexture(); + if (UNLIKELY(!AcousticTexture)) + { + return {}; + } + return AcousticTexture->Name; +} + +FName FWwiseRefAcousticTexture::AcousticTextureObjectPath() const +{ + const auto* AcousticTexture = GetAcousticTexture(); + if (UNLIKELY(!AcousticTexture)) + { + return {}; + } + return AcousticTexture->ObjectPath; +} + +uint32 FWwiseRefAcousticTexture::Hash() const +{ + auto Result = FWwiseRefSoundBank::Hash(); + Result = HashCombine(Result, GetTypeHash(AcousticTextureIndex)); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefAudioDevice.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefAudioDevice.cpp new file mode 100644 index 0000000..dbf5784 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefAudioDevice.cpp @@ -0,0 +1,119 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefAudioDevice.h" +#include "Wwise/WwiseProjectDatabaseModule.h" + +#include "Wwise/Metadata/WwiseMetadataPlugin.h" +#include "Wwise/Metadata/WwiseMetadataPluginGroup.h" +#include "Wwise/Metadata/WwiseMetadataSoundBank.h" +#include "Wwise/Ref/WwiseRefMedia.h" +#include "Wwise/Stats/ProjectDatabase.h" + +const TCHAR* const FWwiseRefAudioDevice::NAME = TEXT("AudioDevice"); + +const FWwiseMetadataPlugin* FWwiseRefAudioDevice::GetPlugin() const +{ + const auto* SoundBank = GetSoundBank(); + if (UNLIKELY(!SoundBank || !SoundBank->Plugins)) + { + return nullptr; + } + + const auto& Plugins = SoundBank->Plugins->AudioDevices; + if (Plugins.IsValidIndex(AudioDeviceIndex)) + { + return &Plugins[AudioDeviceIndex]; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get Audio Device index #%zu"), AudioDeviceIndex); + return nullptr; + } +} + +WwiseMediaIdsMap FWwiseRefAudioDevice::GetPluginMedia(const WwiseMediaGlobalIdsMap& GlobalMap) const +{ + const auto* AudioDevice = GetPlugin(); + const auto* SoundBank = GetSoundBank(); + if (UNLIKELY(!AudioDevice || !SoundBank)) + { + return {}; + } + const auto& Media = AudioDevice->MediaRefs; + + WwiseMediaIdsMap Result; + Result.Empty(Media.Num()); + for (const auto& Elem : Media) + { + FWwiseDatabaseMediaIdKey Id(Elem.Id, SoundBank->Id); + + const auto* MediaInGlobalMap = GlobalMap.Find(Id); + if (MediaInGlobalMap) + { + Result.Add(Elem.Id, *MediaInGlobalMap); + } + } + return Result; +} + +uint32 FWwiseRefAudioDevice::AudioDeviceId() const +{ + const auto* AudioDevice = GetPlugin(); + if (UNLIKELY(!AudioDevice)) + { + return 0; + } + return AudioDevice->Id; +} + +FGuid FWwiseRefAudioDevice::AudioDeviceGuid() const +{ + const auto* AudioDevice = GetPlugin(); + if (UNLIKELY(!AudioDevice)) + { + return {}; + } + return AudioDevice->GUID; +} + +FName FWwiseRefAudioDevice::AudioDeviceName() const +{ + const auto* AudioDevice = GetPlugin(); + if (UNLIKELY(!AudioDevice)) + { + return {}; + } + return AudioDevice->Name; +} + +FName FWwiseRefAudioDevice::AudioDeviceObjectPath() const +{ + const auto* AudioDevice = GetPlugin(); + if (UNLIKELY(!AudioDevice)) + { + return {}; + } + return AudioDevice->ObjectPath; +} + +uint32 FWwiseRefAudioDevice::Hash() const +{ + auto Result = FWwiseRefSoundBank::Hash(); + Result = HashCombine(Result, GetTypeHash(AudioDeviceIndex)); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefAuxBus.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefAuxBus.cpp new file mode 100644 index 0000000..88b5c60 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefAuxBus.cpp @@ -0,0 +1,191 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefAuxBus.h" +#include "Wwise/WwiseProjectDatabaseModule.h" + +#include "Wwise/Metadata/WwiseMetadataBus.h" +#include "Wwise/Metadata/WwiseMetadataSoundBank.h" +#include "Wwise/Ref/WwiseRefAudioDevice.h" +#include "Wwise/Ref/WwiseRefCustomPlugin.h" +#include "Wwise/Ref/WwiseRefPluginShareSet.h" +#include "Wwise/Stats/ProjectDatabase.h" +#include + +const TCHAR* const FWwiseRefAuxBus::NAME = TEXT("AuxBus"); + +const FWwiseMetadataBus* FWwiseRefAuxBus::GetAuxBus() const +{ + const auto* SoundBank = GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + return nullptr; + } + const auto& AuxBusses = SoundBank->AuxBusses; + if (AuxBusses.IsValidIndex(AuxBusIndex)) + { + return &AuxBusses[AuxBusIndex]; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get AuxBus index #%zu"), AuxBusIndex); + return nullptr; + } +} + +void FWwiseRefAuxBus::GetAllAuxBusRefs(TSet& OutAuxBusRefs, const WwiseAuxBusGlobalIdsMap& InGlobalMap) const +{ + bool bIsAlreadyInSet = false; + OutAuxBusRefs.Add(this, &bIsAlreadyInSet); + if (UNLIKELY(bIsAlreadyInSet)) // Unlikely but can still be done (circular references are possible in Aux Busses) + { + return; + } + + const auto* AuxBus = GetAuxBus(); + if (UNLIKELY(!AuxBus)) + { + return; + } + for (const auto& SubAuxBus : AuxBus->AuxBusRefs) + { + const auto* SubAuxBusRef = InGlobalMap.Find(FWwiseDatabaseLocalizableIdKey(SubAuxBus.Id, LanguageId)); + if (UNLIKELY(!SubAuxBusRef)) + { + SubAuxBusRef = InGlobalMap.Find(FWwiseDatabaseLocalizableIdKey(SubAuxBus.Id, 0)); + } + if (UNLIKELY(!SubAuxBusRef)) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get Aux Bus Id %" PRIu32), SubAuxBus.Id); + continue; + } + SubAuxBusRef->GetAllAuxBusRefs(OutAuxBusRefs, InGlobalMap); + } +} + +WwiseCustomPluginIdsMap FWwiseRefAuxBus::GetAuxBusCustomPlugins(const WwiseCustomPluginGlobalIdsMap& GlobalMap) const +{ + const auto* AuxBus = GetAuxBus(); + if (!AuxBus || !AuxBus->PluginRefs) + { + return {}; + } + const auto& Plugins = AuxBus->PluginRefs->Custom; + WwiseCustomPluginIdsMap Result; + Result.Empty(Plugins.Num()); + for (const auto& Elem : Plugins) + { + FWwiseDatabaseLocalizableIdKey Id(Elem.Id, LanguageId); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + return Result; +} + +WwisePluginShareSetIdsMap FWwiseRefAuxBus::GetAuxBusPluginShareSets(const WwisePluginShareSetGlobalIdsMap& GlobalMap) const +{ + const auto* AuxBus = GetAuxBus(); + if (!AuxBus || !AuxBus->PluginRefs) + { + return {}; + } + const auto& Plugins = AuxBus->PluginRefs->ShareSets; + WwisePluginShareSetIdsMap Result; + Result.Empty(Plugins.Num()); + for (const auto& Elem : Plugins) + { + FWwiseDatabaseLocalizableIdKey Id(Elem.Id, LanguageId); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + return Result; +} + +WwiseAudioDeviceIdsMap FWwiseRefAuxBus::GetAuxBusAudioDevices(const WwiseAudioDeviceGlobalIdsMap& GlobalMap) const +{ + const auto* AuxBus = GetAuxBus(); + if (!AuxBus || !AuxBus->PluginRefs) + { + return {}; + } + const auto& Plugins = AuxBus->PluginRefs->AudioDevices; + WwiseAudioDeviceIdsMap Result; + Result.Empty(Plugins.Num()); + for (const auto& Elem : Plugins) + { + FWwiseDatabaseLocalizableIdKey Id(Elem.Id, LanguageId); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + return Result; +} + +uint32 FWwiseRefAuxBus::AuxBusId() const +{ + const auto* AuxBus = GetAuxBus(); + if (UNLIKELY(!AuxBus)) + { + return 0; + } + return AuxBus->Id; +} + +FGuid FWwiseRefAuxBus::AuxBusGuid() const +{ + const auto* AuxBus = GetAuxBus(); + if (UNLIKELY(!AuxBus)) + { + return {}; + } + return AuxBus->GUID; +} + +FName FWwiseRefAuxBus::AuxBusName() const +{ + const auto* AuxBus = GetAuxBus(); + if (UNLIKELY(!AuxBus)) + { + return {}; + } + return AuxBus->Name; +} + +FName FWwiseRefAuxBus::AuxBusObjectPath() const +{ + const auto* AuxBus = GetAuxBus(); + if (UNLIKELY(!AuxBus)) + { + return {}; + } + return AuxBus->ObjectPath; +} + +uint32 FWwiseRefAuxBus::Hash() const +{ + auto Result = FWwiseRefSoundBank::Hash(); + Result = HashCombine(Result, GetTypeHash(AuxBusIndex)); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefBus.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefBus.cpp new file mode 100644 index 0000000..8419f1e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefBus.cpp @@ -0,0 +1,91 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefBus.h" +#include "Wwise/WwiseProjectDatabaseModule.h" + +#include "Wwise/Metadata/WwiseMetadataBus.h" +#include "Wwise/Metadata/WwiseMetadataSoundBank.h" +#include "Wwise/Stats/ProjectDatabase.h" + +const TCHAR* const FWwiseRefBus::NAME = TEXT("Bus"); + +const FWwiseMetadataBus* FWwiseRefBus::GetBus() const +{ + const auto* SoundBank = GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + return nullptr; + } + const auto& Busses = SoundBank->Busses; + if (Busses.IsValidIndex(BusIndex)) + { + return &Busses[BusIndex]; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get Bus index #%zu"), BusIndex); + return nullptr; + } +} + +uint32 FWwiseRefBus::BusId() const +{ + const auto* Bus = GetBus(); + if (UNLIKELY(!Bus)) + { + return 0; + } + return Bus->Id; +} + +FGuid FWwiseRefBus::BusGuid() const +{ + const auto* Bus = GetBus(); + if (UNLIKELY(!Bus)) + { + return {}; + } + return Bus->GUID; +} + +FName FWwiseRefBus::BusName() const +{ + const auto* Bus = GetBus(); + if (UNLIKELY(!Bus)) + { + return {}; + } + return Bus->Name; +} + +FName FWwiseRefBus::BusObjectPath() const +{ + const auto* Bus = GetBus(); + if (UNLIKELY(!Bus)) + { + return {}; + } + return Bus->ObjectPath; +} + +uint32 FWwiseRefBus::Hash() const +{ + auto Result = FWwiseRefSoundBank::Hash(); + Result = HashCombine(Result, GetTypeHash(BusIndex)); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefCustomPlugin.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefCustomPlugin.cpp new file mode 100644 index 0000000..7a955fc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefCustomPlugin.cpp @@ -0,0 +1,119 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefCustomPlugin.h" + +#include "Wwise/WwiseProjectDatabaseModule.h" +#include "Wwise/Metadata/WwiseMetadataPlugin.h" +#include "Wwise/Metadata/WwiseMetadataPluginGroup.h" +#include "Wwise/Metadata/WwiseMetadataSoundBank.h" +#include "Wwise/Ref/WwiseRefMedia.h" +#include "Wwise/Stats/ProjectDatabase.h" + +const TCHAR* const FWwiseRefCustomPlugin::NAME = TEXT("CustomPlugin"); + +const FWwiseMetadataPlugin* FWwiseRefCustomPlugin::GetPlugin() const +{ + const auto* SoundBank = GetSoundBank(); + if (UNLIKELY(!SoundBank || !SoundBank->Plugins)) + { + return nullptr; + } + + const auto& Plugins = SoundBank->Plugins->Custom; + if (Plugins.IsValidIndex(CustomPluginIndex)) + { + return &Plugins[CustomPluginIndex]; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get Custom Plugin index #%zu"), CustomPluginIndex); + return nullptr; + } +} + +WwiseMediaIdsMap FWwiseRefCustomPlugin::GetPluginMedia(const WwiseMediaGlobalIdsMap& GlobalMap) const +{ + const auto* CustomPlugin = GetPlugin(); + const auto* SoundBank = GetSoundBank(); + if (UNLIKELY(!CustomPlugin || !SoundBank)) + { + return {}; + } + const auto& Media = CustomPlugin->MediaRefs; + + WwiseMediaIdsMap Result; + Result.Empty(Media.Num()); + for (const auto& Elem : Media) + { + FWwiseDatabaseMediaIdKey Id(Elem.Id, SoundBank->Id); + + const auto* MediaInGlobalMap = GlobalMap.Find(Id); + if (MediaInGlobalMap) + { + Result.Add(Elem.Id, *MediaInGlobalMap); + } + } + return Result; +} + +uint32 FWwiseRefCustomPlugin::CustomPluginId() const +{ + const auto* CustomPlugin = GetPlugin(); + if (UNLIKELY(!CustomPlugin)) + { + return 0; + } + return CustomPlugin->Id; +} + +FGuid FWwiseRefCustomPlugin::CustomPluginGuid() const +{ + const auto* CustomPlugin = GetPlugin(); + if (UNLIKELY(!CustomPlugin)) + { + return {}; + } + return CustomPlugin->GUID; +} + +FName FWwiseRefCustomPlugin::CustomPluginName() const +{ + const auto* CustomPlugin = GetPlugin(); + if (UNLIKELY(!CustomPlugin)) + { + return {}; + } + return CustomPlugin->Name; +} + +FName FWwiseRefCustomPlugin::CustomPluginObjectPath() const +{ + const auto* CustomPlugin = GetPlugin(); + if (UNLIKELY(!CustomPlugin)) + { + return {}; + } + return CustomPlugin->ObjectPath; +} + +uint32 FWwiseRefCustomPlugin::Hash() const +{ + auto Result = FWwiseRefSoundBank::Hash(); + Result = HashCombine(Result, GetTypeHash(CustomPluginIndex)); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefDialogueArgument.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefDialogueArgument.cpp new file mode 100644 index 0000000..b7426d4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefDialogueArgument.cpp @@ -0,0 +1,89 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefDialogueArgument.h" +#include "Wwise/WwiseProjectDatabaseModule.h" +#include "Wwise/Stats/ProjectDatabase.h" +#include "Wwise/Metadata/WwiseMetadataDialogue.h" + +const TCHAR* const FWwiseRefDialogueArgument::NAME = TEXT("DialogueArgument"); + +const FWwiseMetadataDialogueArgument* FWwiseRefDialogueArgument::GetDialogueArgument() const +{ + const auto* DialogueEvent = GetDialogueEvent(); + if (UNLIKELY(!DialogueEvent)) + { + return nullptr; + } + const auto& Arguments = DialogueEvent->Arguments; + if (Arguments.IsValidIndex(DialogueArgumentIndex)) + { + return &Arguments[DialogueArgumentIndex]; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get Dialogue Argument index #%zu"), DialogueArgumentIndex); + return nullptr; + } +} + +uint32 FWwiseRefDialogueArgument::DialogueArgumentId() const +{ + const auto* DialogueArgument = GetDialogueArgument(); + if (UNLIKELY(!DialogueArgument)) + { + return 0; + } + return DialogueArgument->Id; +} + +FGuid FWwiseRefDialogueArgument::DialogueArgumentGuid() const +{ + const auto* DialogueArgument = GetDialogueArgument(); + if (UNLIKELY(!DialogueArgument)) + { + return {}; + } + return DialogueArgument->GUID; +} + +FName FWwiseRefDialogueArgument::DialogueArgumentName() const +{ + const auto* DialogueArgument = GetDialogueArgument(); + if (UNLIKELY(!DialogueArgument)) + { + return {}; + } + return DialogueArgument->Name; +} + +FName FWwiseRefDialogueArgument::DialogueArgumentObjectPath() const +{ + const auto* DialogueArgument = GetDialogueArgument(); + if (UNLIKELY(!DialogueArgument)) + { + return {}; + } + return DialogueArgument->ObjectPath; +} + +uint32 FWwiseRefDialogueArgument::Hash() const +{ + auto Result = FWwiseRefDialogueEvent::Hash(); + Result = HashCombine(Result, GetTypeHash(DialogueArgumentIndex)); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefDialogueEvent.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefDialogueEvent.cpp new file mode 100644 index 0000000..fedc7fb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefDialogueEvent.cpp @@ -0,0 +1,122 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefDialogueEvent.h" + +#include "Wwise/Ref/WwiseRefCollections.h" +#include "Wwise/Ref/WwiseRefDialogueArgument.h" +#include "Wwise/WwiseProjectDatabaseModule.h" +#include "Wwise/Metadata/WwiseMetadataDialogue.h" +#include "Wwise/Metadata/WwiseMetadataSoundBank.h" +#include "Wwise/Stats/ProjectDatabase.h" + +#include + + +const TCHAR* const FWwiseRefDialogueEvent::NAME = TEXT("DialogueEvent"); + +const FWwiseMetadataDialogueEvent* FWwiseRefDialogueEvent::GetDialogueEvent() const +{ + const auto* SoundBank = GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + return nullptr; + } + const auto& DialogueEvents = SoundBank->DialogueEvents; + if (DialogueEvents.IsValidIndex(DialogueEventIndex)) + { + return &DialogueEvents[DialogueEventIndex]; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get Dialogue Event index #%zu"), DialogueEventIndex); + return nullptr; + } +} + +WwiseDialogueArgumentIdsMap FWwiseRefDialogueEvent::GetDialogueArguments(const WwiseDialogueArgumentGlobalIdsMap& GlobalMap) const +{ + const auto* DialogueEvent = GetDialogueEvent(); + if (!DialogueEvent) + { + return {}; + } + const auto Arguments = DialogueEvent->Arguments; + WwiseDialogueArgumentIdsMap Result; + Result.Empty(Arguments.Num()); + for (const auto& Elem : Arguments) + { + const auto* GlobalRef = GlobalMap.Find(FWwiseDatabaseLocalizableIdKey(Elem.Id, LanguageId)); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get Dialogue Argument ID %" PRIu32), Elem.Id); + } + } + + return Result; +} + +uint32 FWwiseRefDialogueEvent::DialogueEventId() const +{ + const auto* DialogueEvent = GetDialogueEvent(); + if (UNLIKELY(!DialogueEvent)) + { + return 0; + } + return DialogueEvent->Id; +} + +FGuid FWwiseRefDialogueEvent::DialogueEventGuid() const +{ + const auto* DialogueEvent = GetDialogueEvent(); + if (UNLIKELY(!DialogueEvent)) + { + return {}; + } + return DialogueEvent->GUID; +} + +FName FWwiseRefDialogueEvent::DialogueEventName() const +{ + const auto* DialogueEvent = GetDialogueEvent(); + if (UNLIKELY(!DialogueEvent)) + { + return {}; + } + return DialogueEvent->Name; +} + +FName FWwiseRefDialogueEvent::DialogueEventObjectPath() const +{ + const auto* DialogueEvent = GetDialogueEvent(); + if (UNLIKELY(!DialogueEvent)) + { + return {}; + } + return DialogueEvent->ObjectPath; +} + +uint32 FWwiseRefDialogueEvent::Hash() const +{ + auto Result = FWwiseRefSoundBank::Hash(); + Result = HashCombine(Result, GetTypeHash(DialogueEventIndex)); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefEvent.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefEvent.cpp new file mode 100644 index 0000000..9d8a0a8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefEvent.cpp @@ -0,0 +1,583 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefEvent.h" + +#include "Wwise/Ref/WwiseRefAudioDevice.h" +#include "Wwise/Ref/WwiseRefAuxBus.h" +#include "Wwise/Ref/WwiseRefCustomPlugin.h" +#include "Wwise/Ref/WwiseRefExternalSource.h" +#include "Wwise/Ref/WwiseRefMedia.h" +#include "Wwise/Ref/WwiseRefPluginShareSet.h" +#include "Wwise/Ref/WwiseRefState.h" +#include "Wwise/Ref/WwiseRefSwitch.h" +#include "Wwise/Ref/WwiseRefSwitchContainer.h" +#include "Wwise/Ref/WwiseRefTrigger.h" +#include "Wwise/WwiseProjectDatabaseModule.h" + +#include "Wwise/Metadata/WwiseMetadataEvent.h" +#include "Wwise/Metadata/WwiseMetadataPlugin.h" +#include "Wwise/Metadata/WwiseMetadataPluginGroup.h" +#include "Wwise/Metadata/WwiseMetadataSoundBank.h" + +#include "Wwise/Stats/ProjectDatabase.h" + +#include + +const TCHAR* const FWwiseRefEvent::NAME = TEXT("Event"); + +const FWwiseMetadataEvent* FWwiseRefEvent::GetEvent() const +{ + const auto* SoundBank = GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + return nullptr; + } + const auto& Events = SoundBank->Events; + if (Events.IsValidIndex(EventIndex)) + { + return &Events[EventIndex]; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get Event index #%zu"), EventIndex); + return nullptr; + } +} + +WwiseMediaIdsMap FWwiseRefEvent::GetEventMedia(const WwiseMediaGlobalIdsMap& GlobalMap) const +{ + const auto* SoundBank = GetSoundBank(); + const auto* Event = GetEvent(); + if (!Event || !SoundBank) + { + return {}; + } + const auto& MediaRefs = Event->MediaRefs; + WwiseMediaIdsMap Result; + Result.Empty(MediaRefs.Num()); + for (const auto& Elem : MediaRefs) + { + FWwiseDatabaseMediaIdKey Id(Elem.Id, SoundBank->Id); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + return Result; +} + +WwiseMediaIdsMap FWwiseRefEvent::GetAllMedia(const WwiseMediaGlobalIdsMap& GlobalMap) const +{ + const auto* SoundBank = GetSoundBank(); + const auto* Event = GetEvent(); + if (!Event || !SoundBank) + { + return {}; + } + WwiseMediaIdsMap Result = GetEventMedia(GlobalMap); + + const auto& SwitchContainers = Event->SwitchContainers; + for (const auto& SwitchContainer : SwitchContainers) + { + for (const auto& Elem : SwitchContainer.GetAllMedia()) + { + FWwiseDatabaseMediaIdKey Id(Elem.Id, SoundBank->Id); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + } + + if (Event->PluginRefs && SoundBank->Plugins) + { + const auto& CustomPlugins = Event->PluginRefs->ShareSets; + for (const auto& CustomPlugin : CustomPlugins) + { + const auto PluginId = CustomPlugin.Id; + const auto* Plugin = SoundBank->Plugins->Custom.FindByPredicate([PluginId](const FWwiseMetadataPlugin& RhsValue) + { + return RhsValue.Id == PluginId; + }); + if (LIKELY(Plugin)) + { + for (const auto& Elem : Plugin->MediaRefs) + { + FWwiseDatabaseMediaIdKey Id(Elem.Id, SoundBank->Id); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Cannot find Plugin %" PRIu32), PluginId); + } + } + + const auto& PluginShareSets = Event->PluginRefs->ShareSets; + for (const auto& PluginShareSet : PluginShareSets) + { + const auto PluginId = PluginShareSet.Id; + const auto* Plugin = SoundBank->Plugins->ShareSets.FindByPredicate([PluginId](const FWwiseMetadataPlugin& RhsValue) + { + return RhsValue.Id == PluginId; + }); + if (LIKELY(Plugin)) + { + for (const auto& Elem : Plugin->MediaRefs) + { + FWwiseDatabaseMediaIdKey Id(Elem.Id, SoundBank->Id); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Cannot find Plugin %" PRIu32), PluginId); + } + } + + const auto& AudioDevices = Event->PluginRefs->AudioDevices; + for (const auto& AudioDevice : AudioDevices) + { + const auto PluginId = AudioDevice.Id; + const auto* Plugin = SoundBank->Plugins->AudioDevices.FindByPredicate([PluginId](const FWwiseMetadataPlugin& RhsValue) + { + return RhsValue.Id == PluginId; + }); + if (LIKELY(Plugin)) + { + for (const auto& Elem : Plugin->MediaRefs) + { + FWwiseDatabaseMediaIdKey Id(Elem.Id, SoundBank->Id); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Cannot find Plugin %" PRIu32), PluginId); + } + } + } + return Result; +} + +WwiseExternalSourceIdsMap FWwiseRefEvent::GetEventExternalSources(const WwiseExternalSourceGlobalIdsMap& GlobalMap) const +{ + const auto* Event = GetEvent(); + if (!Event) + { + return {}; + } + const auto& ExternalSourceRefs = Event->ExternalSourceRefs; + WwiseExternalSourceIdsMap Result; + Result.Empty(ExternalSourceRefs.Num()); + for (const auto& Elem : ExternalSourceRefs) + { + FWwiseDatabaseLocalizableIdKey Id(Elem.Cookie, 0); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Cookie, *GlobalRef); + } + } + return Result; +} + +WwiseExternalSourceIdsMap FWwiseRefEvent::GetAllExternalSources(const WwiseExternalSourceGlobalIdsMap& GlobalMap) const +{ + const auto* Event = GetEvent(); + if (!Event) + { + return {}; + } + WwiseExternalSourceIdsMap Result = GetEventExternalSources(GlobalMap); + + const auto& SwitchContainers = Event->SwitchContainers; + for (const auto& SwitchContainer : SwitchContainers) + { + for (const auto& Elem : SwitchContainer.GetAllExternalSources()) + { + FWwiseDatabaseLocalizableIdKey Id(Elem.Cookie, LanguageId); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Cookie, *GlobalRef); + } + } + } + return Result; +} + +WwiseCustomPluginIdsMap FWwiseRefEvent::GetEventCustomPlugins(const WwiseCustomPluginGlobalIdsMap& GlobalMap) const +{ + const auto* Event = GetEvent(); + if (!Event || !Event->PluginRefs) + { + return {}; + } + const auto& Plugins = Event->PluginRefs->Custom; + WwiseCustomPluginIdsMap Result; + Result.Empty(Plugins.Num()); + for (const auto& Elem : Plugins) + { + FWwiseDatabaseLocalizableIdKey Id(Elem.Id, LanguageId); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + return Result; +} + +WwiseCustomPluginIdsMap FWwiseRefEvent::GetAllCustomPlugins(const WwiseCustomPluginGlobalIdsMap& GlobalMap) const +{ + const auto* Event = GetEvent(); + if (!Event || !Event->PluginRefs) + { + return {}; + } + WwiseCustomPluginIdsMap Result = GetEventCustomPlugins(GlobalMap); + + const auto& SwitchContainers = Event->SwitchContainers; + for (const auto& SwitchContainer : SwitchContainers) + { + for (const auto& Elem : SwitchContainer.GetAllCustomPlugins()) + { + FWwiseDatabaseLocalizableIdKey Id(Elem.Id, LanguageId); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + } + return Result; +} + +WwisePluginShareSetIdsMap FWwiseRefEvent::GetEventPluginShareSets(const WwisePluginShareSetGlobalIdsMap& GlobalMap) const +{ + const auto* Event = GetEvent(); + if (!Event || !Event->PluginRefs) + { + return {}; + } + const auto& Plugins = Event->PluginRefs->ShareSets; + WwisePluginShareSetIdsMap Result; + Result.Empty(Plugins.Num()); + for (const auto& Elem : Plugins) + { + FWwiseDatabaseLocalizableIdKey Id(Elem.Id, LanguageId); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + return Result; +} + +WwisePluginShareSetIdsMap FWwiseRefEvent::GetAllPluginShareSets(const WwisePluginShareSetGlobalIdsMap& GlobalMap) const +{ + const auto* Event = GetEvent(); + if (!Event || !Event->PluginRefs) + { + return {}; + } + WwisePluginShareSetIdsMap Result = GetEventPluginShareSets(GlobalMap); + + const auto& SwitchContainers = Event->SwitchContainers; + for (const auto& SwitchContainer : SwitchContainers) + { + for (const auto& Elem : SwitchContainer.GetAllPluginShareSets()) + { + FWwiseDatabaseLocalizableIdKey Id(Elem.Id, LanguageId); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + } + return Result; +} + +WwiseAudioDeviceIdsMap FWwiseRefEvent::GetEventAudioDevices(const WwiseAudioDeviceGlobalIdsMap& GlobalMap) const +{ + const auto* Event = GetEvent(); + if (!Event || !Event->PluginRefs) + { + return {}; + } + const auto& Plugins = Event->PluginRefs->AudioDevices; + WwiseAudioDeviceIdsMap Result; + Result.Empty(Plugins.Num()); + for (const auto& Elem : Plugins) + { + FWwiseDatabaseLocalizableIdKey Id(Elem.Id, LanguageId); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + return Result; +} + +WwiseAudioDeviceIdsMap FWwiseRefEvent::GetAllAudioDevices(const WwiseAudioDeviceGlobalIdsMap& GlobalMap) const +{ + const auto* Event = GetEvent(); + if (!Event || !Event->PluginRefs) + { + return {}; + } + WwiseAudioDeviceIdsMap Result = GetEventAudioDevices(GlobalMap); + + const auto& SwitchContainers = Event->SwitchContainers; + for (const auto& SwitchContainer : SwitchContainers) + { + for (const auto& Elem : SwitchContainer.GetAllPluginShareSets()) + { + FWwiseDatabaseLocalizableIdKey Id(Elem.Id, LanguageId); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + } + return Result; +} + +WwiseSwitchContainerArray FWwiseRefEvent::GetSwitchContainers(const WwiseSwitchContainersByEvent& GlobalMap) const +{ + const auto* Event = GetEvent(); + if (!Event) + { + return {}; + } + FWwiseDatabaseLocalizableIdKey LocId(Event->Id, LanguageId); + + WwiseSwitchContainerArray Result; + GlobalMap.MultiFind(LocId, Result); + return Result; +} + +WwiseEventIdsMap FWwiseRefEvent::GetActionPostEvent(const WwiseEventGlobalIdsMap& GlobalMap) const +{ + const auto* Event = GetEvent(); + if (!Event) + { + return {}; + } + const auto& PostEvents = Event->ActionPostEvent; + WwiseEventIdsMap Result; + Result.Empty(PostEvents.Num()); + for (const auto& PostEvent : PostEvents) + { + const auto* GlobalRef = GlobalMap.Find(FWwiseDatabaseLocalizableIdKey(PostEvent.Id, LanguageId, SoundBankId())); + if (GlobalRef) + { + Result.Add(PostEvent.Id, *GlobalRef); + } + } + + return Result; +} + +WwiseStateIdsMap FWwiseRefEvent::GetActionSetState(const WwiseStateGlobalIdsMap& GlobalMap) const +{ + const auto* Event = GetEvent(); + if (!Event) + { + return {}; + } + const auto& SetStates = Event->ActionSetState; + WwiseStateIdsMap Result; + Result.Empty(SetStates.Num()); + for (const auto& SetState : SetStates) + { + const auto* StateRef = GlobalMap.Find(FWwiseDatabaseLocalizableGroupValueKey(SetState.GroupId, SetState.Id, LanguageId)); + if (StateRef) + { + const auto* State = StateRef->GetState(); + const auto* StateGroup = StateRef->GetStateGroup(); + if (State && StateGroup) + { + Result.Add(FWwiseDatabaseGroupValueKey(StateGroup->Id, State->Id), *StateRef); + } + } + } + + return Result; +} + +WwiseSwitchIdsMap FWwiseRefEvent::GetActionSetSwitch(const WwiseSwitchGlobalIdsMap& GlobalMap) const +{ + const auto* Event = GetEvent(); + if (!Event) + { + return {}; + } + const auto& SetSwitches = Event->ActionSetSwitch; + WwiseSwitchIdsMap Result; + Result.Empty(SetSwitches.Num()); + for (const auto& SetSwitch : SetSwitches) + { + const auto* SwitchRef = GlobalMap.Find(FWwiseDatabaseLocalizableGroupValueKey(SetSwitch.GroupId, SetSwitch.Id, LanguageId)); + if (SwitchRef) + { + const auto* Switch = SwitchRef->GetSwitch(); + const auto* SwitchGroup = SwitchRef->GetSwitchGroup(); + if (Switch && SwitchGroup) + { + Result.Add(FWwiseDatabaseGroupValueKey(SwitchGroup->Id, Switch->Id), *SwitchRef); + } + } + } + + return Result; +} + +WwiseTriggerIdsMap FWwiseRefEvent::GetActionTrigger(const WwiseTriggerGlobalIdsMap& GlobalMap) const +{ + const auto* Event = GetEvent(); + if (!Event) + { + return {}; + } + const auto& Triggers = Event->ActionTrigger; + WwiseTriggerIdsMap Result; + Result.Empty(Triggers.Num()); + for (const auto& Trigger : Triggers) + { + const auto* GlobalRef = GlobalMap.Find(FWwiseDatabaseLocalizableIdKey(Trigger.Id, LanguageId)); + if (GlobalRef) + { + Result.Add(Trigger.Id, *GlobalRef); + } + } + + return Result; +} + +WwiseAuxBusIdsMap FWwiseRefEvent::GetEventAuxBusses(const WwiseAuxBusGlobalIdsMap& GlobalMap) const +{ + const auto* Event = GetEvent(); + if (!Event) + { + return {}; + } + const auto& AuxBusRefs = Event->AuxBusRefs; + WwiseAuxBusIdsMap Result; + Result.Empty(AuxBusRefs.Num()); + for (const auto& AuxBusRef : AuxBusRefs) + { + const auto* GlobalRef = GlobalMap.Find(FWwiseDatabaseLocalizableIdKey(AuxBusRef.Id, LanguageId)); + if (GlobalRef) + { + Result.Add(AuxBusRef.Id, *GlobalRef); + } + } + + return Result; +} + +uint32 FWwiseRefEvent::EventId() const +{ + const auto* Event = GetEvent(); + if (UNLIKELY(!Event)) + { + return {}; + } + return Event->Id; +} + +FGuid FWwiseRefEvent::EventGuid() const +{ + const auto* Event = GetEvent(); + if (UNLIKELY(!Event)) + { + return {}; + } + return Event->GUID; +} + +FName FWwiseRefEvent::EventName() const +{ + const auto* Event = GetEvent(); + if (UNLIKELY(!Event)) + { + return {}; + } + return Event->Name; +} + +FName FWwiseRefEvent::EventObjectPath() const +{ + const auto* Event = GetEvent(); + if (UNLIKELY(!Event)) + { + return {}; + } + return Event->ObjectPath; +} + +uint32 FWwiseRefEvent::MaxAttenuation() const +{ + const auto* Event = GetEvent(); + if (UNLIKELY(!Event)) + { + return 0; + } + return Event->MaxAttenuation; +} + +bool FWwiseRefEvent::GetDuration(EWwiseMetadataEventDurationType& OutDurationType, float& OutDurationMin, float& OutDurationMax) const +{ + const auto* Event = GetEvent(); + if (UNLIKELY(!Event)) + { + return false; + } + + OutDurationType = Event->DurationType; + OutDurationMin = Event->DurationMin; + OutDurationMax = Event->DurationMax; + return true; +} + +uint32 FWwiseRefEvent::Hash() const +{ + auto Result = FWwiseRefSoundBank::Hash(); + Result = HashCombine(Result, GetTypeHash(EventIndex)); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefExternalSource.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefExternalSource.cpp new file mode 100644 index 0000000..d12edfa --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefExternalSource.cpp @@ -0,0 +1,90 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefExternalSource.h" +#include "Wwise/WwiseProjectDatabaseModule.h" +#include "Wwise/Stats/ProjectDatabase.h" +#include "Wwise/Metadata/WwiseMetadataExternalSource.h" +#include "Wwise/Metadata/WwiseMetadataSoundBank.h" + +const TCHAR* const FWwiseRefExternalSource::NAME = TEXT("ExternalSource"); + +const FWwiseMetadataExternalSource* FWwiseRefExternalSource::GetExternalSource() const +{ + const auto* SoundBank = GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + return nullptr; + } + const auto& ExternalSources = SoundBank->ExternalSources; + if (ExternalSources.IsValidIndex(ExternalSourceIndex)) + { + return &ExternalSources[ExternalSourceIndex]; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get External Source index #%zu"), ExternalSourceIndex); + return nullptr; + } +} + +uint32 FWwiseRefExternalSource::ExternalSourceCookie() const +{ + const auto* ExternalSource = GetExternalSource(); + if (UNLIKELY(!ExternalSource)) + { + return {}; + } + return ExternalSource->Cookie; +} + +FGuid FWwiseRefExternalSource::ExternalSourceGuid() const +{ + const auto* ExternalSource = GetExternalSource(); + if (UNLIKELY(!ExternalSource)) + { + return {}; + } + return ExternalSource->GUID; +} + +FName FWwiseRefExternalSource::ExternalSourceName() const +{ + const auto* ExternalSource = GetExternalSource(); + if (UNLIKELY(!ExternalSource)) + { + return {}; + } + return ExternalSource->Name; +} + +FName FWwiseRefExternalSource::ExternalSourceObjectPath() const +{ + const auto* ExternalSource = GetExternalSource(); + if (UNLIKELY(!ExternalSource)) + { + return {}; + } + return ExternalSource->ObjectPath; +} + +uint32 FWwiseRefExternalSource::Hash() const +{ + auto Result = FWwiseRefSoundBank::Hash(); + Result = HashCombine(Result, GetTypeHash(ExternalSourceIndex)); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefGameParameter.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefGameParameter.cpp new file mode 100644 index 0000000..e630416 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefGameParameter.cpp @@ -0,0 +1,90 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefGameParameter.h" +#include "Wwise/WwiseProjectDatabaseModule.h" +#include "Wwise/Stats/ProjectDatabase.h" +#include "Wwise/Metadata/WwiseMetadataGameParameter.h" +#include "Wwise/Metadata/WwiseMetadataSoundBank.h" + +const TCHAR* const FWwiseRefGameParameter::NAME = TEXT("GameParameter"); + +const FWwiseMetadataGameParameter* FWwiseRefGameParameter::GetGameParameter() const +{ + const auto* SoundBank = GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + return nullptr; + } + const auto& GameParameters = SoundBank->GameParameters; + if (GameParameters.IsValidIndex(GameParameterIndex)) + { + return &GameParameters[GameParameterIndex]; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get GameParameter index #%zu"), GameParameterIndex); + return nullptr; + } +} + +uint32 FWwiseRefGameParameter::GameParameterId() const +{ + const auto* GameParameter = GetGameParameter(); + if (UNLIKELY(!GameParameter)) + { + return 0; + } + return GameParameter->Id; +} + +FGuid FWwiseRefGameParameter::GameParameterGuid() const +{ + const auto* GameParameter = GetGameParameter(); + if (UNLIKELY(!GameParameter)) + { + return {}; + } + return GameParameter->GUID; +} + +FName FWwiseRefGameParameter::GameParameterName() const +{ + const auto* GameParameter = GetGameParameter(); + if (UNLIKELY(!GameParameter)) + { + return {}; + } + return GameParameter->Name; +} + +FName FWwiseRefGameParameter::GameParameterObjectPath() const +{ + const auto* GameParameter = GetGameParameter(); + if (UNLIKELY(!GameParameter)) + { + return {}; + } + return GameParameter->ObjectPath; +} + +uint32 FWwiseRefGameParameter::Hash() const +{ + auto Result = FWwiseRefSoundBank::Hash(); + Result = HashCombine(Result, GetTypeHash(GameParameterIndex)); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefLanguage.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefLanguage.cpp new file mode 100644 index 0000000..2b408c1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefLanguage.cpp @@ -0,0 +1,81 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefLanguage.h" +#include "Wwise/Stats/ProjectDatabase.h" +#include "Wwise/WwiseProjectDatabaseModule.h" + +#include "Wwise/Metadata/WwiseMetadataLanguage.h" +#include "Wwise/Metadata/WwiseMetadataProjectInfo.h" + +const TCHAR* const FWwiseRefLanguage::NAME = TEXT("Language"); + +const FWwiseMetadataLanguage* FWwiseRefLanguage::GetLanguage() const +{ + const auto* ProjectInfo = GetProjectInfo(); + if (UNLIKELY(!ProjectInfo)) + { + return nullptr; + } + const auto& Platforms = ProjectInfo->Languages; + if (Platforms.IsValidIndex(LanguageIndex)) + { + return &Platforms[LanguageIndex]; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get Language index #%zu"), LanguageIndex); + return nullptr; + } +} + +uint32 FWwiseRefLanguage::LanguageId() const +{ + const auto* Language = GetLanguage(); + if (UNLIKELY(!Language)) + { + return 0; + } + return Language->Id; +} + +FGuid FWwiseRefLanguage::LanguageGuid() const +{ + const auto* Language = GetLanguage(); + if (UNLIKELY(!Language)) + { + return {}; + } + return Language->GUID; +} + +FName FWwiseRefLanguage::LanguageName() const +{ + const auto* Language = GetLanguage(); + if (UNLIKELY(!Language)) + { + return {}; + } + return Language->Name; +} + +uint32 FWwiseRefLanguage::Hash() const +{ + auto Result = FWwiseRefProjectInfo::Hash(); + Result = HashCombine(Result, GetTypeHash(LanguageIndex)); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefMedia.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefMedia.cpp new file mode 100644 index 0000000..dbdd66d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefMedia.cpp @@ -0,0 +1,81 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefMedia.h" +#include "Wwise/WwiseProjectDatabaseModule.h" +#include "Wwise/Stats/ProjectDatabase.h" +#include "Wwise/Metadata/WwiseMetadataMedia.h" +#include "Wwise/Metadata/WwiseMetadataSoundBank.h" + +const TCHAR* const FWwiseRefMedia::NAME = TEXT("Media"); + +const FWwiseMetadataMedia* FWwiseRefMedia::GetMedia() const +{ + const auto* SoundBank = GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + return nullptr; + } + + const auto& Media = SoundBank->Media; + if (Media.IsValidIndex(MediaIndex)) + { + return &Media[MediaIndex]; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get Media index #%zu"), MediaIndex); + return nullptr; + } +} + +uint32 FWwiseRefMedia::MediaId() const +{ + const auto* Media = GetMedia(); + if (UNLIKELY(!Media)) + { + return 0; + } + return Media->Id; +} + +FName FWwiseRefMedia::MediaShortName() const +{ + const auto* Media = GetMedia(); + if (UNLIKELY(!Media)) + { + return {}; + } + return Media->ShortName; +} + +FName FWwiseRefMedia::MediaPath() const +{ + const auto* Media = GetMedia(); + if (UNLIKELY(!Media)) + { + return {}; + } + return Media->Path; +} + +uint32 FWwiseRefMedia::Hash() const +{ + auto Result = FWwiseRefSoundBank::Hash(); + Result = HashCombine(Result, GetTypeHash(MediaIndex)); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefPlatform.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefPlatform.cpp new file mode 100644 index 0000000..892e2a5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefPlatform.cpp @@ -0,0 +1,132 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefPlatform.h" +#include "Wwise/WwiseProjectDatabaseModule.h" +#include "Wwise/Stats/ProjectDatabase.h" +#include "Wwise/Metadata/WwiseMetadataPlatform.h" +#include "Wwise/Metadata/WwiseMetadataPlatformInfo.h" +#include "Wwise/Metadata/WwiseMetadataProjectInfo.h" + +const TCHAR* const FWwiseRefPlatform::NAME = TEXT("Platform"); + +void FWwiseRefPlatform::Merge(FWwiseRefPlatform&& InOtherPlatform) +{ + if (UNLIKELY(InOtherPlatform.ProjectInfo.IsValid() && InOtherPlatform.IsValid())) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("FWwiseRefPlatform::Merge: Merging with a complete OtherPlatform.")); + } + if (UNLIKELY(ProjectInfo.IsValid() && IsValid())) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("FWwiseRefPlatform::Merge: Merging with a complete self.")); + } + + if (InOtherPlatform.IsValid()) + { + if (UNLIKELY(IsValid())) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("FWwiseRefPlatform::Merge: Already have a PlatformInfo. Overriding.")); + } + RootFileRef = MoveTemp(InOtherPlatform.RootFileRef); + JsonFilePath = MoveTemp(InOtherPlatform.JsonFilePath); + } + if (InOtherPlatform.ProjectInfo.IsValid()) + { + if (UNLIKELY(ProjectInfo.IsValid())) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("FWwiseRefPlatform::Merge: Already have a ProjectInfo. Overriding.")); + } + ProjectInfo = MoveTemp(InOtherPlatform.ProjectInfo); + ProjectInfoPlatformReferenceIndex = MoveTemp(InOtherPlatform.ProjectInfoPlatformReferenceIndex); + } +} + +const FWwiseMetadataPlatform* FWwiseRefPlatform::GetPlatform() const +{ + const auto* PlatformInfo = GetPlatformInfo(); + if (UNLIKELY(!PlatformInfo)) + { + return nullptr; + } + return &PlatformInfo->Platform; +} + +const FWwiseMetadataPlatformReference* FWwiseRefPlatform::GetPlatformReference() const +{ + const auto* GetProjectInfo = ProjectInfo.GetProjectInfo(); + if (UNLIKELY(!GetProjectInfo)) + { + return nullptr; + } + const auto& Platforms = GetProjectInfo->Platforms; + if (Platforms.IsValidIndex(ProjectInfoPlatformReferenceIndex)) + { + return &Platforms[ProjectInfoPlatformReferenceIndex]; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get Platform Reference index #%zu"), ProjectInfoPlatformReferenceIndex); + return nullptr; + } +} + +FGuid FWwiseRefPlatform::PlatformGuid() const +{ + const auto* PlatformReference = GetPlatformReference(); + if (UNLIKELY(!PlatformReference)) + { + return {}; + } + return PlatformReference->GUID; +} + +FName FWwiseRefPlatform::PlatformName() const +{ + const auto* PlatformReference = GetPlatformReference(); + if (UNLIKELY(!PlatformReference)) + { + return {}; + } + return PlatformReference->Name; +} + +FGuid FWwiseRefPlatform::BasePlatformGuid() const +{ + const auto* PlatformReference = GetPlatformReference(); + if (UNLIKELY(!PlatformReference)) + { + return {}; + } + return PlatformReference->BasePlatformGUID; +} + +FName FWwiseRefPlatform::BasePlatformName() const +{ + const auto* PlatformReference = GetPlatformReference(); + if (UNLIKELY(!PlatformReference)) + { + return {}; + } + return PlatformReference->BasePlatform; +} +uint32 FWwiseRefPlatform::Hash() const +{ + auto Result = FWwiseRefPlatformInfo::Hash(); + Result = HashCombine(Result, ProjectInfo.Hash()); + Result = HashCombine(Result, GetTypeHash(ProjectInfoPlatformReferenceIndex)); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefPlatformInfo.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefPlatformInfo.cpp new file mode 100644 index 0000000..92a1dc2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefPlatformInfo.cpp @@ -0,0 +1,35 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefPlatformInfo.h" + +#include "Wwise/Stats/ProjectDatabase.h" +#include "Wwise/Metadata/WwiseMetadataRootFile.h" +#include "Wwise/WwiseProjectDatabaseModule.h" + +const TCHAR* const FWwiseRefPlatformInfo::NAME = TEXT("PlatformInfo"); + +const FWwiseMetadataPlatformInfo* FWwiseRefPlatformInfo::GetPlatformInfo() const +{ + const auto* RootFile = GetRootFile(); + if (UNLIKELY(!RootFile)) + { + return nullptr; + } + UE_CLOG(!RootFile->PlatformInfo, LogWwiseProjectDatabase, Error, TEXT("Could not get PlatformInfo")); + return RootFile->PlatformInfo; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefPluginInfo.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefPluginInfo.cpp new file mode 100644 index 0000000..db886a3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefPluginInfo.cpp @@ -0,0 +1,34 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefPluginInfo.h" +#include "Wwise/Stats/ProjectDatabase.h" +#include "Wwise/Metadata/WwiseMetadataRootFile.h" +#include "Wwise/WwiseProjectDatabaseModule.h" + +const TCHAR* const FWwiseRefPluginInfo::NAME = TEXT("PluginInfo"); + +const FWwiseMetadataPluginInfo* FWwiseRefPluginInfo::GetPluginInfo() const +{ + const auto* RootFile = GetRootFile(); + if (UNLIKELY(!RootFile)) + { + return nullptr; + } + UE_CLOG(!RootFile->PluginInfo, LogWwiseProjectDatabase, Error, TEXT("Could not get PluginInfo")); + return RootFile->PluginInfo; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefPluginLib.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefPluginLib.cpp new file mode 100644 index 0000000..11c25d1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefPluginLib.cpp @@ -0,0 +1,70 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefPluginLib.h" +#include "Wwise/Metadata/WwiseMetadataPluginInfo.h" +#include "Wwise/WwiseProjectDatabaseModule.h" +#include "Wwise/Stats/ProjectDatabase.h" +#include "Wwise/Metadata/WwiseMetadataPluginLib.h" + +const TCHAR* const FWwiseRefPluginLib::NAME = TEXT("PluginLib"); + +const FWwiseMetadataPluginLib* FWwiseRefPluginLib::GetPluginLib() const +{ + const auto* PluginInfo = GetPluginInfo(); + if (UNLIKELY(!PluginInfo)) + { + return nullptr; + } + const auto& PluginLibs = PluginInfo->PluginLibs; + if (PluginLibs.IsValidIndex(PluginLibIndex)) + { + return &PluginLibs[PluginLibIndex]; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get PluginLib index #%zu"), PluginLibIndex); + return nullptr; + } +} + +uint32 FWwiseRefPluginLib::PluginLibId() const +{ + const auto* PluginLib = GetPluginLib(); + if (UNLIKELY(!PluginLib)) + { + return 0; + } + return PluginLib->LibId; +} + +FName FWwiseRefPluginLib::PluginLibName() const +{ + const auto* PluginLib = GetPluginLib(); + if (UNLIKELY(!PluginLib)) + { + return {}; + } + return PluginLib->LibName; +} + +uint32 FWwiseRefPluginLib::Hash() const +{ + auto Result = FWwiseRefPluginInfo::Hash(); + Result = HashCombine(Result, GetTypeHash(PluginLibIndex)); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefPluginShareSet.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefPluginShareSet.cpp new file mode 100644 index 0000000..62ed38f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefPluginShareSet.cpp @@ -0,0 +1,187 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefPluginShareSet.h" + +#include "Wwise/WwiseProjectDatabaseModule.h" +#include "Wwise/Metadata/WwiseMetadataPlugin.h" +#include "Wwise/Metadata/WwiseMetadataPluginGroup.h" +#include "Wwise/Metadata/WwiseMetadataSoundBank.h" +#include "Wwise/Ref/WwiseRefAudioDevice.h" +#include "Wwise/Ref/WwiseRefCustomPlugin.h" +#include "Wwise/Ref/WwiseRefMedia.h" +#include "Wwise/Stats/ProjectDatabase.h" + +const TCHAR* const FWwiseRefPluginShareSet::NAME = TEXT("PluginShareSet"); + +const FWwiseMetadataPlugin* FWwiseRefPluginShareSet::GetPlugin() const +{ + const auto* SoundBank = GetSoundBank(); + if (UNLIKELY(!SoundBank || !SoundBank->Plugins)) + { + return nullptr; + } + + const auto& Plugins = SoundBank->Plugins->ShareSets; + if (Plugins.IsValidIndex(PluginShareSetIndex)) + { + return &Plugins[PluginShareSetIndex]; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get Plugin ShareSet index #%zu"), PluginShareSetIndex); + return nullptr; + } +} + +WwiseMediaIdsMap FWwiseRefPluginShareSet::GetPluginMedia(const WwiseMediaGlobalIdsMap& GlobalMap) const +{ + const auto* PluginShareSet = GetPlugin(); + const auto* SoundBank = GetSoundBank(); + if (UNLIKELY(!PluginShareSet || !SoundBank)) + { + return {}; + } + const auto& Media = PluginShareSet->MediaRefs; + + WwiseMediaIdsMap Result; + Result.Empty(Media.Num()); + for (const auto& Elem : Media) + { + FWwiseDatabaseMediaIdKey Id(Elem.Id, SoundBank->Id); + + const auto* MediaInGlobalMap = GlobalMap.Find(Id); + if (MediaInGlobalMap) + { + Result.Add(Elem.Id, *MediaInGlobalMap); + } + } + return Result; +} + +WwiseCustomPluginIdsMap FWwiseRefPluginShareSet::GetPluginCustomPlugins(const WwiseCustomPluginGlobalIdsMap& GlobalMap) const +{ + const auto* Plugin = GetPlugin(); + if (!Plugin || !Plugin->PluginRefs) + { + return {}; + } + const auto& Plugins = Plugin->PluginRefs->Custom; + WwiseCustomPluginIdsMap Result; + Result.Empty(Plugins.Num()); + for (const auto& Elem : Plugins) + { + FWwiseDatabaseLocalizableIdKey Id(Elem.Id, LanguageId); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + return Result; +} + +WwisePluginShareSetIdsMap FWwiseRefPluginShareSet::GetPluginPluginShareSets(const WwisePluginShareSetGlobalIdsMap& GlobalMap) const +{ + const auto* Plugin = GetPlugin(); + if (!Plugin || !Plugin->PluginRefs) + { + return {}; + } + const auto& Plugins = Plugin->PluginRefs->ShareSets; + WwisePluginShareSetIdsMap Result; + Result.Empty(Plugins.Num()); + for (const auto& Elem : Plugins) + { + FWwiseDatabaseLocalizableIdKey Id(Elem.Id, LanguageId); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + return Result; +} + +WwiseAudioDeviceIdsMap FWwiseRefPluginShareSet::GetPluginAudioDevices(const WwiseAudioDeviceGlobalIdsMap& GlobalMap) const +{ + const auto* Plugin = GetPlugin(); + if (!Plugin || !Plugin->PluginRefs) + { + return {}; + } + const auto& Plugins = Plugin->PluginRefs->AudioDevices; + WwiseAudioDeviceIdsMap Result; + Result.Empty(Plugins.Num()); + for (const auto& Elem : Plugins) + { + FWwiseDatabaseLocalizableIdKey Id(Elem.Id, LanguageId); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + return Result; +} + +uint32 FWwiseRefPluginShareSet::PluginShareSetId() const +{ + const auto* PluginShareSet = GetPlugin(); + if (UNLIKELY(!PluginShareSet)) + { + return 0; + } + return PluginShareSet->Id; +} + +FGuid FWwiseRefPluginShareSet::PluginShareSetGuid() const +{ + const auto* PluginShareSet = GetPlugin(); + if (UNLIKELY(!PluginShareSet)) + { + return {}; + } + return PluginShareSet->GUID; +} + +FName FWwiseRefPluginShareSet::PluginShareSetName() const +{ + const auto* PluginShareSet = GetPlugin(); + if (UNLIKELY(!PluginShareSet)) + { + return {}; + } + return PluginShareSet->Name; +} + +FName FWwiseRefPluginShareSet::PluginShareSetObjectPath() const +{ + const auto* PluginShareSet = GetPlugin(); + if (UNLIKELY(!PluginShareSet)) + { + return {}; + } + return PluginShareSet->ObjectPath; +} + +uint32 FWwiseRefPluginShareSet::Hash() const +{ + auto Result = FWwiseRefSoundBank::Hash(); + Result = HashCombine(Result, GetTypeHash(PluginShareSetIndex)); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefProjectInfo.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefProjectInfo.cpp new file mode 100644 index 0000000..863ef89 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefProjectInfo.cpp @@ -0,0 +1,35 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefProjectInfo.h" + +#include "Wwise/Metadata/WwiseMetadataRootFile.h" +#include "Wwise/WwiseProjectDatabaseModule.h" +#include "Wwise/Stats/ProjectDatabase.h" + +const TCHAR* const FWwiseRefProjectInfo::NAME = TEXT("ProjectInfo"); + +const FWwiseMetadataProjectInfo* FWwiseRefProjectInfo::GetProjectInfo() const +{ + const auto* RootFile = GetRootFile(); + if (UNLIKELY(!RootFile)) + { + return nullptr; + } + UE_CLOG(!RootFile->ProjectInfo, LogWwiseProjectDatabase, Error, TEXT("Could not get ProjectInfo")); + return RootFile->ProjectInfo; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefRootFile.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefRootFile.cpp new file mode 100644 index 0000000..893c286 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefRootFile.cpp @@ -0,0 +1,40 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefRootFile.h" +#include "Wwise/Stats/ProjectDatabase.h" +#include "Wwise/WwiseProjectDatabaseModule.h" + +const TCHAR* const FWwiseRefRootFile::NAME = TEXT("RootFile"); + +uint32 FWwiseRefRootFile::Hash() const +{ + auto Result = HashCombine(GetTypeHash(JsonFilePath), GetTypeHash(static_cast::type>(Type()))); + return Result; +} + +bool FWwiseRefRootFile::IsValid() const +{ + return static_cast(RootFileRef); +} + +const FWwiseMetadataRootFile* FWwiseRefRootFile::GetRootFile() const +{ + const auto* Result = RootFileRef.Get(); + UE_CLOG(!Result, LogWwiseProjectDatabase, Error, TEXT("Could not get Root File Ref")); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefSoundBank.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefSoundBank.cpp new file mode 100644 index 0000000..07e9f1c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefSoundBank.cpp @@ -0,0 +1,516 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefSoundBank.h" + +#include "Wwise/Ref/WwiseRefAcousticTexture.h" +#include "Wwise/Ref/WwiseRefAudioDevice.h" +#include "Wwise/Ref/WwiseRefAuxBus.h" +#include "Wwise/Ref/WwiseRefBus.h" +#include "Wwise/Ref/WwiseRefCustomPlugin.h" +#include "Wwise/Ref/WwiseRefDialogueArgument.h" +#include "Wwise/Ref/WwiseRefEvent.h" +#include "Wwise/Ref/WwiseRefExternalSource.h" +#include "Wwise/Ref/WwiseRefGameParameter.h" +#include "Wwise/Ref/WwiseRefMedia.h" +#include "Wwise/Ref/WwiseRefPluginShareSet.h" +#include "Wwise/Ref/WwiseRefState.h" +#include "Wwise/Ref/WwiseRefStateGroup.h" +#include "Wwise/WwiseProjectDatabaseModule.h" +#include "Wwise/Ref/WwiseRefSwitch.h" +#include "Wwise/Ref/WwiseRefSwitchGroup.h" +#include "Wwise/Ref/WwiseRefTrigger.h" + +#include "Wwise/Metadata/WwiseMetadataPlugin.h" +#include "Wwise/Metadata/WwiseMetadataPluginGroup.h" +#include "Wwise/Metadata/WwiseMetadataSoundBank.h" +#include "Wwise/Metadata/WwiseMetadataSoundBanksInfo.h" +#include "Wwise/Stats/ProjectDatabase.h" + + +const TCHAR* const FWwiseRefSoundBank::NAME = TEXT("SoundBank"); + +const FWwiseMetadataSoundBank* FWwiseRefSoundBank::GetSoundBank() const +{ + const auto* SoundBanksInfo = GetSoundBanksInfo(); + if (UNLIKELY(!SoundBanksInfo)) + { + return nullptr; + } + const auto& SoundBanks = SoundBanksInfo->SoundBanks; + if (SoundBanks.IsValidIndex(SoundBankIndex)) + { + return &SoundBanks[SoundBankIndex]; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get SoundBank index #%zu"), SoundBankIndex); + return nullptr; + } +} + +WwiseMediaIdsMap FWwiseRefSoundBank::GetSoundBankMedia(const WwiseMediaGlobalIdsMap& GlobalMap) const +{ + const auto* SoundBank = GetSoundBank(); + if (!SoundBank) + { + return {}; + } + const auto& Media = SoundBank->Media; + + WwiseMediaIdsMap Result; + Result.Empty(Media.Num()); + for (const auto& Elem : Media) + { + FWwiseDatabaseMediaIdKey Id(Elem.Id, SoundBank->Id); + + const auto* MediaInGlobalMap = GlobalMap.Find(Id); + if (MediaInGlobalMap) + { + Result.Add(Elem.Id, *MediaInGlobalMap); + } + } + return Result; +} + +WwiseCustomPluginIdsMap FWwiseRefSoundBank::GetSoundBankCustomPlugins(const WwiseCustomPluginGlobalIdsMap& GlobalMap) const +{ + const auto* SoundBank = GetSoundBank(); + if (!SoundBank || !SoundBank->Plugins) + { + return {}; + } + const auto& CustomPlugins = SoundBank->Plugins->Custom; + + WwiseCustomPluginIdsMap Result; + Result.Empty(CustomPlugins.Num()); + for (const auto& Elem : CustomPlugins) + { + FWwiseDatabaseLocalizableIdKey Id(Elem.Id, LanguageId); + + const auto* InGlobalMap = GlobalMap.Find(Id); + if (InGlobalMap) + { + Result.Add(Elem.Id, *InGlobalMap); + } + } + return Result; +} + +WwisePluginShareSetIdsMap FWwiseRefSoundBank::GetSoundBankPluginShareSets(const WwisePluginShareSetGlobalIdsMap& GlobalMap) const +{ + const auto* SoundBank = GetSoundBank(); + if (!SoundBank || !SoundBank->Plugins) + { + return {}; + } + const auto& PluginShareSets = SoundBank->Plugins->ShareSets; + + WwisePluginShareSetIdsMap Result; + Result.Empty(PluginShareSets.Num()); + for (const auto& Elem : PluginShareSets) + { + FWwiseDatabaseLocalizableIdKey Id(Elem.Id, LanguageId); + + const auto* InGlobalMap = GlobalMap.Find(Id); + if (InGlobalMap) + { + Result.Add(Elem.Id, *InGlobalMap); + } + } + return Result; +} + +WwiseAudioDeviceIdsMap FWwiseRefSoundBank::GetSoundBankAudioDevices(const WwiseAudioDeviceGlobalIdsMap& GlobalMap) const +{ + const auto* SoundBank = GetSoundBank(); + if (!SoundBank || !SoundBank->Plugins) + { + return {}; + } + const auto& AudioDevices = SoundBank->Plugins->AudioDevices; + + WwiseAudioDeviceIdsMap Result; + Result.Empty(AudioDevices.Num()); + for (const auto& Elem : AudioDevices) + { + FWwiseDatabaseLocalizableIdKey Id(Elem.Id, LanguageId); + + const auto* InGlobalMap = GlobalMap.Find(Id); + if (InGlobalMap) + { + Result.Add(Elem.Id, *InGlobalMap); + } + } + return Result; +} + +WwiseEventIdsMap FWwiseRefSoundBank::GetSoundBankEvents(const WwiseEventGlobalIdsMap& GlobalMap) const +{ + const auto* SoundBank = GetSoundBank(); + if (!SoundBank) + { + return {}; + } + const auto& Events = SoundBank->Events; + + WwiseEventIdsMap Result; + Result.Empty(Events.Num()); + for (const auto& Elem : Events) + { + FWwiseDatabaseLocalizableIdKey Id(Elem.Id, LanguageId, SoundBankId()); + + const auto* InGlobalMap = GlobalMap.Find(Id); + if (InGlobalMap) + { + Result.Add(Elem.Id, *InGlobalMap); + } + } + return Result; +} + +WwiseDialogueEventIdsMap FWwiseRefSoundBank::GetSoundBankDialogueEvents(const WwiseDialogueEventGlobalIdsMap& GlobalMap) const +{ + const auto* SoundBank = GetSoundBank(); + if (!SoundBank) + { + return {}; + } + const auto& DialogueEvents = SoundBank->DialogueEvents; + WwiseDialogueEventIdsMap Result; + Result.Empty(DialogueEvents.Num()); + for (const auto& Elem : DialogueEvents) + { + const auto* GlobalRef = GlobalMap.Find(FWwiseDatabaseLocalizableIdKey(Elem.Id, LanguageId)); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + + return Result; +} + +WwiseDialogueArgumentIdsMap FWwiseRefSoundBank::GetAllSoundBankDialogueArguments(const WwiseDialogueArgumentGlobalIdsMap& GlobalMap) const +{ + const auto* SoundBank = GetSoundBank(); + if (!SoundBank) + { + return {}; + } + const auto DialogueArguments = SoundBank->GetAllDialogueArguments(); + WwiseDialogueArgumentIdsMap Result; + Result.Empty(DialogueArguments.Num()); + for (const auto& Elem : DialogueArguments) + { + const auto* GlobalRef = GlobalMap.Find(FWwiseDatabaseLocalizableIdKey(Elem.Id, LanguageId)); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + + return Result; +} + +WwiseBusIdsMap FWwiseRefSoundBank::GetSoundBankBusses(const WwiseBusGlobalIdsMap& GlobalMap) const +{ + const auto* SoundBank = GetSoundBank(); + if (!SoundBank) + { + return {}; + } + const auto& Busses = SoundBank->Busses; + WwiseBusIdsMap Result; + Result.Empty(Busses.Num()); + for (const auto& Elem : Busses) + { + const auto* GlobalRef = GlobalMap.Find(FWwiseDatabaseLocalizableIdKey(Elem.Id, LanguageId)); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + + return Result; +} + +WwiseAuxBusIdsMap FWwiseRefSoundBank::GetSoundBankAuxBusses(const WwiseAuxBusGlobalIdsMap& GlobalMap) const +{ + const auto* SoundBank = GetSoundBank(); + if (!SoundBank) + { + return {}; + } + const auto& AuxBusses = SoundBank->AuxBusses; + WwiseAuxBusIdsMap Result; + Result.Empty(AuxBusses.Num()); + for (const auto& Elem : AuxBusses) + { + const auto* GlobalRef = GlobalMap.Find(FWwiseDatabaseLocalizableIdKey(Elem.Id, LanguageId)); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + + return Result; +} + +WwiseGameParameterIdsMap FWwiseRefSoundBank::GetSoundBankGameParameters(const WwiseGameParameterGlobalIdsMap& GlobalMap) const +{ + const auto* SoundBank = GetSoundBank(); + if (!SoundBank) + { + return {}; + } + const auto& GameParameters = SoundBank->GameParameters; + WwiseGameParameterIdsMap Result; + Result.Empty(GameParameters.Num()); + for (const auto& Elem : GameParameters) + { + const auto* GlobalRef = GlobalMap.Find(FWwiseDatabaseLocalizableIdKey(Elem.Id, LanguageId)); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + + return Result; +} + +WwiseStateGroupIdsMap FWwiseRefSoundBank::GetSoundBankStateGroups(const WwiseStateGroupGlobalIdsMap& GlobalMap) const +{ + const auto* SoundBank = GetSoundBank(); + if (!SoundBank) + { + return {}; + } + const auto& StateGroups = SoundBank->StateGroups; + WwiseStateGroupIdsMap Result; + Result.Empty(StateGroups.Num()); + for (const auto& Elem : StateGroups) + { + const auto* GlobalRef = GlobalMap.Find(FWwiseDatabaseLocalizableIdKey(Elem.Id, LanguageId)); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + + return Result; +} + +WwiseStateIdsMap FWwiseRefSoundBank::GetAllSoundBankStates(const WwiseStateGlobalIdsMap& GlobalMap) const +{ + const auto* SoundBank = GetSoundBank(); + if (!SoundBank) + { + return {}; + } + const auto States = SoundBank->GetAllStates(); + WwiseStateIdsMap Result; + Result.Empty(States.Num()); + for (const auto& Elem : States) + { + const auto* GlobalRef = GlobalMap.Find(FWwiseDatabaseLocalizableGroupValueKey(Elem.Get<0>().Id, Elem.Get<1>().Id, LanguageId)); + if (GlobalRef) + { + Result.Add(FWwiseDatabaseGroupValueKey(Elem.Get<0>().Id, Elem.Get<1>().Id), *GlobalRef); + } + } + + return Result; +} + +WwiseSwitchGroupIdsMap FWwiseRefSoundBank::GetSoundBankSwitchGroups(const WwiseSwitchGroupGlobalIdsMap& GlobalMap) const +{ + const auto* SoundBank = GetSoundBank(); + if (!SoundBank) + { + return {}; + } + const auto& SwitchGroups = SoundBank->SwitchGroups; + WwiseSwitchGroupIdsMap Result; + Result.Empty(SwitchGroups.Num()); + for (const auto& Elem : SwitchGroups) + { + const auto* GlobalRef = GlobalMap.Find(FWwiseDatabaseLocalizableIdKey(Elem.Id, LanguageId)); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + + return Result; +} + +WwiseSwitchIdsMap FWwiseRefSoundBank::GetAllSoundBankSwitches(const WwiseSwitchGlobalIdsMap& GlobalMap) const +{ + const auto* SoundBank = GetSoundBank(); + if (!SoundBank) + { + return {}; + } + const auto Switches = SoundBank->GetAllSwitches(); + WwiseSwitchIdsMap Result; + Result.Empty(Switches.Num()); + for (const auto& Elem : Switches) + { + const auto* GlobalRef = GlobalMap.Find(FWwiseDatabaseLocalizableGroupValueKey(Elem.Get<0>().Id, Elem.Get<1>().Id, LanguageId)); + if (GlobalRef) + { + Result.Add(FWwiseDatabaseGroupValueKey(Elem.Get<0>().Id, Elem.Get<1>().Id), *GlobalRef); + } + } + + return Result; +} + +WwiseTriggerIdsMap FWwiseRefSoundBank::GetSoundBankTriggers(const WwiseTriggerGlobalIdsMap& GlobalMap) const +{ + const auto* SoundBank = GetSoundBank(); + if (!SoundBank) + { + return {}; + } + const auto& Triggers = SoundBank->Triggers; + WwiseTriggerIdsMap Result; + Result.Empty(Triggers.Num()); + for (const auto& Elem : Triggers) + { + const auto* GlobalRef = GlobalMap.Find(FWwiseDatabaseLocalizableIdKey(Elem.Id, LanguageId)); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + + return Result; +} + +WwiseExternalSourceIdsMap FWwiseRefSoundBank::GetSoundBankExternalSources(const WwiseExternalSourceGlobalIdsMap& GlobalMap) const +{ + const auto* SoundBank = GetSoundBank(); + if (!SoundBank) + { + return {}; + } + const auto& ExternalSources = SoundBank->ExternalSources; + WwiseExternalSourceIdsMap Result; + Result.Empty(ExternalSources.Num()); + for (const auto& Elem : ExternalSources) + { + const auto* GlobalRef = GlobalMap.Find(FWwiseDatabaseLocalizableIdKey(Elem.Cookie, LanguageId)); + if (GlobalRef) + { + Result.Add(Elem.Cookie, *GlobalRef); + } + } + + return Result; +} + +WwiseAcousticTextureIdsMap FWwiseRefSoundBank::GetSoundBankAcousticTextures(const WwiseAcousticTextureGlobalIdsMap& GlobalMap) const +{ + const auto* SoundBank = GetSoundBank(); + if (!SoundBank) + { + return {}; + } + const auto& AcousticTextures = SoundBank->AcousticTextures; + WwiseAcousticTextureIdsMap Result; + Result.Empty(AcousticTextures.Num()); + for (const auto& Elem : AcousticTextures) + { + const auto* GlobalRef = GlobalMap.Find(FWwiseDatabaseLocalizableIdKey(Elem.Id, LanguageId)); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + + return Result; +} + +bool FWwiseRefSoundBank::IsUserBank() const +{ + const auto* SoundBank = GetSoundBank(); + if (!SoundBank) + { + return false; + } + return SoundBank->Type == EMetadataSoundBankType::User; +} + +bool FWwiseRefSoundBank::IsInitBank() const +{ + const auto* SoundBank = GetSoundBank(); + if (!SoundBank) + { + return false; + } + return SoundBank->IsInitBank(); +} + + +uint32 FWwiseRefSoundBank::SoundBankId() const +{ + const auto* SoundBank = GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + return 0; + } + return SoundBank->Id; +} + +FGuid FWwiseRefSoundBank::SoundBankGuid() const +{ + const auto* SoundBank = GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + return {}; + } + return SoundBank->GUID; +} + +FName FWwiseRefSoundBank::SoundBankShortName() const +{ + const auto* SoundBank = GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + return {}; + } + return SoundBank->ShortName; +} + +FName FWwiseRefSoundBank::SoundBankObjectPath() const +{ + const auto* SoundBank = GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + return {}; + } + return SoundBank->ObjectPath; +} + +uint32 FWwiseRefSoundBank::Hash() const +{ + auto Result = FWwiseRefSoundBanksInfo::Hash(); + Result = HashCombine(Result, GetTypeHash(SoundBankIndex)); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefSoundBanksInfo.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefSoundBanksInfo.cpp new file mode 100644 index 0000000..098f991 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefSoundBanksInfo.cpp @@ -0,0 +1,35 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefSoundBanksInfo.h" + +#include "Wwise/Metadata/WwiseMetadataRootFile.h" +#include "Wwise/WwiseProjectDatabaseModule.h" +#include "Wwise/Stats/ProjectDatabase.h" + +const TCHAR* const FWwiseRefSoundBanksInfo::NAME = TEXT("SoundbanksInfo"); + +const FWwiseMetadataSoundBanksInfo* FWwiseRefSoundBanksInfo::GetSoundBanksInfo() const +{ + const auto* RootFile = GetRootFile(); + if (!RootFile) + { + return nullptr; + } + UE_CLOG(!RootFile->SoundBanksInfo, LogWwiseProjectDatabase, Error, TEXT("Could not get SoundBanksInfo")); + return RootFile->SoundBanksInfo; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefState.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefState.cpp new file mode 100644 index 0000000..1b30283 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefState.cpp @@ -0,0 +1,91 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefState.h" +#include "Wwise/Stats/ProjectDatabase.h" +#include "Wwise/Metadata/WwiseMetadataStateGroup.h" +#include "Wwise/WwiseProjectDatabaseModule.h" + +#include "Wwise/Metadata/WwiseMetadataState.h" + +const TCHAR* const FWwiseRefState::NAME = TEXT("State"); + +const FWwiseMetadataState* FWwiseRefState::GetState() const +{ + const auto* StateGroup = GetStateGroup(); + if (UNLIKELY(!StateGroup)) + { + return nullptr; + } + const auto& States = StateGroup->States; + if (States.IsValidIndex(StateIndex)) + { + return &States[StateIndex]; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get State index #%zu"), StateIndex); + return nullptr; + } +} + +uint32 FWwiseRefState::StateId() const +{ + const auto* State = GetState(); + if (UNLIKELY(!State)) + { + return 0; + } + return State->Id; +} + +FGuid FWwiseRefState::StateGuid() const +{ + const auto* State = GetState(); + if (UNLIKELY(!State)) + { + return {}; + } + return State->GUID; +} + +FName FWwiseRefState::StateName() const +{ + const auto* State = GetState(); + if (UNLIKELY(!State)) + { + return {}; + } + return State->Name; +} + +FName FWwiseRefState::StateObjectPath() const +{ + const auto* State = GetState(); + if (UNLIKELY(!State)) + { + return {}; + } + return State->ObjectPath; +} + +uint32 FWwiseRefState::Hash() const +{ + auto Result = FWwiseRefStateGroup::Hash(); + Result = HashCombine(Result, GetTypeHash(StateIndex)); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefStateGroup.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefStateGroup.cpp new file mode 100644 index 0000000..6588f9b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefStateGroup.cpp @@ -0,0 +1,92 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefStateGroup.h" + +#include "Wwise/Metadata/WwiseMetadataSoundBank.h" +#include "Wwise/WwiseProjectDatabaseModule.h" + +#include "Wwise/Metadata/WwiseMetadataStateGroup.h" +#include "Wwise/Stats/ProjectDatabase.h" + +const TCHAR* const FWwiseRefStateGroup::NAME = TEXT("StateGroup"); + +const FWwiseMetadataStateGroup* FWwiseRefStateGroup::GetStateGroup() const +{ + const auto* SoundBank = GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + return nullptr; + } + const auto& StateGroups = SoundBank->StateGroups; + if (StateGroups.IsValidIndex(StateGroupIndex)) + { + return &StateGroups[StateGroupIndex]; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get State Group index #%zu"), StateGroupIndex); + return nullptr; + } +} + +uint32 FWwiseRefStateGroup::StateGroupId() const +{ + const auto* StateGroup = GetStateGroup(); + if (UNLIKELY(!StateGroup)) + { + return 0; + } + return StateGroup->Id; +} + +FGuid FWwiseRefStateGroup::StateGroupGuid() const +{ + const auto* StateGroup = GetStateGroup(); + if (UNLIKELY(!StateGroup)) + { + return {}; + } + return StateGroup->GUID; +} + +FName FWwiseRefStateGroup::StateGroupName() const +{ + const auto* StateGroup = GetStateGroup(); + if (UNLIKELY(!StateGroup)) + { + return {}; + } + return StateGroup->Name; +} + +FName FWwiseRefStateGroup::StateGroupObjectPath() const +{ + const auto* StateGroup = GetStateGroup(); + if (UNLIKELY(!StateGroup)) + { + return {}; + } + return StateGroup->ObjectPath; +} + +uint32 FWwiseRefStateGroup::Hash() const +{ + auto Result = FWwiseRefSoundBank::Hash(); + Result = HashCombine(Result, GetTypeHash(StateGroupIndex)); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefSwitch.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefSwitch.cpp new file mode 100644 index 0000000..2bd0946 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefSwitch.cpp @@ -0,0 +1,91 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefSwitch.h" +#include "Wwise/Stats/ProjectDatabase.h" +#include "Wwise/Metadata/WwiseMetadataSwitchGroup.h" +#include "Wwise/WwiseProjectDatabaseModule.h" + +#include "Wwise/Metadata/WwiseMetadataSwitch.h" + +const TCHAR* const FWwiseRefSwitch::NAME = TEXT("Switch"); + +const FWwiseMetadataSwitch* FWwiseRefSwitch::GetSwitch() const +{ + const auto* SwitchGroup = GetSwitchGroup(); + if (UNLIKELY(!SwitchGroup)) + { + return nullptr; + } + const auto& Switches = SwitchGroup->Switches; + if (Switches.IsValidIndex(SwitchIndex)) + { + return &Switches[SwitchIndex]; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get Switch index #%zu"), SwitchIndex); + return nullptr; + } +} + +uint32 FWwiseRefSwitch::SwitchId() const +{ + const auto* Switch = GetSwitch(); + if (UNLIKELY(!Switch)) + { + return 0; + } + return Switch->Id; +} + +FGuid FWwiseRefSwitch::SwitchGuid() const +{ + const auto* Switch = GetSwitch(); + if (UNLIKELY(!Switch)) + { + return {}; + } + return Switch->GUID; +} + +FName FWwiseRefSwitch::SwitchName() const +{ + const auto* Switch = GetSwitch(); + if (UNLIKELY(!Switch)) + { + return {}; + } + return Switch->Name; +} + +FName FWwiseRefSwitch::SwitchObjectPath() const +{ + const auto* Switch = GetSwitch(); + if (UNLIKELY(!Switch)) + { + return {}; + } + return Switch->ObjectPath; +} + +uint32 FWwiseRefSwitch::Hash() const +{ + auto Result = FWwiseRefSwitchGroup::Hash(); + Result = HashCombine(Result, GetTypeHash(SwitchIndex)); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefSwitchContainer.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefSwitchContainer.cpp new file mode 100644 index 0000000..e3a021b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefSwitchContainer.cpp @@ -0,0 +1,269 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefSwitchContainer.h" + +#include "Wwise/Stats/ProjectDatabase.h" +#include "Wwise/Ref/WwiseAnyRef.h" +#include "Wwise/Ref/WwiseRefAudioDevice.h" +#include "Wwise/Ref/WwiseRefCustomPlugin.h" +#include "Wwise/Ref/WwiseRefExternalSource.h" +#include "Wwise/Ref/WwiseRefMedia.h" +#include "Wwise/Ref/WwiseRefPluginShareSet.h" +#include "Wwise/Ref/WwiseRefState.h" +#include "Wwise/WwiseProjectDatabaseModule.h" +#include "Wwise/Ref/WwiseRefSwitch.h" +#include "Wwise/Metadata/WwiseMetadataEvent.h" +#include "Wwise/Metadata/WwiseMetadataMedia.h" +#include "Wwise/Metadata/WwiseMetadataPlugin.h" +#include "Wwise/Metadata/WwiseMetadataPluginGroup.h" +#include "Wwise/Metadata/WwiseMetadataSoundBank.h" +#include "Wwise/Metadata/WwiseMetadataSwitchContainer.h" +#include "Wwise/Metadata/WwiseMetadataSwitchValue.h" + +const TCHAR* const FWwiseRefSwitchContainer::NAME = TEXT("SwitchContainer"); + +const FWwiseMetadataSwitchContainer* FWwiseRefSwitchContainer::GetSwitchContainer() const +{ + const auto* Event = GetEvent(); + if (UNLIKELY(!Event)) + { + return nullptr; + } + + const auto* SwitchContainers = &Event->SwitchContainers; + const FWwiseMetadataSwitchContainer* Result = nullptr; + for (auto Index : ChildrenIndices) + { + if (!SwitchContainers->IsValidIndex(Index)) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get Switch Container index #%zu"), Index); + return nullptr; + } + Result = &(*SwitchContainers)[Index]; + SwitchContainers = &Result->Children; + } + return Result; +} + +FWwiseAnyRef FWwiseRefSwitchContainer::GetSwitchValue(const WwiseSwitchGlobalIdsMap& SwitchGlobalMap, const WwiseStateGlobalIdsMap& StateGlobalMap) const +{ + const auto* Container = GetSwitchContainer(); + if (!Container) + { + return {}; + } + const auto& SwitchValue = Container->SwitchValue; + switch (SwitchValue.GroupType) + { + case EWwiseMetadataSwitchValueGroupType::Switch: + { + const auto* GlobalRef = SwitchGlobalMap.Find(FWwiseDatabaseLocalizableGroupValueKey(SwitchValue.GroupId, SwitchValue.Id, LanguageId)); + if (UNLIKELY(!GlobalRef)) + { + return {}; + } + return FWwiseAnyRef::Create(*GlobalRef); + } + case EWwiseMetadataSwitchValueGroupType::State: + { + const auto* GlobalRef = StateGlobalMap.Find(FWwiseDatabaseLocalizableGroupValueKey(SwitchValue.GroupId, SwitchValue.Id, LanguageId)); + if (UNLIKELY(!GlobalRef)) + { + return {}; + } + return FWwiseAnyRef::Create(*GlobalRef); + } + } + return {}; +} + +WwiseMediaIdsMap FWwiseRefSwitchContainer::GetSwitchContainerMedia(const WwiseMediaGlobalIdsMap& GlobalMap) const +{ + const auto* SwitchContainer = GetSwitchContainer(); + const auto* SoundBank = GetSoundBank(); + TArray MapKeys; + if (!SwitchContainer || !SoundBank) + { + return {}; + } + const auto& MediaRefs = SwitchContainer->MediaRefs; + WwiseMediaIdsMap Result; + Result.Empty(MediaRefs.Num()); + for (const auto& Elem : MediaRefs) + { + FWwiseDatabaseMediaIdKey SoundBankFileId(Elem.Id, SoundBank->Id); + const auto* GlobalRef = GlobalMap.Find(SoundBankFileId); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + return Result; +} + +WwiseExternalSourceIdsMap FWwiseRefSwitchContainer::GetSwitchContainerExternalSources(const WwiseExternalSourceGlobalIdsMap& GlobalMap) const +{ + const auto* SwitchContainer = GetSwitchContainer(); + if (!SwitchContainer) + { + return {}; + } + const auto& ExternalSourceRefs = SwitchContainer->ExternalSourceRefs; + WwiseExternalSourceIdsMap Result; + Result.Empty(ExternalSourceRefs.Num()); + for (const auto& Elem : ExternalSourceRefs) + { + FWwiseDatabaseLocalizableIdKey Id(Elem.Cookie, LanguageId); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Cookie, *GlobalRef); + } + } + return Result; +} + +WwiseCustomPluginIdsMap FWwiseRefSwitchContainer::GetSwitchContainerCustomPlugins(const WwiseCustomPluginGlobalIdsMap& GlobalMap) const +{ + const auto* SwitchContainer = GetSwitchContainer(); + if (!SwitchContainer || !SwitchContainer->PluginRefs) + { + return {}; + } + const auto& Plugins = SwitchContainer->PluginRefs->Custom; + WwiseCustomPluginIdsMap Result; + Result.Empty(Plugins.Num()); + for (const auto& Elem : Plugins) + { + FWwiseDatabaseLocalizableIdKey Id(Elem.Id, LanguageId); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + return Result; +} + +WwisePluginShareSetIdsMap FWwiseRefSwitchContainer::GetSwitchContainerPluginShareSets(const WwisePluginShareSetGlobalIdsMap& GlobalMap) const +{ + const auto* SwitchContainer = GetSwitchContainer(); + if (!SwitchContainer || !SwitchContainer->PluginRefs) + { + return {}; + } + const auto& Plugins = SwitchContainer->PluginRefs->ShareSets; + WwisePluginShareSetIdsMap Result; + Result.Empty(Plugins.Num()); + for (const auto& Elem : Plugins) + { + FWwiseDatabaseLocalizableIdKey Id(Elem.Id, LanguageId); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + return Result; +} + +WwiseAudioDeviceIdsMap FWwiseRefSwitchContainer::GetSwitchContainerAudioDevices(const WwiseAudioDeviceGlobalIdsMap& GlobalMap) const +{ + const auto* SwitchContainer = GetSwitchContainer(); + if (!SwitchContainer || !SwitchContainer->PluginRefs) + { + return {}; + } + const auto& Plugins = SwitchContainer->PluginRefs->AudioDevices; + WwiseAudioDeviceIdsMap Result; + Result.Empty(Plugins.Num()); + for (const auto& Elem : Plugins) + { + FWwiseDatabaseLocalizableIdKey Id(Elem.Id, LanguageId); + const auto* GlobalRef = GlobalMap.Find(Id); + if (GlobalRef) + { + Result.Add(Elem.Id, *GlobalRef); + } + } + return Result; +} + +TArray FWwiseRefSwitchContainer::GetSwitchValues(const WwiseSwitchGlobalIdsMap& SwitchGlobalMap, const WwiseStateGlobalIdsMap& StateGlobalMap) const +{ + const auto* Event = GetEvent(); + if (!Event) + { + return {}; + } + + const auto* SwitchContainers = &Event->SwitchContainers; + TArray Result; + for (auto Index : ChildrenIndices) + { + if (UNLIKELY(!SwitchContainers->IsValidIndex(Index))) + { + return {}; + } + const auto& SwitchContainer = (*SwitchContainers)[Index]; + const auto& SwitchValue = SwitchContainer.SwitchValue; + + // Skipping Default Switches, but keep different ones + if (!SwitchValue.bDefault) + { + switch (SwitchValue.GroupType) + { + case EWwiseMetadataSwitchValueGroupType::Switch: + { + const auto* GlobalRef = SwitchGlobalMap.Find(FWwiseDatabaseLocalizableGroupValueKey(SwitchValue.GroupId, SwitchValue.Id, LanguageId)); + if (UNLIKELY(!GlobalRef)) + { + return {}; + } + Result.Add(FWwiseAnyRef::Create(*GlobalRef)); + break; + } + case EWwiseMetadataSwitchValueGroupType::State: + { + const auto* GlobalRef = StateGlobalMap.Find(FWwiseDatabaseLocalizableGroupValueKey(SwitchValue.GroupId, SwitchValue.Id, LanguageId)); + if (UNLIKELY(!GlobalRef)) + { + return {}; + } + Result.Add(FWwiseAnyRef::Create(*GlobalRef)); + break; + } + default: + return {}; + } + } + + SwitchContainers = &SwitchContainer.Children; + } + return Result; +} + +uint32 FWwiseRefSwitchContainer::Hash() const +{ + auto Result = FWwiseRefEvent::Hash(); + if (ChildrenIndices.Num() > 0) + { + Result = HashCombine(Result, uint32(CityHash64((const char*)ChildrenIndices.GetData(), ChildrenIndices.GetTypeSize() * ChildrenIndices.Num()))); + } + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefSwitchGroup.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefSwitchGroup.cpp new file mode 100644 index 0000000..9a508b0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefSwitchGroup.cpp @@ -0,0 +1,102 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefSwitchGroup.h" + +#include "Wwise/Metadata/WwiseMetadataSoundBank.h" +#include "Wwise/WwiseProjectDatabaseModule.h" +#include "Wwise/Stats/ProjectDatabase.h" +#include "Wwise/Metadata/WwiseMetadataSwitchGroup.h" + +const TCHAR* const FWwiseRefSwitchGroup::NAME = TEXT("SwitchGroup"); + +const FWwiseMetadataSwitchGroup* FWwiseRefSwitchGroup::GetSwitchGroup() const +{ + const auto* SoundBank = GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + return nullptr; + } + const auto& SwitchGroups = SoundBank->SwitchGroups; + if (SwitchGroups.IsValidIndex(SwitchGroupIndex)) + { + return &SwitchGroups[SwitchGroupIndex]; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get Switch Group index #%zu"), SwitchGroupIndex); + return nullptr; + } +} + +bool FWwiseRefSwitchGroup::IsControlledByGameParameter() const +{ + const auto* SwitchGroup = GetSwitchGroup(); + if (!SwitchGroup) + { + return false; + } + + return SwitchGroup->GameParameterRef != nullptr; +} + +uint32 FWwiseRefSwitchGroup::SwitchGroupId() const +{ + const auto* SwitchGroup = GetSwitchGroup(); + if (UNLIKELY(!SwitchGroup)) + { + return 0; + } + return SwitchGroup->Id; +} + +FGuid FWwiseRefSwitchGroup::SwitchGroupGuid() const +{ + const auto* SwitchGroup = GetSwitchGroup(); + if (UNLIKELY(!SwitchGroup)) + { + return {}; + } + return SwitchGroup->GUID; +} + +FName FWwiseRefSwitchGroup::SwitchGroupName() const +{ + const auto* SwitchGroup = GetSwitchGroup(); + if (UNLIKELY(!SwitchGroup)) + { + return {}; + } + return SwitchGroup->Name; +} + +FName FWwiseRefSwitchGroup::SwitchGroupObjectPath() const +{ + const auto* SwitchGroup = GetSwitchGroup(); + if (UNLIKELY(!SwitchGroup)) + { + return {}; + } + return SwitchGroup->ObjectPath; +} + +uint32 FWwiseRefSwitchGroup::Hash() const +{ + auto Result = FWwiseRefSoundBank::Hash(); + Result = HashCombine(Result, GetTypeHash(SwitchGroupIndex)); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefTrigger.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefTrigger.cpp new file mode 100644 index 0000000..bf7a4e5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Ref/WwiseRefTrigger.cpp @@ -0,0 +1,92 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Ref/WwiseRefTrigger.h" + +#include "Wwise/Metadata/WwiseMetadataSoundBank.h" +#include "Wwise/WwiseProjectDatabaseModule.h" +#include "Wwise/Stats/FileHandler.h" +#include "Wwise/Metadata/WwiseMetadataTrigger.h" +#include "Wwise/Stats/ProjectDatabase.h" + +const TCHAR* const FWwiseRefTrigger::NAME = TEXT("Trigger"); + +const FWwiseMetadataTrigger* FWwiseRefTrigger::GetTrigger() const +{ + const auto* SoundBank = GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + return nullptr; + } + const auto& Triggers = SoundBank->Triggers; + if (Triggers.IsValidIndex(TriggerIndex)) + { + return &Triggers[TriggerIndex]; + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not get Trigger index #%zu"), TriggerIndex); + return nullptr; + } +} + +uint32 FWwiseRefTrigger::TriggerId() const +{ + const auto* Trigger = GetTrigger(); + if (UNLIKELY(!Trigger)) + { + return 0; + } + return Trigger->Id; +} + +FGuid FWwiseRefTrigger::TriggerGuid() const +{ + const auto* Trigger = GetTrigger(); + if (UNLIKELY(!Trigger)) + { + return {}; + } + return Trigger->GUID; +} + +FName FWwiseRefTrigger::TriggerName() const +{ + const auto* Trigger = GetTrigger(); + if (UNLIKELY(!Trigger)) + { + return {}; + } + return Trigger->Name; +} + +FName FWwiseRefTrigger::TriggerObjectPath() const +{ + const auto* Trigger = GetTrigger(); + if (UNLIKELY(!Trigger)) + { + return {}; + } + return Trigger->ObjectPath; +} + +uint32 FWwiseRefTrigger::Hash() const +{ + auto Result = FWwiseRefSoundBank::Hash(); + Result = HashCombine(Result, GetTypeHash(TriggerIndex)); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Stats/ProjectDatabase.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Stats/ProjectDatabase.cpp new file mode 100644 index 0000000..130aeef --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/Stats/ProjectDatabase.cpp @@ -0,0 +1,22 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Stats/ProjectDatabase.h" + +DEFINE_STAT(STAT_WwiseProjectDatabaseMemory); + +DEFINE_LOG_CATEGORY(LogWwiseProjectDatabase); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseDataStructure.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseDataStructure.cpp new file mode 100644 index 0000000..c1dc9e3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseDataStructure.cpp @@ -0,0 +1,829 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseDataStructure.h" +#include "Wwise/WwiseDirectoryVisitor.h" + +#include "Wwise/Metadata/WwiseMetadataAcousticTexture.h" +#include "Wwise/Metadata/WwiseMetadataBus.h" +#include "Wwise/Metadata/WwiseMetadataDialogue.h" +#include "Wwise/Metadata/WwiseMetadataEvent.h" +#include "Wwise/Metadata/WwiseMetadataExternalSource.h" +#include "Wwise/Metadata/WwiseMetadataGameParameter.h" +#include "Wwise/Metadata/WwiseMetadataLanguage.h" +#include "Wwise/Metadata/WwiseMetadataMedia.h" +#include "Wwise/Metadata/WwiseMetadataPlatformInfo.h" +#include "Wwise/Metadata/WwiseMetadataPlugin.h" +#include "Wwise/Metadata/WwiseMetadataPluginGroup.h" +#include "Wwise/Metadata/WwiseMetadataPluginInfo.h" +#include "Wwise/Metadata/WwiseMetadataPluginLib.h" +#include "Wwise/Metadata/WwiseMetadataProjectInfo.h" +#include "Wwise/Metadata/WwiseMetadataRootFile.h" +#include "Wwise/Metadata/WwiseMetadataSoundBank.h" +#include "Wwise/Metadata/WwiseMetadataSoundBanksInfo.h" +#include "Wwise/Metadata/WwiseMetadataState.h" +#include "Wwise/Metadata/WwiseMetadataStateGroup.h" +#include "Wwise/Metadata/WwiseMetadataSwitch.h" +#include "Wwise/Metadata/WwiseMetadataSwitchContainer.h" +#include "Wwise/Metadata/WwiseMetadataSwitchGroup.h" +#include "Wwise/Metadata/WwiseMetadataTrigger.h" + +#include "WwiseDefines.h" + +#include "Async/Async.h" +#if UE_5_0_OR_LATER +#include "HAL/PlatformFileManager.h" +#else +#include "HAL/PlatformFilemanager.h" +#endif +#include "Misc/LocalTimestampDirectoryVisitor.h" +#include "Misc/Paths.h" + +FWwiseDataStructure::FWwiseDataStructure(const FDirectoryPath& InDirectoryPath, const FName* InPlatform, const FGuid* InBasePlatformGuid) +{ + if (InDirectoryPath.Path.IsEmpty()) + { + return; + } + + IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); + FWwiseDirectoryVisitor Visitor(PlatformFile, InPlatform, InBasePlatformGuid); + PlatformFile.IterateDirectory(*InDirectoryPath.Path, Visitor); + auto Directory = Visitor.Get(); + + if (!Directory.IsValid()) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Invalid Generated Directory %s"), *InDirectoryPath.Path); + return; + } + + FString RequestedPlatformPath; + if (InPlatform) + { + const auto& ProjectInfoPlatforms = Directory.ProjectInfo->ProjectInfo->Platforms; + for (const auto& Platform : ProjectInfoPlatforms) + { + if (Platform.Name == *InPlatform) + { + RequestedPlatformPath = InDirectoryPath.Path / Platform.Path.ToString(); + FPaths::CollapseRelativeDirectories(RequestedPlatformPath); + } + } + } + + if (InPlatform) + { + if (Directory.Platforms.Num() == 0) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not find platform %s in Generated Directory %s"), *InPlatform->ToString(), *RequestedPlatformPath); + return; + } + + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Parsing Wwise data structure for platform %s at: %s..."), *InPlatform->ToString(), *RequestedPlatformPath); + } + else + { + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Parsing Wwise data structure at: %s..."), *InDirectoryPath.Path); + } + + + LoadDataStructure(MoveTemp(Directory)); +} + +FWwiseDataStructure::~FWwiseDataStructure() +{ + FWriteScopeLock ScopeLock(Lock); +} + +void FWwiseDataStructure::LoadDataStructure(FWwiseGeneratedFiles&& Directory) +{ + SCOPED_WWISEPROJECTDATABASE_EVENT_2(TEXT("FWwiseDataStructure::LoadDataStructure")); + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("Starting load task")); + + // Create the file lists to be used in loading root files + TArray FileListForRoot; + { + const FString ProjectInfoPath = Directory.GeneratedRootFiles.ProjectInfoFile.Get<0>(); + if (ProjectInfoPath.IsEmpty()) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("- Could not find project info")); + } + else + { + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("- Adding project info: %s"), *ProjectInfoPath); + FileListForRoot.Add(ProjectInfoPath); + } + } + + TSharedFuture RootFuture = Async(EAsyncExecution::TaskGraph, [this, &FileListForRoot, &Directory] { + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Loading Generated file contents for root")); + auto JsonFiles = FWwiseMetadataRootFile::LoadFiles(FileListForRoot); + + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Parsing Generated file contents for root")); + auto RootDataStructure = new FWwiseRootDataStructure(MoveTemp(JsonFiles)); + RootDataStructure->GeneratedRootFiles = MoveTemp(Directory.GeneratedRootFiles); + return RootDataStructure; + }).Share(); + + // Create the file lists to be used in loading files per platform + TMap> Futures; + for (const auto& Platform : Directory.Platforms) + { + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("Loading platform files: %s"), *Platform.Key.GetPlatformName().ToString()); + TArray FileList; + const FWwiseSharedPlatformId& PlatformRef = Platform.Key; + const FWwiseGeneratedFiles::FPlatformFiles& Files = Platform.Value; + + // Add Platform and Plug-in files + const FString PlatformInfoPath = Files.PlatformInfoFile.Get<0>(); + { + if (UNLIKELY(PlatformInfoPath.IsEmpty())) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("No PlatformInfo file for platform %s"), *PlatformRef.GetPlatformName().ToString()); + continue; + } + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("- Adding platform info: %s"), *PlatformInfoPath); + FileList.Add(PlatformInfoPath); + } + { + const FString PluginInfoPath = Files.PluginInfoFile.Get<0>(); + if (UNLIKELY(PluginInfoPath.IsEmpty())) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("No PluginInfo file for platform %s"), *PlatformRef.GetPlatformName().ToString()); + continue; + } + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("- Adding plugin info: %s"), *PluginInfoPath); + FileList.Add(PluginInfoPath); + } + + // Parse PlatformInfo file to detect settings + // (will be parsed twice. Now once, and officially later - since the file is small, it's not a big worry) + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("Pre-parsing platform info file for settings")); + auto PlatformInfoFile = FWwiseMetadataRootFile::LoadFile(PlatformInfoPath); + if (!PlatformInfoFile || !PlatformInfoFile->PlatformInfo) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not read PlatformInfo for platform %s."), *PlatformRef.GetPlatformName().ToString()); + continue; + } + const auto& Settings = PlatformInfoFile->PlatformInfo->Settings; + bool bIsValid = true; + if (!Settings.bCopyLooseStreamedMediaFiles) + { + bIsValid = false; + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Platform %s: Requires \"Copy Loose/Streamed Media\"."), *PlatformRef.GetPlatformName().ToString()); + } + if (!Settings.bGenerateMetadataJSON) + { + bIsValid = false; + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Platform %s: Requires \"Generate JSON Metadata\"."), *PlatformRef.GetPlatformName().ToString()); + } + if (Settings.bGenerateAllBanksMetadata && Settings.bGeneratePerBankMetadata) + { + UE_LOG(LogWwiseProjectDatabase, Log, TEXT("Platform %s: Having both \"Generate All Banks Metadata file\" and \"Generate Per Bank Metadata file\" will use the latter."), *PlatformRef.GetPlatformName().ToString()); + } + else if (Settings.bGenerateAllBanksMetadata) + { + UE_LOG(LogWwiseProjectDatabase, Log, TEXT("Platform %s: Using \"Generate All Banks Metadata file\" is less efficient than Per Bank."), *PlatformRef.GetPlatformName().ToString()); + } + else if (!Settings.bGeneratePerBankMetadata) + { + bIsValid = false; + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Platform %s: No metadata generated. Requires one of the \"Generate Metadata file\" option set."), *PlatformRef.GetPlatformName().ToString()); + } + if (!Settings.bPrintObjectGuid) + { + bIsValid = false; + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Platform %s: Requires \"Object GUID\" Metadata."), *PlatformRef.GetPlatformName().ToString()); + } + if (!Settings.bPrintObjectPath) + { + bIsValid = false; + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Platform %s: Requires \"Object Path\" Metadata."), *PlatformRef.GetPlatformName().ToString()); + } + if (!Settings.bMaxAttenuationInfo) + { + bIsValid = false; + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Platform %s: Requires \"Max Attenuation\" Metadata."), *PlatformRef.GetPlatformName().ToString()); + } + if (!Settings.bEstimatedDurationInfo) + { + bIsValid = false; + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Platform %s: Requires \"Estimated Duration\" Metadata."), *PlatformRef.GetPlatformName().ToString()); + } + if (!bIsValid) + { + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Skipping platform")); + continue; + } + + // Monolithic SoundBanksInfo or split files + if (Settings.bGeneratePerBankMetadata) + { + if (UNLIKELY(Files.MetadataFiles.Num() == 0)) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Platform %s: Generated Per Bank metadata, but no metadata file found."), *PlatformRef.GetPlatformName().ToString()); + continue; + } + + FileList.Reserve(FileList.Num() + Files.MetadataFiles.Num()); + for (const auto& MetadataFile : Files.MetadataFiles) + { + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("- Adding metadata file: %s"), *MetadataFile.Key); + FileList.Add(MetadataFile.Key); + } + } + else if (!Files.SoundbanksInfoFile.Get<0>().IsEmpty()) + { + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("- Adding monolithic SoundBanks info file: %s"), *Files.SoundbanksInfoFile.Get<0>()); + FileList.Add(Files.SoundbanksInfoFile.Get<0>()); + } + else + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Platform %s: Generated All Banks metadata, but SoundBanksInfo.json file not found."), *PlatformRef.GetPlatformName().ToString()); + continue; + } + + Futures.Add(Platform.Key, Async(EAsyncExecution::TaskGraph, [this, PlatformRef, RootFuture, FileList, &Directory] { + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Loading Generated file contents for platform %s"), *PlatformRef.GetPlatformName().ToString()); + auto JsonFiles = FWwiseMetadataRootFile::LoadFiles(FileList); + + auto PlatformData = new FWwisePlatformDataStructure(PlatformRef, *RootFuture.Get(), MoveTemp(JsonFiles)); + PlatformData->GeneratedPlatformFiles = MoveTemp(Directory.Platforms[PlatformRef]); + return PlatformData; + })); + } + + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("Finalizing parsing %d files"), Futures.Num()); + for (const auto& Future : Futures) + { + auto* Result = Future.Value.Get(); + if (UNLIKELY(!Result)) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("File parsing failed")); + } + else + { + Platforms.Add(Future.Key, MoveTemp(*Result)); + delete Result; + } + } + Futures.Empty(); + + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("Finalizing parsing root")); + if (auto* Result = RootFuture.Get()) + { + // Will move result. Must have all other Futures done + RootData = MoveTemp(*Result); + delete Result; + } +} + + +FWwiseRootDataStructure::FWwiseRootDataStructure(WwiseMetadataFileMap&& InJsonFiles) : + JsonFiles(MoveTemp(InJsonFiles)) +{ + SCOPED_WWISEPROJECTDATABASE_EVENT_2(TEXT("FWwiseRootDataStructure::FWwiseRootDataStructure")); + for (const auto& JsonFileKV : JsonFiles) + { + const auto JsonFilePath = FName(JsonFileKV.Key); + if (JsonFileKV.Value) + { + const WwiseMetadataSharedRootFileConstPtr SharedRootFile = JsonFileKV.Value; + const FWwiseMetadataRootFile& RootFile = *SharedRootFile; + + if (RootFile.ProjectInfo) + { + const FWwiseMetadataProjectInfo& ProjectInfo = *RootFile.ProjectInfo; + + // PlatformReferenceNames + PlatformReferenceGuids; + for (WwiseRefIndexType PlatformIndex = 0; PlatformIndex < ProjectInfo.Platforms.Num(); ++PlatformIndex) + { + const FWwiseMetadataPlatformReference& PlatformReference = ProjectInfo.Platforms[PlatformIndex]; + PlatformNames.Add(PlatformReference.Name, FWwiseRefPlatform(SharedRootFile, JsonFilePath, PlatformIndex)); + PlatformGuids.Add(PlatformReference.GUID, FWwiseRefPlatform(SharedRootFile, JsonFilePath, PlatformIndex)); + Platforms.Emplace(FWwiseSharedPlatformId(PlatformReference.GUID, PlatformReference.Name, PlatformReference.Path), nullptr); + } + + // LanguageNames, LanguageIds, LanguageRefs + for (WwiseRefIndexType LanguageIndex = 0; LanguageIndex < ProjectInfo.Languages.Num(); ++LanguageIndex) + { + const FWwiseMetadataLanguage& Language = ProjectInfo.Languages[LanguageIndex]; + LanguageNames.Add(Language.Name, FWwiseRefLanguage(SharedRootFile, JsonFilePath, LanguageIndex)); + LanguageIds.Add(Language.Id, FWwiseRefLanguage(SharedRootFile, JsonFilePath, LanguageIndex)); + Languages.Emplace(FWwiseSharedLanguageId(Language.Id, Language.Name, Language.bDefault ? EWwiseLanguageRequirement::IsDefault : EWwiseLanguageRequirement::IsOptional), nullptr); + } + } + } + } +} + +FWwisePlatformDataStructure::FWwisePlatformDataStructure() : + AcousticTextures(FWwiseRefAcousticTexture::FGlobalIdsMap::GlobalIdsMap), + AudioDevices(FWwiseRefAudioDevice::FGlobalIdsMap::GlobalIdsMap), + AuxBusses(FWwiseRefAuxBus::FGlobalIdsMap::GlobalIdsMap), + Busses(FWwiseRefBus::FGlobalIdsMap::GlobalIdsMap), + CustomPlugins(FWwiseRefCustomPlugin::FGlobalIdsMap::GlobalIdsMap), + DialogueArguments(FWwiseRefDialogueArgument::FGlobalIdsMap::GlobalIdsMap), + DialogueEvents(FWwiseRefDialogueEvent::FGlobalIdsMap::GlobalIdsMap), + Events(FWwiseRefEvent::FGlobalIdsMap::GlobalIdsMap), + ExternalSources(FWwiseRefExternalSource::FGlobalIdsMap::GlobalIdsMap), + GameParameters(FWwiseRefGameParameter::FGlobalIdsMap::GlobalIdsMap), + MediaFiles(FWwiseRefMedia::FGlobalIdsMap::GlobalIdsMap), + PluginLibs(FWwiseRefPluginLib::FGlobalIdsMap::GlobalIdsMap), + PluginShareSets(FWwiseRefPluginShareSet::FGlobalIdsMap::GlobalIdsMap), + SoundBanks(FWwiseRefSoundBank::FGlobalIdsMap::GlobalIdsMap), + States(FWwiseRefState::FGlobalIdsMap::GlobalIdsMap), + StateGroups(FWwiseRefStateGroup::FGlobalIdsMap::GlobalIdsMap), + Switches(FWwiseRefSwitch::FGlobalIdsMap::GlobalIdsMap), + SwitchGroups(FWwiseRefSwitchGroup::FGlobalIdsMap::GlobalIdsMap), + Triggers(FWwiseRefTrigger::FGlobalIdsMap::GlobalIdsMap) +{} + +FWwisePlatformDataStructure::FWwisePlatformDataStructure(const FWwiseSharedPlatformId& InPlatform, FWwiseRootDataStructure& InRootData, WwiseMetadataFileMap&& InJsonFiles) : + Platform(InPlatform), + JsonFiles(MoveTemp(InJsonFiles)), + AcousticTextures(FWwiseRefAcousticTexture::FGlobalIdsMap::GlobalIdsMap), + AudioDevices(FWwiseRefAudioDevice::FGlobalIdsMap::GlobalIdsMap), + AuxBusses(FWwiseRefAuxBus::FGlobalIdsMap::GlobalIdsMap), + Busses(FWwiseRefBus::FGlobalIdsMap::GlobalIdsMap), + CustomPlugins(FWwiseRefCustomPlugin::FGlobalIdsMap::GlobalIdsMap), + DialogueArguments(FWwiseRefDialogueArgument::FGlobalIdsMap::GlobalIdsMap), + DialogueEvents(FWwiseRefDialogueEvent::FGlobalIdsMap::GlobalIdsMap), + Events(FWwiseRefEvent::FGlobalIdsMap::GlobalIdsMap), + ExternalSources(FWwiseRefExternalSource::FGlobalIdsMap::GlobalIdsMap), + GameParameters(FWwiseRefGameParameter::FGlobalIdsMap::GlobalIdsMap), + MediaFiles(FWwiseRefMedia::FGlobalIdsMap::GlobalIdsMap), + PluginLibs(FWwiseRefPluginLib::FGlobalIdsMap::GlobalIdsMap), + PluginShareSets(FWwiseRefPluginShareSet::FGlobalIdsMap::GlobalIdsMap), + SoundBanks(FWwiseRefSoundBank::FGlobalIdsMap::GlobalIdsMap), + States(FWwiseRefState::FGlobalIdsMap::GlobalIdsMap), + StateGroups(FWwiseRefStateGroup::FGlobalIdsMap::GlobalIdsMap), + Switches(FWwiseRefSwitch::FGlobalIdsMap::GlobalIdsMap), + SwitchGroups(FWwiseRefSwitchGroup::FGlobalIdsMap::GlobalIdsMap), + Triggers(FWwiseRefTrigger::FGlobalIdsMap::GlobalIdsMap) +{ + SCOPED_WWISEPROJECTDATABASE_EVENT_2(TEXT("FWwisePlatformDataStructure::FWwisePlatformDataStructure")); + for (const auto& JsonFileKV : JsonFiles) + { + const auto JsonFilePath = FName(JsonFileKV.Key); + if (JsonFileKV.Value) + { + const WwiseMetadataSharedRootFileConstPtr SharedRootFile = JsonFileKV.Value; + const FWwiseMetadataRootFile& RootFile = *SharedRootFile; + + if (RootFile.PlatformInfo) + { + // Platform have different information depending on its location. + // Project's Platform contains the Path, generator version, and Guid. + // PlatformInfo contains all the other information, including generation data. + // So we must merge one into the other. + const FWwiseMetadataPlatformInfo& PlatformInfo = *RootFile.PlatformInfo; + FWwiseRefPlatform NewPlatformRef(SharedRootFile, JsonFilePath); + + const auto& PlatformName = PlatformInfo.Platform.Name; + + // Update PlatformNames + FWwiseRefPlatform* RootPlatformByName = InRootData.PlatformNames.Find(PlatformName); + if (UNLIKELY(!RootPlatformByName)) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not find platform %s in ProjectInfo"), *PlatformName.ToString()); + continue; + } + RootPlatformByName->Merge(MoveTemp(NewPlatformRef)); + + // Update PlatformGUID + const auto* PlatformReference = RootPlatformByName->GetPlatformReference(); + check(PlatformReference); + const auto& Guid = PlatformReference->GUID; + FWwiseRefPlatform* RootPlatformByGuid = InRootData.PlatformGuids.Find(Guid); + if (UNLIKELY(!RootPlatformByGuid)) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not find platform %s guid %s in ProjectInfo"), *PlatformName.ToString(), *Guid.ToString()); + continue; + } + *RootPlatformByGuid = *RootPlatformByName; + PlatformRef = *RootPlatformByName; + } + + if (RootFile.PluginInfo) + { + const FWwiseMetadataPluginInfo& PluginInfo = *RootFile.PluginInfo; + + // PluginLibNames + PluginLibIDs + for (WwiseRefIndexType PluginLibIndex = 0; PluginLibIndex < PluginInfo.PluginLibs.Num(); ++PluginLibIndex) + { + const FWwiseMetadataPluginLib& PluginLib = PluginInfo.PluginLibs[PluginLibIndex]; + const auto& PluginRef = FWwiseRefPluginLib(SharedRootFile, JsonFilePath, PluginLibIndex); + AddRefToMap(PluginLibs, PluginRef, PluginLib.LibId, &PluginLib.LibName, nullptr, nullptr); + PluginLibNames.Add(PluginLib.LibName, PluginRef); + } + } + + if (RootFile.ProjectInfo) + { + const FWwiseMetadataProjectInfo& ProjectInfo = *RootFile.ProjectInfo; + // Should be loaded in FWwiseRootDataStructure + } + + if (RootFile.SoundBanksInfo) + { + const FWwiseMetadataSoundBanksInfo& SoundBanksInfo = *RootFile.SoundBanksInfo; + + // SoundBanks + for (WwiseRefIndexType SoundBankIndex = 0; SoundBankIndex < SoundBanksInfo.SoundBanks.Num(); ++SoundBankIndex) + { + const FWwiseMetadataSoundBank& SoundBank = SoundBanksInfo.SoundBanks[SoundBankIndex]; + const uint32 LanguageId = InRootData.GetLanguageId(SoundBank.Language); + AddRefToMap(SoundBanks, FWwiseRefSoundBank(SharedRootFile, JsonFilePath, SoundBankIndex, LanguageId), SoundBank.Id, &SoundBank.ShortName, &SoundBank.ObjectPath, &SoundBank.GUID); + + // Media + for (WwiseRefIndexType MediaIndex = 0; MediaIndex < SoundBank.Media.Num(); ++MediaIndex) + { + const FWwiseMetadataMedia& File = SoundBank.Media[MediaIndex]; + MediaFiles.Add(FWwiseDatabaseMediaIdKey(File.Id, SoundBank.Id), + FWwiseRefMedia(SharedRootFile, JsonFilePath, SoundBankIndex, LanguageId, MediaIndex)); + } + + // DialogueEvents + for (WwiseRefIndexType DialogueEventIndex = 0; DialogueEventIndex < SoundBank.DialogueEvents.Num(); ++DialogueEventIndex) + { + const FWwiseMetadataDialogueEvent& DialogueEvent = SoundBank.DialogueEvents[DialogueEventIndex]; + AddBasicRefToMap(DialogueEvents, FWwiseRefDialogueEvent(SharedRootFile, JsonFilePath, SoundBankIndex, LanguageId, DialogueEventIndex), DialogueEvent); + + // DialogueArguments + for (WwiseRefIndexType DialogueArgumentIndex = 0; DialogueArgumentIndex < DialogueEvent.Arguments.Num(); ++DialogueArgumentIndex) + { + const FWwiseMetadataDialogueArgument& DialogueArgument = DialogueEvent.Arguments[DialogueArgumentIndex]; + AddBasicRefToMap(DialogueArguments, FWwiseRefDialogueArgument(SharedRootFile, JsonFilePath, SoundBankIndex, LanguageId, DialogueEventIndex, DialogueArgumentIndex), DialogueArgument); + } + } + + // We have multiple copies of the Busses. We currently want the Init Bank version. + if (SoundBank.IsInitBank()) + { + // Busses + for (WwiseRefIndexType BusIndex = 0; BusIndex < SoundBank.Busses.Num(); ++BusIndex) + { + const FWwiseMetadataBus& Bus = SoundBank.Busses[BusIndex]; + AddBasicRefToMap(Busses, FWwiseRefBus(SharedRootFile, JsonFilePath, SoundBankIndex, LanguageId, BusIndex), Bus); + } + + // AuxBusses + for (WwiseRefIndexType AuxBusIndex = 0; AuxBusIndex < SoundBank.AuxBusses.Num(); ++AuxBusIndex) + { + const FWwiseMetadataBus& AuxBus = SoundBank.AuxBusses[AuxBusIndex]; + AddBasicRefToMap(AuxBusses, FWwiseRefAuxBus(SharedRootFile, JsonFilePath, SoundBankIndex, LanguageId, AuxBusIndex), AuxBus); + } + } + + // Plugins + if (SoundBank.Plugins) + { + const auto& Plugins = *SoundBank.Plugins; + + // CustomPlugins + for (WwiseRefIndexType CustomPluginIndex = 0; CustomPluginIndex < Plugins.Custom.Num(); ++CustomPluginIndex) + { + const FWwiseMetadataPlugin& CustomPlugin = Plugins.Custom[CustomPluginIndex]; + AddBasicRefToMap(CustomPlugins, FWwiseRefCustomPlugin(SharedRootFile, JsonFilePath, SoundBankIndex, LanguageId, CustomPluginIndex), CustomPlugin); + } + + // PluginShareSets + for (WwiseRefIndexType PluginShareSetIndex = 0; PluginShareSetIndex < Plugins.ShareSets.Num(); ++PluginShareSetIndex) + { + const FWwiseMetadataPlugin& PluginShareSet = Plugins.ShareSets[PluginShareSetIndex]; + AddBasicRefToMap(PluginShareSets, FWwiseRefPluginShareSet(SharedRootFile, JsonFilePath, SoundBankIndex, LanguageId, PluginShareSetIndex), PluginShareSet); + } + + // AudioDevices + for (WwiseRefIndexType AudioDeviceIndex = 0; AudioDeviceIndex < Plugins.AudioDevices.Num(); ++AudioDeviceIndex) + { + const FWwiseMetadataPlugin& AudioDevice = Plugins.AudioDevices[AudioDeviceIndex]; + AddBasicRefToMap(AudioDevices, FWwiseRefAudioDevice(SharedRootFile, JsonFilePath, SoundBankIndex, LanguageId, AudioDeviceIndex), AudioDevice); + } + } + + // Events + for (WwiseRefIndexType EventIndex = 0; EventIndex < SoundBank.Events.Num(); ++EventIndex) + { + const FWwiseMetadataEvent& Event = SoundBank.Events[EventIndex]; + AddEventRefToMap(Events, FWwiseRefEvent(SharedRootFile, JsonFilePath, SoundBankIndex, LanguageId, EventIndex), Event); + + // Switch Containers + TArray ContainerIndex; + ContainerIndex.Add(0); + while (true) + { + if (ContainerIndex.Num() == 0) + { + // Fully done + break; + } + + // Retrieve Container + const FWwiseMetadataSwitchContainer* Container = nullptr; + const auto* ContainerArray = &Event.SwitchContainers; + for (WwiseRefIndexType ContainerLevel = 0; ContainerLevel < ContainerIndex.Num() && ContainerArray; ++ContainerLevel) + { + WwiseRefIndexType CurrentIndex = ContainerIndex[ContainerLevel]; + if (!ContainerArray->IsValidIndex(CurrentIndex)) + { + // Done last level + ContainerArray = nullptr; + Container = nullptr; + break; + } + Container = &(*ContainerArray)[CurrentIndex]; + ContainerArray = &Container->Children; + } + + if (Container == nullptr) + { + // Done this level + ContainerIndex.Pop(); + + if (ContainerIndex.Num() > 0) + { + ++ContainerIndex[ContainerIndex.Num() - 1]; + } + continue; + } + + if (Container->MediaRefs.Num() > 0 || Container->ExternalSourceRefs.Num() >0 || Container->PluginRefs != nullptr) + { + const auto& Ref = FWwiseRefSwitchContainer(SharedRootFile, JsonFilePath, SoundBankIndex, LanguageId, EventIndex, ContainerIndex); + SwitchContainersByEvent.Add(FWwiseDatabaseLocalizableIdKey(Event.Id, LanguageId), Ref); + } + + if (ContainerArray->Num() > 0) + { + // There are children. Add one sublevel + ContainerIndex.Add(0); + } + else + { + // No children. Next. + ++ContainerIndex[ContainerIndex.Num() - 1]; + } + } + } + + // ExternalSources + for (WwiseRefIndexType ExternalSourceIndex = 0; ExternalSourceIndex < SoundBank.ExternalSources.Num(); ++ExternalSourceIndex) + { + const FWwiseMetadataExternalSource& ExternalSource = SoundBank.ExternalSources[ExternalSourceIndex]; + AddRefToMap(ExternalSources, FWwiseRefExternalSource(SharedRootFile, JsonFilePath, SoundBankIndex, LanguageId, ExternalSourceIndex), ExternalSource.Cookie, &ExternalSource.Name, &ExternalSource.ObjectPath, &ExternalSource.GUID); + } + + // AcousticTextures + for (WwiseRefIndexType AcousticTextureIndex = 0; AcousticTextureIndex < SoundBank.AcousticTextures.Num(); ++AcousticTextureIndex) + { + const FWwiseMetadataAcousticTexture& AcousticTexture = SoundBank.AcousticTextures[AcousticTextureIndex]; + AddBasicRefToMap(AcousticTextures, FWwiseRefAcousticTexture(SharedRootFile, JsonFilePath, SoundBankIndex, LanguageId, AcousticTextureIndex), AcousticTexture); + } + + // GameParameters + for (WwiseRefIndexType GameParameterIndex = 0; GameParameterIndex < SoundBank.GameParameters.Num(); ++GameParameterIndex) + { + const FWwiseMetadataGameParameter& GameParameter = SoundBank.GameParameters[GameParameterIndex]; + AddBasicRefToMap(GameParameters, FWwiseRefGameParameter(SharedRootFile, JsonFilePath, SoundBankIndex, LanguageId, GameParameterIndex), GameParameter); + } + + // StateGroups + for (WwiseRefIndexType StateGroupIndex = 0; StateGroupIndex < SoundBank.StateGroups.Num(); ++StateGroupIndex) + { + const FWwiseMetadataStateGroup& StateGroup = SoundBank.StateGroups[StateGroupIndex]; + AddBasicRefToMap(StateGroups, FWwiseRefStateGroup(SharedRootFile, JsonFilePath, SoundBankIndex, LanguageId, StateGroupIndex), StateGroup); + + // States + for (WwiseRefIndexType StateIndex = 0; StateIndex < StateGroup.States.Num(); ++StateIndex) + { + const FWwiseMetadataState& State = StateGroup.States[StateIndex]; + const FWwiseRefState StateRef(SharedRootFile, JsonFilePath, SoundBankIndex, LanguageId, StateGroupIndex, StateIndex); + const FWwiseAnyRef AnyRef = FWwiseAnyRef::Create(StateRef); + States.Add(FWwiseDatabaseLocalizableGroupValueKey(StateGroup.Id, State.Id, LanguageId), StateRef); + if (State.GUID != FGuid()) Guids.Add(FWwiseDatabaseLocalizableGuidKey(State.GUID, LanguageId), AnyRef); + if (!State.Name.IsNone()) Names.Add(FWwiseDatabaseLocalizableNameKey(State.Name, LanguageId), AnyRef); + if (!State.ObjectPath.IsNone()) Names.Add(FWwiseDatabaseLocalizableNameKey(State.ObjectPath, LanguageId), AnyRef); + } + } + + // SwitchGroups + for (WwiseRefIndexType SwitchGroupIndex = 0; SwitchGroupIndex < SoundBank.SwitchGroups.Num(); ++SwitchGroupIndex) + { + const FWwiseMetadataSwitchGroup& SwitchGroup = SoundBank.SwitchGroups[SwitchGroupIndex]; + AddBasicRefToMap(SwitchGroups, FWwiseRefSwitchGroup(SharedRootFile, JsonFilePath, SoundBankIndex, LanguageId, SwitchGroupIndex), SwitchGroup); + + // Switches + for (WwiseRefIndexType SwitchIndex = 0; SwitchIndex < SwitchGroup.Switches.Num(); ++SwitchIndex) + { + const FWwiseMetadataSwitch& Switch = SwitchGroup.Switches[SwitchIndex]; + const FWwiseRefSwitch SwitchRef(SharedRootFile, JsonFilePath, SoundBankIndex, LanguageId, SwitchGroupIndex, SwitchIndex); + const FWwiseAnyRef AnyRef = FWwiseAnyRef::Create(SwitchRef); + Switches.Add(FWwiseDatabaseLocalizableGroupValueKey(SwitchGroup.Id, Switch.Id, LanguageId), SwitchRef); + if (Switch.GUID != FGuid()) Guids.Add(FWwiseDatabaseLocalizableGuidKey(Switch.GUID, LanguageId), AnyRef); + if (!Switch.Name.IsNone()) Names.Add(FWwiseDatabaseLocalizableNameKey(Switch.Name, LanguageId), AnyRef); + if (!Switch.ObjectPath.IsNone()) Names.Add(FWwiseDatabaseLocalizableNameKey(Switch.ObjectPath, LanguageId), AnyRef); + } + } + + // Triggers + for (WwiseRefIndexType TriggerIndex = 0; TriggerIndex < SoundBank.Triggers.Num(); ++TriggerIndex) + { + const FWwiseMetadataTrigger& Trigger = SoundBank.Triggers[TriggerIndex]; + AddBasicRefToMap(Triggers, FWwiseRefTrigger(SharedRootFile, JsonFilePath, SoundBankIndex, LanguageId, TriggerIndex), Trigger); + } + } + } + } + } +} + +FWwisePlatformDataStructure::FWwisePlatformDataStructure(const FWwisePlatformDataStructure& Rhs) : + FWwiseRefAcousticTexture::FGlobalIdsMap(Rhs), + FWwiseRefAudioDevice::FGlobalIdsMap(Rhs), + FWwiseRefAuxBus::FGlobalIdsMap(Rhs), + FWwiseRefBus::FGlobalIdsMap(Rhs), + FWwiseRefCustomPlugin::FGlobalIdsMap(Rhs), + FWwiseRefDialogueArgument::FGlobalIdsMap(Rhs), + FWwiseRefDialogueEvent::FGlobalIdsMap(Rhs), + FWwiseRefEvent::FGlobalIdsMap(Rhs), + FWwiseRefExternalSource::FGlobalIdsMap(Rhs), + FWwiseRefGameParameter::FGlobalIdsMap(Rhs), + FWwiseRefMedia::FGlobalIdsMap(Rhs), + FWwiseRefPluginLib::FGlobalIdsMap(Rhs), + FWwiseRefPluginShareSet::FGlobalIdsMap(Rhs), + FWwiseRefSoundBank::FGlobalIdsMap(Rhs), + FWwiseRefState::FGlobalIdsMap(Rhs), + FWwiseRefStateGroup::FGlobalIdsMap(Rhs), + FWwiseRefSwitch::FGlobalIdsMap(Rhs), + FWwiseRefSwitchGroup::FGlobalIdsMap(Rhs), + FWwiseRefTrigger::FGlobalIdsMap(Rhs), + Platform(Rhs.Platform), + PlatformRef(Rhs.PlatformRef), + GeneratedPlatformFiles(Rhs.GeneratedPlatformFiles), + JsonFiles(Rhs.JsonFiles), + AcousticTextures(FWwiseRefAcousticTexture::FGlobalIdsMap::GlobalIdsMap), + AudioDevices(FWwiseRefAudioDevice::FGlobalIdsMap::GlobalIdsMap), + AuxBusses(FWwiseRefAuxBus::FGlobalIdsMap::GlobalIdsMap), + Busses(FWwiseRefBus::FGlobalIdsMap::GlobalIdsMap), + CustomPlugins(FWwiseRefCustomPlugin::FGlobalIdsMap::GlobalIdsMap), + DialogueArguments(FWwiseRefDialogueArgument::FGlobalIdsMap::GlobalIdsMap), + DialogueEvents(FWwiseRefDialogueEvent::FGlobalIdsMap::GlobalIdsMap), + Events(FWwiseRefEvent::FGlobalIdsMap::GlobalIdsMap), + ExternalSources(FWwiseRefExternalSource::FGlobalIdsMap::GlobalIdsMap), + GameParameters(FWwiseRefGameParameter::FGlobalIdsMap::GlobalIdsMap), + MediaFiles(FWwiseRefMedia::FGlobalIdsMap::GlobalIdsMap), + PluginLibs(FWwiseRefPluginLib::FGlobalIdsMap::GlobalIdsMap), + PluginShareSets(FWwiseRefPluginShareSet::FGlobalIdsMap::GlobalIdsMap), + SoundBanks(FWwiseRefSoundBank::FGlobalIdsMap::GlobalIdsMap), + States(FWwiseRefState::FGlobalIdsMap::GlobalIdsMap), + StateGroups(FWwiseRefStateGroup::FGlobalIdsMap::GlobalIdsMap), + Switches(FWwiseRefSwitch::FGlobalIdsMap::GlobalIdsMap), + SwitchGroups(FWwiseRefSwitchGroup::FGlobalIdsMap::GlobalIdsMap), + Triggers(FWwiseRefTrigger::FGlobalIdsMap::GlobalIdsMap), + PluginLibNames(Rhs.PluginLibNames), + SwitchContainersByEvent(Rhs.SwitchContainersByEvent), + Guids(Rhs.Guids), + Names(Rhs.Names) +{} + +FWwisePlatformDataStructure::FWwisePlatformDataStructure(FWwisePlatformDataStructure&& Rhs) : + FWwiseRefAcousticTexture::FGlobalIdsMap(MoveTemp(Rhs)), + FWwiseRefAudioDevice::FGlobalIdsMap(MoveTemp(Rhs)), + FWwiseRefAuxBus::FGlobalIdsMap(MoveTemp(Rhs)), + FWwiseRefBus::FGlobalIdsMap(MoveTemp(Rhs)), + FWwiseRefCustomPlugin::FGlobalIdsMap(MoveTemp(Rhs)), + FWwiseRefDialogueArgument::FGlobalIdsMap(MoveTemp(Rhs)), + FWwiseRefDialogueEvent::FGlobalIdsMap(MoveTemp(Rhs)), + FWwiseRefEvent::FGlobalIdsMap(MoveTemp(Rhs)), + FWwiseRefExternalSource::FGlobalIdsMap(MoveTemp(Rhs)), + FWwiseRefGameParameter::FGlobalIdsMap(MoveTemp(Rhs)), + FWwiseRefMedia::FGlobalIdsMap(MoveTemp(Rhs)), + FWwiseRefPluginLib::FGlobalIdsMap(MoveTemp(Rhs)), + FWwiseRefPluginShareSet::FGlobalIdsMap(MoveTemp(Rhs)), + FWwiseRefSoundBank::FGlobalIdsMap(MoveTemp(Rhs)), + FWwiseRefState::FGlobalIdsMap(MoveTemp(Rhs)), + FWwiseRefStateGroup::FGlobalIdsMap(MoveTemp(Rhs)), + FWwiseRefSwitch::FGlobalIdsMap(MoveTemp(Rhs)), + FWwiseRefSwitchGroup::FGlobalIdsMap(MoveTemp(Rhs)), + FWwiseRefTrigger::FGlobalIdsMap(MoveTemp(Rhs)), + Platform(MoveTemp(Rhs.Platform)), + PlatformRef(MoveTemp(Rhs.PlatformRef)), + GeneratedPlatformFiles(MoveTemp(Rhs.GeneratedPlatformFiles)), + JsonFiles(MoveTemp(Rhs.JsonFiles)), + AcousticTextures(FWwiseRefAcousticTexture::FGlobalIdsMap::GlobalIdsMap), + AudioDevices(FWwiseRefAudioDevice::FGlobalIdsMap::GlobalIdsMap), + AuxBusses(FWwiseRefAuxBus::FGlobalIdsMap::GlobalIdsMap), + Busses(FWwiseRefBus::FGlobalIdsMap::GlobalIdsMap), + CustomPlugins(FWwiseRefCustomPlugin::FGlobalIdsMap::GlobalIdsMap), + DialogueArguments(FWwiseRefDialogueArgument::FGlobalIdsMap::GlobalIdsMap), + DialogueEvents(FWwiseRefDialogueEvent::FGlobalIdsMap::GlobalIdsMap), + Events(FWwiseRefEvent::FGlobalIdsMap::GlobalIdsMap), + ExternalSources(FWwiseRefExternalSource::FGlobalIdsMap::GlobalIdsMap), + GameParameters(FWwiseRefGameParameter::FGlobalIdsMap::GlobalIdsMap), + MediaFiles(FWwiseRefMedia::FGlobalIdsMap::GlobalIdsMap), + PluginLibs(FWwiseRefPluginLib::FGlobalIdsMap::GlobalIdsMap), + PluginShareSets(FWwiseRefPluginShareSet::FGlobalIdsMap::GlobalIdsMap), + SoundBanks(FWwiseRefSoundBank::FGlobalIdsMap::GlobalIdsMap), + States(FWwiseRefState::FGlobalIdsMap::GlobalIdsMap), + StateGroups(FWwiseRefStateGroup::FGlobalIdsMap::GlobalIdsMap), + Switches(FWwiseRefSwitch::FGlobalIdsMap::GlobalIdsMap), + SwitchGroups(FWwiseRefSwitchGroup::FGlobalIdsMap::GlobalIdsMap), + Triggers(FWwiseRefTrigger::FGlobalIdsMap::GlobalIdsMap), + PluginLibNames(MoveTemp(Rhs.PluginLibNames)), + SwitchContainersByEvent(MoveTemp(Rhs.SwitchContainersByEvent)), + Guids(MoveTemp(Rhs.Guids)), + Names(MoveTemp(Rhs.Names)) +{} + +FWwiseRootDataStructure& FWwiseRootDataStructure::operator+=(FWwiseRootDataStructure&& Rhs) +{ + // GeneratedRootFiles += MoveTemp(Rhs.GeneratedRootFiles); + JsonFiles.Append(MoveTemp(Rhs.JsonFiles)); + LanguageNames.Append(MoveTemp(Rhs.LanguageNames)); + LanguageIds.Append(MoveTemp(Rhs.LanguageIds)); + PlatformNames.Append(MoveTemp(Rhs.PlatformNames)); + PlatformGuids.Append(MoveTemp(Rhs.PlatformGuids)); + return *this; +} + +FWwisePlatformDataStructure& FWwisePlatformDataStructure::operator+=(FWwisePlatformDataStructure&& Rhs) +{ + // GeneratedPlatformFiles += MoveTemp(Rhs.GeneratedPlatformFiles); + JsonFiles.Append(MoveTemp(Rhs.JsonFiles)); + MediaFiles.Append(MoveTemp(Rhs.MediaFiles)); + PluginLibNames.Append(MoveTemp(Rhs.PluginLibNames)); + PluginLibs.Append(MoveTemp(Rhs.PluginLibs)); + SoundBanks.Append(MoveTemp(Rhs.SoundBanks)); + DialogueEvents.Append(MoveTemp(Rhs.DialogueEvents)); + DialogueArguments.Append(MoveTemp(Rhs.DialogueArguments)); + Busses.Append(MoveTemp(Rhs.Busses)); + AuxBusses.Append(MoveTemp(Rhs.AuxBusses)); + Events.Append(MoveTemp(Rhs.Events)); + ExternalSources.Append(MoveTemp(Rhs.ExternalSources)); + AcousticTextures.Append(MoveTemp(Rhs.AcousticTextures)); + GameParameters.Append(MoveTemp(Rhs.GameParameters)); + StateGroups.Append(MoveTemp(Rhs.StateGroups)); + SwitchGroups.Append(MoveTemp(Rhs.SwitchGroups)); + Triggers.Append(MoveTemp(Rhs.Triggers)); + States.Append(MoveTemp(Rhs.States)); + Switches.Append(MoveTemp(Rhs.Switches)); + SwitchContainersByEvent.Append(MoveTemp(Rhs.SwitchContainersByEvent)); + return *this; +} + +bool FWwisePlatformDataStructure::GetFromId(FWwiseRefMedia& OutRef, uint32 InShortId, uint32 InLanguageId, uint32 InSoundBankId) const +{ + const FWwiseRefMedia* Result = nullptr; + if (LIKELY(InSoundBankId != 0)) + { + FWwiseDatabaseMediaIdKey MediaId(InShortId, InSoundBankId); + Result = MediaFiles.Find(MediaId); + } + else + { + for (const auto& MediaFile : MediaFiles) + { + if (MediaFile.Key.MediaId == InShortId) + { + Result = &MediaFile.Value; + break; + } + } + } + + if (UNLIKELY(!Result)) + { + UE_LOG(LogWwiseProjectDatabase, Warning, TEXT("Could not find Media %" PRIu32 " (Lang=%" PRIu32 "; SB=%" PRIu32 ")"), InShortId, InLanguageId, InSoundBankId); + return false; + } + + OutRef = *Result; + return true; +} + + +FWwiseDataStructure& FWwiseDataStructure::operator+=(FWwiseDataStructure&& Rhs) +{ + FWriteScopeLock ScopeLockLhs(Lock); + FWriteScopeLock ScopeLockRhs(Rhs.Lock); + + Platforms.Append(MoveTemp(Rhs.Platforms)); + + return *this; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseDatabaseIdentifiers.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseDatabaseIdentifiers.cpp new file mode 100644 index 0000000..17ced0b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseDatabaseIdentifiers.cpp @@ -0,0 +1,60 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseDatabaseIdentifiers.h" + +uint32 GetTypeHash(const FWwiseDatabaseMediaIdKey& MediaId) +{ + return HashCombine( + GetTypeHash(MediaId.MediaId), + GetTypeHash(MediaId.SoundBankId)); +} + +uint32 GetTypeHash(const FWwiseDatabaseLocalizableIdKey& LocalizableId) +{ + return HashCombine(HashCombine( + GetTypeHash(LocalizableId.Id), + GetTypeHash(LocalizableId.SoundBankId)), + GetTypeHash(LocalizableId.LanguageId)); +} + +uint32 GetTypeHash(const FWwiseDatabaseGroupValueKey& GroupId) +{ + return HashCombine( + GetTypeHash(GroupId.GroupId), + GetTypeHash(GroupId.Id)); +} + +uint32 GetTypeHash(const FWwiseDatabaseLocalizableGroupValueKey& LocalizableGroupValue) +{ + return HashCombine( + GetTypeHash(LocalizableGroupValue.GroupValue), + GetTypeHash(LocalizableGroupValue.LanguageId)); +} + +uint32 GetTypeHash(const FWwiseDatabaseLocalizableGuidKey& LocalizableGuid) +{ + return HashCombine( + GetTypeHash(LocalizableGuid.Guid), + GetTypeHash(LocalizableGuid.LanguageId)); +} +uint32 GetTypeHash(const FWwiseDatabaseLocalizableNameKey& LocalizableName) +{ + return HashCombine( + GetTypeHash(LocalizableName.Name), + GetTypeHash(LocalizableName.LanguageId)); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseDirectoryVisitor.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseDirectoryVisitor.cpp new file mode 100644 index 0000000..ce09da5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseDirectoryVisitor.cpp @@ -0,0 +1,480 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseDirectoryVisitor.h" + +#include "AkUnrealHelper.h" +#include "Wwise/Metadata/WwiseMetadataRootFile.h" +#include "Wwise/Metadata/WwiseMetadataProjectInfo.h" +#include "Wwise/Metadata/WwiseMetadataPlatform.h" +#include "Wwise/Metadata/WwiseMetadataLanguage.h" +#include "Wwise/Stats/ProjectDatabase.h" + +#include "Async/Async.h" +#include "Misc/Paths.h" + +// +// FPlatformRootDirectoryVisitor +// +class FWwiseDirectoryVisitor::FPlatformRootDirectoryVisitor : public IPlatformFile::FDirectoryVisitor, public FWwiseDirectoryVisitor::IGettableVisitor +{ +public: + FPlatformRootDirectoryVisitor( + const FWwiseSharedPlatformId& InPlatform, + IPlatformFile& InFileInterface) : + Platform(InPlatform), + FileInterface(InFileInterface) + {} + bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override; + bool StartJobIfValid(); + FWwiseGeneratedFiles::FPlatformFiles& Get() override; + + const FWwiseSharedPlatformId Platform; + IPlatformFile& FileInterface; + + FWwiseGeneratedFiles::FPlatformFiles PlatformFiles; + TArray> Futures; +}; + +// +// FSoundBankVisitor +// +class FWwiseDirectoryVisitor::FSoundBankVisitor : public IPlatformFile::FDirectoryVisitor, public FWwiseDirectoryVisitor::IGettableVisitor +{ +public: + FSoundBankVisitor(IPlatformFile& InFileInterface) : + FileInterface(InFileInterface) + {} + virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory); + FWwiseGeneratedFiles::FPlatformFiles& Get() override; + + IPlatformFile& FileInterface; + FWwiseGeneratedFiles::FPlatformFiles Result; +}; + +// +// FMediaVisitor +// +class FWwiseDirectoryVisitor::FMediaVisitor : public IPlatformFile::FDirectoryVisitor, public FWwiseDirectoryVisitor::IGettableVisitor +{ +public: + FMediaVisitor(IPlatformFile& InFileInterface) : + FileInterface(InFileInterface) + {} + virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory); + FWwiseGeneratedFiles::FPlatformFiles& Get() override; + + IPlatformFile& FileInterface; + FWwiseGeneratedFiles::FPlatformFiles Result; +}; + +// +// FWwiseDirectoryVisitor +// +bool FWwiseDirectoryVisitor::Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) +{ + SCOPED_WWISEPROJECTDATABASE_EVENT_2(TEXT("FWwiseDirectoryVisitor::Visit")); + // make sure all paths are "standardized" so the other end can match up with it's own standardized paths + FString RelativeFilename = FilenameOrDirectory; + FPaths::MakeStandardFilename(RelativeFilename); + const auto Filename = FPaths::GetCleanFilename(RelativeFilename); + + if (Filename.StartsWith(TEXT("."))) + { + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("[WwiseDirectoryVisitor] Skipping: %s"), *RelativeFilename); + return true; + } + + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("[WwiseDirectoryVisitor] Visiting %s"), *RelativeFilename); + + if (bIsDirectory) + { + // Skip directories, they are to be processed by ProjectInfo.json's Path + return true; + } + + FWwiseGeneratedFiles::FileTuple FileToAdd(RelativeFilename, FileInterface.GetTimeStamp(FilenameOrDirectory)); + const auto Extension = FPaths::GetExtension(RelativeFilename); + + if (Filename.Equals(TEXT("ProjectInfo.json"), ESearchCase::IgnoreCase)) + { + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Found ProjectInfo: %s"), *RelativeFilename); + + // We need to retrieve the Path from ProjectInfo. Parse this file immediately + // (will be parsed twice. Now once, and officially later - since the file is small, it's not a big worry) + auto Root = FWwiseMetadataRootFile::LoadFile(RelativeFilename); + if (!Root || !Root->ProjectInfo) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not read ProjectInfo to retrieve paths."), *RelativeFilename); + return true; + } + + GeneratedDirectory.ProjectInfo = Root; + + const auto Path = FPaths::GetPath(FilenameOrDirectory); + + auto& Platforms = Root->ProjectInfo->Platforms; + bool bFoundPlatform = false; + if (!PlatformName) + { + UE_LOG(LogWwiseProjectDatabase, Log, TEXT("Skipping loading all platforms")); + } + else + { + for (auto& Platform : Platforms) + { + if (PlatformName->ToString().Equals(Platform.Name.ToString(), ESearchCase::IgnoreCase)) + { + bFoundPlatform = true; + break; + } + } + + UE_CLOG(UNLIKELY(!bFoundPlatform), LogWwiseProjectDatabase, Log, TEXT("Requested platform not found: %s"), *PlatformName->ToString()); + } + + if (bFoundPlatform) + { + for (auto& Platform : Platforms) + { + const auto PlatformPath = Path / Platform.Path.ToString(); + + if (!PlatformName->ToString().Equals(Platform.Name.ToString(), ESearchCase::IgnoreCase)) + { + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Skipping platform %s"), *Platform.Name.ToString()); + continue; + } + if (PlatformGuid && *PlatformGuid != Platform.BasePlatformGUID) + { + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Skipping platform %s (Base %s)"), *Platform.Name.ToString(), *Platform.BasePlatform.ToString()); + continue; + } + + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Visiting platform %s at: %s"), *Platform.Name.ToString(), *Platform.Path.ToString()); + + FWwisePlatformId CurrentPlatform; + CurrentPlatform.PlatformGuid = Platform.GUID; + CurrentPlatform.PlatformName = Platform.Name; + FString RelativePlatformPath(PlatformPath); + FPaths::MakePathRelativeTo(RelativePlatformPath, *AkUnrealHelper::GetSoundBankDirectory()); + CurrentPlatform.PathRelativeToGeneratedSoundBanks = FName(RelativePlatformPath); + FWwiseSharedPlatformId PlatformRef; + PlatformRef.Platform = MakeShared(CurrentPlatform); + + Futures.Add(Async(EAsyncExecution::TaskGraph, [this, PlatformRef, PlatformPath] { + auto* RootVisitor = new FPlatformRootDirectoryVisitor(PlatformRef, FileInterface); + if (!FileInterface.IterateDirectory(*PlatformPath, *RootVisitor) || + !RootVisitor->StartJobIfValid()) + { + UE_LOG(LogWwiseProjectDatabase, Warning, TEXT("Could not find generated platform %s at: %s"), *PlatformRef.GetPlatformName().ToString(), *PlatformRef.Platform->PathRelativeToGeneratedSoundBanks.ToString()); + delete RootVisitor; + RootVisitor = nullptr; + } + return RootVisitor; + })); + } + } + + GeneratedDirectory.GeneratedRootFiles.ProjectInfoFile = MoveTemp(FileToAdd); + } + else if (Filename.Equals(TEXT("Wwise_IDs.h"), ESearchCase::IgnoreCase)) + { + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Found Wwise IDs: %s"), *RelativeFilename); + GeneratedDirectory.GeneratedRootFiles.WwiseIDsFile = MoveTemp(FileToAdd); + } + else if (Filename.Equals(TEXT("SoundBanksGeneration.log"), ESearchCase::IgnoreCase) + || Extension.Equals(TEXT("xml"), ESearchCase::IgnoreCase)) + { + // Nothing to do + } + else + { + UE_LOG(LogWwiseProjectDatabase, Log, TEXT("Unknown file. Not in a platform. Will be ignored: %s"), *RelativeFilename); + } + + return true; +} + +FWwiseGeneratedFiles& FWwiseDirectoryVisitor::Get() +{ + SCOPED_WWISEPROJECTDATABASE_EVENT_4(TEXT("FWwiseDirectoryVisitor::Get")); + for (const auto& Future : Futures) + { + auto* Result = Future.Get(); + if (Result) + { + auto& PlatformFiles = Result->Get(); + if (PlatformFiles.IsValid()) + { + GeneratedDirectory.Platforms.Add(Result->Platform, PlatformFiles); + } + delete Result; + } + } + Futures.Empty(); + return GeneratedDirectory; +} + + + +// +// FPlatformRootDirectoryVisitor +// +bool FWwiseDirectoryVisitor::FPlatformRootDirectoryVisitor::Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) +{ + SCOPED_WWISEPROJECTDATABASE_EVENT_2(TEXT("FPlatformRootDirectoryVisitor::Visit")); + // make sure all paths are "standardized" so the other end can match up with it's own standardized paths + FString RelativeFilename = FilenameOrDirectory; + FPaths::MakeStandardFilename(RelativeFilename); + const auto Filename = FPaths::GetCleanFilename(RelativeFilename); + + if (Filename.StartsWith(TEXT("."))) + { + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("[RootFilesVisitor] Skipping: %s"), *RelativeFilename); + return true; + } + + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("[RootFilesVisitor] Visiting %s"), *RelativeFilename); + + if (bIsDirectory) + { + PlatformFiles.DirectoriesToWatch.Add(RelativeFilename); + + if (Filename.Equals(TEXT("Media"), ESearchCase::IgnoreCase)) + { + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Found media directory: %s"), *RelativeFilename); + PlatformFiles.MediaDirectory = FilenameOrDirectory; + } + else if (Filename.Equals(TEXT("Bus"), ESearchCase::IgnoreCase) + || Filename.Equals(TEXT("Event"), ESearchCase::IgnoreCase)) + { + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Found auto SoundBank directory: %s"), *RelativeFilename); + PlatformFiles.AutoSoundBankDirectories.Add(FilenameOrDirectory); + } + else { + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Found language directory: %s"), *RelativeFilename); + PlatformFiles.LanguageDirectories.Add(FilenameOrDirectory); + } + + return true; + } + + FWwiseGeneratedFiles::FileTuple FileToAdd(RelativeFilename, FileInterface.GetTimeStamp(FilenameOrDirectory)); + const auto Extension = FPaths::GetExtension(RelativeFilename); + + if (!Extension.Equals(TEXT("json"), ESearchCase::IgnoreCase) + && !Extension.Equals(TEXT("txt"), ESearchCase::IgnoreCase) + && !Extension.Equals(TEXT("bnk"), ESearchCase::IgnoreCase) + && !Extension.Equals(TEXT("xml"), ESearchCase::IgnoreCase)) + { + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("Adding extra file: %s"), *RelativeFilename); + PlatformFiles.ExtraFiles.Add(MoveTemp(FileToAdd)); + return true; + } + + if (Extension.Equals(TEXT("bnk"), ESearchCase::IgnoreCase)) + { + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("Adding SoundBank file: %s"), *RelativeFilename); + PlatformFiles.SoundBankFiles.Add(MoveTemp(FileToAdd)); + return true; + } + else if (Extension.Equals(TEXT("xml"), ESearchCase::IgnoreCase) + || Extension.Equals(TEXT("txt"), ESearchCase::IgnoreCase)) + { + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("Skipping file: %s"), *RelativeFilename); + return true; + } + else if (Filename.Equals(TEXT("SoundbanksInfo.json"), ESearchCase::IgnoreCase)) + { + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Found monolithic SoundBank info: %s"), *RelativeFilename); + PlatformFiles.SoundbanksInfoFile = MoveTemp(FileToAdd); + } + else if (Filename.Equals(TEXT("PlatformInfo.json"), ESearchCase::IgnoreCase)) + { + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Found platform info: %s"), *RelativeFilename); + PlatformFiles.PlatformInfoFile = MoveTemp(FileToAdd); + } + else if (Filename.Equals(TEXT("PluginInfo.json"), ESearchCase::IgnoreCase)) + { + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Found plugin info: %s"), *RelativeFilename); + PlatformFiles.PluginInfoFile = MoveTemp(FileToAdd); + } + else + { + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("Adding metadata file: %s"), *RelativeFilename); + PlatformFiles.MetadataFiles.Add(MoveTemp(FileToAdd)); + } + + return true; +} + +bool FWwiseDirectoryVisitor::FPlatformRootDirectoryVisitor::StartJobIfValid() +{ + if (!PlatformFiles.IsValid()) + { + return false; + } + + if (!PlatformFiles.MediaDirectory.IsEmpty()) + { + const auto& Elem = PlatformFiles.MediaDirectory; + Futures.Add(Async(EAsyncExecution::TaskGraph, [this, Elem] { + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Visiting media directory: %s"), *Elem); + auto* MediaVisitor = new FMediaVisitor(FileInterface); + FileInterface.IterateDirectory(*Elem, *MediaVisitor); + return static_cast(MediaVisitor); + })); + } + + for (const auto& Elem : PlatformFiles.AutoSoundBankDirectories) + { + Futures.Add(Async(EAsyncExecution::TaskGraph, [this, Elem] { + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Visiting auto SoundBank directory: %s"), *Elem); + auto* SoundBankVisitor = new FSoundBankVisitor(FileInterface); + FileInterface.IterateDirectory(*Elem, *SoundBankVisitor); + return static_cast(SoundBankVisitor); + })); + } + + for (const auto& Elem : PlatformFiles.LanguageDirectories) + { + Futures.Add(Async(EAsyncExecution::TaskGraph, [this, Elem] { + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Visiting language directory: %s"), *Elem); + auto* SoundBankVisitor = new FSoundBankVisitor(FileInterface); + FileInterface.IterateDirectory(*Elem, *SoundBankVisitor); + return static_cast(SoundBankVisitor); + })); + } + + return true; +} + +FWwiseGeneratedFiles::FPlatformFiles& FWwiseDirectoryVisitor::FPlatformRootDirectoryVisitor::Get() +{ + SCOPED_WWISEPROJECTDATABASE_EVENT_4(TEXT("FPlatformRootDirectoryVisitor::Get")); + for (const auto& Future : Futures) + { + auto* Result = Future.Get(); + if (Result) + { + PlatformFiles.Append(MoveTemp(Result->Get())); + delete Result; + } + } + Futures.Empty(); + return PlatformFiles; +} + +// +// FSoundBankVisitor +// +bool FWwiseDirectoryVisitor::FSoundBankVisitor::Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) +{ + // make sure all paths are "standardized" so the other end can match up with it's own standardized paths + FString RelativeFilename = FilenameOrDirectory; + FPaths::MakeStandardFilename(RelativeFilename); + const auto Filename = FPaths::GetCleanFilename(RelativeFilename); + const auto Extension = FPaths::GetExtension(RelativeFilename); + + if (Filename.StartsWith(TEXT("."))) + { + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("[SoundBankVisitor] Skipping: %s"), *RelativeFilename); + return true; + } + + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("[SoundBankVisitor] Visiting %s"), *RelativeFilename); + + if (bIsDirectory) + { + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("Iterating folder: %s"), *RelativeFilename); + Result.DirectoriesToWatch.Add(RelativeFilename); + FileInterface.IterateDirectory(FilenameOrDirectory, *this); + return true; + } + + FWwiseGeneratedFiles::FileTuple FileToAdd(RelativeFilename, FileInterface.GetTimeStamp(FilenameOrDirectory)); + + if (Extension.Equals(TEXT("json"), ESearchCase::IgnoreCase)) + { + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("Adding metadata file: %s"), *RelativeFilename); + Result.MetadataFiles.Add(MoveTemp(FileToAdd)); + return true; + } + else if (Extension.Equals(TEXT("bnk"), ESearchCase::IgnoreCase)) + { + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("Adding SoundBank file: %s"), *RelativeFilename); + Result.SoundBankFiles.Add(MoveTemp(FileToAdd)); + return true; + } + else if (Extension.Equals(TEXT("xml"), ESearchCase::IgnoreCase) + || Extension.Equals(TEXT("txt"), ESearchCase::IgnoreCase)) + { + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("Skipping file: %s"), *RelativeFilename); + return true; + } + else + { + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("Adding extra file: %s"), *RelativeFilename); + Result.ExtraFiles.Add(MoveTemp(FileToAdd)); + return true; + } +} + +FWwiseGeneratedFiles::FPlatformFiles& FWwiseDirectoryVisitor::FSoundBankVisitor::Get() +{ + return Result; +} + +// +// FMediaVisitor +// +bool FWwiseDirectoryVisitor::FMediaVisitor::Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) +{ + // make sure all paths are "standardized" so the other end can match up with it's own standardized paths + FString RelativeFilename = FilenameOrDirectory; + FPaths::MakeStandardFilename(RelativeFilename); + + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("[MediaVisitor] Visiting %s"), *RelativeFilename); + + if (bIsDirectory) + { + Result.DirectoriesToWatch.Add(RelativeFilename); + FileInterface.IterateDirectory(FilenameOrDirectory, *this); + } + else + { + FWwiseGeneratedFiles::FileTuple FileToAdd(RelativeFilename, FileInterface.GetTimeStamp(FilenameOrDirectory)); + const auto Extension = FPaths::GetExtension(RelativeFilename); + + if (Extension.Equals(TEXT("wem"), ESearchCase::IgnoreCase)) + { + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("Adding media file: %s"), *RelativeFilename); + Result.MediaFiles.Add(MoveTemp(FileToAdd)); + } + else + { + UE_LOG(LogWwiseProjectDatabase, Log, TEXT("Adding unexpected extra file: %s"), *RelativeFilename); + Result.ExtraFiles.Add(MoveTemp(FileToAdd)); + } + } + return true; +} + +FWwiseGeneratedFiles::FPlatformFiles& FWwiseDirectoryVisitor::FMediaVisitor::Get() +{ + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseDirectoryVisitor.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseDirectoryVisitor.h new file mode 100644 index 0000000..3c31b04 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseDirectoryVisitor.h @@ -0,0 +1,65 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseGeneratedFiles.h" + +#include "Async/Future.h" +#include "CoreTypes.h" +#include "Containers/UnrealString.h" +#include "GenericPlatform/GenericPlatformFile.h" + +class WWISEPROJECTDATABASE_API FWwiseDirectoryVisitor : public IPlatformFile::FDirectoryVisitor +{ +public: + FWwiseDirectoryVisitor(IPlatformFile& InFileInterface, + const FName* InPlatformName = nullptr, + const FGuid* InPlatformGuid = nullptr) : + FileInterface(InFileInterface), + PlatformName(InPlatformName), + PlatformGuid(InPlatformGuid) + {} + + FWwiseGeneratedFiles& Get(); + +protected: + bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override; + +private: + IPlatformFile& FileInterface; + FWwiseGeneratedFiles GeneratedDirectory; + + class IGettableVisitor + { + public: + virtual FWwiseGeneratedFiles::FPlatformFiles& Get() = 0; + virtual ~IGettableVisitor() {} + }; + class FPlatformRootDirectoryVisitor; + + TArray> Futures; + + const FName* PlatformName; + const FGuid* PlatformGuid; + + class FSoundBankVisitor; + class FMediaVisitor; + + FWwiseDirectoryVisitor& operator=(const FWwiseDirectoryVisitor& Rhs) = delete; + FWwiseDirectoryVisitor(const FWwiseDirectoryVisitor& Rhs) = delete; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseGeneratedFiles.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseGeneratedFiles.cpp new file mode 100644 index 0000000..1dd600d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseGeneratedFiles.cpp @@ -0,0 +1,44 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseGeneratedFiles.h" +#include "Wwise/Metadata//WwiseMetadataRootFile.h" + +bool FWwiseGeneratedFiles::FPlatformFiles::IsValid() const +{ + return !PlatformInfoFile.Get<0>().IsEmpty() + && !PluginInfoFile.Get<0>().IsEmpty() + && (!SoundbanksInfoFile.Get<0>().IsEmpty() || SoundBankFiles.Num() > 0); +} + +void FWwiseGeneratedFiles::FPlatformFiles::Append(FPlatformFiles&& Rhs) +{ + SoundBankFiles.Append(MoveTemp(Rhs.SoundBankFiles)); + MediaFiles.Append(MoveTemp(Rhs.MediaFiles)); + MetadataFiles.Append(MoveTemp(Rhs.MetadataFiles)); + ExtraFiles.Append(MoveTemp(Rhs.ExtraFiles)); + + DirectoriesToWatch.Append(MoveTemp(Rhs.DirectoriesToWatch)); + LanguageDirectories.Append(MoveTemp(Rhs.LanguageDirectories)); + AutoSoundBankDirectories.Append(MoveTemp(Rhs.AutoSoundBankDirectories)); +} + +bool FWwiseGeneratedFiles::IsValid() const +{ + return !GeneratedRootFiles.ProjectInfoFile.Get<0>().IsEmpty() + && ProjectInfo.IsValid() && ProjectInfo->ProjectInfo; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseProjectDatabase.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseProjectDatabase.cpp new file mode 100644 index 0000000..33928dc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseProjectDatabase.cpp @@ -0,0 +1,501 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseProjectDatabase.h" + +#include "Wwise/WwiseResourceLoader.h" +#include "Wwise/WwiseProjectDatabaseDelegates.h" + +#include "Async/Async.h" +#include "Misc/ScopedSlowTask.h" +#include "Wwise/Metadata/WwiseMetadataPlatformInfo.h" + +#define LOCTEXT_NAMESPACE "WwiseProjectDatabase" + +FWwiseDataStructureScopeLock::FWwiseDataStructureScopeLock(const FWwiseProjectDatabase& InProjectDatabase) : + FRWScopeLock(const_cast(InProjectDatabase.GetLockedDataStructure()->Lock), SLT_ReadOnly), + DataStructure(*InProjectDatabase.GetLockedDataStructure()), + CurrentLanguage(InProjectDatabase.GetCurrentLanguage()), + CurrentPlatform(InProjectDatabase.GetCurrentPlatform()), + bDisableDefaultPlatforms(InProjectDatabase.DisableDefaultPlatforms()) +{ +} + +const WwiseAcousticTextureGlobalIdsMap& FWwiseDataStructureScopeLock::GetAcousticTextures() const +{ + static const auto Empty = WwiseAcousticTextureGlobalIdsMap(); + + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return Empty; + + return PlatformData->AcousticTextures; +} + +FWwiseRefAcousticTexture FWwiseDataStructureScopeLock::GetAcousticTexture(const FWwiseObjectInfo& InInfo) const +{ + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return {}; + + FWwiseRefAcousticTexture Result; + PlatformData->GetRef(Result, GetCurrentLanguage(), InInfo); + return Result; +} + +const WwiseAudioDeviceGlobalIdsMap& FWwiseDataStructureScopeLock::GetAudioDevices() const +{ + static const auto Empty = WwiseAudioDeviceGlobalIdsMap(); + + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return Empty; + + return PlatformData->AudioDevices; +} + +FWwiseRefAudioDevice FWwiseDataStructureScopeLock::GetAudioDevice(const FWwiseObjectInfo& InInfo) const +{ + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return {}; + + FWwiseRefAudioDevice Result; + PlatformData->GetRef(Result, GetCurrentLanguage(), InInfo); + return Result; +} + +const WwiseAuxBusGlobalIdsMap& FWwiseDataStructureScopeLock::GetAuxBusses() const +{ + static const auto Empty = WwiseAuxBusGlobalIdsMap(); + + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return Empty; + + return PlatformData->AuxBusses; +} + +FWwiseRefAuxBus FWwiseDataStructureScopeLock::GetAuxBus(const FWwiseObjectInfo& InInfo) const +{ + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return {}; + + FWwiseRefAuxBus Result; + PlatformData->GetRef(Result, GetCurrentLanguage(), InInfo); + return Result; +} + +const WwiseBusGlobalIdsMap& FWwiseDataStructureScopeLock::GetBusses() const +{ + static const auto Empty = WwiseBusGlobalIdsMap(); + + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return Empty; + + return PlatformData->Busses; +} + +FWwiseRefBus FWwiseDataStructureScopeLock::GetBus(const FWwiseObjectInfo& InInfo) const +{ + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return {}; + + FWwiseRefBus Result; + PlatformData->GetRef(Result, GetCurrentLanguage(), InInfo); + return Result; +} + +const WwiseCustomPluginGlobalIdsMap& FWwiseDataStructureScopeLock::GetCustomPlugins() const +{ + static const auto Empty = WwiseCustomPluginGlobalIdsMap(); + + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return Empty; + + return PlatformData->CustomPlugins; +} + +FWwiseRefCustomPlugin FWwiseDataStructureScopeLock::GetCustomPlugin(const FWwiseObjectInfo& InInfo) const +{ + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return {}; + + FWwiseRefCustomPlugin Result; + PlatformData->GetRef(Result, GetCurrentLanguage(), InInfo); + return Result; +} + +const WwiseDialogueArgumentGlobalIdsMap& FWwiseDataStructureScopeLock::GetDialogueArguments() const +{ + static const auto Empty = WwiseDialogueArgumentGlobalIdsMap(); + + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return Empty; + + return PlatformData->DialogueArguments; +} + +FWwiseRefDialogueArgument FWwiseDataStructureScopeLock::GetDialogueArgument(const FWwiseObjectInfo& InInfo) const +{ + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return {}; + + FWwiseRefDialogueArgument Result; + PlatformData->GetRef(Result, GetCurrentLanguage(), InInfo); + return Result; +} + +const WwiseDialogueEventGlobalIdsMap& FWwiseDataStructureScopeLock::GetDialogueEvents() const +{ + static const auto Empty = WwiseDialogueEventGlobalIdsMap(); + + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return Empty; + + return PlatformData->DialogueEvents; +} + +FWwiseRefDialogueEvent FWwiseDataStructureScopeLock::GetDialogueEvent(const FWwiseObjectInfo& InInfo) const +{ + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return {}; + + FWwiseRefDialogueEvent Result; + PlatformData->GetRef(Result, GetCurrentLanguage(), InInfo); + return Result; +} + +const WwiseEventGlobalIdsMap& FWwiseDataStructureScopeLock::GetEvents() const +{ + static const auto Empty = WwiseEventGlobalIdsMap(); + + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return Empty; + + return PlatformData->Events; +} + +TSet FWwiseDataStructureScopeLock::GetEvent(const FWwiseEventInfo& InInfo) const +{ + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return {}; + + TSet Result; + PlatformData->GetRef(Result, GetCurrentLanguage(), InInfo); + return Result; +} + +const WwiseExternalSourceGlobalIdsMap& FWwiseDataStructureScopeLock::GetExternalSources() const +{ + static const auto Empty = WwiseExternalSourceGlobalIdsMap(); + + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return Empty; + + return PlatformData->ExternalSources; +} + +FWwiseRefExternalSource FWwiseDataStructureScopeLock::GetExternalSource(const FWwiseObjectInfo& InInfo) const +{ + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return {}; + + FWwiseRefExternalSource Result; + PlatformData->GetRef(Result, GetCurrentLanguage(), InInfo); + return Result; +} + +const WwiseGameParameterGlobalIdsMap& FWwiseDataStructureScopeLock::GetGameParameters() const +{ + static const auto Empty = WwiseGameParameterGlobalIdsMap(); + + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return Empty; + + return PlatformData->GameParameters; +} + +FWwiseRefGameParameter FWwiseDataStructureScopeLock::GetGameParameter(const FWwiseObjectInfo& InInfo) const +{ + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return {}; + + FWwiseRefGameParameter Result; + PlatformData->GetRef(Result, GetCurrentLanguage(), InInfo); + return Result; +} + +const WwiseMediaGlobalIdsMap& FWwiseDataStructureScopeLock::GetMediaFiles() const +{ + static const auto Empty = WwiseMediaGlobalIdsMap(); + + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return Empty; + + return PlatformData->MediaFiles; +} + +FWwiseRefMedia FWwiseDataStructureScopeLock::GetMediaFile(const FWwiseObjectInfo& InInfo) const +{ + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return {}; + + FWwiseRefMedia Result; + PlatformData->GetRef(Result, GetCurrentLanguage(), InInfo); + return Result; +} + +const WwisePluginLibGlobalIdsMap& FWwiseDataStructureScopeLock::GetPluginLibs() const +{ + static const auto Empty = WwisePluginLibGlobalIdsMap(); + + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return Empty; + + return PlatformData->PluginLibs; +} + +FWwiseRefPluginLib FWwiseDataStructureScopeLock::GetPluginLib(const FWwiseObjectInfo& InInfo) const +{ + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return {}; + + FWwiseRefPluginLib Result; + PlatformData->GetRef(Result, GetCurrentLanguage(), InInfo); + return Result; +} + +const WwisePluginShareSetGlobalIdsMap& FWwiseDataStructureScopeLock::GetPluginShareSets() const +{ + static const auto Empty = WwisePluginShareSetGlobalIdsMap(); + + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return Empty; + + return PlatformData->PluginShareSets; +} + +FWwiseRefPluginShareSet FWwiseDataStructureScopeLock::GetPluginShareSet(const FWwiseObjectInfo& InInfo) const +{ + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return {}; + + FWwiseRefPluginShareSet Result; + PlatformData->GetRef(Result, GetCurrentLanguage(), InInfo); + return Result; +} + +const WwiseSoundBankGlobalIdsMap& FWwiseDataStructureScopeLock::GetSoundBanks() const +{ + static const auto Empty = WwiseSoundBankGlobalIdsMap(); + + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return Empty; + + return PlatformData->SoundBanks; +} + +FWwiseRefSoundBank FWwiseDataStructureScopeLock::GetSoundBank(const FWwiseObjectInfo& InInfo) const +{ + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return {}; + + FWwiseRefSoundBank Result; + PlatformData->GetRef(Result, GetCurrentLanguage(), InInfo); + return Result; +} + +const WwiseStateGlobalIdsMap& FWwiseDataStructureScopeLock::GetStates() const +{ + static const auto Empty = WwiseStateGlobalIdsMap(); + + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return Empty; + + return PlatformData->States; +} + +FWwiseRefState FWwiseDataStructureScopeLock::GetState(const FWwiseGroupValueInfo& InInfo) const +{ + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return {}; + + FWwiseRefState Result; + PlatformData->GetRef(Result, GetCurrentLanguage(), InInfo); + return Result; +} + +const WwiseStateGroupGlobalIdsMap& FWwiseDataStructureScopeLock::GetStateGroups() const +{ + static const auto Empty = WwiseStateGroupGlobalIdsMap(); + + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return Empty; + + return PlatformData->StateGroups; +} + +FWwiseRefStateGroup FWwiseDataStructureScopeLock::GetStateGroup(const FWwiseObjectInfo& InInfo) const +{ + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return {}; + + FWwiseRefStateGroup Result; + PlatformData->GetRef(Result, GetCurrentLanguage(), InInfo); + return Result; +} + +const WwiseSwitchGlobalIdsMap& FWwiseDataStructureScopeLock::GetSwitches() const +{ + static const auto Empty = WwiseSwitchGlobalIdsMap(); + + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return Empty; + + return PlatformData->Switches; +} + +FWwiseRefSwitch FWwiseDataStructureScopeLock::GetSwitch(const FWwiseGroupValueInfo& InInfo) const +{ + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return {}; + + FWwiseRefSwitch Result; + PlatformData->GetRef(Result, GetCurrentLanguage(), InInfo); + return Result; +} + +const WwiseSwitchGroupGlobalIdsMap& FWwiseDataStructureScopeLock::GetSwitchGroups() const +{ + static const auto Empty = WwiseSwitchGroupGlobalIdsMap(); + + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return Empty; + + return PlatformData->SwitchGroups; +} + +FWwiseRefSwitchGroup FWwiseDataStructureScopeLock::GetSwitchGroup(const FWwiseObjectInfo& InInfo) const +{ + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return {}; + + FWwiseRefSwitchGroup Result; + PlatformData->GetRef(Result, GetCurrentLanguage(), InInfo); + return Result; +} + +const WwiseTriggerGlobalIdsMap& FWwiseDataStructureScopeLock::GetTriggers() const +{ + static const auto Empty = WwiseTriggerGlobalIdsMap(); + + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return Empty; + + return PlatformData->Triggers; +} + +FWwiseRefTrigger FWwiseDataStructureScopeLock::GetTrigger(const FWwiseObjectInfo& InInfo) const +{ + const auto* PlatformData = GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) return {}; + + FWwiseRefTrigger Result; + PlatformData->GetRef(Result, GetCurrentLanguage(), InInfo); + return Result; +} + +const FWwisePlatformDataStructure* FWwiseDataStructureScopeLock::GetCurrentPlatformData() const +{ + if (DisableDefaultPlatforms()) + { + UE_LOG(LogWwiseProjectDatabase, VeryVerbose, TEXT("Trying to access current platform data when none is loaded by design (cooking)")); + return nullptr; + } + + const auto& Platform = GetCurrentPlatform(); + const auto* PlatformData = DataStructure.Platforms.Find(Platform); + if (UNLIKELY(!PlatformData)) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Current platform %s not found"), *Platform.GetPlatformName().ToString()); + return nullptr; + } + return PlatformData; +} + +const TSet& FWwiseDataStructureScopeLock::GetLanguages() const +{ + return DataStructure.RootData.Languages; +} + +const TSet& FWwiseDataStructureScopeLock::GetPlatforms() const +{ + return DataStructure.RootData.Platforms; +} + +FWwiseRefPlatform FWwiseDataStructureScopeLock::GetPlatform(const FWwiseSharedPlatformId& InPlatformId) const +{ + if (const auto* Platform = DataStructure.RootData.PlatformGuids.Find(InPlatformId.GetPlatformGuid())) + { + return *Platform; + } + return {}; +} + + + +FWwiseDataStructureWriteScopeLock::FWwiseDataStructureWriteScopeLock(FWwiseProjectDatabase& InProjectDatabase) : + FRWScopeLock(InProjectDatabase.GetLockedDataStructure()->Lock, SLT_Write), + DataStructure(*InProjectDatabase.GetLockedDataStructure()) +{ +} + +#if PLATFORM_LINUX +const FGuid FWwiseProjectDatabase::BasePlatformGuid(0xbd0bdf13, 0x3125454f, 0x8bfd3195, 0x37169f81); +#elif PLATFORM_MAC +const FGuid FWwiseProjectDatabase::BasePlatformGuid(0x9c6217d5, 0xdd114795, 0x87c16ce0, 0x2853c540); +#elif PLATFORM_WINDOWS +const FGuid FWwiseProjectDatabase::BasePlatformGuid(0x6e0cb257, 0xc6c84c5c, 0x83662740, 0xdfc441eb); +#else +static_assert(false); +#endif + +FWwiseSharedLanguageId FWwiseProjectDatabase::GetCurrentLanguage() const +{ + auto* ResourceLoader = GetResourceLoader(); + if (UNLIKELY(!ResourceLoader)) + { + return {}; + } + + const auto CurrentLanguage = ResourceLoader->GetCurrentLanguage(); + return FWwiseSharedLanguageId(CurrentLanguage.GetLanguageId(), CurrentLanguage.GetLanguageName(), CurrentLanguage.LanguageRequirement); +} + +FWwiseSharedPlatformId FWwiseProjectDatabase::GetCurrentPlatform() const +{ + auto* ResourceLoader = GetResourceLoader(); + if (UNLIKELY(!ResourceLoader)) + { + return {}; + } + + return ResourceLoader->GetCurrentPlatform(); +} + +bool FWwiseProjectDatabase::DisableDefaultPlatforms() const +{ + return UNLIKELY(IWwiseProjectDatabaseModule::IsInACookingCommandlet()) && (Get() == this); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseProjectDatabaseImpl.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseProjectDatabaseImpl.cpp new file mode 100644 index 0000000..5d14395 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseProjectDatabaseImpl.cpp @@ -0,0 +1,157 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseProjectDatabaseImpl.h" + +#include "Wwise/Metadata/WwiseMetadataPlatformInfo.h" +#include "Wwise/WwiseResourceLoader.h" +#include "Wwise/WwiseProjectDatabaseDelegates.h" + +#include "Async/Async.h" +#include "Misc/ScopedSlowTask.h" + +#define LOCTEXT_NAMESPACE "WwiseProjectDatabase" + +FWwiseProjectDatabaseImpl::FWwiseProjectDatabaseImpl() : + ResourceLoaderOverride(nullptr), + LockedDataStructure(new FWwiseDataStructure()) +{ +} + +FWwiseProjectDatabaseImpl::~FWwiseProjectDatabaseImpl() +{ +} + +void FWwiseProjectDatabaseImpl::UpdateDataStructure(const FDirectoryPath* InUpdateGeneratedSoundBanksPath, const FGuid* InBasePlatformGuid) +{ + SCOPED_WWISEPROJECTDATABASE_EVENT_2(TEXT("FWwiseProjectDatabaseImpl::UpdateDataStructure")); + FWwiseSharedPlatformId Platform; + FDirectoryPath SourcePath; + { + auto* ResourceLoader = GetResourceLoader(); + if (UNLIKELY(!ResourceLoader)) + { + return; + } + if (InUpdateGeneratedSoundBanksPath) + { + ResourceLoader->SetUnrealGeneratedSoundBanksPath(*InUpdateGeneratedSoundBanksPath); + } + + Platform = ResourceLoader->GetCurrentPlatform(); + SourcePath = ResourceLoader->GetUnrealGeneratedSoundBanksPath(); + } + + { + FWriteScopeLock WLock(LockedDataStructure->Lock); + auto& DataStructure = LockedDataStructure.Get(); + + if (DisableDefaultPlatforms()) + { + UE_LOG(LogWwiseProjectDatabase, Log, TEXT("UpdateDataStructure: Retrieving root data structure in (%s)"), *SourcePath.Path); + FScopedSlowTask SlowTask(0, LOCTEXT("WwiseProjectDatabaseUpdate", "Retrieving Wwise data structure root...")); + + { + FWwiseDataStructure UpdatedDataStructure(SourcePath, nullptr, nullptr); + DataStructure = MoveTemp(UpdatedDataStructure); + } + } + else + { + UE_LOG(LogWwiseProjectDatabase, Log, TEXT("UpdateDataStructure: Retrieving data structure for %s (Base: %s) in (%s)"), + *Platform.GetPlatformName().ToString(), InBasePlatformGuid ? *InBasePlatformGuid->ToString() : TEXT("null"), *SourcePath.Path); + FScopedSlowTask SlowTask(0, FText::Format( + LOCTEXT("WwiseProjectDatabaseUpdate", "Retrieving Wwise data structure for platform {0}..."), + FText::FromName(Platform.GetPlatformName()))); + + { + FWwiseDataStructure UpdatedDataStructure(SourcePath, &Platform.GetPlatformName(), InBasePlatformGuid); + DataStructure = MoveTemp(UpdatedDataStructure); + + // Update platform according to data found if different + FWwiseSharedPlatformId FoundSimilarPlatform = Platform; + for (const auto& LoadedPlatform : DataStructure.Platforms) + { + FoundSimilarPlatform = LoadedPlatform.Key; + if (FoundSimilarPlatform == Platform) + { + break; + } + } + + //Update SharedPlatformId with parsed root paths + if (DataStructure.Platforms.Contains(FoundSimilarPlatform) ) + { + const FWwisePlatformDataStructure& PlatformEntry = DataStructure.Platforms.FindRef(FoundSimilarPlatform); + FoundSimilarPlatform.Platform->ExternalSourceRootPath = PlatformEntry.PlatformRef.GetPlatformInfo()->RootPaths.ExternalSourcesOutputRoot; + } + //Update the resource loader current platform as internal data may have changed + auto* ResourceLoader = GetResourceLoader(); + if (UNLIKELY(!ResourceLoader)) + { + return; + } + + ResourceLoader->SetPlatform(FoundSimilarPlatform); + } + + if (UNLIKELY(DataStructure.Platforms.Num() == 0)) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("UpdateDataStructure: Could not find suitable platform for %s (Base: %s) in (%s)"), + *Platform.GetPlatformName().ToString(), InBasePlatformGuid ? *InBasePlatformGuid->ToString() : TEXT("null"), *SourcePath.Path); + return; + } + } + bIsDatabaseParsed = true; + UE_LOG(LogWwiseProjectDatabase, Log, TEXT("UpdateDataStructure: Done.")); + } + if (Get() == this) // Only broadcast database updates on main project. + { + FWwiseProjectDatabaseDelegates::Get().GetOnDatabaseUpdateCompletedDelegate().Broadcast(); + } +} + +void FWwiseProjectDatabaseImpl::PrepareProjectDatabaseForPlatform(FWwiseResourceLoader*&& InResourceLoader) +{ + ResourceLoaderOverride.Reset(InResourceLoader); +} + +FWwiseResourceLoader* FWwiseProjectDatabaseImpl::GetResourceLoader() +{ + if (ResourceLoaderOverride.IsValid()) + { + return ResourceLoaderOverride.Get(); + } + else + { + return FWwiseResourceLoader::Get(); + } +} + +const FWwiseResourceLoader* FWwiseProjectDatabaseImpl::GetResourceLoader() const +{ + if (ResourceLoaderOverride.IsValid()) + { + return ResourceLoaderOverride.Get(); + } + else + { + return FWwiseResourceLoader::Get(); + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseProjectDatabaseModuleImpl.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseProjectDatabaseModuleImpl.cpp new file mode 100644 index 0000000..ba8c57b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Private/Wwise/WwiseProjectDatabaseModuleImpl.cpp @@ -0,0 +1,62 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseProjectDatabaseModuleImpl.h" +#include "Wwise/WwiseProjectDatabaseImpl.h" +#include "Wwise/Stats/ProjectDatabase.h" + +IMPLEMENT_MODULE(FWwiseProjectDatabaseModule, WwiseProjectDatabase) + +FWwiseProjectDatabase* FWwiseProjectDatabaseModule::GetProjectDatabase() +{ + Lock.ReadLock(); + if (LIKELY(ProjectDatabase)) + { + Lock.ReadUnlock(); + } + else + { + Lock.ReadUnlock(); + Lock.WriteLock(); + if (LIKELY(!ProjectDatabase)) + { + UE_LOG(LogWwiseProjectDatabase, Display, TEXT("Initializing default Project Database.")); + ProjectDatabase.Reset(InstantiateProjectDatabase()); + } + Lock.WriteUnlock(); + } + return ProjectDatabase.Get(); +} + +FWwiseProjectDatabase* FWwiseProjectDatabaseModule::InstantiateProjectDatabase() +{ + SCOPED_WWISEPROJECTDATABASE_EVENT(TEXT("InstantiateProjectDatabase")); + return new FWwiseProjectDatabaseImpl; +} + +void FWwiseProjectDatabaseModule::ShutdownModule() +{ + SCOPED_WWISEPROJECTDATABASE_EVENT(TEXT("ShutdownModule")); + Lock.WriteLock(); + if (ProjectDatabase.IsValid()) + { + UE_LOG(LogWwiseProjectDatabase, Display, TEXT("Shutting down default Project Database.")); + ProjectDatabase.Reset(); + } + Lock.WriteUnlock(); + IWwiseProjectDatabaseModule::ShutdownModule(); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataAcousticTexture.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataAcousticTexture.h new file mode 100644 index 0000000..2794c84 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataAcousticTexture.h @@ -0,0 +1,35 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataBasicReference.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataAcousticTexture : public FWwiseMetadataBasicReference +{ + float AbsorptionLow; + float AbsorptionMidLow; + float AbsorptionMidHigh; + float AbsorptionHigh; + + FWwiseMetadataAcousticTexture(FWwiseMetadataLoader& Loader); + +private: + static const TMap FloatProperties; + static const TMap FillFloatProperties(); +}; + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataActionEntries.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataActionEntries.h new file mode 100644 index 0000000..3412910 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataActionEntries.h @@ -0,0 +1,40 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataGroupValueReference.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataActionPostEventEntry : public FWwiseMetadataBasicReference +{ + FWwiseMetadataActionPostEventEntry(FWwiseMetadataLoader& Loader); +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataActionSetStateEntry : public FWwiseMetadataGroupValueReference +{ + FWwiseMetadataActionSetStateEntry(FWwiseMetadataLoader& Loader); +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataActionSetSwitchEntry : public FWwiseMetadataGroupValueReference +{ + FWwiseMetadataActionSetSwitchEntry(FWwiseMetadataLoader& Loader); +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataActionTriggerEntry : public FWwiseMetadataBasicReference +{ + FWwiseMetadataActionTriggerEntry(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataBasicReference.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataBasicReference.h new file mode 100644 index 0000000..d0a1894 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataBasicReference.h @@ -0,0 +1,56 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataLoadable.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataBasicReference : public FWwiseMetadataLoadable +{ + uint32 Id; + FName Name; + FName ObjectPath; + FGuid GUID; + + FWwiseMetadataBasicReference(); + FWwiseMetadataBasicReference(uint32 InId, FName&& InName, FName&& InObjectPath, FGuid&& InGUID) : + Id(MoveTemp(InId)), + Name(MoveTemp(InName)), + ObjectPath(MoveTemp(InObjectPath)), + GUID(MoveTemp(InGUID)) + {} + FWwiseMetadataBasicReference(uint32 InId, const FName& InName, const FName& InObjectPath, const FGuid& InGUID) : + Id(InId), + Name(InName), + ObjectPath(InObjectPath), + GUID(InGUID) + {} + FWwiseMetadataBasicReference(FWwiseMetadataLoader& Loader); +}; + +inline uint32 GetTypeHash(const FWwiseMetadataBasicReference& Ref) +{ + return GetTypeHash(Ref.Id); +} +inline bool operator==(const FWwiseMetadataBasicReference& Lhs, const FWwiseMetadataBasicReference& Rhs) +{ + return Lhs.Id == Rhs.Id; +} +inline bool operator<(const FWwiseMetadataBasicReference& Lhs, const FWwiseMetadataBasicReference& Rhs) +{ + return Lhs.Id < Rhs.Id; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataBus.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataBus.h new file mode 100644 index 0000000..d2b5094 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataBus.h @@ -0,0 +1,33 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataBasicReference.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataBusReference : public FWwiseMetadataBasicReference +{ + FWwiseMetadataBusReference(FWwiseMetadataLoader& Loader); +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataBus : public FWwiseMetadataBusReference +{ + FWwiseMetadataPluginReferenceGroup* PluginRefs; + TArray AuxBusRefs; + + FWwiseMetadataBus(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataCollections.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataCollections.h new file mode 100644 index 0000000..993d561 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataCollections.h @@ -0,0 +1,29 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataForwardDeclarations.h" + +#include "CoreMinimal.h" + +using WwiseMetadataSharedRootFilePtr = TSharedPtr; +using WwiseMetadataSharedRootFileConstPtr = TSharedPtr; +using WwiseMetadataFileMap = TMap; + +using WwiseMetadataStateWithGroup = TPair; +using WwiseMetadataSwitchWithGroup = TPair; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataDialogue.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataDialogue.h new file mode 100644 index 0000000..e5b5d06 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataDialogue.h @@ -0,0 +1,37 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataBasicReference.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataDialogueEventReference : public FWwiseMetadataBasicReference +{ + FWwiseMetadataDialogueEventReference(FWwiseMetadataLoader& Loader); +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataDialogueArgument : public FWwiseMetadataBasicReference +{ + FWwiseMetadataDialogueArgument(FWwiseMetadataLoader& Loader); +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataDialogueEvent : public FWwiseMetadataDialogueEventReference +{ + TArray Arguments; + + FWwiseMetadataDialogueEvent(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataEvent.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataEvent.h new file mode 100644 index 0000000..f730b6f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataEvent.h @@ -0,0 +1,60 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataActionEntries.h" +#include "Wwise/Metadata/WwiseMetadataBus.h" +#include "Wwise/Metadata/WwiseMetadataSwitchContainer.h" + +enum class EWwiseMetadataEventDurationType : uint32 +{ + OneShot = 0, + Unknown = 1, + Infinite = 2, + Mixed = 3 +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataEventReference : public FWwiseMetadataBasicReference +{ + uint32 MaxAttenuation; + EWwiseMetadataEventDurationType DurationType; + float DurationMin; + float DurationMax; + + FWwiseMetadataEventReference(FWwiseMetadataLoader& Loader); + +private: + static EWwiseMetadataEventDurationType DurationTypeFromString(const FName& TypeString); +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataEvent : public FWwiseMetadataEventReference +{ + TArray MediaRefs; + TArray ExternalSourceRefs; + FWwiseMetadataPluginReferenceGroup* PluginRefs; + TArray AuxBusRefs; + TArray SwitchContainers; + TArray ActionPostEvent; + TArray ActionSetState; + TArray ActionSetSwitch; + TArray ActionTrigger; + + FWwiseMetadataEvent(FWwiseMetadataLoader& Loader); + + bool IsMandatory() const; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataExternalSource.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataExternalSource.h new file mode 100644 index 0000000..b703453 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataExternalSource.h @@ -0,0 +1,49 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataLoadable.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataExternalSourceReference : public FWwiseMetadataLoadable +{ + uint32 Cookie; + + FWwiseMetadataExternalSourceReference(FWwiseMetadataLoader& Loader); +}; + +inline uint32 GetTypeHash(const FWwiseMetadataExternalSourceReference& Ref) +{ + return GetTypeHash(Ref.Cookie); +} +inline bool operator==(const FWwiseMetadataExternalSourceReference& Lhs, const FWwiseMetadataExternalSourceReference& Rhs) +{ + return Lhs.Cookie == Rhs.Cookie; +} +inline bool operator<(const FWwiseMetadataExternalSourceReference& Lhs, const FWwiseMetadataExternalSourceReference& Rhs) +{ + return Lhs.Cookie < Rhs.Cookie; +} + +struct WWISEPROJECTDATABASE_API FWwiseMetadataExternalSource : public FWwiseMetadataExternalSourceReference +{ + FName Name; + FName ObjectPath; + FGuid GUID; + + FWwiseMetadataExternalSource(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataForwardDeclarations.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataForwardDeclarations.h new file mode 100644 index 0000000..5f739b4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataForwardDeclarations.h @@ -0,0 +1,74 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +struct FWwiseMetadataLoadable; +struct FWwiseMetadataAcousticTexture; +struct FWwiseMetadataActionPostEventEntry; +struct FWwiseMetadataActionSetStateEntry; +struct FWwiseMetadataActionSetSwitchEntry; +struct FWwiseMetadataActionTriggerEntry; +struct FWwiseMetadataBasicReference; +struct FWwiseMetadataBusReference; +struct FWwiseMetadataBus; +struct FWwiseMetadataDialogueEventReference; +struct FWwiseMetadataDialogueArgument; +struct FWwiseMetadataDialogueEvent; +struct FWwiseMetadataEventReference; +struct FWwiseMetadataEvent; +struct FWwiseMetadataExternalSourceReference; +struct FWwiseMetadataExternalSource; +struct FWwiseMetadataGameParameterReference; +struct FWwiseMetadataGameParameter; +struct FWwiseMetadataLanguageAttributes; +struct FWwiseMetadataLanguage; +struct FWwiseMetadataLoadable; +struct FWwiseMetadataLoader; +struct FWwiseMetadataMediaReference; +struct FWwiseMetadataMediaAttributes; +struct FWwiseMetadataMedia; +struct FWwiseMetadataPlatformAttributes; +struct FWwiseMetadataPlatformReference; +struct FWwiseMetadataPlatform; +struct FWwiseMetadataRootPaths; +struct FWwiseMetadataSettings; +struct FWwiseMetadataPlatformInfo; +struct FWwiseMetadataPluginReference; +struct FWwiseMetadataPluginAttributes; +struct FWwiseMetadataPlugin; +struct FWwiseMetadataPluginReferenceGroup; +struct FWwiseMetadataPluginGroup; +struct FWwiseMetadataPluginInfoAttributes; +struct FWwiseMetadataPluginInfo; +struct FWwiseMetadataPluginLibAttributes; +struct FWwiseMetadataPluginLib; +struct FWwiseMetadataProjectInfo; +struct FWwiseMetadataRootFile; +struct FWwiseMetadataSoundBankReference; +struct FWwiseMetadataSoundBankAttributes; +struct FWwiseMetadataSoundBank; +struct FWwiseMetadataSoundBanksInfoAttributes; +struct FWwiseMetadataSoundBanksInfo; +struct FWwiseMetadataState; +struct FWwiseMetadataStateGroup; +struct FWwiseMetadataSwitch; +struct FWwiseMetadataSwitchContainer; +struct FWwiseMetadataSwitchGroup; +struct FWwiseMetadataSwitchValueAttributes; +struct FWwiseMetadataSwitchValue; +struct FWwiseMetadataTrigger; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataGameParameter.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataGameParameter.h new file mode 100644 index 0000000..726a3d8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataGameParameter.h @@ -0,0 +1,32 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataBasicReference.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataGameParameterReference : public FWwiseMetadataLoadable +{ + FWwiseMetadataGameParameterReference(FWwiseMetadataLoader& Loader); + + uint32 Id; +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataGameParameter : public FWwiseMetadataBasicReference +{ + FWwiseMetadataGameParameter(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataGroupValueReference.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataGroupValueReference.h new file mode 100644 index 0000000..b5be902 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataGroupValueReference.h @@ -0,0 +1,42 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataBasicReference.h" + + +struct WWISEPROJECTDATABASE_API FWwiseMetadataGroupValueReference : public FWwiseMetadataBasicReference +{ + uint32 GroupId; + + FWwiseMetadataGroupValueReference(); + FWwiseMetadataGroupValueReference(FWwiseMetadataLoader& Loader); +}; + +inline uint32 GetTypeHash(const FWwiseMetadataGroupValueReference& Ref) +{ + return HashCombine(GetTypeHash((const FWwiseMetadataBasicReference&)Ref), GetTypeHash(Ref.Id)); +} +inline bool operator==(const FWwiseMetadataGroupValueReference& Lhs, const FWwiseMetadataGroupValueReference& Rhs) +{ + return (const FWwiseMetadataBasicReference&)Lhs == Rhs && Lhs.GroupId == Rhs.GroupId; +} +inline bool operator<(const FWwiseMetadataGroupValueReference& Lhs, const FWwiseMetadataGroupValueReference& Rhs) +{ + return (const FWwiseMetadataBasicReference&)Lhs < Rhs && Lhs.GroupId < Rhs.GroupId; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataLanguage.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataLanguage.h new file mode 100644 index 0000000..addd8ac --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataLanguage.h @@ -0,0 +1,36 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataLoadable.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataLanguageAttributes : public FWwiseMetadataLoadable +{ + FName Name; + uint32 Id; + FGuid GUID; + bool bDefault; + bool bUseAsStandIn; + + FWwiseMetadataLanguageAttributes(FWwiseMetadataLoader& Loader); +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataLanguage : public FWwiseMetadataLanguageAttributes +{ + FWwiseMetadataLanguage(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataLoadable.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataLoadable.h new file mode 100644 index 0000000..69a5da4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataLoadable.h @@ -0,0 +1,49 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataForwardDeclarations.h" + +#include "CoreMinimal.h" + +class FJsonObject; +class FJsonValue; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataLoadable +{ +protected: + TSet RequestedValues; + size_t LoadedSize; + + inline FWwiseMetadataLoadable() : + RequestedValues(), + LoadedSize(0) + {} + + inline ~FWwiseMetadataLoadable() + { + UnloadLoadedSize(); + } + +public: + void AddRequestedValue(const FString& Type, const FString& Value); + void CheckRequestedValues(TSharedRef& JsonObject); + void IncLoadedSize(size_t Size); + void DecLoadedSize(size_t Size); + void UnloadLoadedSize(); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataMedia.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataMedia.h new file mode 100644 index 0000000..01cbc11 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataMedia.h @@ -0,0 +1,74 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataLoadable.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataMediaReference : public FWwiseMetadataLoadable +{ + uint32 Id; + + FWwiseMetadataMediaReference(FWwiseMetadataLoader& Loader); +}; + +inline uint32 GetTypeHash(const FWwiseMetadataMediaReference& Media) +{ + return GetTypeHash(Media.Id); +} +inline bool operator ==(const FWwiseMetadataMediaReference& Lhs, const FWwiseMetadataMediaReference& Rhs) +{ + return Lhs.Id == Rhs.Id; +} +inline bool operator <(const FWwiseMetadataMediaReference& Lhs, const FWwiseMetadataMediaReference& Rhs) +{ + return Lhs.Id < Rhs.Id; +} + +enum class EWwiseMetadataMediaLocation : uint32 +{ + Memory, + Loose, + OtherBank, + + Unknown = (uint32)-1 +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataMediaAttributes : public FWwiseMetadataMediaReference +{ + FName Language; + bool bStreaming; + EWwiseMetadataMediaLocation Location; + bool bUsingReferenceLanguage; + uint32 Align; + bool bDeviceMemory; + + FWwiseMetadataMediaAttributes(FWwiseMetadataLoader& Loader); + +private: + static EWwiseMetadataMediaLocation LocationFromString(const FName& LocationString); +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataMedia : public FWwiseMetadataMediaAttributes +{ + FName ShortName; + FName Path; + FName CachePath; + uint32 PrefetchSize; + + FWwiseMetadataMedia(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataPlatform.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataPlatform.h new file mode 100644 index 0000000..2bf4736 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataPlatform.h @@ -0,0 +1,47 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataLoadable.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataPlatformAttributes : public FWwiseMetadataLoadable +{ + FName Name; + FName BasePlatform; + FName Generator; + + FWwiseMetadataPlatformAttributes(); + FWwiseMetadataPlatformAttributes(FWwiseMetadataLoader& Loader); +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataPlatformReference : public FWwiseMetadataLoadable +{ + FName Name; + FGuid GUID; + FName BasePlatform; + FGuid BasePlatformGUID; + FName Path; + + FWwiseMetadataPlatformReference(FWwiseMetadataLoader& Loader); +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataPlatform : public FWwiseMetadataPlatformAttributes +{ + FWwiseMetadataPlatform(); + FWwiseMetadataPlatform(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataPlatformInfo.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataPlatformInfo.h new file mode 100644 index 0000000..969524a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataPlatformInfo.h @@ -0,0 +1,33 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataPlatform.h" +#include "Wwise/Metadata/WwiseMetadataRootPaths.h" +#include "Wwise/Metadata/WwiseMetadataSettings.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataPlatformInfo : public FWwiseMetadataLoadable +{ + FWwiseMetadataPlatform Platform; + FWwiseMetadataRootPaths RootPaths; + uint32 DefaultAlign; + FWwiseMetadataSettings Settings; + FGuid FileHash; + + FWwiseMetadataPlatformInfo(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataPlugin.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataPlugin.h new file mode 100644 index 0000000..1fe1e2f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataPlugin.h @@ -0,0 +1,57 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataBasicReference.h" +#include "Wwise/Metadata/WwiseMetadataMedia.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataPluginReference : public FWwiseMetadataLoadable +{ + uint32 Id; + + FWwiseMetadataPluginReference(FWwiseMetadataLoader& Loader); +}; + +inline uint32 GetTypeHash(const FWwiseMetadataPluginReference& Plugin) +{ + return GetTypeHash(Plugin.Id); +} +inline bool operator ==(const FWwiseMetadataPluginReference& Lhs, const FWwiseMetadataPluginReference& Rhs) +{ + return Lhs.Id == Rhs.Id; +} +inline bool operator <(const FWwiseMetadataPluginReference& Lhs, const FWwiseMetadataPluginReference& Rhs) +{ + return Lhs.Id < Rhs.Id; +} + +struct WWISEPROJECTDATABASE_API FWwiseMetadataPluginAttributes : public FWwiseMetadataBasicReference +{ + FName LibName; + uint32 LibId; + + FWwiseMetadataPluginAttributes(FWwiseMetadataLoader& Loader); +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataPlugin : public FWwiseMetadataPluginAttributes +{ + TArray MediaRefs; + FWwiseMetadataPluginReferenceGroup* PluginRefs; + + FWwiseMetadataPlugin(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataPluginGroup.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataPluginGroup.h new file mode 100644 index 0000000..5541b8a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataPluginGroup.h @@ -0,0 +1,39 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataLoadable.h" +#include "Wwise/Metadata/WwiseMetadataPlugin.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataPluginReferenceGroup : public FWwiseMetadataLoadable +{ + TArray Custom; + TArray ShareSets; + TArray AudioDevices; + + FWwiseMetadataPluginReferenceGroup(FWwiseMetadataLoader& Loader); +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataPluginGroup : public FWwiseMetadataLoadable +{ + TArray Custom; + TArray ShareSets; + TArray AudioDevices; + + FWwiseMetadataPluginGroup(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataPluginInfo.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataPluginInfo.h new file mode 100644 index 0000000..685ffcc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataPluginInfo.h @@ -0,0 +1,37 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataLoadable.h" +#include "Wwise/Metadata/WwiseMetadataPluginLib.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataPluginInfoAttributes : public FWwiseMetadataLoadable +{ + FName Platform; + FName BasePlatform; + + FWwiseMetadataPluginInfoAttributes(FWwiseMetadataLoader& Loader); +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataPluginInfo : public FWwiseMetadataPluginInfoAttributes +{ + TArray PluginLibs; + FGuid FileHash; + + FWwiseMetadataPluginInfo(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataPluginLib.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataPluginLib.h new file mode 100644 index 0000000..6a53476 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataPluginLib.h @@ -0,0 +1,48 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataLoadable.h" + +enum class EWwiseMetadataPluginLibType : uint32 +{ + Source, + Effect, + AudioDevice, + Metadata, + Unknown = (uint32)-1 +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataPluginLibAttributes : public FWwiseMetadataLoadable +{ + FName LibName; + uint32 LibId; + EWwiseMetadataPluginLibType Type; + FName DLL; + FName StaticLib; + + FWwiseMetadataPluginLibAttributes(FWwiseMetadataLoader& Loader); + +private: + static EWwiseMetadataPluginLibType TypeFromString(const FName& TypeString); +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataPluginLib : public FWwiseMetadataPluginLibAttributes +{ + FWwiseMetadataPluginLib(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataProject.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataProject.h new file mode 100644 index 0000000..7dbcbd3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataProject.h @@ -0,0 +1,31 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataLoadable.h" + +struct FWwiseMetadataLoader; +struct WWISEPROJECTDATABASE_API FWwiseMetadataProject : public FWwiseMetadataLoadable +{ + FName Name; + FGuid GUID; + FName Generator; + + FWwiseMetadataProject(); + FWwiseMetadataProject(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataProjectInfo.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataProjectInfo.h new file mode 100644 index 0000000..b3f449a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataProjectInfo.h @@ -0,0 +1,33 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataLanguage.h" +#include "Wwise/Metadata/WwiseMetadataPlatform.h" +#include "Wwise/Metadata/WwiseMetadataProject.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataProjectInfo : public FWwiseMetadataLoadable +{ + FWwiseMetadataProject Project; + FName CacheRoot; + TArray Platforms; + TArray Languages; + FGuid FileHash; + + FWwiseMetadataProjectInfo(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataRootFile.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataRootFile.h new file mode 100644 index 0000000..0e46312 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataRootFile.h @@ -0,0 +1,37 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataCollections.h" +#include "Wwise/Metadata/WwiseMetadataLoadable.h" + + +struct WWISEPROJECTDATABASE_API FWwiseMetadataRootFile : public FWwiseMetadataLoadable +{ + FWwiseMetadataPlatformInfo* PlatformInfo; + FWwiseMetadataPluginInfo* PluginInfo; + FWwiseMetadataProjectInfo* ProjectInfo; + FWwiseMetadataSoundBanksInfo* SoundBanksInfo; + + FWwiseMetadataRootFile(FWwiseMetadataLoader& Loader); + ~FWwiseMetadataRootFile(); + + static WwiseMetadataSharedRootFilePtr LoadFile(const FString& FilePath); + static WwiseMetadataSharedRootFilePtr LoadFile(FString&& File, const FString& FilePath); + static WwiseMetadataFileMap LoadFiles(const TArray& FilePaths); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataRootPaths.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataRootPaths.h new file mode 100644 index 0000000..7fca9be --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataRootPaths.h @@ -0,0 +1,32 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataLoadable.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataRootPaths : public FWwiseMetadataLoadable +{ + FName ProjectRoot; + FName SourceFilesRoot; + FName SoundBanksRoot; + FName ExternalSourcesInputFile; + FName ExternalSourcesOutputRoot; + + FWwiseMetadataRootPaths(); + FWwiseMetadataRootPaths(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataSettings.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataSettings.h new file mode 100644 index 0000000..4e4f9cd --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataSettings.h @@ -0,0 +1,44 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataLoadable.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataSettings : public FWwiseMetadataLoadable +{ + bool bAutoSoundBankDefinition; + bool bCopyLooseStreamedMediaFiles; + bool bSubFoldersForGeneratedFiles; + bool bRemoveUnusedGeneratedFiles; + bool bSourceControlGeneratedFiles; + bool bGenerateHeaderFile; + bool bGenerateContentTxtFile; + bool bGenerateMetadataXML; + bool bGenerateMetadataJSON; + bool bGenerateAllBanksMetadata; + bool bGeneratePerBankMetadata; + bool bUseSoundBankNames; + bool bAllowExceedingMaxSize; + bool bMaxAttenuationInfo; + bool bEstimatedDurationInfo; + bool bPrintObjectGuid; + bool bPrintObjectPath; + + FWwiseMetadataSettings(); + FWwiseMetadataSettings(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataSoundBank.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataSoundBank.h new file mode 100644 index 0000000..7de8c73 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataSoundBank.h @@ -0,0 +1,101 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataAcousticTexture.h" +#include "Wwise/Metadata/WwiseMetadataBus.h" +#include "Wwise/Metadata/WwiseMetadataCollections.h" +#include "Wwise/Metadata/WwiseMetadataDialogue.h" +#include "Wwise/Metadata/WwiseMetadataEvent.h" +#include "Wwise/Metadata/WwiseMetadataExternalSource.h" +#include "Wwise/Metadata/WwiseMetadataGameParameter.h" +#include "Wwise/Metadata/WwiseMetadataMedia.h" +#include "Wwise/Metadata/WwiseMetadataPluginGroup.h" +#include "Wwise/Metadata/WwiseMetadataStateGroup.h" +#include "Wwise/Metadata/WwiseMetadataSwitchGroup.h" +#include "Wwise/Metadata/WwiseMetadataTrigger.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataSoundBankReference : public FWwiseMetadataLoadable +{ + uint32 Id; + FGuid GUID; + FName Language; + + FWwiseMetadataSoundBankReference(FWwiseMetadataLoader& Loader); +}; + +enum class EMetadataSoundBankType : uint32 +{ + User = 0, + Event = 30, + Bus = 31, + Unknown = (uint32)-1 +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataSoundBankAttributes : public FWwiseMetadataSoundBankReference +{ + uint32 Align; + bool bDeviceMemory; + FGuid Hash; + EMetadataSoundBankType Type; + + FWwiseMetadataSoundBankAttributes(FWwiseMetadataLoader& Loader); + +private: + static EMetadataSoundBankType TypeFromString(const FName& TypeString); +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataSoundBank : public FWwiseMetadataSoundBankAttributes +{ + FName ObjectPath; + FName ShortName; + FName Path; + + TArray Media; + FWwiseMetadataPluginGroup* Plugins; + TArray Events; + TArray DialogueEvents; + TArray Busses; + TArray AuxBusses; + TArray GameParameters; + TArray StateGroups; + TArray SwitchGroups; + TArray Triggers; + TArray ExternalSources; + TArray AcousticTextures; + + FWwiseMetadataSoundBank(FWwiseMetadataLoader& Loader); + TSet GetAllDialogueArguments() const; + TSet GetAllStates() const; + TSet GetAllSwitches() const; + bool IsInitBank() const + { + return bIsInitBank; + } + bool ContainsMedia() const + { + return Media.ContainsByPredicate([](const FWwiseMetadataMedia& MediaToTest) + { + return MediaToTest.Location == EWwiseMetadataMediaLocation::Memory; + }); + } + +protected: + bool bIsInitBank; + +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataSoundBanksInfo.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataSoundBanksInfo.h new file mode 100644 index 0000000..8d55a5d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataSoundBanksInfo.h @@ -0,0 +1,43 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataLoadable.h" +#include "Wwise/Metadata/WwiseMetadataSoundBank.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataSoundBanksInfoAttributes : public FWwiseMetadataLoadable +{ + FName Platform; + FName BasePlatform; + uint32 SchemaVersion; + uint32 SoundBankVersion; + + FWwiseMetadataSoundBanksInfoAttributes(FWwiseMetadataLoader& Loader); +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataSoundBanksInfo : public FWwiseMetadataSoundBanksInfoAttributes +{ + FWwiseMetadataRootPaths* RootPaths; + TArray DialogueEvents; + + TArray SoundBanks; + FGuid FileHash; + + FWwiseMetadataSoundBanksInfo(FWwiseMetadataLoader& Loader); + ~FWwiseMetadataSoundBanksInfo(); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataState.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataState.h new file mode 100644 index 0000000..1a4ff23 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataState.h @@ -0,0 +1,25 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataBasicReference.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataState : public FWwiseMetadataBasicReference +{ + FWwiseMetadataState(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataStateGroup.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataStateGroup.h new file mode 100644 index 0000000..5de7833 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataStateGroup.h @@ -0,0 +1,27 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataState.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataStateGroup : public FWwiseMetadataBasicReference +{ + TArray States; + + FWwiseMetadataStateGroup(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataSwitch.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataSwitch.h new file mode 100644 index 0000000..53c7df2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataSwitch.h @@ -0,0 +1,25 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataBasicReference.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataSwitch : public FWwiseMetadataBasicReference +{ + FWwiseMetadataSwitch(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataSwitchContainer.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataSwitchContainer.h new file mode 100644 index 0000000..7ee8161 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataSwitchContainer.h @@ -0,0 +1,37 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataExternalSource.h" +#include "Wwise/Metadata/WwiseMetadataSwitchValue.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataSwitchContainer : public FWwiseMetadataLoadable +{ + FWwiseMetadataSwitchValue SwitchValue; + TArray MediaRefs; + TArray ExternalSourceRefs; + FWwiseMetadataPluginReferenceGroup* PluginRefs; + TArray Children; + + FWwiseMetadataSwitchContainer(FWwiseMetadataLoader& Loader); + TSet GetAllMedia() const; + TSet GetAllExternalSources() const; + TSet GetAllCustomPlugins() const; + TSet GetAllPluginShareSets() const; + TSet GetAllAudioDevices() const; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataSwitchGroup.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataSwitchGroup.h new file mode 100644 index 0000000..ec18749 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataSwitchGroup.h @@ -0,0 +1,28 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataSwitch.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataSwitchGroup : public FWwiseMetadataBasicReference +{ + FWwiseMetadataGameParameterReference* GameParameterRef; + TArray Switches; + + FWwiseMetadataSwitchGroup(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataSwitchValue.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataSwitchValue.h new file mode 100644 index 0000000..ee87fe9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataSwitchValue.h @@ -0,0 +1,48 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataLoadable.h" + +enum class EWwiseMetadataSwitchValueGroupType : uint32 +{ + Switch, + State, + Unknown = (uint32)-1 +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataSwitchValueAttributes : public FWwiseMetadataLoadable +{ + EWwiseMetadataSwitchValueGroupType GroupType; + uint32 GroupId; + uint32 Id; + FGuid GUID; + bool bDefault; + + FWwiseMetadataSwitchValueAttributes(); + FWwiseMetadataSwitchValueAttributes(FWwiseMetadataLoader& Loader); + +private: + static EWwiseMetadataSwitchValueGroupType GroupTypeFromString(const FName& TypeString); +}; + +struct WWISEPROJECTDATABASE_API FWwiseMetadataSwitchValue : public FWwiseMetadataSwitchValueAttributes +{ + FWwiseMetadataSwitchValue(); + FWwiseMetadataSwitchValue(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataTrigger.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataTrigger.h new file mode 100644 index 0000000..66694a8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Metadata/WwiseMetadataTrigger.h @@ -0,0 +1,25 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataBasicReference.h" + +struct WWISEPROJECTDATABASE_API FWwiseMetadataTrigger : public FWwiseMetadataBasicReference +{ + FWwiseMetadataTrigger(FWwiseMetadataLoader& Loader); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseAnyRef.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseAnyRef.h new file mode 100644 index 0000000..971dea3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseAnyRef.h @@ -0,0 +1,167 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataForwardDeclarations.h" +#include "Wwise/Ref/WwiseRefRootFile.h" +#include "Wwise/Ref/WwiseRefType.h" + +class WWISEPROJECTDATABASE_API FWwiseAnyRef +{ +public: + TSharedPtr Ref; + + template + static FWwiseAnyRef Create(const EWwiseRefType& inRef) + { + return FWwiseAnyRef(new EWwiseRefType(inRef)); + } + + FWwiseAnyRef() : + Ref() + {} + + FWwiseAnyRef(FWwiseAnyRef&& InRef) : + Ref(MoveTemp(InRef.Ref)) + {} + + FWwiseAnyRef(const FWwiseAnyRef& InRef) : + Ref(InRef.Ref) + {} + +private: + FWwiseAnyRef(FWwiseRefRootFile*&& InRef) : + Ref(InRef) + { + } + +public: + ~FWwiseAnyRef() + { + } + + EWwiseRefType GetType() const + { + if (!Ref) + { + return EWwiseRefType::None; + } + return (EWwiseRefType)Ref->Type(); + } + operator bool() const { return Ref != nullptr; } + bool IsValid() const { return Ref != nullptr; } + + const FWwiseRefLanguage* GetLanguageRef() const; + const FWwiseRefPlatform* GetPlatformRef() const; + const FWwiseRefPluginLib* GetPluginLibRef() const; + const FWwiseRefSoundBank* GetSoundBankRef() const; + const FWwiseRefMedia* GetMediaRef() const; + const FWwiseRefCustomPlugin* GetCustomPluginRef() const; + const FWwiseRefPluginShareSet* GetPluginShareSetRef() const; + const FWwiseRefAudioDevice* GetAudioDeviceRef() const; + const FWwiseRefEvent* GetEventRef() const; + const FWwiseRefSwitchContainer* GetSwitchContainerRef() const; + const FWwiseRefDialogueEvent* GetDialogueEventRef() const; + const FWwiseRefDialogueArgument* GetDialogueArgumentRef() const; + const FWwiseRefBus* GetBusRef() const; + const FWwiseRefAuxBus* GetAuxBusRef() const; + const FWwiseRefGameParameter* GetGameParameterRef() const; + const FWwiseRefStateGroup* GetStateGroupRef() const; + const FWwiseRefState* GetStateRef() const; + const FWwiseRefSwitchGroup* GetSwitchGroupRef() const; + const FWwiseRefSwitch* GetSwitchRef() const; + const FWwiseRefTrigger* GetTriggerRef() const; + const FWwiseRefExternalSource* GetExternalSourceRef() const; + const FWwiseRefAcousticTexture* GetAcousticTextureRef() const; + + const FWwiseMetadataLanguage* GetLanguage() const; + const FWwiseMetadataPlatform* GetPlatform() const; + const FWwiseMetadataPlatformReference* GetPlatformReference() const; + const FWwiseMetadataPluginLib* GetPluginLib() const; + const FWwiseMetadataSoundBank* GetSoundBank() const; + const FWwiseMetadataMedia* GetMedia() const; + const FWwiseMetadataPlugin* GetCustomPlugin() const; + const FWwiseMetadataPlugin* GetPluginShareSet() const; + const FWwiseMetadataPlugin* GetAudioDevice() const; + const FWwiseMetadataEvent* GetEvent() const; + const FWwiseMetadataSwitchContainer* GetSwitchContainer() const; + const FWwiseMetadataDialogueEvent* GetDialogueEvent() const; + const FWwiseMetadataDialogueArgument* GetDialogueArgument() const; + const FWwiseMetadataBus* GetBus() const; + const FWwiseMetadataBus* GetAuxBus() const; + const FWwiseMetadataGameParameter* GetGameParameter() const; + const FWwiseMetadataStateGroup* GetStateGroup() const; + const FWwiseMetadataState* GetState() const; + const FWwiseMetadataSwitchGroup* GetSwitchGroup() const; + const FWwiseMetadataSwitch* GetSwitch() const; + const FWwiseMetadataTrigger* GetTrigger() const; + const FWwiseMetadataExternalSource* GetExternalSource() const; + const FWwiseMetadataAcousticTexture* GetAcousticTexture() const; + + bool GetRef(FWwiseRefLanguage& OutRef) const; + bool GetRef(FWwiseRefPlatform& OutRef) const; + bool GetRef(FWwiseRefPluginLib& OutRef) const; + bool GetRef(FWwiseRefSoundBank& OutRef) const; + bool GetRef(FWwiseRefMedia& OutRef) const; + bool GetRef(FWwiseRefCustomPlugin& OutRef) const; + bool GetRef(FWwiseRefPluginShareSet& OutRef) const; + bool GetRef(FWwiseRefAudioDevice& OutRef) const; + bool GetRef(FWwiseRefEvent& OutRef) const; + bool GetRef(FWwiseRefSwitchContainer& OutRef) const; + bool GetRef(FWwiseRefDialogueEvent& OutRef) const; + bool GetRef(FWwiseRefDialogueArgument& OutRef) const; + bool GetRef(FWwiseRefBus& OutRef) const; + bool GetRef(FWwiseRefAuxBus& OutRef) const; + bool GetRef(FWwiseRefGameParameter& OutRef) const; + bool GetRef(FWwiseRefStateGroup& OutRef) const; + bool GetRef(FWwiseRefState& OutRef) const; + bool GetRef(FWwiseRefSwitchGroup& OutRef) const; + bool GetRef(FWwiseRefSwitch& OutRef) const; + bool GetRef(FWwiseRefTrigger& OutRef) const; + bool GetRef(FWwiseRefExternalSource& OutRef) const; + bool GetRef(FWwiseRefAcousticTexture& OutRef) const; + + FGuid GetGuid(const EWwiseRefType* TypeOverride = nullptr) const; + uint32 GetGroupId(const EWwiseRefType* TypeOverride = nullptr) const; + uint32 GetId(const EWwiseRefType* TypeOverride = nullptr) const; + FName GetName(const EWwiseRefType* TypeOverride = nullptr) const; + FName GetObjectPath(const EWwiseRefType* TypeOverride = nullptr) const; + + bool operator ==(const FWwiseAnyRef& Rhs) const + { + return GetType() == Rhs.GetType() + && operator bool() == Rhs.operator bool() + && (!operator bool() + || Ref->Hash() == Rhs.Ref->Hash()); + } + + bool operator !=(const FWwiseAnyRef& Rhs) const + { + return !(operator == (Rhs)); + } + +}; + +inline uint32 GetTypeHash(const FWwiseAnyRef& InValue) +{ + if ((bool)InValue) + { + return InValue.Ref->Hash(); + } + return 0; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefAcousticTexture.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefAcousticTexture.h new file mode 100644 index 0000000..3090e30 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefAcousticTexture.h @@ -0,0 +1,66 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefSoundBank.h" + +class WWISEPROJECTDATABASE_API FWwiseRefAcousticTexture : public FWwiseRefSoundBank +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::AcousticTexture; + struct FGlobalIdsMap; + + WwiseRefIndexType AcousticTextureIndex; + + FWwiseRefAcousticTexture() {} + FWwiseRefAcousticTexture(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath, + WwiseRefIndexType InSoundBankIndex, uint32 InLanguageId, + WwiseRefIndexType InAcousticTextureIndex) : + FWwiseRefSoundBank(InRootMediaRef, InJsonFilePath, InSoundBankIndex, InLanguageId), + AcousticTextureIndex(InAcousticTextureIndex) + {} + const FWwiseMetadataAcousticTexture* GetAcousticTexture() const; + + uint32 AcousticTextureId() const; + FGuid AcousticTextureGuid() const; + FName AcousticTextureName() const; + FName AcousticTextureObjectPath() const; + + uint32 Hash() const override; + EWwiseRefType Type() const override { return TYPE; } + bool operator==(const FWwiseRefAcousticTexture& Rhs) const + { + return FWwiseRefSoundBank::operator ==(Rhs) + && AcousticTextureIndex == Rhs.AcousticTextureIndex; + } + bool operator!=(const FWwiseRefAcousticTexture& Rhs) const { return !operator==(Rhs); } +}; + +struct WWISEPROJECTDATABASE_API FWwiseRefAcousticTexture::FGlobalIdsMap +{ + WwiseAcousticTextureGlobalIdsMap GlobalIdsMap; + + FGlobalIdsMap() {} + FGlobalIdsMap(const FGlobalIdsMap& Rhs) : + GlobalIdsMap(Rhs.GlobalIdsMap) + {} + FGlobalIdsMap(FGlobalIdsMap&& Rhs) : + GlobalIdsMap(MoveTemp(Rhs.GlobalIdsMap)) + {} +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefAudioDevice.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefAudioDevice.h new file mode 100644 index 0000000..cf66b5e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefAudioDevice.h @@ -0,0 +1,59 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefSoundBank.h" + +class WWISEPROJECTDATABASE_API FWwiseRefAudioDevice : public FWwiseRefSoundBank +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::AudioDevice; + struct FGlobalIdsMap; + + WwiseRefIndexType AudioDeviceIndex; + + FWwiseRefAudioDevice() {} + FWwiseRefAudioDevice(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath, + WwiseRefIndexType InSoundBankIndex, uint32 InLanguageId, + WwiseRefIndexType InAudioDeviceIndex) : + FWwiseRefSoundBank(InRootMediaRef, InJsonFilePath, InSoundBankIndex, InLanguageId), + AudioDeviceIndex(InAudioDeviceIndex) + {} + const FWwiseMetadataPlugin* GetPlugin() const; + WwiseMediaIdsMap GetPluginMedia(const WwiseMediaGlobalIdsMap& GlobalMap) const; + + uint32 AudioDeviceId() const; + FGuid AudioDeviceGuid() const; + FName AudioDeviceName() const; + FName AudioDeviceObjectPath() const; + + uint32 Hash() const override; + EWwiseRefType Type() const override { return TYPE; } + bool operator==(const FWwiseRefAudioDevice& Rhs) const + { + return FWwiseRefSoundBank::operator==(Rhs) + && AudioDeviceIndex == Rhs.AudioDeviceIndex; + } + bool operator!=(const FWwiseRefAudioDevice& Rhs) const { return !operator==(Rhs); } +}; + +struct WWISEPROJECTDATABASE_API FWwiseRefAudioDevice::FGlobalIdsMap +{ + WwiseAudioDeviceGlobalIdsMap GlobalIdsMap; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefAuxBus.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefAuxBus.h new file mode 100644 index 0000000..48c4b93 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefAuxBus.h @@ -0,0 +1,62 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefSoundBank.h" + +class WWISEPROJECTDATABASE_API FWwiseRefAuxBus : public FWwiseRefSoundBank +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::AuxBus; + struct FGlobalIdsMap; + + WwiseRefIndexType AuxBusIndex; + + FWwiseRefAuxBus() {} + FWwiseRefAuxBus(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath, + WwiseRefIndexType InSoundBankIndex, uint32 InLanguageId, + WwiseRefIndexType InAuxBusIndex) : + FWwiseRefSoundBank(InRootMediaRef, InJsonFilePath, InSoundBankIndex, InLanguageId), + AuxBusIndex(InAuxBusIndex) + {} + const FWwiseMetadataBus* GetAuxBus() const; + void GetAllAuxBusRefs(TSet& OutAuxBusRefs, const WwiseAuxBusGlobalIdsMap& InGlobalMap) const; + WwiseCustomPluginIdsMap GetAuxBusCustomPlugins(const WwiseCustomPluginGlobalIdsMap& GlobalMap) const; + WwisePluginShareSetIdsMap GetAuxBusPluginShareSets(const WwisePluginShareSetGlobalIdsMap& GlobalMap) const; + WwiseAudioDeviceIdsMap GetAuxBusAudioDevices(const WwiseAudioDeviceGlobalIdsMap& GlobalMap) const; + + uint32 AuxBusId() const; + FGuid AuxBusGuid() const; + FName AuxBusName() const; + FName AuxBusObjectPath() const; + + uint32 Hash() const override; + EWwiseRefType Type() const override { return TYPE; } + bool operator==(const FWwiseRefAuxBus& Rhs) const + { + return FWwiseRefSoundBank::operator ==(Rhs) + && AuxBusIndex == Rhs.AuxBusIndex; + } + bool operator!=(const FWwiseRefAuxBus& Rhs) const { return !operator==(Rhs); } +}; + +struct WWISEPROJECTDATABASE_API FWwiseRefAuxBus::FGlobalIdsMap +{ + WwiseAuxBusGlobalIdsMap GlobalIdsMap; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefBus.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefBus.h new file mode 100644 index 0000000..b85ec4a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefBus.h @@ -0,0 +1,58 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefSoundBank.h" + +class WWISEPROJECTDATABASE_API FWwiseRefBus : public FWwiseRefSoundBank +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::Bus; + struct FGlobalIdsMap; + + WwiseRefIndexType BusIndex; + + FWwiseRefBus() {} + FWwiseRefBus(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath, + WwiseRefIndexType InSoundBankIndex, uint32 InLanguageId, + WwiseRefIndexType InBusIndex) : + FWwiseRefSoundBank(InRootMediaRef, InJsonFilePath, InSoundBankIndex, InLanguageId), + BusIndex(InBusIndex) + {} + const FWwiseMetadataBus* GetBus() const; + + uint32 BusId() const; + FGuid BusGuid() const; + FName BusName() const; + FName BusObjectPath() const; + + uint32 Hash() const override; + EWwiseRefType Type() const override { return TYPE; } + bool operator==(const FWwiseRefBus& Rhs) const + { + return FWwiseRefSoundBank::operator ==(Rhs) + && BusIndex == Rhs.BusIndex; + } + bool operator!=(const FWwiseRefBus& Rhs) const { return !operator==(Rhs); } +}; + +struct WWISEPROJECTDATABASE_API FWwiseRefBus::FGlobalIdsMap +{ + WwiseBusGlobalIdsMap GlobalIdsMap; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefCollections.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefCollections.h new file mode 100644 index 0000000..cfbd736 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefCollections.h @@ -0,0 +1,73 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefForwardDeclarations.h" +#include "Wwise/WwiseDatabaseIdentifiers.h" + +#include "CoreMinimal.h" + +using WwiseLanguageNamesMap = TMap; +using WwiseLanguageIdsMap = TMap; +using WwisePlatformNamesMap = TMap; +using WwisePlatformGuidsMap = TMap; +using WwiseMediaIdsMap = TMap; +using WwisePluginLibNamesMap = TMap; +using WwisePluginLibIdsMap = TMap; +using WwiseSoundBankIdsMap = TMap; +using WwiseDialogueEventIdsMap = TMap; +using WwiseDialogueArgumentIdsMap = TMap; +using WwiseBusIdsMap = TMap; +using WwiseAuxBusIdsMap = TMap; +using WwiseCustomPluginIdsMap = TMap; +using WwisePluginShareSetIdsMap = TMap; +using WwiseAudioDeviceIdsMap = TMap; +using WwiseEventIdsMap = TMap; +using WwiseExternalSourceIdsMap = TMap; +using WwiseAcousticTextureIdsMap = TMap; +using WwiseGameParameterIdsMap = TMap; +using WwiseStateGroupIdsMap = TMap; +using WwiseSwitchGroupIdsMap = TMap; +using WwiseTriggerIdsMap = TMap; +using WwiseStateIdsMap = TMap; +using WwiseSwitchIdsMap = TMap; +using WwiseSwitchContainerArray = TArray; + +using WwiseMediaGlobalIdsMap = TMap; +using WwiseSoundBankGlobalIdsMap = TMap; +using WwiseDialogueEventGlobalIdsMap = TMap; +using WwiseDialogueArgumentGlobalIdsMap = TMap; +using WwiseBusGlobalIdsMap = TMap; +using WwiseAuxBusGlobalIdsMap = TMap; +using WwiseCustomPluginGlobalIdsMap = TMap; +using WwisePluginShareSetGlobalIdsMap = TMap; +using WwisePluginLibGlobalIdsMap = TMap; +using WwiseAudioDeviceGlobalIdsMap = TMap; +using WwiseEventGlobalIdsMap = TMap; +using WwiseExternalSourceGlobalIdsMap = TMap; +using WwiseAcousticTextureGlobalIdsMap = TMap; +using WwiseGameParameterGlobalIdsMap = TMap; +using WwiseStateGroupGlobalIdsMap = TMap; +using WwiseSwitchGroupGlobalIdsMap = TMap; +using WwiseTriggerGlobalIdsMap = TMap; +using WwiseStateGlobalIdsMap = TMap; +using WwiseSwitchGlobalIdsMap = TMap; +using WwiseSwitchContainersByEvent = TMultiMap; + +using WwiseGuidMap = TMultiMap; +using WwiseNameMap = TMultiMap; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefCustomPlugin.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefCustomPlugin.h new file mode 100644 index 0000000..2cbf577 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefCustomPlugin.h @@ -0,0 +1,59 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefSoundBank.h" + +class WWISEPROJECTDATABASE_API FWwiseRefCustomPlugin : public FWwiseRefSoundBank +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::CustomPlugin; + struct FGlobalIdsMap; + + WwiseRefIndexType CustomPluginIndex; + + FWwiseRefCustomPlugin() {} + FWwiseRefCustomPlugin(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath, + WwiseRefIndexType InSoundBankIndex, uint32 InLanguageId, + WwiseRefIndexType InCustomPluginIndex) : + FWwiseRefSoundBank(InRootMediaRef, InJsonFilePath, InSoundBankIndex, InLanguageId), + CustomPluginIndex(InCustomPluginIndex) + {} + const FWwiseMetadataPlugin* GetPlugin() const; + WwiseMediaIdsMap GetPluginMedia(const WwiseMediaGlobalIdsMap& GlobalMap) const; + + uint32 CustomPluginId() const; + FGuid CustomPluginGuid() const; + FName CustomPluginName() const; + FName CustomPluginObjectPath() const; + + uint32 Hash() const override; + EWwiseRefType Type() const override { return TYPE; } + bool operator==(const FWwiseRefCustomPlugin& Rhs) const + { + return FWwiseRefSoundBank::operator==(Rhs) + && CustomPluginIndex == Rhs.CustomPluginIndex; + } + bool operator!=(const FWwiseRefCustomPlugin& Rhs) const { return !operator==(Rhs); } +}; + +struct WWISEPROJECTDATABASE_API FWwiseRefCustomPlugin::FGlobalIdsMap +{ + WwiseCustomPluginGlobalIdsMap GlobalIdsMap; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefDialogueArgument.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefDialogueArgument.h new file mode 100644 index 0000000..77bb70c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefDialogueArgument.h @@ -0,0 +1,59 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefDialogueEvent.h" + +class WWISEPROJECTDATABASE_API FWwiseRefDialogueArgument : public FWwiseRefDialogueEvent +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::DialogueArgument; + struct FGlobalIdsMap; + + WwiseRefIndexType DialogueArgumentIndex; + + FWwiseRefDialogueArgument() {} + FWwiseRefDialogueArgument(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath, + WwiseRefIndexType InSoundBankIndex, uint32 InLanguageId, + WwiseRefIndexType InDialogueEventIndex, + WwiseRefIndexType InDialogueArgumentIndex) : + FWwiseRefDialogueEvent(InRootMediaRef, InJsonFilePath, InSoundBankIndex, InLanguageId, InDialogueEventIndex), + DialogueArgumentIndex(InDialogueArgumentIndex) + {} + const FWwiseMetadataDialogueArgument* GetDialogueArgument() const; + + uint32 DialogueArgumentId() const; + FGuid DialogueArgumentGuid() const; + FName DialogueArgumentName() const; + FName DialogueArgumentObjectPath() const; + + uint32 Hash() const override; + EWwiseRefType Type() const override { return TYPE; } + bool operator==(const FWwiseRefDialogueArgument& Rhs) const + { + return FWwiseRefDialogueEvent::operator ==(Rhs) + && DialogueArgumentIndex == Rhs.DialogueArgumentIndex; + } + bool operator!=(const FWwiseRefDialogueArgument& Rhs) const { return !operator==(Rhs); } +}; + +struct WWISEPROJECTDATABASE_API FWwiseRefDialogueArgument::FGlobalIdsMap +{ + WwiseDialogueArgumentGlobalIdsMap GlobalIdsMap; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefDialogueEvent.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefDialogueEvent.h new file mode 100644 index 0000000..a4af80f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefDialogueEvent.h @@ -0,0 +1,59 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefSoundBank.h" + +class WWISEPROJECTDATABASE_API FWwiseRefDialogueEvent : public FWwiseRefSoundBank +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::DialogueEvent; + struct FGlobalIdsMap; + + WwiseRefIndexType DialogueEventIndex; + + FWwiseRefDialogueEvent() {} + FWwiseRefDialogueEvent(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath, + WwiseRefIndexType InSoundBankIndex, uint32 InLanguageId, + WwiseRefIndexType InDialogueEventIndex) : + FWwiseRefSoundBank(InRootMediaRef, InJsonFilePath, InSoundBankIndex, InLanguageId), + DialogueEventIndex(InDialogueEventIndex) + {} + const FWwiseMetadataDialogueEvent* GetDialogueEvent() const; + WwiseDialogueArgumentIdsMap GetDialogueArguments(const WwiseDialogueArgumentGlobalIdsMap& GlobalMap) const; + + uint32 DialogueEventId() const; + FGuid DialogueEventGuid() const; + FName DialogueEventName() const; + FName DialogueEventObjectPath() const; + + uint32 Hash() const override; + EWwiseRefType Type() const override { return TYPE; } + bool operator==(const FWwiseRefDialogueEvent& Rhs) const + { + return FWwiseRefSoundBank::operator ==(Rhs) + && DialogueEventIndex == Rhs.DialogueEventIndex; + } + bool operator!=(const FWwiseRefDialogueEvent& Rhs) const { return !operator==(Rhs); } +}; + +struct WWISEPROJECTDATABASE_API FWwiseRefDialogueEvent::FGlobalIdsMap +{ + WwiseDialogueEventGlobalIdsMap GlobalIdsMap; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefEvent.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefEvent.h new file mode 100644 index 0000000..3e09048 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefEvent.h @@ -0,0 +1,77 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataEvent.h" +#include "Wwise/Ref/WwiseRefSoundBank.h" + +class WWISEPROJECTDATABASE_API FWwiseRefEvent : public FWwiseRefSoundBank +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::Event; + struct FGlobalIdsMap; + + WwiseRefIndexType EventIndex; + + FWwiseRefEvent() {} + FWwiseRefEvent(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath, + WwiseRefIndexType InSoundBankIndex, uint32 InLanguageId, + WwiseRefIndexType InEventIndex) : + FWwiseRefSoundBank(InRootMediaRef, InJsonFilePath, InSoundBankIndex, InLanguageId), + EventIndex(InEventIndex) + {} + const FWwiseMetadataEvent* GetEvent() const; + WwiseMediaIdsMap GetEventMedia(const WwiseMediaGlobalIdsMap& GlobalMap) const; + WwiseMediaIdsMap GetAllMedia(const WwiseMediaGlobalIdsMap& GlobalMap) const; + WwiseExternalSourceIdsMap GetEventExternalSources(const WwiseExternalSourceGlobalIdsMap& GlobalMap) const; + WwiseExternalSourceIdsMap GetAllExternalSources(const WwiseExternalSourceGlobalIdsMap& GlobalMap) const; + WwiseCustomPluginIdsMap GetEventCustomPlugins(const WwiseCustomPluginGlobalIdsMap& GlobalMap) const; + WwiseCustomPluginIdsMap GetAllCustomPlugins(const WwiseCustomPluginGlobalIdsMap& GlobalMap) const; + WwisePluginShareSetIdsMap GetEventPluginShareSets(const WwisePluginShareSetGlobalIdsMap& GlobalMap) const; + WwisePluginShareSetIdsMap GetAllPluginShareSets(const WwisePluginShareSetGlobalIdsMap& GlobalMap) const; + WwiseAudioDeviceIdsMap GetEventAudioDevices(const WwiseAudioDeviceGlobalIdsMap& GlobalMap) const; + WwiseAudioDeviceIdsMap GetAllAudioDevices(const WwiseAudioDeviceGlobalIdsMap& GlobalMap) const; + WwiseSwitchContainerArray GetSwitchContainers(const WwiseSwitchContainersByEvent& GlobalMap) const; + WwiseEventIdsMap GetActionPostEvent(const WwiseEventGlobalIdsMap& GlobalMap) const; + WwiseStateIdsMap GetActionSetState(const WwiseStateGlobalIdsMap& GlobalMap) const; + WwiseSwitchIdsMap GetActionSetSwitch(const WwiseSwitchGlobalIdsMap& GlobalMap) const; + WwiseTriggerIdsMap GetActionTrigger(const WwiseTriggerGlobalIdsMap& GlobalMap) const; + WwiseAuxBusIdsMap GetEventAuxBusses(const WwiseAuxBusGlobalIdsMap& GlobalMap) const; + + uint32 EventId() const; + FGuid EventGuid() const; + FName EventName() const; + FName EventObjectPath() const; + uint32 MaxAttenuation() const; + bool GetDuration(EWwiseMetadataEventDurationType& OutDurationType, float& OutDurationMin, float& OutDurationMax) const; + + uint32 Hash() const override; + EWwiseRefType Type() const override { return TYPE; } + bool operator==(const FWwiseRefEvent& Rhs) const + { + return FWwiseRefSoundBank::operator ==(Rhs) + && EventIndex == Rhs.EventIndex; + } + bool operator!=(const FWwiseRefEvent& Rhs) const { return !operator==(Rhs); } +}; + +struct WWISEPROJECTDATABASE_API FWwiseRefEvent::FGlobalIdsMap +{ + WwiseEventGlobalIdsMap GlobalIdsMap; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefExternalSource.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefExternalSource.h new file mode 100644 index 0000000..58db035 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefExternalSource.h @@ -0,0 +1,58 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefSoundBank.h" + +class WWISEPROJECTDATABASE_API FWwiseRefExternalSource : public FWwiseRefSoundBank +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::ExternalSource; + struct FGlobalIdsMap; + + WwiseRefIndexType ExternalSourceIndex; + + FWwiseRefExternalSource() {} + FWwiseRefExternalSource(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath, + WwiseRefIndexType InSoundBankIndex, uint32 InLanguageId, + WwiseRefIndexType InExternalSourceIndex) : + FWwiseRefSoundBank(InRootMediaRef, InJsonFilePath, InSoundBankIndex, InLanguageId), + ExternalSourceIndex(InExternalSourceIndex) + {} + const FWwiseMetadataExternalSource* GetExternalSource() const; + + uint32 ExternalSourceCookie() const; + FGuid ExternalSourceGuid() const; + FName ExternalSourceName() const; + FName ExternalSourceObjectPath() const; + + uint32 Hash() const override; + EWwiseRefType Type() const override { return TYPE; } + bool operator==(const FWwiseRefExternalSource& Rhs) const + { + return FWwiseRefSoundBank::operator ==(Rhs) + && ExternalSourceIndex == Rhs.ExternalSourceIndex; + } + bool operator!=(const FWwiseRefExternalSource& Rhs) const { return !operator==(Rhs); } +}; + +struct WWISEPROJECTDATABASE_API FWwiseRefExternalSource::FGlobalIdsMap +{ + WwiseExternalSourceGlobalIdsMap GlobalIdsMap; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefForwardDeclarations.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefForwardDeclarations.h new file mode 100644 index 0000000..4051640 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefForwardDeclarations.h @@ -0,0 +1,47 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +class FWwiseAnyRef; +class FWwiseRefRootFile; +class FWwiseRefProjectInfo; +class FWwiseRefLanguage; +class FWwiseRefPlatformInfo; +class FWwiseRefPlatform; +class FWwiseRefPluginInfo; +class FWwiseRefPluginLib; +class FWwiseRefSoundBanksInfo; +class FWwiseRefSoundBank; +class FWwiseRefDialogueEvent; +class FWwiseRefDialogueArgument; +class FWwiseRefBus; +class FWwiseRefAuxBus; +class FWwiseRefCustomPlugin; +class FWwiseRefPluginShareSet; +class FWwiseRefAudioDevice; +class FWwiseRefEvent; +class FWwiseRefExternalSource; +class FWwiseRefAcousticTexture; +class FWwiseRefMedia; +class FWwiseRefGameParameter; +class FWwiseRefStateGroup; +class FWwiseRefSwitchGroup; +class FWwiseRefTrigger; +class FWwiseRefState; +class FWwiseRefSwitch; +class FWwiseRefSwitchContainer; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefGameParameter.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefGameParameter.h new file mode 100644 index 0000000..46868ba --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefGameParameter.h @@ -0,0 +1,58 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefSoundBank.h" + +class WWISEPROJECTDATABASE_API FWwiseRefGameParameter : public FWwiseRefSoundBank +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::GameParameter; + struct FGlobalIdsMap; + + WwiseRefIndexType GameParameterIndex; + + FWwiseRefGameParameter() {} + FWwiseRefGameParameter(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath, + WwiseRefIndexType InSoundBankIndex, uint32 InLanguageId, + WwiseRefIndexType InGameParameterIndex) : + FWwiseRefSoundBank(InRootMediaRef, InJsonFilePath, InSoundBankIndex, InLanguageId), + GameParameterIndex(InGameParameterIndex) + {} + const FWwiseMetadataGameParameter* GetGameParameter() const; + + uint32 GameParameterId() const; + FGuid GameParameterGuid() const; + FName GameParameterName() const; + FName GameParameterObjectPath() const; + + uint32 Hash() const override; + EWwiseRefType Type() const override { return TYPE; } + bool operator==(const FWwiseRefGameParameter& Rhs) const + { + return FWwiseRefSoundBank::operator ==(Rhs) + && GameParameterIndex == Rhs.GameParameterIndex; + } + bool operator!=(const FWwiseRefGameParameter& Rhs) const { return !operator==(Rhs); } +}; + +struct WWISEPROJECTDATABASE_API FWwiseRefGameParameter::FGlobalIdsMap +{ + WwiseGameParameterGlobalIdsMap GlobalIdsMap; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefLanguage.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefLanguage.h new file mode 100644 index 0000000..6654a12 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefLanguage.h @@ -0,0 +1,52 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefProjectInfo.h" + +class WWISEPROJECTDATABASE_API FWwiseRefLanguage : public FWwiseRefProjectInfo +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::Language; + + WwiseRefIndexType LanguageIndex; + + FWwiseRefLanguage() : + LanguageIndex(INDEX_NONE) + {} + FWwiseRefLanguage(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath, + WwiseRefIndexType InLanguageIndex) : + FWwiseRefProjectInfo(InRootMediaRef, InJsonFilePath), + LanguageIndex(InLanguageIndex) + {} + const FWwiseMetadataLanguage* GetLanguage() const; + + uint32 LanguageId() const; + FGuid LanguageGuid() const; + FName LanguageName() const; + + uint32 Hash() const override; + EWwiseRefType Type() const override { return TYPE; } + bool operator==(const FWwiseRefLanguage& Rhs) const + { + return FWwiseRefProjectInfo::operator==(Rhs) + && LanguageIndex == Rhs.LanguageIndex; + } + bool operator!=(const FWwiseRefLanguage& Rhs) const { return !operator==(Rhs); } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefMedia.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefMedia.h new file mode 100644 index 0000000..5366788 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefMedia.h @@ -0,0 +1,57 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefSoundBank.h" + +class WWISEPROJECTDATABASE_API FWwiseRefMedia : public FWwiseRefSoundBank +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::Media; + struct FGlobalIdsMap; + + WwiseRefIndexType MediaIndex; + + FWwiseRefMedia() {} + FWwiseRefMedia(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath, + WwiseRefIndexType InSoundBankIndex, uint32 InLanguageId, + WwiseRefIndexType InMediaIndex) : + FWwiseRefSoundBank(InRootMediaRef, InJsonFilePath, InSoundBankIndex, InLanguageId), + MediaIndex(InMediaIndex) + {} + const FWwiseMetadataMedia* GetMedia() const; + + uint32 MediaId() const; + FName MediaShortName() const; + FName MediaPath() const; + + uint32 Hash() const override; + EWwiseRefType Type() const override { return TYPE; } + bool operator==(const FWwiseRefMedia& Rhs) const + { + return FWwiseRefSoundBank::operator==(Rhs) + && MediaIndex == Rhs.MediaIndex; + } + bool operator!=(const FWwiseRefMedia& Rhs) const { return !operator==(Rhs); } +}; + +struct WWISEPROJECTDATABASE_API FWwiseRefMedia::FGlobalIdsMap +{ + WwiseMediaGlobalIdsMap GlobalIdsMap; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefPlatform.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefPlatform.h new file mode 100644 index 0000000..f1cd508 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefPlatform.h @@ -0,0 +1,67 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefPlatformInfo.h" +#include "Wwise/Ref/WwiseRefProjectInfo.h" + +class WWISEPROJECTDATABASE_API FWwiseRefPlatform : public FWwiseRefPlatformInfo +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::Platform; + + // The reference does contain supplemental information, such as Path. + FWwiseRefProjectInfo ProjectInfo; + WwiseRefIndexType ProjectInfoPlatformReferenceIndex; + + FWwiseRefPlatform() : + ProjectInfo(), + ProjectInfoPlatformReferenceIndex(INDEX_NONE) + {} + FWwiseRefPlatform(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath, + const WwiseMetadataSharedRootFileConstPtr& InProjectInfoRootMediaRef, const FName& InProjectInfoJsonFilePath, + WwiseRefIndexType InProjectInfoPlatformReferenceIndex) : + FWwiseRefPlatformInfo(InRootMediaRef, InJsonFilePath), + ProjectInfo(InProjectInfoRootMediaRef, InProjectInfoJsonFilePath), + ProjectInfoPlatformReferenceIndex(InProjectInfoPlatformReferenceIndex) + {} + FWwiseRefPlatform(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath) : + FWwiseRefPlatformInfo(InRootMediaRef, InJsonFilePath), + ProjectInfo(), + ProjectInfoPlatformReferenceIndex() + {} + FWwiseRefPlatform(const WwiseMetadataSharedRootFileConstPtr& InProjectInfoRootMediaRef, const FName& InProjectInfoJsonFilePath, + WwiseRefIndexType InProjectInfoPlatformReferenceIndex) : + FWwiseRefPlatformInfo(), + ProjectInfo(InProjectInfoRootMediaRef, InProjectInfoJsonFilePath), + ProjectInfoPlatformReferenceIndex(InProjectInfoPlatformReferenceIndex) + {} + void Merge(FWwiseRefPlatform&& InOtherPlatform); + + const FWwiseMetadataPlatform* GetPlatform() const; + const FWwiseMetadataPlatformReference* GetPlatformReference() const; + + FGuid PlatformGuid() const; + FName PlatformName() const; + FGuid BasePlatformGuid() const; + FName BasePlatformName() const; + + uint32 Hash() const override; + EWwiseRefType Type() const override { return TYPE; } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefPlatformInfo.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefPlatformInfo.h new file mode 100644 index 0000000..b4b69a7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefPlatformInfo.h @@ -0,0 +1,34 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefRootFile.h" + +class WWISEPROJECTDATABASE_API FWwiseRefPlatformInfo : public FWwiseRefRootFile +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::PlatformInfo; + + FWwiseRefPlatformInfo() {} + FWwiseRefPlatformInfo(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath) : + FWwiseRefRootFile(InRootMediaRef, InJsonFilePath) + {} + const FWwiseMetadataPlatformInfo* GetPlatformInfo() const; + EWwiseRefType Type() const override { return TYPE; } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefPluginInfo.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefPluginInfo.h new file mode 100644 index 0000000..7e47691 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefPluginInfo.h @@ -0,0 +1,34 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefRootFile.h" + +class WWISEPROJECTDATABASE_API FWwiseRefPluginInfo : public FWwiseRefRootFile +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::PluginInfo; + + FWwiseRefPluginInfo() {} + FWwiseRefPluginInfo(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath) : + FWwiseRefRootFile(InRootMediaRef, InJsonFilePath) + {} + const FWwiseMetadataPluginInfo* GetPluginInfo() const; + EWwiseRefType Type() const override { return TYPE; } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefPluginLib.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefPluginLib.h new file mode 100644 index 0000000..42429be --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefPluginLib.h @@ -0,0 +1,57 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefPluginInfo.h" + +class WWISEPROJECTDATABASE_API FWwiseRefPluginLib : public FWwiseRefPluginInfo +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::PluginLib; + struct FGlobalIdsMap; + + WwiseRefIndexType PluginLibIndex; + + FWwiseRefPluginLib() : + PluginLibIndex(INDEX_NONE) + {} + FWwiseRefPluginLib(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath, + WwiseRefIndexType InPluginIndex) : + FWwiseRefPluginInfo(InRootMediaRef, InJsonFilePath), + PluginLibIndex(InPluginIndex) + {} + const FWwiseMetadataPluginLib* GetPluginLib() const; + + uint32 PluginLibId() const; + FName PluginLibName() const; + + uint32 Hash() const override; + EWwiseRefType Type() const override { return TYPE; } + bool operator==(const FWwiseRefPluginLib& Rhs) const + { + return FWwiseRefPluginInfo::operator==(Rhs) + && PluginLibIndex == Rhs.PluginLibIndex; + } + bool operator!=(const FWwiseRefPluginLib& Rhs) const { return !operator==(Rhs); } +}; + +struct WWISEPROJECTDATABASE_API FWwiseRefPluginLib::FGlobalIdsMap +{ + WwisePluginLibGlobalIdsMap GlobalIdsMap; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefPluginShareSet.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefPluginShareSet.h new file mode 100644 index 0000000..5acf654 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefPluginShareSet.h @@ -0,0 +1,62 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefSoundBank.h" + +class WWISEPROJECTDATABASE_API FWwiseRefPluginShareSet : public FWwiseRefSoundBank +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::PluginShareSet; + struct FGlobalIdsMap; + + WwiseRefIndexType PluginShareSetIndex; + + FWwiseRefPluginShareSet() {} + FWwiseRefPluginShareSet(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath, + WwiseRefIndexType InSoundBankIndex, uint32 InLanguageId, + WwiseRefIndexType InPluginShareSetIndex) : + FWwiseRefSoundBank(InRootMediaRef, InJsonFilePath, InSoundBankIndex, InLanguageId), + PluginShareSetIndex(InPluginShareSetIndex) + {} + const FWwiseMetadataPlugin* GetPlugin() const; + WwiseMediaIdsMap GetPluginMedia(const WwiseMediaGlobalIdsMap& GlobalMap) const; + WwiseCustomPluginIdsMap GetPluginCustomPlugins(const WwiseCustomPluginGlobalIdsMap& GlobalMap) const; + WwisePluginShareSetIdsMap GetPluginPluginShareSets(const WwisePluginShareSetGlobalIdsMap& GlobalMap) const; + WwiseAudioDeviceIdsMap GetPluginAudioDevices(const WwiseAudioDeviceGlobalIdsMap& GlobalMap) const; + + uint32 PluginShareSetId() const; + FGuid PluginShareSetGuid() const; + FName PluginShareSetName() const; + FName PluginShareSetObjectPath() const; + + uint32 Hash() const override; + EWwiseRefType Type() const override { return TYPE; } + bool operator==(const FWwiseRefPluginShareSet& Rhs) const + { + return FWwiseRefSoundBank::operator==(Rhs) + && PluginShareSetIndex == Rhs.PluginShareSetIndex; + } + bool operator!=(const FWwiseRefPluginShareSet& Rhs) const { return !operator==(Rhs); } +}; + +struct WWISEPROJECTDATABASE_API FWwiseRefPluginShareSet::FGlobalIdsMap +{ + WwisePluginShareSetGlobalIdsMap GlobalIdsMap; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefProjectInfo.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefProjectInfo.h new file mode 100644 index 0000000..443d977 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefProjectInfo.h @@ -0,0 +1,34 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefRootFile.h" + +class WWISEPROJECTDATABASE_API FWwiseRefProjectInfo : public FWwiseRefRootFile +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::ProjectInfo; + + FWwiseRefProjectInfo() {} + FWwiseRefProjectInfo(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath) : + FWwiseRefRootFile(InRootMediaRef, InJsonFilePath) + {} + const FWwiseMetadataProjectInfo* GetProjectInfo() const; + EWwiseRefType Type() const override { return TYPE; } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefRootFile.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefRootFile.h new file mode 100644 index 0000000..e3218c6 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefRootFile.h @@ -0,0 +1,54 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataCollections.h" +#include "Wwise/Ref/WwiseRefCollections.h" +#include "Wwise/Ref/WwiseRefType.h" + +class WWISEPROJECTDATABASE_API FWwiseRefRootFile +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::RootFile; + + WwiseMetadataSharedRootFileConstPtr RootFileRef; + FName JsonFilePath; + + FWwiseRefRootFile() {} + FWwiseRefRootFile(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath) : + RootFileRef(InRootMediaRef), + JsonFilePath(InJsonFilePath) + {} + virtual ~FWwiseRefRootFile() {} + virtual uint32 Hash() const; + virtual EWwiseRefType Type() const { return TYPE; } + bool operator==(const FWwiseRefRootFile& Rhs) const + { + return JsonFilePath == Rhs.JsonFilePath; + } + bool operator!=(const FWwiseRefRootFile& Rhs) const { return !operator==(Rhs); } + + bool IsValid() const; + const FWwiseMetadataRootFile* GetRootFile() const; +}; + +inline uint32 GetTypeHash(const FWwiseRefRootFile& Type) +{ + return Type.Hash(); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefSoundBank.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefSoundBank.h new file mode 100644 index 0000000..78c6cd0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefSoundBank.h @@ -0,0 +1,78 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefSoundBanksInfo.h" + +class WWISEPROJECTDATABASE_API FWwiseRefSoundBank : public FWwiseRefSoundBanksInfo +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::SoundBank; + struct WWISEPROJECTDATABASE_API FGlobalIdsMap; + + WwiseRefIndexType SoundBankIndex; + uint32 LanguageId; + + FWwiseRefSoundBank() {} + FWwiseRefSoundBank(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath, + WwiseRefIndexType InSoundBankIndex, uint32 InLanguageId) : + FWwiseRefSoundBanksInfo(InRootMediaRef, InJsonFilePath), + SoundBankIndex(InSoundBankIndex), + LanguageId(InLanguageId) + {} + const FWwiseMetadataSoundBank* GetSoundBank() const; + WwiseMediaIdsMap GetSoundBankMedia(const WwiseMediaGlobalIdsMap& GlobalMap) const; + WwiseCustomPluginIdsMap GetSoundBankCustomPlugins(const WwiseCustomPluginGlobalIdsMap& GlobalMap) const; + WwisePluginShareSetIdsMap GetSoundBankPluginShareSets(const WwisePluginShareSetGlobalIdsMap& GlobalMap) const; + WwiseAudioDeviceIdsMap GetSoundBankAudioDevices(const WwiseAudioDeviceGlobalIdsMap& GlobalMap) const; + WwiseEventIdsMap GetSoundBankEvents(const WwiseEventGlobalIdsMap& GlobalMap) const; + WwiseDialogueEventIdsMap GetSoundBankDialogueEvents(const WwiseDialogueEventGlobalIdsMap& GlobalMap) const; + WwiseDialogueArgumentIdsMap GetAllSoundBankDialogueArguments(const WwiseDialogueArgumentGlobalIdsMap& GlobalMap) const; + WwiseBusIdsMap GetSoundBankBusses(const WwiseBusGlobalIdsMap& GlobalMap) const; + WwiseAuxBusIdsMap GetSoundBankAuxBusses(const WwiseAuxBusGlobalIdsMap& GlobalMap) const; + WwiseGameParameterIdsMap GetSoundBankGameParameters(const WwiseGameParameterGlobalIdsMap& GlobalMap) const; + WwiseStateGroupIdsMap GetSoundBankStateGroups(const WwiseStateGroupGlobalIdsMap& GlobalMap) const; + WwiseStateIdsMap GetAllSoundBankStates(const WwiseStateGlobalIdsMap& GlobalMap) const; + WwiseSwitchGroupIdsMap GetSoundBankSwitchGroups(const WwiseSwitchGroupGlobalIdsMap& GlobalMap) const; + WwiseSwitchIdsMap GetAllSoundBankSwitches(const WwiseSwitchGlobalIdsMap& GlobalMap) const; + WwiseTriggerIdsMap GetSoundBankTriggers(const WwiseTriggerGlobalIdsMap& GlobalMap) const; + WwiseExternalSourceIdsMap GetSoundBankExternalSources(const WwiseExternalSourceGlobalIdsMap& GlobalMap) const; + WwiseAcousticTextureIdsMap GetSoundBankAcousticTextures(const WwiseAcousticTextureGlobalIdsMap& GlobalMap) const; + bool IsUserBank() const; + bool IsInitBank() const; + + uint32 SoundBankId() const; + FGuid SoundBankGuid() const; + FName SoundBankShortName() const; + FName SoundBankObjectPath() const; + + uint32 Hash() const override; + EWwiseRefType Type() const override { return TYPE; } + bool operator==(const FWwiseRefSoundBank& Rhs) const + { + return FWwiseRefSoundBanksInfo::operator ==(Rhs) + && SoundBankIndex == Rhs.SoundBankIndex; + } + bool operator!=(const FWwiseRefSoundBank& Rhs) const { return !operator==(Rhs); } +}; + +struct WWISEPROJECTDATABASE_API FWwiseRefSoundBank::FGlobalIdsMap +{ + WwiseSoundBankGlobalIdsMap GlobalIdsMap; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefSoundBanksInfo.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefSoundBanksInfo.h new file mode 100644 index 0000000..a9966d8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefSoundBanksInfo.h @@ -0,0 +1,35 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefRootFile.h" + +class WWISEPROJECTDATABASE_API FWwiseRefSoundBanksInfo : public FWwiseRefRootFile +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::SoundBanksInfo; + + EWwiseRefType Type() const override { return TYPE; } + + FWwiseRefSoundBanksInfo() {} + FWwiseRefSoundBanksInfo(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath) : + FWwiseRefRootFile(InRootMediaRef, InJsonFilePath) + {} + const FWwiseMetadataSoundBanksInfo* GetSoundBanksInfo() const; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefState.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefState.h new file mode 100644 index 0000000..452c616 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefState.h @@ -0,0 +1,59 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefStateGroup.h" + +class WWISEPROJECTDATABASE_API FWwiseRefState : public FWwiseRefStateGroup +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::State; + struct FGlobalIdsMap; + + WwiseRefIndexType StateIndex; + + FWwiseRefState() {} + FWwiseRefState(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath, + WwiseRefIndexType InSoundBankIndex, uint32 InLanguageId, + WwiseRefIndexType InStateGroupIndex, + WwiseRefIndexType InStateIndex) : + FWwiseRefStateGroup(InRootMediaRef, InJsonFilePath, InSoundBankIndex, InLanguageId, InStateGroupIndex), + StateIndex(InStateIndex) + {} + const FWwiseMetadataState* GetState() const; + + uint32 StateId() const; + FGuid StateGuid() const; + FName StateName() const; + FName StateObjectPath() const; + + uint32 Hash() const override; + EWwiseRefType Type() const override { return TYPE; } + bool operator==(const FWwiseRefState& Rhs) const + { + return FWwiseRefStateGroup::operator ==(Rhs) + && StateIndex == Rhs.StateIndex; + } + bool operator!=(const FWwiseRefState& Rhs) const { return !operator==(Rhs); } +}; + +struct WWISEPROJECTDATABASE_API FWwiseRefState::FGlobalIdsMap +{ + WwiseStateGlobalIdsMap GlobalIdsMap; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefStateGroup.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefStateGroup.h new file mode 100644 index 0000000..462701d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefStateGroup.h @@ -0,0 +1,58 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefSoundBank.h" + +class WWISEPROJECTDATABASE_API FWwiseRefStateGroup : public FWwiseRefSoundBank +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::StateGroup; + struct FGlobalIdsMap; + + WwiseRefIndexType StateGroupIndex; + + FWwiseRefStateGroup() {} + FWwiseRefStateGroup(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath, + WwiseRefIndexType InSoundBankIndex, uint32 InLanguageId, + WwiseRefIndexType InStateGroupIndex) : + FWwiseRefSoundBank(InRootMediaRef, InJsonFilePath, InSoundBankIndex, InLanguageId), + StateGroupIndex(InStateGroupIndex) + {} + const FWwiseMetadataStateGroup* GetStateGroup() const; + + uint32 StateGroupId() const; + FGuid StateGroupGuid() const; + FName StateGroupName() const; + FName StateGroupObjectPath() const; + + uint32 Hash() const override; + EWwiseRefType Type() const override { return TYPE; } + bool operator==(const FWwiseRefStateGroup& Rhs) const + { + return FWwiseRefSoundBank::operator ==(Rhs) + && StateGroupIndex == Rhs.StateGroupIndex; + } + bool operator!=(const FWwiseRefStateGroup& Rhs) const { return !operator==(Rhs); } +}; + +struct WWISEPROJECTDATABASE_API FWwiseRefStateGroup::FGlobalIdsMap +{ + WwiseStateGroupGlobalIdsMap GlobalIdsMap; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefSwitch.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefSwitch.h new file mode 100644 index 0000000..fc94fca --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefSwitch.h @@ -0,0 +1,59 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefSwitchGroup.h" + +class WWISEPROJECTDATABASE_API FWwiseRefSwitch : public FWwiseRefSwitchGroup +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::Switch; + struct FGlobalIdsMap; + + WwiseRefIndexType SwitchIndex; + + FWwiseRefSwitch() {} + FWwiseRefSwitch(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath, + WwiseRefIndexType InSoundBankIndex, uint32 InLanguageId, + WwiseRefIndexType InSwitchGroupIndex, + WwiseRefIndexType InSwitchIndex) : + FWwiseRefSwitchGroup(InRootMediaRef, InJsonFilePath, InSoundBankIndex, InLanguageId, InSwitchGroupIndex), + SwitchIndex(InSwitchIndex) + {} + const FWwiseMetadataSwitch* GetSwitch() const; + + uint32 SwitchId() const; + FGuid SwitchGuid() const; + FName SwitchName() const; + FName SwitchObjectPath() const; + + uint32 Hash() const override; + EWwiseRefType Type() const override { return TYPE; } + bool operator==(const FWwiseRefSwitch& Rhs) const + { + return FWwiseRefSwitchGroup::operator ==(Rhs) + && SwitchIndex == Rhs.SwitchIndex; + } + bool operator!=(const FWwiseRefSwitch& Rhs) const { return !operator==(Rhs); } +}; + +struct WWISEPROJECTDATABASE_API FWwiseRefSwitch::FGlobalIdsMap +{ + WwiseSwitchGlobalIdsMap GlobalIdsMap; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefSwitchContainer.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefSwitchContainer.h new file mode 100644 index 0000000..d61817b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefSwitchContainer.h @@ -0,0 +1,55 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefEvent.h" + +class WWISEPROJECTDATABASE_API FWwiseRefSwitchContainer : public FWwiseRefEvent +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::SwitchContainer; + + TArray ChildrenIndices; + + FWwiseRefSwitchContainer() {} + FWwiseRefSwitchContainer(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath, + WwiseRefIndexType InSoundBankIndex, uint32 InLanguageId, + WwiseRefIndexType InEventIndex, + const TArray& InChildrenIndices) : + FWwiseRefEvent(InRootMediaRef, InJsonFilePath, InSoundBankIndex, InLanguageId, InEventIndex), + ChildrenIndices(InChildrenIndices) + {} + const FWwiseMetadataSwitchContainer* GetSwitchContainer() const; + FWwiseAnyRef GetSwitchValue(const WwiseSwitchGlobalIdsMap& SwitchGlobalMap, const WwiseStateGlobalIdsMap& StateGlobalMap) const; + WwiseMediaIdsMap GetSwitchContainerMedia(const WwiseMediaGlobalIdsMap& GlobalMap) const; + WwiseExternalSourceIdsMap GetSwitchContainerExternalSources(const WwiseExternalSourceGlobalIdsMap& GlobalMap) const; + WwiseCustomPluginIdsMap GetSwitchContainerCustomPlugins(const WwiseCustomPluginGlobalIdsMap& GlobalMap) const; + WwisePluginShareSetIdsMap GetSwitchContainerPluginShareSets(const WwisePluginShareSetGlobalIdsMap& GlobalMap) const; + WwiseAudioDeviceIdsMap GetSwitchContainerAudioDevices(const WwiseAudioDeviceGlobalIdsMap& GlobalMap) const; + TArray GetSwitchValues(const WwiseSwitchGlobalIdsMap& SwitchGlobalMap, const WwiseStateGlobalIdsMap& StateGlobalMap) const; + + uint32 Hash() const override; + EWwiseRefType Type() const override { return TYPE; } + bool operator==(const FWwiseRefSwitchContainer& Rhs) const + { + return FWwiseRefEvent::operator ==(Rhs) + && ChildrenIndices == Rhs.ChildrenIndices; + } + bool operator!=(const FWwiseRefSwitchContainer& Rhs) const { return !operator==(Rhs); } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefSwitchGroup.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefSwitchGroup.h new file mode 100644 index 0000000..e1fdca6 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefSwitchGroup.h @@ -0,0 +1,59 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefSoundBank.h" + +class WWISEPROJECTDATABASE_API FWwiseRefSwitchGroup : public FWwiseRefSoundBank +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::SwitchGroup; + struct FGlobalIdsMap; + + WwiseRefIndexType SwitchGroupIndex; + + FWwiseRefSwitchGroup() {} + FWwiseRefSwitchGroup(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath, + WwiseRefIndexType InSoundBankIndex, uint32 InLanguageId, + WwiseRefIndexType InSwitchGroupIndex) : + FWwiseRefSoundBank(InRootMediaRef, InJsonFilePath, InSoundBankIndex, InLanguageId), + SwitchGroupIndex(InSwitchGroupIndex) + {} + const FWwiseMetadataSwitchGroup* GetSwitchGroup() const; + bool IsControlledByGameParameter() const; + + uint32 SwitchGroupId() const; + FGuid SwitchGroupGuid() const; + FName SwitchGroupName() const; + FName SwitchGroupObjectPath() const; + + uint32 Hash() const override; + EWwiseRefType Type() const override { return TYPE; } + bool operator==(const FWwiseRefSwitchGroup& Rhs) const + { + return FWwiseRefSoundBank::operator ==(Rhs) + && SwitchGroupIndex == Rhs.SwitchGroupIndex; + } + bool operator!=(const FWwiseRefSwitchGroup& Rhs) const { return !operator==(Rhs); } +}; + +struct WWISEPROJECTDATABASE_API FWwiseRefSwitchGroup::FGlobalIdsMap +{ + WwiseSwitchGroupGlobalIdsMap GlobalIdsMap; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefTrigger.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefTrigger.h new file mode 100644 index 0000000..2ddf636 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefTrigger.h @@ -0,0 +1,58 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Ref/WwiseRefSoundBank.h" + +class WWISEPROJECTDATABASE_API FWwiseRefTrigger : public FWwiseRefSoundBank +{ +public: + static const TCHAR* const NAME; + static constexpr EWwiseRefType TYPE = EWwiseRefType::Trigger; + struct FGlobalIdsMap; + + WwiseRefIndexType TriggerIndex; + + FWwiseRefTrigger() {} + FWwiseRefTrigger(const WwiseMetadataSharedRootFileConstPtr& InRootMediaRef, const FName& InJsonFilePath, + WwiseRefIndexType InSoundBankIndex, uint32 InLanguageId, + WwiseRefIndexType InTriggerIndex) : + FWwiseRefSoundBank(InRootMediaRef, InJsonFilePath, InSoundBankIndex, InLanguageId), + TriggerIndex(InTriggerIndex) + {} + const FWwiseMetadataTrigger* GetTrigger() const; + + uint32 TriggerId() const; + FGuid TriggerGuid() const; + FName TriggerName() const; + FName TriggerObjectPath() const; + + uint32 Hash() const override; + EWwiseRefType Type() const override { return TYPE; } + bool operator==(const FWwiseRefTrigger& Rhs) const + { + return FWwiseRefSoundBank::operator ==(Rhs) + && TriggerIndex == Rhs.TriggerIndex; + } + bool operator!=(const FWwiseRefTrigger& Rhs) const { return !operator==(Rhs); } +}; + +struct WWISEPROJECTDATABASE_API FWwiseRefTrigger::FGlobalIdsMap +{ + WwiseTriggerGlobalIdsMap GlobalIdsMap; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefType.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefType.h new file mode 100644 index 0000000..288799c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Ref/WwiseRefType.h @@ -0,0 +1,60 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" + +UENUM() +enum class EWwiseRefType : uint32 +{ + RootFile, + + ProjectInfo, + Language, + + PlatformInfo, + Platform, + + PluginInfo, + PluginLib, + + SoundBanksInfo, + SoundBank, + Media, + CustomPlugin, + PluginShareSet, + AudioDevice, + Event, + SwitchContainer, + DialogueEvent, + DialogueArgument, + Bus, + AuxBus, + GameParameter, + StateGroup, + State, + SwitchGroup, + Switch, + Trigger, + ExternalSource, + AcousticTexture, + + None = (uint32)-1 +}; + +using WwiseRefIndexType = int32; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Stats/ProjectDatabase.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Stats/ProjectDatabase.h new file mode 100644 index 0000000..bde3dd6 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/Stats/ProjectDatabase.h @@ -0,0 +1,35 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Stats/Stats.h" +#include "Wwise/Stats/NamedEvents.h" + +DECLARE_STATS_GROUP(TEXT("WwiseProjectDatabase"), STATGROUP_WwiseProjectDatabase, STATCAT_Wwise); +DECLARE_MEMORY_STAT_EXTERN(TEXT("Memory"), STAT_WwiseProjectDatabaseMemory, STATGROUP_WwiseProjectDatabase, WWISEPROJECTDATABASE_API); + +WWISEPROJECTDATABASE_API DECLARE_LOG_CATEGORY_EXTERN(LogWwiseProjectDatabase, Log, All); + +#define SCOPED_WWISEPROJECTDATABASE_EVENT(Text) SCOPED_WWISE_NAMED_EVENT(TEXT("WwiseProjectDatabase"), Text) +#define SCOPED_WWISEPROJECTDATABASE_EVENT_2(Text) SCOPED_WWISE_NAMED_EVENT_2(TEXT("WwiseProjectDatabase"), Text) +#define SCOPED_WWISEPROJECTDATABASE_EVENT_3(Text) SCOPED_WWISE_NAMED_EVENT_3(TEXT("WwiseProjectDatabase"), Text) +#define SCOPED_WWISEPROJECTDATABASE_EVENT_4(Text) SCOPED_WWISE_NAMED_EVENT_4(TEXT("WwiseProjectDatabase"), Text) +#define SCOPED_WWISEPROJECTDATABASE_EVENT_F(Format, ...) SCOPED_WWISE_NAMED_EVENT_F(TEXT("WwiseProjectDatabase"), Format, __VA_ARGS__) +#define SCOPED_WWISEPROJECTDATABASE_EVENT_F_2(Format, ...) SCOPED_WWISE_NAMED_EVENT_F_2(TEXT("WwiseProjectDatabase"), Format, __VA_ARGS__) +#define SCOPED_WWISEPROJECTDATABASE_EVENT_F_3(Format, ...) SCOPED_WWISE_NAMED_EVENT_F_3(TEXT("WwiseProjectDatabase"), Format, __VA_ARGS__) +#define SCOPED_WWISEPROJECTDATABASE_EVENT_F_4(Format, ...) SCOPED_WWISE_NAMED_EVENT_F_4(TEXT("WwiseProjectDatabase"), Format, __VA_ARGS__) diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseDataStructure.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseDataStructure.h new file mode 100644 index 0000000..fb6300c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseDataStructure.h @@ -0,0 +1,719 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseGeneratedFiles.h" +#include "Wwise/Stats/ProjectDatabase.h" +#include "Wwise/WwiseSharedLanguageId.h" + +#include "Wwise/Info/WwiseObjectInfo.h" +#include "Wwise/Info/WwiseEventInfo.h" +#include "Wwise/Info/WwiseGroupValueInfo.h" +#include "Wwise/Metadata/WwiseMetadataSoundBank.h" +#include "Wwise/Ref/WwiseRefAcousticTexture.h" +#include "Wwise/Ref/WwiseAnyRef.h" +#include "Wwise/Ref/WwiseRefAudioDevice.h" +#include "Wwise/Ref/WwiseRefAuxBus.h" +#include "Wwise/Ref/WwiseRefBus.h" +#include "Wwise/Ref/WwiseRefCustomPlugin.h" +#include "Wwise/Ref/WwiseRefDialogueArgument.h" +#include "Wwise/Ref/WwiseRefExternalSource.h" +#include "Wwise/Ref/WwiseRefGameParameter.h" +#include "Wwise/Ref/WwiseRefLanguage.h" +#include "Wwise/Ref/WwiseRefMedia.h" +#include "Wwise/Ref/WwiseRefPlatform.h" +#include "Wwise/Ref/WwiseRefPluginLib.h" +#include "Wwise/Ref/WwiseRefPluginShareSet.h" +#include "Wwise/Ref/WwiseRefSoundBank.h" +#include "Wwise/Ref/WwiseRefState.h" +#include "Wwise/Ref/WwiseRefSwitch.h" +#include "Wwise/Ref/WwiseRefSwitchContainer.h" +#include "Wwise/Ref/WwiseRefTrigger.h" + +#include "Engine/EngineTypes.h" +#include "UObject/SoftObjectPath.h" + +#include + +struct WWISEPROJECTDATABASE_API FWwiseRootDataStructure +{ + FWwiseGeneratedFiles::FGeneratedRootFiles GeneratedRootFiles; + WwiseMetadataFileMap JsonFiles; + + WwiseLanguageNamesMap LanguageNames; + WwiseLanguageIdsMap LanguageIds; + WwisePlatformNamesMap PlatformNames; + WwisePlatformGuidsMap PlatformGuids; + + TSet Languages; + TSet Platforms; + + FWwiseRootDataStructure() {} + FWwiseRootDataStructure(WwiseMetadataFileMap&& JsonFiles); + FWwiseRootDataStructure& operator+=(FWwiseRootDataStructure&& Rhs); + + uint32 GetLanguageId(const FName& Name) const + { + if (const auto* Language = LanguageNames.Find(Name)) + { + return Language->LanguageId(); + } + return FWwiseDatabaseLocalizableIdKey::GENERIC_LANGUAGE; + } +}; + +struct WWISEPROJECTDATABASE_API FWwisePlatformDataStructure : + private FWwiseRefAcousticTexture::FGlobalIdsMap, + private FWwiseRefAudioDevice::FGlobalIdsMap, + private FWwiseRefAuxBus::FGlobalIdsMap, + private FWwiseRefBus::FGlobalIdsMap, + private FWwiseRefCustomPlugin::FGlobalIdsMap, + private FWwiseRefDialogueArgument::FGlobalIdsMap, + private FWwiseRefDialogueEvent::FGlobalIdsMap, + private FWwiseRefEvent::FGlobalIdsMap, + private FWwiseRefExternalSource::FGlobalIdsMap, + private FWwiseRefGameParameter::FGlobalIdsMap, + private FWwiseRefMedia::FGlobalIdsMap, + private FWwiseRefPluginLib::FGlobalIdsMap, + private FWwiseRefPluginShareSet::FGlobalIdsMap, + private FWwiseRefSoundBank::FGlobalIdsMap, + private FWwiseRefState::FGlobalIdsMap, + private FWwiseRefStateGroup::FGlobalIdsMap, + private FWwiseRefSwitch::FGlobalIdsMap, + private FWwiseRefSwitchGroup::FGlobalIdsMap, + private FWwiseRefTrigger::FGlobalIdsMap +{ + FWwiseSharedPlatformId Platform; + FWwiseRefPlatform PlatformRef; + + FWwiseGeneratedFiles::FPlatformFiles GeneratedPlatformFiles; + WwiseMetadataFileMap JsonFiles; + + WwiseAcousticTextureGlobalIdsMap& AcousticTextures; + WwiseAudioDeviceGlobalIdsMap& AudioDevices; + WwiseAuxBusGlobalIdsMap& AuxBusses; + WwiseBusGlobalIdsMap& Busses; + WwiseCustomPluginGlobalIdsMap& CustomPlugins; + WwiseDialogueArgumentGlobalIdsMap& DialogueArguments; + WwiseDialogueEventGlobalIdsMap& DialogueEvents; + WwiseEventGlobalIdsMap& Events; + WwiseExternalSourceGlobalIdsMap& ExternalSources; + WwiseGameParameterGlobalIdsMap& GameParameters; + WwiseMediaGlobalIdsMap& MediaFiles; + WwisePluginLibGlobalIdsMap& PluginLibs; + WwisePluginShareSetGlobalIdsMap& PluginShareSets; + WwiseSoundBankGlobalIdsMap& SoundBanks; + WwiseStateGlobalIdsMap& States; + WwiseStateGroupGlobalIdsMap& StateGroups; + WwiseSwitchGlobalIdsMap& Switches; + WwiseSwitchGroupGlobalIdsMap& SwitchGroups; + WwiseTriggerGlobalIdsMap& Triggers; + + WwisePluginLibNamesMap PluginLibNames; + WwiseSwitchContainersByEvent SwitchContainersByEvent; + WwiseGuidMap Guids; + WwiseNameMap Names; + + FWwisePlatformDataStructure(); + FWwisePlatformDataStructure(const FWwiseSharedPlatformId& InPlatform, FWwiseRootDataStructure& InRootData, WwiseMetadataFileMap&& InJsonFiles); + FWwisePlatformDataStructure(const FWwisePlatformDataStructure& Rhs); + FWwisePlatformDataStructure(FWwisePlatformDataStructure&& Rhs); + FWwisePlatformDataStructure& operator+=(FWwisePlatformDataStructure&& Rhs); + + template + void GetRefMap(TMap& OutRefMap, const TSet& InLanguages, const FWwiseObjectInfo& InInfo) const; + + template + void GetRefMap(TMap>& OutRefMap, const TSet& InLanguages, const FWwiseObjectInfo& InInfo) const; + + template + bool GetRef(TSet& OutRef, const FWwiseSharedLanguageId& InLanguage, const FWwiseObjectInfo& InInfo) const; + + template + bool GetRef(RequiredRef& OutRef, const FWwiseSharedLanguageId& InLanguage, const FWwiseObjectInfo& InInfo) const; + + template + bool GetRef(RequiredRef& OutRef, const FWwiseSharedLanguageId& InLanguage, const FWwiseGroupValueInfo& InInfo) const; + + template + static bool GetLocalizableRef(RequiredRef& OutRef, const TMap& InGlobalMap, + uint32 InShortId, uint32 InLanguageId, uint32 InSoundBankId, const TCHAR* InDebugName); + + template + static bool GetLocalizableGroupRef(RequiredRef& OutRef, const TMap& InGlobalMap, + FWwiseDatabaseGroupValueKey InGroupValue, uint32 InLanguageId, uint32 InSoundBankId, const TCHAR* InDebugName); + + template + static void GetLocalizableRefs(TArray& OutRefs, const TMap& InGlobalMap, + uint32 InShortId, uint32 InLanguageId, uint32 InSoundBankId, const TCHAR* InDebugName); + + template + bool GetFromId(RefType& OutRef, uint32 InId, uint32 InLanguageId = 0, uint32 InSoundBankId = 0) const + { + return GetLocalizableRef(OutRef, RefType::FGlobalIdsMap::GlobalIdsMap, InId, InLanguageId, InSoundBankId, RefType::NAME); + } + + template + bool GetFromId(RefType& OutRef, FWwiseDatabaseGroupValueKey InId, uint32 InLanguageId = 0, uint32 InSoundBankId = 0) const + { + return GetLocalizableGroupRef(OutRef, RefType::FGlobalIdsMap::GlobalIdsMap, InId, InLanguageId, InSoundBankId, RefType::NAME); + } + template + bool GetFromId(TSet& OutRef, uint32 InId, uint32 InLanguageId, uint32 InSoundBankId) const; + + bool GetFromId(FWwiseRefMedia& OutRef, uint32 InShortId, uint32 InLanguageId, uint32 InSoundBankId) const; + + template + void AddBasicRefToMap(TMap& OutMap, const RequiredRef& InRef, const FWwiseMetadataBasicReference& InObject); + + template + void AddEventRefToMap(TMap& OutMap, const RequiredRef& InRef, const FWwiseMetadataBasicReference& InObject); + + template + void AddRefToMap(TMap& OutMap, const RequiredRef& InRef, const uint32& InId, const FName* InName, const FName* InObjectPath, const FGuid* InGuid); + +private: + FWwisePlatformDataStructure& operator=(const FWwisePlatformDataStructure& Rhs) = delete; +}; + +struct WWISEPROJECTDATABASE_API FWwiseDataStructure +{ + FRWLock Lock; + + FWwiseRootDataStructure RootData; + TMap Platforms; + + FWwiseDataStructure() {} + FWwiseDataStructure(const FDirectoryPath& InDirectoryPath, const FName* InPlatform = nullptr, const FGuid* InBasePlatformGuid = nullptr); + ~FWwiseDataStructure(); + + FWwiseDataStructure& operator+=(FWwiseDataStructure&& Rhs); + FWwiseDataStructure& operator=(FWwiseDataStructure&& Rhs) + { + RootData = MoveTemp(Rhs.RootData); + Platforms = MoveTemp(Rhs.Platforms); + return *this; + } + +private: + FWwiseDataStructure(const FWwiseDataStructure& other) = delete; + FWwiseDataStructure& operator=(const FWwiseDataStructure& other) = delete; + void LoadDataStructure(FWwiseGeneratedFiles&& Directory); +}; + + +template +inline void FWwisePlatformDataStructure::GetRefMap(TMap& OutRefMap, const TSet& InLanguages, const FWwiseObjectInfo& InInfo) const +{ + OutRefMap.Empty(InLanguages.Num()); + for (const auto& Language : InLanguages) + { + RequiredRef Ref; + if (GetRef(Ref, Language, InInfo)) + { + OutRefMap.Add(Language, Ref); + } + } +} + +template +inline void FWwisePlatformDataStructure::GetRefMap(TMap>& OutRefMap, const TSet& InLanguages, const FWwiseObjectInfo& InInfo) const +{ + OutRefMap.Empty(InLanguages.Num()); + for (const auto& Language : InLanguages) + { + TSet Refs; + if (GetRef(Refs, Language, InInfo)) + { + if (Refs.Num() > 1) + { + UE_LOG(LogWwiseProjectDatabase,Log, TEXT("More than one ref per language found in %s (%s %" PRIu32 ")"), RequiredRef::NAME, *InInfo.WwiseName.ToString(), InInfo.WwiseShortId); + } + OutRefMap.Add(Language, Refs); + } + } +} + +template +bool FWwisePlatformDataStructure::GetRef(TSet& OutRef, const FWwiseSharedLanguageId& InLanguage, const FWwiseObjectInfo& InInfo) const +{ + const auto LanguageId = InLanguage.GetLanguageId(); + + // Get from GUID + bool bResult = false; + + if (InInfo.WwiseGuid.IsValid()) + { + TArray Results; + if (LanguageId != 0) + { + Guids.MultiFindPointer(FWwiseDatabaseLocalizableGuidKey(InInfo.WwiseGuid, LanguageId), Results, false); + } + Guids.MultiFindPointer(FWwiseDatabaseLocalizableGuidKey(InInfo.WwiseGuid, 0), Results, false); + if (LIKELY(Results.Num() > 0)) + { + for (const auto* Any : Results) + { + RequiredRef Result; + if (LIKELY(Any->GetRef(Result))) + { + bool bAlreadyInSet = OutRef.Find(Result) != nullptr; + if (LIKELY(!bAlreadyInSet)) + { + if (InInfo.HardCodedSoundBankShortId == 0 || InInfo.HardCodedSoundBankShortId == Result.SoundBankId()) + { + OutRef.Add(Result, &bAlreadyInSet); + if (UNLIKELY(InInfo.WwiseName.IsNone())) + { + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Name not set while retrieving Wwise Object GUID %s: Should be %s or %s."), + *InInfo.WwiseGuid.ToString(), *Any->GetName().ToString(), *Any->GetObjectPath().ToString()); + } + else if (UNLIKELY(InInfo.WwiseName != Any->GetName() && InInfo.WwiseName != Any->GetObjectPath())) + { + UE_LOG(LogWwiseProjectDatabase, Log, TEXT("Different name while retrieving Wwise Object GUID %s (%s): Should be %s or %s."), + *InInfo.WwiseGuid.ToString(), *InInfo.WwiseName.ToString(), *Any->GetName().ToString(), *Any->GetObjectPath().ToString()); + } + if (UNLIKELY(InInfo.WwiseShortId == 0)) + { + UE_LOG(LogWwiseProjectDatabase, Log, TEXT("Id not set while retrieving Wwise Object GUID %s: Should be %" PRIu32 "."), + *InInfo.WwiseGuid.ToString(), Any->GetId()); + } + else if (UNLIKELY(InInfo.WwiseShortId != Any->GetId())) + { + UE_LOG(LogWwiseProjectDatabase, Log, TEXT("Different Id while retrieving Wwise Object GUID %s (%" PRIu32 "): Should be %" PRIu32 "."), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, Any->GetId()); + } + } + } + + bResult |= !bAlreadyInSet; + } + } + return bResult; + } + } + + // Get from Short ID + if (InInfo.WwiseShortId != 0) + { + auto OldOutRefNum = OutRef.Num(); + if (GetFromId(OutRef, InInfo.WwiseShortId, InLanguage.GetLanguageId(), InInfo.HardCodedSoundBankShortId)) + { + return OldOutRefNum != OutRef.Num(); + } + } + + // Get from Name. Try all found assets with such name until we get one + if (!InInfo.WwiseName.IsNone()) + { + FWwiseDatabaseLocalizableNameKey LocalizableName(InInfo.WwiseName, InLanguage.GetLanguageId()); + TArray Results; + if (LanguageId != 0) + { + Names.MultiFindPointer(FWwiseDatabaseLocalizableNameKey(InInfo.WwiseName, 0), Results); + } + Names.MultiFindPointer(FWwiseDatabaseLocalizableNameKey(InInfo.WwiseName, LanguageId), Results); + for (const auto *Any : Results) + { + RequiredRef Result; + if (LIKELY(Any->GetRef(Result))) + { + bool bAlreadyInSet = OutRef.Find(Result) != nullptr; + if (LIKELY(!bAlreadyInSet)) + { + OutRef.Add(Result, &bAlreadyInSet); + if (UNLIKELY(InInfo.WwiseName.IsNone())) + { + UE_LOG(LogWwiseProjectDatabase, Verbose, TEXT("Name not set while retrieving Wwise Object GUID %s: Should be %s or %s."), + *InInfo.WwiseGuid.ToString(), *Any->GetName().ToString(), *Any->GetObjectPath().ToString()); + } + else if (UNLIKELY(InInfo.WwiseName != Any->GetName() && InInfo.WwiseName != Any->GetObjectPath())) + { + UE_LOG(LogWwiseProjectDatabase, Log, TEXT("Different name while retrieving Wwise Object GUID %s (%s): Should be %s or %s."), + *InInfo.WwiseGuid.ToString(), *InInfo.WwiseName.ToString(), *Any->GetName().ToString(), *Any->GetObjectPath().ToString()); + } + if (UNLIKELY(InInfo.WwiseShortId == 0)) + { + UE_LOG(LogWwiseProjectDatabase, Log, TEXT("Id not set while retrieving Wwise Object GUID %s: Should be %" PRIu32 "."), + *InInfo.WwiseGuid.ToString(), Any->GetId()); + } + else if (UNLIKELY(InInfo.WwiseShortId != Any->GetId())) + { + UE_LOG(LogWwiseProjectDatabase, Log, TEXT("Different Id while retrieving Wwise Object GUID %s (%" PRIu32 "): Should be %" PRIu32 "."), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, Any->GetId()); + } + } + bResult |= !bAlreadyInSet; + } + return bResult; + } + } + return false; +} + +template +inline bool FWwisePlatformDataStructure::GetRef(RequiredRef& OutRef, const FWwiseSharedLanguageId& InLanguage, const FWwiseObjectInfo& InInfo) const +{ + const auto LanguageId = InLanguage.GetLanguageId(); + + // Get from GUID + if (InInfo.WwiseGuid.IsValid()) + { + FWwiseDatabaseLocalizableGuidKey LocalizableGuid(InInfo.WwiseGuid, LanguageId); + const auto *AssetFromGuid = Guids.Find(LocalizableGuid); + if (LIKELY(AssetFromGuid)) + { + return AssetFromGuid->GetRef(OutRef); + } + + if (LIKELY(LanguageId != 0)) + { + LocalizableGuid = FWwiseDatabaseLocalizableGuidKey(InInfo.WwiseGuid, 0); + AssetFromGuid = Guids.Find(LocalizableGuid); + if (LIKELY(AssetFromGuid)) + { + return AssetFromGuid->GetRef(OutRef); + } + } + } + + // Get from Short ID + if (InInfo.WwiseShortId != 0) + { + if (GetFromId(OutRef, InInfo.WwiseShortId, LanguageId, InInfo.HardCodedSoundBankShortId)) + { + return true; + } + } + + // Get from Name. Try all found assets with such name until we get one + if (!InInfo.WwiseName.IsNone()) + { + TArray FoundAssets; + Names.MultiFindPointer(FWwiseDatabaseLocalizableNameKey(InInfo.WwiseName, LanguageId), FoundAssets); + if (LIKELY(LanguageId != 0)) + { + Names.MultiFindPointer(FWwiseDatabaseLocalizableNameKey(InInfo.WwiseName, 0), FoundAssets); + } + for (const auto* Asset : FoundAssets) + { + if (Asset->GetRef(OutRef)) + { + return true; + } + } + } + return false; +} + +template +inline bool FWwisePlatformDataStructure::GetRef(RequiredRef& OutRef, const FWwiseSharedLanguageId& InLanguage, const FWwiseGroupValueInfo& InInfo) const +{ + const auto LanguageId = InLanguage.GetLanguageId(); + + // Get from GUID + if (InInfo.WwiseGuid.IsValid()) + { + FWwiseDatabaseLocalizableGuidKey LocalizableGuid(InInfo.WwiseGuid, LanguageId); + const auto *AssetFromGuid = Guids.Find(LocalizableGuid); + if (LIKELY(AssetFromGuid)) + { + return AssetFromGuid->GetRef(OutRef); + } + + if (LIKELY(LanguageId != 0)) + { + LocalizableGuid = FWwiseDatabaseLocalizableGuidKey(InInfo.WwiseGuid, 0); + AssetFromGuid = Guids.Find(LocalizableGuid); + if (LIKELY(AssetFromGuid)) + { + return AssetFromGuid->GetRef(OutRef); + } + } + } + + // Get from Short ID + if (InInfo.WwiseShortId != 0) + { + if (GetFromId(OutRef, FWwiseDatabaseGroupValueKey(InInfo.GroupShortId, InInfo.WwiseShortId), InLanguage.GetLanguageId(), 0)) + { + return true; + } + } + + // Get from Name. Try all found assets with such name until we get one + if (!InInfo.WwiseName.IsNone()) + { + FWwiseDatabaseLocalizableNameKey LocalizableName(InInfo.WwiseName, InLanguage.GetLanguageId()); + TArray FoundAssets; + Names.MultiFindPointer(FWwiseDatabaseLocalizableNameKey(InInfo.WwiseName, LanguageId), FoundAssets); + if (LIKELY(LanguageId != 0)) + { + Names.MultiFindPointer(FWwiseDatabaseLocalizableNameKey(InInfo.WwiseName, 0), FoundAssets); + } + for (const auto *Asset : FoundAssets) + { + if (Asset->GetRef(OutRef)) + { + return true; + } + } + } + return false; +} + +template +inline bool FWwisePlatformDataStructure::GetLocalizableRef(RequiredRef & OutRef, const TMap &InGlobalMap, + uint32 InShortId, uint32 InLanguageId, uint32 InSoundBankId, const TCHAR *InDebugName) +{ + const RequiredRef* Result = nullptr; + if (LIKELY(InLanguageId != 0)) + { + FWwiseDatabaseLocalizableIdKey LocalizableId(InShortId, InLanguageId); + Result = InGlobalMap.Find(LocalizableId); + + if (!Result) + { + FWwiseDatabaseLocalizableIdKey NoLanguageId(InShortId, FWwiseDatabaseLocalizableIdKey::GENERIC_LANGUAGE); + Result = InGlobalMap.Find(NoLanguageId); + } + } + else + { + for (const auto& Elem : InGlobalMap) + { + if (Elem.Key.Id == InShortId) + { + Result = &Elem.Value; + break; + } + } + } + + if (UNLIKELY(!Result)) + { + return false; + } + + if (InSoundBankId != 0) + { + const FWwiseMetadataSoundBank* SoundBank = Result->GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not retrieve SoundBank for %s %" PRIu32 " (Lang=%" PRIu32 "; SB=%" PRIu32 ")"), InDebugName, InShortId, InLanguageId, InSoundBankId); + return false; + } + if (UNLIKELY(SoundBank->Id != InSoundBankId)) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Get incorrect SoundBank %" PRIu32 " for %s %" PRIu32 " (Lang = %" PRIu32 "; SB = %" PRIu32 ")"), SoundBank->Id, InDebugName, InShortId, InLanguageId, InSoundBankId); + return false; + } + } + + OutRef = *Result; + return true; +} + +template +void FWwisePlatformDataStructure::GetLocalizableRefs(TArray& OutRefs, + const TMap& InGlobalMap, uint32 InShortId, uint32 InLanguageId, + uint32 InSoundBankId, const TCHAR* InDebugName) +{ + if (LIKELY(InLanguageId != 0 && InSoundBankId != 0)) + { + const RequiredRef* Result = nullptr; + FWwiseDatabaseLocalizableIdKey RefId(InShortId, InLanguageId, InSoundBankId); + Result = InGlobalMap.Find(RefId); + + if (!Result) + { + FWwiseDatabaseLocalizableIdKey NoLanguageId(InShortId, FWwiseDatabaseLocalizableIdKey::GENERIC_LANGUAGE, InSoundBankId); + Result = InGlobalMap.Find(NoLanguageId); + } + + if (Result) + { + OutRefs.Add(*Result); + } + } + else + { + for (const auto& Elem : InGlobalMap) + { + if (UNLIKELY(Elem.Key.Id == InShortId) + && (InLanguageId == 0 || Elem.Key.LanguageId == InLanguageId || Elem.Key.LanguageId == 0) + && (InSoundBankId == 0 || Elem.Key.SoundBankId == InSoundBankId)) + { + OutRefs.Add(Elem.Value); + } + } + } +} + +template <> +inline bool FWwisePlatformDataStructure::GetLocalizableRef(FWwiseRefPluginLib& OutRef, const TMap& InGlobalMap, + uint32 InShortId, uint32 InLanguageId, uint32 InSoundBankId, const TCHAR* InDebugName) +{ + const FWwiseRefPluginLib* Result = nullptr; + if (LIKELY(InLanguageId != 0)) + { + FWwiseDatabaseLocalizableIdKey LocalizableId(InShortId, InLanguageId); + Result = InGlobalMap.Find(LocalizableId); + + if (!Result) + { + FWwiseDatabaseLocalizableIdKey NoLanguageId(InShortId, FWwiseDatabaseLocalizableIdKey::GENERIC_LANGUAGE); + Result = InGlobalMap.Find(NoLanguageId); + } + } + else + { + for (const auto& Elem : InGlobalMap) + { + if (Elem.Key.Id == InShortId) + { + Result = &Elem.Value; + break; + } + } + } + + if (UNLIKELY(!Result)) + { + return false; + } + + OutRef = *Result; + return true; +} + +template +inline bool FWwisePlatformDataStructure::GetLocalizableGroupRef(RequiredRef& OutRef, const TMap& InGlobalMap, + FWwiseDatabaseGroupValueKey InGroupValue, uint32 InLanguageId, uint32 InSoundBankId, const TCHAR* InDebugName) +{ + const RequiredRef* Result = nullptr; + if (LIKELY(InLanguageId != 0)) + { + FWwiseDatabaseLocalizableGroupValueKey LocalizableGroupValue(InGroupValue, InLanguageId); + Result = InGlobalMap.Find(LocalizableGroupValue); + + if (!Result) + { + FWwiseDatabaseLocalizableGroupValueKey NoLanguageId(InGroupValue, FWwiseDatabaseLocalizableIdKey::GENERIC_LANGUAGE); + Result = InGlobalMap.Find(NoLanguageId); + } + } + else + { + for (const auto& Elem : InGlobalMap) + { + if (Elem.Key.GroupValue == InGroupValue) + { + Result = &Elem.Value; + break; + } + } + } + + if (UNLIKELY(!Result)) + { + return false; + } + + if (InSoundBankId != 0) + { + const FWwiseMetadataSoundBank* SoundBank = Result->GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Could not retrieve SoundBank for %s %" PRIu32 " %" PRIu32 " (Lang = %" PRIu32 "; SB = %" PRIu32 ")"), InDebugName, InGroupValue.GroupId, InGroupValue.Id, InLanguageId, InSoundBankId); + return false; + } + if (UNLIKELY(SoundBank->Id != InSoundBankId)) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("Get incorrect SoundBank %" PRIu32 " for %s %" PRIu32 " %" PRIu32 " (Lang = %" PRIu32 "; SB = %" PRIu32 ")"), SoundBank->Id, InDebugName, InGroupValue.GroupId, InGroupValue.Id, InLanguageId, InSoundBankId); + return false; + } + } + + OutRef = *Result; + return true; +} + + +template +inline bool FWwisePlatformDataStructure::GetFromId(TSet& OutRef, uint32 InId, uint32 InLanguageId, uint32 InSoundBankId) const +{ + TArray Refs; + GetLocalizableRefs(Refs, RefType::FGlobalIdsMap::GlobalIdsMap, InId, InLanguageId, InSoundBankId, RefType::NAME); + if (LIKELY(Refs.Num() > 0)) + { + OutRef.Append(Refs); + return true; + } + return false; +} + +template +inline void FWwisePlatformDataStructure::AddBasicRefToMap(TMap& OutMap, const RequiredRef& InRef, const FWwiseMetadataBasicReference& InObject) +{ + AddRefToMap(OutMap, InRef, InObject.Id, &InObject.Name, &InObject.ObjectPath, &InObject.GUID); +} + +template +inline void FWwisePlatformDataStructure::AddEventRefToMap(TMap& OutMap, const RequiredRef& InRef, const FWwiseMetadataBasicReference& InObject) +{ + AddRefToMap(OutMap, InRef, InObject.Id, &InObject.Name, &InObject.ObjectPath, &InObject.GUID); +} + +template +void FWwisePlatformDataStructure::AddRefToMap(TMap& OutMap, const RequiredRef& InRef, const uint32& InId, const FName* InName, const FName* InObjectPath, const FGuid* InGuid) +{ + const auto AnyRef = FWwiseAnyRef::Create(InRef); + if (InName && !InName->IsNone()) + { + Names.Add(FWwiseDatabaseLocalizableNameKey(*InName, InRef.LanguageId), AnyRef); + } + if (InObjectPath && !InObjectPath->IsNone()) + { + Names.Add(FWwiseDatabaseLocalizableNameKey(*InObjectPath, InRef.LanguageId), AnyRef); + } + if (InGuid && InGuid->IsValid()) + { + Guids.Add(FWwiseDatabaseLocalizableGuidKey(*InGuid, InRef.LanguageId), AnyRef); + } + OutMap.Add(FWwiseDatabaseLocalizableIdKey(InId, InRef.LanguageId), InRef); +} + +template<> +inline void FWwisePlatformDataStructure::AddRefToMap(TMap& OutMap, const FWwiseRefPluginLib& InRef, const uint32& InId, const FName* InName, const FName* InObjectPath, const FGuid* InGuid) +{ + const auto AnyRef = FWwiseAnyRef::Create(InRef); + if (InName && !InName->IsNone()) + { + Names.Add(FWwiseDatabaseLocalizableNameKey(*InName, 0), AnyRef); + } + if (InObjectPath && !InObjectPath->IsNone()) + { + Names.Add(FWwiseDatabaseLocalizableNameKey(*InObjectPath, 0), AnyRef); + } + if (InGuid && InGuid->IsValid()) + { + Guids.Add(FWwiseDatabaseLocalizableGuidKey(*InGuid, 0), AnyRef); + } + OutMap.Add(FWwiseDatabaseLocalizableIdKey(InId, 0), InRef); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseDatabaseIdentifiers.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseDatabaseIdentifiers.h new file mode 100644 index 0000000..53aa216 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseDatabaseIdentifiers.h @@ -0,0 +1,207 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" + +#include "WwiseDatabaseIdentifiers.generated.h" + +USTRUCT() +struct WWISEPROJECTDATABASE_API FWwiseDatabaseMediaIdKey +{ + GENERATED_BODY() + + UPROPERTY() uint32 MediaId = 0; + UPROPERTY() uint32 SoundBankId = 0; + + FWwiseDatabaseMediaIdKey() + {} + FWwiseDatabaseMediaIdKey(uint32 InMediaId, uint32 InSoundBankId) : + MediaId(InMediaId), + SoundBankId(InSoundBankId) + {} + bool operator==(const FWwiseDatabaseMediaIdKey& Rhs) const + { + return MediaId == Rhs.MediaId + && SoundBankId == Rhs.SoundBankId; + } + bool operator<(const FWwiseDatabaseMediaIdKey& Rhs) const + { + return (MediaId < Rhs.MediaId) + || (MediaId == Rhs.MediaId && SoundBankId < Rhs.SoundBankId); + } +}; + +USTRUCT() +struct WWISEPROJECTDATABASE_API FWwiseDatabaseLocalizableIdKey +{ + GENERATED_BODY() + + static constexpr uint32 GENERIC_LANGUAGE = 0; + + UPROPERTY() uint32 Id = 0; + UPROPERTY() uint32 LanguageId = 0; + UPROPERTY() uint32 SoundBankId = 0; + + FWwiseDatabaseLocalizableIdKey() + {} + FWwiseDatabaseLocalizableIdKey(uint32 InId, uint32 InLanguageId) : + Id(InId), + LanguageId(InLanguageId) + {} + FWwiseDatabaseLocalizableIdKey(uint32 InId, uint32 InLanguageId, uint32 InSoundBankId) : + Id(InId), + LanguageId(InLanguageId), + SoundBankId(InSoundBankId) + {} + bool operator==(const FWwiseDatabaseLocalizableIdKey& Rhs) const + { + return Id == Rhs.Id + && LanguageId == Rhs.LanguageId + && SoundBankId == Rhs.SoundBankId; + } + bool operator<(const FWwiseDatabaseLocalizableIdKey& Rhs) const + { + return (Id < Rhs.Id) + || (Id == Rhs.Id && LanguageId < Rhs.LanguageId) + || (Id == Rhs.Id && LanguageId == Rhs.LanguageId && SoundBankId < Rhs.SoundBankId); + } +}; + +USTRUCT() +struct WWISEPROJECTDATABASE_API FWwiseDatabaseGroupValueKey +{ + GENERATED_BODY() + + UPROPERTY() uint32 GroupId = 0; + UPROPERTY() uint32 Id = 0; + + FWwiseDatabaseGroupValueKey() + {} + FWwiseDatabaseGroupValueKey(uint32 InGroupId, uint32 InId) : + GroupId(InGroupId), + Id(InId) + {} + bool operator==(const FWwiseDatabaseGroupValueKey& Rhs) const + { + return GroupId == Rhs.GroupId + && Id == Rhs.Id; + } + bool operator<(const FWwiseDatabaseGroupValueKey& Rhs) const + { + return (GroupId < Rhs.GroupId) + || (GroupId == Rhs.GroupId && Id < Rhs.Id); + } +}; + +USTRUCT() +struct WWISEPROJECTDATABASE_API FWwiseDatabaseLocalizableGroupValueKey +{ + GENERATED_BODY() + + static constexpr uint32 GENERIC_LANGUAGE = 0; + + UPROPERTY() FWwiseDatabaseGroupValueKey GroupValue; + UPROPERTY() uint32 LanguageId = 0; + + FWwiseDatabaseLocalizableGroupValueKey() + {} + FWwiseDatabaseLocalizableGroupValueKey(uint32 InGroup, uint32 InId, uint32 InLanguageId) : + GroupValue(InGroup, InId), + LanguageId(InLanguageId) + {} + FWwiseDatabaseLocalizableGroupValueKey(FWwiseDatabaseGroupValueKey InGroupValue, uint32 InLanguageId) : + GroupValue(InGroupValue), + LanguageId(InLanguageId) + {} + bool operator==(const FWwiseDatabaseLocalizableGroupValueKey& Rhs) const + { + return GroupValue == Rhs.GroupValue + && LanguageId == Rhs.LanguageId; + } + bool operator<(const FWwiseDatabaseLocalizableGroupValueKey& Rhs) const + { + return (GroupValue < Rhs.GroupValue) + || (GroupValue == Rhs.GroupValue && LanguageId < Rhs.LanguageId); + } +}; + + +USTRUCT() +struct WWISEPROJECTDATABASE_API FWwiseDatabaseLocalizableGuidKey +{ + GENERATED_BODY() + + static constexpr uint32 GENERIC_LANGUAGE = FWwiseDatabaseLocalizableIdKey::GENERIC_LANGUAGE; + + UPROPERTY() FGuid Guid; + UPROPERTY() uint32 LanguageId = 0; // 0 if no Language + + FWwiseDatabaseLocalizableGuidKey() + {} + FWwiseDatabaseLocalizableGuidKey(FGuid InGuid, uint32 InLanguageId) : + Guid(InGuid), + LanguageId(InLanguageId) + {} + bool operator==(const FWwiseDatabaseLocalizableGuidKey& Rhs) const + { + return Guid == Rhs.Guid + && LanguageId == Rhs.LanguageId; + } + bool operator<(const FWwiseDatabaseLocalizableGuidKey& Rhs) const + { + return (Guid < Rhs.Guid) + || (Guid == Rhs.Guid && LanguageId < Rhs.LanguageId); + } +}; + +USTRUCT() +struct WWISEPROJECTDATABASE_API FWwiseDatabaseLocalizableNameKey +{ + GENERATED_BODY() + + static constexpr uint32 GENERIC_LANGUAGE = FWwiseDatabaseLocalizableIdKey::GENERIC_LANGUAGE; + + UPROPERTY() FName Name; + UPROPERTY() uint32 LanguageId = 0; // 0 if no Language + + FWwiseDatabaseLocalizableNameKey() + {} + FWwiseDatabaseLocalizableNameKey(FName InName, uint32 InLanguageId) : + Name(InName), + LanguageId(InLanguageId) + {} + bool operator==(const FWwiseDatabaseLocalizableNameKey& Rhs) const + { + return Name == Rhs.Name + && LanguageId == Rhs.LanguageId; + } + bool operator<(const FWwiseDatabaseLocalizableNameKey& Rhs) const + { + return (Name.FastLess(Rhs.Name)) + || (Name == Rhs.Name && LanguageId < Rhs.LanguageId); + } +}; + +uint32 WWISEPROJECTDATABASE_API GetTypeHash(const FWwiseDatabaseMediaIdKey& FileId); +uint32 WWISEPROJECTDATABASE_API GetTypeHash(const FWwiseDatabaseLocalizableIdKey& LocalizableId); +uint32 WWISEPROJECTDATABASE_API GetTypeHash(const FWwiseDatabaseGroupValueKey& LocalizableGroupValue); +uint32 WWISEPROJECTDATABASE_API GetTypeHash(const FWwiseDatabaseLocalizableGroupValueKey& LocalizableGroupValue); +uint32 WWISEPROJECTDATABASE_API GetTypeHash(const FWwiseDatabaseLocalizableIdKey& EventId); +uint32 WWISEPROJECTDATABASE_API GetTypeHash(const FWwiseDatabaseLocalizableGuidKey& LocalizableGuid); +uint32 WWISEPROJECTDATABASE_API GetTypeHash(const FWwiseDatabaseLocalizableNameKey& LocalizableName); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseGeneratedFiles.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseGeneratedFiles.h new file mode 100644 index 0000000..90dde03 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseGeneratedFiles.h @@ -0,0 +1,64 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Metadata/WwiseMetadataCollections.h" +#include "Wwise/WwiseSharedPlatformId.h" + +#include "Containers/Array.h" +#include "Containers/UnrealString.h" +#include "Containers/Map.h" + +struct WWISEPROJECTDATABASE_API FWwiseGeneratedFiles +{ + using FileTuple = TTuple; + using FileDateTimeMap = TMap; + + struct WWISEPROJECTDATABASE_API FGeneratedRootFiles + { + FileTuple ProjectInfoFile; + FileTuple WwiseIDsFile; + }; + + struct WWISEPROJECTDATABASE_API FPlatformFiles + { + FileTuple PlatformInfoFile; + FileTuple PluginInfoFile; + FileTuple SoundbanksInfoFile; + + FileDateTimeMap SoundBankFiles; + FileDateTimeMap MediaFiles; + FileDateTimeMap MetadataFiles; + FileDateTimeMap ExtraFiles; + + TArray DirectoriesToWatch; + TArray LanguageDirectories; + TArray AutoSoundBankDirectories; + FString MediaDirectory; + + bool IsValid() const; + + void Append(FPlatformFiles&& Rhs); + }; + + FGeneratedRootFiles GeneratedRootFiles; + TMap Platforms; + WwiseMetadataSharedRootFileConstPtr ProjectInfo; + + bool IsValid() const; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseProjectDatabase.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseProjectDatabase.h new file mode 100644 index 0000000..8d0a337 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseProjectDatabase.h @@ -0,0 +1,207 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseDataStructure.h" +#include "Wwise/WwiseResourceLoader.h" +#include "Wwise/WwiseProjectDatabaseModule.h" + +class FWwiseResourceLoader; +class FWwiseProjectDatabase; +using FSharedWwiseDataStructure = TSharedRef; + +class WWISEPROJECTDATABASE_API FWwiseDataStructureScopeLock : public FRWScopeLock +{ +public: + FWwiseDataStructureScopeLock(const FWwiseProjectDatabase& InProjectDatabase); + + const FWwiseDataStructure& operator*() const + { + return DataStructure; + } + + const FWwiseDataStructure* operator->() const + { + return &DataStructure; + } + + const WwiseAcousticTextureGlobalIdsMap& GetAcousticTextures() const; + FWwiseRefAcousticTexture GetAcousticTexture(const FWwiseObjectInfo& InInfo) const; + + const WwiseAudioDeviceGlobalIdsMap& GetAudioDevices() const; + FWwiseRefAudioDevice GetAudioDevice(const FWwiseObjectInfo& InInfo) const; + + const WwiseAuxBusGlobalIdsMap& GetAuxBusses() const; + FWwiseRefAuxBus GetAuxBus(const FWwiseObjectInfo& InInfo) const; + + const WwiseBusGlobalIdsMap& GetBusses() const; + FWwiseRefBus GetBus(const FWwiseObjectInfo& InInfo) const; + + const WwiseCustomPluginGlobalIdsMap& GetCustomPlugins() const; + FWwiseRefCustomPlugin GetCustomPlugin(const FWwiseObjectInfo& InInfo) const; + + const WwiseDialogueArgumentGlobalIdsMap& GetDialogueArguments() const; + FWwiseRefDialogueArgument GetDialogueArgument(const FWwiseObjectInfo& InInfo) const; + + const WwiseDialogueEventGlobalIdsMap& GetDialogueEvents() const; + FWwiseRefDialogueEvent GetDialogueEvent(const FWwiseObjectInfo& InInfo) const; + + const WwiseEventGlobalIdsMap& GetEvents() const; + TSet GetEvent(const FWwiseEventInfo& InInfo) const; + + const WwiseExternalSourceGlobalIdsMap& GetExternalSources() const; + FWwiseRefExternalSource GetExternalSource(const FWwiseObjectInfo& InInfo) const; + + const WwiseGameParameterGlobalIdsMap& GetGameParameters() const; + FWwiseRefGameParameter GetGameParameter(const FWwiseObjectInfo& InInfo) const; + + const WwiseMediaGlobalIdsMap& GetMediaFiles() const; + FWwiseRefMedia GetMediaFile(const FWwiseObjectInfo& InInfo) const; + + const WwisePluginLibGlobalIdsMap& GetPluginLibs() const; + FWwiseRefPluginLib GetPluginLib(const FWwiseObjectInfo& InInfo) const; + + const WwisePluginShareSetGlobalIdsMap& GetPluginShareSets() const; + FWwiseRefPluginShareSet GetPluginShareSet(const FWwiseObjectInfo& InInfo) const; + + const WwiseSoundBankGlobalIdsMap& GetSoundBanks() const; + FWwiseRefSoundBank GetSoundBank(const FWwiseObjectInfo& InInfo) const; + + const WwiseStateGlobalIdsMap& GetStates() const; + FWwiseRefState GetState(const FWwiseGroupValueInfo& InInfo) const; + + const WwiseStateGroupGlobalIdsMap& GetStateGroups() const; + FWwiseRefStateGroup GetStateGroup(const FWwiseObjectInfo& InInfo) const; + + const WwiseSwitchGlobalIdsMap& GetSwitches() const; + FWwiseRefSwitch GetSwitch(const FWwiseGroupValueInfo& InInfo) const; + + const WwiseSwitchGroupGlobalIdsMap& GetSwitchGroups() const; + FWwiseRefSwitchGroup GetSwitchGroup(const FWwiseObjectInfo& InInfo) const; + + const WwiseTriggerGlobalIdsMap& GetTriggers() const; + FWwiseRefTrigger GetTrigger(const FWwiseObjectInfo& InInfo) const; + + const TSet& GetLanguages() const; + const TSet& GetPlatforms() const; + FWwiseRefPlatform GetPlatform(const FWwiseSharedPlatformId& InPlatformId) const; + + const FWwisePlatformDataStructure* GetCurrentPlatformData() const; + + const FWwiseSharedLanguageId& GetCurrentLanguage() const { return CurrentLanguage; } + const FWwiseSharedPlatformId& GetCurrentPlatform() const { return CurrentPlatform; } + bool DisableDefaultPlatforms() const { return bDisableDefaultPlatforms; } + +private: + const FWwiseDataStructure& DataStructure; + + FWwiseSharedLanguageId CurrentLanguage; + FWwiseSharedPlatformId CurrentPlatform; + bool bDisableDefaultPlatforms; + + UE_NONCOPYABLE(FWwiseDataStructureScopeLock); +}; + +class WWISEPROJECTDATABASE_API FWwiseDataStructureWriteScopeLock : public FRWScopeLock +{ +public: + FWwiseDataStructureWriteScopeLock(FWwiseProjectDatabase& InProjectDatabase); + + FWwiseDataStructure& operator*() + { + return DataStructure; + } + + FWwiseDataStructure* operator->() + { + return &DataStructure; + } + +private: + FWwiseDataStructure& DataStructure; + UE_NONCOPYABLE(FWwiseDataStructureWriteScopeLock); +}; + +class WWISEPROJECTDATABASE_API FWwiseProjectDatabase +{ + friend class FWwiseDataStructureScopeLock; + friend class FWwiseDataStructureWriteScopeLock; + +public: + static const FGuid BasePlatformGuid; + + inline static FWwiseProjectDatabase* Get() + { + if (auto* Module = IWwiseProjectDatabaseModule::GetModule()) + { + return Module->GetProjectDatabase(); + } + return nullptr; + } + static FWwiseProjectDatabase* Instantiate() + { + if (auto* Module = IWwiseProjectDatabaseModule::GetModule()) + { + return Module->InstantiateProjectDatabase(); + } + return nullptr; + } + + + FWwiseProjectDatabase() {} + virtual ~FWwiseProjectDatabase() {} + + virtual void UpdateDataStructure( + const FDirectoryPath* InUpdateGeneratedSoundBanksPath = nullptr, + const FGuid* InBasePlatformGuid = &BasePlatformGuid) {} + + virtual void PrepareProjectDatabaseForPlatform(FWwiseResourceLoader*&& InResourceLoader) {} + virtual FWwiseResourceLoader* GetResourceLoader() { return nullptr; } + virtual const FWwiseResourceLoader* GetResourceLoader() const { return nullptr; } + + FWwiseSharedLanguageId GetCurrentLanguage() const; + FWwiseSharedPlatformId GetCurrentPlatform() const; + virtual bool IsProjectDatabaseParsed() const {return bIsDatabaseParsed;}; + + +protected: + virtual FSharedWwiseDataStructure& GetLockedDataStructure() { check(false); UE_ASSUME(false); } + virtual const FSharedWwiseDataStructure& GetLockedDataStructure() const { check(false); UE_ASSUME(false); } + + template + bool GetRef(RequiredRef& OutRef, const FWwiseObjectInfo& InInfo) + { + const auto* ResourceLoader = GetResourceLoader(); + check(ResourceLoader); + const auto& PlatformRef = ResourceLoader->GetCurrentPlatform(); + + const auto& DataStructure = *GetLockedDataStructure(); + + const auto* Platform = DataStructure.Platforms.Find(PlatformRef); + if (UNLIKELY(!Platform)) + { + UE_LOG(LogWwiseProjectDatabase, Error, TEXT("GetRef: Platform not found")); + return false; + } + + return Platform->GetRef(OutRef, InInfo); + } + + bool DisableDefaultPlatforms() const; + bool bIsDatabaseParsed = false; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseProjectDatabaseDelegates.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseProjectDatabaseDelegates.h new file mode 100644 index 0000000..41fc073 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseProjectDatabaseDelegates.h @@ -0,0 +1,38 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +DECLARE_MULTICAST_DELEGATE(FOnDatabaseUpdateCompletedDelegate); + +#define DEFINE_WWISE_DATABASE_DELEGATE(DelegateType) \ + public: F##DelegateType& Get##DelegateType() { return DelegateType; } \ + private: F##DelegateType DelegateType; + +class WWISEPROJECTDATABASE_API FWwiseProjectDatabaseDelegates +{ + + DEFINE_WWISE_DATABASE_DELEGATE(OnDatabaseUpdateCompletedDelegate); + +public: + static FWwiseProjectDatabaseDelegates& Get() + { + // return the singleton object + static FWwiseProjectDatabaseDelegates Singleton; + return Singleton; + } +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseProjectDatabaseImpl.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseProjectDatabaseImpl.h new file mode 100644 index 0000000..1c55b7c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseProjectDatabaseImpl.h @@ -0,0 +1,47 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseProjectDatabase.h" + +class FWwiseResourceLoader; +class FWwiseProjectDatabase; +using FSharedWwiseDataStructure = TSharedRef; + +class WWISEPROJECTDATABASE_API FWwiseProjectDatabaseImpl : public FWwiseProjectDatabase +{ +public: + FWwiseProjectDatabaseImpl(); + ~FWwiseProjectDatabaseImpl() override; + + TUniquePtr ResourceLoaderOverride; + + void UpdateDataStructure( + const FDirectoryPath* InUpdateGeneratedSoundBanksPath = nullptr, + const FGuid* InBasePlatformGuid = &BasePlatformGuid) override; + + void PrepareProjectDatabaseForPlatform(FWwiseResourceLoader*&& InResourceLoader); + FWwiseResourceLoader* GetResourceLoader() override; + const FWwiseResourceLoader* GetResourceLoader() const override; + +protected: + FSharedWwiseDataStructure LockedDataStructure; + + FSharedWwiseDataStructure& GetLockedDataStructure() override { return LockedDataStructure; } + const FSharedWwiseDataStructure& GetLockedDataStructure() const override { return LockedDataStructure; } +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseProjectDatabaseModule.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseProjectDatabaseModule.h new file mode 100644 index 0000000..0d0c78f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseProjectDatabaseModule.h @@ -0,0 +1,99 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Modules/ModuleManager.h" +#include "Misc/ConfigCacheIni.h" +#include "WwiseDefines.h" + +class FWwiseProjectDatabase; + +class IWwiseProjectDatabaseModule : public IModuleInterface +{ +public: + static FName GetModuleName() + { + static const FName ModuleName = GetModuleNameFromConfig(); + return ModuleName; + } + + /** + * Checks to see if this module is loaded and ready. + * + * @return True if the module is loaded and ready to use + */ + static bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded(GetModuleName()); + } + + static IWwiseProjectDatabaseModule* GetModule() + { + const auto ModuleName = GetModuleName(); + if (ModuleName.IsNone()) + { + return nullptr; + } + + FModuleManager& ModuleManager = FModuleManager::Get(); + IWwiseProjectDatabaseModule* Result = ModuleManager.GetModulePtr(ModuleName); + if (UNLIKELY(!Result)) + { + if (UNLIKELY(IsEngineExitRequested())) + { + UE_LOG(LogLoad, Verbose, TEXT("Skipping reloading missing WwiseProjectDatabase module: Exiting.")); + } + else if (UNLIKELY(!IsInGameThread())) + { + UE_LOG(LogLoad, Warning, TEXT("Skipping loading missing WwiseProjectDatabase module: Not in game thread")); + } + else + { + UE_LOG(LogLoad, Log, TEXT("Loading WwiseProjectDatabase module: %s"), *ModuleName.GetPlainNameString()); + Result = ModuleManager.LoadModulePtr(ModuleName); + if (UNLIKELY(!Result)) + { + UE_LOG(LogLoad, Fatal, TEXT("Could not load WwiseProjectDatabase module: %s not found"), *ModuleName.GetPlainNameString()); + } + } + } + + return Result; + } + + static bool IsInACookingCommandlet() + { +#if UE_5_0_OR_LATER + return ::IsRunningCookCommandlet(); +#else + return IsRunningCommandlet(); // UE4 Wwise Integration assumes all commandlets are loaded bare-bones +#endif + } + + virtual FWwiseProjectDatabase* GetProjectDatabase() { return nullptr; } + virtual FWwiseProjectDatabase* InstantiateProjectDatabase() { return nullptr; } + + +private: + static FName GetModuleNameFromConfig() + { + FString ModuleName = TEXT("WwiseProjectDatabase"); + GConfig->GetString(TEXT("Audio"), TEXT("WwiseProjectDatabaseModuleName"), ModuleName, GEngineIni); + return FName(ModuleName); + } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseProjectDatabaseModuleImpl.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseProjectDatabaseModuleImpl.h new file mode 100644 index 0000000..bd34483 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/Public/Wwise/WwiseProjectDatabaseModuleImpl.h @@ -0,0 +1,34 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseProjectDatabaseModule.h" +#include "Wwise/WwiseProjectDatabase.h" + +class WWISEPROJECTDATABASE_API FWwiseProjectDatabaseModule : public IWwiseProjectDatabaseModule +{ +public: + FWwiseProjectDatabase* GetProjectDatabase() override; + FWwiseProjectDatabase* InstantiateProjectDatabase() override; + + void ShutdownModule() override; + +protected: + FRWLock Lock; + TUniquePtr ProjectDatabase; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/WwiseProjectDatabase.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/WwiseProjectDatabase.Build.cs new file mode 100644 index 0000000..995f473 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseProjectDatabase/WwiseProjectDatabase.Build.cs @@ -0,0 +1,52 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; + +public class WwiseProjectDatabase : ModuleRules +{ + public WwiseProjectDatabase(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + + "EditorSubsystem", + "Json", + + "WwiseFileHandler", + "WwiseResourceLoader", + "WwiseSoundEngine" + } + ); + + if (Target.bBuildEditor) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "UnrealEd" + } + ); + } + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/Private/Wwise/Stats/Reconcile.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/Private/Wwise/Stats/Reconcile.cpp new file mode 100644 index 0000000..0f66b4e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/Private/Wwise/Stats/Reconcile.cpp @@ -0,0 +1,20 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Stats/Reconcile.h" + +DEFINE_LOG_CATEGORY(LogWwiseReconcile); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/Private/Wwise/WwiseReconcileCommandlet.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/Private/Wwise/WwiseReconcileCommandlet.cpp new file mode 100644 index 0000000..cbb666a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/Private/Wwise/WwiseReconcileCommandlet.cpp @@ -0,0 +1,544 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseReconcileCommandlet.h" + +#include "AkAudioEvent.h" +#include "AkAudioType.h" +#include "AkAuxBus.h" +#include "AkEffectShareSet.h" +#include "AkInitBank.h" +#include "AkSettings.h" +#include "AkStateValue.h" +#include "AkSwitchValue.h" +#include "AkTrigger.h" +#include "AkUnrealAssetDataHelper.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "AssetToolsModule.h" +#include "FileHelpers.h" +#include "ObjectTools.h" +#include "AkAssetFactories.h" +#include "PackageTools.h" +#include "Wwise/Stats/Reconcile.h" +#include "Wwise/WwiseReconcile.h" +#include "Wwise/WwiseProjectDatabase.h" + +static constexpr auto ModesParam = TEXT("modes"); +static constexpr auto CreateOption = TEXT("create"); +static constexpr auto UpdateOption = TEXT("update"); +static constexpr auto DeleteOption = TEXT("delete"); +static constexpr auto AllOption = TEXT("all"); +static constexpr auto HelpOption = TEXT("help"); + +UWwiseReconcileCommandlet::UWwiseReconcileCommandlet() +{ + IsClient = false; + IsEditor = true; + IsServer = false; + LogToConsole = true; + + HelpDescription = TEXT("Commandlet to generate Wwise SoundBanks."); + + HelpParamNames.Add(ModesParam); + HelpParamDescriptions.Add(FString::Format(TEXT("Comma separated list of operations to perform on assets.\n" + "{0}: Create Unreal assets from the Generated SoundBanks\n" + "{1}: Update existing Unreal assets. This updates the asset name as well as its metadata.\n" + "{2}: Delete Unreal assets that no longer exist in the Generated SoundBanks\n" + "{3}: Fully reconcile Unreal assets"), + {CreateOption, UpdateOption, DeleteOption, AllOption})); + + HelpParamNames.Add("?, help"); + HelpParamDescriptions.Add(TEXT("Display help")); + + HelpUsage = FString::Format(TEXT(" -run=WwiseReconcileCommandlet -modes={0},{1},{2},{3}"), {CreateOption, DeleteOption, UpdateOption, AllOption}); +} + +int32 UWwiseReconcileCommandlet::Main(const FString& Params) +{ + int32 Result = 0; + + TMap ParsedParams; + ParseCommandLine(*Params, CmdTokens, CmdSwitches, ParsedParams); + + if( Params.Contains(TEXT("?")) || Params.Contains(HelpOption) ) + { + PrintHelp(); + return Result; + } + + EWwiseReconcileOperationFlags ReconcileOperationFlags = EWwiseReconcileOperationFlags::None; + + if (ParsedParams.Contains(ModesParam)) + { + FString ModeStr = ParsedParams.FindRef("modes"); + + TArray Modes; + if (ModeStr.Contains(TEXT(","))) + { + ModeStr.ParseIntoArray(Modes, TEXT(","), true); + } + else + { + Modes.Add(ModeStr); + } + + if (Modes.Num() == 0) + { + PrintHelp(); + Result = -1; + return Result; + } + + if (Modes.Contains(CreateOption)) + { + ReconcileOperationFlags |= EWwiseReconcileOperationFlags::Create; + } + if (Modes.Contains(UpdateOption)) + { + ReconcileOperationFlags |= EWwiseReconcileOperationFlags::UpdateExisting; + } + if (Modes.Contains(DeleteOption)) + { + ReconcileOperationFlags |= EWwiseReconcileOperationFlags::Delete; + } + if (Modes.Contains(AllOption)) + { + ReconcileOperationFlags |= EWwiseReconcileOperationFlags::All; + } + } + + if (ReconcileOperationFlags == EWwiseReconcileOperationFlags::None) + { + UE_LOG(LogWwiseReconcile, Error, TEXT("No Reconcile mode specified")) + PrintHelp(); + Result = -1; + return Result; + } + + GetAllAssets(); + + GetAssetChanges(ReconcileOperationFlags); + + for (const auto& Asset : AssetsToCreate) + { + UE_LOG(LogWwiseReconcile, Verbose, TEXT("New Asset %s will be created."), *Asset.WwiseAnyRef.GetName().ToString()); + } + + for (const auto& Asset: AssetsToUpdate) + { + UE_LOG(LogWwiseReconcile, Verbose, TEXT("Asset %s will be updated."), *Asset.GetFullName()); + } + + for (const auto& Asset: AssetsToDelete) + { + UE_LOG(LogWwiseReconcile, Verbose, TEXT("Asset %s of type %s will be deleted."), *Asset.GetFullName(), *AkUnrealAssetDataHelper::GetAssetClassName(Asset).ToString()); + } + + int NumAssetsToReconcile = AssetsToCreate.Num() + AssetsToUpdate.Num() + AssetsToDelete.Num(); + + if (NumAssetsToReconcile > 0) + { + UE_LOG(LogWwiseReconcile, Display, TEXT("Reconciling %d Wwise Asset(s)..."), NumAssetsToReconcile) + + if (!ReconcileAssets()) + { + Result = -1; + UE_LOG(LogWwiseReconcile, Error, TEXT("Failed to reconcile assets. Check the log for details")); + } + } + + else + { + UE_LOG(LogWwiseReconcile, Display, TEXT("No Wwise Assets to Reconcile...")) + } + + UE_LOG(LogWwiseReconcile, Display, TEXT("Finished reconciling Wwise Assets...")) + + + return Result; +} + +void UWwiseReconcileCommandlet::PrintHelp() +{ + UE_LOG(LogWwiseReconcile, Display, TEXT("%s"), *HelpDescription); + UE_LOG(LogWwiseReconcile, Display, TEXT("Usage: %s"), *HelpUsage); + UE_LOG(LogWwiseReconcile, Display, TEXT("Parameters:")); + for (int32 i = 0; i < HelpParamNames.Num(); ++i) + { + UE_LOG(LogWwiseReconcile, Display, TEXT("\t- %s: %s"), *HelpParamNames[i], *HelpParamDescriptions[i]); + } + UE_LOG(LogWwiseReconcile, Display, TEXT("For more information, see %s"), *HelpWebLink); +} + +void UWwiseReconcileCommandlet::GetAllAssets() +{ + AssetRegistryModule = &FModuleManager::LoadModuleChecked( + "AssetRegistry"); + + AssetToolsModule = &FModuleManager::LoadModuleChecked("AssetTools"); + + if (!AssetToolsModule) + { + UE_LOG(LogWwiseReconcile, Error, TEXT("Could not load the AssetTools Module")); + return; + } + + if (!AssetRegistryModule) + { + UE_LOG(LogWwiseReconcile, Error, TEXT("Could not load the AssetRegistry Module")); + return; + } + +#if UE_5_1_OR_LATER + AssetRegistryModule->Get().GetAssetsByClass(UAkAudioType::StaticClass()->GetClassPathName(), Assets, true); +#else + AssetRegistryModule->Get().GetAssetsByClass(UAkAudioType::StaticClass()->GetFName(), Assets, true); +#endif + + GuidAssetMap.Empty(); + for (const FAssetData& AssetData : Assets) + { + if (UAkAudioType* AkAudioAsset = Cast(AssetData.GetAsset())) + { + auto AssetGuid = AkAudioAsset->GetWwiseGuid(); + // Exclude the Init bank, and invalid assets + if (AssetGuid.IsValid()) + { + GuidAssetMap.Add(AssetGuid, AssetData); + } + + else if (!AkUnrealAssetDataHelper::AssetOfType(AssetData)) + { + InvalidAssets.Add(AssetData); + } + } + } + + + ProjectDatabase = FWwiseProjectDatabase::Get(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogWwiseReconcile, Error, TEXT("Could not load project database")); + } + + ProjectDatabase->UpdateDataStructure(); + + GuidWwiseMetadataMap.Empty(); + +} + +void UWwiseReconcileCommandlet::GetAssetChanges(EWwiseReconcileOperationFlags OperationFlags) +{ + ProjectDatabase = FWwiseProjectDatabase::Get(); + + ProjectDatabase->UpdateDataStructure(); + + if (UNLIKELY(!ProjectDatabase || !ProjectDatabase->IsProjectDatabaseParsed())) + { + UE_LOG(LogWwiseReconcile, Error, TEXT("No data loaded from Wwise project database")); + return; + } + + FWwiseDataStructureScopeLock DataStructure(*ProjectDatabase); + + TSet FoundAssets; + + // Check to make sure there are no issues getting data for the CurrentPlatform + if (DataStructure.GetSoundBanks().Num() == 0) + { + FName PlatformName = DataStructure.GetCurrentPlatform().GetPlatformName(); + UE_LOG(LogWwiseReconcile, Error, TEXT("No data loaded from Wwise project database for the curent platform %s"), *PlatformName.ToString()); + return; + } + + if (DataStructure.GetCurrentPlatformData()->Guids.Num() == 0) + { + FName PlatformName = DataStructure.GetCurrentPlatform().GetPlatformName(); + UE_LOG(LogWwiseReconcile, Error, TEXT("No data loaded from Wwise project database for the curent platform %s"), *PlatformName.ToString()); + return; + } + + for (const auto& WwiseRef : DataStructure.GetCurrentPlatformData()->Guids) + { + const FWwiseAnyRef WwiseRefValue = WwiseRef.Value; + EWwiseRefType RefType = WwiseRefValue.GetType(); + UClass* RefClass = GetUClassFromWwiseRefType(RefType); + FGuid RefGuid = WwiseRefValue.GetGuid(); + + if (!RefClass || FoundAssets.Contains(RefGuid)) + { + continue; + } + + FAssetData Asset = GuidAssetMap.FindRef(WwiseRefValue.GetGuid()); + + if (!Asset.IsValid()) + { + if (RefClass && EnumHasAnyFlags(OperationFlags, EWwiseReconcileOperationFlags::Create)) + { + AssetsToCreate.Add({ WwiseRefValue }); + } + } + + else if (EnumHasAnyFlags(OperationFlags, EWwiseReconcileOperationFlags::UpdateExisting)) + { + UAkAudioType* AkAudioAsset = Cast(Asset.GetAsset()); + if (RefClass && AkAudioAsset) + { + // Ignore SoundBanks + if (RefType != EWwiseRefType::SoundBanksInfo) + { + + FName AssetName = AkUnrealAssetDataHelper::GetAssetDefaultName(&WwiseRefValue); + + if (AkAudioAsset->IsAssetOutOfDate(WwiseRefValue)) + { + AssetsToUpdate.Add(Asset); + } + + if (AkAudioAsset->GetName() != AssetName.ToString()) + { + AssetsToRename.Add(Asset); + } + } + } + } + + FoundAssets.Add(RefGuid); + GuidAssetMap.Remove(RefGuid); + } + + if (EnumHasAnyFlags(OperationFlags, EWwiseReconcileOperationFlags::Delete)) + { + for (const auto& GuidAsset : GuidAssetMap) + { + if(!FoundAssets.Contains(GuidAsset.Key)) + { + AssetsToDelete.Add(GuidAsset.Value); + } + } + + for (const auto& Asset : InvalidAssets) + { + UAkAudioType* AkAudioAsset = Cast(Asset.GetAsset()); + + if (!AkAudioAsset->ObjectIsInSoundBanks()) + { + AssetsToDelete.Add(Asset); + } + } + + GuidAssetMap.Empty(); + InvalidAssets.Empty(); + } + + if (EnumHasAnyFlags(OperationFlags, EWwiseReconcileOperationFlags::UpdateExisting)) + { + for (const auto& Asset : InvalidAssets) + { + UAkAudioType* AkAudioAsset = Cast(Asset.GetAsset()); + if (AkAudioAsset->ObjectIsInSoundBanks()) + { + AssetsToUpdate.Add(Asset); + AssetsToRename.Add(Asset); + } + } + } +} + + +bool UWwiseReconcileCommandlet::ReconcileAssets() +{ + + bool Succeeded = true; + + if (AssetsToCreate.Num() != 0 && CreateAssets().Num() == 0) + { + UE_LOG(LogWwiseReconcile, Warning, TEXT("No New AkAudioType assets created")); + Succeeded = false; + } + + if (AssetsToUpdate.Num() != 0 && UpdateExistingAssets().Num() == 0) + { + UE_LOG(LogWwiseReconcile, Warning, TEXT("Failed to consolidate existing AkAudioType assets")); + Succeeded = false; + } + + if (AssetsToDelete.Num() != 0 && DeleteAssets() <= 0) + { + UE_LOG(LogWwiseReconcile, Warning, TEXT("Failed to delete outdated AkAudioType assets")) + Succeeded = false; + } + + return Succeeded; +} + +TArray UWwiseReconcileCommandlet::CreateAssets() +{ + check(IsInGameThread()); + + TArray PackagesToSave; + TArray NewAssets; + + for (const auto& Asset : AssetsToCreate) + { + const FWwiseAnyRef WwiseRef = Asset.WwiseAnyRef; + + FName AssetName = AkUnrealAssetDataHelper::GetAssetDefaultName(&WwiseRef); + FString AssetPackagePath = AkUnrealAssetDataHelper::GetAssetDefaultPackagePath(&WwiseRef); + AssetPackagePath = UPackageTools::SanitizePackageName(AssetPackagePath); + + UClass* NewAssetClass = GetUClassFromWwiseRefType(WwiseRef.GetType()); + + if(!NewAssetClass) + { + UE_LOG(LogWwiseReconcile, Error, TEXT("Could not determine which type of asset to create for '%s' in '%s'."), *AssetName.ToString(), *AssetPackagePath); + continue; + } + + UE_LOG(LogWwiseReconcile, Verbose, TEXT("Creating new asset '%s' in '%s'."), *AssetName.ToString(), *AssetPackagePath); + + UAkAudioType* NewAkAudioObject = Cast( + AssetToolsModule->Get().CreateAsset(AssetName.ToString(), AssetPackagePath, NewAssetClass, nullptr)); + + if (!NewAkAudioObject) + { + UE_LOG(LogWwiseReconcile, Error, TEXT("Could not save asset %s"), *AssetName.ToString()); + continue; + } + + NewAkAudioObject->FillInfo(WwiseRef); + + NewAssets.Add(FAssetData(NewAkAudioObject)); + + UE_LOG(LogWwiseReconcile, Verbose, TEXT("Created asset %s"), *AssetName.ToString()); + + PackagesToSave.Add(NewAkAudioObject->GetPackage()); + } + + AssetsToCreate.Empty(); + + if (!UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, false)) + { + UE_LOG(LogWwiseReconcile, Error, TEXT("Could not save packages")); + return {}; + } + + return NewAssets; +} + +TArray UWwiseReconcileCommandlet::UpdateExistingAssets() +{ + check(IsInGameThread()); + + TArray AssetsToRenameData; + TArray PackagesToSave; + TArray UpdatedAssets; + + for (const auto& AssetData: AssetsToUpdate) + { + if (auto AkAudioAsset = Cast(AssetData.GetAsset())) + { + AkAudioAsset->FillInfo(); + FAssetData NewAssetData = FAssetData(AkAudioAsset); + PackagesToSave.Add(NewAssetData.GetPackage()); + UpdatedAssets.Add(NewAssetData); + AkAudioAsset->MarkPackageDirty(); + } + } + + for (const auto& AssetData: AssetsToRename) + { + if (auto AkAudioAsset = Cast(AssetData.GetAsset())) + { + FName NewAssetName = AkAudioAsset->GetAssetDefaultName(); + FAssetRenameData AssetRenameData(AssetData.GetAsset(), AssetData.PackagePath.ToString(), NewAssetName.ToString()); + AssetsToRenameData.Add(AssetRenameData); + } + } + + AssetsToUpdate.Empty(); + AssetsToRename.Empty(); + + if (!UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, false)) + { + UE_LOG(LogWwiseReconcile, Error, TEXT("Failed to save updated Wwise assets.")) + return {}; + } + + if (AssetsToRenameData.Num() > 0 && !AssetToolsModule->Get().RenameAssets(AssetsToRenameData)) + { + UE_LOG(LogWwiseReconcile, Error, TEXT("Failed to rename updated Wwise assets.")) + } + + return UpdatedAssets; +} + +int32 UWwiseReconcileCommandlet::DeleteAssets() +{ + check(IsInGameThread()); + + if (AssetsToDelete.Num() == 0) + { + return 0; + } + + TArray ObjectsToDelete; + for (const auto& Asset : AssetsToDelete) + { + ObjectsToDelete.Add(Asset.GetAsset()); + } + + int32 NumDeletedObjects = ObjectTools::ForceDeleteObjects(ObjectsToDelete, false); + + if (NumDeletedObjects != ObjectsToDelete.Num()) + { + UE_LOG(LogWwiseReconcile, Error, TEXT("Could not delete assets. Verify that none of the assets are still being referenced.")) + } + + AssetsToDelete.Empty(); + + return NumDeletedObjects; + +} + +UClass* UWwiseReconcileCommandlet::GetUClassFromWwiseRefType(EWwiseRefType RefType) +{ + switch (RefType) + { + case EWwiseRefType::Event: + return UAkAudioEvent::StaticClass(); + case EWwiseRefType::AuxBus: + return UAkAuxBus::StaticClass(); + case EWwiseRefType::AcousticTexture: + return UAkAcousticTexture::StaticClass(); + case EWwiseRefType::State: + return UAkStateValue::StaticClass(); + case EWwiseRefType::Switch: + return UAkSwitchValue::StaticClass(); + case EWwiseRefType::GameParameter: + return UAkRtpc::StaticClass(); + case EWwiseRefType::Trigger: + return UAkTrigger::StaticClass(); + case EWwiseRefType::PluginShareSet: + return UAkEffectShareSet::StaticClass(); + case EWwiseRefType::None: + return nullptr; + default: + return nullptr; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/Private/Wwise/WwiseReconcileModule.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/Private/Wwise/WwiseReconcileModule.cpp new file mode 100644 index 0000000..885fa1e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/Private/Wwise/WwiseReconcileModule.cpp @@ -0,0 +1,31 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseReconcileModule.h" +#include "Wwise/Stats/Reconcile.h" + +IMPLEMENT_MODULE(FWwiseReconcileModule, WwiseReconcileModule) + +void FWwiseReconcileModule::StartupModule() +{ + UE_LOG(LogWwiseReconcile, Display, TEXT("Starting Wwise Asset Sync Module")); +} + +void FWwiseReconcileModule::ShutdownModule() +{ + UE_LOG(LogWwiseReconcile, Display, TEXT("Shutting down Wwise Asset Sync Module")); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/Public/Wwise/Stats/Reconcile.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/Public/Wwise/Stats/Reconcile.h new file mode 100644 index 0000000..c59f9b1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/Public/Wwise/Stats/Reconcile.h @@ -0,0 +1,22 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Logging/LogMacros.h" + +WWISERECONCILE_API DECLARE_LOG_CATEGORY_EXTERN(LogWwiseReconcile, Log, All); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/Public/Wwise/WwiseReconcile.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/Public/Wwise/WwiseReconcile.h new file mode 100644 index 0000000..24f61ae --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/Public/Wwise/WwiseReconcile.h @@ -0,0 +1,42 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AssetRegistry/AssetData.h" +#include "Wwise/Info/WwiseObjectInfo.h" +#include "Wwise/Ref/WwiseAnyRef.h" + +enum class EWwiseReconcileOperationFlags +{ + None = 0, + Create = 1 << 0, + UpdateExisting = 1 << 1, + Delete = 1 << 3, + All = Create | UpdateExisting | Delete +}; + +ENUM_CLASS_FLAGS(EWwiseReconcileOperationFlags) + +struct FWwiseNewAsset +{ + FWwiseAnyRef WwiseAnyRef; +}; + +class WWISERECONCILE_API FWwiseReconcile +{ +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/Public/Wwise/WwiseReconcileCommandlet.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/Public/Wwise/WwiseReconcileCommandlet.h new file mode 100644 index 0000000..d3fbda4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/Public/Wwise/WwiseReconcileCommandlet.h @@ -0,0 +1,83 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "AkAudioEvent.h" +#include "WwiseReconcile.h" +#include "Commandlets/Commandlet.h" +#include "Wwise/Metadata/WwiseMetadataBasicReference.h" + +#include "WwiseReconcileCommandlet.generated.h" + +class UAkAssetFactory; +class FWwiseProjectDatabase; +class FAssetRegistryModule; +class FAssetToolsModule; + +UCLASS() +class WWISERECONCILE_API UWwiseReconcileCommandlet : public UCommandlet +{ + GENERATED_BODY() + +public: + virtual int32 Main(const FString& Params) override; + UWwiseReconcileCommandlet(); + +private: + /** All commandline Tokens */ + TArray CmdTokens; + + /** All commandline switches */ + TArray CmdSwitches; + + FWwiseProjectDatabase* ProjectDatabase; + + FAssetRegistryModule* AssetRegistryModule; + + FAssetToolsModule* AssetToolsModule; + + // Array of all existing UAkAudioType uassets + TArray Assets; + + // Map from Wwise GUIDs to existing UAkAudioType uassets + TMap GuidAssetMap; + + // Existing assets without a GUID + TArray InvalidAssets; + + // Map of existing objects in the Wwise Project Database + TMap GuidWwiseMetadataMap; + + TArray AssetsToDelete; + TArray AssetsToCreate; + TArray AssetsToUpdate; + TArray AssetsToRename; + + /** Prints command line arguments */ + void PrintHelp(); + void GetAllAssets(); + + void GetAssetChanges(EWwiseReconcileOperationFlags OperationFlags = EWwiseReconcileOperationFlags::All); + + TArray CreateAssets(); + TArray UpdateExistingAssets(); + bool ReconcileAssets(); + int32 DeleteAssets(); + UClass* GetUClassFromWwiseRefType(EWwiseRefType RefType); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/Public/Wwise/WwiseReconcileModule.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/Public/Wwise/WwiseReconcileModule.h new file mode 100644 index 0000000..54b5a13 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/Public/Wwise/WwiseReconcileModule.h @@ -0,0 +1,29 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" + +class FWwiseReconcileModule : public IModuleInterface +{ +public: + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/RunCommandlet.bat b/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/RunCommandlet.bat new file mode 100644 index 0000000..898068d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/RunCommandlet.bat @@ -0,0 +1,4 @@ +echo "%~dp0..\..\..\..\WwiseDemoGame" + +C:\Code\UE5_AK\Engine\Binaries\Win64\UnrealEditor-Cmd.exe "%~dp0..\..\..\..\WwiseDemoGame.uproject" VERYVERBOSE -run=WwiseReconcile -modes=all +REM "C:\Program Files\Epic Games\UE_5.0\Engine\Binaries\Win64\UnrealEditor-Cmd.exe" "%~dp0..\..\..\..\WwiseDemoGame.uproject" VERYVERBOSE -run=WwiseReconcile -modes=update \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/WwiseReconcile.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/WwiseReconcile.Build.cs new file mode 100644 index 0000000..363345b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseReconcile/WwiseReconcile.Build.cs @@ -0,0 +1,41 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; + +public class WwiseReconcile : ModuleRules +{ + public WwiseReconcile(ReadOnlyTargetRules Target) : base(Target) + { + PrivateDependencyModuleNames.AddRange(new[] + { + "AkAudio", + "AudiokineticTools", + "WwiseSoundEngine", + "WwiseResourceLoader", + "WwiseProjectDatabase" + }); + PublicDependencyModuleNames.AddRange(new[] + { + "Core", + "CoreUObject", + "Engine", + "UnrealEd", + } + ); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Private/Wwise/Stats/ResourceCooker.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Private/Wwise/Stats/ResourceCooker.cpp new file mode 100644 index 0000000..b3e79b9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Private/Wwise/Stats/ResourceCooker.cpp @@ -0,0 +1,20 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Stats/ResourceCooker.h" + +DEFINE_LOG_CATEGORY(LogWwiseResourceCooker); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Private/Wwise/WwiseCookingCache.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Private/Wwise/WwiseCookingCache.h new file mode 100644 index 0000000..40b4145 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Private/Wwise/WwiseCookingCache.h @@ -0,0 +1,59 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Info/WwiseEventInfo.h" +#include "Wwise/Info/WwiseGroupValueInfo.h" +#include "Wwise/CookedData/WwiseAcousticTextureCookedData.h" +#include "Wwise/CookedData/WwiseInitBankCookedData.h" +#include "Wwise/CookedData/WwiseLocalizedAuxBusCookedData.h" +#include "Wwise/CookedData/WwiseLocalizedSoundBankCookedData.h" +#include "Wwise/CookedData/WwiseLocalizedEventCookedData.h" +#include "Wwise/CookedData/WwiseLocalizedShareSetCookedData.h" +#include "Wwise/CookedData/WwiseGameParameterCookedData.h" +#include "Wwise/CookedData/WwiseTriggerCookedData.h" + +#include "Wwise/Info/WwiseObjectInfo.h" + +#include "Wwise/WwiseDatabaseIdentifiers.h" + +class IWwiseExternalSourceManager; + +class WWISERESOURCECOOKER_API FWwiseCookingCache +{ +public: + FWwiseCookingCache() : + ExternalSourceManager(nullptr) + {} + + TMap StagedFiles; + TMap AuxBusCache; + TMap SoundBankCache; + TMap EventCache; + TMap ExternalSourceCache; + TMap InitBankCache; + TMap MediaCache; + TMap ShareSetCache; + TMap StateCache; + TMap SwitchCache; + TMap GameParameterCache; + TMap AcousticTextureCache; + TMap TriggerCache; + + IWwiseExternalSourceManager* ExternalSourceManager; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Private/Wwise/WwiseResourceCooker.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Private/Wwise/WwiseResourceCooker.cpp new file mode 100644 index 0000000..20d13c1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Private/Wwise/WwiseResourceCooker.cpp @@ -0,0 +1,563 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseResourceCooker.h" + +#include "Wwise/Stats/ResourceCooker.h" +#include "Wwise/WwiseResourceLoader.h" +#include "Wwise/WwiseCookingCache.h" +#include "Wwise/Stats/ResourceCooker.h" + +#include "Async/Async.h" + +void FWwiseResourceCooker::CookAuxBus(const FWwiseObjectInfo& InInfo, WriteAdditionalFileFunction WriteAdditionalFile) +{ + auto* CookingCache = GetCookingCache(); + if (UNLIKELY(!CookingCache)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("CookAuxBus: No CookingCache.")); + return; + } + + if (const auto* CachedCookedData = CookingCache->AuxBusCache.Find(InInfo)) + { + CookLocalizedAuxBusToSandbox(*CachedCookedData, WriteAdditionalFile); + } + else + { + FWwiseLocalizedAuxBusCookedData CookedData; + if (UNLIKELY(!GetAuxBusCookedData(CookedData, InInfo))) + { + return; + } + + CookingCache->AuxBusCache.Add(InInfo, CookedData); + CookLocalizedAuxBusToSandbox(CookedData, WriteAdditionalFile); + } +} + +void FWwiseResourceCooker::CookEvent(const FWwiseEventInfo& InInfo, WriteAdditionalFileFunction WriteAdditionalFile) +{ + auto* CookingCache = GetCookingCache(); + if (UNLIKELY(!CookingCache)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("CookEvent: No CookingCache.")); + return; + } + + if (const auto* CachedCookedData = CookingCache->EventCache.Find(InInfo)) + { + CookLocalizedEventToSandbox(*CachedCookedData, WriteAdditionalFile); + } + else + { + FWwiseLocalizedEventCookedData CookedData; + if (UNLIKELY(!GetEventCookedData(CookedData, InInfo))) + { + return; + } + + CookingCache->EventCache.Add(InInfo, CookedData); + CookLocalizedEventToSandbox(CookedData, WriteAdditionalFile); + } +} + +void FWwiseResourceCooker::CookExternalSource(uint32 InCookie, WriteAdditionalFileFunction WriteAdditionalFile) +{ + auto* CookingCache = GetCookingCache(); + if (UNLIKELY(!CookingCache)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("CookExternalSource: No CookingCache.")); + return; + } + + if (const auto* CachedCookedData = CookingCache->ExternalSourceCache.Find(InCookie)) + { + CookExternalSourceToSandbox(*CachedCookedData, WriteAdditionalFile); + } + else + { + FWwiseExternalSourceCookedData CookedData; + if (UNLIKELY(!GetExternalSourceCookedData(CookedData, InCookie))) + { + return; + } + + CookingCache->ExternalSourceCache.Add(InCookie, CookedData); + CookExternalSourceToSandbox(CookedData, WriteAdditionalFile); + } +} + +void FWwiseResourceCooker::CookInitBank(const FWwiseObjectInfo& InInfo, WriteAdditionalFileFunction WriteAdditionalFile) +{ + auto* CookingCache = GetCookingCache(); + if (UNLIKELY(!CookingCache)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("CookInitBank: No CookingCache.")); + return; + } + + if (const auto* CachedCookedData = CookingCache->InitBankCache.Find(InInfo)) + { + CookInitBankToSandbox(*CachedCookedData, WriteAdditionalFile); + } + else + { + FWwiseInitBankCookedData CookedData; + if (UNLIKELY(!GetInitBankCookedData(CookedData, InInfo))) + { + return; + } + + CookingCache->InitBankCache.Add(InInfo, CookedData); + CookInitBankToSandbox(CookedData, WriteAdditionalFile); + } +} + +void FWwiseResourceCooker::CookMedia(const FWwiseObjectInfo& InInfo, WriteAdditionalFileFunction WriteAdditionalFile) +{ + auto* CookingCache = GetCookingCache(); + if (UNLIKELY(!CookingCache)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("CookMedia: No CookingCache.")); + return; + } + + FWwiseDatabaseMediaIdKey MediaKey; + MediaKey.MediaId = InInfo.WwiseShortId; + MediaKey.SoundBankId = InInfo.HardCodedSoundBankShortId; + + if (const auto* CachedCookedData = CookingCache->MediaCache.Find(MediaKey)) + { + CookMediaToSandbox(*CachedCookedData, WriteAdditionalFile); + } + else + { + FWwiseMediaCookedData CookedData; + if (UNLIKELY(!GetMediaCookedData(CookedData, InInfo))) + { + return; + } + + CookingCache->MediaCache.Add(MediaKey, CookedData); + CookMediaToSandbox(CookedData, WriteAdditionalFile); + } +} + +void FWwiseResourceCooker::CookShareSet(const FWwiseObjectInfo& InInfo, WriteAdditionalFileFunction WriteAdditionalFile) +{ + auto* CookingCache = GetCookingCache(); + if (UNLIKELY(!CookingCache)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("CookShareSet: No CookingCache.")); + return; + } + + if (const auto* CachedCookedData = CookingCache->ShareSetCache.Find(InInfo)) + { + CookLocalizedShareSetToSandbox(*CachedCookedData, WriteAdditionalFile); + } + else + { + FWwiseLocalizedShareSetCookedData CookedData; + if (UNLIKELY(!GetShareSetCookedData(CookedData, InInfo))) + { + return; + } + + CookingCache->ShareSetCache.Add(InInfo, CookedData); + CookLocalizedShareSetToSandbox(CookedData, WriteAdditionalFile); + } +} + +void FWwiseResourceCooker::CookSoundBank(const FWwiseObjectInfo& InInfo, WriteAdditionalFileFunction WriteAdditionalFile) +{ + auto* CookingCache = GetCookingCache(); + if (UNLIKELY(!CookingCache)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("CookSoundBank: No CookingCache.")); + return; + } + + if (const auto* CachedCookedData = CookingCache->SoundBankCache.Find(InInfo)) + { + CookLocalizedSoundBankToSandbox(*CachedCookedData, WriteAdditionalFile); + } + else + { + FWwiseLocalizedSoundBankCookedData CookedData; + if (UNLIKELY(!GetSoundBankCookedData(CookedData, InInfo))) + { + return; + } + + CookingCache->SoundBankCache.Add(InInfo, CookedData); + CookLocalizedSoundBankToSandbox(CookedData, WriteAdditionalFile); + } +} + +bool FWwiseResourceCooker::PrepareCookedData(FWwiseAcousticTextureCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) +{ + auto* CookingCache = GetCookingCache(); + if (!CookingCache) + { + return GetAcousticTextureCookedData(OutCookedData, InInfo); + } + + if (const auto* CachedCookedData = CookingCache->AcousticTextureCache.Find(InInfo)) + { + OutCookedData = *CachedCookedData; + return true; + } + else if (LIKELY(GetAcousticTextureCookedData(OutCookedData, InInfo))) + { + CookingCache->AcousticTextureCache.Add(InInfo, OutCookedData); + return true; + } + return false; +} + +bool FWwiseResourceCooker::PrepareCookedData(FWwiseLocalizedAuxBusCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) +{ + auto* CookingCache = GetCookingCache(); + if (!CookingCache) + { + return GetAuxBusCookedData(OutCookedData, InInfo); + } + + if (const auto* CachedCookedData = CookingCache->AuxBusCache.Find(InInfo)) + { + OutCookedData = *CachedCookedData; + return true; + } + else if (LIKELY(GetAuxBusCookedData(OutCookedData, InInfo))) + { + CookingCache->AuxBusCache.Add(InInfo, OutCookedData); + return true; + } + return false; +} + +bool FWwiseResourceCooker::PrepareCookedData(FWwiseLocalizedEventCookedData& OutCookedData, const FWwiseEventInfo& InInfo) +{ + auto* CookingCache = GetCookingCache(); + if (!CookingCache) + { + return GetEventCookedData(OutCookedData, InInfo); + } + + if (const auto* CachedCookedData = CookingCache->EventCache.Find(InInfo)) + { + OutCookedData = *CachedCookedData; + return true; + } + else if (LIKELY(GetEventCookedData(OutCookedData, InInfo))) + { + CookingCache->EventCache.Add(InInfo, OutCookedData); + return true; + } + return false; +} + +bool FWwiseResourceCooker::PrepareCookedData(FWwiseExternalSourceCookedData& OutCookedData, uint32 InCookie) +{ + auto* CookingCache = GetCookingCache(); + if (!CookingCache) + { + return GetExternalSourceCookedData(OutCookedData, InCookie); + } + + if (const auto* CachedCookedData = CookingCache->ExternalSourceCache.Find(InCookie)) + { + OutCookedData = *CachedCookedData; + return true; + } + else if (LIKELY(GetExternalSourceCookedData(OutCookedData, InCookie))) + { + CookingCache->ExternalSourceCache.Add(InCookie, OutCookedData); + return true; + } + return false; +} + +bool FWwiseResourceCooker::PrepareCookedData(FWwiseGameParameterCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) +{ + auto* CookingCache = GetCookingCache(); + if (!CookingCache) + { + return GetGameParameterCookedData(OutCookedData, InInfo); + } + + if (const auto* CachedCookedData = CookingCache->GameParameterCache.Find(InInfo)) + { + OutCookedData = *CachedCookedData; + return true; + } + else if (LIKELY(GetGameParameterCookedData(OutCookedData, InInfo))) + { + CookingCache->GameParameterCache.Add(InInfo, OutCookedData); + return true; + } + return false; +} + +bool FWwiseResourceCooker::PrepareCookedData(FWwiseGroupValueCookedData& OutCookedData, const FWwiseGroupValueInfo& InInfo, EWwiseGroupType InGroupType) +{ + auto* CookingCache = GetCookingCache(); + if (InGroupType == EWwiseGroupType::State) + { + if (!CookingCache) + { + return GetStateCookedData(OutCookedData, InInfo); + } + + if (const auto* CachedCookedData = CookingCache->StateCache.Find(InInfo)) + { + OutCookedData = *CachedCookedData; + return true; + } + else if (LIKELY(GetStateCookedData(OutCookedData, InInfo))) + { + CookingCache->StateCache.Add(InInfo, OutCookedData); + return true; + } + } + else if (InGroupType == EWwiseGroupType::Switch) + { + if (!CookingCache) + { + return GetSwitchCookedData(OutCookedData, InInfo); + } + + if (const auto* CachedCookedData = CookingCache->SwitchCache.Find(InInfo)) + { + OutCookedData = *CachedCookedData; + return true; + } + else if (LIKELY(GetSwitchCookedData(OutCookedData, InInfo))) + { + CookingCache->SwitchCache.Add(InInfo, OutCookedData); + return true; + } + } + return false; +} + +bool FWwiseResourceCooker::PrepareCookedData(FWwiseInitBankCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) +{ + auto* CookingCache = GetCookingCache(); + if (!CookingCache) + { + return GetInitBankCookedData(OutCookedData, InInfo); + } + + if (const auto* CachedCookedData = CookingCache->InitBankCache.Find(InInfo)) + { + OutCookedData = *CachedCookedData; + return true; + } + else if (LIKELY(GetInitBankCookedData(OutCookedData, InInfo))) + { + CookingCache->InitBankCache.Add(InInfo, OutCookedData); + return true; + } + return false; +} + +bool FWwiseResourceCooker::PrepareCookedData(FWwiseMediaCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) +{ + auto* CookingCache = GetCookingCache(); + if (!CookingCache) + { + return GetMediaCookedData(OutCookedData, InInfo); + } + + FWwiseDatabaseMediaIdKey MediaIdKey; + MediaIdKey.MediaId = InInfo.WwiseShortId; + MediaIdKey.SoundBankId = InInfo.HardCodedSoundBankShortId; + + if (const auto* CachedCookedData = CookingCache->MediaCache.Find(MediaIdKey)) + { + OutCookedData = *CachedCookedData; + return true; + } + else if (LIKELY(GetMediaCookedData(OutCookedData, InInfo))) + { + CookingCache->MediaCache.Add(MediaIdKey, OutCookedData); + return true; + } + return false; +} + +bool FWwiseResourceCooker::PrepareCookedData(FWwiseLocalizedShareSetCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) +{ + auto* CookingCache = GetCookingCache(); + if (!CookingCache) + { + return GetShareSetCookedData(OutCookedData, InInfo); + } + + if (const auto* CachedCookedData = CookingCache->ShareSetCache.Find(InInfo)) + { + OutCookedData = *CachedCookedData; + return true; + } + else if (LIKELY(GetShareSetCookedData(OutCookedData, InInfo))) + { + CookingCache->ShareSetCache.Add(InInfo, OutCookedData); + return true; + } + return false; +} + +bool FWwiseResourceCooker::PrepareCookedData(FWwiseLocalizedSoundBankCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) +{ + auto* CookingCache = GetCookingCache(); + if (!CookingCache) + { + return GetSoundBankCookedData(OutCookedData, InInfo); + } + + if (const auto* CachedCookedData = CookingCache->SoundBankCache.Find(InInfo)) + { + OutCookedData = *CachedCookedData; + return true; + } + else if (LIKELY(GetSoundBankCookedData(OutCookedData, InInfo))) + { + CookingCache->SoundBankCache.Add(InInfo, OutCookedData); + return true; + } + return false; +} + +bool FWwiseResourceCooker::PrepareCookedData(FWwiseTriggerCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) +{ + auto* CookingCache = GetCookingCache(); + if (!CookingCache) + { + return GetTriggerCookedData(OutCookedData, InInfo); + } + + if (const auto* CachedCookedData = CookingCache->TriggerCache.Find(InInfo)) + { + OutCookedData = *CachedCookedData; + return true; + } + else if (LIKELY(GetTriggerCookedData(OutCookedData, InInfo))) + { + CookingCache->TriggerCache.Add(InInfo, OutCookedData); + return true; + } + return false; +} + +void FWwiseResourceCooker::SetSandboxRootPath(const TCHAR* InPackageFilename) +{ + if (!SandboxRootPath.IsEmpty()) + { + return; + } + + auto SandboxPath = FPaths::GetPath(InPackageFilename); // Remove Filename.uasset + while (!SandboxPath.IsEmpty() && FPaths::GetCleanFilename(SandboxPath) != TEXT("Content")) + { + SandboxPath = FPaths::GetPath(SandboxPath); + } + + UE_LOG(LogWwiseResourceCooker, Display, TEXT("SetSandboxRootPath: Updating Sandbox Root Path: %s"), *SandboxPath); + + SandboxRootPath = SandboxPath; +} + +FWwiseResourceLoader* FWwiseResourceCooker::GetResourceLoader() +{ + if (auto* ProjectDatabase = GetProjectDatabase()) + { + return ProjectDatabase->GetResourceLoader(); + } + else + { + return FWwiseResourceLoader::Get(); + } +} + +const FWwiseResourceLoader* FWwiseResourceCooker::GetResourceLoader() const +{ + if (const auto* ProjectDatabase = GetProjectDatabase()) + { + return ProjectDatabase->GetResourceLoader(); + } + else + { + return FWwiseResourceLoader::Get(); + } +} + +FWwiseSharedLanguageId FWwiseResourceCooker::GetCurrentLanguage() const +{ + if (const auto* ProjectDatabase = GetProjectDatabase()) + { + return ProjectDatabase->GetCurrentLanguage(); + } + return {}; +} + +FWwiseSharedPlatformId FWwiseResourceCooker::GetCurrentPlatform() const +{ + if (const auto* ProjectDatabase = GetProjectDatabase()) + { + return ProjectDatabase->GetCurrentPlatform(); + } + return {}; +} + +void FWwiseResourceCooker::CookLocalizedAuxBusToSandbox(const FWwiseLocalizedAuxBusCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) +{ + for (const auto& AuxBus : InCookedData.AuxBusLanguageMap) + { + UE_LOG(LogWwiseResourceCooker, Verbose, TEXT("Cooking AuxBus %s in %s %" PRIu32), *InCookedData.DebugName.ToString(), *AuxBus.Key.GetLanguageName().ToString(), (uint32)AuxBus.Key.GetLanguageId()); + CookAuxBusToSandbox(AuxBus.Value, WriteAdditionalFile); + } +} + +void FWwiseResourceCooker::CookLocalizedEventToSandbox(const FWwiseLocalizedEventCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) +{ + for (const auto& Event : InCookedData.EventLanguageMap) + { + UE_LOG(LogWwiseResourceCooker, Verbose, TEXT("Cooking Event %s in %s %" PRIu32), *InCookedData.DebugName.ToString(), *Event.Key.GetLanguageName().ToString(), (uint32)Event.Key.GetLanguageId()); + CookEventToSandbox(Event.Value, WriteAdditionalFile); + } +} + +void FWwiseResourceCooker::CookLocalizedShareSetToSandbox(const FWwiseLocalizedShareSetCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) +{ + for (const auto& ShareSet : InCookedData.ShareSetLanguageMap) + { + UE_LOG(LogWwiseResourceCooker, Verbose, TEXT("Cooking ShareSet %s in %s %" PRIu32), *InCookedData.DebugName.ToString(), *ShareSet.Key.GetLanguageName().ToString(), (uint32)ShareSet.Key.GetLanguageId()); + CookShareSetToSandbox(ShareSet.Value, WriteAdditionalFile); + } +} + +void FWwiseResourceCooker::CookLocalizedSoundBankToSandbox(const FWwiseLocalizedSoundBankCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) +{ + for (const auto& SoundBank : InCookedData.SoundBankLanguageMap) + { + UE_LOG(LogWwiseResourceCooker, Verbose, TEXT("Cooking SoundBank %s in %s %" PRIu32), *InCookedData.DebugName.ToString(), *SoundBank.Key.GetLanguageName().ToString(), (uint32)SoundBank.Key.GetLanguageId()); + CookSoundBankToSandbox(SoundBank.Value, WriteAdditionalFile); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Private/Wwise/WwiseResourceCookerImpl.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Private/Wwise/WwiseResourceCookerImpl.cpp new file mode 100644 index 0000000..9702966 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Private/Wwise/WwiseResourceCookerImpl.cpp @@ -0,0 +1,2281 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseResourceCookerImpl.h" + +#include "Wwise/WwiseExternalSourceManager.h" +#include "Wwise/WwiseResourceLoader.h" +#include "Wwise/WwiseCookingCache.h" +#include "Wwise/Metadata/WwiseMetadataPlatformInfo.h" +#include "Wwise/Metadata/WwiseMetadataPlugin.h" +#include "Wwise/Stats/ResourceCooker.h" + +#include "Async/Async.h" +#include "Async/MappedFileHandle.h" +#include "Misc/FileHelper.h" +#if UE_5_0_OR_LATER +#include "HAL/PlatformFileManager.h" +#else +#include "HAL/PlatformFilemanager.h" +#endif +#include "Wwise/CookedData/WwiseSoundBankCookedData.h" +#include "Wwise/Stats/ResourceCooker.h" + +FWwiseResourceCookerImpl::FWwiseResourceCookerImpl() : + ExportDebugNameRule(EWwiseExportDebugNameRule::ObjectPath), + CookingCache(nullptr), + ProjectDatabaseOverride(nullptr) +{ +} + +FWwiseResourceCookerImpl::~FWwiseResourceCookerImpl() +{ +} + +FWwiseProjectDatabase* FWwiseResourceCookerImpl::GetProjectDatabase() +{ + if (ProjectDatabaseOverride.IsValid()) + { + return ProjectDatabaseOverride.Get(); + } + else + { + return FWwiseProjectDatabase::Get(); + } +} + +const FWwiseProjectDatabase* FWwiseResourceCookerImpl::GetProjectDatabase() const +{ + if (ProjectDatabaseOverride.IsValid()) + { + return ProjectDatabaseOverride.Get(); + } + else + { + return FWwiseProjectDatabase::Get(); + } +} + +void FWwiseResourceCookerImpl::PrepareResourceCookerForPlatform(FWwiseProjectDatabase*&& InProjectDatabaseOverride, EWwiseExportDebugNameRule InExportDebugNameRule) +{ + ProjectDatabaseOverride.Reset(InProjectDatabaseOverride); + ExportDebugNameRule = InExportDebugNameRule; + CookingCache.Reset(new FWwiseCookingCache); + CookingCache->ExternalSourceManager = IWwiseExternalSourceManager::Get(); +} + + + +void FWwiseResourceCookerImpl::CookAuxBusToSandbox(const FWwiseAuxBusCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) +{ + UE_LOG(LogWwiseResourceCooker, Verbose, TEXT("Cooking AuxBus %s %" PRIu32), *InCookedData.DebugName.ToString(), (uint32)InCookedData.AuxBusId); + for (const auto& SoundBank : InCookedData.SoundBanks) + { + CookSoundBankToSandbox(SoundBank, WriteAdditionalFile); + } + for (const auto& Media : InCookedData.Media) + { + CookMediaToSandbox(Media, WriteAdditionalFile); + } + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("Done cooking AuxBus %s %" PRIu32), *InCookedData.DebugName.ToString(), (uint32)InCookedData.AuxBusId); +} + +void FWwiseResourceCookerImpl::CookEventToSandbox(const FWwiseEventCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) +{ + UE_LOG(LogWwiseResourceCooker, Verbose, TEXT("Cooking Event %s %" PRIu32), *InCookedData.DebugName.ToString(), (uint32)InCookedData.EventId); + for (const auto& SoundBank : InCookedData.SoundBanks) + { + CookSoundBankToSandbox(SoundBank, WriteAdditionalFile); + } + for (const auto& Media : InCookedData.Media) + { + CookMediaToSandbox(Media, WriteAdditionalFile); + } + for (const auto& ExternalSource : InCookedData.ExternalSources) + { + CookExternalSourceToSandbox(ExternalSource, WriteAdditionalFile); + } + for (const auto& SwitchContainerLeaf : InCookedData.SwitchContainerLeaves) + { + UE_LOG(LogWwiseResourceCooker, Verbose, TEXT("Cooking Event %s %" PRIu32 " Switched Media"), *InCookedData.DebugName.ToString(), (uint32)InCookedData.EventId); + for (const auto& SoundBank : SwitchContainerLeaf.SoundBanks) + { + CookSoundBankToSandbox(SoundBank, WriteAdditionalFile); + } + for (const auto& Media : SwitchContainerLeaf.Media) + { + CookMediaToSandbox(Media, WriteAdditionalFile); + } + for (const auto& ExternalSource : SwitchContainerLeaf.ExternalSources) + { + CookExternalSourceToSandbox(ExternalSource, WriteAdditionalFile); + } + } + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("Done cooking Event %s %" PRIu32), *InCookedData.DebugName.ToString(), (uint32)InCookedData.EventId); +} + +void FWwiseResourceCookerImpl::CookExternalSourceToSandbox(const FWwiseExternalSourceCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) +{ + if (LIKELY(CookingCache && CookingCache->ExternalSourceManager)) + { + CookingCache->ExternalSourceManager->Cook(*this, InCookedData, WriteAdditionalFile, GetCurrentPlatform(), GetCurrentLanguage()); + } + else + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("No External Source Manager while cooking External Source %s %" PRIu32), *InCookedData.DebugName.ToString(), (uint32)InCookedData.Cookie); + } +} + +void FWwiseResourceCookerImpl::CookInitBankToSandbox(const FWwiseInitBankCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) +{ + UE_LOG(LogWwiseResourceCooker, Verbose, TEXT("Cooking Init SoundBank %s %" PRIu32), *InCookedData.DebugName.ToString(), (uint32)InCookedData.SoundBankId); + CookSoundBankToSandbox(InCookedData, WriteAdditionalFile); + + for (const auto& Media : InCookedData.Media) + { + CookMediaToSandbox(Media, WriteAdditionalFile); + } + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("Done cooking Init SoundBank %s %" PRIu32), *InCookedData.DebugName.ToString(), (uint32)InCookedData.SoundBankId); +} + +void FWwiseResourceCookerImpl::CookMediaToSandbox(const FWwiseMediaCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) +{ + UE_LOG(LogWwiseResourceCooker, Verbose, TEXT("Cooking Media %s %" PRIu32), *InCookedData.DebugName.ToString(), (uint32)InCookedData.MediaId); + + if (UNLIKELY(InCookedData.MediaPathName.IsNone())) + { + UE_LOG(LogWwiseResourceCooker, Fatal, TEXT("Empty pathname for Media %s %" PRIu32), *InCookedData.DebugName.ToString(), (uint32)InCookedData.MediaId); + return; + } + + auto* ResourceLoader = GetResourceLoader(); + if (UNLIKELY(!ResourceLoader)) + { + return; + } + const FString GeneratedSoundBanksPath = ResourceLoader->GetUnrealGeneratedSoundBanksPath(InCookedData.MediaPathName); + + CookFileToSandbox(GeneratedSoundBanksPath, InCookedData.MediaPathName, WriteAdditionalFile); +} + +void FWwiseResourceCookerImpl::CookShareSetToSandbox(const FWwiseShareSetCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) +{ + UE_LOG(LogWwiseResourceCooker, Verbose, TEXT("Cooking ShareSet %s %" PRIu32), *InCookedData.DebugName.ToString(), (uint32)InCookedData.ShareSetId); + for (const auto& SoundBank : InCookedData.SoundBanks) + { + CookSoundBankToSandbox(SoundBank, WriteAdditionalFile); + } + for (const auto& Media : InCookedData.Media) + { + CookMediaToSandbox(Media, WriteAdditionalFile); + } + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("Done cooking ShareSet %s %" PRIu32), *InCookedData.DebugName.ToString(), (uint32)InCookedData.ShareSetId); +} + +void FWwiseResourceCookerImpl::CookSoundBankToSandbox(const FWwiseSoundBankCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) +{ + UE_LOG(LogWwiseResourceCooker, Verbose, TEXT("Cooking SoundBank %s %" PRIu32), *InCookedData.DebugName.ToString(), (uint32)InCookedData.SoundBankId); + + if (UNLIKELY(InCookedData.SoundBankPathName.IsNone())) + { + UE_LOG(LogWwiseResourceCooker, Fatal, TEXT("Empty pathname for SoundBank %s %" PRIu32), *InCookedData.DebugName.ToString(), (uint32)InCookedData.SoundBankId); + return; + } + + auto* ResourceLoader = GetResourceLoader(); + if (UNLIKELY(!ResourceLoader)) + { + return; + } + const FString GeneratedSoundBanksPath = ResourceLoader->GetUnrealGeneratedSoundBanksPath(InCookedData.SoundBankPathName); + + CookFileToSandbox(GeneratedSoundBanksPath, InCookedData.SoundBankPathName, WriteAdditionalFile); +} + +void FWwiseResourceCookerImpl::CookFileToSandbox(const FString& InInputPathName, const FName& InOutputPathName, WriteAdditionalFileFunction WriteAdditionalFile, bool bInStageRelativeToContent) +{ + auto* ResourceLoader = GetResourceLoader(); + if (UNLIKELY(!ResourceLoader)) + { + return; + } + + FString StagePath = bInStageRelativeToContent + ? SandboxRootPath / InOutputPathName.ToString() + : SandboxRootPath / ResourceLoader->GetUnrealStagePath(InOutputPathName); + auto& StageFiles = CookingCache->StagedFiles; + + if (const auto* AlreadyStaged = StageFiles.Find(StagePath)) + { + if (*AlreadyStaged == InInputPathName) + { + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("Cook: Skipping already present file %s -> %s"), *InInputPathName, *StagePath); + } + else + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("Cook: Trying to stage two different files to the same path: [%s and %s] -> %s"), *InInputPathName, **AlreadyStaged, *StagePath); + } + + return; + } + StageFiles.Add(StagePath, InInputPathName); + + if (auto* MappedHandle = FPlatformFileManager::Get().GetPlatformFile().OpenMapped(*InInputPathName)) + { + auto* MappedRegion = MappedHandle->MapRegion(); + if (MappedRegion) + { + UE_LOG(LogWwiseResourceCooker, Display, TEXT("Adding file %s [%" PRIi64 " bytes]"), *StagePath, MappedRegion->GetMappedSize()); + WriteAdditionalFile(*StagePath, (void*)MappedRegion->GetMappedPtr(), MappedRegion->GetMappedSize()); + delete MappedRegion; + delete MappedHandle; + return; + } + else + { + delete MappedHandle; + } + } + + TArray Data; + if (!FFileHelper::LoadFileToArray(Data, *InInputPathName)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("Cook: Could not read file %s"), *InInputPathName); + return; + } + + UE_LOG(LogWwiseResourceCooker, Display, TEXT("Adding file %s [%" PRIi64 " bytes]"), *StagePath, (int64)Data.Num()); + WriteAdditionalFile(*StagePath, (void*)Data.GetData(), Data.Num()); +} + +bool FWwiseResourceCookerImpl::GetAcousticTextureCookedData(FWwiseAcousticTextureCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) const +{ + const auto* ProjectDatabase = GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetAcousticTextureCookedData (%s %" PRIu32 " %s): ProjectDatabase not initialized"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + const FWwiseDataStructureScopeLock DataStructure(*ProjectDatabase); + const auto* PlatformData = DataStructure.GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetAcousticTextureCookedData (%s %" PRIu32 " %s): No data for platform"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + FWwiseRefAcousticTexture AcousticTextureRef; + + if (UNLIKELY(!PlatformData->GetRef(AcousticTextureRef, FWwiseSharedLanguageId(), InInfo))) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetAcousticTextureCookedData (%s %" PRIu32 " %s): No acoustic texture data found"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + const auto* AcousticTexture = AcousticTextureRef.GetAcousticTexture(); + + OutCookedData.ShortId = AcousticTexture->Id; + if (ExportDebugNameRule == EWwiseExportDebugNameRule::Release) + { + OutCookedData.DebugName = FName(); + } + else + { + OutCookedData.DebugName = FName((ExportDebugNameRule == EWwiseExportDebugNameRule::Name) ? AcousticTexture->Name : AcousticTexture->ObjectPath); + } + + return true; +} + +bool FWwiseResourceCookerImpl::GetAuxBusCookedData(FWwiseLocalizedAuxBusCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) const +{ + const auto* ProjectDatabase = GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetAuxBusCookedData (%s %" PRIu32 " %s): ProjectDatabase not initialized"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + const FWwiseDataStructureScopeLock DataStructure(*ProjectDatabase); + const auto* PlatformData = DataStructure.GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetAuxBusCookedData (%s %" PRIu32 " %s): No data for platform"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + const auto* PlatformInfo = PlatformData->PlatformRef.GetPlatformInfo(); + if (UNLIKELY(!PlatformInfo)) return false; + + const TSet& Languages = DataStructure.GetLanguages(); + + TMap> RefLanguageMap; + PlatformData->GetRefMap(RefLanguageMap, Languages, InInfo); + if (UNLIKELY(RefLanguageMap.Num() == 0)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetAuxBusCookedData (%s %" PRIu32 " %s): No ref found"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + OutCookedData.AuxBusLanguageMap.Empty(RefLanguageMap.Num()); + + for (auto& Ref : RefLanguageMap) + { + FWwiseAuxBusCookedData CookedData; + + TSet SoundBankSet; + TSet MediaSet; + TSet& AuxBusses = Ref.Value; + + if (UNLIKELY(AuxBusses.Num() == 0)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetAuxBusCookedData (%s %" PRIu32 " %s): Empty ref for language"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + // Set up basic global Aux Bus information + { + TSet::TConstIterator FirstAuxBus(AuxBusses); + CookedData.AuxBusId = FirstAuxBus->AuxBusId(); + if (ExportDebugNameRule == EWwiseExportDebugNameRule::Release) + { + OutCookedData.DebugName = FName(); + } + else + { + CookedData.DebugName = FName((ExportDebugNameRule == EWwiseExportDebugNameRule::Name) ? FirstAuxBus->AuxBusName() : FirstAuxBus->AuxBusObjectPath()); + OutCookedData.DebugName = CookedData.DebugName; + } + OutCookedData.AuxBusId = CookedData.AuxBusId; + } + + for (auto& AuxBusRef : AuxBusses) + { + const auto* AuxBus = AuxBusRef.GetAuxBus(); + if (UNLIKELY(!AuxBus)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetAuxBusCookedData (%s %" PRIu32 " %s): Could not get AuxBus from Ref"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + continue; + } + + TSet SubAuxBusRefs; + AuxBusRef.GetAllAuxBusRefs(SubAuxBusRefs, PlatformData->AuxBusses); + for (const auto* SubAuxBusRef : SubAuxBusRefs) + { + const auto* SoundBank = SubAuxBusRef->GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetAuxBusCookedData (%s %" PRIu32 " %s): Could not get SoundBank from Ref"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + continue; + } + + if (!SoundBank->IsInitBank()) + { + FWwiseSoundBankCookedData SoundBankCookedData; + if (UNLIKELY(!FillSoundBankBaseInfo(SoundBankCookedData, *PlatformInfo, *SoundBank))) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetAuxBusCookedData (%s %" PRIu32 " %s): Could not fill SoundBank from Data"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + continue; + } + SoundBankSet.Add(SoundBankCookedData); + } + + { + WwiseCustomPluginIdsMap CustomPluginsRefs = SubAuxBusRef->GetAuxBusCustomPlugins(PlatformData->CustomPlugins); + for (const auto& Plugin : CustomPluginsRefs) + { + const WwiseMediaIdsMap MediaRefs = Plugin.Value.GetPluginMedia(PlatformData->MediaFiles); + for (const auto& MediaRef : MediaRefs) + { + if (UNLIKELY(!AddRequirementsForMedia(SoundBankSet, MediaSet, MediaRef.Value, FWwiseSharedLanguageId(), *PlatformData))) + { + return false; + } + } + } + } + + { + WwisePluginShareSetIdsMap ShareSetRefs = SubAuxBusRef->GetAuxBusPluginShareSets(PlatformData->PluginShareSets); + for (const auto& ShareSet : ShareSetRefs) + { + const WwiseMediaIdsMap MediaRefs = ShareSet.Value.GetPluginMedia(PlatformData->MediaFiles); + for (const auto& MediaRef : MediaRefs) + { + if (UNLIKELY(!AddRequirementsForMedia(SoundBankSet, MediaSet, MediaRef.Value, FWwiseSharedLanguageId(), *PlatformData))) + { + return false; + } + } + } + } + + { + WwiseAudioDeviceIdsMap AudioDevicesRefs = SubAuxBusRef->GetAuxBusAudioDevices(PlatformData->AudioDevices); + for (const auto& AudioDevice : AudioDevicesRefs) + { + const WwiseMediaIdsMap MediaRefs = AudioDevice.Value.GetPluginMedia(PlatformData->MediaFiles); + for (const auto& MediaRef : MediaRefs) + { + if (UNLIKELY(!AddRequirementsForMedia(SoundBankSet, MediaSet, MediaRef.Value, FWwiseSharedLanguageId(), *PlatformData))) + { + return false; + } + } + } + } + } + } + CookedData.SoundBanks = SoundBankSet.Array(); + CookedData.Media = MediaSet.Array(); + + OutCookedData.AuxBusLanguageMap.Add(FWwiseLanguageCookedData(Ref.Key.GetLanguageId(), Ref.Key.GetLanguageName(), Ref.Key.LanguageRequirement), MoveTemp(CookedData)); + } + + if (UNLIKELY(OutCookedData.AuxBusLanguageMap.Num() == 0)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetAuxBusCookedData (%s %" PRIu32 " %s): No AuxBus"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + // Make this a SFX if all CookedData are identical + { + auto& Map = OutCookedData.AuxBusLanguageMap; + TArray Keys; + Map.GetKeys(Keys); + + auto LhsKey = Keys.Pop(false); + const auto* Lhs = Map.Find(LhsKey); + while (Keys.Num() > 0) + { + auto RhsKey = Keys.Pop(false); + const auto* Rhs = Map.Find(RhsKey); + + if (Lhs->AuxBusId != Rhs->AuxBusId + || Lhs->DebugName != Rhs->DebugName + || Lhs->SoundBanks.Num() != Rhs->SoundBanks.Num() + || Lhs->Media.Num() != Rhs->Media.Num()) + { + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetAuxBusCookedData (%s %" PRIu32 " %s): AuxBus has languages"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return true; + } + for (const auto& Elem : Lhs->SoundBanks) + { + if (!Rhs->SoundBanks.Contains(Elem)) + { + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetAuxBusCookedData (%s %" PRIu32 " %s): AuxBus has languages due to banks"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return true; + } + } + for (const auto& Elem : Lhs->Media) + { + if (!Rhs->Media.Contains(Elem)) + { + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetAuxBusCookedData (%s %" PRIu32 " %s): AuxBus has languages due to media"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return true; + } + } + } + + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetAuxBusCookedData (%s %" PRIu32 " %s): AuxBus is a SFX"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + std::remove_reference_t SfxMap; + SfxMap.Add(FWwiseLanguageCookedData::Sfx, *Lhs); + + Map = SfxMap; + } + + return true; +} + +bool FWwiseResourceCookerImpl::GetEventCookedData(FWwiseLocalizedEventCookedData& OutCookedData, const FWwiseEventInfo& InInfo) const +{ + const auto* ProjectDatabase = GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetEventCookedData (%s %" PRIu32 " %s): ProjectDatabase not initialized"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + const FWwiseDataStructureScopeLock DataStructure(*ProjectDatabase); + const auto* PlatformData = DataStructure.GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetEventCookedData (%s %" PRIu32 " %s): No data for platform"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + const TSet& Languages = DataStructure.GetLanguages(); + + const auto* PlatformInfo = PlatformData->PlatformRef.GetPlatformInfo(); + if (UNLIKELY(!PlatformInfo)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetEventCookedData (%s %" PRIu32 " %s): No Platform Info"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + TMap> RefLanguageMap; + PlatformData->GetRefMap(RefLanguageMap, Languages, InInfo); + if (UNLIKELY(RefLanguageMap.Num() == 0)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetEventCookedData (%s %" PRIu32 " %s): No ref found"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + OutCookedData.EventLanguageMap.Empty(RefLanguageMap.Num()); + UE_LOG(LogWwiseResourceCooker, Verbose, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Adding %d languages to map"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString(), RefLanguageMap.Num()); + + for (auto& Ref : RefLanguageMap) + { + FWwiseEventCookedData CookedData; + + TSet SoundBankSet; + TSet MediaSet; + + const FWwiseSharedLanguageId& LanguageId = Ref.Key; + TSet& Events = Ref.Value; + WwiseSwitchContainerArray SwitchContainerRefs; + + if (UNLIKELY(Events.Num() == 0)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Empty ref for language"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + // Set up basic global Event information + { + TSet::TConstIterator FirstEvent(Events); + CookedData.EventId = FirstEvent->EventId(); + if (ExportDebugNameRule != EWwiseExportDebugNameRule::Release) + { + CookedData.DebugName = FName((ExportDebugNameRule == EWwiseExportDebugNameRule::Name) ? FirstEvent->EventName() : FirstEvent->EventObjectPath()); + OutCookedData.DebugName = CookedData.DebugName; + } + + OutCookedData.EventId = CookedData.EventId; + SwitchContainerRefs = FirstEvent->GetSwitchContainers(PlatformData->SwitchContainersByEvent); + } + + // Add extra events recursively + { + TSet DiffEvents = Events; + while (true) + { + bool bHaveMore = false; + TSet OldEvents(Events); + for (auto& EventRef : OldEvents) + { + const FWwiseMetadataEvent* Event = EventRef.GetEvent(); + if (UNLIKELY(!Event)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Could not get Event from Ref"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + for (const auto& ActionPostEvent : Event->ActionPostEvent) + { + bool bHaveMoreInThisEvent = PlatformData->GetRef(Events, LanguageId, FWwiseEventInfo(ActionPostEvent.Id, ActionPostEvent.Name)); + bHaveMore = bHaveMore || bHaveMoreInThisEvent; + } + } + if (bHaveMore) + { + DiffEvents = Events.Difference(OldEvents); + if (DiffEvents.Num() == 0) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetEventCookedData (%s %" PRIu32 " %s): GetRef should return false when no more additional Refs"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + break; + } + for (auto& EventRef : DiffEvents) + { + SwitchContainerRefs.Append(EventRef.GetSwitchContainers(PlatformData->SwitchContainersByEvent)); + } + } + else + { + break; + } + } + } + + // Add mandatory SoundBank information + TSet ExternalSourceSet; + TSet RequiredGroupValueSet; + for (auto& EventRef : Events) + { + const FWwiseMetadataEvent* Event = EventRef.GetEvent(); + if (UNLIKELY(!Event)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Could not get Event from Ref"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + if (LIKELY(Event->IsMandatory()) || LIKELY(Events.Num() == 1)) + { + // Add main SoundBank + { + const auto* SoundBank = EventRef.GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Could not get SoundBank from Ref"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + if (!SoundBank->IsInitBank()) + { + FWwiseSoundBankCookedData MainSoundBank; + if (UNLIKELY(!FillSoundBankBaseInfo(MainSoundBank, *PlatformInfo, *SoundBank))) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Could not fill SoundBank from Data"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + SoundBankSet.Add(MainSoundBank); + } + } + + // Get Aux Bus banks & media + { + WwiseAuxBusIdsMap AuxBusRefs = EventRef.GetEventAuxBusses(PlatformData->AuxBusses); + TSet SubAuxBusRefs; + for (const auto& AuxBusRef : AuxBusRefs) + { + AuxBusRef.Value.GetAllAuxBusRefs(SubAuxBusRefs, PlatformData->AuxBusses); + } + for (const auto* SubAuxBusRef : SubAuxBusRefs) + { + const auto* SoundBank = SubAuxBusRef->GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Could not get SoundBank from Ref"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + if (!SoundBank->IsInitBank()) + { + FWwiseSoundBankCookedData SoundBankCookedData; + if (UNLIKELY(!FillSoundBankBaseInfo(SoundBankCookedData, *PlatformInfo, *SoundBank))) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Could not fill SoundBank from Data"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + SoundBankSet.Add(SoundBankCookedData); + } + + { + WwiseCustomPluginIdsMap CustomPluginsRefs = SubAuxBusRef->GetAuxBusCustomPlugins(PlatformData->CustomPlugins); + for (const auto& Plugin : CustomPluginsRefs) + { + const WwiseMediaIdsMap MediaRefs = Plugin.Value.GetPluginMedia(PlatformData->MediaFiles); + for (const auto& MediaRef : MediaRefs) + { + if (UNLIKELY(!AddRequirementsForMedia(SoundBankSet, MediaSet, MediaRef.Value, FWwiseSharedLanguageId(), *PlatformData))) + { + return false; + } + } + } + } + + { + WwisePluginShareSetIdsMap ShareSetRefs = SubAuxBusRef->GetAuxBusPluginShareSets(PlatformData->PluginShareSets); + for (const auto& ShareSet : ShareSetRefs) + { + const WwiseMediaIdsMap MediaRefs = ShareSet.Value.GetPluginMedia(PlatformData->MediaFiles); + for (const auto& MediaRef : MediaRefs) + { + if (UNLIKELY(!AddRequirementsForMedia(SoundBankSet, MediaSet, MediaRef.Value, FWwiseSharedLanguageId(), *PlatformData))) + { + return false; + } + } + } + } + + { + WwiseAudioDeviceIdsMap AudioDevicesRefs = SubAuxBusRef->GetAuxBusAudioDevices(PlatformData->AudioDevices); + for (const auto& AudioDevice : AudioDevicesRefs) + { + const WwiseMediaIdsMap MediaRefs = AudioDevice.Value.GetPluginMedia(PlatformData->MediaFiles); + for (const auto& MediaRef : MediaRefs) + { + if (UNLIKELY(!AddRequirementsForMedia(SoundBankSet, MediaSet, MediaRef.Value, FWwiseSharedLanguageId(), *PlatformData))) + { + return false; + } + } + } + } + } + } + + // Get media + { + WwiseMediaIdsMap MediaRefs = EventRef.GetEventMedia(PlatformData->MediaFiles); + for (const auto& MediaRef : MediaRefs) + { + if (UNLIKELY(!AddRequirementsForMedia(SoundBankSet, MediaSet, MediaRef.Value, LanguageId, *PlatformData))) + { + return false; + } + } + } + + // Get Media from custom plugins + { + WwiseCustomPluginIdsMap CustomPluginsRefs = EventRef.GetEventCustomPlugins(PlatformData->CustomPlugins); + for (const auto& Plugin : CustomPluginsRefs) + { + const WwiseMediaIdsMap MediaRefs = Plugin.Value.GetPluginMedia(PlatformData->MediaFiles); + for (const auto& MediaRef : MediaRefs) + { + if (UNLIKELY(!AddRequirementsForMedia(SoundBankSet, MediaSet, MediaRef.Value, LanguageId, *PlatformData))) + { + return false; + } + } + } + } + + // Get Media from plugin ShareSets + { + WwisePluginShareSetIdsMap ShareSetRefs = EventRef.GetEventPluginShareSets(PlatformData->PluginShareSets); + for (const auto& ShareSet : ShareSetRefs) + { + const WwiseMediaIdsMap MediaRefs = ShareSet.Value.GetPluginMedia(PlatformData->MediaFiles); + for (const auto& MediaRef : MediaRefs) + { + if (UNLIKELY(!AddRequirementsForMedia(SoundBankSet, MediaSet, MediaRef.Value, LanguageId, *PlatformData))) + { + return false; + } + } + } + } + + // Get Media from audio devices + { + WwiseAudioDeviceIdsMap AudioDevicesRefs = EventRef.GetEventAudioDevices(PlatformData->AudioDevices); + for (const auto& AudioDevice : AudioDevicesRefs) + { + const WwiseMediaIdsMap MediaRefs = AudioDevice.Value.GetPluginMedia(PlatformData->MediaFiles); + for (const auto& MediaRef : MediaRefs) + { + if (UNLIKELY(!AddRequirementsForMedia(SoundBankSet, MediaSet, MediaRef.Value, LanguageId, *PlatformData))) + { + return false; + } + } + } + } + + // Get External Sources + { + WwiseExternalSourceIdsMap ExternalSourceRefs = EventRef.GetEventExternalSources(PlatformData->ExternalSources); + for (const auto& ExternalSourceRef : ExternalSourceRefs) + { + if (UNLIKELY(!AddRequirementsForExternalSource(ExternalSourceSet, ExternalSourceRef.Value))) + { + return false; + } + } + } + + // Get required GroupValues + { + for (const auto& Switch : EventRef.GetActionSetSwitch(PlatformData->Switches)) + { + if (LIKELY(Switch.Value.IsValid())) + { + RequiredGroupValueSet.Add(FWwiseAnyRef::Create(Switch.Value)); + } + } + + for (const auto& State : EventRef.GetActionSetState(PlatformData->States)) + { + if (LIKELY(State.Value.IsValid())) + { + RequiredGroupValueSet.Add(FWwiseAnyRef::Create(State.Value)); + } + } + } + } + } + + // Get Switched Media, negating required switches. + { + TMap> SwitchValuesMap; + + for (const auto& SwitchContainerRef : SwitchContainerRefs) + { + const auto* SwitchContainer = SwitchContainerRef.GetSwitchContainer(); + if (UNLIKELY(!SwitchContainer)) + { + return false; + } + + auto SwitchValues = TSet(SwitchContainerRef.GetSwitchValues(PlatformData->Switches, PlatformData->States)); + + TSet SwitchesToRemove; + for (const auto& SwitchValue : SwitchValues) + { + // Remove all SwitchValues if we load them all by default + if (InInfo.SwitchContainerLoading == EWwiseEventSwitchContainerLoading::AlwaysLoad) + { + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Skip value %s (%" PRIu32 ":%" PRIu32 "): Event Switch Container set to AlwaysLoad"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString(), *SwitchValue.GetName().ToString(), SwitchValue.GetGroupId(), SwitchValue.GetId()); + + SwitchesToRemove.Add(SwitchValue); + continue; + } + + // Remove SwitchValues that are already present in RequiredGroupValueSet + if (RequiredGroupValueSet.Contains(SwitchValue)) + { + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Skip value %s (%" PRIu32 ":%" PRIu32 "): Already in the required set"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString(), *SwitchValue.GetName().ToString(), SwitchValue.GetGroupId(), SwitchValue.GetId()); + + SwitchesToRemove.Add(SwitchValue); + continue; + } + + // Remove SwitchValues that have an ID of "0" (wildcard in music) + if (SwitchValue.GetId() == 0) + { + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Skip value %s (%" PRIu32 ":%" PRIu32 "): Wildcard ID"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString(), *SwitchValue.GetName().ToString(), SwitchValue.GetGroupId(), SwitchValue.GetId()); + + SwitchesToRemove.Add(SwitchValue); + continue; + } + + // Remove Switch groups that are controlled by a Game Parameter (RTPC) + if (SwitchValue.GetType() == EWwiseRefType::Switch) + { + const auto* SwitchRef = SwitchValue.GetSwitchRef(); + check(SwitchRef); + + if (SwitchRef->IsControlledByGameParameter()) + { + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Skip value %s (%" PRIu32 ":%" PRIu32 "): Controlled by Game Parameter"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString(), *SwitchValue.GetName().ToString(), SwitchValue.GetGroupId(), SwitchValue.GetId()); + + SwitchesToRemove.Add(SwitchValue); + continue; + } + } + } + SwitchValues = SwitchValues.Difference(SwitchesToRemove); + + if (SwitchValues.Num() == 0) + { + // Media and SoundBank are compulsory. Add them so they are always loaded. + const auto* SoundBank = SwitchContainerRef.GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Could not get SoundBank from Switch Container Ref"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + if (!SoundBank->IsInitBank()) + { + FWwiseSoundBankCookedData SwitchContainerSoundBank; + if (UNLIKELY(!FillSoundBankBaseInfo(SwitchContainerSoundBank, *PlatformInfo, *SoundBank))) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Could not fill SoundBank from Switch Container Data"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + SoundBankSet.Add(SwitchContainerSoundBank); + } + + { + TArray MediaToAdd; + SwitchContainerRef.GetSwitchContainerMedia(PlatformData->MediaFiles).GenerateValueArray(MediaToAdd); + for (const auto& MediaRef : MediaToAdd) + { + if (UNLIKELY(!AddRequirementsForMedia(SoundBankSet, MediaSet, MediaRef, Ref.Key, *PlatformData))) + { + return false; + } + } + } + + { + WwiseCustomPluginIdsMap CustomPluginsRefs = SwitchContainerRef.GetSwitchContainerCustomPlugins(PlatformData->CustomPlugins); + for (const auto& Plugin : CustomPluginsRefs) + { + const WwiseMediaIdsMap MediaRefs = Plugin.Value.GetPluginMedia(PlatformData->MediaFiles); + for (const auto& MediaRef : MediaRefs) + { + if (UNLIKELY(!AddRequirementsForMedia(SoundBankSet, MediaSet, MediaRef.Value, LanguageId, *PlatformData))) + { + return false; + } + } + } + } + + { + WwisePluginShareSetIdsMap ShareSetRefs = SwitchContainerRef.GetSwitchContainerPluginShareSets(PlatformData->PluginShareSets); + for (const auto& ShareSet : ShareSetRefs) + { + const WwiseMediaIdsMap MediaRefs = ShareSet.Value.GetPluginMedia(PlatformData->MediaFiles); + for (const auto& MediaRef : MediaRefs) + { + if (UNLIKELY(!AddRequirementsForMedia(SoundBankSet, MediaSet, MediaRef.Value, LanguageId, *PlatformData))) + { + return false; + } + } + } + } + + { + WwiseAudioDeviceIdsMap AudioDevicesRefs = SwitchContainerRef.GetSwitchContainerAudioDevices(PlatformData->AudioDevices); + for (const auto& AudioDevice : AudioDevicesRefs) + { + const WwiseMediaIdsMap MediaRefs = AudioDevice.Value.GetPluginMedia(PlatformData->MediaFiles); + for (const auto& MediaRef : MediaRefs) + { + if (UNLIKELY(!AddRequirementsForMedia(SoundBankSet, MediaSet, MediaRef.Value, LanguageId, *PlatformData))) + { + return false; + } + } + } + } + + TArray ExternalSourcesToAdd; + SwitchContainerRef.GetSwitchContainerExternalSources(PlatformData->ExternalSources).GenerateValueArray(ExternalSourcesToAdd); + + for (const auto& ExternalSourceRef : ExternalSourcesToAdd) + { + if (UNLIKELY(!AddRequirementsForExternalSource(ExternalSourceSet, ExternalSourceRef))) + { + return false; + } + } + } + else + { + // Media is optional. Will process later + SwitchValuesMap.Add(SwitchContainerRef, SwitchValues); + } + } + + // Process Switch Containers that seemingly contain additional media and conditions + for (const auto& SwitchContainerRef : SwitchContainerRefs) + { + const auto* SwitchValues = SwitchValuesMap.Find(SwitchContainerRef); + if (!SwitchValues) + { + continue; + } + + // Prepare media and main SoundBank to add + TSet SoundBankSetToAdd; + TSet MediaSetToAdd; + TSet ExternalSourceSetToAdd; + + const auto* SoundBank = SwitchContainerRef.GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Could not get SoundBank from Switch Container Ref"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + if (!SoundBank->IsInitBank()) + { + FWwiseSoundBankCookedData SwitchContainerSoundBank; + if (UNLIKELY(!FillSoundBankBaseInfo(SwitchContainerSoundBank, *PlatformInfo, *SoundBank))) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Could not fill SoundBank from Switch Container Data"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + SoundBankSetToAdd.Add(SwitchContainerSoundBank); + } + + { + TArray MediaToAdd; + SwitchContainerRef.GetSwitchContainerMedia(PlatformData->MediaFiles).GenerateValueArray(MediaToAdd); + FWwiseMediaCookedData MediaCookedData; + for (const auto& MediaRef : MediaToAdd) + { + if (UNLIKELY(!AddRequirementsForMedia(SoundBankSetToAdd, MediaSetToAdd, MediaRef, Ref.Key, *PlatformData))) + { + return false; + } + } + } + + { + WwiseCustomPluginIdsMap CustomPluginsRefs = SwitchContainerRef.GetSwitchContainerCustomPlugins(PlatformData->CustomPlugins); + for (const auto& Plugin : CustomPluginsRefs) + { + const WwiseMediaIdsMap MediaRefs = Plugin.Value.GetPluginMedia(PlatformData->MediaFiles); + for (const auto& MediaRef : MediaRefs) + { + if (UNLIKELY(!AddRequirementsForMedia(SoundBankSetToAdd, MediaSetToAdd, MediaRef.Value, LanguageId, *PlatformData))) + { + return false; + } + } + } + } + + { + WwisePluginShareSetIdsMap ShareSetRefs = SwitchContainerRef.GetSwitchContainerPluginShareSets(PlatformData->PluginShareSets); + for (const auto& ShareSet : ShareSetRefs) + { + const WwiseMediaIdsMap MediaRefs = ShareSet.Value.GetPluginMedia(PlatformData->MediaFiles); + for (const auto& MediaRef : MediaRefs) + { + if (UNLIKELY(!AddRequirementsForMedia(SoundBankSetToAdd, MediaSetToAdd, MediaRef.Value, LanguageId, *PlatformData))) + { + return false; + } + } + } + } + + { + WwiseAudioDeviceIdsMap AudioDevicesRefs = SwitchContainerRef.GetSwitchContainerAudioDevices(PlatformData->AudioDevices); + for (const auto& AudioDevice : AudioDevicesRefs) + { + const WwiseMediaIdsMap MediaRefs = AudioDevice.Value.GetPluginMedia(PlatformData->MediaFiles); + for (const auto& MediaRef : MediaRefs) + { + if (UNLIKELY(!AddRequirementsForMedia(SoundBankSetToAdd, MediaSetToAdd, MediaRef.Value, LanguageId, *PlatformData))) + { + return false; + } + } + } + } + + + TArray ExternalSourcesToAdd; + SwitchContainerRef.GetSwitchContainerExternalSources(PlatformData->ExternalSources).GenerateValueArray(ExternalSourcesToAdd); + + for (const auto& ExternalSourceRef : ExternalSourcesToAdd) + { + if (UNLIKELY(!AddRequirementsForExternalSource(ExternalSourceSetToAdd, ExternalSourceRef))) + { + return false; + } + } + + SoundBankSetToAdd = SoundBankSetToAdd.Difference(SoundBankSet); + MediaSetToAdd = MediaSetToAdd.Difference(MediaSet); + ExternalSourceSetToAdd = ExternalSourceSetToAdd.Difference(ExternalSourceSet); + + // Have we already included all the external banks and media + if (SoundBankSetToAdd.Num() == 0 && MediaSetToAdd.Num() == 0 && ExternalSourceSetToAdd.Num() == 0) + { + continue; + } + + // Fill up SwitchContainerCookedData and add it to SwitchContainerLeaves + FWwiseSwitchContainerLeafCookedData SwitchContainerCookedData; + for (const auto& SwitchValue : *SwitchValues) + { + FWwiseGroupValueCookedData SwitchCookedData; + switch (SwitchValue.GetType()) + { + case EWwiseRefType::Switch: SwitchCookedData.Type = EWwiseGroupType::Switch; break; + case EWwiseRefType::State: SwitchCookedData.Type = EWwiseGroupType::State; break; + default: SwitchCookedData.Type = EWwiseGroupType::Unknown; + } + SwitchCookedData.GroupId = SwitchValue.GetGroupId(); + SwitchCookedData.Id = SwitchValue.GetId(); + if (ExportDebugNameRule == EWwiseExportDebugNameRule::Release) + { + SwitchCookedData.DebugName = FName(); + } + else + { + SwitchCookedData.DebugName = FName((ExportDebugNameRule == EWwiseExportDebugNameRule::Name) ? SwitchValue.GetName() : SwitchValue.GetObjectPath()); + } + SwitchContainerCookedData.GroupValueSet.Add(MoveTemp(SwitchCookedData)); + } + if (auto* ExistingSwitchedMedia = CookedData.SwitchContainerLeaves.FindByPredicate([&SwitchContainerCookedData](const FWwiseSwitchContainerLeafCookedData& RhsValue) + { + return RhsValue.GroupValueSet.Difference(SwitchContainerCookedData.GroupValueSet).Num() == 0 + && SwitchContainerCookedData.GroupValueSet.Difference(RhsValue.GroupValueSet).Num() == 0; + })) + { + SoundBankSetToAdd.Append(ExistingSwitchedMedia->SoundBanks); + MediaSetToAdd.Append(ExistingSwitchedMedia->Media); + ExternalSourceSetToAdd.Append(ExistingSwitchedMedia->ExternalSources); + + ExistingSwitchedMedia->SoundBanks = SoundBankSetToAdd.Array(); + ExistingSwitchedMedia->Media = MediaSetToAdd.Array(); + ExistingSwitchedMedia->ExternalSources = ExternalSourceSetToAdd.Array(); + } + else + { + SwitchContainerCookedData.SoundBanks = SoundBankSetToAdd.Array(); + SwitchContainerCookedData.Media = MediaSetToAdd.Array(); + SwitchContainerCookedData.ExternalSources = ExternalSourceSetToAdd.Array(); + CookedData.SwitchContainerLeaves.Add(MoveTemp(SwitchContainerCookedData)); + } + } + } + + // Finalize banks and media + CookedData.SoundBanks.Append(SoundBankSet.Array()); + if (CookedData.SoundBanks.Num() == 0) + { + UE_LOG(LogWwiseResourceCooker, Log, TEXT("GetEventCookedData (%s %" PRIu32 " %s): No SoundBank set for Event. Unless Switch values are properly set, no SoundBank will be loaded."), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + } + CookedData.Media.Append(MediaSet.Array()); + CookedData.ExternalSources.Append(ExternalSourceSet.Array()); + + for (const auto& SwitchRef : RequiredGroupValueSet) + { + FWwiseGroupValueCookedData SwitchCookedData; + switch (SwitchRef.GetType()) + { + case EWwiseRefType::Switch: SwitchCookedData.Type = EWwiseGroupType::Switch; break; + case EWwiseRefType::State: SwitchCookedData.Type = EWwiseGroupType::State; break; + default: SwitchCookedData.Type = EWwiseGroupType::Unknown; + } + SwitchCookedData.GroupId = SwitchRef.GetGroupId(); + SwitchCookedData.Id = SwitchRef.GetId(); + if (ExportDebugNameRule == EWwiseExportDebugNameRule::Release) + { + SwitchCookedData.DebugName = FName(); + } + else + { + SwitchCookedData.DebugName = FName((ExportDebugNameRule == EWwiseExportDebugNameRule::Name) ? SwitchRef.GetName() : SwitchRef.GetObjectPath()); + } + + CookedData.RequiredGroupValueSet.Add(MoveTemp(SwitchCookedData)); + } + + CookedData.DestroyOptions = InInfo.DestroyOptions; + + OutCookedData.EventLanguageMap.Add(FWwiseLanguageCookedData(LanguageId.GetLanguageId(), LanguageId.GetLanguageName(), LanguageId.LanguageRequirement), MoveTemp(CookedData)); + } + + if (UNLIKELY(OutCookedData.EventLanguageMap.Num() == 0)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetEventCookedData (%s %" PRIu32 " %s): No Event"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + // Make this a SFX if all CookedData are identical + { + auto& Map = OutCookedData.EventLanguageMap; + TArray Keys; + Map.GetKeys(Keys); + + auto LhsKey = Keys.Pop(false); + const auto* Lhs = Map.Find(LhsKey); + while (Keys.Num() > 0) + { + auto RhsKey = Keys.Pop(false); + const auto* Rhs = Map.Find(RhsKey); + + if (Lhs->EventId != Rhs->EventId + || Lhs->DebugName != Rhs->DebugName + || Lhs->SoundBanks.Num() != Rhs->SoundBanks.Num() + || Lhs->ExternalSources.Num() != Rhs->ExternalSources.Num() + || Lhs->Media.Num() != Rhs->Media.Num() + || Lhs->RequiredGroupValueSet.Num() != Rhs->RequiredGroupValueSet.Num() + || Lhs->SwitchContainerLeaves.Num() != Rhs->SwitchContainerLeaves.Num()) + { + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Event has languages"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return true; + } + for (const auto& Elem : Lhs->SoundBanks) + { + if (!Rhs->SoundBanks.Contains(Elem)) + { + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Event has languages due to banks"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return true; + } + } + for (const auto& Elem : Lhs->ExternalSources) + { + if (!Rhs->ExternalSources.Contains(Elem)) + { + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Event has languages due to external sources"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return true; + } + } + for (const auto& Elem : Lhs->Media) + { + if (!Rhs->Media.Contains(Elem)) + { + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Event has languages due to media"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return true; + } + } + for (const auto& Elem : Lhs->RequiredGroupValueSet) + { + if (!Rhs->RequiredGroupValueSet.Contains(Elem)) + { + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Event has languages due to required group values"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return true; + } + } + for (const auto& LhsLeaf : Lhs->SwitchContainerLeaves) + { + const auto RhsLeafIndex = Rhs->SwitchContainerLeaves.Find(LhsLeaf); + if (RhsLeafIndex == INDEX_NONE) + { + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Event has languages due to switch container"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return true; + } + const auto& RhsLeaf = Rhs->SwitchContainerLeaves[RhsLeafIndex]; + + for (const auto& Elem : LhsLeaf.SoundBanks) + { + if (!RhsLeaf.SoundBanks.Contains(Elem)) + { + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Event has languages due to banks in switch container"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return true; + } + } + for (const auto& Elem : LhsLeaf.ExternalSources) + { + if (!RhsLeaf.ExternalSources.Contains(Elem)) + { + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Event has languages due to external sources in switch container"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return true; + } + } + for (const auto& Elem : LhsLeaf.Media) + { + if (!RhsLeaf.Media.Contains(Elem)) + { + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Event has languages due to media in switch container"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return true; + } + } + } + } + + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetEventCookedData (%s %" PRIu32 " %s): Event is a SFX"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + std::remove_reference_t SfxMap; + SfxMap.Add(FWwiseLanguageCookedData::Sfx, *Lhs); + + Map = SfxMap; + } + + return true; +} + +bool FWwiseResourceCookerImpl::GetExternalSourceCookedData(FWwiseExternalSourceCookedData& OutCookedData, uint32 InCookie) const +{ + const auto* ProjectDatabase = GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetExternalSourceCookedData (%" PRIu32 "): ProjectDatabase not initialized"), + InCookie); + return false; + } + + const FWwiseDataStructureScopeLock DataStructure(*ProjectDatabase); + const auto* PlatformData = DataStructure.GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetExternalSourceCookedData (%" PRIu32 "): No data for platform"), + InCookie); + return false; + } + + const auto LocalizableId = FWwiseDatabaseLocalizableIdKey(InCookie, FWwiseDatabaseLocalizableIdKey::GENERIC_LANGUAGE); + const auto* ExternalSourceRef = PlatformData->ExternalSources.Find(LocalizableId); + if (UNLIKELY(!ExternalSourceRef)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetExternalSourceCookedData (%" PRIu32 "): Could not find External Source"), + InCookie); + return false; + } + const auto* ExternalSource = ExternalSourceRef->GetExternalSource(); + if (UNLIKELY(!ExternalSource)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetExternalSourceCookedData (%" PRIu32 "): Could not get External Source"), + InCookie); + return false; + } + + if (UNLIKELY(!FillExternalSourceBaseInfo(OutCookedData, *ExternalSource))) + { + return false; + } + return true; +} + +bool FWwiseResourceCookerImpl::GetGameParameterCookedData(FWwiseGameParameterCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) const +{ + const auto* ProjectDatabase = GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetGameParameterCookedData (%s %" PRIu32 " %s): ProjectDatabase not initialized"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + const FWwiseDataStructureScopeLock DataStructure(*ProjectDatabase); + const auto* PlatformData = DataStructure.GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetGameParameterCookedData (%s %" PRIu32 " %s): No data for platform"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + FWwiseRefGameParameter GameParameterRef; + + if (UNLIKELY(!PlatformData->GetRef(GameParameterRef, FWwiseSharedLanguageId(), InInfo))) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetGameParameterCookedData (%s %" PRIu32 " %s): No game parameter found"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + const auto* GameParameter = GameParameterRef.GetGameParameter(); + + OutCookedData.ShortId = GameParameter->Id; + if (ExportDebugNameRule == EWwiseExportDebugNameRule::Release) + { + OutCookedData.DebugName = FName(); + } + else + { + OutCookedData.DebugName = FName((ExportDebugNameRule == EWwiseExportDebugNameRule::Name) ? GameParameter->Name : GameParameter->ObjectPath); + } + + return true; +} + +bool FWwiseResourceCookerImpl::GetInitBankCookedData(FWwiseInitBankCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) const +{ + const auto* ProjectDatabase = GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetInitBankCookedData (%s %" PRIu32 " %s): ProjectDatabase not initialized"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + const FWwiseDataStructureScopeLock DataStructure(*ProjectDatabase); + const auto* PlatformData = DataStructure.GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetInitBankCookedData (%s %" PRIu32 " %s): No data for platform"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + const auto* PlatformInfo = PlatformData->PlatformRef.GetPlatformInfo(); + if (UNLIKELY(!PlatformInfo)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetInitBankCookedData (%s %" PRIu32 " %s): No Platform Info"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + FWwiseRefSoundBank SoundBankRef; + if (UNLIKELY(!PlatformData->GetRef(SoundBankRef, FWwiseSharedLanguageId(), InInfo))) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetInitBankCookedData (%s %" PRIu32 " %s): No ref found"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + if (UNLIKELY(!SoundBankRef.IsInitBank())) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetInitBankCookedData (%s %" PRIu32 " %s): Not an init SoundBank"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + TSet AdditionalSoundBanks; + { + const auto* SoundBank = SoundBankRef.GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetInitBankCookedData (%s %" PRIu32 " %s): Could not get SoundBank from Ref"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + if (UNLIKELY(!FillSoundBankBaseInfo(OutCookedData, *PlatformInfo, *SoundBank))) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetInitBankCookedData (%s %" PRIu32 " %s): Could not fill SoundBank from Data"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + // Add all Init SoundBank media + TSet MediaSet; + { + for (const auto& MediaRef : SoundBankRef.GetSoundBankMedia(PlatformData->MediaFiles)) + { + if (UNLIKELY(!AddRequirementsForMedia(AdditionalSoundBanks, MediaSet, MediaRef.Value, FWwiseSharedLanguageId(), *PlatformData))) + { + return false; + } + } + } + + { + WwiseCustomPluginIdsMap CustomPluginsRefs = SoundBankRef.GetSoundBankCustomPlugins(PlatformData->CustomPlugins); + for (const auto& Plugin : CustomPluginsRefs) + { + const WwiseMediaIdsMap MediaRefs = Plugin.Value.GetPluginMedia(PlatformData->MediaFiles); + for (const auto& MediaRef : MediaRefs) + { + if (UNLIKELY(!AddRequirementsForMedia(AdditionalSoundBanks, MediaSet, MediaRef.Value, FWwiseSharedLanguageId(), *PlatformData))) + { + return false; + } + } + } + } + + { + WwisePluginShareSetIdsMap ShareSetRefs = SoundBankRef.GetSoundBankPluginShareSets(PlatformData->PluginShareSets); + for (const auto& ShareSet : ShareSetRefs) + { + const WwiseMediaIdsMap MediaRefs = ShareSet.Value.GetPluginMedia(PlatformData->MediaFiles); + for (const auto& MediaRef : MediaRefs) + { + if (UNLIKELY(!AddRequirementsForMedia(AdditionalSoundBanks, MediaSet, MediaRef.Value, FWwiseSharedLanguageId(), *PlatformData))) + { + return false; + } + } + } + } + + { + WwiseAudioDeviceIdsMap AudioDevicesRefs = SoundBankRef.GetSoundBankAudioDevices(PlatformData->AudioDevices); + for (const auto& AudioDevice : AudioDevicesRefs) + { + const WwiseMediaIdsMap MediaRefs = AudioDevice.Value.GetPluginMedia(PlatformData->MediaFiles); + for (const auto& MediaRef : MediaRefs) + { + if (UNLIKELY(!AddRequirementsForMedia(AdditionalSoundBanks, MediaSet, MediaRef.Value, FWwiseSharedLanguageId(), *PlatformData))) + { + return false; + } + } + } + } + + + OutCookedData.Media = MediaSet.Array(); + const TSet& Languages = DataStructure.GetLanguages(); + OutCookedData.Language.Empty(Languages.Num()); + for (const FWwiseSharedLanguageId& Language : Languages) + { + OutCookedData.Language.Add({ Language.GetLanguageId(), Language.GetLanguageName(), Language.LanguageRequirement }); + } + + if (ExportDebugNameRule == EWwiseExportDebugNameRule::Release) + { + OutCookedData.DebugName = FName(); + } + else + { + OutCookedData.DebugName = FName((ExportDebugNameRule == EWwiseExportDebugNameRule::Name) ? SoundBank->ShortName : SoundBank->ObjectPath); + } + } + + return true; +} + +bool FWwiseResourceCookerImpl::GetMediaCookedData(FWwiseMediaCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) const +{ + const auto* ProjectDatabase = GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetMediaCookedData (%" PRIu32 "): ProjectDatabase not initialized"), + InInfo.WwiseShortId); + return false; + } + + const FWwiseDataStructureScopeLock DataStructure(*ProjectDatabase); + const auto* PlatformData = DataStructure.GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetMediaCookedData (%" PRIu32 "): No data for platform"), + InInfo.WwiseShortId); + return false; + } + + auto MediaId = FWwiseDatabaseMediaIdKey(InInfo.WwiseShortId, InInfo.HardCodedSoundBankShortId); + const auto* MediaRef = PlatformData->MediaFiles.Find(MediaId); + if (UNLIKELY(!MediaRef)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetMediaCookedData (%" PRIu32 "): Could not find Media in SoundBank %" PRIu32), + InInfo.WwiseShortId, InInfo.HardCodedSoundBankShortId); + return false; + } + + const FWwiseSharedLanguageId* LanguageRefPtr = nullptr; + if (MediaRef->LanguageId) + { + const auto& Languages = DataStructure.GetLanguages(); + LanguageRefPtr = Languages.Find(FWwiseSharedLanguageId(MediaRef->LanguageId, TEXT(""), EWwiseLanguageRequirement::IsOptional)); + if (UNLIKELY(!LanguageRefPtr)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetMediaCookedData (%" PRIu32 "): Could not find language %" PRIu32), + InInfo.WwiseShortId, MediaRef->LanguageId); + return false; + } + } + const auto& LanguageRef = LanguageRefPtr ? *LanguageRefPtr : FWwiseSharedLanguageId(); + + TSet SoundBankSet; + TSet MediaSet; + if (UNLIKELY(!AddRequirementsForMedia(SoundBankSet, MediaSet, *MediaRef, LanguageRef, *PlatformData))) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetMediaCookedData (%" PRIu32 "): Could not get requirements for media."), + InInfo.WwiseShortId); + return false; + } + + if (UNLIKELY(SoundBankSet.Num() > 0)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetMediaCookedData (%" PRIu32 "): Asking for a media in a particular SoundBank (%" PRIu32 ") must have it fully defined in this SoundBank."), + InInfo.WwiseShortId, InInfo.HardCodedSoundBankShortId); + return false; + } + + if (MediaSet.Num() == 0) + { + // Not directly an error: Media is in this SoundBank, without streaming. Can be a logical error, but it's not our error. + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetMediaCookedData (%" PRIu32 "): Media is fully in SoundBank. Returning no media."), + InInfo.WwiseShortId); + return false; + } + + auto Media = MediaSet.Array()[0]; + + OutCookedData = MoveTemp(Media); + return true; +} + +bool FWwiseResourceCookerImpl::GetShareSetCookedData(FWwiseLocalizedShareSetCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) const +{ + const auto* ProjectDatabase = GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetShareSetCookedData (%s %" PRIu32 " %s): ProjectDatabase not initialized"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + const FWwiseDataStructureScopeLock DataStructure(*ProjectDatabase); + const auto* PlatformData = DataStructure.GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetShareSetCookedData (%s %" PRIu32 " %s): No data for platform"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + const TSet& Languages = DataStructure.GetLanguages(); + + const auto* PlatformInfo = PlatformData->PlatformRef.GetPlatformInfo(); + if (UNLIKELY(!PlatformInfo)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetShareSetCookedData (%s %" PRIu32 " %s): No Platform Info"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + TMap> RefLanguageMap; + PlatformData->GetRefMap(RefLanguageMap, Languages, InInfo); + if (UNLIKELY(RefLanguageMap.Num() == 0)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetShareSetCookedData (%s %" PRIu32 " %s): No ref found"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + OutCookedData.ShareSetLanguageMap.Empty(RefLanguageMap.Num()); + + for (auto& Ref : RefLanguageMap) + { + FWwiseShareSetCookedData CookedData; + TSet SoundBankSet; + TSet MediaSet; + TSet& ShareSets = Ref.Value; + + if (UNLIKELY(ShareSets.Num() == 0)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetAuxBusCookedData (%s %" PRIu32 " %s): Empty ref for language"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + // Set up basic global Aux bus information + { + TSet::TConstIterator FirstShareSet(ShareSets); + CookedData.ShareSetId = FirstShareSet->PluginShareSetId(); + if (ExportDebugNameRule == EWwiseExportDebugNameRule::Release) + { + OutCookedData.DebugName = FName(); + } + else + { + CookedData.DebugName = FName((ExportDebugNameRule == EWwiseExportDebugNameRule::Name) ? FirstShareSet->PluginShareSetName() : FirstShareSet->PluginShareSetObjectPath()); + OutCookedData.DebugName = CookedData.DebugName; + } + OutCookedData.ShareSetId = CookedData.ShareSetId; + } + for (auto& ShareSetRef : ShareSets) + { + + const auto* PluginShareSet = ShareSetRef.GetPlugin(); + if (UNLIKELY(!PluginShareSet)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetShareSetCookedData (%s %" PRIu32 " %s): Could not get ShareSet from Ref"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + CookedData.ShareSetId = PluginShareSet->Id; + + const auto* SoundBank = ShareSetRef.GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetShareSetCookedData (%s %" PRIu32 " %s): Could not get SoundBank from Ref"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + if (!SoundBank->IsInitBank()) + { + FWwiseSoundBankCookedData MainSoundBank; + if (UNLIKELY(!FillSoundBankBaseInfo(MainSoundBank, *PlatformInfo, *SoundBank))) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetShareSetCookedData (%s %" PRIu32 " %s): Could not fill SoundBank from Data"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + SoundBankSet.Add(MainSoundBank); + } + + { + WwiseCustomPluginIdsMap CustomPluginsRefs = ShareSetRef.GetPluginCustomPlugins(PlatformData->CustomPlugins); + for (const auto& Plugin : CustomPluginsRefs) + { + const WwiseMediaIdsMap MediaRefs = Plugin.Value.GetPluginMedia(PlatformData->MediaFiles); + for (const auto& MediaRef : MediaRefs) + { + if (UNLIKELY(!AddRequirementsForMedia(SoundBankSet, MediaSet, MediaRef.Value, FWwiseSharedLanguageId(), *PlatformData))) + { + return false; + } + } + } + } + + { + WwisePluginShareSetIdsMap ShareSetRefs = ShareSetRef.GetPluginPluginShareSets(PlatformData->PluginShareSets); + for (const auto& ShareSet : ShareSetRefs) + { + const WwiseMediaIdsMap MediaRefs = ShareSet.Value.GetPluginMedia(PlatformData->MediaFiles); + for (const auto& MediaRef : MediaRefs) + { + if (UNLIKELY(!AddRequirementsForMedia(SoundBankSet, MediaSet, MediaRef.Value, FWwiseSharedLanguageId(), *PlatformData))) + { + return false; + } + } + } + } + + { + WwiseAudioDeviceIdsMap AudioDevicesRefs = ShareSetRef.GetPluginAudioDevices(PlatformData->AudioDevices); + for (const auto& AudioDevice : AudioDevicesRefs) + { + const WwiseMediaIdsMap MediaRefs = AudioDevice.Value.GetPluginMedia(PlatformData->MediaFiles); + for (const auto& MediaRef : MediaRefs) + { + if (UNLIKELY(!AddRequirementsForMedia(SoundBankSet, MediaSet, MediaRef.Value, FWwiseSharedLanguageId(), *PlatformData))) + { + return false; + } + } + } + } + } + + CookedData.SoundBanks = SoundBankSet.Array(); + CookedData.Media = MediaSet.Array(); + OutCookedData.ShareSetLanguageMap.Add(FWwiseLanguageCookedData(Ref.Key.GetLanguageId(), Ref.Key.GetLanguageName(), Ref.Key.LanguageRequirement), MoveTemp(CookedData)); + } + + if (UNLIKELY(OutCookedData.ShareSetLanguageMap.Num() == 0)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetShareSetCookedData (%s %" PRIu32 " %s): No ShareSet"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + // Make this a SFX if all CookedData are identical + { + auto& Map = OutCookedData.ShareSetLanguageMap; + TArray Keys; + Map.GetKeys(Keys); + + auto LhsKey = Keys.Pop(false); + const auto* Lhs = Map.Find(LhsKey); + while (Keys.Num() > 0) + { + auto RhsKey = Keys.Pop(false); + const auto* Rhs = Map.Find(RhsKey); + + if (Lhs->ShareSetId != Rhs->ShareSetId + || Lhs->DebugName != Rhs->DebugName + || Lhs->SoundBanks.Num() != Rhs->SoundBanks.Num() + || Lhs->Media.Num() != Rhs->Media.Num()) + { + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetShareSetCookedData (%s %" PRIu32 " %s): ShareSet has languages"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return true; + } + for (const auto& Elem : Lhs->SoundBanks) + { + if (!Rhs->SoundBanks.Contains(Elem)) + { + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetShareSetCookedData (%s %" PRIu32 " %s): ShareSet has languages due to banks"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return true; + } + } + for (const auto& Elem : Lhs->Media) + { + if (!Rhs->Media.Contains(Elem)) + { + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetShareSetCookedData (%s %" PRIu32 " %s): ShareSet has languages due to media"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return true; + } + } + } + + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetShareSetCookedData (%s %" PRIu32 " %s): ShareSet is a SFX"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + std::remove_reference_t SfxMap; + SfxMap.Add(FWwiseLanguageCookedData::Sfx, *Lhs); + + Map = SfxMap; + } + + return true; +} + +bool FWwiseResourceCookerImpl::GetSoundBankCookedData(FWwiseLocalizedSoundBankCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) const +{ + const auto* ProjectDatabase = GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetSoundBankCookedData (%s %" PRIu32 " %s): ProjectDatabase not initialized"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + const FWwiseDataStructureScopeLock DataStructure(*ProjectDatabase); + const auto* PlatformData = DataStructure.GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetSoundBankCookedData (%s %" PRIu32 " %s): No data for platform"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + const TSet& Languages = DataStructure.GetLanguages(); + + const auto* PlatformInfo = PlatformData->PlatformRef.GetPlatformInfo(); + if (UNLIKELY(!PlatformInfo)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetSoundBankCookedData (%s %" PRIu32 " %s): No Platform Info"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + TMap RefLanguageMap; + PlatformData->GetRefMap(RefLanguageMap, Languages, InInfo); + if (UNLIKELY(RefLanguageMap.Num() == 0)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetSoundBankCookedData (%s %" PRIu32 " %s): No ref found"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + OutCookedData.SoundBankLanguageMap.Empty(RefLanguageMap.Num()); + + for (const auto& Ref : RefLanguageMap) + { + FWwiseSoundBankCookedData CookedData; + const auto* SoundBank = Ref.Value.GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetSoundBankCookedData (%s %" PRIu32 " %s): Could not get SoundBank from Ref"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + if (UNLIKELY(!FillSoundBankBaseInfo(CookedData, *PlatformInfo, *SoundBank))) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetSoundBankCookedData (%s %" PRIu32 " %s): Could not fill SoundBank from Data"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + if (ExportDebugNameRule == EWwiseExportDebugNameRule::Release) + { + OutCookedData.DebugName = FName(); + } + else + { + CookedData.DebugName = FName((ExportDebugNameRule == EWwiseExportDebugNameRule::Name) ? SoundBank->ShortName : SoundBank->ObjectPath); + OutCookedData.DebugName = CookedData.DebugName; + } + + OutCookedData.SoundBankId = CookedData.SoundBankId; + OutCookedData.SoundBankLanguageMap.Add(FWwiseLanguageCookedData(Ref.Key.GetLanguageId(), Ref.Key.GetLanguageName(), Ref.Key.LanguageRequirement), MoveTemp(CookedData)); + } + + if (UNLIKELY(OutCookedData.SoundBankLanguageMap.Num() == 0)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetSoundBankCookedData (%s %" PRIu32 " %s): No SoundBank"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + // Make this a SFX if all CookedData are identical + { + auto& Map = OutCookedData.SoundBankLanguageMap; + TArray Keys; + Map.GetKeys(Keys); + + auto LhsKey = Keys.Pop(false); + const auto* Lhs = Map.Find(LhsKey); + while (Keys.Num() > 0) + { + auto RhsKey = Keys.Pop(false); + const auto* Rhs = Map.Find(RhsKey); + + if (GetTypeHash(*Lhs) != GetTypeHash(*Rhs)) + { + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetSoundBankCookedData (%s %" PRIu32 " %s): SoundBank has languages"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return true; + } + } + + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("GetSoundBankCookedData (%s %" PRIu32 " %s): SoundBank is a SFX"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + std::remove_reference_t SfxMap; + SfxMap.Add(FWwiseLanguageCookedData::Sfx, *Lhs); + + Map = SfxMap; + } + return true; +} + +bool FWwiseResourceCookerImpl::GetStateCookedData(FWwiseGroupValueCookedData& OutCookedData, const FWwiseGroupValueInfo& InInfo) const +{ + const auto* ProjectDatabase = GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetStateCookedData (%s %" PRIu32 " %" PRIu32 " %s): ProjectDatabase not initialized"), + *InInfo.WwiseGuid.ToString(), InInfo.GroupShortId, InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + const FWwiseDataStructureScopeLock DataStructure(*ProjectDatabase); + const auto* PlatformData = DataStructure.GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetStateCookedData (%s %" PRIu32 " %" PRIu32 " %s): No data for platform"), + *InInfo.WwiseGuid.ToString(), InInfo.GroupShortId, InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + FWwiseRefState StateRef; + if (UNLIKELY(!PlatformData->GetRef(StateRef, FWwiseSharedLanguageId(), InInfo))) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetStateCookedData (%s %" PRIu32 " %" PRIu32 " %s): No state found"), + *InInfo.WwiseGuid.ToString(), InInfo.GroupShortId, InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + const auto* State = StateRef.GetState(); + const auto* StateGroup = StateRef.GetStateGroup(); + if (UNLIKELY(!State || !StateGroup)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetStateCookedData (%s %" PRIu32 " %" PRIu32 " %s): No state in ref"), + *InInfo.WwiseGuid.ToString(), InInfo.GroupShortId, InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + OutCookedData.Type = EWwiseGroupType::State; + OutCookedData.GroupId = StateGroup->Id; + OutCookedData.Id = State->Id; + if (ExportDebugNameRule == EWwiseExportDebugNameRule::Release) + { + OutCookedData.DebugName = FName(); + } + else + { + OutCookedData.DebugName = FName((ExportDebugNameRule == EWwiseExportDebugNameRule::Name) ? State->Name : State->ObjectPath); + } + return true; +} + +bool FWwiseResourceCookerImpl::GetSwitchCookedData(FWwiseGroupValueCookedData& OutCookedData, const FWwiseGroupValueInfo& InInfo) const +{ + const auto* ProjectDatabase = GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetSwitchCookedData (%s %" PRIu32 " %" PRIu32 " %s): ProjectDatabase not initialized"), + *InInfo.WwiseGuid.ToString(), InInfo.GroupShortId, InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + const FWwiseDataStructureScopeLock DataStructure(*ProjectDatabase); + const auto* PlatformData = DataStructure.GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetSwitchCookedData (%s %" PRIu32 " %" PRIu32 " %s): No data for platform"), + *InInfo.WwiseGuid.ToString(), InInfo.GroupShortId, InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + FWwiseRefSwitch SwitchRef; + if (UNLIKELY(!PlatformData->GetRef(SwitchRef, FWwiseSharedLanguageId(), InInfo))) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetSwitchCookedData (%s %" PRIu32 " %" PRIu32 " %s): No switch found"), + *InInfo.WwiseGuid.ToString(), InInfo.GroupShortId, InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + const auto* Switch = SwitchRef.GetSwitch(); + const auto* SwitchGroup = SwitchRef.GetSwitchGroup(); + if (UNLIKELY(!Switch || !SwitchGroup)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetSwitchCookedData (%s %" PRIu32 " %" PRIu32 " %s): No switch in ref"), + *InInfo.WwiseGuid.ToString(), InInfo.GroupShortId, InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + OutCookedData.Type = EWwiseGroupType::Switch; + OutCookedData.GroupId = SwitchGroup->Id; + OutCookedData.Id = Switch->Id; + if (ExportDebugNameRule == EWwiseExportDebugNameRule::Release) + { + OutCookedData.DebugName = FName(); + } + else + { + OutCookedData.DebugName = FName((ExportDebugNameRule == EWwiseExportDebugNameRule::Name) ? Switch->Name : Switch->ObjectPath); + } + return true; +} + +bool FWwiseResourceCookerImpl::GetTriggerCookedData(FWwiseTriggerCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) const +{ + const auto* ProjectDatabase = GetProjectDatabase(); + if (UNLIKELY(!ProjectDatabase)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetTriggerCookedData (%s %" PRIu32 " %s): ProjectDatabase not initialized"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + const FWwiseDataStructureScopeLock DataStructure(*ProjectDatabase); + const auto* PlatformData = DataStructure.GetCurrentPlatformData(); + if (UNLIKELY(!PlatformData)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetTriggerCookedData (%s %" PRIu32 " %s): No data for platform"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + const TSet& Languages = DataStructure.GetLanguages(); + + FWwiseRefTrigger TriggerRef; + + if (UNLIKELY(!PlatformData->GetRef(TriggerRef, FWwiseSharedLanguageId(), InInfo))) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("GetTriggerCookedData (%s %" PRIu32 " %s): No trigger data found"), + *InInfo.WwiseGuid.ToString(), InInfo.WwiseShortId, *InInfo.WwiseName.ToString()); + return false; + } + + const auto* Trigger = TriggerRef.GetTrigger(); + + OutCookedData.TriggerId = Trigger->Id; + if (ExportDebugNameRule == EWwiseExportDebugNameRule::Release) + { + OutCookedData.DebugName = FName(); + } + else + { + OutCookedData.DebugName = FName((ExportDebugNameRule == EWwiseExportDebugNameRule::Name) ? Trigger->Name : Trigger->ObjectPath); + } + + return true; +} + +bool FWwiseResourceCookerImpl::FillSoundBankBaseInfo(FWwiseSoundBankCookedData& OutSoundBankCookedData, const FWwiseMetadataPlatformInfo& InPlatformInfo, const FWwiseMetadataSoundBank& InSoundBank) const +{ + OutSoundBankCookedData.SoundBankId = InSoundBank.Id; + OutSoundBankCookedData.SoundBankPathName = InSoundBank.Path; + OutSoundBankCookedData.MemoryAlignment = InSoundBank.Align == 0 ? InPlatformInfo.DefaultAlign : InSoundBank.Align; + OutSoundBankCookedData.bDeviceMemory = InSoundBank.bDeviceMemory; + OutSoundBankCookedData.bContainsMedia = InSoundBank.ContainsMedia(); + switch (InSoundBank.Type) + { + case EMetadataSoundBankType::Bus: + OutSoundBankCookedData.SoundBankType = EWwiseSoundBankType::Bus; + break; + case EMetadataSoundBankType::Event: + OutSoundBankCookedData.SoundBankType = EWwiseSoundBankType::Event; + break; + case EMetadataSoundBankType::User: + default: + OutSoundBankCookedData.SoundBankType = EWwiseSoundBankType::User; + } + if (ExportDebugNameRule == EWwiseExportDebugNameRule::Release) + { + OutSoundBankCookedData.DebugName = FName(); + } + else + { + OutSoundBankCookedData.DebugName = FName((ExportDebugNameRule == EWwiseExportDebugNameRule::Name) ? InSoundBank.ShortName : InSoundBank.ObjectPath); + } + return true; +} + +bool FWwiseResourceCookerImpl::FillMediaBaseInfo(FWwiseMediaCookedData& OutMediaCookedData, const FWwiseMetadataPlatformInfo& InPlatformInfo, const FWwiseMetadataSoundBank& InSoundBank, const FWwiseMetadataMediaReference& InMediaReference) const +{ + for (const auto& Media : InSoundBank.Media) + { + if (Media.Id == InMediaReference.Id) + { + return FillMediaBaseInfo(OutMediaCookedData, InPlatformInfo, InSoundBank, Media); + } + } + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("FillMediaBaseInfo: Could not get Media Reference %" PRIu32 " in SoundBank %s %" PRIu32), + InMediaReference.Id, *InSoundBank.ShortName.ToString(), InSoundBank.Id); + return false; +} + +bool FWwiseResourceCookerImpl::FillMediaBaseInfo(FWwiseMediaCookedData& OutMediaCookedData, const FWwiseMetadataPlatformInfo& InPlatformInfo, const FWwiseMetadataSoundBank& InSoundBank, const FWwiseMetadataMedia& InMedia) const +{ + OutMediaCookedData.MediaId = InMedia.Id; + if (InMedia.Path.IsNone()) + { + if (UNLIKELY(InMedia.CachePath.IsNone())) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("FillMediaBaseInfo: Empty path for Media %" PRIu32 " in SoundBank %s %" PRIu32), + InMedia.Id, *InSoundBank.ShortName.ToString(), InSoundBank.Id); + return false; + } + OutMediaCookedData.MediaPathName = InMedia.CachePath; + } + else + { + OutMediaCookedData.MediaPathName = InMedia.Path; + } + OutMediaCookedData.PrefetchSize = InMedia.PrefetchSize; + OutMediaCookedData.MemoryAlignment = InMedia.Align == 0 ? InPlatformInfo.DefaultAlign : InMedia.Align; + OutMediaCookedData.bDeviceMemory = InMedia.bDeviceMemory; + OutMediaCookedData.bStreaming = InMedia.bStreaming; + + if (ExportDebugNameRule == EWwiseExportDebugNameRule::Release) + { + OutMediaCookedData.DebugName = FName(); + } + else + { + OutMediaCookedData.DebugName = FName(InMedia.ShortName); + } + return true; +} + +bool FWwiseResourceCookerImpl::FillExternalSourceBaseInfo(FWwiseExternalSourceCookedData& OutExternalSourceCookedData, const FWwiseMetadataExternalSource& InExternalSource) const +{ + OutExternalSourceCookedData.Cookie = InExternalSource.Cookie; + if (ExportDebugNameRule == EWwiseExportDebugNameRule::Release) + { + OutExternalSourceCookedData.DebugName = FName(); + } + else + { + OutExternalSourceCookedData.DebugName = FName((ExportDebugNameRule == EWwiseExportDebugNameRule::Name) ? InExternalSource.Name : InExternalSource.ObjectPath); + } + return true; +} + +bool FWwiseResourceCookerImpl::AddRequirementsForMedia(TSet& OutSoundBankSet, TSet& OutMediaSet, + const FWwiseRefMedia& InMediaRef, const FWwiseSharedLanguageId& InLanguage, + const FWwisePlatformDataStructure& InPlatformData) const +{ + const auto* Media = InMediaRef.GetMedia(); + if (UNLIKELY(!Media)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("AddRequirementsForMedia: Could not get Media from Media Ref")); + return false; + } + + const auto* PlatformInfo = InPlatformData.PlatformRef.GetPlatformInfo(); + if (UNLIKELY(!PlatformInfo)) return false; + + if (Media->Location == EWwiseMetadataMediaLocation::Memory && !Media->bStreaming) + { + // In-Memory media is already loaded with current SoundBank + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("AddRequirementsForMedia (%s %" PRIu32 " in %s %" PRIu32 "): Media is in memory and not streaming. Skipping."), + *Media->ShortName.ToString(), Media->Id, *InLanguage.GetLanguageName().ToString(), InLanguage.GetLanguageId()); + } + else if (Media->Location == EWwiseMetadataMediaLocation::OtherBank) + { + // Media resides in another SoundBank. Find that other SoundBank and add it as a requirement. + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("AddRequirementsForMedia (%s %" PRIu32 " in %s %" PRIu32 "): Media is in another SoundBank. Locate SoundBank and add requirement."), + *Media->ShortName.ToString(), Media->Id, *InLanguage.GetLanguageName().ToString(), InLanguage.GetLanguageId()); + + FWwiseObjectInfo MediaInfo; + MediaInfo.WwiseShortId = Media->Id; + MediaInfo.WwiseName = Media->ShortName; + + FWwiseRefMedia OtherSoundBankMediaRef; + if (UNLIKELY(!InPlatformData.GetRef(OtherSoundBankMediaRef, InLanguage, MediaInfo))) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("AddRequirementsForMedia (%s %" PRIu32 " in %s %" PRIu32 "): Could not get Ref for other SoundBank media %s %" PRIu32 " %s"), + *Media->ShortName.ToString(), Media->Id, *InLanguage.GetLanguageName().ToString(), InLanguage.GetLanguageId(), + *MediaInfo.WwiseGuid.ToString(), MediaInfo.WwiseShortId, *MediaInfo.WwiseName.ToString()); + return false; + } + + const auto* SoundBank = OtherSoundBankMediaRef.GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("AddRequirementsForMedia (%s %" PRIu32 " in %s %" PRIu32 "): Could not get SoundBank from Media in another SoundBank Ref"), + *Media->ShortName.ToString(), Media->Id, *InLanguage.GetLanguageName().ToString(), InLanguage.GetLanguageId()); + return false; + } + + if (SoundBank->IsInitBank()) + { + // We assume Init SoundBanks are fully loaded + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("AddRequirementsForMedia (%s %" PRIu32 " in %s %" PRIu32 "): Media is in Init SoundBank. Skipping."), + *Media->ShortName.ToString(), Media->Id, *InLanguage.GetLanguageName().ToString(), InLanguage.GetLanguageId()); + } + + FWwiseSoundBankCookedData MediaSoundBank; + if (UNLIKELY(!FillSoundBankBaseInfo(MediaSoundBank, *PlatformInfo, *SoundBank))) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("AddRequirementsForMedia (%s %" PRIu32 " in %s %" PRIu32 "): Could not fill SoundBank from Media in another SoundBank Data"), + *Media->ShortName.ToString(), Media->Id, *InLanguage.GetLanguageName().ToString(), InLanguage.GetLanguageId()); + return false; + } + OutSoundBankSet.Add(MoveTemp(MediaSoundBank)); + } + else + { + // Media has a required loose file. + UE_LOG(LogWwiseResourceCooker, VeryVerbose, TEXT("AddRequirementsForMedia (%s %" PRIu32 " in %s %" PRIu32 "): Adding loose media requirement."), + *Media->ShortName.ToString(), Media->Id, *InLanguage.GetLanguageName().ToString(), InLanguage.GetLanguageId()); + + const auto* SoundBank = InMediaRef.GetSoundBank(); + if (UNLIKELY(!SoundBank)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("AddRequirementsForMedia (%s %" PRIu32 " in %s %" PRIu32 "): Could not get SoundBank from Media"), + *Media->ShortName.ToString(), Media->Id, *InLanguage.GetLanguageName().ToString(), InLanguage.GetLanguageId()); + return false; + } + + FWwiseMediaCookedData MediaCookedData; + if (UNLIKELY(!FillMediaBaseInfo(MediaCookedData, *PlatformInfo, *SoundBank, *Media))) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("AddRequirementsForMedia (%s %" PRIu32 " in %s %" PRIu32 "): Could not fill Media from Media Ref"), + *Media->ShortName.ToString(), Media->Id, *InLanguage.GetLanguageName().ToString(), InLanguage.GetLanguageId()); + return false; + } + + OutMediaSet.Add(MoveTemp(MediaCookedData)); + } + + return true; +} + +bool FWwiseResourceCookerImpl::AddRequirementsForExternalSource(TSet& OutExternalSourceSet, const FWwiseRefExternalSource& InExternalSourceRef) const +{ + const auto* ExternalSource = InExternalSourceRef.GetExternalSource(); + if (UNLIKELY(!ExternalSource)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("AddRequirementsForExternalSource: Could not get External Source from External Source Ref")); + return false; + } + FWwiseExternalSourceCookedData ExternalSourceCookedData; + if (UNLIKELY(!FillExternalSourceBaseInfo(ExternalSourceCookedData, *ExternalSource))) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("AddRequirementsForExternalSource (%s %" PRIu32 "): Could not fill External Source from External Source Ref"), + *ExternalSource->Name.ToString(), ExternalSource->Cookie); + return false; + } + OutExternalSourceSet.Add(MoveTemp(ExternalSourceCookedData)); + return true; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Private/Wwise/WwiseResourceCookerModule.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Private/Wwise/WwiseResourceCookerModule.cpp new file mode 100644 index 0000000..5420eef --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Private/Wwise/WwiseResourceCookerModule.cpp @@ -0,0 +1,55 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseResourceCookerModule.h" +#include "Wwise/WwiseResourceCookerImpl.h" +#include "GameDelegates.h" + +FDelegateHandle IWwiseResourceCookerModule::ModifyCookDelegateHandle; + +void IWwiseResourceCookerModule::StartupModule() +{ +#if UE_5_0_OR_LATER + // This StartupModule can be executed multiple times as more than one module can derive from the interface. + // Use GetModule to load what the user wishes, and ignore the current "this". + auto* This = GetModule(); + if (UNLIKELY(!This)) + { + return; + } + + // The act of GetModule() can StartupModule another one. Make sure we are not recursively set. + if (ModifyCookDelegateHandle.IsValid()) + { + return; + } + ModifyCookDelegateHandle = FGameDelegates::Get().GetModifyCookDelegate().AddRaw(This, &IWwiseResourceCookerModule::OnModifyCook); +#endif +} + +void IWwiseResourceCookerModule::ShutdownModule() +{ +#if UE_5_0_OR_LATER + if (!ModifyCookDelegateHandle.IsValid()) + { + return; + } + + FGameDelegates::Get().GetModifyCookDelegate().Remove(ModifyCookDelegateHandle); + ModifyCookDelegateHandle.Reset(); +#endif +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Private/Wwise/WwiseResourceCookerModuleImpl.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Private/Wwise/WwiseResourceCookerModuleImpl.cpp new file mode 100644 index 0000000..d13a537 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Private/Wwise/WwiseResourceCookerModuleImpl.cpp @@ -0,0 +1,260 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseResourceCookerModuleImpl.h" +#include "Interfaces/ITargetPlatform.h" +#include "Wwise/WwiseCookingCache.h" +#include "Wwise/WwiseResourceCookerImpl.h" + +#include "Wwise/WwiseResourceLoader.h" + +#include "CookOnTheSide/CookLog.h" +#include "Wwise/Stats/ResourceCooker.h" + +#include "Misc/CommandLine.h" + +IMPLEMENT_MODULE(FWwiseResourceCookerModule, WwiseResourceCooker) + +FWwiseResourceCooker* FWwiseResourceCookerModule::GetResourceCooker() +{ + Lock.ReadLock(); + if (LIKELY(ResourceCooker)) + { + Lock.ReadUnlock(); + } + else + { + Lock.ReadUnlock(); + Lock.WriteLock(); + if (LIKELY(!ResourceCooker)) + { + UE_LOG(LogWwiseResourceCooker, Display, TEXT("Initializing default Resource Cooker.")); + ResourceCooker.Reset(InstantiateResourceCooker()); + } + Lock.WriteUnlock(); + } + return ResourceCooker.Get(); +} + +FWwiseResourceCooker* FWwiseResourceCookerModule::InstantiateResourceCooker() +{ + return new FWwiseResourceCookerImpl; +} + +FWwiseResourceCooker* FWwiseResourceCookerModule::CreateCookerForPlatform(const ITargetPlatform* TargetPlatform, + const FWwiseSharedPlatformId& InPlatform, EWwiseExportDebugNameRule InExportDebugNameRule) +{ + if (TargetPlatform && TargetPlatform->IsServerOnly()) + { + static FString bOnce; + UE_CLOG(bOnce != TargetPlatform->PlatformName(), LogWwiseResourceCooker, Display, TEXT("CreateCookerForPlatform: Disabling cooker on cooking for server platform %s (UE: %s)"), + TargetPlatform ? *TargetPlatform->PlatformName() : TEXT("[nullptr]"), + TargetPlatform ? *TargetPlatform->IniPlatformName() : TEXT("[nullptr]")); + bOnce = TargetPlatform->PlatformName(); + return nullptr; + } + + { + const auto* CookingPlatform = CookingPlatforms.Find(TargetPlatform); + + if (UNLIKELY(!CookingPlatform)) + { +#if UE_5_0_OR_LATER + if (IWwiseProjectDatabaseModule::IsInACookingCommandlet() && !FParse::Param(FCommandLine::Get(), TEXT("CookOnTheFly"))) // By The Book cooking needs to predefine the requested platforms. InEditor and OnTheFly should create them all the time. + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("CreateCookerForPlatform: Not cooking for platform %s (UE: %s, Wwise: %s)"), + TargetPlatform ? *TargetPlatform->PlatformName() : TEXT("[nullptr]"), + TargetPlatform ? *TargetPlatform->IniPlatformName() : TEXT("[nullptr]"), + *InPlatform.GetPlatformName().ToString()); + return nullptr; + } +#endif + } + else if (*CookingPlatform) + { + return CookingPlatform->Get(); + } + } + + const auto* DefaultResourceCooker = GetResourceCooker(); + if (UNLIKELY(!DefaultResourceCooker)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("CreateCookerForPlatform: No Default Cooker available creating platform %s (UE: %s, Wwise: %s)"), + TargetPlatform ? *TargetPlatform->PlatformName() : TEXT("[nullptr]"), + TargetPlatform ? *TargetPlatform->IniPlatformName() : TEXT("[nullptr]"), + *InPlatform.GetPlatformName().ToString()); + return nullptr; + } + + const auto* DefaultProjectDatabase = DefaultResourceCooker->GetProjectDatabase(); + if (UNLIKELY(!DefaultProjectDatabase)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("CreateCookerForPlatform: No Default ProjectDatabase available creating platform %s (UE: %s, Wwise: %s)"), + TargetPlatform ? *TargetPlatform->PlatformName() : TEXT("[nullptr]"), + TargetPlatform ? *TargetPlatform->IniPlatformName() : TEXT("[nullptr]"), + *InPlatform.GetPlatformName().ToString()); + return nullptr; + } + + const auto* DefaultResourceLoader = DefaultProjectDatabase->GetResourceLoader(); + if (UNLIKELY(!DefaultResourceLoader)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("CreateCookerForPlatform: No ResourceLoader available creating platform %s (UE: %s, Wwise: %s)"), + TargetPlatform ? *TargetPlatform->PlatformName() : TEXT("[nullptr]"), + TargetPlatform ? *TargetPlatform->IniPlatformName() : TEXT("[nullptr]"), + *InPlatform.GetPlatformName().ToString()); + return nullptr; + } + + const auto* DefaultResourceLoaderImpl = DefaultResourceLoader->ResourceLoaderImpl.Get(); + if (UNLIKELY(!DefaultResourceLoader)) + { + UE_LOG(LogWwiseResourceCooker, Warning, TEXT("CreateCookerForPlatform: No ResourceLoaderImpl available creating platform %s (UE: %s, Wwise: %s)"), + TargetPlatform ? *TargetPlatform->PlatformName() : TEXT("[nullptr]"), + TargetPlatform ? *TargetPlatform->IniPlatformName() : TEXT("[nullptr]"), + *InPlatform.GetPlatformName().ToString()); + return nullptr; + } + + UE_LOG(LogWwiseResourceCooker, Display, TEXT("Starting cooking process for platform %s (UE: %s, Wwise: %s)"), + TargetPlatform ? *TargetPlatform->PlatformName() : TEXT("[nullptr]"), + TargetPlatform ? *TargetPlatform->IniPlatformName() : TEXT("[nullptr]"), + *InPlatform.GetPlatformName().ToString()); + + auto* NewResourceLoaderImpl = FWwiseResourceLoaderImpl::Instantiate(); + if (UNLIKELY(!NewResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("CreateCookerForPlatform: Could not instantiate ResourceLoaderImpl creating platform %s (UE: %s, Wwise: %s)"), + TargetPlatform ? *TargetPlatform->PlatformName() : TEXT("[nullptr]"), + TargetPlatform ? *TargetPlatform->IniPlatformName() : TEXT("[nullptr]"), + *InPlatform.GetPlatformName().ToString()); + return nullptr; + } + + NewResourceLoaderImpl->CurrentPlatform = InPlatform; + NewResourceLoaderImpl->StagePath = DefaultResourceLoaderImpl->StagePath; +#if WITH_EDITORONLY_DATA + NewResourceLoaderImpl->GeneratedSoundBanksPath = DefaultResourceLoaderImpl->GeneratedSoundBanksPath; +#endif + + auto* NewResourceLoader = FWwiseResourceLoader::Instantiate(); + if (UNLIKELY(!NewResourceLoader)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("CreateCookerForPlatform: Could not instantiate ResourceLoader creating platform %s (UE: %s, Wwise: %s)"), + TargetPlatform ? *TargetPlatform->PlatformName() : TEXT("[nullptr]"), + TargetPlatform ? *TargetPlatform->IniPlatformName() : TEXT("[nullptr]"), + *InPlatform.GetPlatformName().ToString()); + delete NewResourceLoaderImpl; + return nullptr; + } + NewResourceLoader->ResourceLoaderImpl.Reset(NewResourceLoaderImpl); + NewResourceLoaderImpl->CurrentLanguage = NewResourceLoader->SystemLanguage(); + + auto* NewProjectDatabase = FWwiseProjectDatabase::Instantiate(); + if (UNLIKELY(!NewProjectDatabase)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("CreateCookerForPlatform: Could not instantiate ProjectDatabase creating platform %s (UE: %s, Wwise: %s)"), + TargetPlatform ? *TargetPlatform->PlatformName() : TEXT("[nullptr]"), + TargetPlatform ? *TargetPlatform->IniPlatformName() : TEXT("[nullptr]"), + *InPlatform.GetPlatformName().ToString()); + delete NewResourceLoader; + return nullptr; + } + NewProjectDatabase->PrepareProjectDatabaseForPlatform(MoveTemp(NewResourceLoader)); + NewProjectDatabase->UpdateDataStructure(nullptr, nullptr); + + auto* NewResourceCooker = FWwiseResourceCooker::Instantiate(); + if (UNLIKELY(!NewResourceCooker)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("CreateCookerForPlatform: Could not instantiate ResourceCooker creating platform %s (UE: %s, Wwise: %s)"), + TargetPlatform ? *TargetPlatform->PlatformName() : TEXT("[nullptr]"), + TargetPlatform ? *TargetPlatform->IniPlatformName() : TEXT("[nullptr]"), + * InPlatform.GetPlatformName().ToString()); + delete NewProjectDatabase; + return nullptr; + } + NewResourceCooker->PrepareResourceCookerForPlatform(MoveTemp(NewProjectDatabase), InExportDebugNameRule); + + CookingPlatforms.Add(TargetPlatform, TUniquePtr(NewResourceCooker)); + return NewResourceCooker; +} + +void FWwiseResourceCookerModule::DestroyCookerForPlatform(const ITargetPlatform* TargetPlatform) +{ + if (UNLIKELY(!CookingPlatforms.Find(TargetPlatform))) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("DestroyCookerForPlatform: Target %s not created"), + TargetPlatform ? *TargetPlatform->IniPlatformName() : TEXT("null")); + return; + } + + UE_LOG(LogWwiseResourceCooker, Log, TEXT("DestroyCookerForPlatform for target: %s"), + TargetPlatform ? *TargetPlatform->IniPlatformName() : TEXT("null")); + + CookingPlatforms.Remove(TargetPlatform); +} + +FWwiseResourceCooker* FWwiseResourceCookerModule::GetCookerForPlatform(const ITargetPlatform* TargetPlatform) +{ + if (TargetPlatform->IsServerOnly()) + { + UE_LOG(LogWwiseResourceCooker, Verbose, TEXT("GetCookerForPlatform: Target %s is server-only."), *TargetPlatform->IniPlatformName()); + return nullptr; + } + auto* Result = CookingPlatforms.Find(TargetPlatform); + if (UNLIKELY(!Result)) + { + UE_LOG(LogWwiseResourceCooker, Error, TEXT("GetCookerForPlatform: Target %s not created"), *TargetPlatform->IniPlatformName()); + return nullptr; + } + return Result->Get(); +} + +void FWwiseResourceCookerModule::DestroyAllCookerPlatforms() +{ + TArray PreviousTargets; + CookingPlatforms.GetKeys(PreviousTargets); + + for (const auto* PreviousTarget : PreviousTargets) + { + DestroyCookerForPlatform(PreviousTarget); + } +} + +void FWwiseResourceCookerModule::ShutdownModule() +{ + DestroyAllCookerPlatforms(); + Lock.WriteLock(); + if (ResourceCooker.IsValid()) + { + UE_LOG(LogWwiseResourceCooker, Display, TEXT("Shutting down default Resource Cooker.")); + ResourceCooker.Reset(); + } + Lock.WriteUnlock(); + IWwiseResourceCookerModule::ShutdownModule(); +} + +void FWwiseResourceCookerModule::OnModifyCook(TConstArrayView InTargetPlatforms, TArray& InOutPackagesToCook, TArray& InOutPackagesToNeverCook) +{ + DestroyAllCookerPlatforms(); + + for (const auto* TargetPlatform : InTargetPlatforms) + { + UE_LOG(LogWwiseResourceCooker, Log, TEXT("Starting cooking process for platform %s"), *TargetPlatform->DisplayName().ToString()); + CookingPlatforms.Add(TargetPlatform); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Public/Wwise/Stats/ResourceCooker.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Public/Wwise/Stats/ResourceCooker.h new file mode 100644 index 0000000..69f7855 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Public/Wwise/Stats/ResourceCooker.h @@ -0,0 +1,23 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Stats/Stats.h" +#include "Logging/LogMacros.h" + +WWISERESOURCECOOKER_API DECLARE_LOG_CATEGORY_EXTERN(LogWwiseResourceCooker, Log, All); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Public/Wwise/WwiseResourceCooker.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Public/Wwise/WwiseResourceCooker.h new file mode 100644 index 0000000..4595cd6 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Public/Wwise/WwiseResourceCooker.h @@ -0,0 +1,159 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseProjectDatabase.h" +#include "Wwise/WwiseResourceCookerModule.h" + +struct FWwiseSoundBankCookedData; +struct FWwiseGameParameterCookedData; +struct FWwiseTriggerCookedData; +struct FWwiseAcousticTextureCookedData; +class FWwiseCookingCache; + +class WWISERESOURCECOOKER_API FWwiseResourceCooker +{ +public: + using WriteAdditionalFileFunction = TFunctionRef; + + static FWwiseResourceCooker* GetDefault() + { + if (auto* Module = IWwiseResourceCookerModule::GetModule()) + { + return Module->GetResourceCooker(); + } + return nullptr; + } + static FWwiseResourceCooker* Instantiate() + { + if (auto* Module = IWwiseResourceCookerModule::GetModule()) + { + return Module->InstantiateResourceCooker(); + } + return nullptr; + } + static FWwiseResourceCooker* CreateForPlatform( + const ITargetPlatform* TargetPlatform, + const FWwiseSharedPlatformId& InPlatform, + EWwiseExportDebugNameRule InExportDebugNameRule = EWwiseExportDebugNameRule::Release) + { + if (auto* Module = IWwiseResourceCookerModule::GetModule()) + { + return Module->CreateCookerForPlatform(TargetPlatform, InPlatform, InExportDebugNameRule); + } + return nullptr; + } + + static void DestroyForPlatform(const ITargetPlatform* TargetPlatform) + { + if (auto* Module = IWwiseResourceCookerModule::GetModule()) + { + Module->DestroyCookerForPlatform(TargetPlatform); + } + } + + static FWwiseResourceCooker* GetForPlatform(const ITargetPlatform* TargetPlatform) + { + if (auto* Module = IWwiseResourceCookerModule::GetModule()) + { + return Module->GetCookerForPlatform(TargetPlatform); + } + return nullptr; + } + + static FWwiseResourceCooker* GetForArchive(const FArchive& InArchive) + { + if (auto* Module = IWwiseResourceCookerModule::GetModule()) + { + return Module->GetCookerForArchive(InArchive); + } + return nullptr; + } + + FWwiseResourceCooker() {} + virtual ~FWwiseResourceCooker() {} + + void CookAuxBus(const FWwiseObjectInfo& InInfo, WriteAdditionalFileFunction WriteAdditionalFile); + void CookEvent(const FWwiseEventInfo& InInfo, WriteAdditionalFileFunction WriteAdditionalFile); + void CookExternalSource(uint32 InCookie, WriteAdditionalFileFunction WriteAdditionalFile); + void CookInitBank(const FWwiseObjectInfo& InInfo, WriteAdditionalFileFunction WriteAdditionalFile); + void CookMedia(const FWwiseObjectInfo& InInfo, WriteAdditionalFileFunction WriteAdditionalFile); + void CookShareSet(const FWwiseObjectInfo& InInfo, WriteAdditionalFileFunction WriteAdditionalFile); + void CookSoundBank(const FWwiseObjectInfo& InInfo, WriteAdditionalFileFunction WriteAdditionalFile); + + bool PrepareCookedData(FWwiseAcousticTextureCookedData& OutCookedData, const FWwiseObjectInfo& InInfo); + bool PrepareCookedData(FWwiseLocalizedAuxBusCookedData& OutCookedData, const FWwiseObjectInfo& InInfo); + bool PrepareCookedData(FWwiseLocalizedEventCookedData& OutCookedData, const FWwiseEventInfo& InInfo); + bool PrepareCookedData(FWwiseExternalSourceCookedData& OutCookedData, uint32 InCookie); + bool PrepareCookedData(FWwiseGameParameterCookedData& OutCookedData, const FWwiseObjectInfo& InInfo); + bool PrepareCookedData(FWwiseGroupValueCookedData& OutCookedData, const FWwiseGroupValueInfo& InInfo, EWwiseGroupType InGroupType); + bool PrepareCookedData(FWwiseInitBankCookedData& OutCookedData, const FWwiseObjectInfo& InInfo = FWwiseObjectInfo::DefaultInitBank); + bool PrepareCookedData(FWwiseMediaCookedData& OutCookedData, const FWwiseObjectInfo& InInfo); + bool PrepareCookedData(FWwiseLocalizedShareSetCookedData& OutCookedData, const FWwiseObjectInfo& InInfo); + bool PrepareCookedData(FWwiseLocalizedSoundBankCookedData& OutCookedData, const FWwiseObjectInfo& InInfo); + bool PrepareCookedData(FWwiseTriggerCookedData& OutCookedData, const FWwiseObjectInfo& InInfo); + + virtual FWwiseProjectDatabase* GetProjectDatabase() { return nullptr; } + virtual const FWwiseProjectDatabase* GetProjectDatabase() const { return nullptr; } + + virtual void PrepareResourceCookerForPlatform(FWwiseProjectDatabase*&& InProjectDatabaseOverride, EWwiseExportDebugNameRule InExportDebugNameRule) {} + + virtual void SetSandboxRootPath(const TCHAR* InPackageFilename); + FString GetSandboxRootPath() const {return SandboxRootPath;} + + FWwiseResourceLoader* GetResourceLoader(); + const FWwiseResourceLoader* GetResourceLoader() const; + + FWwiseSharedLanguageId GetCurrentLanguage() const; + FWwiseSharedPlatformId GetCurrentPlatform() const; + + // Low-level operations + + virtual FWwiseCookingCache* GetCookingCache() { return nullptr; } + + void CookLocalizedAuxBusToSandbox(const FWwiseLocalizedAuxBusCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile); + void CookLocalizedSoundBankToSandbox(const FWwiseLocalizedSoundBankCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile); + void CookLocalizedEventToSandbox(const FWwiseLocalizedEventCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile); + void CookLocalizedShareSetToSandbox(const FWwiseLocalizedShareSetCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile); + + virtual void CookAuxBusToSandbox(const FWwiseAuxBusCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) {} + virtual void CookEventToSandbox(const FWwiseEventCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) {} + virtual void CookExternalSourceToSandbox(const FWwiseExternalSourceCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) {} + virtual void CookInitBankToSandbox(const FWwiseInitBankCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) {} + virtual void CookMediaToSandbox(const FWwiseMediaCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) {} + virtual void CookShareSetToSandbox(const FWwiseShareSetCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) {} + virtual void CookSoundBankToSandbox(const FWwiseSoundBankCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) {} + + virtual void CookFileToSandbox(const FString& InInputPathName, const FName& InOutputPathName, WriteAdditionalFileFunction WriteAdditionalFile, bool bInStageRelativeToContent = false) {} + + virtual bool GetAcousticTextureCookedData(FWwiseAcousticTextureCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) const { return false; } + virtual bool GetAuxBusCookedData(FWwiseLocalizedAuxBusCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) const { return false; } + virtual bool GetEventCookedData(FWwiseLocalizedEventCookedData& OutCookedData, const FWwiseEventInfo& InInfo) const { return false; } + virtual bool GetExternalSourceCookedData(FWwiseExternalSourceCookedData& OutCookedData, uint32 InCookie) const { return false; } + virtual bool GetGameParameterCookedData(FWwiseGameParameterCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) const { return false; } + virtual bool GetInitBankCookedData(FWwiseInitBankCookedData& OutCookedData, const FWwiseObjectInfo& InInfo = FWwiseObjectInfo::DefaultInitBank) const { return false; } + virtual bool GetMediaCookedData(FWwiseMediaCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) const { return false; } + virtual bool GetShareSetCookedData(FWwiseLocalizedShareSetCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) const { return false; } + virtual bool GetSoundBankCookedData(FWwiseLocalizedSoundBankCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) const { return false; } + virtual bool GetStateCookedData(FWwiseGroupValueCookedData& OutCookedData, const FWwiseGroupValueInfo& InInfo) const { return false; } + virtual bool GetSwitchCookedData(FWwiseGroupValueCookedData& OutCookedData, const FWwiseGroupValueInfo& InInfo) const { return false; } + virtual bool GetTriggerCookedData(FWwiseTriggerCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) const { return false; } + +protected: + FString SandboxRootPath; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Public/Wwise/WwiseResourceCookerImpl.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Public/Wwise/WwiseResourceCookerImpl.h new file mode 100644 index 0000000..05e4831 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Public/Wwise/WwiseResourceCookerImpl.h @@ -0,0 +1,83 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseResourceCooker.h" + +class WWISERESOURCECOOKER_API FWwiseResourceCookerImpl : public FWwiseResourceCooker +{ +public: + FWwiseResourceCookerImpl(); + ~FWwiseResourceCookerImpl() override; + + EWwiseExportDebugNameRule ExportDebugNameRule; + + FWwiseProjectDatabase* GetProjectDatabase() override; + const FWwiseProjectDatabase* GetProjectDatabase() const override; + + void PrepareResourceCookerForPlatform(FWwiseProjectDatabase*&& InProjectDatabaseOverride, EWwiseExportDebugNameRule InExportDebugNameRule) override; + +protected: + TUniquePtr CookingCache; + TUniquePtr ProjectDatabaseOverride; + + FWwiseCookingCache* GetCookingCache() override { return CookingCache.Get(); } + + void CookAuxBusToSandbox(const FWwiseAuxBusCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) override; + void CookEventToSandbox(const FWwiseEventCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) override; + void CookExternalSourceToSandbox(const FWwiseExternalSourceCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) override; + void CookInitBankToSandbox(const FWwiseInitBankCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) override; + void CookMediaToSandbox(const FWwiseMediaCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) override; + void CookShareSetToSandbox(const FWwiseShareSetCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) override; + void CookSoundBankToSandbox(const FWwiseSoundBankCookedData& InCookedData, WriteAdditionalFileFunction WriteAdditionalFile) override; + + void CookFileToSandbox(const FString& InInputPathName, const FName& InOutputPathName, WriteAdditionalFileFunction WriteAdditionalFile, bool bInStageRelativeToContent = false) override; + + bool GetAcousticTextureCookedData(FWwiseAcousticTextureCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) const override; + bool GetAuxBusCookedData(FWwiseLocalizedAuxBusCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) const override; + bool GetEventCookedData(FWwiseLocalizedEventCookedData& OutCookedData, const FWwiseEventInfo& InInfo) const override; + bool GetExternalSourceCookedData(FWwiseExternalSourceCookedData& OutCookedData, uint32 InCookie) const override; + bool GetGameParameterCookedData(FWwiseGameParameterCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) const override; + bool GetInitBankCookedData(FWwiseInitBankCookedData& OutCookedData, const FWwiseObjectInfo& InInfo = FWwiseObjectInfo::DefaultInitBank) const override; + bool GetMediaCookedData(FWwiseMediaCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) const override; + bool GetShareSetCookedData(FWwiseLocalizedShareSetCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) const override; + bool GetSoundBankCookedData(FWwiseLocalizedSoundBankCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) const override; + bool GetStateCookedData(FWwiseGroupValueCookedData& OutCookedData, const FWwiseGroupValueInfo& InInfo) const override; + bool GetSwitchCookedData(FWwiseGroupValueCookedData& OutCookedData, const FWwiseGroupValueInfo& InInfo) const override; + bool GetTriggerCookedData(FWwiseTriggerCookedData& OutCookedData, const FWwiseObjectInfo& InInfo) const override; + + virtual bool FillSoundBankBaseInfo(FWwiseSoundBankCookedData& OutSoundBankCookedData, + const FWwiseMetadataPlatformInfo& InPlatformInfo, + const FWwiseMetadataSoundBank& InSoundBank) const; + virtual bool FillMediaBaseInfo(FWwiseMediaCookedData& OutMediaCookedData, + const FWwiseMetadataPlatformInfo& InPlatformInfo, + const FWwiseMetadataSoundBank& InSoundBank, + const FWwiseMetadataMediaReference& InMediaReference) const; + virtual bool FillMediaBaseInfo(FWwiseMediaCookedData& OutMediaCookedData, + const FWwiseMetadataPlatformInfo& InPlatformInfo, + const FWwiseMetadataSoundBank& InSoundBank, + const FWwiseMetadataMedia& InMedia) const; + virtual bool FillExternalSourceBaseInfo(FWwiseExternalSourceCookedData& OutExternalSourceCookedData, + const FWwiseMetadataExternalSource& InExternalSource) const; + + virtual bool AddRequirementsForMedia(TSet& OutSoundBankSet, TSet& OutMediaSet, + const FWwiseRefMedia& InMediaRef, const FWwiseSharedLanguageId& InLanguage, + const FWwisePlatformDataStructure& InPlatformData) const; + virtual bool AddRequirementsForExternalSource(TSet& OutExternalSourceSet, + const FWwiseRefExternalSource& InExternalSourceRef) const; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Public/Wwise/WwiseResourceCookerModule.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Public/Wwise/WwiseResourceCookerModule.h new file mode 100644 index 0000000..dcdf8d4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Public/Wwise/WwiseResourceCookerModule.h @@ -0,0 +1,148 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Misc/ConfigCacheIni.h" +#include "Modules/ModuleManager.h" +#include "UObject/Class.h" + +struct FWwiseSharedPlatformId; +class FWwiseResourceCooker; + +UENUM() +enum class EWwiseExportDebugNameRule +{ + Release, + Name, + ObjectPath +}; + +class IWwiseResourceCookerModule : public IModuleInterface +{ +public: + void WWISERESOURCECOOKER_API StartupModule() override; + void WWISERESOURCECOOKER_API ShutdownModule() override; + + static FName GetModuleName() + { + static const FName ModuleName = GetModuleNameFromConfig(); + return ModuleName; + } + + /** + * Checks to see if this module is loaded and ready. + * + * @return True if the module is loaded and ready to use + */ + static bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded(GetModuleName()); + } + + static IWwiseResourceCookerModule* GetModule() + { + const auto ModuleName = GetModuleName(); + if (ModuleName.IsNone()) + { + return nullptr; + } +#if UE_SERVER + return nullptr; +#else + FModuleManager& ModuleManager = FModuleManager::Get(); + IWwiseResourceCookerModule* Result = ModuleManager.GetModulePtr(ModuleName); + if (UNLIKELY(!Result)) + { + if (UNLIKELY(IsEngineExitRequested())) + { + UE_LOG(LogLoad, Verbose, TEXT("Skipping reloading missing WwiseResourceCooker module: Exiting.")); + } + else if (UNLIKELY(!IsInGameThread())) + { + UE_LOG(LogLoad, Warning, TEXT("Skipping loading missing WwiseResourceCooker module: Not in game thread")); + } + else + { + UE_LOG(LogLoad, Log, TEXT("Loading WwiseResourceCooker module: %s"), *ModuleName.GetPlainNameString()); + Result = ModuleManager.LoadModulePtr(ModuleName); + if (UNLIKELY(!Result)) + { + UE_LOG(LogLoad, Fatal, TEXT("Could not load WwiseResourceCooker module: %s not found"), *ModuleName.GetPlainNameString()); + } + } + } + + return Result; +#endif + } + + virtual FWwiseResourceCooker* GetResourceCooker() + { + return nullptr; + } + virtual FWwiseResourceCooker* InstantiateResourceCooker() + { + return nullptr; + } + + virtual FWwiseResourceCooker* CreateCookerForPlatform( + const ITargetPlatform* TargetPlatform, + const FWwiseSharedPlatformId& InPlatform, + EWwiseExportDebugNameRule InExportDebugNameRule = EWwiseExportDebugNameRule::Release) + { + return nullptr; + } + + virtual void DestroyCookerForPlatform(const ITargetPlatform* TargetPlatform) + { + } + + virtual FWwiseResourceCooker* GetCookerForPlatform(const ITargetPlatform* TargetPlatform) + { + return nullptr; + } + + FWwiseResourceCooker* GetCookerForArchive(const FArchive& InArchive) + { + if (!InArchive.IsCooking() || !InArchive.IsSaving()) + { + return nullptr; + } + + return GetCookerForPlatform(InArchive.CookingTarget()); + } + + virtual void DestroyAllCookerPlatforms() + { + } + +protected: + static WWISERESOURCECOOKER_API FDelegateHandle ModifyCookDelegateHandle; + + virtual void OnModifyCook(TConstArrayView InTargetPlatforms, TArray& InOutPackagesToCook, TArray& InOutPackagesToNeverCook) + { + } + +private: + static inline FName GetModuleNameFromConfig() + { + FString ModuleName = TEXT("WwiseResourceCooker"); + GConfig->GetString(TEXT("Audio"), TEXT("WwiseResourceCookerModuleName"), ModuleName, GEngineIni); + return FName(ModuleName); + } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Public/Wwise/WwiseResourceCookerModuleImpl.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Public/Wwise/WwiseResourceCookerModuleImpl.h new file mode 100644 index 0000000..68ef414 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/Public/Wwise/WwiseResourceCookerModuleImpl.h @@ -0,0 +1,47 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseResourceCookerModule.h" +#include "Wwise/WwiseResourceCooker.h" + +class WWISERESOURCECOOKER_API FWwiseResourceCookerModule : public IWwiseResourceCookerModule +{ +public: + FWwiseResourceCooker* GetResourceCooker() override; + FWwiseResourceCooker* InstantiateResourceCooker() override; + + FWwiseResourceCooker* CreateCookerForPlatform( + const ITargetPlatform* TargetPlatform, + const FWwiseSharedPlatformId& InPlatform, + EWwiseExportDebugNameRule InExportDebugNameRule = EWwiseExportDebugNameRule::Release) override; + void DestroyCookerForPlatform(const ITargetPlatform* TargetPlatform) override; + FWwiseResourceCooker* GetCookerForPlatform(const ITargetPlatform* TargetPlatform) override; + + void DestroyAllCookerPlatforms() override; + + void ShutdownModule() override; + +protected: + FRWLock Lock; + TUniquePtr ResourceCooker; + + TMap> CookingPlatforms; + + void OnModifyCook(TConstArrayView InTargetPlatforms, TArray& InOutPackagesToCook, TArray& InOutPackagesToNeverCook) override; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/WwiseResourceCooker.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/WwiseResourceCooker.Build.cs new file mode 100644 index 0000000..d634df4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceCooker/WwiseResourceCooker.Build.cs @@ -0,0 +1,60 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; + +public class WwiseResourceCooker : ModuleRules +{ + public WwiseResourceCooker(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + + "WwiseResourceLoader" + } + ); + + if (Target.bBuildEditor) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "EditorSubsystem", + "UnrealEd", + + "WwiseFileHandler" + } + ); + } + + if (Target.bBuildWithEditorOnlyData) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "WwiseProjectDatabase" + } + ); + } + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseAcousticTextureCookedData.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseAcousticTextureCookedData.cpp new file mode 100644 index 0000000..4a05079 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseAcousticTextureCookedData.cpp @@ -0,0 +1,47 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/CookedData/WwiseAcousticTextureCookedData.h" + +#include "Wwise/Stats/ResourceLoader.h" + +#include + + +FWwiseAcousticTextureCookedData::FWwiseAcousticTextureCookedData(): ShortId(0) +{ +} + +void FWwiseAcousticTextureCookedData::Serialize(FArchive& Ar) +{ + UStruct* Struct = StaticStruct(); + check(Struct); + + if (Ar.WantBinaryPropertySerialization()) + { + Struct->SerializeBin(Ar, this); + } + else + { + Struct->SerializeTaggedProperties(Ar, (uint8*)this, Struct, nullptr); + } +} + +FString FWwiseAcousticTextureCookedData::GetDebugString() const +{ + return FString::Printf(TEXT("AcousticTexture %s (%" PRIu32 ")"), *DebugName.ToString(), ShortId); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseAuxBusCookedData.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseAuxBusCookedData.cpp new file mode 100644 index 0000000..7f71e03 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseAuxBusCookedData.cpp @@ -0,0 +1,77 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/CookedData/WwiseAuxBusCookedData.h" + +#include "Wwise/Stats/ResourceLoader.h" + +#include + +FWwiseAuxBusCookedData::FWwiseAuxBusCookedData(): + AuxBusId(0), + SoundBanks(), + Media(), + DebugName() +{} + +void FWwiseAuxBusCookedData::Serialize(FArchive& Ar) +{ + UStruct* Struct = StaticStruct(); + check(Struct); + + if (Ar.WantBinaryPropertySerialization()) + { + Struct->SerializeBin(Ar, this); + } + else + { + Struct->SerializeTaggedProperties(Ar, (uint8*)this, Struct, nullptr); + } +} + +FString FWwiseAuxBusCookedData::GetDebugString() const +{ + bool bFirst = true; + auto Result = FString::Printf(TEXT("Event %s (%" PRIu32 ")"), *DebugName.ToString(), AuxBusId); + if (SoundBanks.Num() > 0) + { + if (bFirst) + { + Result += TEXT(" with "); + bFirst = false; + } + else + { + Result += TEXT(" and "); + } + Result += FString::Printf(TEXT("%d SoundBank%s"), SoundBanks.Num(), SoundBanks.Num() > 1 ? TEXT("s") : TEXT("")); + } + if (Media.Num() > 0) + { + if (bFirst) + { + Result += TEXT(" with "); + bFirst = false; + } + else + { + Result += TEXT(" and "); + } + Result += FString::Printf(TEXT("%d Media"), Media.Num()); + } + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseEventCookedData.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseEventCookedData.cpp new file mode 100644 index 0000000..c5eeeee --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseEventCookedData.cpp @@ -0,0 +1,121 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/CookedData/WwiseEventCookedData.h" + +#include "Wwise/Stats/ResourceLoader.h" + +#include + +FWwiseEventCookedData::FWwiseEventCookedData(): + EventId(0), + SoundBanks(), + Media(), + ExternalSources(), + SwitchContainerLeaves(), + RequiredGroupValueSet(), + DestroyOptions(EWwiseEventDestroyOptions::WaitForEventEnd) +{} + +void FWwiseEventCookedData::Serialize(FArchive& Ar) +{ + UStruct* Struct = StaticStruct(); + check(Struct); + + if (Ar.WantBinaryPropertySerialization()) + { + Struct->SerializeBin(Ar, this); + } + else + { + Struct->SerializeTaggedProperties(Ar, (uint8*)this, Struct, nullptr); + } +} + +FString FWwiseEventCookedData::GetDebugString() const +{ + bool bFirst = true; + auto Result = FString::Printf(TEXT("Event %s (%" PRIu32 ")"), *DebugName.ToString(), EventId); + if (SoundBanks.Num() > 0) + { + if (bFirst) + { + Result += TEXT(" with "); + bFirst = false; + } + else + { + Result += TEXT(", "); + } + Result += FString::Printf(TEXT("%d SoundBank%s"), SoundBanks.Num(), SoundBanks.Num() > 1 ? TEXT("s") : TEXT("")); + } + if (Media.Num() > 0) + { + if (bFirst) + { + Result += TEXT(" with "); + bFirst = false; + } + else + { + Result += TEXT(", "); + } + Result += FString::Printf(TEXT("%d Media"), Media.Num()); + } + if (ExternalSources.Num() > 0) + { + if (bFirst) + { + Result += TEXT(" with "); + bFirst = false; + } + else + { + Result += TEXT(", "); + } + Result += FString::Printf(TEXT("%d ExternalSource%s"), ExternalSources.Num(), ExternalSources.Num() > 1 ? TEXT("s") : TEXT("")); + } + if (SwitchContainerLeaves.Num() > 0) + { + if (bFirst) + { + Result += TEXT(" with "); + bFirst = false; + } + else + { + Result += TEXT(", "); + } + Result += FString::Printf(TEXT("%d SwitchContainer Lea%s"), SwitchContainerLeaves.Num(), SwitchContainerLeaves.Num() > 1 ? TEXT("ves") : TEXT("f")); + } + if (RequiredGroupValueSet.Num() > 0) + { + if (bFirst) + { + Result += TEXT(" with "); + bFirst = false; + } + else + { + Result += TEXT(", "); + } + Result += FString::Printf(TEXT("%d GroupValue%s"), RequiredGroupValueSet.Num(), RequiredGroupValueSet.Num() > 1 ? TEXT("s") : TEXT("")); + } + + Result += FString::Printf(TEXT(" (%s)"), DestroyOptions == EWwiseEventDestroyOptions::StopEventOnDestroy ? TEXT("sod") : TEXT("wfe")); + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseGameParameterCookedData.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseGameParameterCookedData.cpp new file mode 100644 index 0000000..4bd2403 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseGameParameterCookedData.cpp @@ -0,0 +1,46 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/CookedData/WwiseGameParameterCookedData.h" + +#include "Wwise/Stats/ResourceLoader.h" + +#include + +FWwiseGameParameterCookedData::FWwiseGameParameterCookedData(): ShortId(0) +{ +} + +void FWwiseGameParameterCookedData::Serialize(FArchive& Ar) +{ + UStruct* Struct = StaticStruct(); + check(Struct); + + if (Ar.WantBinaryPropertySerialization()) + { + Struct->SerializeBin(Ar, this); + } + else + { + Struct->SerializeTaggedProperties(Ar, (uint8*)this, Struct, nullptr); + } +} + +FString FWwiseGameParameterCookedData::GetDebugString() const +{ + return FString::Printf(TEXT("GameParameter %s (%" PRIu32 ")"), *DebugName.ToString(), ShortId); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseGroupValueCookedData.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseGroupValueCookedData.cpp new file mode 100644 index 0000000..f298ad0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseGroupValueCookedData.cpp @@ -0,0 +1,66 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/CookedData/WwiseGroupValueCookedData.h" + +#include "Wwise/Stats/ResourceLoader.h" + +#include + +FWwiseGroupValueCookedData::FWwiseGroupValueCookedData(): + Type(EWwiseGroupType::Unknown), + GroupId(0), + Id(0) +{} + +const FString& FWwiseGroupValueCookedData::GetTypeName() const +{ + static const FString StateName = TEXT("State"); + static const FString SwitchName = TEXT("Switch"); + static const FString UnknownName = TEXT("Unknown"); + + switch(Type) + { + case EWwiseGroupType::State: + return StateName; + case EWwiseGroupType::Switch: + return SwitchName; + case EWwiseGroupType::Unknown: + default: + return UnknownName; + } +} + +void FWwiseGroupValueCookedData::Serialize(FArchive& Ar) +{ + UStruct* Struct = StaticStruct(); + check(Struct); + + if (Ar.WantBinaryPropertySerialization()) + { + Struct->SerializeBin(Ar, this); + } + else + { + Struct->SerializeTaggedProperties(Ar, (uint8*)this, Struct, nullptr); + } +} + +FString FWwiseGroupValueCookedData::GetDebugString() const +{ + return FString::Printf(TEXT("%s %s (%" PRIu32 ":%" PRIu32 ")"), *GetTypeName(), *DebugName.ToString(), GroupId, Id); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseInitBankCookedData.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseInitBankCookedData.cpp new file mode 100644 index 0000000..19812c4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseInitBankCookedData.cpp @@ -0,0 +1,62 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/CookedData/WwiseInitBankCookedData.h" + +#include "Wwise/Stats/ResourceLoader.h" + +#include + +FWwiseInitBankCookedData::FWwiseInitBankCookedData(): + Media() +{} + +void FWwiseInitBankCookedData::Serialize(FArchive& Ar) +{ + UStruct* Struct = StaticStruct(); + check(Struct); + + if (Ar.WantBinaryPropertySerialization()) + { + Struct->SerializeBin(Ar, this); + } + else + { + Struct->SerializeTaggedProperties(Ar, (uint8*)this, Struct, nullptr); + } +} + +FString FWwiseInitBankCookedData::GetDebugString() const +{ + if (Media.Num() > 0) + { + return FString::Printf(TEXT("InitBank %s (%" PRIu32 ") with %d Media @ %s (ma:%" PRIi32 " %sdm %smedia %suser)"), + *DebugName.ToString(), SoundBankId, + Media.Num(), + *SoundBankPathName.ToString(), MemoryAlignment, + bDeviceMemory ? TEXT("") : TEXT("!"), bContainsMedia ? TEXT("") : TEXT("!"), + SoundBankType == EWwiseSoundBankType::User ? TEXT("") : TEXT("!")); + } + else + { + return FString::Printf(TEXT("InitBank %s (%" PRIu32 ") @ %s (ma:%" PRIi32 " %sdm %smedia %suser)"), + *DebugName.ToString(), SoundBankId, + *SoundBankPathName.ToString(), MemoryAlignment, + bDeviceMemory ? TEXT("") : TEXT("!"), bContainsMedia ? TEXT("") : TEXT("!"), + SoundBankType == EWwiseSoundBankType::User ? TEXT("") : TEXT("!")); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseLocalizedAuxBusCookedData.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseLocalizedAuxBusCookedData.cpp new file mode 100644 index 0000000..2f364c1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseLocalizedAuxBusCookedData.cpp @@ -0,0 +1,39 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/CookedData/WwiseLocalizedAuxBusCookedData.h" + +FWwiseLocalizedAuxBusCookedData::FWwiseLocalizedAuxBusCookedData(): + AuxBusLanguageMap(), + DebugName(), + AuxBusId(0) +{} + +void FWwiseLocalizedAuxBusCookedData::Serialize(FArchive& Ar) +{ + UStruct* Struct = StaticStruct(); + check(Struct); + + if (Ar.WantBinaryPropertySerialization()) + { + Struct->SerializeBin(Ar, this); + } + else + { + Struct->SerializeTaggedProperties(Ar, (uint8*)this, Struct, nullptr); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseLocalizedEventCookedData.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseLocalizedEventCookedData.cpp new file mode 100644 index 0000000..8812d13 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseLocalizedEventCookedData.cpp @@ -0,0 +1,39 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/CookedData/WwiseLocalizedEventCookedData.h" + +FWwiseLocalizedEventCookedData::FWwiseLocalizedEventCookedData(): + EventLanguageMap(), + DebugName(), + EventId(0) +{} + +void FWwiseLocalizedEventCookedData::Serialize(FArchive& Ar) +{ + UStruct* Struct = StaticStruct(); + check(Struct); + + if (Ar.WantBinaryPropertySerialization()) + { + Struct->SerializeBin(Ar, this); + } + else + { + Struct->SerializeTaggedProperties(Ar, (uint8*)this, Struct, nullptr); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseLocalizedShareSetCookedData.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseLocalizedShareSetCookedData.cpp new file mode 100644 index 0000000..1f648e3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseLocalizedShareSetCookedData.cpp @@ -0,0 +1,39 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/CookedData/WwiseLocalizedShareSetCookedData.h" + +FWwiseLocalizedShareSetCookedData::FWwiseLocalizedShareSetCookedData(): + ShareSetLanguageMap(), + DebugName(), + ShareSetId(0) +{} + +void FWwiseLocalizedShareSetCookedData::Serialize(FArchive& Ar) +{ + UStruct* Struct = StaticStruct(); + check(Struct); + + if (Ar.WantBinaryPropertySerialization()) + { + Struct->SerializeBin(Ar, this); + } + else + { + Struct->SerializeTaggedProperties(Ar, (uint8*)this, Struct, nullptr); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseLocalizedSoundBankCookedData.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseLocalizedSoundBankCookedData.cpp new file mode 100644 index 0000000..0dc6c76 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseLocalizedSoundBankCookedData.cpp @@ -0,0 +1,39 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/CookedData/WwiseLocalizedSoundBankCookedData.h" + +FWwiseLocalizedSoundBankCookedData::FWwiseLocalizedSoundBankCookedData(): + SoundBankLanguageMap(), + DebugName(), + SoundBankId(0) +{} + +void FWwiseLocalizedSoundBankCookedData::Serialize(FArchive& Ar) +{ + UStruct* Struct = StaticStruct(); + check(Struct); + + if (Ar.WantBinaryPropertySerialization()) + { + Struct->SerializeBin(Ar, this); + } + else + { + Struct->SerializeTaggedProperties(Ar, (uint8*)this, Struct, nullptr); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseShareSetCookedData.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseShareSetCookedData.cpp new file mode 100644 index 0000000..28d6af4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseShareSetCookedData.cpp @@ -0,0 +1,50 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/CookedData/WwiseShareSetCookedData.h" + +#include "Wwise/Stats/ResourceLoader.h" + +#include + +FWwiseShareSetCookedData::FWwiseShareSetCookedData(): + ShareSetId(0), + SoundBanks(), + Media(), + DebugName() +{} + +void FWwiseShareSetCookedData::Serialize(FArchive& Ar) +{ + UStruct* Struct = StaticStruct(); + check(Struct); + + if (Ar.WantBinaryPropertySerialization()) + { + Struct->SerializeBin(Ar, this); + } + else + { + Struct->SerializeTaggedProperties(Ar, (uint8*)this, Struct, nullptr); + } +} + +FString FWwiseShareSetCookedData::GetDebugString() const +{ + return FString::Printf(TEXT("ShareSet %s (%" PRIu32 ") with %d SoundBanks and %d Media"), *DebugName.ToString(), ShareSetId, + SoundBanks.Num(), Media.Num()); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseSwitchContainerLeafCookedData.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseSwitchContainerLeafCookedData.cpp new file mode 100644 index 0000000..8231ab2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseSwitchContainerLeafCookedData.cpp @@ -0,0 +1,84 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/CookedData/WwiseSwitchContainerLeafCookedData.h" + +#include "Wwise/Stats/ResourceLoader.h" + +#include + +FWwiseSwitchContainerLeafCookedData::FWwiseSwitchContainerLeafCookedData(): + GroupValueSet(), + SoundBanks(), + Media(), + ExternalSources() +{} + +void FWwiseSwitchContainerLeafCookedData::Serialize(FArchive& Ar) +{ + UStruct* Struct = StaticStruct(); + check(Struct); + + if (Ar.WantBinaryPropertySerialization()) + { + Struct->SerializeBin(Ar, this); + } + else + { + Struct->SerializeTaggedProperties(Ar, (uint8*)this, Struct, nullptr); + } +} + +bool FWwiseSwitchContainerLeafCookedData::operator==(const FWwiseSwitchContainerLeafCookedData& Rhs) const +{ + if (GroupValueSet.Num() != Rhs.GroupValueSet.Num()) + { + return false; + } + for (const auto& Elem : GroupValueSet) + { + if (!Rhs.GroupValueSet.Contains(Elem)) + { + return false; + } + } + return true; +} + +FString FWwiseSwitchContainerLeafCookedData::GetDebugString() const +{ + auto Result = FString::Printf(TEXT("Switch Container Leaf %d"), static_cast(GroupValueSet.Num())); + bool Add = false; + for (const auto& GroupValue : GroupValueSet) + { + if (Add) + { + Result += TEXT(", "); + } + else + { + Result += TEXT(" ["); + Add = true; + } + Result += GroupValue.GetDebugString(); + } + if (Add) + { + Result += TEXT("]"); + } + return Result; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseTriggerCookedData.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseTriggerCookedData.cpp new file mode 100644 index 0000000..f14de5e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/CookedData/WwiseTriggerCookedData.cpp @@ -0,0 +1,46 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/CookedData/WwiseTriggerCookedData.h" + +#include "Wwise/Stats/ResourceLoader.h" + +#include + +FWwiseTriggerCookedData::FWwiseTriggerCookedData(): TriggerId(0) +{ +} + +void FWwiseTriggerCookedData::Serialize(FArchive& Ar) +{ + UStruct* Struct = StaticStruct(); + check(Struct); + + if (Ar.WantBinaryPropertySerialization()) + { + Struct->SerializeBin(Ar, this); + } + else + { + Struct->SerializeTaggedProperties(Ar, (uint8*)this, Struct, nullptr); + } +} + +FString FWwiseTriggerCookedData::GetDebugString() const +{ + return FString::Printf(TEXT("Trigger %s (%" PRIu32 ")"), *DebugName.ToString(), TriggerId); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Info/WwiseEventInfoLibrary.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Info/WwiseEventInfoLibrary.cpp new file mode 100644 index 0000000..8c801d9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Info/WwiseEventInfoLibrary.cpp @@ -0,0 +1,18 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Info/WwiseEventInfoLibrary.h" diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Info/WwiseEventInfoLibrary.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Info/WwiseEventInfoLibrary.h new file mode 100644 index 0000000..03dce2d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Info/WwiseEventInfoLibrary.h @@ -0,0 +1,189 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Wwise/Info/WwiseEventInfo.h" + +#include "WwiseEventInfoLibrary.generated.h" + +UCLASS() +class WWISERESOURCELOADER_API UWwiseEventInfoLibrary: public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintPure, Category = "Wwise|EventInfo", Meta = (BlueprintThreadSafe, DisplayName = "Make EventInfo", AdvancedDisplay = "DestroyOptions, HardCodedSoundBankShortId")) + static + UPARAM(DisplayName="Event Info") FWwiseEventInfo + MakeStruct( + const FGuid& WwiseGuid, + int32 WwiseShortId, + const FString& WwiseName, + EWwiseEventSwitchContainerLoading SwitchContainerLoading, + EWwiseEventDestroyOptions DestroyOptions, + int32 HardCodedSoundBankShortId = 0) + { + return FWwiseEventInfo(WwiseGuid, (uint32)WwiseShortId, FName(WwiseName), SwitchContainerLoading, DestroyOptions, (uint32)HardCodedSoundBankShortId); + } + + UFUNCTION(BlueprintPure, Category = "Wwise|EventInfo", Meta = (BlueprintThreadSafe, DisplayName = "Break EventInfo", AdvancedDisplay = "OutDestroyOptions, OutHardCodedSoundBankShortId")) + static void + BreakStruct( + UPARAM(DisplayName="Event Info") FWwiseEventInfo Ref, + FGuid& OutWwiseGuid, + int32& OutWwiseShortId, + FString& OutWwiseName, + EWwiseEventSwitchContainerLoading& OutSwitchContainerLoading, + EWwiseEventDestroyOptions& OutDestroyOptions, + int32& OutHardCodedSoundBankShortId) + { + OutWwiseGuid = Ref.WwiseGuid; + OutWwiseShortId = (int32)Ref.WwiseShortId; + OutWwiseName = Ref.WwiseName.ToString(); + OutSwitchContainerLoading = Ref.SwitchContainerLoading; + OutDestroyOptions = Ref.DestroyOptions; + OutHardCodedSoundBankShortId = (int32)Ref.HardCodedSoundBankShortId; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|Event Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="GUID") FGuid + GetWwiseGuid( + UPARAM(DisplayName="Event Info") const FWwiseEventInfo& Ref) + { + return Ref.WwiseGuid; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|Event Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Short Id") int32 + GetWwiseShortId( + UPARAM(DisplayName="Event Info") const FWwiseEventInfo& Ref) + { + return (int32)Ref.WwiseShortId; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|Event Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Name") FString + GetWwiseName( + UPARAM(DisplayName="Event Info") const FWwiseEventInfo& Ref) + { + return Ref.WwiseName.ToString(); + } + + UFUNCTION(BlueprintPure, Category = "Wwise|Event Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Switch Container Loading") EWwiseEventSwitchContainerLoading + GetSwitchContainerLoading( + UPARAM(DisplayName="Event Info") const FWwiseEventInfo& Ref) + { + return Ref.SwitchContainerLoading; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|Event Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Destroy Options") EWwiseEventDestroyOptions + GetDestroyOptions( + UPARAM(DisplayName="Event Info") const FWwiseEventInfo& Ref) + { + return Ref.DestroyOptions; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|Event Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Short Id") int32 + GetHardCodedSoundBankShortId( + UPARAM(DisplayName="Event Info") const FWwiseEventInfo& Ref) + { + return (int32)Ref.HardCodedSoundBankShortId; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|Event Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Struct Out") FWwiseEventInfo + SetWwiseGuid( + UPARAM(DisplayName="Event Info") const FWwiseEventInfo& Ref, + const FGuid& WwiseGuid) + { + auto Result = Ref; + Result.WwiseGuid = WwiseGuid; + return Result; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|Event Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Struct Out") FWwiseEventInfo + SetWwiseShortId( + UPARAM(DisplayName="Event Info") const FWwiseEventInfo& Ref, + int32 WwiseShortId) + { + auto Result = Ref; + Result.WwiseShortId = WwiseShortId; + return Result; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|Event Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Struct Out") FWwiseEventInfo + SetWwiseName( + UPARAM(DisplayName="Event Info") const FWwiseEventInfo& Ref, + const FString& WwiseName) + { + auto Result = Ref; + Result.WwiseName = FName(WwiseName); + return Result; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|Event Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Struct Out") FWwiseEventInfo + SetSwitchContainerLoading( + UPARAM(DisplayName="Event Info") const FWwiseEventInfo& Ref, + const EWwiseEventSwitchContainerLoading& SwitchContainerLoading) + { + auto Result = Ref; + Result.SwitchContainerLoading = SwitchContainerLoading; + return Result; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|Event Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Struct Out") FWwiseEventInfo + SetDestroyOptions( + UPARAM(DisplayName="Event Info") const FWwiseEventInfo& Ref, + const EWwiseEventDestroyOptions& DestroyOptions) + { + auto Result = Ref; + Result.DestroyOptions = DestroyOptions; + return Result; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|Event Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Struct Out") FWwiseEventInfo + SetHardCodedSoundBankShortId( + UPARAM(DisplayName="Event Info") const FWwiseEventInfo& Ref, + int32 HardCodedSoundBankShortId = 0) + { + auto Result = Ref; + Result.HardCodedSoundBankShortId = (uint32)HardCodedSoundBankShortId; + return Result; + } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Info/WwiseGroupValueInfoLibrary.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Info/WwiseGroupValueInfoLibrary.cpp new file mode 100644 index 0000000..374f8d3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Info/WwiseGroupValueInfoLibrary.cpp @@ -0,0 +1,18 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Info/WwiseGroupValueInfoLibrary.h" diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Info/WwiseGroupValueInfoLibrary.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Info/WwiseGroupValueInfoLibrary.h new file mode 100644 index 0000000..9156c2a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Info/WwiseGroupValueInfoLibrary.h @@ -0,0 +1,141 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Wwise/Info/WwiseGroupValueInfo.h" + +#include "WwiseGroupValueInfoLibrary.generated.h" + +UCLASS() +class WWISERESOURCELOADER_API UWwiseGroupValueInfoLibrary: public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintPure, Category = "Wwise|GroupValueInfo", Meta = (BlueprintThreadSafe, DisplayName = "Make GroupValueInfo")) + static + UPARAM(DisplayName="GroupValue Info") FWwiseGroupValueInfo + MakeStruct( + const FGuid& AssetGuid, + int32 GroupShortId, + int32 WwiseShortId, + const FString& WwiseName) + { + return FWwiseGroupValueInfo(AssetGuid, (uint32)GroupShortId, (uint32)WwiseShortId, FName(WwiseName)); + } + + UFUNCTION(BlueprintPure, Category = "Wwise|GroupValueInfo", Meta = (BlueprintThreadSafe, DisplayName = "Break GroupValueInfo")) + static void + BreakStruct( + UPARAM(DisplayName="GroupValue Info") FWwiseGroupValueInfo Ref, + FGuid& OutAssetGuid, + int32& OutGroupShortId, + int32& OutWwiseShortId, + FString& OutWwiseName) + { + OutAssetGuid = Ref.WwiseGuid; + OutGroupShortId = (int32)Ref.GroupShortId; + OutWwiseShortId = (int32)Ref.WwiseShortId; + OutWwiseName = Ref.WwiseName.ToString(); + } + + UFUNCTION(BlueprintPure, Category = "Wwise|GroupValue Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="GUID") FGuid + GetAssetGuid( + UPARAM(DisplayName="GroupValue Info") const FWwiseGroupValueInfo& Ref) + { + return Ref.WwiseGuid; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|GroupValue Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Group Short Id") int32 + GetGroupShortId( + UPARAM(DisplayName="GroupValue Info") const FWwiseGroupValueInfo& Ref) + { + return (int32)Ref.GroupShortId; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|GroupValue Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Short Id") int32 + GetWwiseShortId( + UPARAM(DisplayName="GroupValue Info") const FWwiseGroupValueInfo& Ref) + { + return (int32)Ref.WwiseShortId; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|GroupValue Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Name") FString + GetWwiseName( + UPARAM(DisplayName="GroupValue Info") const FWwiseGroupValueInfo& Ref) + { + return Ref.WwiseName.ToString(); + } + + UFUNCTION(BlueprintPure, Category = "Wwise|GroupValue Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Struct Out") FWwiseGroupValueInfo + SetAssetGuid( + UPARAM(DisplayName="GroupValue Info") const FWwiseGroupValueInfo& Ref, + const FGuid& AssetGuid) + { + auto Result = Ref; + Result.WwiseGuid = AssetGuid; + return Result; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|GroupValue Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Struct Out") FWwiseGroupValueInfo + SetGroupShortId( + UPARAM(DisplayName="GroupValue Info") const FWwiseGroupValueInfo& Ref, + int32 GroupShortId) + { + auto Result = Ref; + Result.GroupShortId = (uint32)GroupShortId; + return Result; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|GroupValue Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Struct Out") FWwiseGroupValueInfo + SetWwiseShortId( + UPARAM(DisplayName="GroupValue Info") const FWwiseGroupValueInfo& Ref, + int32 WwiseShortId) + { + auto Result = Ref; + Result.WwiseShortId = WwiseShortId; + return Result; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|GroupValue Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Struct Out") FWwiseGroupValueInfo + SetWwiseName( + UPARAM(DisplayName="GroupValue Info") const FWwiseGroupValueInfo& Ref, + const FString& WwiseName) + { + auto Result = Ref; + Result.WwiseName = FName(WwiseName); + return Result; + } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Info/WwiseObjectInfo.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Info/WwiseObjectInfo.cpp new file mode 100644 index 0000000..9d33ec1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Info/WwiseObjectInfo.cpp @@ -0,0 +1,20 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Info/WwiseObjectInfo.h" + +const FWwiseObjectInfo FWwiseObjectInfo::DefaultInitBank = FWwiseObjectInfo(FGuid(0x701ECBBD, 0x9C7B4030, 0x8CDB749E, 0xE5D1C7B9), 1355168291, FName("Init")); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Info/WwiseObjectInfoLibrary.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Info/WwiseObjectInfoLibrary.cpp new file mode 100644 index 0000000..ed86bdf --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Info/WwiseObjectInfoLibrary.cpp @@ -0,0 +1,18 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Info/WwiseObjectInfoLibrary.h" diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Info/WwiseObjectInfoLibrary.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Info/WwiseObjectInfoLibrary.h new file mode 100644 index 0000000..869da1e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Info/WwiseObjectInfoLibrary.h @@ -0,0 +1,141 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Wwise/Info/WwiseObjectInfo.h" + +#include "WwiseObjectInfoLibrary.generated.h" + +UCLASS() +class WWISERESOURCELOADER_API UWwiseObjectInfoLibrary: public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintPure, Category = "Wwise|WwiseObjectInfo", Meta = (BlueprintThreadSafe, DisplayName = "Make WwiseObjectInfo", AdvancedDisplay = "HardCodedSoundBankShortId")) + static + UPARAM(DisplayName="Wwise Object Info") FWwiseObjectInfo + MakeStruct( + const FGuid& WwiseGuid, + int32 WwiseShortId, + const FString& WwiseName, + int32 HardCodedSoundBankShortId = 0) + { + return FWwiseObjectInfo(WwiseGuid, (uint32)WwiseShortId, WwiseName, (uint32)HardCodedSoundBankShortId); + } + + UFUNCTION(BlueprintPure, Category = "Wwise|WwiseObjectInfo", Meta = (BlueprintThreadSafe, DisplayName = "Break WwiseObjectInfo", AdvancedDisplay = "OutHardCodedSoundBankShortId")) + static void + BreakStruct( + UPARAM(DisplayName="Wwise Object Info") FWwiseObjectInfo Ref, + FGuid& OutWwiseGuid, + int32& OutWwiseShortId, + FString& OutWwiseName, + int32& OutHardCodedSoundBankShortId) + { + OutWwiseGuid = Ref.WwiseGuid; + OutWwiseShortId = (int32)Ref.WwiseShortId; + OutWwiseName = Ref.WwiseName.ToString(); + OutHardCodedSoundBankShortId = (int32)Ref.HardCodedSoundBankShortId; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|Wwise Object Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="GUID") FGuid + GetWwiseGuid( + UPARAM(DisplayName="Wwise Object Info") const FWwiseObjectInfo& Ref) + { + return Ref.WwiseGuid; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|Wwise Object Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Short Id") int32 + GetWwiseShortId( + UPARAM(DisplayName="Wwise Object Info") const FWwiseObjectInfo& Ref) + { + return (int32)Ref.WwiseShortId; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|Wwise Object Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Name") FString + GetWwiseName( + UPARAM(DisplayName="Wwise Object Info") const FWwiseObjectInfo& Ref) + { + return Ref.WwiseName.ToString(); + } + + UFUNCTION(BlueprintPure, Category = "Wwise|Wwise Object Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Short Id") int32 + GetHardCodedSoundBankShortId( + UPARAM(DisplayName="Wwise Object Info") const FWwiseObjectInfo& Ref) + { + return (int32)Ref.HardCodedSoundBankShortId; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|Wwise Object Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Struct Out") FWwiseObjectInfo + SetWwiseGuid( + UPARAM(DisplayName="Wwise Object Info") const FWwiseObjectInfo& Ref, + const FGuid& WwiseGuid) + { + auto Result = Ref; + Result.WwiseGuid = WwiseGuid; + return Result; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|Wwise Object Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Struct Out") FWwiseObjectInfo + SetWwiseShortId( + UPARAM(DisplayName="Wwise Object Info") const FWwiseObjectInfo& Ref, + int32 WwiseShortId) + { + auto Result = Ref; + Result.WwiseShortId = WwiseShortId; + return Result; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|Wwise Object Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Struct Out") FWwiseObjectInfo + SetWwiseName( + UPARAM(DisplayName="Wwise Object Info") const FWwiseObjectInfo& Ref, + const FString& WwiseName) + { + auto Result = Ref; + Result.WwiseName = FName(WwiseName); + return Result; + } + + UFUNCTION(BlueprintPure, Category = "Wwise|Wwise Object Info", Meta = (BlueprintThreadSafe)) + static + UPARAM(DisplayName="Struct Out") FWwiseObjectInfo + SetHardCodedSoundBankShortId( + UPARAM(DisplayName="Wwise Object Info") const FWwiseObjectInfo& Ref, + int32 HardCodedSoundBankShortId = 0) + { + auto Result = Ref; + Result.HardCodedSoundBankShortId = (uint32)HardCodedSoundBankShortId; + return Result; + } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedAuxBus.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedAuxBus.cpp new file mode 100644 index 0000000..28b1946 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedAuxBus.cpp @@ -0,0 +1,55 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Loaded/WwiseLoadedAuxBus.h" +#include "Wwise/Stats/ResourceLoader.h" + +#include + + +FWwiseLoadedAuxBusInfo::FWwiseLoadedAuxBusInfo(const FWwiseLocalizedAuxBusCookedData& InAuxBus, + const FWwiseLanguageCookedData& InLanguage): + LocalizedAuxBusCookedData(InAuxBus), + LanguageRef(InLanguage) +{} + +bool FWwiseLoadedAuxBusInfo::FLoadedData::IsLoaded() const +{ + return LoadedSoundBanks.Num() > 0 || LoadedMedia.Num() > 0; +} + +FString FWwiseLoadedAuxBusInfo::GetDebugString() const +{ + if (const auto* CookedData = LocalizedAuxBusCookedData.AuxBusLanguageMap.Find(LanguageRef)) + { + return FString::Printf(TEXT("%s in language %s (%" PRIu32 ")"), + *CookedData->GetDebugString(), + *LanguageRef.LanguageName.ToString(), LanguageRef.LanguageId); + } + else + { + return FString::Printf(TEXT("AuxBus %s (%" PRIu32 ") unset for language %s (%" PRIu32 ")"), + *LocalizedAuxBusCookedData.DebugName.ToString(), LocalizedAuxBusCookedData.AuxBusId, + *LanguageRef.LanguageName.ToString(), LanguageRef.LanguageId); + } +} + +FWwiseLoadedAuxBusInfo::FWwiseLoadedAuxBusInfo(const FWwiseLoadedAuxBusInfo& InOriginal): + LocalizedAuxBusCookedData(InOriginal.LocalizedAuxBusCookedData), + LanguageRef(InOriginal.LanguageRef) +{ +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedEvent.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedEvent.cpp new file mode 100644 index 0000000..c60988a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedEvent.cpp @@ -0,0 +1,55 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Loaded/WwiseLoadedEvent.h" +#include "Wwise/Stats/ResourceLoader.h" + +#include + + +FWwiseLoadedEventInfo::FWwiseLoadedEventInfo(const FWwiseLocalizedEventCookedData& InEvent, + const FWwiseLanguageCookedData& InLanguage): + LocalizedEventCookedData(InEvent), + LanguageRef(InLanguage) +{} + +bool FWwiseLoadedEventInfo::FLoadedData::IsLoaded() const +{ + return bLoadedSwitchContainerLeaves || LoadedSoundBanks.Num() > 0 || LoadedExternalSources.Num() > 0 || LoadedMedia.Num() > 0 || LoadedRequiredGroupValues.Num() > 0; +} + +FString FWwiseLoadedEventInfo::GetDebugString() const +{ + if (const auto* CookedData = LocalizedEventCookedData.EventLanguageMap.Find(LanguageRef)) + { + return FString::Printf(TEXT("%s in language %s (%" PRIu32 ")"), + *CookedData->GetDebugString(), + *LanguageRef.LanguageName.ToString(), LanguageRef.LanguageId); + } + else + { + return FString::Printf(TEXT("Event %s (%" PRIu32 ") unset for language %s (%" PRIu32 ")"), + *LocalizedEventCookedData.DebugName.ToString(), LocalizedEventCookedData.EventId, + *LanguageRef.LanguageName.ToString(), LanguageRef.LanguageId); + } +} + +FWwiseLoadedEventInfo::FWwiseLoadedEventInfo(const FWwiseLoadedEventInfo& InOriginal): + LocalizedEventCookedData(InOriginal.LocalizedEventCookedData), + LanguageRef(InOriginal.LanguageRef) +{ +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedExternalSource.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedExternalSource.cpp new file mode 100644 index 0000000..eb272a0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedExternalSource.cpp @@ -0,0 +1,41 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Loaded/WwiseLoadedExternalSource.h" +#include "Wwise/Stats/ResourceLoader.h" + +#include + + +FWwiseLoadedExternalSourceInfo::FWwiseLoadedExternalSourceInfo(const FWwiseExternalSourceCookedData& InExternalSource): + ExternalSourceCookedData(InExternalSource) +{} + +bool FWwiseLoadedExternalSourceInfo::FLoadedData::IsLoaded() const +{ + return bLoaded; +} + +FString FWwiseLoadedExternalSourceInfo::GetDebugString() const +{ + return ExternalSourceCookedData.GetDebugString(); +} + +FWwiseLoadedExternalSourceInfo::FWwiseLoadedExternalSourceInfo(const FWwiseLoadedExternalSourceInfo& InOriginal): + ExternalSourceCookedData(InOriginal.ExternalSourceCookedData) +{ +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedGroupValue.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedGroupValue.cpp new file mode 100644 index 0000000..8edac6c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedGroupValue.cpp @@ -0,0 +1,41 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Loaded/WwiseLoadedGroupValue.h" +#include "Wwise/Stats/ResourceLoader.h" + +#include + + +FWwiseLoadedGroupValueInfo::FWwiseLoadedGroupValueInfo(const FWwiseGroupValueCookedData& InGroupValue): + GroupValueCookedData(InGroupValue) +{} + +bool FWwiseLoadedGroupValueInfo::FLoadedData::IsLoaded() const +{ + return bLoaded; +} + +FString FWwiseLoadedGroupValueInfo::GetDebugString() const +{ + return GroupValueCookedData.GetDebugString(); +} + +FWwiseLoadedGroupValueInfo::FWwiseLoadedGroupValueInfo(const FWwiseLoadedGroupValueInfo& InOriginal): + GroupValueCookedData(InOriginal.GroupValueCookedData) +{ +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedInitBank.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedInitBank.cpp new file mode 100644 index 0000000..e4958ae --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedInitBank.cpp @@ -0,0 +1,41 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Loaded/WwiseLoadedInitBank.h" +#include "Wwise/Stats/ResourceLoader.h" + +#include + + +FWwiseLoadedInitBankInfo::FWwiseLoadedInitBankInfo(const FWwiseInitBankCookedData& InInitBank): + InitBankCookedData(InInitBank) +{} + +bool FWwiseLoadedInitBankInfo::FLoadedData::IsLoaded() const +{ + return bLoaded || LoadedMedia.Num() > 0; +} + +FString FWwiseLoadedInitBankInfo::GetDebugString() const +{ + return InitBankCookedData.GetDebugString(); +} + +FWwiseLoadedInitBankInfo::FWwiseLoadedInitBankInfo(const FWwiseLoadedInitBankInfo& InOriginal): + InitBankCookedData(InOriginal.InitBankCookedData) +{ +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedMedia.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedMedia.cpp new file mode 100644 index 0000000..bfb763a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedMedia.cpp @@ -0,0 +1,41 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Loaded/WwiseLoadedMedia.h" +#include "Wwise/Stats/ResourceLoader.h" + +#include + + +FWwiseLoadedMediaInfo::FWwiseLoadedMediaInfo(const FWwiseMediaCookedData& InMedia): + MediaCookedData(InMedia) +{} + +bool FWwiseLoadedMediaInfo::FLoadedData::IsLoaded() const +{ + return bLoaded; +} + +FString FWwiseLoadedMediaInfo::GetDebugString() const +{ + return MediaCookedData.GetDebugString(); +} + +FWwiseLoadedMediaInfo::FWwiseLoadedMediaInfo(const FWwiseLoadedMediaInfo& InOriginal): + MediaCookedData(InOriginal.MediaCookedData) +{ +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedShareSet.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedShareSet.cpp new file mode 100644 index 0000000..c4732ab --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedShareSet.cpp @@ -0,0 +1,55 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Loaded/WwiseLoadedShareSet.h" +#include "Wwise/Stats/ResourceLoader.h" + +#include + + +FWwiseLoadedShareSetInfo::FWwiseLoadedShareSetInfo(const FWwiseLocalizedShareSetCookedData& InShareSet, + const FWwiseLanguageCookedData& InLanguage): + LocalizedShareSetCookedData(InShareSet), + LanguageRef(InLanguage) +{} + +bool FWwiseLoadedShareSetInfo::FLoadedData::IsLoaded() const +{ + return LoadedSoundBanks.Num() > 0 || LoadedMedia.Num() > 0; +} + +FString FWwiseLoadedShareSetInfo::GetDebugString() const +{ + if (const auto* CookedData = LocalizedShareSetCookedData.ShareSetLanguageMap.Find(LanguageRef)) + { + return FString::Printf(TEXT("%s in language %s (%" PRIu32 ")"), + *CookedData->GetDebugString(), + *LanguageRef.LanguageName.ToString(), LanguageRef.LanguageId); + } + else + { + return FString::Printf(TEXT("ShareSet %s (%" PRIu32 ") unset for language %s (%" PRIu32 ")"), + *LocalizedShareSetCookedData.DebugName.ToString(), LocalizedShareSetCookedData.ShareSetId, + *LanguageRef.LanguageName.ToString(), LanguageRef.LanguageId); + } +} + +FWwiseLoadedShareSetInfo::FWwiseLoadedShareSetInfo(const FWwiseLoadedShareSetInfo& InOriginal): + LocalizedShareSetCookedData(InOriginal.LocalizedShareSetCookedData), + LanguageRef(InOriginal.LanguageRef) +{ +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedSoundBank.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedSoundBank.cpp new file mode 100644 index 0000000..09239cc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Loaded/WwiseLoadedSoundBank.cpp @@ -0,0 +1,55 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Loaded/WwiseLoadedSoundBank.h" +#include "Wwise/Stats/ResourceLoader.h" + +#include + + +FWwiseLoadedSoundBankInfo::FWwiseLoadedSoundBankInfo(const FWwiseLocalizedSoundBankCookedData& InSoundBank, + const FWwiseLanguageCookedData& InLanguage): + LocalizedSoundBankCookedData(InSoundBank), + LanguageRef(InLanguage) +{} + +bool FWwiseLoadedSoundBankInfo::FLoadedData::IsLoaded() const +{ + return bLoaded; +} + +FString FWwiseLoadedSoundBankInfo::GetDebugString() const +{ + if (const auto* CookedData = LocalizedSoundBankCookedData.SoundBankLanguageMap.Find(LanguageRef)) + { + return FString::Printf(TEXT("%s in language %s (%" PRIu32 ")"), + *CookedData->GetDebugString(), + *LanguageRef.LanguageName.ToString(), LanguageRef.LanguageId); + } + else + { + return FString::Printf(TEXT("SoundBank %s (%" PRIu32 ") unset for language %s (%" PRIu32 ")"), + *LocalizedSoundBankCookedData.DebugName.ToString(), LocalizedSoundBankCookedData.SoundBankId, + *LanguageRef.LanguageName.ToString(), LanguageRef.LanguageId); + } +} + +FWwiseLoadedSoundBankInfo::FWwiseLoadedSoundBankInfo(const FWwiseLoadedSoundBankInfo& InOriginal): + LocalizedSoundBankCookedData(InOriginal.LocalizedSoundBankCookedData), + LanguageRef(InOriginal.LanguageRef) +{ +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Stats/ResourceLoader.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Stats/ResourceLoader.cpp new file mode 100644 index 0000000..fe4af6f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/Stats/ResourceLoader.cpp @@ -0,0 +1,32 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Stats/ResourceLoader.h" + +DEFINE_STAT(STAT_WwiseResourceLoaderAuxBusses); +DEFINE_STAT(STAT_WwiseResourceLoaderEvents); +DEFINE_STAT(STAT_WwiseResourceLoaderExternalSources); +DEFINE_STAT(STAT_WwiseResourceLoaderGroupValues); +DEFINE_STAT(STAT_WwiseResourceLoaderInitBanks); +DEFINE_STAT(STAT_WwiseResourceLoaderMedia); +DEFINE_STAT(STAT_WwiseResourceLoaderShareSets); +DEFINE_STAT(STAT_WwiseResourceLoaderSoundBanks); +DEFINE_STAT(STAT_WwiseResourceLoaderSwitchContainerCombinations); + +DEFINE_STAT(STAT_WwiseResourceLoaderTiming); + +DEFINE_LOG_CATEGORY(LogWwiseResourceLoader); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/WwiseLanguageId.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/WwiseLanguageId.cpp new file mode 100644 index 0000000..c581865 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/WwiseLanguageId.cpp @@ -0,0 +1,20 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseLanguageId.h" + +const FWwiseLanguageId FWwiseLanguageId::Sfx(0, TEXT("SFX")); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/WwiseResourceLoader.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/WwiseResourceLoader.cpp new file mode 100644 index 0000000..fe3ccf9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/WwiseResourceLoader.cpp @@ -0,0 +1,1162 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseResourceLoader.h" + +#include "Wwise/CookedData/WwiseInitBankCookedData.h" +#include "Wwise/CookedData/WwiseLanguageCookedData.h" +#include "Wwise/CookedData/WwiseLocalizedAuxBusCookedData.h" +#include "Wwise/CookedData/WwiseLocalizedEventCookedData.h" +#include "Wwise/CookedData/WwiseLocalizedShareSetCookedData.h" +#include "Wwise/CookedData/WwiseLocalizedSoundBankCookedData.h" + +#include "Async/Async.h" + +#if WITH_EDITORONLY_DATA && PLATFORM_WINDOWS +static const auto DefaultPlatform = MakeShared(FGuid(0x6E0CB257, 0xC6C84C5C, 0x83662740, 0xDFC441EC), TEXT("Windows"), TEXT("Windows")); +#elif WITH_EDITORONLY_DATA && PLATFORM_MAC +static const auto DefaultPlatform = MakeShared(FGuid(0x02DC7702, 0x6E7B4AE4, 0xBAE464D2, 0xB1057150), TEXT("Mac"), TEXT("Mac")); +#elif WITH_EDITORONLY_DATA && PLATFORM_LINUX +static const auto DefaultPlatform = MakeShared(FGuid(0xBD0BDF13, 0x3125454F, 0x8BFD3195, 0x37169F81), TEXT("Linux"), TEXT("Linux")); +#else +static const auto DefaultPlatform = MakeShared(FGuid(0x6E0CB257, 0xC6C84C5C, 0x83662740, 0xDFC441EC), TEXT("Windows")); +#endif + +static const FWwiseLanguageCookedData DefaultLanguage(684519430, TEXT("English(US)"), EWwiseLanguageRequirement::IsDefault); + + +bool FWwiseResourceLoader::IsEnabled() const +{ + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + return false; + } + + return ResourceLoaderImpl->IsEnabled(); +} + +void FWwiseResourceLoader::Enable() +{ + UE_LOG(LogWwiseResourceLoader, Verbose, TEXT("Enabling ResourceLoaderImpl...")); + ResourceLoaderImpl->Enable(); +} + +void FWwiseResourceLoader::Disable() +{ + UE_LOG(LogWwiseResourceLoader, Verbose, TEXT("Disabling ResourceLoaderImpl...")); + ResourceLoaderImpl->Disable(); +} + +FWwiseResourceLoader::FWwiseResourceLoader() : + ResourceLoaderImpl(FWwiseResourceLoaderImpl::Instantiate()) +{ + ResourceLoaderImpl->CurrentLanguage = SystemLanguage(); + ResourceLoaderImpl->CurrentPlatform = SystemPlatform(); +} + +void FWwiseResourceLoader::SetLanguage(FWwiseLanguageCookedData InLanguage, EWwiseReloadLanguage InReloadLanguage) +{ + SCOPED_WWISERESOURCELOADER_EVENT_4(TEXT("SetLanguage")); + SetLanguageAsync(InLanguage, InReloadLanguage).Wait(); +} + +void FWwiseResourceLoader::SetPlatform(const FWwiseSharedPlatformId& InPlatform) +{ + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + return; + } + + ResourceLoaderImpl->SetPlatform(InPlatform); +} + + +FWwiseLanguageCookedData FWwiseResourceLoader::GetCurrentLanguage() const +{ + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + return {}; + } + + return ResourceLoaderImpl->CurrentLanguage; +} + +FWwiseSharedPlatformId FWwiseResourceLoader::GetCurrentPlatform() const +{ + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + return {}; + } + + return ResourceLoaderImpl->CurrentPlatform; +} + +FString FWwiseResourceLoader::GetUnrealPath(const FString& InPath) const +{ + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + return {}; + } + + return ResourceLoaderImpl->GetUnrealPath(InPath); +} + +FName FWwiseResourceLoader::GetUnrealExternalSourcePath() const +{ + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + return {}; + } + + return ResourceLoaderImpl->GetUnrealExternalSourcePath(); +} + +FString FWwiseResourceLoader::GetUnrealStagePath(const FString& InPath) const +{ + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + return {}; + } + + return ResourceLoaderImpl->GetUnrealStagePath(InPath); +} + +#if WITH_EDITORONLY_DATA +FString FWwiseResourceLoader::GetUnrealGeneratedSoundBanksPath(const FString& InPath) const +{ + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + return {}; + } + + return ResourceLoaderImpl->GetUnrealGeneratedSoundBanksPath(InPath); +} + +void FWwiseResourceLoader::SetUnrealGeneratedSoundBanksPath(const FDirectoryPath& DirectoryPath) +{ + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + return; + } + + ResourceLoaderImpl->GeneratedSoundBanksPath = DirectoryPath; +} + +const FDirectoryPath& FWwiseResourceLoader::GetUnrealGeneratedSoundBanksPath() +{ + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + static const FDirectoryPath Empty; + return Empty; + } + + return ResourceLoaderImpl->GeneratedSoundBanksPath; +} +#endif + +// +// User-facing loading and unloading operations +// + +FWwiseLoadedAuxBus FWwiseResourceLoader::LoadAuxBus(const FWwiseLocalizedAuxBusCookedData& InAuxBusCookedData, + const FWwiseLanguageCookedData* InLanguageOverride) +{ + SCOPED_WWISERESOURCELOADER_EVENT_4(TEXT("LoadAuxBus")); + return LoadAuxBusAsync(InAuxBusCookedData, InLanguageOverride).Get(); +} + +void FWwiseResourceLoader::UnloadAuxBus(FWwiseLoadedAuxBus&& InAuxBus) +{ + SCOPED_WWISERESOURCELOADER_EVENT_4(TEXT("UnloadAuxBus")); + FWwiseLoadedAuxBusPromise Promise; + Promise.EmplaceValue(MoveTemp(InAuxBus)); + UnloadAuxBusAsync(Promise.GetFuture()).Wait(); +} + +FWwiseLoadedEvent FWwiseResourceLoader::LoadEvent(const FWwiseLocalizedEventCookedData& InEventCookedData, + const FWwiseLanguageCookedData* InLanguageOverride) +{ + SCOPED_WWISERESOURCELOADER_EVENT_4(TEXT("LoadEvent")); + return LoadEventAsync(InEventCookedData, InLanguageOverride).Get(); +} + +void FWwiseResourceLoader::UnloadEvent(FWwiseLoadedEvent&& InEvent) +{ + SCOPED_WWISERESOURCELOADER_EVENT_4(TEXT("UnloadEvent")); + FWwiseLoadedEventPromise Promise; + Promise.EmplaceValue(MoveTemp(InEvent)); + UnloadEventAsync(Promise.GetFuture()).Wait(); +} + +FWwiseLoadedExternalSource FWwiseResourceLoader::LoadExternalSource( + const FWwiseExternalSourceCookedData& InExternalSourceCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_4(TEXT("LoadExternalSource")); + return LoadExternalSourceAsync(InExternalSourceCookedData).Get(); +} + +void FWwiseResourceLoader::UnloadExternalSource(FWwiseLoadedExternalSource&& InExternalSource) +{ + SCOPED_WWISERESOURCELOADER_EVENT_4(TEXT("UnloadExternalSource")); + FWwiseLoadedExternalSourcePromise Promise; + Promise.EmplaceValue(MoveTemp(InExternalSource)); + UnloadExternalSourceAsync(Promise.GetFuture()).Wait(); +} + +FWwiseLoadedGroupValue FWwiseResourceLoader::LoadGroupValue(const FWwiseGroupValueCookedData& InGroupValueCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_4(TEXT("LoadGroupValue")); + return LoadGroupValueAsync(InGroupValueCookedData).Get(); +} + +void FWwiseResourceLoader::UnloadGroupValue(FWwiseLoadedGroupValue&& InGroupValue) +{ + SCOPED_WWISERESOURCELOADER_EVENT_4(TEXT("UnloadGroupValue")); + FWwiseLoadedGroupValuePromise Promise; + Promise.EmplaceValue(MoveTemp(InGroupValue)); + UnloadGroupValueAsync(Promise.GetFuture()).Wait(); +} + +FWwiseLoadedInitBank FWwiseResourceLoader::LoadInitBank(const FWwiseInitBankCookedData& InInitBankCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_4(TEXT("LoadInitBank")); + return LoadInitBankAsync(InInitBankCookedData).Get(); +} + +void FWwiseResourceLoader::UnloadInitBank(FWwiseLoadedInitBank&& InInitBank) +{ + SCOPED_WWISERESOURCELOADER_EVENT_4(TEXT("UnloadInitBank")); + FWwiseLoadedInitBankPromise Promise; + Promise.EmplaceValue(MoveTemp(InInitBank)); + UnloadInitBankAsync(Promise.GetFuture()).Wait(); +} + +FWwiseLoadedMedia FWwiseResourceLoader::LoadMedia(const FWwiseMediaCookedData& InMediaCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_4(TEXT("LoadMedia")); + return LoadMediaAsync(InMediaCookedData).Get(); +} + +void FWwiseResourceLoader::UnloadMedia(FWwiseLoadedMedia&& InMedia) +{ + SCOPED_WWISERESOURCELOADER_EVENT_4(TEXT("UnloadMedia")); + FWwiseLoadedMediaPromise Promise; + Promise.EmplaceValue(MoveTemp(InMedia)); + UnloadMediaAsync(Promise.GetFuture()).Wait(); +} + +FWwiseLoadedShareSet FWwiseResourceLoader::LoadShareSet(const FWwiseLocalizedShareSetCookedData& InShareSetCookedData, + const FWwiseLanguageCookedData* InLanguageOverride) +{ + SCOPED_WWISERESOURCELOADER_EVENT_4(TEXT("LoadShareSet")); + return LoadShareSetAsync(InShareSetCookedData, InLanguageOverride).Get(); +} + +void FWwiseResourceLoader::UnloadShareSet(FWwiseLoadedShareSet&& InShareSet) +{ + SCOPED_WWISERESOURCELOADER_EVENT_4(TEXT("UnloadShareSet")); + FWwiseLoadedShareSetPromise Promise; + Promise.EmplaceValue(MoveTemp(InShareSet)); + UnloadShareSetAsync(Promise.GetFuture()).Wait(); +} + +FWwiseLoadedSoundBank FWwiseResourceLoader::LoadSoundBank( + const FWwiseLocalizedSoundBankCookedData& InSoundBankCookedData, const FWwiseLanguageCookedData* InLanguageOverride) +{ + SCOPED_WWISERESOURCELOADER_EVENT_4(TEXT("LoadSoundBank")); + return LoadSoundBankAsync(InSoundBankCookedData, InLanguageOverride).Get(); +} + +void FWwiseResourceLoader::UnloadSoundBank(FWwiseLoadedSoundBank&& InSoundBank) +{ + SCOPED_WWISERESOURCELOADER_EVENT_4(TEXT("UnloadSoundBank")); + FWwiseLoadedSoundBankPromise Promise; + Promise.EmplaceValue(MoveTemp(InSoundBank)); + UnloadSoundBankAsync(Promise.GetFuture()).Wait(); +} + +FWwiseResourceLoader::FWwiseSetLanguageFuture FWwiseResourceLoader::SetLanguageAsync( + FWwiseLanguageCookedData InLanguage, EWwiseReloadLanguage InReloadLanguage) +{ + FWwiseSetLanguagePromise Promise; + auto Future = Promise.GetFuture(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(); + return Future; + } + + ResourceLoaderImpl->SetLanguageAsync(MoveTemp(Promise), InLanguage, InReloadLanguage); + return Future; +} + +FWwiseLoadedAuxBusFuture FWwiseResourceLoader::LoadAuxBusAsync(const FWwiseLocalizedAuxBusCookedData& InAuxBusCookedData, + const FWwiseLanguageCookedData* InLanguageOverride) +{ + FWwiseLoadedAuxBusPromise Promise; + auto Future = Promise.GetFuture(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(nullptr); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(nullptr); + } + else + { + auto* AuxBusNode = ResourceLoaderImpl->CreateAuxBusNode(InAuxBusCookedData, InLanguageOverride); + if (UNLIKELY(!AuxBusNode)) + { + Promise.EmplaceValue(nullptr); + } + else + { + ResourceLoaderImpl->LoadAuxBusAsync(MoveTemp(Promise), MoveTemp(AuxBusNode)); + } + } + + return Future; +} + +FWwiseResourceUnloadFuture FWwiseResourceLoader::UnloadAuxBusAsync(FWwiseLoadedAuxBusFuture&& InAuxBus) +{ + FWwiseResourceUnloadPromise Promise; + auto Future = Promise.GetFuture(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(); + } + else if (UNLIKELY(InAuxBus.IsReady() && InAuxBus.Get() == nullptr)) + { + Promise.EmplaceValue(); + } + else if (LIKELY(InAuxBus.IsReady())) + { + auto* AuxBus = InAuxBus.Get(); + ResourceLoaderImpl->UnloadAuxBusAsync(MoveTemp(Promise), MoveTemp(AuxBus)); + } + else + { + AsyncTask(ResourceLoaderImpl->TaskThread, [this, InAuxBus = MoveTemp(InAuxBus), Promise = MoveTemp(Promise)]() mutable + { + { + int WaitCount = 0; + while (!InAuxBus.WaitFor(FTimespan::FromSeconds(1.0))) + { + if (IsEngineExitRequested()) + { + UE_LOG(LogWwiseResourceLoader, Verbose, TEXT("Giving up on waiting for Aux Bus load since we are exiting. Gave up on count [%d]"), WaitCount); + Promise.EmplaceValue(); + return; + } + else + { + UE_CLOG(WaitCount != 10, LogWwiseResourceLoader, Verbose, TEXT("Waiting for an Aux Bus to be fully loaded so we can unload it [%d]"), WaitCount); + UE_CLOG(WaitCount == 10, LogWwiseResourceLoader, Warning, TEXT("Waited 10 seconds for an Aux Bus to be loaded so we can unload it.")); + ++WaitCount; + } + } + } + auto* AuxBus = InAuxBus.Get(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(); + } + else if (UNLIKELY(!AuxBus)) + { + Promise.EmplaceValue(); + } + else + { + ResourceLoaderImpl->UnloadAuxBusAsync(MoveTemp(Promise), MoveTemp(AuxBus)); + } + }); + } + + return Future; +} + +FWwiseLoadedEventFuture FWwiseResourceLoader::LoadEventAsync(const FWwiseLocalizedEventCookedData& InEventCookedData, + const FWwiseLanguageCookedData* InLanguageOverride) +{ + FWwiseLoadedEventPromise Promise; + auto Future = Promise.GetFuture(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(nullptr); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(nullptr); + } + else + { + auto* EventNode = ResourceLoaderImpl->CreateEventNode(InEventCookedData, InLanguageOverride); + if (UNLIKELY(!EventNode)) + { + Promise.EmplaceValue(nullptr); + } + else + { + ResourceLoaderImpl->LoadEventAsync(MoveTemp(Promise), MoveTemp(EventNode)); + } + } + + return Future; +} + +FWwiseResourceUnloadFuture FWwiseResourceLoader::UnloadEventAsync(FWwiseLoadedEventFuture&& InEvent) +{ + FWwiseResourceUnloadPromise Promise; + auto Future = Promise.GetFuture(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(); + } + else if (UNLIKELY(InEvent.IsReady() && InEvent.Get() == nullptr)) + { + Promise.EmplaceValue(); + } + else if (LIKELY(InEvent.IsReady())) + { + auto* Event = InEvent.Get(); + ResourceLoaderImpl->UnloadEventAsync(MoveTemp(Promise), MoveTemp(Event)); + } + else + { + AsyncTask(ResourceLoaderImpl->TaskThread, [this, InEvent = MoveTemp(InEvent), Promise = MoveTemp(Promise)]() mutable + { + { + int WaitCount = 0; + while (!InEvent.WaitFor(FTimespan::FromSeconds(1.0))) + { + if (IsEngineExitRequested()) + { + UE_LOG(LogWwiseResourceLoader, Verbose, TEXT("Giving up on waiting for Event load since we are exiting. Gave up on count [%d]"), WaitCount); + Promise.EmplaceValue(); + return; + } + else + { + UE_CLOG(WaitCount != 10, LogWwiseResourceLoader, Verbose, TEXT("Waiting for a Event to be fully loaded so we can unload it [%d]"), WaitCount); + UE_CLOG(WaitCount == 10, LogWwiseResourceLoader, Warning, TEXT("Waited 10 seconds for a Event to be loaded so we can unload it.")); + ++WaitCount; + } + } + } + auto* Event = InEvent.Get(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(); + } + else if (UNLIKELY(!Event)) + { + Promise.EmplaceValue(); + } + else + { + ResourceLoaderImpl->UnloadEventAsync(MoveTemp(Promise), MoveTemp(Event)); + } + }); + } + + return Future; +} + +FWwiseLoadedExternalSourceFuture FWwiseResourceLoader::LoadExternalSourceAsync( + const FWwiseExternalSourceCookedData& InExternalSourceCookedData) +{ + FWwiseLoadedExternalSourcePromise Promise; + auto Future = Promise.GetFuture(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(nullptr); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(nullptr); + } + else + { + auto* ExternalSourceNode = ResourceLoaderImpl->CreateExternalSourceNode(InExternalSourceCookedData); + if (UNLIKELY(!ExternalSourceNode)) + { + Promise.EmplaceValue(nullptr); + } + else + { + ResourceLoaderImpl->LoadExternalSourceAsync(MoveTemp(Promise), MoveTemp(ExternalSourceNode)); + } + } + + return Future; +} + +FWwiseResourceUnloadFuture FWwiseResourceLoader::UnloadExternalSourceAsync(FWwiseLoadedExternalSourceFuture&& InExternalSource) +{ + FWwiseResourceUnloadPromise Promise; + auto Future = Promise.GetFuture(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(); + } + else if (UNLIKELY(InExternalSource.IsReady() && InExternalSource.Get() == nullptr)) + { + Promise.EmplaceValue(); + } + else if (LIKELY(InExternalSource.IsReady())) + { + auto* ExternalSource = InExternalSource.Get(); + ResourceLoaderImpl->UnloadExternalSourceAsync(MoveTemp(Promise), MoveTemp(ExternalSource)); + } + else + { + AsyncTask(ResourceLoaderImpl->TaskThread, [this, InExternalSource = MoveTemp(InExternalSource), Promise = MoveTemp(Promise)]() mutable + { + { + int WaitCount = 0; + while (!InExternalSource.WaitFor(FTimespan::FromSeconds(1.0))) + { + if (IsEngineExitRequested()) + { + UE_LOG(LogWwiseResourceLoader, Verbose, TEXT("Giving up on waiting for External Source load since we are exiting. Gave up on count [%d]"), WaitCount); + Promise.EmplaceValue(); + return; + } + else + { + UE_CLOG(WaitCount != 10, LogWwiseResourceLoader, Verbose, TEXT("Waiting for a External Source to be fully loaded so we can unload it [%d]"), WaitCount); + UE_CLOG(WaitCount == 10, LogWwiseResourceLoader, Warning, TEXT("Waited 10 seconds for a External Source to be loaded so we can unload it.")); + ++WaitCount; + } + } + } + auto* ExternalSource = InExternalSource.Get(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(); + } + else if (UNLIKELY(!ExternalSource)) + { + Promise.EmplaceValue(); + } + else + { + ResourceLoaderImpl->UnloadExternalSourceAsync(MoveTemp(Promise), MoveTemp(ExternalSource)); + } + }); + } + + return Future; +} + +FWwiseLoadedGroupValueFuture FWwiseResourceLoader::LoadGroupValueAsync(const FWwiseGroupValueCookedData& InGroupValueCookedData) +{ + FWwiseLoadedGroupValuePromise Promise; + auto Future = Promise.GetFuture(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(nullptr); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(nullptr); + } + else + { + auto* GroupValueNode = ResourceLoaderImpl->CreateGroupValueNode(InGroupValueCookedData); + if (UNLIKELY(!GroupValueNode)) + { + Promise.EmplaceValue(nullptr); + } + else + { + ResourceLoaderImpl->LoadGroupValueAsync(MoveTemp(Promise), MoveTemp(GroupValueNode)); + } + } + + return Future; +} + +FWwiseResourceUnloadFuture FWwiseResourceLoader::UnloadGroupValueAsync(FWwiseLoadedGroupValueFuture&& InGroupValue) +{ + FWwiseResourceUnloadPromise Promise; + auto Future = Promise.GetFuture(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(); + } + else if (UNLIKELY(InGroupValue.IsReady() && InGroupValue.Get() == nullptr)) + { + Promise.EmplaceValue(); + } + else if (LIKELY(InGroupValue.IsReady())) + { + auto* GroupValue = InGroupValue.Get(); + ResourceLoaderImpl->UnloadGroupValueAsync(MoveTemp(Promise), MoveTemp(GroupValue)); + } + else + { + AsyncTask(ResourceLoaderImpl->TaskThread, [this, InGroupValue = MoveTemp(InGroupValue), Promise = MoveTemp(Promise)]() mutable + { + { + int WaitCount = 0; + while (!InGroupValue.WaitFor(FTimespan::FromSeconds(1.0))) + { + if (IsEngineExitRequested()) + { + UE_LOG(LogWwiseResourceLoader, Verbose, TEXT("Giving up on waiting for Group Value load since we are exiting. Gave up on count [%d]"), WaitCount); + Promise.EmplaceValue(); + return; + } + else + { + UE_CLOG(WaitCount != 10, LogWwiseResourceLoader, Verbose, TEXT("Waiting for a Group Value to be fully loaded so we can unload it [%d]"), WaitCount); + UE_CLOG(WaitCount == 10, LogWwiseResourceLoader, Warning, TEXT("Waited 10 seconds for a Group Value to be loaded so we can unload it.")); + ++WaitCount; + } + } + } + auto* GroupValue = InGroupValue.Get(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(); + } + else if (UNLIKELY(!GroupValue)) + { + Promise.EmplaceValue(); + } + else + { + ResourceLoaderImpl->UnloadGroupValueAsync(MoveTemp(Promise), MoveTemp(GroupValue)); + } + }); + } + + return Future; +} + +FWwiseLoadedInitBankFuture FWwiseResourceLoader::LoadInitBankAsync(const FWwiseInitBankCookedData& InInitBankCookedData) +{ + FWwiseLoadedInitBankPromise Promise; + auto Future = Promise.GetFuture(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(nullptr); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(nullptr); + } + else + { + auto* InitBankNode = ResourceLoaderImpl->CreateInitBankNode(InInitBankCookedData); + if (UNLIKELY(!InitBankNode)) + { + Promise.EmplaceValue(nullptr); + } + else + { + ResourceLoaderImpl->LoadInitBankAsync(MoveTemp(Promise), MoveTemp(InitBankNode)); + } + } + + return Future; +} + +FWwiseResourceUnloadFuture FWwiseResourceLoader::UnloadInitBankAsync(FWwiseLoadedInitBankFuture&& InInitBank) +{ + FWwiseResourceUnloadPromise Promise; + auto Future = Promise.GetFuture(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(); + } + else if (UNLIKELY(InInitBank.IsReady() && InInitBank.Get() == nullptr)) + { + Promise.EmplaceValue(); + } + else if (LIKELY(InInitBank.IsReady())) + { + auto* InitBank = InInitBank.Get(); + ResourceLoaderImpl->UnloadInitBankAsync(MoveTemp(Promise), MoveTemp(InitBank)); + } + else + { + AsyncTask(ResourceLoaderImpl->TaskThread, [this, InInitBank = MoveTemp(InInitBank), Promise = MoveTemp(Promise)]() mutable + { + { + int WaitCount = 0; + while (!InInitBank.WaitFor(FTimespan::FromSeconds(1.0))) + { + if (IsEngineExitRequested()) + { + UE_LOG(LogWwiseResourceLoader, Verbose, TEXT("Giving up on waiting for Init Bank load since we are exiting. Gave up on count [%d]"), WaitCount); + Promise.EmplaceValue(); + return; + } + else + { + UE_CLOG(WaitCount != 10, LogWwiseResourceLoader, Verbose, TEXT("Waiting for a Init Bank to be fully loaded so we can unload it [%d]"), WaitCount); + UE_CLOG(WaitCount == 10, LogWwiseResourceLoader, Warning, TEXT("Waited 10 seconds for a Init Bank to be loaded so we can unload it.")); + ++WaitCount; + } + } + } + auto* InitBank = InInitBank.Get(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(); + } + else if (UNLIKELY(!InitBank)) + { + Promise.EmplaceValue(); + } + else + { + ResourceLoaderImpl->UnloadInitBankAsync(MoveTemp(Promise), MoveTemp(InitBank)); + } + }); + } + + return Future; +} + +FWwiseLoadedMediaFuture FWwiseResourceLoader::LoadMediaAsync(const FWwiseMediaCookedData& InMediaCookedData) +{ + FWwiseLoadedMediaPromise Promise; + auto Future = Promise.GetFuture(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(nullptr); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(nullptr); + } + else + { + auto* MediaNode = ResourceLoaderImpl->CreateMediaNode(InMediaCookedData); + if (UNLIKELY(!MediaNode)) + { + Promise.EmplaceValue(nullptr); + } + else + { + ResourceLoaderImpl->LoadMediaAsync(MoveTemp(Promise), MoveTemp(MediaNode)); + } + } + + return Future; +} + +FWwiseResourceUnloadFuture FWwiseResourceLoader::UnloadMediaAsync(FWwiseLoadedMediaFuture&& InMedia) +{ + FWwiseResourceUnloadPromise Promise; + auto Future = Promise.GetFuture(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(); + } + else if (UNLIKELY(InMedia.IsReady() && InMedia.Get() == nullptr)) + { + Promise.EmplaceValue(); + } + else if (LIKELY(InMedia.IsReady())) + { + auto* Media = InMedia.Get(); + ResourceLoaderImpl->UnloadMediaAsync(MoveTemp(Promise), MoveTemp(Media)); + } + else + { + AsyncTask(ResourceLoaderImpl->TaskThread, [this, InMedia = MoveTemp(InMedia), Promise = MoveTemp(Promise)]() mutable + { + { + int WaitCount = 0; + while (!InMedia.WaitFor(FTimespan::FromSeconds(1.0))) + { + if (IsEngineExitRequested()) + { + UE_LOG(LogWwiseResourceLoader, Verbose, TEXT("Giving up on waiting for Media load since we are exiting. Gave up on count [%d]"), WaitCount); + Promise.EmplaceValue(); + return; + } + else + { + UE_CLOG(WaitCount != 10, LogWwiseResourceLoader, Verbose, TEXT("Waiting for a Media to be fully loaded so we can unload it [%d]"), WaitCount); + UE_CLOG(WaitCount == 10, LogWwiseResourceLoader, Warning, TEXT("Waited 10 seconds for a Media to be loaded so we can unload it.")); + ++WaitCount; + } + } + } + auto* Media = InMedia.Get(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(); + } + else if (UNLIKELY(!Media)) + { + Promise.EmplaceValue(); + } + else + { + ResourceLoaderImpl->UnloadMediaAsync(MoveTemp(Promise), MoveTemp(Media)); + } + }); + } + + return Future; +} + +FWwiseLoadedShareSetFuture FWwiseResourceLoader::LoadShareSetAsync( + const FWwiseLocalizedShareSetCookedData& InShareSetCookedData, const FWwiseLanguageCookedData* InLanguageOverride) +{ + FWwiseLoadedShareSetPromise Promise; + auto Future = Promise.GetFuture(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(nullptr); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(nullptr); + } + else + { + auto* ShareSetNode = ResourceLoaderImpl->CreateShareSetNode(InShareSetCookedData, InLanguageOverride); + if (UNLIKELY(!ShareSetNode)) + { + Promise.EmplaceValue(nullptr); + } + else + { + ResourceLoaderImpl->LoadShareSetAsync(MoveTemp(Promise), MoveTemp(ShareSetNode)); + } + } + + return Future; +} + +FWwiseResourceUnloadFuture FWwiseResourceLoader::UnloadShareSetAsync(FWwiseLoadedShareSetFuture&& InShareSet) +{ + FWwiseResourceUnloadPromise Promise; + auto Future = Promise.GetFuture(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(); + } + else if (UNLIKELY(InShareSet.IsReady() && InShareSet.Get() == nullptr)) + { + Promise.EmplaceValue(); + } + else if (LIKELY(InShareSet.IsReady())) + { + auto* ShareSet = InShareSet.Get(); + ResourceLoaderImpl->UnloadShareSetAsync(MoveTemp(Promise), MoveTemp(ShareSet)); + } + else + { + AsyncTask(ResourceLoaderImpl->TaskThread, [this, InShareSet = MoveTemp(InShareSet), Promise = MoveTemp(Promise)]() mutable + { + { + int WaitCount = 0; + while (!InShareSet.WaitFor(FTimespan::FromSeconds(1.0))) + { + if (IsEngineExitRequested()) + { + UE_LOG(LogWwiseResourceLoader, Verbose, TEXT("Giving up on waiting for ShareSet load since we are exiting. Gave up on count [%d]"), WaitCount); + Promise.EmplaceValue(); + return; + } + else + { + UE_CLOG(WaitCount != 10, LogWwiseResourceLoader, Verbose, TEXT("Waiting for a ShareSet to be fully loaded so we can unload it [%d]"), WaitCount); + UE_CLOG(WaitCount == 10, LogWwiseResourceLoader, Warning, TEXT("Waited 10 seconds for a ShareSet to be loaded so we can unload it.")); + ++WaitCount; + } + } + } + auto* ShareSet = InShareSet.Get(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(); + } + else if (UNLIKELY(!ShareSet)) + { + Promise.EmplaceValue(); + } + else + { + ResourceLoaderImpl->UnloadShareSetAsync(MoveTemp(Promise), MoveTemp(ShareSet)); + } + }); + } + + return Future; +} + +FWwiseLoadedSoundBankFuture FWwiseResourceLoader::LoadSoundBankAsync( + const FWwiseLocalizedSoundBankCookedData& InSoundBankCookedData, const FWwiseLanguageCookedData* InLanguageOverride) +{ + FWwiseLoadedSoundBankPromise Promise; + auto Future = Promise.GetFuture(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(nullptr); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(nullptr); + } + else + { + auto* SoundBankNode = ResourceLoaderImpl->CreateSoundBankNode(InSoundBankCookedData, InLanguageOverride); + if (UNLIKELY(!SoundBankNode)) + { + Promise.EmplaceValue(nullptr); + } + else + { + ResourceLoaderImpl->LoadSoundBankAsync(MoveTemp(Promise), MoveTemp(SoundBankNode)); + } + } + + return Future; +} + +FWwiseResourceUnloadFuture FWwiseResourceLoader::UnloadSoundBankAsync(FWwiseLoadedSoundBankFuture&& InSoundBank) +{ + FWwiseResourceUnloadPromise Promise; + auto Future = Promise.GetFuture(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(); + } + else if (UNLIKELY(InSoundBank.IsReady() && InSoundBank.Get() == nullptr)) + { + Promise.EmplaceValue(); + } + else if (LIKELY(InSoundBank.IsReady())) + { + auto* SoundBank = InSoundBank.Get(); + ResourceLoaderImpl->UnloadSoundBankAsync(MoveTemp(Promise), MoveTemp(SoundBank)); + } + else + { + AsyncTask(ResourceLoaderImpl->TaskThread, [this, InSoundBank = MoveTemp(InSoundBank), Promise = MoveTemp(Promise)]() mutable + { + { + int WaitCount = 0; + while (!InSoundBank.WaitFor(FTimespan::FromSeconds(1.0))) + { + if (IsEngineExitRequested()) + { + UE_LOG(LogWwiseResourceLoader, Verbose, TEXT("Giving up on waiting for SoundBank load since we are exiting. Gave up on count [%d]"), WaitCount); + Promise.EmplaceValue(); + return; + } + else + { + UE_CLOG(WaitCount != 10, LogWwiseResourceLoader, Verbose, TEXT("Waiting for a SoundBank to be fully loaded so we can unload it [%d]"), WaitCount); + UE_CLOG(WaitCount == 10, LogWwiseResourceLoader, Warning, TEXT("Waited 10 seconds for a SoundBank to be loaded so we can unload it.")); + ++WaitCount; + } + } + } + auto* SoundBank = InSoundBank.Get(); + + if (UNLIKELY(!ResourceLoaderImpl)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("No ResourceLoaderImpl")); + Promise.EmplaceValue(); + } + else if (!IsEnabled()) + { + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("ResourceLoaderImpl is disabled")); + Promise.EmplaceValue(); + } + else if (UNLIKELY(!SoundBank)) + { + Promise.EmplaceValue(); + } + else + { + ResourceLoaderImpl->UnloadSoundBankAsync(MoveTemp(Promise), MoveTemp(SoundBank)); + } + }); + } + + return Future; +} + +// +// Basic info +// + +FWwiseSharedPlatformId FWwiseResourceLoader::SystemPlatform() const +{ + auto Result = FWwiseSharedPlatformId(); + Result.Platform = DefaultPlatform; + return Result; +} + +FWwiseLanguageCookedData FWwiseResourceLoader::SystemLanguage() const +{ + return DefaultLanguage; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/WwiseResourceLoaderImpl.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/WwiseResourceLoaderImpl.cpp new file mode 100644 index 0000000..5abf7b1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/WwiseResourceLoaderImpl.cpp @@ -0,0 +1,2511 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseResourceLoaderImpl.h" + +#include "Wwise/CookedData/WwiseInitBankCookedData.h" +#include "Wwise/CookedData/WwiseLocalizedAuxBusCookedData.h" +#include "Wwise/CookedData/WwiseLocalizedSoundBankCookedData.h" +#include "Wwise/CookedData/WwiseLocalizedEventCookedData.h" +#include "Wwise/CookedData/WwiseLocalizedShareSetCookedData.h" + +#include "Wwise/API/WwiseSoundEngineAPI.h" +#include "Wwise/Stats/AsyncStats.h" + +#include "Wwise/WwiseExternalSourceManager.h" +#include "Wwise/WwiseGlobalCallbacks.h" +#include "Wwise/WwiseMediaManager.h" +#include "Wwise/WwiseResourceLoader.h" +#include "Wwise/WwiseSoundBankManager.h" + +#include "Async/Async.h" + +#include + +FWwiseSwitchContainerLeafGroupValueUsageCount::FLoadedData::FLoadedData() +{ +} + +bool FWwiseSwitchContainerLeafGroupValueUsageCount::FLoadedData::IsLoaded() const +{ + return LoadedSoundBanks.Num() > 0 || LoadedExternalSources.Num() > 0 || LoadedMedia.Num() > 0; +} + +FWwiseSwitchContainerLeafGroupValueUsageCount::FWwiseSwitchContainerLeafGroupValueUsageCount( + const FWwiseSwitchContainerLeafCookedData& InKey): + Key(InKey) +{} + +bool FWwiseSwitchContainerLeafGroupValueUsageCount::HaveAllKeys() const +{ + if (UNLIKELY(Key.GroupValueSet.Num() < LoadedGroupValues.Num())) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("Have more keys loaded (%d) than existing in key (%d) @ %p for key %s"), + LoadedGroupValues.Num(), Key.GroupValueSet.Num(), &LoadedData, *Key.GetDebugString()); + return true; + } + + return Key.GroupValueSet.Num() == LoadedGroupValues.Num(); +} + +FWwiseResourceLoaderImpl::FWwiseResourceLoaderImpl() +{ +} + +FName FWwiseResourceLoaderImpl::GetUnrealExternalSourcePath() const +{ +#if WITH_EDITORONLY_DATA + return FName(GeneratedSoundBanksPath.Path / CurrentPlatform.Platform->PathRelativeToGeneratedSoundBanks.ToString() / CurrentPlatform.Platform->ExternalSourceRootPath.ToString()); +#else + auto* ExternalSourceManager = IWwiseExternalSourceManager::Get(); + if (UNLIKELY(!ExternalSourceManager)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("GetUnrealExternalSourcePath: Failed to retrieve External Source Manager, returning empty string.")); + return {}; + } + return FName(FPaths::ProjectContentDir() / ExternalSourceManager->GetStagingDirectory()); +#endif +} + +FString FWwiseResourceLoaderImpl::GetUnrealPath() const +{ +#if WITH_EDITOR + return GeneratedSoundBanksPath.Path / CurrentPlatform.Platform->PathRelativeToGeneratedSoundBanks.ToString(); +#elif WITH_EDITORONLY_DATA + UE_LOG(LogWwiseResourceLoader, Error, TEXT("GetUnrealPath should not be used in WITH_EDITORONLY_DATA (Getting path for %s)"), *InPath); + return GeneratedSoundBanksPath.Path / CurrentPlatform.Platform->PathRelativeToGeneratedSoundBanks; +#else + return StagePath; +#endif +} + +FString FWwiseResourceLoaderImpl::GetUnrealPath(const FString& InPath) const +{ +#if WITH_EDITOR + return GetUnrealGeneratedSoundBanksPath(InPath); +#elif WITH_EDITORONLY_DATA + UE_LOG(LogWwiseResourceLoader, Error, TEXT("GetUnrealPath should not be used in WITH_EDITORONLY_DATA (Getting path for %s)"), *InPath); + return GetUnrealGeneratedSoundBanksPath(InPath); +#else + return GetUnrealStagePath(InPath); +#endif +} + +FString FWwiseResourceLoaderImpl::GetUnrealStagePath(const FString& InPath) const +{ + if (UNLIKELY(StagePath.IsEmpty())) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("StagePath not set up (GetUnrealStagePath for %s)"), *InPath); + } + return StagePath / InPath; +} + +#if WITH_EDITORONLY_DATA +FString FWwiseResourceLoaderImpl::GetUnrealGeneratedSoundBanksPath(const FString& InPath) const +{ + if (UNLIKELY(GeneratedSoundBanksPath.Path.IsEmpty())) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("GeneratedSoundBanksPath not set up (GetUnrealGeneratedSoundBanksPath for %s)"), *InPath); + } + + return GeneratedSoundBanksPath.Path / CurrentPlatform.Platform->PathRelativeToGeneratedSoundBanks.ToString() / InPath; +} +#endif + + +EWwiseResourceLoaderState FWwiseResourceLoaderImpl::GetResourceLoaderState() +{ + return WwiseResourceLoaderState; +} + +void FWwiseResourceLoaderImpl::SetResourceLoaderState(EWwiseResourceLoaderState State) +{ + WwiseResourceLoaderState = State; +} + +bool FWwiseResourceLoaderImpl::IsEnabled() +{ + return WwiseResourceLoaderState == EWwiseResourceLoaderState::Enabled; +} + +void FWwiseResourceLoaderImpl::Disable() +{ + SetResourceLoaderState(EWwiseResourceLoaderState::AlwaysDisabled); +} + +void FWwiseResourceLoaderImpl::Enable() +{ + SetResourceLoaderState(EWwiseResourceLoaderState::Enabled); +} + +void FWwiseResourceLoaderImpl::SetLanguageAsync(FWwiseSetLanguagePromise&& Promise, const FWwiseLanguageCookedData& InLanguage, EWwiseReloadLanguage InReloadLanguage) +{ + SCOPED_WWISERESOURCELOADER_EVENT(TEXT("SetLanguageAsync")); + SCOPE_CYCLE_COUNTER(STAT_WwiseResourceLoaderTiming); + + auto OldLanguage = CurrentLanguage; + auto NewLanguage = InLanguage; + + if (OldLanguage == NewLanguage) + { + return Promise.EmplaceValue(); + } + + UE_CLOG(!OldLanguage.GetLanguageName().IsValid(), LogWwiseResourceLoader, Log, TEXT("[SetLanguage] To %s"), *NewLanguage.GetLanguageName().ToString()); + UE_CLOG(OldLanguage.GetLanguageName().IsValid(), LogWwiseResourceLoader, Log, TEXT("[SetLanguage] from %s to %s"), *OldLanguage.GetLanguageName().ToString(), *NewLanguage.GetLanguageName().ToString()); + + FCompletionFuture Future = MakeFulfilledWwisePromise().GetFuture(); + + if (InReloadLanguage == EWwiseReloadLanguage::Safe) + { + UE_LOG(LogWwiseResourceLoader, Verbose, TEXT("SetLanguage: Stopping all sounds")); + auto* SoundEngine = IWwiseSoundEngineAPI::Get(); + if (UNLIKELY(!SoundEngine)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("SetLanguage: SoundEngine not available to stop all sounds")); + } + else + { + SoundEngine->StopAll(); + + // Wait two audio processing passes to make sure our StopAll was processed. + FCompletionPromise EndPromise; + auto EndFuture = EndPromise.GetFuture(); + Future.Next([Promise = MoveTemp(EndPromise)](int) mutable + { + if(auto* WwiseGlobalCallbacks = FWwiseGlobalCallbacks::Get()) + { + FWwiseGlobalCallbacks::FCompletionPromise WaitPromise; + auto WaitFuture = WaitPromise.GetFuture(); + WwiseGlobalCallbacks->EndCompletion(MoveTemp(WaitPromise), 2); + WaitFuture.Next([Promise = MoveTemp(Promise)](int) mutable + { + Promise.EmplaceValue(); + }); + } + else + { + Promise.EmplaceValue(); + } + }); + Future = MoveTemp(EndFuture); + } + } + + CurrentLanguage = NewLanguage; + + if (InReloadLanguage == EWwiseReloadLanguage::Manual) + { + Future.Next([Promise = MoveTemp(Promise)](int) mutable + { + UE_LOG(LogWwiseResourceLoader, Verbose, TEXT("SetLanguage: Done (Manual)")); + Promise.EmplaceValue(); + }); + return; + } + + Future.Next([this, OldLanguage = MoveTemp(OldLanguage), NewLanguage = MoveTemp(NewLanguage), Promise = MoveTemp(Promise)](int) mutable + { + LoadedListExecutionQueue.Async([this, OldLanguage = MoveTemp(OldLanguage), NewLanguage = MoveTemp(NewLanguage), Promise = MoveTemp(Promise)]() mutable + { + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("SetLanguageAsync Unloading")); + // Note: these are written as "Log" since it's more dangerous to do loading and unloading operations while the + // asynchronous SetLanguage is executed. This allows for better debugging. + + UE_LOG(LogWwiseResourceLoader, Log, TEXT("SetLanguage: Switching languages. Unloading old language %s."), + *OldLanguage.GetLanguageName().ToString()); + + TArray AffectedSoundBanks; + TArray AffectedAuxBusses; + TArray AffectedShareSets; + TArray AffectedEvents; + + // Unload all objects with a language equal to the old language + FCompletionFutureArray UnloadFutureArray; + + for (auto& LoadedSoundBank : LoadedSoundBankList) + { + if (LoadedSoundBank.LanguageRef != OldLanguage) + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("SetLanguage: Skipping SoundBank %s with language %s"), + *LoadedSoundBank.LocalizedSoundBankCookedData.DebugName.ToString(), *LoadedSoundBank.LanguageRef.GetLanguageName().ToString()); + continue; + } + + auto* SoundBank = LoadedSoundBank.LocalizedSoundBankCookedData.SoundBankLanguageMap.Find(LoadedSoundBank.LanguageRef); + if (LIKELY(SoundBank)) + { + AffectedSoundBanks.Add(&LoadedSoundBank); + + FCompletionPromise UnloadPromise; + UnloadFutureArray.Add(UnloadPromise.GetFuture()); + UnloadSoundBankResources(MoveTemp(UnloadPromise), LoadedSoundBank.LoadedData, *SoundBank); + } + else + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("SetLanguage: Could not find SoundBank %s with language %s"), + *LoadedSoundBank.LocalizedSoundBankCookedData.DebugName.ToString(), *LoadedSoundBank.LanguageRef.GetLanguageName().ToString()); + } + } + + for (auto& LoadedAuxBus : LoadedAuxBusList) + { + if (LoadedAuxBus.LanguageRef != OldLanguage) + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("SetLanguage: Skipping AuxBus %s with language %s"), + *LoadedAuxBus.LocalizedAuxBusCookedData.DebugName.ToString(), *LoadedAuxBus.LanguageRef.GetLanguageName().ToString()); + continue; + } + + auto* AuxBus = LoadedAuxBus.LocalizedAuxBusCookedData.AuxBusLanguageMap.Find(LoadedAuxBus.LanguageRef); + if (LIKELY(AuxBus)) + { + AffectedAuxBusses.Add(&LoadedAuxBus); + + FCompletionPromise UnloadPromise; + UnloadFutureArray.Add(UnloadPromise.GetFuture()); + UnloadAuxBusResources(MoveTemp(UnloadPromise), LoadedAuxBus.LoadedData, *AuxBus); + } + else + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("SetLanguage: Could not find AuxBus %s with language %s"), + *LoadedAuxBus.LocalizedAuxBusCookedData.DebugName.ToString(), *LoadedAuxBus.LanguageRef.GetLanguageName().ToString()); + } + } + + for (auto& LoadedShareSet : LoadedShareSetList) + { + if (LoadedShareSet.LanguageRef != OldLanguage) + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("SetLanguage: Skipping ShareSet %s with language %s"), + *LoadedShareSet.LocalizedShareSetCookedData.DebugName.ToString(), *LoadedShareSet.LanguageRef.GetLanguageName().ToString()); + continue; + } + + auto* ShareSet = LoadedShareSet.LocalizedShareSetCookedData.ShareSetLanguageMap.Find(LoadedShareSet.LanguageRef); + if (LIKELY(ShareSet)) + { + AffectedShareSets.Add(&LoadedShareSet); + + FCompletionPromise UnloadPromise; + UnloadFutureArray.Add(UnloadPromise.GetFuture()); + UnloadShareSetResources(MoveTemp(UnloadPromise), LoadedShareSet.LoadedData, *ShareSet); + } + else + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("SetLanguage: Could not find ShareSet %s with language %s"), + *LoadedShareSet.LocalizedShareSetCookedData.DebugName.ToString(), *LoadedShareSet.LanguageRef.GetLanguageName().ToString()); + } + } + for (auto& LoadedEvent : LoadedEventList) + { + if (LoadedEvent.LanguageRef != OldLanguage) + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("SetLanguage: Skipping Event %s with language %s"), + *LoadedEvent.LocalizedEventCookedData.DebugName.ToString(), *LoadedEvent.LanguageRef.GetLanguageName().ToString()); + continue; + } + + auto* Event = LoadedEvent.LocalizedEventCookedData.EventLanguageMap.Find(LoadedEvent.LanguageRef); + if (LIKELY(Event)) + { + AffectedEvents.Add(&LoadedEvent); + + FCompletionPromise UnloadPromise; + UnloadFutureArray.Add(UnloadPromise.GetFuture()); + UnloadEventResources(MoveTemp(UnloadPromise), LoadedEvent.LoadedData, *Event); + } + else + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("SetLanguage: Could not find Event %s with language %s"), + *LoadedEvent.LocalizedEventCookedData.DebugName.ToString(), *LoadedEvent.LanguageRef.GetLanguageName().ToString()); + } + } + + WaitForFutures(MoveTemp(UnloadFutureArray), [this, + OldLanguage = MoveTemp(OldLanguage), + NewLanguage = MoveTemp(NewLanguage), + Promise = MoveTemp(Promise), + AffectedAuxBusses = MoveTemp(AffectedAuxBusses), + AffectedEvents = MoveTemp(AffectedEvents), + AffectedShareSets = MoveTemp(AffectedShareSets), + AffectedSoundBanks = MoveTemp(AffectedSoundBanks)]() mutable + { + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("SetLanguageAsync Loading")); + UE_LOG(LogWwiseResourceLoader, Log, TEXT("SetLanguage: Loading new language %s."), + *NewLanguage.GetLanguageName().ToString()); + + FCompletionFutureArray LoadFutureArray; + + // Note: The results are ignored. Reloading Wwise objects can individually fail for any given reasons, and it's Out Of Scope + // for the end product to know SetLanguage wasn't totally successful, since there's no real recourse at that point. + + for (auto* LoadedSoundBank : AffectedSoundBanks) + { + LoadedSoundBank->LanguageRef = NewLanguage; + auto* SoundBank = LoadedSoundBank->LocalizedSoundBankCookedData.SoundBankLanguageMap.Find(LoadedSoundBank->LanguageRef); + if (LIKELY(SoundBank)) + { + FCompletionPromise LoadPromise; + LoadFutureArray.Add(LoadPromise.GetFuture()); + FWwiseResourceLoadPromise ResourceLoadPromise; + ResourceLoadPromise.GetFuture().Next([LoadPromise = MoveTemp(LoadPromise)](int) mutable + { + LoadPromise.EmplaceValue(); + }); + LoadSoundBankResources(MoveTemp(ResourceLoadPromise), LoadedSoundBank->LoadedData, *SoundBank); + } + else + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("SetLanguage: Could not find SoundBank %s with language %s"), + *LoadedSoundBank->LocalizedSoundBankCookedData.DebugName.ToString(), *LoadedSoundBank->LanguageRef.GetLanguageName().ToString()); + } + } + + for (auto* LoadedAuxBus : AffectedAuxBusses) + { + LoadedAuxBus->LanguageRef = NewLanguage; + auto* AuxBus = LoadedAuxBus->LocalizedAuxBusCookedData.AuxBusLanguageMap.Find(LoadedAuxBus->LanguageRef); + if (LIKELY(AuxBus)) + { + FCompletionPromise LoadPromise; + LoadFutureArray.Add(LoadPromise.GetFuture()); + FWwiseResourceLoadPromise ResourceLoadPromise; + ResourceLoadPromise.GetFuture().Next([LoadPromise = MoveTemp(LoadPromise)](int) mutable + { + LoadPromise.EmplaceValue(); + }); + LoadAuxBusResources(MoveTemp(ResourceLoadPromise), LoadedAuxBus->LoadedData, *AuxBus); + } + else + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("SetLanguage: Could not find AuxBus %s with language %s"), + *LoadedAuxBus->LocalizedAuxBusCookedData.DebugName.ToString(), *LoadedAuxBus->LanguageRef.GetLanguageName().ToString()); + } + } + + for (auto* LoadedShareSet : AffectedShareSets) + { + LoadedShareSet->LanguageRef = NewLanguage; + auto* ShareSet = LoadedShareSet->LocalizedShareSetCookedData.ShareSetLanguageMap.Find(LoadedShareSet->LanguageRef); + if (LIKELY(ShareSet)) + { + FCompletionPromise LoadPromise; + LoadFutureArray.Add(LoadPromise.GetFuture()); + FWwiseResourceLoadPromise ResourceLoadPromise; + ResourceLoadPromise.GetFuture().Next([LoadPromise = MoveTemp(LoadPromise)](int) mutable + { + LoadPromise.EmplaceValue(); + }); + LoadShareSetResources(MoveTemp(ResourceLoadPromise), LoadedShareSet->LoadedData, *ShareSet); + } + else + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("SetLanguage: Could not find ShareSet %s with language %s"), + *LoadedShareSet->LocalizedShareSetCookedData.DebugName.ToString(), *LoadedShareSet->LanguageRef.GetLanguageName().ToString()); + } + } + + for (auto* LoadedEvent : AffectedEvents) + { + LoadedEvent->LanguageRef = NewLanguage; + auto* Event = LoadedEvent->LocalizedEventCookedData.EventLanguageMap.Find(LoadedEvent->LanguageRef); + if (LIKELY(Event)) + { + FCompletionPromise LoadPromise; + LoadFutureArray.Add(LoadPromise.GetFuture()); + FWwiseResourceLoadPromise ResourceLoadPromise; + ResourceLoadPromise.GetFuture().Next([LoadPromise = MoveTemp(LoadPromise)](int) mutable + { + LoadPromise.EmplaceValue(); + }); + LoadEventResources(MoveTemp(ResourceLoadPromise), LoadedEvent->LoadedData, *Event); + } + else + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("SetLanguage: Could not find Event %s with language %s"), + *LoadedEvent->LocalizedEventCookedData.DebugName.ToString(), *LoadedEvent->LanguageRef.GetLanguageName().ToString()); + } + } + + WaitForFutures(MoveTemp(LoadFutureArray), [ + OldLanguage = MoveTemp(OldLanguage), + NewLanguage = MoveTemp(NewLanguage), + Promise = MoveTemp(Promise)]() mutable + { + UE_LOG(LogWwiseResourceLoader, Log, TEXT("SetLanguage: Done switching assets from language %s to language %s."), + *OldLanguage.GetLanguageName().ToString(), *NewLanguage.GetLanguageName().ToString()); + Promise.EmplaceValue(); + }); + }); + }); + }); +} + +void FWwiseResourceLoaderImpl::SetPlatform(const FWwiseSharedPlatformId& InPlatform) +{ + UE_LOG(LogWwiseResourceLoader, Log, TEXT("SetPlatform: Updating platform from %s (%s) to %s (%s)."), + *CurrentPlatform.GetPlatformName().ToString(), *CurrentPlatform.GetPlatformGuid().ToString(), + *InPlatform.GetPlatformName().ToString(), *InPlatform.GetPlatformGuid().ToString()); + + CurrentPlatform = InPlatform; +} + + +FWwiseLoadedAuxBus FWwiseResourceLoaderImpl::CreateAuxBusNode( + const FWwiseLocalizedAuxBusCookedData& InAuxBusCookedData, const FWwiseLanguageCookedData* InLanguageOverride) +{ + const auto* LanguageKey = GetLanguageMapKey(InAuxBusCookedData.AuxBusLanguageMap, InLanguageOverride, InAuxBusCookedData.DebugName); + if (UNLIKELY(!LanguageKey)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("CreateAuxBusNode: Could not find language for Aux Bus %s"), *InAuxBusCookedData.DebugName.ToString()); + return nullptr; + } + + return new FWwiseLoadedAuxBusListNode(FWwiseLoadedAuxBusInfo(InAuxBusCookedData, *LanguageKey)); +} + +void FWwiseResourceLoaderImpl::LoadAuxBusAsync(FWwiseLoadedAuxBusPromise&& Promise, FWwiseLoadedAuxBus&& InAuxBusListNode) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("LoadAuxBusAsync")); + FWwiseAsyncCycleCounter Timing(GET_STATID(STAT_WwiseResourceLoaderTiming)); + + auto& LoadedAuxBus = InAuxBusListNode->GetValue(); + LogLoad(LoadedAuxBus); + const FWwiseAuxBusCookedData* AuxBus = LoadedAuxBus.LocalizedAuxBusCookedData.AuxBusLanguageMap.Find(LoadedAuxBus.LanguageRef); + if (UNLIKELY(!AuxBus)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadAuxBusAsync: Could not find AuxBus %s (%" PRIu32 ") in language %s (%" PRIu32 ")"), + *LoadedAuxBus.LocalizedAuxBusCookedData.DebugName.ToString(), LoadedAuxBus.LocalizedAuxBusCookedData.AuxBusId, *LoadedAuxBus.LanguageRef.LanguageName.ToString(), LoadedAuxBus.LanguageRef.LanguageId); + delete InAuxBusListNode; + Timing.Stop(); + Promise.EmplaceValue(nullptr); + return; + } + + FWwiseResourceLoadPromise ResourceLoadPromise; + auto Future = ResourceLoadPromise.GetFuture(); + LoadAuxBusResources(MoveTemp(ResourceLoadPromise), LoadedAuxBus.LoadedData, *AuxBus); + + Future.Next([this, &LoadedAuxBus, AuxBus, InAuxBusListNode = MoveTemp(InAuxBusListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](bool bResult) mutable + { + if (UNLIKELY(!bResult)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadAuxBusAsync: Could not load AuxBus %s (%" PRIu32 ") in language %s (%" PRIu32 ")"), + *LoadedAuxBus.LocalizedAuxBusCookedData.DebugName.ToString(), LoadedAuxBus.LocalizedAuxBusCookedData.AuxBusId, *LoadedAuxBus.LanguageRef.LanguageName.ToString(), LoadedAuxBus.LanguageRef.LanguageId); + delete InAuxBusListNode; + Timing.Stop(); + Promise.EmplaceValue(nullptr); + return; + } + + AttachAuxBusNode(InAuxBusListNode) + .Next([InAuxBusListNode = MoveTemp(InAuxBusListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + Timing.Stop(); + Promise.EmplaceValue(InAuxBusListNode); + }); + }); +} + +void FWwiseResourceLoaderImpl::UnloadAuxBusAsync(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedAuxBus&& InAuxBusListNode) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("UnloadAuxBusAsync")); + FWwiseAsyncCycleCounter Timing(GET_STATID(STAT_WwiseResourceLoaderTiming)); + + auto& LoadedAuxBus = InAuxBusListNode->GetValue(); + + LogUnload(LoadedAuxBus); + + const FWwiseAuxBusCookedData* AuxBus = LoadedAuxBus.LocalizedAuxBusCookedData.AuxBusLanguageMap.Find(LoadedAuxBus.LanguageRef); + if (UNLIKELY(!AuxBus)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("UnloadAuxBusAsync: Could not find AuxBus %s (%" PRIu32 ") in language %s (%" PRIu32 "). Leaking!"), + *LoadedAuxBus.LocalizedAuxBusCookedData.DebugName.ToString(), LoadedAuxBus.LocalizedAuxBusCookedData.AuxBusId, *LoadedAuxBus.LanguageRef.LanguageName.ToString(), LoadedAuxBus.LanguageRef.LanguageId); + Timing.Stop(); + Promise.EmplaceValue(); + return; + } + + DetachAuxBusNode(InAuxBusListNode) + .Next([this, &LoadedAuxBus, AuxBus, InAuxBusListNode = MoveTemp(InAuxBusListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + + FWwiseResourceUnloadPromise ResourceUnloadPromise; + auto Future = ResourceUnloadPromise.GetFuture(); + UnloadAuxBusResources(MoveTemp(ResourceUnloadPromise), LoadedAuxBus.LoadedData, *AuxBus); + + Future.Next([this, &LoadedAuxBus, AuxBus, InAuxBusListNode = MoveTemp(InAuxBusListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + delete InAuxBusListNode; + + Timing.Stop(); + Promise.EmplaceValue(); + }); + }); +} + + +FWwiseLoadedEvent FWwiseResourceLoaderImpl::CreateEventNode( + const FWwiseLocalizedEventCookedData& InEventCookedData, const FWwiseLanguageCookedData* InLanguageOverride) +{ + const auto* LanguageKey = GetLanguageMapKey(InEventCookedData.EventLanguageMap, InLanguageOverride, InEventCookedData.DebugName); + if (UNLIKELY(!LanguageKey)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("CreateEventNode: Could not find language for Event %s"), *InEventCookedData.DebugName.ToString()); + return nullptr; + } + + return new FWwiseLoadedEventListNode(FWwiseLoadedEventInfo(InEventCookedData, *LanguageKey)); +} + +void FWwiseResourceLoaderImpl::LoadEventAsync(FWwiseLoadedEventPromise&& Promise, FWwiseLoadedEvent&& InEventListNode) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("LoadEventAsync")); + FWwiseAsyncCycleCounter Timing(GET_STATID(STAT_WwiseResourceLoaderTiming)); + + auto& LoadedEvent = InEventListNode->GetValue(); + + LogLoad(LoadedEvent); + + const FWwiseEventCookedData* Event = LoadedEvent.LocalizedEventCookedData.EventLanguageMap.Find(LoadedEvent.LanguageRef); + if (UNLIKELY(!Event)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadEventAsync: Could not find Event %s (%" PRIu32 ") in language %s (%" PRIu32 ")"), + *LoadedEvent.LocalizedEventCookedData.DebugName.ToString(), LoadedEvent.LocalizedEventCookedData.EventId, *LoadedEvent.LanguageRef.LanguageName.ToString(), LoadedEvent.LanguageRef.LanguageId); + delete InEventListNode; + Timing.Stop(); + Promise.EmplaceValue(nullptr); + return; + } + + FWwiseResourceLoadPromise ResourceLoadPromise; + auto Future = ResourceLoadPromise.GetFuture(); + LoadEventResources(MoveTemp(ResourceLoadPromise), LoadedEvent.LoadedData, *Event); + + Future.Next([this, &LoadedEvent, Event, InEventListNode = MoveTemp(InEventListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](bool bResult) mutable + { + if (UNLIKELY(!bResult)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadEventAsync: Could not load Event %s (%" PRIu32 ") in language %s (%" PRIu32 ")"), + *LoadedEvent.LocalizedEventCookedData.DebugName.ToString(), LoadedEvent.LocalizedEventCookedData.EventId, *LoadedEvent.LanguageRef.LanguageName.ToString(), LoadedEvent.LanguageRef.LanguageId); + delete InEventListNode; + Timing.Stop(); + Promise.EmplaceValue(nullptr); + return; + } + + AttachEventNode(InEventListNode) + .Next([this, InEventListNode = MoveTemp(InEventListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + Timing.Stop(); + Promise.EmplaceValue(InEventListNode); + }); + }); +} + +void FWwiseResourceLoaderImpl::UnloadEventAsync(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedEvent&& InEventListNode) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("UnloadEventAsync")); + FWwiseAsyncCycleCounter Timing(GET_STATID(STAT_WwiseResourceLoaderTiming)); + + auto& LoadedEvent = InEventListNode->GetValue(); + + LogUnload(LoadedEvent); + + const FWwiseEventCookedData* Event = LoadedEvent.LocalizedEventCookedData.EventLanguageMap.Find(LoadedEvent.LanguageRef); + if (UNLIKELY(!Event)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("UnloadEventAsync: Could not find Event %s (%" PRIu32 ") in language %s (%" PRIu32 "). Leaking!"), + *LoadedEvent.LocalizedEventCookedData.DebugName.ToString(), LoadedEvent.LocalizedEventCookedData.EventId, *LoadedEvent.LanguageRef.LanguageName.ToString(), LoadedEvent.LanguageRef.LanguageId); + Timing.Stop(); + Promise.EmplaceValue(); + return; + } + + DetachEventNode(InEventListNode) + .Next([this, &LoadedEvent, Event, InEventListNode = MoveTemp(InEventListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + FWwiseResourceUnloadPromise ResourceUnloadPromise; + auto Future = ResourceUnloadPromise.GetFuture(); + UnloadEventResources(MoveTemp(ResourceUnloadPromise), LoadedEvent.LoadedData, *Event); + + Future.Next([this, &LoadedEvent, Event, InEventListNode = MoveTemp(InEventListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + delete InEventListNode; + + Timing.Stop(); + Promise.EmplaceValue(); + }); + }); +} + + +FWwiseLoadedExternalSource FWwiseResourceLoaderImpl::CreateExternalSourceNode( + const FWwiseExternalSourceCookedData& InExternalSourceCookedData) +{ + return new FWwiseLoadedExternalSourceListNode(FWwiseLoadedExternalSourceInfo(InExternalSourceCookedData)); +} + +void FWwiseResourceLoaderImpl::LoadExternalSourceAsync(FWwiseLoadedExternalSourcePromise&& Promise, FWwiseLoadedExternalSource&& InExternalSourceListNode) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("LoadExternalSourceAsync")); + FWwiseAsyncCycleCounter Timing(GET_STATID(STAT_WwiseResourceLoaderTiming)); + + auto& LoadedExternalSource = InExternalSourceListNode->GetValue(); + + LogLoad(LoadedExternalSource); + + const FWwiseExternalSourceCookedData* ExternalSource = &LoadedExternalSource.ExternalSourceCookedData; + + FWwiseResourceLoadPromise ResourceLoadPromise; + auto Future = ResourceLoadPromise.GetFuture(); + LoadExternalSourceResources(MoveTemp(ResourceLoadPromise), LoadedExternalSource.LoadedData, *ExternalSource); + + Future.Next([this, &LoadedExternalSource, ExternalSource, InExternalSourceListNode = MoveTemp(InExternalSourceListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](bool bResult) mutable + { + if (UNLIKELY(!bResult)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadExternalSourceAsync: Could not load ExternalSource %s (%" PRIu32 ")"), + *LoadedExternalSource.ExternalSourceCookedData.DebugName.ToString(), LoadedExternalSource.ExternalSourceCookedData.Cookie); + delete InExternalSourceListNode; + Timing.Stop(); + Promise.EmplaceValue(nullptr); + return; + } + + AttachExternalSourceNode(InExternalSourceListNode) + .Next([this, InExternalSourceListNode = MoveTemp(InExternalSourceListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + + Timing.Stop(); + Promise.EmplaceValue(InExternalSourceListNode); + }); + }); +} + +void FWwiseResourceLoaderImpl::UnloadExternalSourceAsync(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedExternalSource&& InExternalSourceListNode) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("UnloadExternalSourceAsync")); + FWwiseAsyncCycleCounter Timing(GET_STATID(STAT_WwiseResourceLoaderTiming)); + + auto& LoadedExternalSource = InExternalSourceListNode->GetValue(); + + LogUnload(LoadedExternalSource); + + const FWwiseExternalSourceCookedData* ExternalSource = &LoadedExternalSource.ExternalSourceCookedData; + + DetachExternalSourceNode(InExternalSourceListNode) + .Next([this, &LoadedExternalSource, ExternalSource, InExternalSourceListNode = MoveTemp(InExternalSourceListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + FWwiseResourceUnloadPromise ResourceUnloadPromise; + auto Future = ResourceUnloadPromise.GetFuture(); + UnloadExternalSourceResources(MoveTemp(ResourceUnloadPromise), LoadedExternalSource.LoadedData, *ExternalSource); + + Future.Next([this, &LoadedExternalSource, ExternalSource, InExternalSourceListNode = MoveTemp(InExternalSourceListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + delete InExternalSourceListNode; + + Timing.Stop(); + Promise.EmplaceValue(); + }); + }); +} + + +FWwiseLoadedGroupValue FWwiseResourceLoaderImpl::CreateGroupValueNode( + const FWwiseGroupValueCookedData& InGroupValueCookedData) +{ + return new FWwiseLoadedGroupValueListNode(FWwiseLoadedGroupValueInfo(InGroupValueCookedData)); +} + +void FWwiseResourceLoaderImpl::LoadGroupValueAsync(FWwiseLoadedGroupValuePromise&& Promise, FWwiseLoadedGroupValue&& InGroupValueListNode) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("LoadGroupValueAsync")); + FWwiseAsyncCycleCounter Timing(GET_STATID(STAT_WwiseResourceLoaderTiming)); + + auto& LoadedGroupValue = InGroupValueListNode->GetValue(); + + LogLoad(LoadedGroupValue); + + const FWwiseGroupValueCookedData* GroupValue = &LoadedGroupValue.GroupValueCookedData; + + FWwiseResourceLoadPromise ResourceLoadPromise; + auto Future = ResourceLoadPromise.GetFuture(); + LoadGroupValueResources(MoveTemp(ResourceLoadPromise), LoadedGroupValue.LoadedData, *GroupValue); + + Future.Next([this, &LoadedGroupValue, GroupValue, InGroupValueListNode = MoveTemp(InGroupValueListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](bool bResult) mutable + { + if (UNLIKELY(!bResult)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadGroupValueAsync: Could not load GroupValue %s (%s %" PRIu32 ":%" PRIu32 ")"), + *LoadedGroupValue.GroupValueCookedData.DebugName.ToString(), *LoadedGroupValue.GroupValueCookedData.GetTypeName(), LoadedGroupValue.GroupValueCookedData.GroupId, LoadedGroupValue.GroupValueCookedData.Id); + delete InGroupValueListNode; + Timing.Stop(); + Promise.EmplaceValue(nullptr); + return; + } + + AttachGroupValueNode(InGroupValueListNode) + .Next([this, InGroupValueListNode = MoveTemp(InGroupValueListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + Timing.Stop(); + Promise.EmplaceValue(InGroupValueListNode); + }); + }); +} + +void FWwiseResourceLoaderImpl::UnloadGroupValueAsync(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedGroupValue&& InGroupValueListNode) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("UnloadGroupValueAsync")); + FWwiseAsyncCycleCounter Timing(GET_STATID(STAT_WwiseResourceLoaderTiming)); + + auto& LoadedGroupValue = InGroupValueListNode->GetValue(); + + LogUnload(LoadedGroupValue); + + const FWwiseGroupValueCookedData* GroupValue = &LoadedGroupValue.GroupValueCookedData; + + DetachGroupValueNode(InGroupValueListNode) + .Next([this, &LoadedGroupValue, GroupValue, InGroupValueListNode = MoveTemp(InGroupValueListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + FWwiseResourceUnloadPromise ResourceUnloadPromise; + auto Future = ResourceUnloadPromise.GetFuture(); + UnloadGroupValueResources(MoveTemp(ResourceUnloadPromise), LoadedGroupValue.LoadedData, *GroupValue); + + Future.Next([this, &LoadedGroupValue, GroupValue, InGroupValueListNode = MoveTemp(InGroupValueListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + delete InGroupValueListNode; + + Timing.Stop(); + Promise.EmplaceValue(); + }); + }); +} + + +FWwiseLoadedInitBank FWwiseResourceLoaderImpl::CreateInitBankNode( + const FWwiseInitBankCookedData& InInitBankCookedData) +{ + return new FWwiseLoadedInitBankListNode(FWwiseLoadedInitBankInfo(InInitBankCookedData)); +} + +void FWwiseResourceLoaderImpl::LoadInitBankAsync(FWwiseLoadedInitBankPromise&& Promise, FWwiseLoadedInitBank&& InInitBankListNode) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("LoadInitBankAsync")); + FWwiseAsyncCycleCounter Timing(GET_STATID(STAT_WwiseResourceLoaderTiming)); + + auto& LoadedInitBank = InInitBankListNode->GetValue(); + + LogLoad(LoadedInitBank); + + const FWwiseInitBankCookedData* InitBank = &LoadedInitBank.InitBankCookedData; + + FWwiseResourceLoadPromise ResourceLoadPromise; + auto Future = ResourceLoadPromise.GetFuture(); + LoadInitBankResources(MoveTemp(ResourceLoadPromise), LoadedInitBank.LoadedData, *InitBank); + + Future.Next([this, &LoadedInitBank, InitBank, InInitBankListNode = MoveTemp(InInitBankListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](bool bResult) mutable + { + if (UNLIKELY(!bResult)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadInitBankAsync: Could not load InitBank %s (%" PRIu32 ")"), + *LoadedInitBank.InitBankCookedData.DebugName.ToString(), LoadedInitBank.InitBankCookedData.SoundBankId); + delete InInitBankListNode; + Timing.Stop(); + Promise.EmplaceValue(nullptr); + return; + } + + AttachInitBankNode(InInitBankListNode) + .Next([this, InInitBankListNode = MoveTemp(InInitBankListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + Timing.Stop(); + Promise.EmplaceValue(InInitBankListNode); + }); + }); +} + +void FWwiseResourceLoaderImpl::UnloadInitBankAsync(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedInitBank&& InInitBankListNode) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("UnloadInitBankAsync")); + FWwiseAsyncCycleCounter Timing(GET_STATID(STAT_WwiseResourceLoaderTiming)); + + auto& LoadedInitBank = InInitBankListNode->GetValue(); + + LogUnload(LoadedInitBank); + + const FWwiseInitBankCookedData* InitBank = &LoadedInitBank.InitBankCookedData; + + DetachInitBankNode(InInitBankListNode) + .Next([this, &LoadedInitBank, InitBank, InInitBankListNode = MoveTemp(InInitBankListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + FWwiseResourceUnloadPromise ResourceUnloadPromise; + auto Future = ResourceUnloadPromise.GetFuture(); + UnloadInitBankResources(MoveTemp(ResourceUnloadPromise), LoadedInitBank.LoadedData, *InitBank); + + Future.Next([this, &LoadedInitBank, InitBank, InInitBankListNode = MoveTemp(InInitBankListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + delete InInitBankListNode; + + Timing.Stop(); + Promise.EmplaceValue(); + }); + }); +} + + +FWwiseLoadedMedia FWwiseResourceLoaderImpl::CreateMediaNode(const FWwiseMediaCookedData& InMediaCookedData) +{ + return new FWwiseLoadedMediaListNode(FWwiseLoadedMediaInfo(InMediaCookedData)); +} + +void FWwiseResourceLoaderImpl::LoadMediaAsync(FWwiseLoadedMediaPromise&& Promise, FWwiseLoadedMedia&& InMediaListNode) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("LoadMediaAsync")); + FWwiseAsyncCycleCounter Timing(GET_STATID(STAT_WwiseResourceLoaderTiming)); + + auto& LoadedMedia = InMediaListNode->GetValue(); + + LogLoad(LoadedMedia); + + const FWwiseMediaCookedData* Media = &LoadedMedia.MediaCookedData; + + FWwiseResourceLoadPromise ResourceLoadPromise; + auto Future = ResourceLoadPromise.GetFuture(); + LoadMediaResources(MoveTemp(ResourceLoadPromise), LoadedMedia.LoadedData, *Media); + + Future.Next([this, &LoadedMedia, Media, InMediaListNode = MoveTemp(InMediaListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](bool bResult) mutable + { + if (UNLIKELY(!bResult)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadMediaAsync: Could not load Media %s (%" PRIu32 ")"), + *LoadedMedia.MediaCookedData.DebugName.ToString(), LoadedMedia.MediaCookedData.MediaId); + delete InMediaListNode; + Timing.Stop(); + Promise.EmplaceValue(nullptr); + return; + } + + AttachMediaNode(InMediaListNode) + .Next([this, InMediaListNode = MoveTemp(InMediaListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + Timing.Stop(); + Promise.EmplaceValue(InMediaListNode); + }); + }); +} + +void FWwiseResourceLoaderImpl::UnloadMediaAsync(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedMedia&& InMediaListNode) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("UnloadMediaAsync")); + FWwiseAsyncCycleCounter Timing(GET_STATID(STAT_WwiseResourceLoaderTiming)); + + auto& LoadedMedia = InMediaListNode->GetValue(); + + LogUnload(LoadedMedia); + + const FWwiseMediaCookedData* Media = &LoadedMedia.MediaCookedData; + + DetachMediaNode(InMediaListNode) + .Next([this, &LoadedMedia, Media, InMediaListNode = MoveTemp(InMediaListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + FWwiseResourceUnloadPromise ResourceUnloadPromise; + auto Future = ResourceUnloadPromise.GetFuture(); + UnloadMediaResources(MoveTemp(ResourceUnloadPromise), LoadedMedia.LoadedData, *Media); + + Future.Next([this, &LoadedMedia, Media, InMediaListNode = MoveTemp(InMediaListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + delete InMediaListNode; + + Timing.Stop(); + Promise.EmplaceValue(); + }); + }); +} + + +FWwiseLoadedShareSet FWwiseResourceLoaderImpl::CreateShareSetNode( + const FWwiseLocalizedShareSetCookedData& InShareSetCookedData, const FWwiseLanguageCookedData* InLanguageOverride) +{ + const auto* LanguageKey = GetLanguageMapKey(InShareSetCookedData.ShareSetLanguageMap, InLanguageOverride, InShareSetCookedData.DebugName); + if (UNLIKELY(!LanguageKey)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("CreateShareSetNode: Could not find language for ShareSet %s"), *InShareSetCookedData.DebugName.ToString()); + return nullptr; + } + + return new FWwiseLoadedShareSetListNode(FWwiseLoadedShareSetInfo(InShareSetCookedData, *LanguageKey)); +} + +void FWwiseResourceLoaderImpl::LoadShareSetAsync(FWwiseLoadedShareSetPromise&& Promise, FWwiseLoadedShareSet&& InShareSetListNode) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("LoadShareSetAsync")); + FWwiseAsyncCycleCounter Timing(GET_STATID(STAT_WwiseResourceLoaderTiming)); + + auto& LoadedShareSet = InShareSetListNode->GetValue(); + + LogLoad(LoadedShareSet); + + const FWwiseShareSetCookedData* ShareSet = LoadedShareSet.LocalizedShareSetCookedData.ShareSetLanguageMap.Find(LoadedShareSet.LanguageRef); + if (UNLIKELY(!ShareSet)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadShareSetAsync: Could not find ShareSet %s (%" PRIu32 ") in language %s (%" PRIu32 ")"), + *LoadedShareSet.LocalizedShareSetCookedData.DebugName.ToString(), LoadedShareSet.LocalizedShareSetCookedData.ShareSetId, *LoadedShareSet.LanguageRef.LanguageName.ToString(), LoadedShareSet.LanguageRef.LanguageId); + delete InShareSetListNode; + Timing.Stop(); + Promise.EmplaceValue(nullptr); + return; + } + + FWwiseResourceLoadPromise ResourceLoadPromise; + auto Future = ResourceLoadPromise.GetFuture(); + LoadShareSetResources(MoveTemp(ResourceLoadPromise), LoadedShareSet.LoadedData, *ShareSet); + + Future.Next([this, &LoadedShareSet, ShareSet, InShareSetListNode = MoveTemp(InShareSetListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](bool bResult) mutable + { + if (UNLIKELY(!bResult)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadShareSetAsync: Could not load ShareSet %s (%" PRIu32 ") in language %s (%" PRIu32 ")"), + *LoadedShareSet.LocalizedShareSetCookedData.DebugName.ToString(), LoadedShareSet.LocalizedShareSetCookedData.ShareSetId, *LoadedShareSet.LanguageRef.LanguageName.ToString(), LoadedShareSet.LanguageRef.LanguageId); + delete InShareSetListNode; + Timing.Stop(); + Promise.EmplaceValue(nullptr); + return; + } + + AttachShareSetNode(InShareSetListNode) + .Next([this, InShareSetListNode = MoveTemp(InShareSetListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + Timing.Stop(); + Promise.EmplaceValue(InShareSetListNode); + }); + }); +} + +void FWwiseResourceLoaderImpl::UnloadShareSetAsync(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedShareSet&& InShareSetListNode) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("UnloadShareSetAsync")); + FWwiseAsyncCycleCounter Timing(GET_STATID(STAT_WwiseResourceLoaderTiming)); + + auto& LoadedShareSet = InShareSetListNode->GetValue(); + + LogUnload(LoadedShareSet); + + const FWwiseShareSetCookedData* ShareSet = LoadedShareSet.LocalizedShareSetCookedData.ShareSetLanguageMap.Find(LoadedShareSet.LanguageRef); + if (UNLIKELY(!ShareSet)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("UnloadShareSetAsync: Could not find ShareSet %s (%" PRIu32 ") in language %s (%" PRIu32 "). Leaking!"), + *LoadedShareSet.LocalizedShareSetCookedData.DebugName.ToString(), LoadedShareSet.LocalizedShareSetCookedData.ShareSetId, *LoadedShareSet.LanguageRef.LanguageName.ToString(), LoadedShareSet.LanguageRef.LanguageId); + Timing.Stop(); + Promise.EmplaceValue(); + return; + } + + DetachShareSetNode(InShareSetListNode) + .Next([this, &LoadedShareSet, ShareSet, InShareSetListNode = MoveTemp(InShareSetListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + FWwiseResourceUnloadPromise ResourceUnloadPromise; + auto Future = ResourceUnloadPromise.GetFuture(); + UnloadShareSetResources(MoveTemp(ResourceUnloadPromise), LoadedShareSet.LoadedData, *ShareSet); + + Future.Next([this, &LoadedShareSet, ShareSet, InShareSetListNode = MoveTemp(InShareSetListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + delete InShareSetListNode; + + Timing.Stop(); + Promise.EmplaceValue(); + }); + }); +} + +FWwiseLoadedSoundBank FWwiseResourceLoaderImpl::CreateSoundBankNode( + const FWwiseLocalizedSoundBankCookedData& InSoundBankCookedData, const FWwiseLanguageCookedData* InLanguageOverride) +{ + const auto* LanguageKey = GetLanguageMapKey(InSoundBankCookedData.SoundBankLanguageMap, InLanguageOverride, InSoundBankCookedData.DebugName); + if (UNLIKELY(!LanguageKey)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("CreateSoundBankNode: Could not find language for SoundBank %s"), *InSoundBankCookedData.DebugName.ToString()); + return nullptr; + } + + return new FWwiseLoadedSoundBankListNode(FWwiseLoadedSoundBankInfo(InSoundBankCookedData, *LanguageKey)); +} + +void FWwiseResourceLoaderImpl::LoadSoundBankAsync(FWwiseLoadedSoundBankPromise&& Promise, FWwiseLoadedSoundBank&& InSoundBankListNode) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("LoadSoundBankAsync")); + FWwiseAsyncCycleCounter Timing(GET_STATID(STAT_WwiseResourceLoaderTiming)); + + auto& LoadedSoundBank = InSoundBankListNode->GetValue(); + + LogLoad(LoadedSoundBank); + + const FWwiseSoundBankCookedData* SoundBank = LoadedSoundBank.LocalizedSoundBankCookedData.SoundBankLanguageMap.Find(LoadedSoundBank.LanguageRef); + if (UNLIKELY(!SoundBank)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadSoundBankAsync: Could not find SoundBank %s (%" PRIu32 ") in language %s (%" PRIu32 ")"), + *LoadedSoundBank.LocalizedSoundBankCookedData.DebugName.ToString(), LoadedSoundBank.LocalizedSoundBankCookedData.SoundBankId, *LoadedSoundBank.LanguageRef.LanguageName.ToString(), LoadedSoundBank.LanguageRef.LanguageId); + delete InSoundBankListNode; + Timing.Stop(); + Promise.EmplaceValue(nullptr); + return; + } + + FWwiseResourceLoadPromise ResourceLoadPromise; + auto Future = ResourceLoadPromise.GetFuture(); + LoadSoundBankResources(MoveTemp(ResourceLoadPromise), LoadedSoundBank.LoadedData, *SoundBank); + + Future.Next([this, &LoadedSoundBank, SoundBank, InSoundBankListNode = MoveTemp(InSoundBankListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](bool bResult) mutable + { + if (UNLIKELY(!bResult)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadSoundBankAsync: Could not load SoundBank %s (%" PRIu32 ") in language %s (%" PRIu32 ")"), + *LoadedSoundBank.LocalizedSoundBankCookedData.DebugName.ToString(), LoadedSoundBank.LocalizedSoundBankCookedData.SoundBankId, *LoadedSoundBank.LanguageRef.LanguageName.ToString(), LoadedSoundBank.LanguageRef.LanguageId); + delete InSoundBankListNode; + Timing.Stop(); + Promise.EmplaceValue(nullptr); + return; + } + + AttachSoundBankNode(InSoundBankListNode) + .Next([this, InSoundBankListNode = MoveTemp(InSoundBankListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + Timing.Stop(); + Promise.EmplaceValue(InSoundBankListNode); + }); + }); +} + +void FWwiseResourceLoaderImpl::UnloadSoundBankAsync(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedSoundBank&& InSoundBankListNode) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("UnloadSoundBankAsync")); + FWwiseAsyncCycleCounter Timing(GET_STATID(STAT_WwiseResourceLoaderTiming)); + + auto& LoadedSoundBank = InSoundBankListNode->GetValue(); + + LogUnload(LoadedSoundBank); + + const FWwiseSoundBankCookedData* SoundBank = LoadedSoundBank.LocalizedSoundBankCookedData.SoundBankLanguageMap.Find(LoadedSoundBank.LanguageRef); + if (UNLIKELY(!SoundBank)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("UnloadSoundBankAsync: Could not find SoundBank %s (%" PRIu32 ") in language %s (%" PRIu32 "). Leaking!"), + *LoadedSoundBank.LocalizedSoundBankCookedData.DebugName.ToString(), LoadedSoundBank.LocalizedSoundBankCookedData.SoundBankId, *LoadedSoundBank.LanguageRef.LanguageName.ToString(), LoadedSoundBank.LanguageRef.LanguageId); + Timing.Stop(); + Promise.EmplaceValue(); + return; + } + + DetachSoundBankNode(InSoundBankListNode) + .Next([this, &LoadedSoundBank, SoundBank, InSoundBankListNode = MoveTemp(InSoundBankListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + FWwiseResourceUnloadPromise ResourceUnloadPromise; + auto Future = ResourceUnloadPromise.GetFuture(); + UnloadSoundBankResources(MoveTemp(ResourceUnloadPromise), LoadedSoundBank.LoadedData, *SoundBank); + + Future.Next([this, &LoadedSoundBank, SoundBank, InSoundBankListNode = MoveTemp(InSoundBankListNode), Promise = MoveTemp(Promise), Timing = MoveTemp(Timing)](int) mutable + { + delete InSoundBankListNode; + + Timing.Stop(); + Promise.EmplaceValue(); + }); + }); +} + + +void FWwiseResourceLoaderImpl::LoadAuxBusResources(FWwiseResourceLoadPromise&& Promise, FWwiseLoadedAuxBusInfo::FLoadedData& LoadedData, const FWwiseAuxBusCookedData& InCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("LoadAuxBusResources")); + LogLoadResources(InCookedData); + + auto& LoadedSoundBanks = LoadedData.LoadedSoundBanks; + auto& LoadedMedia = LoadedData.LoadedMedia; + + if (UNLIKELY(LoadedData.IsLoaded())) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadAuxBusResources: AuxBus %s (%" PRIu32 ") is already loaded."), + *InCookedData.DebugName.ToString(), (uint32)InCookedData.AuxBusId); + return Promise.EmplaceValue(false); + } + + FCompletionFutureArray FutureArray; + + AddLoadMediaFutures(FutureArray, LoadedMedia, InCookedData.Media, TEXT("AuxBus"), InCookedData.DebugName, InCookedData.AuxBusId); + AddLoadSoundBankFutures(FutureArray, LoadedSoundBanks, InCookedData.SoundBanks, TEXT("AuxBus"), InCookedData.DebugName, InCookedData.AuxBusId); + WaitForFutures(MoveTemp(FutureArray), [this, Promise = MoveTemp(Promise), &LoadedData, &LoadedSoundBanks, &InCookedData]() mutable + { + if (UNLIKELY(LoadedSoundBanks.Num() != InCookedData.SoundBanks.Num())) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadAuxBusResources: Could not load %d prerequisites for AuxBus %s (%" PRIu32 "). Unloading and failing."), + InCookedData.SoundBanks.Num() - LoadedSoundBanks.Num(), *InCookedData.DebugName.ToString(), (uint32)InCookedData.AuxBusId); + + FWwiseResourceUnloadPromise UnloadPromise; + auto UnloadFuture = UnloadPromise.GetFuture(); + UnloadAuxBusResources(MoveTemp(UnloadPromise), LoadedData, InCookedData); + UnloadFuture.Next([Promise = MoveTemp(Promise)](int) mutable + { + return Promise.EmplaceValue(false); + }); + } + else + { + return Promise.EmplaceValue(true); + } + }); +} + +void FWwiseResourceLoaderImpl::LoadEventResources(FWwiseResourceLoadPromise&& Promise, FWwiseLoadedEventInfo::FLoadedData& LoadedData, const FWwiseEventCookedData& InCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("LoadEventResources")); + LogLoadResources(InCookedData); + + auto& LoadedSoundBanks = LoadedData.LoadedSoundBanks; + auto& LoadedExternalSources = LoadedData.LoadedExternalSources; + auto& LoadedMedia = LoadedData.LoadedMedia; + + if (UNLIKELY(LoadedData.IsLoaded())) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadEventResources: Event %s (%" PRIu32 ") is already loaded."), + *InCookedData.DebugName.ToString(), (uint32)InCookedData.EventId); + return Promise.EmplaceValue(false); + } + + FWwiseResourceLoadPromise SwitchContainerPromise; + auto SwitchContainerFuture = SwitchContainerPromise.GetFuture(); + if (InCookedData.RequiredGroupValueSet.Num() > 0 || InCookedData.SwitchContainerLeaves.Num() > 0) + { + LoadEventSwitchContainerResources(MoveTemp(SwitchContainerPromise), LoadedData, InCookedData); + } + else + { + SwitchContainerPromise.EmplaceValue(true); + } + + FCompletionFutureArray FutureArray; + + AddLoadExternalSourceFutures(FutureArray, LoadedExternalSources, InCookedData.ExternalSources, TEXT("Event"), InCookedData.DebugName, InCookedData.EventId); + AddLoadMediaFutures(FutureArray, LoadedMedia, InCookedData.Media, TEXT("Event"), InCookedData.DebugName, InCookedData.EventId); + AddLoadSoundBankFutures(FutureArray, LoadedSoundBanks, InCookedData.SoundBanks, TEXT("Event"), InCookedData.DebugName, InCookedData.EventId); + SwitchContainerFuture.Next([this, FutureArray = MoveTemp(FutureArray), Promise = MoveTemp(Promise), &LoadedData, &LoadedSoundBanks, &InCookedData](bool bResult) mutable + { + WaitForFutures(MoveTemp(FutureArray), [this, Promise = MoveTemp(Promise), &LoadedData, &LoadedSoundBanks, &InCookedData, bSwitchContainerResult = bResult]() mutable + { + if (UNLIKELY(LoadedSoundBanks.Num() != InCookedData.SoundBanks.Num())) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadEventResources: Could not load %d prerequisites for Event %s (%" PRIu32 "). Unloading and failing."), + InCookedData.SoundBanks.Num() - LoadedSoundBanks.Num(), *InCookedData.DebugName.ToString(), (uint32)InCookedData.EventId); + FWwiseResourceUnloadPromise UnloadPromise; + auto UnloadFuture = UnloadPromise.GetFuture(); + UnloadEventResources(MoveTemp(UnloadPromise), LoadedData, InCookedData); + UnloadFuture.Next([Promise = MoveTemp(Promise)](int) mutable + { + return Promise.EmplaceValue(false); + }); + } + else + { + return Promise.EmplaceValue(true); + } + }); + }); +} + +void FWwiseResourceLoaderImpl::LoadEventSwitchContainerResources(FWwiseResourceLoadPromise&& Promise, FWwiseLoadedEventInfo::FLoadedData& LoadedData, const FWwiseEventCookedData& InCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("LoadEventSwitchContainerResources")); + + // Load required GroupValues + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("Loading %d GroupValues for Event %s (%" PRIu32 ")"), + (int)InCookedData.RequiredGroupValueSet.Num(), *InCookedData.DebugName.ToString(), (uint32)InCookedData.EventId); + + FWwiseLoadedGroupValueList& LoadedRequiredGroupValues = LoadedData.LoadedRequiredGroupValues; + bool& bLoadedSwitchContainerLeaves = LoadedData.bLoadedSwitchContainerLeaves; + FCompletionFutureArray FutureArray; + + for (const auto& GroupValue : InCookedData.RequiredGroupValueSet) + { + FCompletionPromise GroupValuePromise; + FutureArray.Add(GroupValuePromise.GetFuture()); + + SwitchContainerExecutionQueue.Async([this, &LoadedRequiredGroupValues, &InCookedData, &GroupValue, GroupValuePromise = MoveTemp(GroupValuePromise)]() mutable + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("Loading GroupValue %s for Event %s (%" PRIu32 ")"), + *GroupValue.GetDebugString(), *InCookedData.DebugName.ToString(), (uint32)InCookedData.EventId); + + auto* LoadedNode = new FWwiseLoadedGroupValueListNode(FWwiseLoadedGroupValueInfo(GroupValue)); + auto& GroupValueLoadedData = LoadedNode->GetValue().LoadedData; + + FWwiseResourceLoadPromise GroupValueResourcePromise; + auto GroupValueResourceFuture = GroupValueResourcePromise.GetFuture(); + LoadGroupValueResources(MoveTemp(GroupValueResourcePromise), GroupValueLoadedData, GroupValue); + GroupValueResourceFuture.Next([this, &LoadedRequiredGroupValues, &InCookedData, &GroupValue, GroupValuePromise = MoveTemp(GroupValuePromise), LoadedNode](bool bResult) mutable + { + const auto& GroupValueLoadedData = LoadedNode->GetValue().LoadedData; + if (UNLIKELY(!bResult || !GroupValueLoadedData.IsLoaded())) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("Could not load required GroupValue %s for Event %s (%" PRIu32 ")"), + *GroupValue.DebugName.ToString(), *InCookedData.DebugName.ToString(), (uint32)InCookedData.EventId); + delete LoadedNode; + GroupValuePromise.EmplaceValue(); + } + else + { + SwitchContainerExecutionQueue.Async([this, &LoadedRequiredGroupValues, LoadedNode, GroupValuePromise = MoveTemp(GroupValuePromise)]() mutable + { + LoadedRequiredGroupValues.AddTail(LoadedNode); + GroupValuePromise.EmplaceValue(); + }); + } + }); + }); + } + + // Load Switch Container Leaves + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("Loading %d Leaves for Event %s (%" PRIu32 ")"), + (int)InCookedData.SwitchContainerLeaves.Num(), *InCookedData.DebugName.ToString(), (uint32)InCookedData.EventId); + + for (const auto& SwitchContainerLeaf : InCookedData.SwitchContainerLeaves) + { + check(SwitchContainerLeaf.GroupValueSet.Num() > 0); + FWwiseSwitchContainerLeafGroupValueUsageCount* UsageCount = new FWwiseSwitchContainerLeafGroupValueUsageCount(SwitchContainerLeaf); + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("Created %s Usage Count @ %p for Event %s (%" PRIu32 ")"), + *SwitchContainerLeaf.GetDebugString(), &UsageCount->LoadedData, + *InCookedData.DebugName.ToString(), (uint32)InCookedData.EventId); + for (const auto& GroupValue : SwitchContainerLeaf.GroupValueSet) + { + FCompletionPromise SwitchContainerLeafPromise; + FutureArray.Add(SwitchContainerLeafPromise.GetFuture()); + + SwitchContainerExecutionQueue.Async([this, &bLoadedSwitchContainerLeaves, &InCookedData, &GroupValue, UsageCount, SwitchContainerLeafPromise = MoveTemp(SwitchContainerLeafPromise)]() mutable + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("Adding optional %s for %s in Event %s (%" PRIu32 ")"), + *GroupValue.GetDebugString(), *UsageCount->Key.GetDebugString(), *InCookedData.DebugName.ToString(), (uint32)InCookedData.EventId); + + auto FoundInfoId = LoadedGroupValueInfo.FindId(FWwiseSwitchContainerLoadedGroupValueInfo(GroupValue)); + auto InfoId = FoundInfoId.IsValidId() ? FoundInfoId : LoadedGroupValueInfo.Add(FWwiseSwitchContainerLoadedGroupValueInfo(GroupValue), nullptr); + FWwiseSwitchContainerLoadedGroupValueInfo& Info = LoadedGroupValueInfo[InfoId]; + bool bIsAlreadyCreated = false; + auto UsageCountId = Info.Leaves.Add(UsageCount, &bIsAlreadyCreated); + if (UNLIKELY(bIsAlreadyCreated)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("Creating already created Switch Container Leaf Usage Count @ %p for key %s"), + &UsageCount->LoadedData, *UsageCount->Key.GetDebugString()); + return SwitchContainerLeafPromise.EmplaceValue(); + } + + bLoadedSwitchContainerLeaves = true; + UE_CLOG(!Info.ShouldBeLoaded(), LogWwiseResourceLoader, VeryVerbose, TEXT("Don't have referencing GroupValues yet: %d for key %s"), Info.LoadCount, *UsageCount->Key.GetDebugString()); + UE_CLOG(Info.ShouldBeLoaded(), LogWwiseResourceLoader, VeryVerbose, TEXT("Have referencing GroupValues: %d for key %s"), Info.LoadCount, *UsageCount->Key.GetDebugString()); + if (Info.ShouldBeLoaded()) + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("Number of GroupValues required for this leaf: %d/%d @ %p for key %s (+1 in Event)"), + (int)UsageCount->LoadedGroupValues.Num() + 1, UsageCount->Key.GroupValueSet.Num(), &UsageCount->LoadedData, *UsageCount->Key.GetDebugString()); + bIsAlreadyCreated = false; + UsageCount->LoadedGroupValues.Add(GroupValue, &bIsAlreadyCreated); + if (UNLIKELY(bIsAlreadyCreated)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("Loading already created Switch Container Leaf LoadedGoupValueCount %d @ %p for key %s"), + (int)UsageCount->LoadedGroupValues.Num(), &UsageCount->LoadedData, *UsageCount->Key.GetDebugString()); + return SwitchContainerLeafPromise.EmplaceValue(); + } + + if (UsageCount->HaveAllKeys()) + { + check(!UsageCount->LoadedData.IsLoaded()); + FWwiseResourceLoadPromise LeafResourcesPromise; + auto LeafResourcesFuture = LeafResourcesPromise.GetFuture(); + LoadSwitchContainerLeafResources(MoveTemp(LeafResourcesPromise), UsageCount->LoadedData, UsageCount->Key); + LeafResourcesFuture.Next([SwitchContainerLeafPromise = MoveTemp(SwitchContainerLeafPromise)](bool bResult) mutable + { + SwitchContainerLeafPromise.EmplaceValue(); + }); + } + else + { + SwitchContainerLeafPromise.EmplaceValue(); + } + } + else + { + SwitchContainerLeafPromise.EmplaceValue(); + } + }); + } + } + + WaitForFutures(MoveTemp(FutureArray), [Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(true); + }); +} + +void FWwiseResourceLoaderImpl::LoadExternalSourceResources(FWwiseResourceLoadPromise&& Promise, FWwiseLoadedExternalSourceInfo::FLoadedData& LoadedData, const FWwiseExternalSourceCookedData& InCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("LoadExternalSourceResources")); + LogLoadResources(InCookedData); + + if (UNLIKELY(LoadedData.IsLoaded())) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadExternalSourceResources: ExternalSource %s (%" PRIu32 ") is already loaded."), + *InCookedData.DebugName.ToString(), (uint32)InCookedData.Cookie); + return Promise.EmplaceValue(false); + } + + LoadExternalSourceFile(InCookedData, [Promise = MoveTemp(Promise), &LoadedData, &InCookedData](bool bResult) mutable + { + LoadedData.bLoaded = bResult; + if (UNLIKELY(!bResult)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadExternalSourceResources: Could not load ExternalSource %s (%" PRIu32 ")"), + *InCookedData.DebugName.ToString(), (uint32)InCookedData.Cookie); + } + Promise.EmplaceValue(bResult); + }); +} + +void FWwiseResourceLoaderImpl::LoadGroupValueResources(FWwiseResourceLoadPromise&& Promise, FWwiseLoadedGroupValueInfo::FLoadedData& LoadedData, const FWwiseGroupValueCookedData& InCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("LoadGroupValueResources")); + LogLoadResources(InCookedData); + + SwitchContainerExecutionQueue.Async([this, &LoadedData, &InCookedData, Promise = MoveTemp(Promise)]() mutable + { + auto FoundInfoId = LoadedGroupValueInfo.FindId(FWwiseSwitchContainerLoadedGroupValueInfo(InCookedData)); + auto InfoId = FoundInfoId.IsValidId() ? FoundInfoId : LoadedGroupValueInfo.Add(FWwiseSwitchContainerLoadedGroupValueInfo(InCookedData), nullptr); + FWwiseSwitchContainerLoadedGroupValueInfo& Info = LoadedGroupValueInfo[InfoId]; + const bool bWasLoaded = Info.ShouldBeLoaded(); + ++Info.LoadCount; + + FCompletionFutureArray FutureArray; + + if (!bWasLoaded && Info.ShouldBeLoaded()) + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("First GroupValue %s (%s %" PRIu32 ":%" PRIu32 ") load. Loading %d leaves."), + *InCookedData.DebugName.ToString(), *InCookedData.GetTypeName(), (uint32)InCookedData.GroupId, (uint32)InCookedData.Id, (int)Info.Leaves.Num()); + + for (auto* UsageCount : Info.Leaves) + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("Number of GroupValues required for a leaf: %d/%d @ %p for key %s (+1 in GroupValue)"), + (int)UsageCount->LoadedGroupValues.Num() + 1, UsageCount->Key.GroupValueSet.Num(), &UsageCount->LoadedData, *UsageCount->Key.GetDebugString()); + bool bIsAlreadyCreated = false; + UsageCount->LoadedGroupValues.Add(InCookedData, &bIsAlreadyCreated); + if (UNLIKELY(bIsAlreadyCreated)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("Loading already created LoadedGroupValue @ %p for key %s"), + &UsageCount->LoadedData, *UsageCount->Key.GetDebugString()); + continue; + } + + if (UsageCount->HaveAllKeys()) + { + FWwiseResourceLoadPromise LeafPromise; + auto LeafFuture = LeafPromise.GetFuture(); + LoadSwitchContainerLeafResources(MoveTemp(LeafPromise), UsageCount->LoadedData, UsageCount->Key); + + FCompletionPromise CompletionPromise; + FutureArray.Add(CompletionPromise.GetFuture()); + + LeafFuture.Next([CompletionPromise = MoveTemp(CompletionPromise)](int) mutable + { + CompletionPromise.EmplaceValue(); + }); + } + } + } + else + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("GroupValue %s (%s %" PRIu32 ":%" PRIu32 ") already loaded (Count: %d times)."), + *InCookedData.DebugName.ToString(), *InCookedData.GetTypeName(), (uint32)InCookedData.GroupId, (uint32)InCookedData.Id, (int)Info.LoadCount); + } + WaitForFutures(MoveTemp(FutureArray), [&LoadedData, Promise = MoveTemp(Promise)]() mutable + { + LoadedData.bLoaded = true; + // We always return success, as GroupValues are not complete deal-breaks and we cannot do anything if they fail. + return Promise.EmplaceValue(true); + }); + }); +} + +void FWwiseResourceLoaderImpl::LoadInitBankResources(FWwiseResourceLoadPromise&& Promise, FWwiseLoadedInitBankInfo::FLoadedData& LoadedData, const FWwiseInitBankCookedData& InCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("LoadInitBankResources")); + LogLoadResources(InCookedData); + + auto& LoadedMedia = LoadedData.LoadedMedia; + + if (UNLIKELY(LoadedData.IsLoaded())) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadInitBankResources: InitBank %s (%" PRIu32 ") is already loaded."), + *InCookedData.DebugName.ToString(), (uint32)InCookedData.SoundBankId); + return Promise.EmplaceValue(false); + } + + FCompletionFutureArray FutureArray; + AddLoadMediaFutures(FutureArray, LoadedMedia, InCookedData.Media, TEXT("InitBank"), InCookedData.DebugName, InCookedData.SoundBankId); + WaitForFutures(MoveTemp(FutureArray), [this, Promise = MoveTemp(Promise), &LoadedData, &InCookedData]() mutable + { + TPromise SoundBankPromise; + auto Future = SoundBankPromise.GetFuture(); + LoadSoundBankFile(InCookedData, [SoundBankPromise = MoveTemp(SoundBankPromise)](bool bInResult) mutable + { + SoundBankPromise.EmplaceValue(bInResult); + }); + Future.Next([this, Promise = MoveTemp(Promise), &LoadedData, &InCookedData](bool bLoaded) mutable + { + LoadedData.bLoaded = bLoaded; + + if (UNLIKELY(!bLoaded)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadInitBankResources: Could not load InitBank %s (%" PRIu32 ")"), + *InCookedData.DebugName.ToString(), (uint32)InCookedData.SoundBankId); + } + return Promise.EmplaceValue(bLoaded); + }); + }); +} + +void FWwiseResourceLoaderImpl::LoadMediaResources(FWwiseResourceLoadPromise&& Promise, FWwiseLoadedMediaInfo::FLoadedData& LoadedData, const FWwiseMediaCookedData& InCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("LoadMediaResources")); + LogLoadResources(InCookedData); + + if (UNLIKELY(LoadedData.IsLoaded())) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadMediaResources: Media %s (%" PRIu32 ") is already loaded."), + *InCookedData.DebugName.ToString(), (uint32)InCookedData.MediaId); + return Promise.EmplaceValue(false); + } + + LoadMediaFile(InCookedData, [Promise = MoveTemp(Promise), &LoadedData, &InCookedData](bool bResult) mutable + { + LoadedData.bLoaded = bResult; + if (UNLIKELY(!bResult)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadMediaResources: Could not load Media %s (%" PRIu32 ")"), + *InCookedData.DebugName.ToString(), (uint32)InCookedData.MediaId); + } + Promise.EmplaceValue(bResult); + }); +} + +void FWwiseResourceLoaderImpl::LoadShareSetResources(FWwiseResourceLoadPromise&& Promise, FWwiseLoadedShareSetInfo::FLoadedData& LoadedData, const FWwiseShareSetCookedData& InCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("LoadShareSetResources")); + LogLoadResources(InCookedData); + + auto& LoadedSoundBanks = LoadedData.LoadedSoundBanks; + auto& LoadedMedia = LoadedData.LoadedMedia; + + if (UNLIKELY(LoadedData.IsLoaded())) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadShareSetResources: ShareSet %s (%" PRIu32 ") is already loaded."), + *InCookedData.DebugName.ToString(), (uint32)InCookedData.ShareSetId); + return Promise.EmplaceValue(false); + } + + FCompletionFutureArray FutureArray; + + AddLoadMediaFutures(FutureArray, LoadedMedia, InCookedData.Media, TEXT("ShareSet"), InCookedData.DebugName, InCookedData.ShareSetId); + AddLoadSoundBankFutures(FutureArray, LoadedSoundBanks, InCookedData.SoundBanks, TEXT("ShareSet"), InCookedData.DebugName, InCookedData.ShareSetId); + WaitForFutures(MoveTemp(FutureArray), [this, Promise = MoveTemp(Promise), &LoadedData, &LoadedSoundBanks, &InCookedData]() mutable + { + if (UNLIKELY(LoadedSoundBanks.Num() != InCookedData.SoundBanks.Num())) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadShareSetResources: Could not load %d SoundBanks for ShareSet %s (%" PRIu32 "). Unloading and failing."), + InCookedData.SoundBanks.Num() - LoadedSoundBanks.Num(), *InCookedData.DebugName.ToString(), (uint32)InCookedData.ShareSetId); + + FWwiseResourceUnloadPromise UnloadPromise; + auto UnloadFuture = UnloadPromise.GetFuture(); + + UnloadShareSetResources(MoveTemp(UnloadPromise), LoadedData, InCookedData); + UnloadFuture.Next([Promise = MoveTemp(Promise)](int) mutable + { + return Promise.EmplaceValue(false); + }); + } + else + { + return Promise.EmplaceValue(true); + } + }); +} + +void FWwiseResourceLoaderImpl::LoadSoundBankResources(FWwiseResourceLoadPromise&& Promise, FWwiseLoadedSoundBankInfo::FLoadedData& LoadedData, const FWwiseSoundBankCookedData& InCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("LoadSoundBankResources")); + LogLoadResources(InCookedData); + + if (UNLIKELY(LoadedData.IsLoaded())) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadSoundBankResources: SoundBank %s (%" PRIu32 ") is already loaded."), + *InCookedData.DebugName.ToString(), (uint32)InCookedData.SoundBankId); + return Promise.EmplaceValue(false); + } + + LoadSoundBankFile(InCookedData, [Promise = MoveTemp(Promise), &LoadedData, &InCookedData](bool bResult) mutable + { + LoadedData.bLoaded = bResult; + if (UNLIKELY(!bResult)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadSoundBankResources: Could not load SoundBank %s (%" PRIu32 ")"), + *InCookedData.DebugName.ToString(), (uint32)InCookedData.SoundBankId); + } + + Promise.EmplaceValue(bResult); + }); +} + +void FWwiseResourceLoaderImpl::LoadSwitchContainerLeafResources(FWwiseResourceLoadPromise&& Promise, FWwiseSwitchContainerLeafGroupValueUsageCount::FLoadedData& LoadedData, const FWwiseSwitchContainerLeafCookedData& InCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("LoadSwitchContainerLeafResources")); + LogLoadResources(InCookedData, &LoadedData); + + auto& LoadedSoundBanks = LoadedData.LoadedSoundBanks; + auto& LoadedExternalSources = LoadedData.LoadedExternalSources; + auto& LoadedMedia = LoadedData.LoadedMedia; + + if (UNLIKELY(LoadedData.IsLoaded())) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("LoadSwitchContainerLeafResources: Leaf is already loaded.")); + return Promise.EmplaceValue(true); // It's loaded, it exists, even if wrong. + } + + FCompletionFutureArray FutureArray; + + AddLoadExternalSourceFutures(FutureArray, LoadedExternalSources, InCookedData.ExternalSources, TEXT("Leaf"), {}, 0); + AddLoadMediaFutures(FutureArray, LoadedMedia, InCookedData.Media, TEXT("Leaf"), {}, 0); + AddLoadSoundBankFutures(FutureArray, LoadedSoundBanks, InCookedData.SoundBanks, TEXT("Leaf"), {}, 0); + WaitForFutures(MoveTemp(FutureArray), [Promise = MoveTemp(Promise)]() mutable + { + INC_DWORD_STAT(STAT_WwiseResourceLoaderSwitchContainerCombinations); + Promise.EmplaceValue(true); + }); +} + + +void FWwiseResourceLoaderImpl::UnloadAuxBusResources(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedAuxBusInfo::FLoadedData& LoadedData, const FWwiseAuxBusCookedData& InCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("UnloadAuxBusResources")); + LogUnloadResources(InCookedData); + + auto& LoadedSoundBanks = LoadedData.LoadedSoundBanks; + auto& LoadedMedia = LoadedData.LoadedMedia; + + FCompletionFutureArray FutureArray; + AddUnloadSoundBankFutures(FutureArray, LoadedSoundBanks); + WaitForFutures(MoveTemp(FutureArray), [this, Promise = MoveTemp(Promise), &LoadedMedia]() mutable + { + FCompletionFutureArray FutureArray; + AddUnloadMediaFutures(FutureArray, LoadedMedia); + WaitForFutures(MoveTemp(FutureArray), [Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + }); +} + +void FWwiseResourceLoaderImpl::UnloadEventResources(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedEventInfo::FLoadedData& LoadedData, const FWwiseEventCookedData& InCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("UnloadEventResources")); + LogUnloadResources(InCookedData); + + auto& LoadedSoundBanks = LoadedData.LoadedSoundBanks; + + FCompletionFutureArray FutureArray; + AddUnloadSoundBankFutures(FutureArray, LoadedSoundBanks); + WaitForFutures(MoveTemp(FutureArray), [this, Promise = MoveTemp(Promise), &LoadedData, &InCookedData]() mutable + { + auto& LoadedExternalSources = LoadedData.LoadedExternalSources; + auto& LoadedMedia = LoadedData.LoadedMedia; + + FCompletionFutureArray FutureArray; + if (LoadedData.bLoadedSwitchContainerLeaves || LoadedData.LoadedRequiredGroupValues.Num() > 0) + { + FCompletionPromise SwitchContainerLeavesPromise; + FutureArray.Add(SwitchContainerLeavesPromise.GetFuture()); + AsyncTask(TaskThread, [this, &LoadedData, &InCookedData, SwitchContainerLeavesPromise = MoveTemp(SwitchContainerLeavesPromise)]() mutable + { + UnloadEventSwitchContainerResources(MoveTemp(SwitchContainerLeavesPromise), LoadedData, InCookedData); + }); + } + AddUnloadExternalSourceFutures(FutureArray, LoadedExternalSources); + AddUnloadMediaFutures(FutureArray, LoadedMedia); + WaitForFutures(MoveTemp(FutureArray), [Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + }); +} + +void FWwiseResourceLoaderImpl::UnloadEventSwitchContainerResources(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedEventInfo::FLoadedData& LoadedData, const FWwiseEventCookedData& InCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("UnloadEventSwitchContainerResources")); + + // Unload required GroupValues + FWwiseLoadedGroupValueList& LoadedRequiredGroupValues = LoadedData.LoadedRequiredGroupValues; + bool& bLoadedSwitchContainerLeaves = LoadedData.bLoadedSwitchContainerLeaves; + + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("Unloading %d GroupValues for Event %s (%" PRIu32 ")"), + (int)LoadedRequiredGroupValues.Num(), *InCookedData.DebugName.ToString(), (uint32)InCookedData.EventId); + + FCompletionFutureArray FutureArray; + + for (auto& GroupValue : LoadedRequiredGroupValues) + { + FCompletionPromise GroupValuePromise; + FutureArray.Add(GroupValuePromise.GetFuture()); + + SwitchContainerExecutionQueue.Async([this, &InCookedData, &GroupValue, GroupValuePromise = MoveTemp(GroupValuePromise)]() mutable + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("Unloading GroupValue %s for Event %s (%" PRIu32 ")"), + *GroupValue.GroupValueCookedData.DebugName.ToString(), *InCookedData.DebugName.ToString(), (uint32)InCookedData.EventId); + + UnloadGroupValueResources(MoveTemp(GroupValuePromise), GroupValue.LoadedData, GroupValue.GroupValueCookedData); + }); + } + + // Unload Switch Container Leaves + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("Unloading %d Leaves for Event %s (%" PRIu32 ")"), + (int)InCookedData.SwitchContainerLeaves.Num(), *InCookedData.DebugName.ToString(), (uint32)InCookedData.EventId); + + if (bLoadedSwitchContainerLeaves) for (const auto& SwitchContainerLeaf : InCookedData.SwitchContainerLeaves) + { + FCompletionPromise SwitchContainerLeavesPromise; + FutureArray.Add(SwitchContainerLeavesPromise.GetFuture()); + + SwitchContainerExecutionQueue.Async([this, &SwitchContainerLeaf, &InCookedData, SwitchContainerLeavesPromise = MoveTemp(SwitchContainerLeavesPromise)]() mutable + { + FWwiseSwitchContainerLeafGroupValueUsageCount* UsageCount = nullptr; + for (const auto& GroupValue : SwitchContainerLeaf.GroupValueSet) + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("Removing requested GroupValue %s for Leaf in Event %s (%" PRIu32 ")"), + *GroupValue.DebugName.ToString(), *InCookedData.DebugName.ToString(), (uint32)InCookedData.EventId); + + FWwiseSwitchContainerLoadedGroupValueInfo* Info = LoadedGroupValueInfo.Find(FWwiseSwitchContainerLoadedGroupValueInfo(GroupValue)); + if (UNLIKELY(!Info)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("Could not find requested GroupValue %s for Leaf in Event %s (%" PRIu32 ")"), + *GroupValue.DebugName.ToString(), *InCookedData.DebugName.ToString(), (uint32)InCookedData.EventId); + continue; + } + + if (!UsageCount) + { + for (auto* Leaf : Info->Leaves) + { + if (&Leaf->Key == &SwitchContainerLeaf) + { + UsageCount = Leaf; + break; + } + } + + if (UNLIKELY(!UsageCount)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("Could not find requested Leaf in GroupValue %s in Event %s (%" PRIu32 ")"), + *GroupValue.DebugName.ToString(), *InCookedData.DebugName.ToString(), (uint32)InCookedData.EventId); + continue; + } + } + Info->Leaves.Remove(UsageCount); + + UE_CLOG(!Info->ShouldBeLoaded(), LogWwiseResourceLoader, VeryVerbose, TEXT("Don't have referencing GroupValues yet: %d for key %s"), Info->LoadCount, *UsageCount->Key.GetDebugString()); + UE_CLOG(Info->ShouldBeLoaded(), LogWwiseResourceLoader, VeryVerbose, TEXT("Have referencing GroupValues: %d for key %s"), Info->LoadCount, *UsageCount->Key.GetDebugString()); + if (Info->ShouldBeLoaded()) + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("Number of GroupValues required for this leaf: %d/%d @ %p for key %s (-1 in SwitchContainer)"), + (int)UsageCount->LoadedGroupValues.Num() - 1, UsageCount->Key.GroupValueSet.Num(), &UsageCount->LoadedData, *UsageCount->Key.GetDebugString()); + UsageCount->LoadedGroupValues.Remove(GroupValue); + } + } + + if (LIKELY(UsageCount)) + { + if (UNLIKELY(UsageCount->LoadedGroupValues.Num() > 0)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("There are still %d loaded elements for leaf in Event %s (%" PRIu32 ")"), + (int)UsageCount->LoadedGroupValues.Num(), *InCookedData.DebugName.ToString(), (uint32)InCookedData.EventId); + } + FWwiseResourceUnloadPromise UnloadPromise; + auto UnloadFuture = UnloadPromise.GetFuture(); + UnloadSwitchContainerLeafResources(MoveTemp(UnloadPromise), UsageCount->LoadedData, UsageCount->Key); + UnloadFuture.Next([UsageCount, SwitchContainerLeavesPromise = MoveTemp(SwitchContainerLeavesPromise)](int) mutable + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("Deleting Switch Container Leaf Usage Count @ %p"), &UsageCount->LoadedData); + delete UsageCount; + SwitchContainerLeavesPromise.EmplaceValue(); + }); + } + else + { + SwitchContainerLeavesPromise.EmplaceValue(); + } + }); + } + + WaitForFutures(MoveTemp(FutureArray), [Promise = MoveTemp(Promise), &LoadedRequiredGroupValues, &bLoadedSwitchContainerLeaves]() mutable + { + LoadedRequiredGroupValues.Empty(); + bLoadedSwitchContainerLeaves = false; + Promise.EmplaceValue(); + }); +} + +void FWwiseResourceLoaderImpl::UnloadExternalSourceResources(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedExternalSourceInfo::FLoadedData& LoadedData, const FWwiseExternalSourceCookedData& InCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("UnloadExternalSourceResources")); + LogUnloadResources(InCookedData); + + if (LoadedData.IsLoaded()) + { + UnloadExternalSourceFile(InCookedData, [ &LoadedData, Promise = MoveTemp(Promise)]() mutable + { + LoadedData.bLoaded = false; + Promise.EmplaceValue(); + }); + } + else + { + Promise.EmplaceValue(); + } +} + +void FWwiseResourceLoaderImpl::UnloadGroupValueResources(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedGroupValueInfo::FLoadedData& LoadedData, const FWwiseGroupValueCookedData& InCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("UnloadGroupValueResources")); + LogUnloadResources(InCookedData); + + SwitchContainerExecutionQueue.Async([this, &LoadedData, &InCookedData, Promise = MoveTemp(Promise)]() mutable + { + FWwiseSwitchContainerLoadedGroupValueInfo* Info = LoadedGroupValueInfo.Find(FWwiseSwitchContainerLoadedGroupValueInfo(InCookedData)); + if (UNLIKELY(!Info)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("Could not find requested GroupValue %s (%s %" PRIu32 ":%" PRIu32 ")"), + *InCookedData.DebugName.ToString(), *InCookedData.GetTypeName(), (uint32)InCookedData.GroupId, (uint32)InCookedData.Id); + return Promise.EmplaceValue(); + } + check(Info->ShouldBeLoaded()); + --Info->LoadCount; + + FCompletionFutureArray FutureArray; + + if (!Info->ShouldBeLoaded()) + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("Last GroupValue %s (%s %" PRIu32 ":%" PRIu32 ") unload. Unloading %d leaves."), + *InCookedData.DebugName.ToString(), *InCookedData.GetTypeName(), (uint32)InCookedData.GroupId, (uint32)InCookedData.Id, (int)Info->Leaves.Num()); + + for (auto* UsageCount : Info->Leaves) + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("Number of GroupValues required for a leaf: %d/%d @ %p for key %s (-1 in GroupValue)"), + (int)UsageCount->LoadedGroupValues.Num() - 1, UsageCount->Key.GroupValueSet.Num(), &UsageCount->LoadedData, *UsageCount->Key.GetDebugString()); + + check(UsageCount->LoadedGroupValues.Num() > 0); + check(UsageCount->LoadedData.IsLoaded() && UsageCount->HaveAllKeys() || !UsageCount->LoadedData.IsLoaded()); + + UsageCount->LoadedGroupValues.Remove(InCookedData); + + FWwiseResourceUnloadPromise UnloadPromise; + FutureArray.Add(UnloadPromise.GetFuture()); + UnloadSwitchContainerLeafResources(MoveTemp(UnloadPromise), UsageCount->LoadedData, UsageCount->Key); + } + } + else + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("GroupValue %s (%s %" PRIu32 ":%" PRIu32 ") still loaded (Count: %d times)."), + *InCookedData.DebugName.ToString(), *InCookedData.GetTypeName(), (uint32)InCookedData.GroupId, (uint32)InCookedData.Id, (int)Info->LoadCount); + } + + WaitForFutures(MoveTemp(FutureArray), [Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + }); +} + +void FWwiseResourceLoaderImpl::UnloadInitBankResources(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedInitBankInfo::FLoadedData& LoadedData, const FWwiseInitBankCookedData& InCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("UnloadInitBankResources")); + LogUnloadResources(InCookedData); + + auto& LoadedMedia = LoadedData.LoadedMedia; + + FCompletionPromise SoundBankPromise; + auto Future = SoundBankPromise.GetFuture(); + + if (LoadedData.bLoaded) + { + UnloadSoundBankFile(InCookedData, [&LoadedData, SoundBankPromise = MoveTemp(SoundBankPromise)]() mutable + { + LoadedData.bLoaded = false; + SoundBankPromise.EmplaceValue(); + }); + } + else + { + SoundBankPromise.EmplaceValue(); + } + + Future.Next([this, Promise = MoveTemp(Promise), &LoadedMedia](int) mutable + { + FCompletionFutureArray FutureArray; + AddUnloadMediaFutures(FutureArray, LoadedMedia); + WaitForFutures(MoveTemp(FutureArray), [Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + }); +} + +void FWwiseResourceLoaderImpl::UnloadMediaResources(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedMediaInfo::FLoadedData& LoadedData, const FWwiseMediaCookedData& InCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("UnloadMediaResources")); + LogUnloadResources(InCookedData); + + if (LoadedData.IsLoaded()) + { + UnloadMediaFile(InCookedData, [Promise = MoveTemp(Promise), &LoadedData]() mutable + { + LoadedData.bLoaded = false; + Promise.EmplaceValue(); + }); + } + else + { + Promise.EmplaceValue(); + } +} + +void FWwiseResourceLoaderImpl::UnloadShareSetResources(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedShareSetInfo::FLoadedData& LoadedData, const FWwiseShareSetCookedData& InCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("UnloadShareSetResources")); + LogUnloadResources(InCookedData); + + auto& LoadedSoundBanks = LoadedData.LoadedSoundBanks; + auto& LoadedMedia = LoadedData.LoadedMedia; + + FCompletionFutureArray FutureArray; + AddUnloadSoundBankFutures(FutureArray, LoadedSoundBanks); + WaitForFutures(MoveTemp(FutureArray), [this, Promise = MoveTemp(Promise), &LoadedMedia]() mutable + { + FCompletionFutureArray FutureArray; + AddUnloadMediaFutures(FutureArray, LoadedMedia); + WaitForFutures(MoveTemp(FutureArray), [Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + }); +} + +void FWwiseResourceLoaderImpl::UnloadSoundBankResources(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedSoundBankInfo::FLoadedData& LoadedData, const FWwiseSoundBankCookedData& InCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("UnloadSoundBankResources")); + LogUnloadResources(InCookedData); + + if (LoadedData.IsLoaded()) + { + UnloadSoundBankFile(InCookedData, [Promise = MoveTemp(Promise), &LoadedData]() mutable + { + LoadedData.bLoaded = false; + Promise.EmplaceValue(); + }); + } + else + { + Promise.EmplaceValue(); + } +} + +void FWwiseResourceLoaderImpl::UnloadSwitchContainerLeafResources(FWwiseResourceUnloadPromise&& Promise, FWwiseSwitchContainerLeafGroupValueUsageCount::FLoadedData& LoadedData, const FWwiseSwitchContainerLeafCookedData& InCookedData) +{ + SCOPED_WWISERESOURCELOADER_EVENT_2(TEXT("UnloadSwitchContainerLeafResources")); + LogUnloadResources(InCookedData, &LoadedData); + + auto& LoadedSoundBanks = LoadedData.LoadedSoundBanks; + auto& LoadedExternalSources = LoadedData.LoadedExternalSources; + auto& LoadedMedia = LoadedData.LoadedMedia; + + FCompletionFutureArray FutureArray; + AddUnloadSoundBankFutures(FutureArray, LoadedSoundBanks); + WaitForFutures(MoveTemp(FutureArray), [this, Promise = MoveTemp(Promise), &LoadedExternalSources, &LoadedMedia, &LoadedData]() mutable + { + FCompletionFutureArray FutureArray; + AddUnloadExternalSourceFutures(FutureArray, LoadedExternalSources); + AddUnloadMediaFutures(FutureArray, LoadedMedia); + WaitForFutures(MoveTemp(FutureArray), [Promise = MoveTemp(Promise), &LoadedData]() mutable + { + DEC_DWORD_STAT(STAT_WwiseResourceLoaderSwitchContainerCombinations); + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("Done unloading Switch Container Leaf @ %p"), &LoadedData); + Promise.EmplaceValue(); + }); + }); +} + + +FWwiseResourceLoaderImpl::FCompletionFuture FWwiseResourceLoaderImpl::AttachAuxBusNode(FWwiseLoadedAuxBus AuxBusListNode) +{ + FCompletionPromise Promise; + auto Future = Promise.GetFuture(); + LoadedListExecutionQueue.Async([this, AuxBusListNode = MoveTemp(AuxBusListNode), Promise = MoveTemp(Promise)]() mutable + { + LoadedAuxBusList.AddTail(AuxBusListNode); + INC_DWORD_STAT(STAT_WwiseResourceLoaderAuxBusses); + FFunctionGraphTask::CreateAndDispatchWhenReady([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + }); + return Future; +} + +FWwiseResourceLoaderImpl::FCompletionFuture FWwiseResourceLoaderImpl::AttachEventNode(FWwiseLoadedEvent EventListNode) +{ + FCompletionPromise Promise; + auto Future = Promise.GetFuture(); + LoadedListExecutionQueue.Async([this, EventListNode = MoveTemp(EventListNode), Promise = MoveTemp(Promise)]() mutable + { + LoadedEventList.AddTail(EventListNode); + INC_DWORD_STAT(STAT_WwiseResourceLoaderEvents); + FFunctionGraphTask::CreateAndDispatchWhenReady([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + }); + return Future; +} + +FWwiseResourceLoaderImpl::FCompletionFuture FWwiseResourceLoaderImpl::AttachExternalSourceNode(FWwiseLoadedExternalSource ExternalSourceListNode) +{ + FCompletionPromise Promise; + auto Future = Promise.GetFuture(); + LoadedListExecutionQueue.Async([this, ExternalSourceListNode = MoveTemp(ExternalSourceListNode), Promise = MoveTemp(Promise)]() mutable + { + LoadedExternalSourceList.AddTail(ExternalSourceListNode); + INC_DWORD_STAT(STAT_WwiseResourceLoaderExternalSources); + FFunctionGraphTask::CreateAndDispatchWhenReady([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + }); + return Future; +} + +FWwiseResourceLoaderImpl::FCompletionFuture FWwiseResourceLoaderImpl::AttachGroupValueNode(FWwiseLoadedGroupValue GroupValueListNode) +{ + FCompletionPromise Promise; + auto Future = Promise.GetFuture(); + LoadedListExecutionQueue.Async([this, GroupValueListNode = MoveTemp(GroupValueListNode), Promise = MoveTemp(Promise)]() mutable + { + LoadedGroupValueList.AddTail(GroupValueListNode); + INC_DWORD_STAT(STAT_WwiseResourceLoaderGroupValues); + FFunctionGraphTask::CreateAndDispatchWhenReady([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + }); + return Future; +} + +FWwiseResourceLoaderImpl::FCompletionFuture FWwiseResourceLoaderImpl::AttachInitBankNode(FWwiseLoadedInitBank InitBankListNode) +{ + FCompletionPromise Promise; + auto Future = Promise.GetFuture(); + LoadedListExecutionQueue.Async([this, InitBankListNode = MoveTemp(InitBankListNode), Promise = MoveTemp(Promise)]() mutable + { + LoadedInitBankList.AddTail(InitBankListNode); + INC_DWORD_STAT(STAT_WwiseResourceLoaderInitBanks); + FFunctionGraphTask::CreateAndDispatchWhenReady([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + }); + return Future; +} + +FWwiseResourceLoaderImpl::FCompletionFuture FWwiseResourceLoaderImpl::AttachMediaNode(FWwiseLoadedMedia MediaListNode) +{ + FCompletionPromise Promise; + auto Future = Promise.GetFuture(); + LoadedListExecutionQueue.Async([this, MediaListNode = MoveTemp(MediaListNode), Promise = MoveTemp(Promise)]() mutable + { + LoadedMediaList.AddTail(MediaListNode); + INC_DWORD_STAT(STAT_WwiseResourceLoaderMedia); + FFunctionGraphTask::CreateAndDispatchWhenReady([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + }); + return Future; +} + +FWwiseResourceLoaderImpl::FCompletionFuture FWwiseResourceLoaderImpl::AttachShareSetNode(FWwiseLoadedShareSet ShareSetListNode) +{ + FCompletionPromise Promise; + auto Future = Promise.GetFuture(); + LoadedListExecutionQueue.Async([this, ShareSetListNode = MoveTemp(ShareSetListNode), Promise = MoveTemp(Promise)]() mutable + { + LoadedShareSetList.AddTail(ShareSetListNode); + INC_DWORD_STAT(STAT_WwiseResourceLoaderShareSets); + FFunctionGraphTask::CreateAndDispatchWhenReady([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + }); + return Future; +} + +FWwiseResourceLoaderImpl::FCompletionFuture FWwiseResourceLoaderImpl::AttachSoundBankNode(FWwiseLoadedSoundBank SoundBankListNode) +{ + FCompletionPromise Promise; + auto Future = Promise.GetFuture(); + LoadedListExecutionQueue.Async([this, SoundBankListNode = MoveTemp(SoundBankListNode), Promise = MoveTemp(Promise)]() mutable + { + LoadedSoundBankList.AddTail(SoundBankListNode); + INC_DWORD_STAT(STAT_WwiseResourceLoaderSoundBanks); + FFunctionGraphTask::CreateAndDispatchWhenReady([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + }); + return Future; +} + + +FWwiseResourceLoaderImpl::FCompletionFuture FWwiseResourceLoaderImpl::DetachAuxBusNode(FWwiseLoadedAuxBus AuxBusListNode) +{ + FCompletionPromise Promise; + auto Future = Promise.GetFuture(); + LoadedListExecutionQueue.Async([this, AuxBusListNode = MoveTemp(AuxBusListNode), Promise = MoveTemp(Promise)]() mutable + { + LoadedAuxBusList.RemoveNode(AuxBusListNode, false); + DEC_DWORD_STAT(STAT_WwiseResourceLoaderAuxBusses); + FFunctionGraphTask::CreateAndDispatchWhenReady([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + }); + return Future; +} + +FWwiseResourceLoaderImpl::FCompletionFuture FWwiseResourceLoaderImpl::DetachEventNode(FWwiseLoadedEvent EventListNode) +{ + FCompletionPromise Promise; + auto Future = Promise.GetFuture(); + LoadedListExecutionQueue.Async([this, EventListNode = MoveTemp(EventListNode), Promise = MoveTemp(Promise)]() mutable + { + LoadedEventList.RemoveNode(EventListNode, false); + DEC_DWORD_STAT(STAT_WwiseResourceLoaderEvents); + FFunctionGraphTask::CreateAndDispatchWhenReady([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + }); + return Future; +} + +FWwiseResourceLoaderImpl::FCompletionFuture FWwiseResourceLoaderImpl::DetachExternalSourceNode(FWwiseLoadedExternalSource ExternalSourceListNode) +{ + FCompletionPromise Promise; + auto Future = Promise.GetFuture(); + LoadedListExecutionQueue.Async([this, ExternalSourceListNode = MoveTemp(ExternalSourceListNode), Promise = MoveTemp(Promise)]() mutable + { + LoadedExternalSourceList.RemoveNode(ExternalSourceListNode, false); + DEC_DWORD_STAT(STAT_WwiseResourceLoaderExternalSources); + FFunctionGraphTask::CreateAndDispatchWhenReady([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + }); + return Future; +} + +FWwiseResourceLoaderImpl::FCompletionFuture FWwiseResourceLoaderImpl::DetachGroupValueNode(FWwiseLoadedGroupValue GroupValueListNode) +{ + FCompletionPromise Promise; + auto Future = Promise.GetFuture(); + LoadedListExecutionQueue.Async([this, GroupValueListNode = MoveTemp(GroupValueListNode), Promise = MoveTemp(Promise)]() mutable + { + LoadedGroupValueList.RemoveNode(GroupValueListNode, false); + DEC_DWORD_STAT(STAT_WwiseResourceLoaderGroupValues); + FFunctionGraphTask::CreateAndDispatchWhenReady([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + }); + return Future; +} + +FWwiseResourceLoaderImpl::FCompletionFuture FWwiseResourceLoaderImpl::DetachInitBankNode(FWwiseLoadedInitBank InitBankListNode) +{ + FCompletionPromise Promise; + auto Future = Promise.GetFuture(); + LoadedListExecutionQueue.Async([this, InitBankListNode = MoveTemp(InitBankListNode), Promise = MoveTemp(Promise)]() mutable + { + LoadedInitBankList.RemoveNode(InitBankListNode, false); + DEC_DWORD_STAT(STAT_WwiseResourceLoaderInitBanks); + FFunctionGraphTask::CreateAndDispatchWhenReady([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + }); + return Future; +} + +FWwiseResourceLoaderImpl::FCompletionFuture FWwiseResourceLoaderImpl::DetachMediaNode(FWwiseLoadedMedia MediaListNode) +{ + FCompletionPromise Promise; + auto Future = Promise.GetFuture(); + LoadedListExecutionQueue.Async([this, MediaListNode = MoveTemp(MediaListNode), Promise = MoveTemp(Promise)]() mutable + { + LoadedMediaList.RemoveNode(MediaListNode, false); + DEC_DWORD_STAT(STAT_WwiseResourceLoaderMedia); + FFunctionGraphTask::CreateAndDispatchWhenReady([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + }); + return Future; +} + +FWwiseResourceLoaderImpl::FCompletionFuture FWwiseResourceLoaderImpl::DetachShareSetNode(FWwiseLoadedShareSet ShareSetListNode) +{ + FCompletionPromise Promise; + auto Future = Promise.GetFuture(); + LoadedListExecutionQueue.Async([this, ShareSetListNode = MoveTemp(ShareSetListNode), Promise = MoveTemp(Promise)]() mutable + { + LoadedShareSetList.RemoveNode(ShareSetListNode, false); + DEC_DWORD_STAT(STAT_WwiseResourceLoaderShareSets); + FFunctionGraphTask::CreateAndDispatchWhenReady([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + }); + return Future; +} + +FWwiseResourceLoaderImpl::FCompletionFuture FWwiseResourceLoaderImpl::DetachSoundBankNode(FWwiseLoadedSoundBank SoundBankListNode) +{ + FCompletionPromise Promise; + auto Future = Promise.GetFuture(); + LoadedListExecutionQueue.Async([this, SoundBankListNode = MoveTemp(SoundBankListNode), Promise = MoveTemp(Promise)]() mutable + { + LoadedSoundBankList.RemoveNode(SoundBankListNode, false); + DEC_DWORD_STAT(STAT_WwiseResourceLoaderSoundBanks); + FFunctionGraphTask::CreateAndDispatchWhenReady([Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + }); + return Future; +} + + +void FWwiseResourceLoaderImpl::AddLoadExternalSourceFutures(FCompletionFutureArray& FutureArray, TArray& LoadedExternalSources, + const TArray& InExternalSources, const TCHAR* InType, FName InDebugName, uint32 InShortId) const +{ + for (const auto& ExternalSource : InExternalSources) + { + TWwisePromise Promise; + FutureArray.Add(Promise.GetFuture()); + LoadExternalSourceFile(ExternalSource, [this, &ExternalSource, &LoadedExternalSources, InType, InDebugName, InShortId, Promise = MoveTemp(Promise)](bool bInResult) mutable + { + if (UNLIKELY(!bInResult)) + { + UE_CLOG(InDebugName.IsValid(), LogWwiseResourceLoader, Warning, TEXT("Load%sResources: Could not load External Source %s (%" PRIu32 ") for %s %s (%" PRIu32 ")"), + InType, + *ExternalSource.DebugName.ToString(), (uint32)ExternalSource.Cookie, + InType, *InDebugName.ToString(), (uint32)InShortId); + UE_CLOG(!InDebugName.IsValid(), LogWwiseResourceLoader, Warning, TEXT("Load%sResources: Could not load External Source %s (%" PRIu32 ") for %s"), + InType, + *ExternalSource.DebugName.ToString(), (uint32)ExternalSource.Cookie, + InType); + Promise.EmplaceValue(); + } + else + { + FileExecutionQueue.Async([&ExternalSource, &LoadedExternalSources, Promise = MoveTemp(Promise)]() mutable + { + LoadedExternalSources.Add(&ExternalSource); + Promise.EmplaceValue(); + }); + } + }); + } +} + +void FWwiseResourceLoaderImpl::AddUnloadExternalSourceFutures(FCompletionFutureArray& FutureArray, + TArray& LoadedExternalSources) const +{ + for (const auto* ExternalSource : LoadedExternalSources) + { + TWwisePromise Promise; + FutureArray.Add(Promise.GetFuture()); + UnloadExternalSourceFile(*ExternalSource, [Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + } + LoadedExternalSources.Empty(); +} + +void FWwiseResourceLoaderImpl::AddLoadMediaFutures(FCompletionFutureArray& FutureArray, TArray& LoadedMedia, + const TArray& InMedia, const TCHAR* InType, FName InDebugName, uint32 InShortId) const +{ + for (const auto& Media : InMedia) + { + TWwisePromise Promise; + FutureArray.Add(Promise.GetFuture()); + LoadMediaFile(Media, [this, &Media, &LoadedMedia, InType, InDebugName, InShortId, Promise = MoveTemp(Promise)](bool bInResult) mutable + { + if (UNLIKELY(!bInResult)) + { + UE_CLOG(InDebugName.IsValid(), LogWwiseResourceLoader, Warning, TEXT("Load%sResources: Could not load Media %s (%" PRIu32 ") for %s %s (%" PRIu32 ")"), + InType, + *Media.DebugName.ToString(), (uint32)Media.MediaId, + InType, *InDebugName.ToString(), (uint32)InShortId); + UE_CLOG(!InDebugName.IsValid(), LogWwiseResourceLoader, Warning, TEXT("Load%sResources: Could not load Media %s (%" PRIu32 ") for %s"), + InType, + *Media.DebugName.ToString(), (uint32)Media.MediaId, + InType); + Promise.EmplaceValue(); + } + else + { + FileExecutionQueue.Async([&Media, &LoadedMedia, Promise = MoveTemp(Promise)]() mutable + { + LoadedMedia.Add(&Media); + Promise.EmplaceValue(); + }); + } + }); + } +} + +void FWwiseResourceLoaderImpl::AddUnloadMediaFutures(FCompletionFutureArray& FutureArray, + TArray& LoadedMedia) const +{ + for (const auto* Media : LoadedMedia) + { + TWwisePromise Promise; + FutureArray.Add(Promise.GetFuture()); + UnloadMediaFile(*Media, [Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + } + LoadedMedia.Empty(); +} + +void FWwiseResourceLoaderImpl::AddLoadSoundBankFutures(FCompletionFutureArray& FutureArray, TArray& LoadedSoundBanks, + const TArray& InSoundBank, const TCHAR* InType, FName InDebugName, uint32 InShortId) const +{ + for (const auto& SoundBank : InSoundBank) + { + TWwisePromise Promise; + FutureArray.Add(Promise.GetFuture()); + LoadSoundBankFile(SoundBank, [this, &SoundBank, &LoadedSoundBanks, InType, InDebugName, InShortId, Promise = MoveTemp(Promise)](bool bInResult) mutable + { + if (UNLIKELY(!bInResult)) + { + UE_CLOG(InDebugName.IsValid(), LogWwiseResourceLoader, Warning, TEXT("Load%sResources: Could not load SoundBank %s (%" PRIu32 ") for %s %s (%" PRIu32 ")"), + InType, + *SoundBank.DebugName.ToString(), (uint32)SoundBank.SoundBankId, + InType, *InDebugName.ToString(), (uint32)InShortId); + UE_CLOG(!InDebugName.IsValid(), LogWwiseResourceLoader, Warning, TEXT("Load%sResources: Could not load SoundBank %s (%" PRIu32 ") for %s"), + InType, + *SoundBank.DebugName.ToString(), (uint32)SoundBank.SoundBankId, + InType); + Promise.EmplaceValue(); + } + else + { + FileExecutionQueue.Async([&SoundBank, &LoadedSoundBanks, Promise = MoveTemp(Promise)]() mutable + { + LoadedSoundBanks.Add(&SoundBank); + Promise.EmplaceValue(); + }); + } + }); + } +} + +void FWwiseResourceLoaderImpl::AddUnloadSoundBankFutures(FCompletionFutureArray& FutureArray, + TArray& LoadedSoundBanks) const +{ + for (const auto* SoundBank : LoadedSoundBanks) + { + TWwisePromise Promise; + FutureArray.Add(Promise.GetFuture()); + UnloadSoundBankFile(*SoundBank, [Promise = MoveTemp(Promise)]() mutable + { + Promise.EmplaceValue(); + }); + } + LoadedSoundBanks.Empty(); +} + +void FWwiseResourceLoaderImpl::WaitForFutures(FCompletionFutureArray&& FutureArray, FCompletionCallback&& Callback, int NextId) const +{ + { + SCOPED_WWISERESOURCELOADER_EVENT_4(TEXT("WaitForFutures")); + while (FutureArray.Num() > NextId) + { + auto Future = MoveTemp(FutureArray[NextId]); + if (Future.IsReady()) + { + ++NextId; + } + else + { + Future.Next([this, FutureArray = MoveTemp(FutureArray), Callback = MoveTemp(Callback), NextId = NextId + 1](int) mutable + { + WaitForFutures(MoveTemp(FutureArray), MoveTemp(Callback), NextId); + }); + return; + } + } + } + return Callback(); +} + +void FWwiseResourceLoaderImpl::LoadSoundBankFile(const FWwiseSoundBankCookedData& InSoundBank, FLoadFileCallback&& InCallback) const +{ + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("[LoadSoundBankAsync: %" PRIu32 "] %s at %s"), + (uint32)InSoundBank.SoundBankId, *InSoundBank.DebugName.ToString(), *InSoundBank.SoundBankPathName.ToString()); + + auto* SoundBankManager = IWwiseSoundBankManager::Get(); + if (UNLIKELY(!SoundBankManager)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("Failed to retrieve SoundBank Manager")); + InCallback(false); + return; + } + SoundBankManager->LoadSoundBank(InSoundBank, GetUnrealPath(), [&InSoundBank, InCallback = MoveTemp(InCallback)](bool bInResult) + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("[LoadSoundBankAsync: %" PRIu32 "] %s: Done."), + (uint32)InSoundBank.SoundBankId, *InSoundBank.DebugName.ToString()); + InCallback(bInResult); + }); +} + +void FWwiseResourceLoaderImpl::UnloadSoundBankFile(const FWwiseSoundBankCookedData& InSoundBank, FUnloadFileCallback&& InCallback) const +{ + auto Path = GetUnrealPath(InSoundBank.SoundBankPathName); + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("[UnloadSoundBankAsync: %" PRIu32 "] %s at %s"), + (uint32)InSoundBank.SoundBankId, *InSoundBank.DebugName.ToString(), *InSoundBank.SoundBankPathName.ToString()); + + auto* SoundBankManager = IWwiseSoundBankManager::Get(); + if (UNLIKELY(!SoundBankManager)) + { + UE_CLOG(!IsEngineExitRequested(), LogWwiseResourceLoader, Error, TEXT("Failed to retrieve SoundBank Manager")); + InCallback(); + return; + } + SoundBankManager->UnloadSoundBank(InSoundBank, GetUnrealPath(), [&InSoundBank, InCallback = MoveTemp(InCallback)]() + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("[UnloadSoundBankAsync: %" PRIu32 "] %s: Done."), + (uint32)InSoundBank.SoundBankId, *InSoundBank.DebugName.ToString()); + InCallback(); + }); +} + +void FWwiseResourceLoaderImpl::LoadMediaFile(const FWwiseMediaCookedData& InMedia, FLoadFileCallback&& InCallback) const +{ + auto Path = GetUnrealPath(InMedia.MediaPathName); + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("[LoadMediaAsync: %" PRIu32 "] %s at %s"), + (uint32)InMedia.MediaId, *InMedia.DebugName.ToString(), *InMedia.MediaPathName.ToString()); + + auto* MediaManager = IWwiseMediaManager::Get(); + if (UNLIKELY(!MediaManager)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("Failed to retrieve Media Manager")); + InCallback(false); + return; + } + + MediaManager->LoadMedia(InMedia, GetUnrealPath(), [&InMedia, InCallback = MoveTemp(InCallback)](bool bInResult) + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("[LoadMediaAsync: %" PRIu32 "] %s: Done."), + (uint32)InMedia.MediaId, *InMedia.DebugName.ToString()); + InCallback(bInResult); + }); +} + +void FWwiseResourceLoaderImpl::UnloadMediaFile(const FWwiseMediaCookedData& InMedia, FUnloadFileCallback&& InCallback) const +{ + auto Path = GetUnrealPath(InMedia.MediaPathName); + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("[UnloadMediaAsync: %" PRIu32 "] %s at %s"), + (uint32)InMedia.MediaId, *InMedia.DebugName.ToString(), *InMedia.MediaPathName.ToString()); + + + auto* MediaManager = IWwiseMediaManager::Get(); + if (UNLIKELY(!MediaManager)) + { + UE_CLOG(!IsEngineExitRequested(), LogWwiseResourceLoader, Error, TEXT("Failed to retrieve Media Manager")); + InCallback(); + return; + } + + MediaManager->UnloadMedia(InMedia, GetUnrealPath(), [&InMedia, InCallback = MoveTemp(InCallback)]() + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("[UnloadMediaAsync: %" PRIu32 "] %s: Done."), + (uint32)InMedia.MediaId, *InMedia.DebugName.ToString()); + InCallback(); + }); +} + +void FWwiseResourceLoaderImpl::LoadExternalSourceFile(const FWwiseExternalSourceCookedData& InExternalSource, FLoadFileCallback&& InCallback) const +{ + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("[LoadExternalSourceAsync: %" PRIu32 "] %s"), + (uint32)InExternalSource.Cookie, *InExternalSource.DebugName.ToString()); + + auto* ExternalSourceManager = IWwiseExternalSourceManager::Get(); + if (UNLIKELY(!ExternalSourceManager)) + { + UE_LOG(LogWwiseResourceLoader, Error, TEXT("Failed to retrieve External Source Manager")); + InCallback(false); + return; + } + + ExternalSourceManager->LoadExternalSource(InExternalSource, GetUnrealExternalSourcePath(), CurrentLanguage, [&InExternalSource, InCallback = MoveTemp(InCallback)](bool bInResult) + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("[LoadExternalSourceAsync: %" PRIu32 "] %s: Done."), + (uint32)InExternalSource.Cookie, *InExternalSource.DebugName.ToString()); + InCallback(bInResult); + }); +} + +void FWwiseResourceLoaderImpl::UnloadExternalSourceFile(const FWwiseExternalSourceCookedData& InExternalSource, FUnloadFileCallback&& InCallback) const +{ + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("[UnloadExternalSourceAsync: %" PRIu32 "] %s"), + (uint32)InExternalSource.Cookie, *InExternalSource.DebugName.ToString()); + + auto* ExternalSourceManager = IWwiseExternalSourceManager::Get(); + if (UNLIKELY(!ExternalSourceManager)) + { + UE_CLOG(!IsEngineExitRequested(), LogWwiseResourceLoader, Error, TEXT("Failed to retrieve External Source Manager")); + InCallback(); + return; + } + + ExternalSourceManager->UnloadExternalSource(InExternalSource, GetUnrealExternalSourcePath(), CurrentLanguage, [&InExternalSource, InCallback = MoveTemp(InCallback)]() + { + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("[UnloadExternalSourceAsync: %" PRIu32 "] %s: Done."), + (uint32)InExternalSource.Cookie, *InExternalSource.DebugName.ToString()); + InCallback(); + }); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/WwiseResourceLoaderModuleImpl.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/WwiseResourceLoaderModuleImpl.cpp new file mode 100644 index 0000000..87c3edf --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/WwiseResourceLoaderModuleImpl.cpp @@ -0,0 +1,67 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseResourceLoaderModuleImpl.h" +#include "Wwise/WwiseResourceLoaderImpl.h" + +IMPLEMENT_MODULE(FWwiseResourceLoaderModule, WwiseResourceLoader) + +FWwiseResourceLoader* FWwiseResourceLoaderModule::GetResourceLoader() +{ + Lock.ReadLock(); + if (LIKELY(ResourceLoader)) + { + Lock.ReadUnlock(); + } + else + { + Lock.ReadUnlock(); + Lock.WriteLock(); + if (LIKELY(!ResourceLoader)) + { + UE_LOG(LogWwiseResourceLoader, Display, TEXT("Initializing default Resource Loader.")); + ResourceLoader.Reset(InstantiateResourceLoader()); + } + Lock.WriteUnlock(); + } + return ResourceLoader.Get(); +} + +FWwiseResourceLoaderImpl* FWwiseResourceLoaderModule::InstantiateResourceLoaderImpl() +{ + SCOPED_WWISERESOURCELOADER_EVENT(TEXT("InstantiateResourceLoaderImpl")); + return new FWwiseResourceLoaderImpl; +} + +FWwiseResourceLoader* FWwiseResourceLoaderModule::InstantiateResourceLoader() +{ + SCOPED_WWISERESOURCELOADER_EVENT(TEXT("InstantiateResourceLoader")); + return new FWwiseResourceLoader; +} + +void FWwiseResourceLoaderModule::ShutdownModule() +{ + SCOPED_WWISERESOURCELOADER_EVENT(TEXT("ShutdownModule")); + Lock.WriteLock(); + if (ResourceLoader.IsValid()) + { + UE_LOG(LogWwiseResourceLoader, Display, TEXT("Shutting down default Resource Loader.")); + ResourceLoader.Reset(); + } + Lock.WriteUnlock(); + IWwiseResourceLoaderModule::ShutdownModule(); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/WwiseSharedLanguageId.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/WwiseSharedLanguageId.cpp new file mode 100644 index 0000000..9b91496 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Private/Wwise/WwiseSharedLanguageId.cpp @@ -0,0 +1,42 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseSharedLanguageId.h" + +const FWwiseSharedLanguageId FWwiseSharedLanguageId::Sfx(FWwiseLanguageId::Sfx, EWwiseLanguageRequirement::SFX); + +FWwiseSharedLanguageId::FWwiseSharedLanguageId(): + Language(new FWwiseLanguageId), + LanguageRequirement(EWwiseLanguageRequirement::IsOptional) +{ +} + +FWwiseSharedLanguageId::FWwiseSharedLanguageId(int32 InLanguageId, const FName& InLanguageName, EWwiseLanguageRequirement InLanguageRequirement) : + Language(new FWwiseLanguageId(InLanguageId, InLanguageName)), + LanguageRequirement(InLanguageRequirement) +{ +} + +FWwiseSharedLanguageId::FWwiseSharedLanguageId(const FWwiseLanguageId& InLanguageId, EWwiseLanguageRequirement InLanguageRequirement): + Language(new FWwiseLanguageId(InLanguageId)), + LanguageRequirement(InLanguageRequirement) +{ +} + +FWwiseSharedLanguageId::~FWwiseSharedLanguageId() +{ +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseAcousticTextureCookedData.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseAcousticTextureCookedData.h new file mode 100644 index 0000000..3554214 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseAcousticTextureCookedData.h @@ -0,0 +1,41 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "WwiseAcousticTextureCookedData.generated.h" + +USTRUCT(BlueprintType) +struct WWISERESOURCELOADER_API FWwiseAcousticTextureCookedData +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + int32 ShortId = 0; + + /** + * @brief Optional debug name. Can be empty in release, contain the name, or the full path of the asset. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + FName DebugName; + + FWwiseAcousticTextureCookedData(); + + void Serialize(FArchive& Ar); + + FString GetDebugString() const; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseAuxBusCookedData.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseAuxBusCookedData.h new file mode 100644 index 0000000..9be6b0b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseAuxBusCookedData.h @@ -0,0 +1,50 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/CookedData/WwiseSoundBankCookedData.h" +#include "Wwise/CookedData/WwiseMediaCookedData.h" + +#include "WwiseAuxBusCookedData.generated.h" + +USTRUCT(BlueprintType) +struct WWISERESOURCELOADER_API FWwiseAuxBusCookedData +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + int32 AuxBusId = 0; + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + TArray SoundBanks; + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + TArray Media; + + /** + * @brief Optional debug name. Can be empty in release, contain the name, or the full path of the asset. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + FName DebugName; + + FWwiseAuxBusCookedData(); + + void Serialize(FArchive& Ar); + + FString GetDebugString() const; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseEventCookedData.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseEventCookedData.h new file mode 100644 index 0000000..3253439 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseEventCookedData.h @@ -0,0 +1,69 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/CookedData/WwiseSwitchContainerLeafCookedData.h" + +#include "WwiseEventCookedData.generated.h" + + +UENUM(BlueprintType) +enum class EWwiseEventDestroyOptions : uint8 +{ + StopEventOnDestroy, + WaitForEventEnd +}; + +USTRUCT(BlueprintType) +struct WWISERESOURCELOADER_API FWwiseEventCookedData +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + int32 EventId = 0; + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + TArray SoundBanks; + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + TArray Media; + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + TArray ExternalSources; + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + TArray SwitchContainerLeaves; + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + TSet RequiredGroupValueSet; + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + EWwiseEventDestroyOptions DestroyOptions = EWwiseEventDestroyOptions::StopEventOnDestroy; + + /** + * @brief Optional debug name. Can be empty in release, contain the name, or the full path of the asset. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + FName DebugName; + + FWwiseEventCookedData(); + + void Serialize(FArchive& Ar); + + FString GetDebugString() const; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseGameParameterCookedData.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseGameParameterCookedData.h new file mode 100644 index 0000000..21d053a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseGameParameterCookedData.h @@ -0,0 +1,41 @@ + +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "WwiseGameParameterCookedData.generated.h" + +USTRUCT(BlueprintType) +struct WWISERESOURCELOADER_API FWwiseGameParameterCookedData +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + int32 ShortId = 0; + + /** + * @brief Optional debug name. Can be empty in release, contain the name, or the full path of the asset. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + FName DebugName; + + FWwiseGameParameterCookedData(); + void Serialize(FArchive& Ar); + + FString GetDebugString() const; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseGroupValueCookedData.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseGroupValueCookedData.h new file mode 100644 index 0000000..6120ff9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseGroupValueCookedData.h @@ -0,0 +1,99 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "WwiseGroupValueCookedData.generated.h" + +UENUM(BlueprintType) +enum class EWwiseGroupType : uint8 +{ + Switch, + State, + Unknown = (uint8)-1 +}; + +USTRUCT(BlueprintType) +struct WWISERESOURCELOADER_API FWwiseGroupValueCookedData +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + EWwiseGroupType Type = EWwiseGroupType::Unknown; + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + int32 GroupId = 0; + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + int32 Id = 0; + + /** + * @brief Optional debug name. Can be empty in release, contain the name, or the full path of the asset. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + FName DebugName; + + FWwiseGroupValueCookedData(); + + const FString& GetTypeName() const; + + friend bool operator==(const FWwiseGroupValueCookedData& InLhs, const FWwiseGroupValueCookedData& InRhs) + { + return InLhs.Type == InRhs.Type + && InLhs.GroupId == InRhs.GroupId + && InLhs.Id == InRhs.Id; + } + + friend bool operator!=(const FWwiseGroupValueCookedData& InLhs, const FWwiseGroupValueCookedData& InRhs) + { + return !(InLhs == InRhs); + } + + friend bool operator<(const FWwiseGroupValueCookedData& InLhs, const FWwiseGroupValueCookedData& InRhs) + { + return (InLhs.Type < InRhs.Type) + || (InLhs.Type == InRhs.Type && InLhs.GroupId < InRhs.GroupId) + || (InLhs.Type == InRhs.Type && InLhs.GroupId == InRhs.GroupId && InLhs.Id < InRhs.Id); + } + + friend bool operator<=(const FWwiseGroupValueCookedData& InLhs, const FWwiseGroupValueCookedData& InRhs) + { + return InLhs == InRhs || InLhs < InRhs; + } + + friend bool operator>(const FWwiseGroupValueCookedData& InLhs, const FWwiseGroupValueCookedData& InRhs) + { + return !(InLhs <= InRhs); + } + + friend bool operator>=(const FWwiseGroupValueCookedData& InLhs, const FWwiseGroupValueCookedData& InRhs) + { + return !(InLhs < InRhs); + } + + void Serialize(FArchive& Ar); + + FString GetDebugString() const; +}; + +inline uint32 GetTypeHash(const FWwiseGroupValueCookedData& InCookedData) +{ + return HashCombine(HashCombine( + GetTypeHash((uint8)InCookedData.Type), + GetTypeHash(InCookedData.GroupId)), + GetTypeHash(InCookedData.Id)); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseInitBankCookedData.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseInitBankCookedData.h new file mode 100644 index 0000000..1c02357 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseInitBankCookedData.h @@ -0,0 +1,42 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/CookedData/WwiseSoundBankCookedData.h" +#include "Wwise/CookedData/WwiseMediaCookedData.h" +#include "Wwise/CookedData/WwiseLanguageCookedData.h" + +#include "WwiseInitBankCookedData.generated.h" + +USTRUCT(BlueprintType) +struct WWISERESOURCELOADER_API FWwiseInitBankCookedData : public FWwiseSoundBankCookedData +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + TArray Media; + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + TArray Language; + + FWwiseInitBankCookedData(); + + void Serialize(FArchive& Ar); + + FString GetDebugString() const; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseLocalizedAuxBusCookedData.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseLocalizedAuxBusCookedData.h new file mode 100644 index 0000000..54f611d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseLocalizedAuxBusCookedData.h @@ -0,0 +1,45 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/CookedData/WwiseAuxBusCookedData.h" +#include "Wwise/CookedData/WwiseLanguageCookedData.h" + +#include "WwiseLocalizedAuxBusCookedData.generated.h" + +USTRUCT(BlueprintType) +struct WWISERESOURCELOADER_API FWwiseLocalizedAuxBusCookedData +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + TMap AuxBusLanguageMap; + + /** + * @brief Optional debug name. Can be empty in release, contain the name, or the full path of the asset. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + FName DebugName; + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + int32 AuxBusId = 0; + + FWwiseLocalizedAuxBusCookedData(); + + void Serialize(FArchive& Ar); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseLocalizedEventCookedData.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseLocalizedEventCookedData.h new file mode 100644 index 0000000..15eb14f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseLocalizedEventCookedData.h @@ -0,0 +1,48 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/CookedData/WwiseEventCookedData.h" +#include "Wwise/CookedData/WwiseLanguageCookedData.h" + +#include "WwiseLocalizedEventCookedData.generated.h" + +USTRUCT(BlueprintType) +struct WWISERESOURCELOADER_API FWwiseLocalizedEventCookedData +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + TMap EventLanguageMap; + + /** + * @brief Optional debug name. Can be empty in release, contain the name, or the full path of the asset. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + FName DebugName; + + /** + * @brief Short ID for the Event. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + int32 EventId = 0; + + FWwiseLocalizedEventCookedData(); + + void Serialize(FArchive& Ar); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseLocalizedShareSetCookedData.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseLocalizedShareSetCookedData.h new file mode 100644 index 0000000..ef55867 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseLocalizedShareSetCookedData.h @@ -0,0 +1,48 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/CookedData/WwiseShareSetCookedData.h" +#include "Wwise/CookedData/WwiseLanguageCookedData.h" + +#include "WwiseLocalizedShareSetCookedData.generated.h" + +USTRUCT(BlueprintType) +struct WWISERESOURCELOADER_API FWwiseLocalizedShareSetCookedData +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + TMap ShareSetLanguageMap; + + /** + * @brief Optional debug name. Can be empty in release, contain the name, or the full path of the asset. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + FName DebugName; + + /** + * @brief Short ID for the Shareset. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + int32 ShareSetId = 0; + + FWwiseLocalizedShareSetCookedData(); + + void Serialize(FArchive& Ar); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseLocalizedSoundBankCookedData.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseLocalizedSoundBankCookedData.h new file mode 100644 index 0000000..4c15d74 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseLocalizedSoundBankCookedData.h @@ -0,0 +1,45 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/CookedData/WwiseSoundBankCookedData.h" +#include "Wwise/CookedData/WwiseLanguageCookedData.h" + +#include "WwiseLocalizedSoundBankCookedData.generated.h" + +USTRUCT(BlueprintType) +struct WWISERESOURCELOADER_API FWwiseLocalizedSoundBankCookedData +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + TMap SoundBankLanguageMap; + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + FName DebugName; + + /** + * @brief Short ID for the SoundBank. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + int32 SoundBankId = 0; + + FWwiseLocalizedSoundBankCookedData(); + + void Serialize(FArchive& Ar); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseShareSetCookedData.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseShareSetCookedData.h new file mode 100644 index 0000000..9f15ac1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseShareSetCookedData.h @@ -0,0 +1,49 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/CookedData/WwiseSoundBankCookedData.h" +#include "Wwise/CookedData/WwiseMediaCookedData.h" + +#include "WwiseShareSetCookedData.generated.h" + +USTRUCT(BlueprintType) +struct WWISERESOURCELOADER_API FWwiseShareSetCookedData +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + int32 ShareSetId = 0; + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + TArray SoundBanks; + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + TArray Media; + + /** + * @brief Optional debug name. Can be empty in release, contain the name, or the full path of the asset. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + FName DebugName; + + FWwiseShareSetCookedData(); + void Serialize(FArchive& Ar); + + FString GetDebugString() const; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseSwitchContainerLeafCookedData.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseSwitchContainerLeafCookedData.h new file mode 100644 index 0000000..5f11171 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseSwitchContainerLeafCookedData.h @@ -0,0 +1,51 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/CookedData/WwiseSoundBankCookedData.h" +#include "Wwise/CookedData/WwiseExternalSourceCookedData.h" +#include "Wwise/CookedData/WwiseGroupValueCookedData.h" +#include "Wwise/CookedData/WwiseMediaCookedData.h" + +#include "WwiseSwitchContainerLeafCookedData.generated.h" + +USTRUCT(BlueprintType) +struct WWISERESOURCELOADER_API FWwiseSwitchContainerLeafCookedData +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + TSet GroupValueSet; + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + TArray SoundBanks; + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + TArray Media; + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + TArray ExternalSources; + + FWwiseSwitchContainerLeafCookedData(); + + void Serialize(FArchive& Ar); + + bool operator==(const FWwiseSwitchContainerLeafCookedData& Rhs) const; + + FString GetDebugString() const; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseTriggerCookedData.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseTriggerCookedData.h new file mode 100644 index 0000000..6ac76c7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/CookedData/WwiseTriggerCookedData.h @@ -0,0 +1,41 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "WwiseTriggerCookedData.generated.h" + +USTRUCT(BlueprintType) +struct WWISERESOURCELOADER_API FWwiseTriggerCookedData +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + int32 TriggerId = 0; + + /** + * @brief Optional debug name. Can be empty in release, contain the name, or the full path of the asset. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + FName DebugName; + + FWwiseTriggerCookedData(); + + void Serialize(FArchive& Ar); + + FString GetDebugString() const; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Info/WwiseEventInfo.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Info/WwiseEventInfo.h new file mode 100644 index 0000000..269baa0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Info/WwiseEventInfo.h @@ -0,0 +1,105 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "WwiseObjectInfo.h" +#include "Wwise/CookedData/WwiseEventCookedData.h" + +#include "WwiseEventInfo.generated.h" + +UENUM(BlueprintType) +enum class EWwiseEventSwitchContainerLoading : uint8 +{ + AlwaysLoad UMETA(DisplayName = "Always Load Media"), + LoadOnReference UMETA(DisplayName = "Load Media Only When Referenced") +}; + +USTRUCT(BlueprintType, Meta = (Category = "Wwise", DisplayName = "Event Info", HasNativeMake = "/Script/WwiseResourceLoader.WwiseEventInfoLibrary:MakeStruct", HasNativeBreak = "/Script/WwiseResourceLoader.WwiseEventInfoLibrary:BreakStruct")) +struct WWISERESOURCELOADER_API FWwiseEventInfo: public FWwiseObjectInfo +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, Category = "Info") + EWwiseEventSwitchContainerLoading SwitchContainerLoading = EWwiseEventSwitchContainerLoading::AlwaysLoad; + + UPROPERTY(EditAnywhere, Category = "Info") + EWwiseEventDestroyOptions DestroyOptions = EWwiseEventDestroyOptions::StopEventOnDestroy; + + FWwiseEventInfo() : + FWwiseObjectInfo(), + SwitchContainerLoading(EWwiseEventSwitchContainerLoading::AlwaysLoad), + DestroyOptions(EWwiseEventDestroyOptions::StopEventOnDestroy) + {} + + FWwiseEventInfo(const FWwiseEventInfo& InEventInfo): + FWwiseObjectInfo(InEventInfo), + SwitchContainerLoading(InEventInfo.SwitchContainerLoading), + DestroyOptions(InEventInfo.DestroyOptions) + {} + + FWwiseEventInfo( + const FGuid& InWwiseGuid, + int32 InWwiseShortId, + const FString& InWwiseName, + EWwiseEventSwitchContainerLoading InSwitchContainerLoading = EWwiseEventSwitchContainerLoading::AlwaysLoad, + EWwiseEventDestroyOptions InDestroyOptions = EWwiseEventDestroyOptions::StopEventOnDestroy, + uint32 InHardCodedSoundBankShortId = 0) : + FWwiseObjectInfo(InWwiseGuid, InWwiseShortId, InWwiseName), + SwitchContainerLoading(InSwitchContainerLoading), + DestroyOptions(InDestroyOptions) + {} + + FWwiseEventInfo( + const FGuid& InWwiseGuid, + int32 InWwiseShortId, + const FName& InWwiseName, + EWwiseEventSwitchContainerLoading InSwitchContainerLoading = EWwiseEventSwitchContainerLoading::AlwaysLoad, + EWwiseEventDestroyOptions InDestroyOptions = EWwiseEventDestroyOptions::StopEventOnDestroy, + uint32 InHardCodedSoundBankShortId = 0) : + FWwiseObjectInfo(InWwiseGuid, InWwiseShortId, InWwiseName), + SwitchContainerLoading(InSwitchContainerLoading), + DestroyOptions(InDestroyOptions) + {} + + FWwiseEventInfo(uint32 InWwiseShortId, const FString& InWwiseName) : + FWwiseObjectInfo(InWwiseShortId, InWwiseName), + SwitchContainerLoading(EWwiseEventSwitchContainerLoading::AlwaysLoad), + DestroyOptions(EWwiseEventDestroyOptions::StopEventOnDestroy) + {} + + FWwiseEventInfo(uint32 InWwiseShortId, const FName& InWwiseName) : + FWwiseObjectInfo(InWwiseShortId, InWwiseName), + SwitchContainerLoading(EWwiseEventSwitchContainerLoading::AlwaysLoad), + DestroyOptions(EWwiseEventDestroyOptions::StopEventOnDestroy) + {} + + FWwiseEventInfo(uint32 InWwiseShortId) : + FWwiseObjectInfo(InWwiseShortId), + SwitchContainerLoading(EWwiseEventSwitchContainerLoading::AlwaysLoad), + DestroyOptions(EWwiseEventDestroyOptions::StopEventOnDestroy) + {} +}; + +inline uint32 GetTypeHash(const FWwiseEventInfo& InValue) +{ + return HashCombine(HashCombine(HashCombine( + GetTypeHash(InValue.WwiseGuid), + GetTypeHash(InValue.WwiseShortId)), + GetTypeHash(InValue.WwiseName)), + GetTypeHash(InValue.HardCodedSoundBankShortId)); +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Info/WwiseGroupValueInfo.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Info/WwiseGroupValueInfo.h new file mode 100644 index 0000000..b03153b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Info/WwiseGroupValueInfo.h @@ -0,0 +1,82 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "Wwise/Info/WwiseObjectInfo.h" +#include "WwiseGroupValueInfo.generated.h" + +USTRUCT(BlueprintType, Meta = (Category = "Wwise", DisplayName = "GroupValue Info", HasNativeMake = "/Script/WwiseResourceLoader.WwiseGroupValueInfoLibrary:MakeStruct", HasNativeBreak = "/Script/WwiseResourceLoader.WwiseGroupValueInfoLibrary:BreakStruct")) +struct WWISERESOURCELOADER_API FWwiseGroupValueInfo: public FWwiseObjectInfo +{ + GENERATED_BODY() + + UPROPERTY(EditAnyWhere, Category = "Info") + uint32 GroupShortId = 0; + + FWwiseGroupValueInfo() : + FWwiseObjectInfo(), + GroupShortId(0) + {} + + FWwiseGroupValueInfo(const FWwiseGroupValueInfo& InGroupValueInfo) : + FWwiseObjectInfo(InGroupValueInfo), + GroupShortId(InGroupValueInfo.GroupShortId) + {} + + FWwiseGroupValueInfo(const FGuid& InWwiseGuid, uint32 InGroupShortId, uint32 InWwiseShortId, const FString& InWwiseName) : + FWwiseObjectInfo(InWwiseGuid, InGroupShortId, InWwiseName), + GroupShortId(InGroupShortId) + {} + + FWwiseGroupValueInfo(const FGuid& InWwiseGuid, uint32 InGroupShortId, uint32 InWwiseShortId, const FName& InWwiseName) : + FWwiseObjectInfo(InWwiseGuid, InGroupShortId, InWwiseName), + GroupShortId(InGroupShortId) + {} + + FWwiseGroupValueInfo(uint32 InGroupShortId, uint32 InWwiseShortId, const FString& InWwiseName) : + FWwiseObjectInfo(InWwiseShortId, InWwiseName), + GroupShortId(InGroupShortId) + {} + + FWwiseGroupValueInfo(uint32 InGroupShortId, uint32 InWwiseShortId, const FName& InWwiseName) : + FWwiseObjectInfo(InWwiseShortId, InWwiseName), + GroupShortId(InGroupShortId) + {} + + bool operator==(const FWwiseGroupValueInfo& Rhs) const + { + return (!WwiseGuid.IsValid() || !Rhs.WwiseGuid.IsValid() || WwiseGuid == Rhs.WwiseGuid) && + ((GroupShortId == 0 && WwiseShortId == 0) || (Rhs.GroupShortId == 0 && Rhs.WwiseShortId == 0) || (GroupShortId == Rhs.GroupShortId && WwiseShortId == Rhs.WwiseShortId)) && + (WwiseName.IsNone() || Rhs.WwiseName.IsNone() || WwiseName == Rhs.WwiseName); + } + + bool operator!=(const FWwiseGroupValueInfo& Rhs) const + { + return !operator==(Rhs); + } +}; + +inline uint32 GetTypeHash(const FWwiseGroupValueInfo& InValue) +{ + return HashCombine(HashCombine(HashCombine( + GetTypeHash(InValue.WwiseGuid), + GetTypeHash(InValue.GroupShortId)), + GetTypeHash(InValue.WwiseShortId)), + GetTypeHash(InValue.WwiseName)); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Info/WwiseObjectInfo.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Info/WwiseObjectInfo.h new file mode 100644 index 0000000..532dc8e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Info/WwiseObjectInfo.h @@ -0,0 +1,101 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#include "CoreMinimal.h" + +#include "WwiseObjectInfo.generated.h" + +USTRUCT(BlueprintType, Meta = (Category = "Wwise", DisplayName = "Wwise Object Info", HasNativeMake = "/Script/WwiseResourceLoader.WwiseObjectInfoLibrary:MakeStruct", HasNativeBreak = "/Script/WwiseResourceLoader.WwiseObjectInfoLibrary:BreakStruct")) +struct WWISERESOURCELOADER_API FWwiseObjectInfo +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, Category = "Info") + FGuid WwiseGuid; + + UPROPERTY(EditAnywhere, Category = "Info") + uint32 WwiseShortId = 0; + + UPROPERTY(EditAnywhere, Category = "Info") + FName WwiseName; + + UPROPERTY(EditAnywhere, Category = "Info") + uint32 HardCodedSoundBankShortId = 0; + + FWwiseObjectInfo() + {} + + FWwiseObjectInfo(const FWwiseObjectInfo& InWwiseObjectInfo) : + WwiseGuid(InWwiseObjectInfo.WwiseGuid), + WwiseShortId(InWwiseObjectInfo.WwiseShortId), + WwiseName(InWwiseObjectInfo.WwiseName), + HardCodedSoundBankShortId(InWwiseObjectInfo.HardCodedSoundBankShortId) + {} + + FWwiseObjectInfo(const FGuid& InWwiseGuid, uint32 InWwiseShortId, const FName& InWwiseName, uint32 InHardCodedSoundBankShortId = 0) : + WwiseGuid(InWwiseGuid), + WwiseShortId(InWwiseShortId), + WwiseName(InWwiseName), + HardCodedSoundBankShortId(InHardCodedSoundBankShortId) + {} + + FWwiseObjectInfo(const FGuid& InWwiseGuid, uint32 InWwiseShortId, const FString& InWwiseName, uint32 InHardCodedSoundBankShortId = 0) : + WwiseGuid(InWwiseGuid), + WwiseShortId(InWwiseShortId), + WwiseName(FName(InWwiseName)), + HardCodedSoundBankShortId(InHardCodedSoundBankShortId) + {} + + FWwiseObjectInfo(uint32 InWwiseShortId, const FName& InWwiseName) : + WwiseShortId(InWwiseShortId), + WwiseName(InWwiseName) + {} + + FWwiseObjectInfo(uint32 InWwiseShortId, const FString& InWwiseName) : + WwiseShortId(InWwiseShortId), + WwiseName(FName(InWwiseName)) + {} + + FWwiseObjectInfo(uint32 InWwiseShortId) : + WwiseShortId(InWwiseShortId) + {} + + static const FWwiseObjectInfo DefaultInitBank; + + bool operator==(const FWwiseObjectInfo& Rhs) const + { + return (!WwiseGuid.IsValid() || !Rhs.WwiseGuid.IsValid() || WwiseGuid == Rhs.WwiseGuid) && + (WwiseShortId == 0 || Rhs.WwiseShortId == 0 || WwiseShortId == Rhs.WwiseShortId) && + (WwiseName.IsNone() || Rhs.WwiseName.IsNone() || WwiseName == Rhs.WwiseName) + && HardCodedSoundBankShortId == Rhs.HardCodedSoundBankShortId; + } + + bool operator!=(const FWwiseObjectInfo& Rhs) const + { + return !operator==(Rhs); + } +}; + +inline uint32 GetTypeHash(const FWwiseObjectInfo& InValue) +{ + return HashCombine(HashCombine(HashCombine( + GetTypeHash(InValue.WwiseGuid), + GetTypeHash(InValue.WwiseShortId)), + GetTypeHash(InValue.WwiseName)), + GetTypeHash(InValue.HardCodedSoundBankShortId)); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedAuxBus.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedAuxBus.h new file mode 100644 index 0000000..ae422d8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedAuxBus.h @@ -0,0 +1,55 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/CookedData/WwiseLocalizedAuxBusCookedData.h" +#include "CoreMinimal.h" +#include "Wwise/WwiseFuture.h" + +struct WWISERESOURCELOADER_API FWwiseLoadedAuxBusInfo +{ + FWwiseLoadedAuxBusInfo(const FWwiseLocalizedAuxBusCookedData& InAuxBus, const FWwiseLanguageCookedData& InLanguage); + FWwiseLoadedAuxBusInfo& operator=(const FWwiseLoadedAuxBusInfo&) = delete; + + const FWwiseLocalizedAuxBusCookedData LocalizedAuxBusCookedData; + FWwiseLanguageCookedData LanguageRef; + + struct WWISERESOURCELOADER_API FLoadedData + { + FLoadedData() {} + FLoadedData(const FLoadedData&) = delete; + FLoadedData& operator=(const FLoadedData&) = delete; + + TArray LoadedSoundBanks; + TArray LoadedMedia; + + bool IsLoaded() const; + } LoadedData; + + FString GetDebugString() const; + +private: + friend class TDoubleLinkedList::TDoubleLinkedListNode; + FWwiseLoadedAuxBusInfo(const FWwiseLoadedAuxBusInfo& InOriginal); +}; + +using FWwiseLoadedAuxBusList = TDoubleLinkedList; +using FWwiseLoadedAuxBusListNode = FWwiseLoadedAuxBusList::TDoubleLinkedListNode; +using FWwiseLoadedAuxBus = FWwiseLoadedAuxBusListNode*; +using FWwiseLoadedAuxBusPromise = TWwisePromise; +using FWwiseLoadedAuxBusFuture = TWwiseFuture; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedEvent.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedEvent.h new file mode 100644 index 0000000..70bfe61 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedEvent.h @@ -0,0 +1,58 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/CookedData/WwiseLocalizedEventCookedData.h" +#include "Wwise/Loaded/WwiseLoadedGroupValue.h" + +struct WWISERESOURCELOADER_API FWwiseLoadedEventInfo +{ + FWwiseLoadedEventInfo(const FWwiseLocalizedEventCookedData& InEvent, const FWwiseLanguageCookedData& InLanguage); + FWwiseLoadedEventInfo& operator=(const FWwiseLoadedEventInfo&) = delete; + + const FWwiseLocalizedEventCookedData LocalizedEventCookedData; + FWwiseLanguageCookedData LanguageRef; + + struct WWISERESOURCELOADER_API FLoadedData + { + FLoadedData() {} + FLoadedData(const FLoadedData&) = delete; + FLoadedData& operator=(const FLoadedData&) = delete; + + TArray LoadedSoundBanks; + TArray LoadedExternalSources; + TArray LoadedMedia; + + FWwiseLoadedGroupValueList LoadedRequiredGroupValues; + bool bLoadedSwitchContainerLeaves = false; + + bool IsLoaded() const; + } LoadedData; + + FString GetDebugString() const; + +private: + friend class TDoubleLinkedList::TDoubleLinkedListNode; + FWwiseLoadedEventInfo(const FWwiseLoadedEventInfo& InOriginal); +}; + +using FWwiseLoadedEventList = TDoubleLinkedList; +using FWwiseLoadedEventListNode = FWwiseLoadedEventList::TDoubleLinkedListNode; +using FWwiseLoadedEvent = FWwiseLoadedEventListNode*; +using FWwiseLoadedEventPromise = TWwisePromise; +using FWwiseLoadedEventFuture = TWwiseFuture; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedExternalSource.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedExternalSource.h new file mode 100644 index 0000000..29958d5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedExternalSource.h @@ -0,0 +1,53 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/CookedData/WwiseExternalSourceCookedData.h" +#include "CoreMinimal.h" +#include "Wwise/WwiseFuture.h" + +struct WWISERESOURCELOADER_API FWwiseLoadedExternalSourceInfo +{ + FWwiseLoadedExternalSourceInfo(const FWwiseExternalSourceCookedData& InExternalSource); + FWwiseLoadedExternalSourceInfo& operator=(const FWwiseLoadedExternalSourceInfo&) = delete; + + const FWwiseExternalSourceCookedData ExternalSourceCookedData; + + struct WWISERESOURCELOADER_API FLoadedData + { + FLoadedData() {} + FLoadedData(const FLoadedData&) = delete; + FLoadedData& operator=(const FLoadedData&) = delete; + + bool bLoaded = false; + + bool IsLoaded() const; + } LoadedData; + + FString GetDebugString() const; + +private: + friend class TDoubleLinkedList::TDoubleLinkedListNode; + FWwiseLoadedExternalSourceInfo(const FWwiseLoadedExternalSourceInfo& InOriginal); +}; + +using FWwiseLoadedExternalSourceList = TDoubleLinkedList; +using FWwiseLoadedExternalSourceListNode = FWwiseLoadedExternalSourceList::TDoubleLinkedListNode; +using FWwiseLoadedExternalSource = FWwiseLoadedExternalSourceListNode*; +using FWwiseLoadedExternalSourcePromise = TWwisePromise; +using FWwiseLoadedExternalSourceFuture = TWwiseFuture; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedGroupValue.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedGroupValue.h new file mode 100644 index 0000000..abf268e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedGroupValue.h @@ -0,0 +1,53 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/CookedData/WwiseGroupValueCookedData.h" +#include "CoreMinimal.h" +#include "Wwise/WwiseFuture.h" + +struct WWISERESOURCELOADER_API FWwiseLoadedGroupValueInfo +{ + FWwiseLoadedGroupValueInfo(const FWwiseGroupValueCookedData& InGroupValue); + FWwiseLoadedGroupValueInfo& operator=(const FWwiseLoadedGroupValueInfo&) = delete; + + const FWwiseGroupValueCookedData GroupValueCookedData; + + struct WWISERESOURCELOADER_API FLoadedData + { + FLoadedData() {} + FLoadedData(const FLoadedData&) = delete; + FLoadedData& operator=(const FLoadedData&) = delete; + + bool bLoaded = false; + + bool IsLoaded() const; + } LoadedData; + + FString GetDebugString() const; + +private: + friend class TDoubleLinkedList::TDoubleLinkedListNode; + FWwiseLoadedGroupValueInfo(const FWwiseLoadedGroupValueInfo& InOriginal); +}; + +using FWwiseLoadedGroupValueList = TDoubleLinkedList; +using FWwiseLoadedGroupValueListNode = FWwiseLoadedGroupValueList::TDoubleLinkedListNode; +using FWwiseLoadedGroupValue = FWwiseLoadedGroupValueListNode*; +using FWwiseLoadedGroupValuePromise = TWwisePromise; +using FWwiseLoadedGroupValueFuture = TWwiseFuture; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedInitBank.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedInitBank.h new file mode 100644 index 0000000..6237af5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedInitBank.h @@ -0,0 +1,54 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/CookedData/WwiseInitBankCookedData.h" +#include "CoreMinimal.h" +#include "Wwise/WwiseFuture.h" + +struct WWISERESOURCELOADER_API FWwiseLoadedInitBankInfo +{ + FWwiseLoadedInitBankInfo(const FWwiseInitBankCookedData& InInitBank); + FWwiseLoadedInitBankInfo& operator=(const FWwiseLoadedInitBankInfo&) = delete; + + const FWwiseInitBankCookedData InitBankCookedData; + + struct WWISERESOURCELOADER_API FLoadedData + { + FLoadedData() {} + FLoadedData(const FLoadedData&) = delete; + FLoadedData& operator=(const FLoadedData&) = delete; + + bool bLoaded = false; + TArray LoadedMedia; + + bool IsLoaded() const; + } LoadedData; + + FString GetDebugString() const; + +private: + friend class TDoubleLinkedList::TDoubleLinkedListNode; + FWwiseLoadedInitBankInfo(const FWwiseLoadedInitBankInfo& InOriginal); +}; + +using FWwiseLoadedInitBankList = TDoubleLinkedList; +using FWwiseLoadedInitBankListNode = FWwiseLoadedInitBankList::TDoubleLinkedListNode; +using FWwiseLoadedInitBank = FWwiseLoadedInitBankListNode*; +using FWwiseLoadedInitBankPromise = TWwisePromise; +using FWwiseLoadedInitBankFuture = TWwiseFuture; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedMedia.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedMedia.h new file mode 100644 index 0000000..5b99bf2 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedMedia.h @@ -0,0 +1,53 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/CookedData/WwiseMediaCookedData.h" +#include "CoreMinimal.h" +#include "Wwise/WwiseFuture.h" + +struct WWISERESOURCELOADER_API FWwiseLoadedMediaInfo +{ + FWwiseLoadedMediaInfo(const FWwiseMediaCookedData& InMedia); + FWwiseLoadedMediaInfo& operator=(const FWwiseLoadedMediaInfo&) = delete; + + const FWwiseMediaCookedData MediaCookedData; + + struct WWISERESOURCELOADER_API FLoadedData + { + FLoadedData() {} + FLoadedData(const FLoadedData&) = delete; + FLoadedData& operator=(const FLoadedData&) = delete; + + bool bLoaded = false; + + bool IsLoaded() const; + } LoadedData; + + FString GetDebugString() const; + +private: + friend class TDoubleLinkedList::TDoubleLinkedListNode; + FWwiseLoadedMediaInfo(const FWwiseLoadedMediaInfo& InOriginal); +}; + +using FWwiseLoadedMediaList = TDoubleLinkedList; +using FWwiseLoadedMediaListNode = FWwiseLoadedMediaList::TDoubleLinkedListNode; +using FWwiseLoadedMedia = FWwiseLoadedMediaListNode*; +using FWwiseLoadedMediaPromise = TWwisePromise; +using FWwiseLoadedMediaFuture = TWwiseFuture; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedShareSet.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedShareSet.h new file mode 100644 index 0000000..ad1ca88 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedShareSet.h @@ -0,0 +1,55 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/CookedData/WwiseLocalizedShareSetCookedData.h" +#include "CoreMinimal.h" +#include "Wwise/WwiseFuture.h" + +struct WWISERESOURCELOADER_API FWwiseLoadedShareSetInfo +{ + FWwiseLoadedShareSetInfo(const FWwiseLocalizedShareSetCookedData& InShareSet, const FWwiseLanguageCookedData& InLanguage); + FWwiseLoadedShareSetInfo& operator=(const FWwiseLoadedShareSetInfo&) = delete; + + const FWwiseLocalizedShareSetCookedData LocalizedShareSetCookedData; + FWwiseLanguageCookedData LanguageRef; + + struct WWISERESOURCELOADER_API FLoadedData + { + FLoadedData() {} + FLoadedData(const FLoadedData&) = delete; + FLoadedData& operator=(const FLoadedData&) = delete; + + TArray LoadedSoundBanks; + TArray LoadedMedia; + + bool IsLoaded() const; + } LoadedData; + + FString GetDebugString() const; + +private: + friend class TDoubleLinkedList::TDoubleLinkedListNode; + FWwiseLoadedShareSetInfo(const FWwiseLoadedShareSetInfo& InOriginal); +}; + +using FWwiseLoadedShareSetList = TDoubleLinkedList; +using FWwiseLoadedShareSetListNode = FWwiseLoadedShareSetList::TDoubleLinkedListNode; +using FWwiseLoadedShareSet = FWwiseLoadedShareSetListNode*; +using FWwiseLoadedShareSetPromise = TWwisePromise; +using FWwiseLoadedShareSetFuture = TWwiseFuture; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedSoundBank.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedSoundBank.h new file mode 100644 index 0000000..bc48a24 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Loaded/WwiseLoadedSoundBank.h @@ -0,0 +1,54 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/CookedData/WwiseLocalizedSoundBankCookedData.h" +#include "CoreMinimal.h" +#include "Wwise/WwiseFuture.h" + +struct WWISERESOURCELOADER_API FWwiseLoadedSoundBankInfo +{ + FWwiseLoadedSoundBankInfo(const FWwiseLocalizedSoundBankCookedData& InSoundBank, const FWwiseLanguageCookedData& InLanguage); + FWwiseLoadedSoundBankInfo& operator=(const FWwiseLoadedSoundBankInfo&) = delete; + + const FWwiseLocalizedSoundBankCookedData LocalizedSoundBankCookedData; + FWwiseLanguageCookedData LanguageRef; + + struct WWISERESOURCELOADER_API FLoadedData + { + FLoadedData() {} + FLoadedData(const FLoadedData&) = delete; + FLoadedData& operator=(const FLoadedData&) = delete; + + bool bLoaded = false; + + bool IsLoaded() const; + } LoadedData; + + FString GetDebugString() const; + +private: + friend class TDoubleLinkedList::TDoubleLinkedListNode; + FWwiseLoadedSoundBankInfo(const FWwiseLoadedSoundBankInfo& InOriginal); +}; + +using FWwiseLoadedSoundBankList = TDoubleLinkedList; +using FWwiseLoadedSoundBankListNode = FWwiseLoadedSoundBankList::TDoubleLinkedListNode; +using FWwiseLoadedSoundBank = FWwiseLoadedSoundBankListNode*; +using FWwiseLoadedSoundBankPromise = TWwisePromise; +using FWwiseLoadedSoundBankFuture = TWwiseFuture; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Stats/ResourceLoader.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Stats/ResourceLoader.h new file mode 100644 index 0000000..e44be8f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/Stats/ResourceLoader.h @@ -0,0 +1,48 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Stats/Stats.h" +#include "Logging/LogMacros.h" + +#include "Wwise/Stats/NamedEvents.h" + +DECLARE_STATS_GROUP(TEXT("Resource Loader"), STATGROUP_WwiseResourceLoader, STATCAT_Wwise); + +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Referenced Aux Busses"), STAT_WwiseResourceLoaderAuxBusses, STATGROUP_WwiseResourceLoader, WWISERESOURCELOADER_API); +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Referenced Events"), STAT_WwiseResourceLoaderEvents, STATGROUP_WwiseResourceLoader, WWISERESOURCELOADER_API); +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Referenced External Sources"), STAT_WwiseResourceLoaderExternalSources, STATGROUP_WwiseResourceLoader, WWISERESOURCELOADER_API); +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Referenced Group Values"), STAT_WwiseResourceLoaderGroupValues, STATGROUP_WwiseResourceLoader, WWISERESOURCELOADER_API); +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Referenced Init Banks"), STAT_WwiseResourceLoaderInitBanks, STATGROUP_WwiseResourceLoader, WWISERESOURCELOADER_API); +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Referenced Media"), STAT_WwiseResourceLoaderMedia, STATGROUP_WwiseResourceLoader, WWISERESOURCELOADER_API); +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Referenced ShareSets"), STAT_WwiseResourceLoaderShareSets, STATGROUP_WwiseResourceLoader, WWISERESOURCELOADER_API); +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Referenced SoundBanks"), STAT_WwiseResourceLoaderSoundBanks, STATGROUP_WwiseResourceLoader, WWISERESOURCELOADER_API); +DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Referenced Switch Container Combinations"), STAT_WwiseResourceLoaderSwitchContainerCombinations, STATGROUP_WwiseResourceLoader, WWISERESOURCELOADER_API); + +DECLARE_CYCLE_STAT_EXTERN(TEXT("Resource Loading"), STAT_WwiseResourceLoaderTiming, STATGROUP_WwiseResourceLoader, WWISERESOURCELOADER_API); + +WWISERESOURCELOADER_API DECLARE_LOG_CATEGORY_EXTERN(LogWwiseResourceLoader, Log, All); + +#define SCOPED_WWISERESOURCELOADER_EVENT(Text) SCOPED_WWISE_NAMED_EVENT(TEXT("WwiseResourceLoader"), Text) +#define SCOPED_WWISERESOURCELOADER_EVENT_2(Text) SCOPED_WWISE_NAMED_EVENT_2(TEXT("WwiseResourceLoader"), Text) +#define SCOPED_WWISERESOURCELOADER_EVENT_3(Text) SCOPED_WWISE_NAMED_EVENT_3(TEXT("WwiseResourceLoader"), Text) +#define SCOPED_WWISERESOURCELOADER_EVENT_4(Text) SCOPED_WWISE_NAMED_EVENT_4(TEXT("WwiseResourceLoader"), Text) +#define SCOPED_WWISERESOURCELOADER_EVENT_F(Format, ...) SCOPED_WWISE_NAMED_EVENT_F(TEXT("WwiseResourceLoader"), Format, __VA_ARGS__) +#define SCOPED_WWISERESOURCELOADER_EVENT_F_2(Format, ...) SCOPED_WWISE_NAMED_EVENT_F_2(TEXT("WwiseResourceLoader"), Format, __VA_ARGS__) +#define SCOPED_WWISERESOURCELOADER_EVENT_F_3(Format, ...) SCOPED_WWISE_NAMED_EVENT_F_3(TEXT("WwiseResourceLoader"), Format, __VA_ARGS__) +#define SCOPED_WWISERESOURCELOADER_EVENT_F_4(Format, ...) SCOPED_WWISE_NAMED_EVENT_F_4(TEXT("WwiseResourceLoader"), Format, __VA_ARGS__) diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseLanguageId.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseLanguageId.h new file mode 100644 index 0000000..b58bbf3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseLanguageId.h @@ -0,0 +1,78 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "WwiseLanguageId.generated.h" + +USTRUCT(BlueprintType) +struct WWISERESOURCELOADER_API FWwiseLanguageId +{ + GENERATED_BODY() + + static const FWwiseLanguageId Sfx; + + FWwiseLanguageId() + {} + FWwiseLanguageId(int32 InLanguageId, const FName& InLanguageName) : + LanguageId(InLanguageId), + LanguageName(InLanguageName) + { + check(!LanguageName.IsNone()); + } + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + int32 LanguageId = 0; + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + FName LanguageName; + + bool operator==(const FWwiseLanguageId& Rhs) const + { + return LanguageId == Rhs.LanguageId; + } + + bool operator!=(const FWwiseLanguageId& Rhs) const + { + return LanguageId != Rhs.LanguageId; + } + + bool operator>=(const FWwiseLanguageId& Rhs) const + { + return LanguageId >= Rhs.LanguageId; + } + + bool operator>(const FWwiseLanguageId& Rhs) const + { + return LanguageId > Rhs.LanguageId; + } + + bool operator<=(const FWwiseLanguageId& Rhs) const + { + return LanguageId <= Rhs.LanguageId; + } + + bool operator<(const FWwiseLanguageId& Rhs) const + { + return LanguageId < Rhs.LanguageId; + } +}; + +inline uint32 GetTypeHash(const FWwiseLanguageId& Id) +{ + return GetTypeHash(Id.LanguageId); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwisePlatformId.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwisePlatformId.h new file mode 100644 index 0000000..9787e7a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwisePlatformId.h @@ -0,0 +1,78 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "WwisePlatformId.generated.h" + +USTRUCT(BlueprintType) +struct WWISERESOURCELOADER_API FWwisePlatformId +{ + GENERATED_BODY() + + FWwisePlatformId() : + PlatformGuid(), + PlatformName() +#if WITH_EDITORONLY_DATA + , PathRelativeToGeneratedSoundBanks() +#endif + {} + + FWwisePlatformId(const FGuid& InPlatformGuid, const FName& InPlatformName) : + PlatformGuid(InPlatformGuid), + PlatformName(InPlatformName) + {} + +#if WITH_EDITORONLY_DATA + FWwisePlatformId(const FGuid& InPlatformGuid, const FName& InPlatformName, const FName& InGeneratedSoundBanksPath) : + PlatformGuid(InPlatformGuid), + PlatformName(InPlatformName), + PathRelativeToGeneratedSoundBanks(InGeneratedSoundBanksPath) + {} +#endif + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + FGuid PlatformGuid; + + /** + * @brief Optional debug name. Can be empty in release, contain the name, or the full path of the asset. + */ + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + FName PlatformName; + +#if WITH_EDITORONLY_DATA + UPROPERTY(Transient, VisibleInstanceOnly, Category = "Wwise") + FName PathRelativeToGeneratedSoundBanks; + + UPROPERTY(Transient, VisibleInstanceOnly, Category = "Wwise") + FName ExternalSourceRootPath; +#endif + + bool operator==(const FWwisePlatformId& Rhs) const + { + return PlatformGuid == Rhs.PlatformGuid; + } + + bool operator!=(const FWwisePlatformId& Rhs) const + { + return PlatformGuid != Rhs.PlatformGuid; + } +}; +inline uint32 GetTypeHash(const FWwisePlatformId& Id) +{ + return GetTypeHash(Id.PlatformGuid); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseResourceLoader.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseResourceLoader.h new file mode 100644 index 0000000..935b7fe --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseResourceLoader.h @@ -0,0 +1,149 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseResourceLoaderImpl.h" +#include "Wwise/WwiseResourceLoaderModule.h" + +/** + * @brief Operations available to manage and handle Wwise SoundBanks in Unreal. +*/ +class WWISERESOURCELOADER_API FWwiseResourceLoader +{ +public: + using FWwiseSetLanguageFuture = TWwiseFuture; + using FWwiseSetLanguagePromise = TWwisePromise; + + inline static FWwiseResourceLoader* Get() + { + if (auto* Module = IWwiseResourceLoaderModule::GetModule()) + { + return Module->GetResourceLoader(); + } + return nullptr; + } + static FWwiseResourceLoader* Instantiate() + { + if (auto* Module = IWwiseResourceLoaderModule::GetModule()) + { + return Module->InstantiateResourceLoader(); + } + return nullptr; + } + + virtual bool IsEnabled() const; + virtual void Enable(); + virtual void Disable(); + + FWwiseResourceLoader(); + virtual ~FWwiseResourceLoader() {} + + // + // User-facing operations + // + + FWwiseLanguageCookedData GetCurrentLanguage() const; + FWwiseSharedPlatformId GetCurrentPlatform() const; + + /** + * @brief Returns the actual Unreal file path needed in order to retrieve this particular Wwise Path. + * + * This method acts differently depending on usage in ResourceLoaderImpl or Editor. In Editor, this will return + * the full path to the Generated SoundBanks folder. In a packaged game, this will return the full + * path to the staged file. + * + * @param WwisePath Requested file path, as found in SoundBanksInfo. + * @return The corresponding Unreal path. + */ + virtual FString GetUnrealPath(const FName& InPath) const { return GetUnrealPath(InPath.ToString()); } + virtual FString GetUnrealPath(const FString& InPath) const; + virtual FName GetUnrealExternalSourcePath() const; + + virtual FString GetUnrealStagePath(const FName& InPath) const { return GetUnrealStagePath(InPath.ToString()); } + virtual FString GetUnrealStagePath(const FString& InPath) const; +#if WITH_EDITORONLY_DATA + virtual FString GetUnrealGeneratedSoundBanksPath(const FName& InPath) const { return GetUnrealGeneratedSoundBanksPath(InPath.ToString()); } + virtual FString GetUnrealGeneratedSoundBanksPath(const FString& InPath) const; + + virtual void SetUnrealGeneratedSoundBanksPath(const FDirectoryPath& DirectoryPath); + virtual const FDirectoryPath& GetUnrealGeneratedSoundBanksPath(); +#endif + + /** + * @brief Sets the language for the current runtime, optionally reloading all affected assets immediately + * @param LanguageId The current language being processed, or 0 if none + * @param ReloadLanguage What reload strategy should be used for language changes + */ + virtual void SetLanguage(FWwiseLanguageCookedData InLanguage, EWwiseReloadLanguage InReloadLanguage); + virtual void SetPlatform(const FWwiseSharedPlatformId& InPlatform); + + virtual FWwiseLoadedAuxBus LoadAuxBus(const FWwiseLocalizedAuxBusCookedData& InAuxBusCookedData, const FWwiseLanguageCookedData* InLanguageOverride = nullptr); + virtual void UnloadAuxBus(FWwiseLoadedAuxBus&& InAuxBus); + + virtual FWwiseLoadedEvent LoadEvent(const FWwiseLocalizedEventCookedData& InEventCookedData, const FWwiseLanguageCookedData* InLanguageOverride = nullptr); + virtual void UnloadEvent(FWwiseLoadedEvent&& InEvent); + + virtual FWwiseLoadedExternalSource LoadExternalSource(const FWwiseExternalSourceCookedData& InExternalSourceCookedData); + virtual void UnloadExternalSource(FWwiseLoadedExternalSource&& InExternalSource); + + virtual FWwiseLoadedGroupValue LoadGroupValue(const FWwiseGroupValueCookedData& InGroupValueCookedData); + virtual void UnloadGroupValue(FWwiseLoadedGroupValue&& InGroupValue); + + virtual FWwiseLoadedInitBank LoadInitBank(const FWwiseInitBankCookedData& InInitBankCookedData); + virtual void UnloadInitBank(FWwiseLoadedInitBank&& InInitBank); + + virtual FWwiseLoadedMedia LoadMedia(const FWwiseMediaCookedData& InMediaCookedData); + virtual void UnloadMedia(FWwiseLoadedMedia&& InMedia); + + virtual FWwiseLoadedShareSet LoadShareSet(const FWwiseLocalizedShareSetCookedData& InShareSetCookedData, const FWwiseLanguageCookedData* InLanguageOverride = nullptr); + virtual void UnloadShareSet(FWwiseLoadedShareSet&& InShareSet); + + virtual FWwiseLoadedSoundBank LoadSoundBank(const FWwiseLocalizedSoundBankCookedData& InSoundBankCookedData, const FWwiseLanguageCookedData* InLanguageOverride = nullptr); + virtual void UnloadSoundBank(FWwiseLoadedSoundBank&& InSoundBank); + + virtual FWwiseSetLanguageFuture SetLanguageAsync(FWwiseLanguageCookedData InLanguage, EWwiseReloadLanguage InReloadLanguage); + + virtual FWwiseLoadedAuxBusFuture LoadAuxBusAsync(const FWwiseLocalizedAuxBusCookedData& InAuxBusCookedData, const FWwiseLanguageCookedData* InLanguageOverride = nullptr); + virtual FWwiseResourceUnloadFuture UnloadAuxBusAsync(FWwiseLoadedAuxBusFuture&& InAuxBus); + + virtual FWwiseLoadedEventFuture LoadEventAsync(const FWwiseLocalizedEventCookedData& InEventCookedData, const FWwiseLanguageCookedData* InLanguageOverride = nullptr); + virtual FWwiseResourceUnloadFuture UnloadEventAsync(FWwiseLoadedEventFuture&& InEvent); + + virtual FWwiseLoadedExternalSourceFuture LoadExternalSourceAsync(const FWwiseExternalSourceCookedData& InExternalSourceCookedData); + virtual FWwiseResourceUnloadFuture UnloadExternalSourceAsync(FWwiseLoadedExternalSourceFuture&& InExternalSource); + + virtual FWwiseLoadedGroupValueFuture LoadGroupValueAsync(const FWwiseGroupValueCookedData& InGroupValueCookedData); + virtual FWwiseResourceUnloadFuture UnloadGroupValueAsync(FWwiseLoadedGroupValueFuture&& InGroupValue); + + virtual FWwiseLoadedInitBankFuture LoadInitBankAsync(const FWwiseInitBankCookedData& InInitBankCookedData); + virtual FWwiseResourceUnloadFuture UnloadInitBankAsync(FWwiseLoadedInitBankFuture&& InInitBank); + + virtual FWwiseLoadedMediaFuture LoadMediaAsync(const FWwiseMediaCookedData& InMediaCookedData); + virtual FWwiseResourceUnloadFuture UnloadMediaAsync(FWwiseLoadedMediaFuture&& InMedia); + + virtual FWwiseLoadedShareSetFuture LoadShareSetAsync(const FWwiseLocalizedShareSetCookedData& InShareSetCookedData, const FWwiseLanguageCookedData* InLanguageOverride = nullptr); + virtual FWwiseResourceUnloadFuture UnloadShareSetAsync(FWwiseLoadedShareSetFuture&& InShareSet); + + virtual FWwiseLoadedSoundBankFuture LoadSoundBankAsync(const FWwiseLocalizedSoundBankCookedData& InSoundBankCookedData, const FWwiseLanguageCookedData* InLanguageOverride = nullptr); + virtual FWwiseResourceUnloadFuture UnloadSoundBankAsync(FWwiseLoadedSoundBankFuture&& InSoundBank); + + virtual FWwiseSharedPlatformId SystemPlatform() const; + virtual FWwiseLanguageCookedData SystemLanguage() const; + + TUniquePtr ResourceLoaderImpl; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseResourceLoaderFuture.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseResourceLoaderFuture.h new file mode 100644 index 0000000..499f293 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseResourceLoaderFuture.h @@ -0,0 +1,25 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseFuture.h" + +using FWwiseResourceLoadPromise = TWwisePromise; +using FWwiseResourceLoadFuture = TWwiseFuture; +using FWwiseResourceUnloadPromise = TWwisePromise; +using FWwiseResourceUnloadFuture = TWwiseFuture; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseResourceLoaderImpl.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseResourceLoaderImpl.h new file mode 100644 index 0000000..70cc114 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseResourceLoaderImpl.h @@ -0,0 +1,562 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "WwiseResourceLoaderModule.h" +#include "Wwise/WwiseExecutionQueue.h" +#include "Wwise/CookedData/WwiseAuxBusCookedData.h" +#include "Wwise/CookedData/WwiseEventCookedData.h" +#include "Wwise/CookedData/WwiseShareSetCookedData.h" +#include "Wwise/Loaded/WwiseLoadedAuxBus.h" +#include "Wwise/Loaded/WwiseLoadedSoundBank.h" +#include "Wwise/Loaded/WwiseLoadedEvent.h" +#include "Wwise/Loaded/WwiseLoadedExternalSource.h" +#include "Wwise/Loaded/WwiseLoadedGroupValue.h" +#include "Wwise/Loaded/WwiseLoadedInitBank.h" +#include "Wwise/Loaded/WwiseLoadedMedia.h" +#include "Wwise/Loaded/WwiseLoadedShareSet.h" + +#include "Wwise/WwiseResourceLoaderFuture.h" +#include "Wwise/WwiseSharedGroupValueKey.h" +#include "Wwise/WwiseSharedLanguageId.h" +#include "Wwise/WwiseSharedPlatformId.h" + +#include "Wwise/Stats/ResourceLoader.h" + +#if WITH_EDITORONLY_DATA +#include "Engine/EngineTypes.h" +#include "UObject/SoftObjectPath.h" +#endif + +#include "WwiseResourceLoaderImpl.generated.h" + +/** + * @brief What reload strategy should be used for language changes +*/ +UENUM() +enum EWwiseReloadLanguage +{ + /// Don't reload anything. The game is fully responsible to reload elements. This doesn't call + /// any operation on the SoundEngine side, so everything will keep on working as usual. + Manual, + + /// Reloads immediately without stopping anything. Game is responsible for stopping and restarting + /// possibly affected sounds or else they might cause audible breaks. This is useful when some + /// sounds can keep on playing, such as music and ambient sounds, while the dialogues are being + /// internally reloaded. + /// + /// Depending on the quantity of currently loaded localized banks, the operation can take a long time. + /// + /// \warning Affected events needs to be restarted once the operation is done. + Immediate, + + /// Stops all sounds first, unloads all the localized banks, and reloads the new language. This will + /// cause an audible break while the operation is done. + /// + /// Depending on the quantity of currently loaded localized banks, the operation can take a long time. + /// + /// \warning Affected events needs to be restarted once the operation is done. + Safe +}; + +/** + * @brief Whether the WwiseResourceLoader is allowed to load/unload assets +*/ +enum class EWwiseResourceLoaderState +{ + /// Do not allow the WwiseResourceLoader to load/unload assets + AlwaysDisabled, + /// Allow the WwiseResourceLoader to load/unload assets + Enabled, +}; + +struct WWISERESOURCELOADER_API FWwiseSwitchContainerLeafGroupValueUsageCount +{ + /** + * @brief SwitchContainer Leaf this structure represents. + */ + const FWwiseSwitchContainerLeafCookedData& Key; + + /** + * @brief Number of GroupValues present in this key that are already loaded. + */ + TSet LoadedGroupValues; + + /** + * @brief Resources represented by the Key that were successfully loaded. + */ + struct WWISERESOURCELOADER_API FLoadedData + { + FLoadedData(); + TArray LoadedSoundBanks; + TArray LoadedExternalSources; + TArray LoadedMedia; + bool IsLoaded() const; + } LoadedData; + + FWwiseSwitchContainerLeafGroupValueUsageCount(const FWwiseSwitchContainerLeafCookedData& InKey); + + bool HaveAllKeys() const; +}; + +struct WWISERESOURCELOADER_API FWwiseSwitchContainerLoadedGroupValueInfo +{ + /** + * @brief GroupValue key this structure represents. + */ + FWwiseSharedGroupValueKey Key; + + /** + * @brief Number of times this particular GroupValue got loaded in the currently loaded maps. + * + * Any value higher than 0 means there's a chance the Leaves might be required. + */ + int LoadCount; + + /** + * @brief Leaves that uses this particular GroupValue. + * + * @note The ownership of this pointer is uniquely created and discarded during SwitchContainerLeaf loading and unloading. + */ + TSet Leaves; + + FWwiseSwitchContainerLoadedGroupValueInfo(const FWwiseSharedGroupValueKey& InKey) : + Key(InKey), + LoadCount(0), + Leaves() + {} + + bool ShouldBeLoaded() const + { + check(LoadCount >= 0); + + return LoadCount > 0; + } + + bool operator ==(const FWwiseSwitchContainerLoadedGroupValueInfo& InRhs) const + { + return Key == InRhs.Key; + } + + bool operator !=(const FWwiseSwitchContainerLoadedGroupValueInfo& InRhs) const + { + return Key != InRhs.Key; + } + + bool operator <(const FWwiseSwitchContainerLoadedGroupValueInfo& InRhs) const + { + return Key < InRhs.Key; + } + + bool operator <=(const FWwiseSwitchContainerLoadedGroupValueInfo& InRhs) const + { + return Key <= InRhs.Key; + } + + bool operator >(const FWwiseSwitchContainerLoadedGroupValueInfo& InRhs) const + { + return Key > InRhs.Key; + } + + bool operator >=(const FWwiseSwitchContainerLoadedGroupValueInfo& InRhs) const + { + return Key >= InRhs.Key; + } + + bool operator ==(const FWwiseSharedGroupValueKey& InRhs) const + { + return Key == InRhs; + } + + bool operator !=(const FWwiseSharedGroupValueKey& InRhs) const + { + return Key != InRhs; + } +}; +inline uint32 GetTypeHash(const FWwiseSwitchContainerLoadedGroupValueInfo& InValue) +{ + return GetTypeHash(InValue.Key); +} + +class WWISERESOURCELOADER_API FWwiseResourceLoaderImpl +{ +public: + using FWwiseSetLanguageFuture = TWwiseFuture; + using FWwiseSetLanguagePromise = TWwisePromise; + + static FWwiseResourceLoaderImpl* Instantiate() + { + if (auto* Module = IWwiseResourceLoaderModule::GetModule()) + { + return Module->InstantiateResourceLoaderImpl(); + } + return nullptr; + } + + EWwiseResourceLoaderState WwiseResourceLoaderState = EWwiseResourceLoaderState::Enabled; + + /** + * @brief Currently targeted platform for this runtime + */ + FWwiseSharedPlatformId CurrentPlatform; + + /** + * @brief Currently targeted language for this runtime + */ + FWwiseLanguageCookedData CurrentLanguage; + + /** + * @brief Location in the staged product where the SoundBank medias are found + */ + FString StagePath; + +#if WITH_EDITORONLY_DATA + /** + * @brief Location where the Wwise Generated SoundBanks product is found on disk relative to the project + */ + FDirectoryPath GeneratedSoundBanksPath; +#endif + + ENamedThreads::Type TaskThread = ENamedThreads::AnyThread; + + FWwiseResourceLoaderImpl(); + virtual ~FWwiseResourceLoaderImpl() {} + + FName GetUnrealExternalSourcePath() const; + FString GetUnrealPath() const; + FString GetUnrealPath(const FName& InPath) const { return GetUnrealPath(InPath.ToString()); } + FString GetUnrealPath(const FString& InPath) const; + + FString GetUnrealStagePath(const FName& InPath) const; + FString GetUnrealStagePath(const FString& InPath) const; +#if WITH_EDITORONLY_DATA + FString GetUnrealGeneratedSoundBanksPath(const FName& InPath) const { return GetUnrealGeneratedSoundBanksPath(InPath.ToString());} + FString GetUnrealGeneratedSoundBanksPath(const FString& InPath) const; +#endif + + virtual EWwiseResourceLoaderState GetResourceLoaderState(); + virtual void SetResourceLoaderState(EWwiseResourceLoaderState State); + virtual bool IsEnabled(); + virtual void Disable(); + virtual void Enable(); + + virtual void SetLanguageAsync(FWwiseSetLanguagePromise&& Promise, const FWwiseLanguageCookedData& InLanguage, EWwiseReloadLanguage InReloadLanguage); + void SetPlatform(const FWwiseSharedPlatformId& InPlatform); + + virtual FWwiseLoadedAuxBus CreateAuxBusNode(const FWwiseLocalizedAuxBusCookedData& InAuxBusCookedData, const FWwiseLanguageCookedData* InLanguageOverride); + virtual void LoadAuxBusAsync(FWwiseLoadedAuxBusPromise&& Promise, FWwiseLoadedAuxBus&& InAuxBusListNode); + virtual void UnloadAuxBusAsync(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedAuxBus&& InAuxBusListNode); + + virtual FWwiseLoadedEvent CreateEventNode(const FWwiseLocalizedEventCookedData& InEventCookedData, const FWwiseLanguageCookedData* InLanguageOverride); + virtual void LoadEventAsync(FWwiseLoadedEventPromise&& Promise, FWwiseLoadedEvent&& InEventListNode); + virtual void UnloadEventAsync(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedEvent&& InEventListNode); + + virtual FWwiseLoadedExternalSource CreateExternalSourceNode(const FWwiseExternalSourceCookedData& InExternalSourceCookedData); + virtual void LoadExternalSourceAsync(FWwiseLoadedExternalSourcePromise&& Promise, FWwiseLoadedExternalSource&& InExternalSourceListNode); + virtual void UnloadExternalSourceAsync(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedExternalSource&& InExternalSourceListNode); + + virtual FWwiseLoadedGroupValue CreateGroupValueNode(const FWwiseGroupValueCookedData& InGroupValueCookedData); + virtual void LoadGroupValueAsync(FWwiseLoadedGroupValuePromise&& Promise, FWwiseLoadedGroupValue&& InGroupValueListNode); + virtual void UnloadGroupValueAsync(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedGroupValue&& InGroupValueListNode); + + virtual FWwiseLoadedInitBank CreateInitBankNode(const FWwiseInitBankCookedData& InInitBankCookedData); + virtual void LoadInitBankAsync(FWwiseLoadedInitBankPromise&& Promise, FWwiseLoadedInitBank&& InInitBankListNode); + virtual void UnloadInitBankAsync(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedInitBank&& InInitBankListNode); + + virtual FWwiseLoadedMedia CreateMediaNode(const FWwiseMediaCookedData& InMediaCookedData); + virtual void LoadMediaAsync(FWwiseLoadedMediaPromise&& Promise, FWwiseLoadedMedia&& InMediaListNode); + virtual void UnloadMediaAsync(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedMedia&& InMediaListNode); + + virtual FWwiseLoadedShareSet CreateShareSetNode(const FWwiseLocalizedShareSetCookedData& InShareSetCookedData, const FWwiseLanguageCookedData* InLanguageOverride); + virtual void LoadShareSetAsync(FWwiseLoadedShareSetPromise&& Promise, FWwiseLoadedShareSet&& InShareSetListNode); + virtual void UnloadShareSetAsync(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedShareSet&& InShareSetListNode); + + virtual FWwiseLoadedSoundBank CreateSoundBankNode(const FWwiseLocalizedSoundBankCookedData& InSoundBankCookedData, const FWwiseLanguageCookedData* InLanguageOverride); + virtual void LoadSoundBankAsync(FWwiseLoadedSoundBankPromise&& Promise, FWwiseLoadedSoundBank&& InSoundBankListNode); + virtual void UnloadSoundBankAsync(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedSoundBank&& InSoundBankListNode); + +protected: + using FLoadFileCallback = TUniqueFunction; + using FUnloadFileCallback = TUniqueFunction; + using FCompletionCallback = TUniqueFunction; + using FCompletionPromise = TWwisePromise; + using FCompletionFuture = TWwiseFuture; + using FCompletionFutureArray = TArray; + + /** + * @brief Execution queue for adding and removing objects from the loaded Wwise Object lists. + * + * Use the execution queue to add and remove objects from the loaded Wwise Object lists. Do not use the execution + * queue for any other operations. As a best practice, use objects only after they are loaded and before they are + * unloaded. Do not use them while they are being loaded or unloaded. + */ + FWwiseExecutionQueue LoadedListExecutionQueue; + + /** + * @brief List of all the loaded Auxiliary Bus Wwise Objects. + * + * This list is maintained through the LoadAuxBusAsync and UnloadAuxBusAsync operations. + * + * @note To modify this list, you must call the operation asynchronously through ListExecutionQueue. + */ + FWwiseLoadedAuxBusList LoadedAuxBusList; + + /** + * @brief List of all the loaded Event Wwise Objects. + * + * This list is maintained through the LoadEventAsync and UnloadEventAsync operations. + * + * @note To modify this list, you must call the operation asynchronously through ListExecutionQueue. + */ + FWwiseLoadedEventList LoadedEventList; + + /** + * @brief List of all the loaded External Source Wwise Objects. + * + * This list is maintained through the LoadExternalSourceAsync and UnloadExternalSourceAsync operations. + * + * External Sources are component parts of many other Wwise objects. Events, for example, can contain + * their own External Sources. This list only contains the External Sources that were added independently, + * not those that are already included in objects such as Events. + * + * @note To modify this list, you must call the operation asynchronously through ListExecutionQueue. + */ + FWwiseLoadedExternalSourceList LoadedExternalSourceList; + + /** + * @brief List of all the loaded GroupValue (States, Switches) Wwise Objects. + * + * This list is maintained through the LoadGroupValueAsync and UnloadGroupValueAsync operations. + * + * GroupValues are component parts of many other Wwise objects. Events, for example, can contain + * their own GroupValues. This list only contains the GroupValues that were added independently, + * not those that are already included in objects such as Events. + * + * @note To modify this list, you must call the operation asynchronously through ListExecutionQueue. + */ + FWwiseLoadedGroupValueList LoadedGroupValueList; + + /** + * @brief List of all the loaded Init Bank Wwise Objects. + * + * This list is maintained through the LoadInitBankAsync and UnloadInitBankAsync operations. + * + * @note In order to modify this list (add or remove), you must call the operation asynchronously through the + * ListExecutionQueue. + * + * @warning Although this is a list, it can only include a single Init Bank. Each project supports one Init Bank, + * which is used for the duration of the Sound Engine's lifespan. + */ + FWwiseLoadedInitBankList LoadedInitBankList; + + /** + * @brief List of all the loaded Media Wwise Objects. + * + * This list is maintained through the LoadMediaAsync and UnloadMediaAsync operations. + * + * Media objects are component parts of many other Wwise objects. Events, for example, can contain their + * own Media objects. This list only contains the Media objects that were added independently, not those + * that are already included in objects such as Events. + * + * @note To modify this list, you must call the operation asynchronously through ListExecutionQueue. + */ + FWwiseLoadedMediaList LoadedMediaList; + + /** + * @brief List of all the loaded Share Set Wwise Objects. + * + * This list is maintained through the LoadShareSetAsync and UnloadShareSetAsync operations. + * + * @note To modify this list, you must call the operation asynchronously through ListExecutionQueue. + */ + FWwiseLoadedShareSetList LoadedShareSetList; + + /** + * @brief List of all the loaded SoundBank Wwise Objects. + * + * This list is maintained through the LoadSoundBankAsync and UnloadSoundBankAsync operations. + * + * SoundBanks are building blocks of multiple other Wwise objects. An Event is included (and thus require) inside + * a SoundBank. This list only contains the independently added SoundBanks, as the different objects, such as + * Events, are responsible for keeping track of their own required sub-objects. + * + * @note To modify this list, you must call the operation asynchronously through ListExecutionQueue. + */ + FWwiseLoadedSoundBankList LoadedSoundBankList; + + + /** + * @brief Execution queue for adding, removing, and editing items from the Switch Container object hierarchy. + * + * When Switch Containers are loaded, they can be modified by levels. For example, a level can add forest footstep + * impact sounds to a generic Footsteps event. Use this execution queue to modify any resources that might be + * affected at load time. + */ + FWwiseExecutionQueue SwitchContainerExecutionQueue; + + /** + * @brief Set of all known GroupValues, each of which contains the list of the Switch Containers that require them. + * + * This set uses a leaking pattern: every known GroupValue appears but is never deleted. Realistically, games have + * fewer than ten thousand different Switches and States, so the effect on memory and performance is minimal. + * + * @note To modify this list, you must call the operation asynchronously through SwitchContainerExecutionQueue. + */ + TSet LoadedGroupValueInfo; + + mutable FWwiseExecutionQueue FileExecutionQueue; + + virtual void LoadAuxBusResources(FWwiseResourceLoadPromise&& Promise, FWwiseLoadedAuxBusInfo::FLoadedData& LoadedData, const FWwiseAuxBusCookedData& InCookedData); + virtual void LoadEventResources(FWwiseResourceLoadPromise&& Promise, FWwiseLoadedEventInfo::FLoadedData& LoadedData, const FWwiseEventCookedData& InCookedData); + virtual void LoadEventSwitchContainerResources(FWwiseResourceLoadPromise&& Promise, FWwiseLoadedEventInfo::FLoadedData& LoadedData, const FWwiseEventCookedData& InCookedData); + virtual void LoadExternalSourceResources(FWwiseResourceLoadPromise&& Promise, FWwiseLoadedExternalSourceInfo::FLoadedData& LoadedData, const FWwiseExternalSourceCookedData& InCookedData); + virtual void LoadGroupValueResources(FWwiseResourceLoadPromise&& Promise, FWwiseLoadedGroupValueInfo::FLoadedData& LoadedData, const FWwiseGroupValueCookedData& InCookedData); + virtual void LoadInitBankResources(FWwiseResourceLoadPromise&& Promise, FWwiseLoadedInitBankInfo::FLoadedData& LoadedData, const FWwiseInitBankCookedData& InCookedData); + virtual void LoadMediaResources(FWwiseResourceLoadPromise&& Promise, FWwiseLoadedMediaInfo::FLoadedData& LoadedData, const FWwiseMediaCookedData& InCookedData); + virtual void LoadShareSetResources(FWwiseResourceLoadPromise&& Promise, FWwiseLoadedShareSetInfo::FLoadedData& LoadedData, const FWwiseShareSetCookedData& InCookedData); + virtual void LoadSoundBankResources(FWwiseResourceLoadPromise&& Promise, FWwiseLoadedSoundBankInfo::FLoadedData& LoadedData, const FWwiseSoundBankCookedData& InCookedData); + virtual void LoadSwitchContainerLeafResources(FWwiseResourceLoadPromise&& Promise, FWwiseSwitchContainerLeafGroupValueUsageCount::FLoadedData& LoadedData, const FWwiseSwitchContainerLeafCookedData& InCookedData); + + virtual void UnloadAuxBusResources(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedAuxBusInfo::FLoadedData& LoadedData, const FWwiseAuxBusCookedData& InCookedData); + virtual void UnloadEventResources(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedEventInfo::FLoadedData& LoadedData, const FWwiseEventCookedData& InCookedData); + virtual void UnloadEventSwitchContainerResources(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedEventInfo::FLoadedData& LoadedData, const FWwiseEventCookedData& InCookedData); + virtual void UnloadExternalSourceResources(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedExternalSourceInfo::FLoadedData& LoadedData, const FWwiseExternalSourceCookedData& InCookedData); + virtual void UnloadGroupValueResources(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedGroupValueInfo::FLoadedData& LoadedData, const FWwiseGroupValueCookedData& InCookedData); + virtual void UnloadInitBankResources(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedInitBankInfo::FLoadedData& LoadedData, const FWwiseInitBankCookedData& InCookedData); + virtual void UnloadMediaResources(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedMediaInfo::FLoadedData& LoadedData, const FWwiseMediaCookedData& InCookedData); + virtual void UnloadShareSetResources(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedShareSetInfo::FLoadedData& LoadedData, const FWwiseShareSetCookedData& InCookedData); + virtual void UnloadSoundBankResources(FWwiseResourceUnloadPromise&& Promise, FWwiseLoadedSoundBankInfo::FLoadedData& LoadedData, const FWwiseSoundBankCookedData& InCookedData); + virtual void UnloadSwitchContainerLeafResources(FWwiseResourceUnloadPromise&& Promise, FWwiseSwitchContainerLeafGroupValueUsageCount::FLoadedData& LoadedData, const FWwiseSwitchContainerLeafCookedData& InCookedData); + + virtual FCompletionFuture AttachAuxBusNode(FWwiseLoadedAuxBusListNode* AuxBusListNode); + virtual FCompletionFuture AttachEventNode(FWwiseLoadedEventListNode* EventListNode); + virtual FCompletionFuture AttachExternalSourceNode(FWwiseLoadedExternalSourceListNode* ExternalSourceListNode); + virtual FCompletionFuture AttachGroupValueNode(FWwiseLoadedGroupValueListNode* GroupValueListNode); + virtual FCompletionFuture AttachInitBankNode(FWwiseLoadedInitBankListNode* InitBankListNode); + virtual FCompletionFuture AttachMediaNode(FWwiseLoadedMediaListNode* MediaListNode); + virtual FCompletionFuture AttachShareSetNode(FWwiseLoadedShareSetListNode* ShareSetListNode); + virtual FCompletionFuture AttachSoundBankNode(FWwiseLoadedSoundBankListNode* SoundBankListNode); + + virtual FCompletionFuture DetachAuxBusNode(FWwiseLoadedAuxBusListNode* AuxBusListNode); + virtual FCompletionFuture DetachEventNode(FWwiseLoadedEventListNode* EventListNode); + virtual FCompletionFuture DetachExternalSourceNode(FWwiseLoadedExternalSourceListNode* ExternalSourceListNode); + virtual FCompletionFuture DetachGroupValueNode(FWwiseLoadedGroupValueListNode* GroupValueListNode); + virtual FCompletionFuture DetachInitBankNode(FWwiseLoadedInitBankListNode* InitBankListNode); + virtual FCompletionFuture DetachMediaNode(FWwiseLoadedMediaListNode* MediaListNode); + virtual FCompletionFuture DetachShareSetNode(FWwiseLoadedShareSetListNode* ShareSetListNode); + virtual FCompletionFuture DetachSoundBankNode(FWwiseLoadedSoundBankListNode* SoundBankListNode); + + void AddLoadExternalSourceFutures(FCompletionFutureArray& FutureArray, TArray& LoadedExternalSources, + const TArray& InExternalSources, const TCHAR* InType, FName InDebugName, uint32 InShortId) const; + void AddUnloadExternalSourceFutures(FCompletionFutureArray& FutureArray, TArray& LoadedExternalSources) const; + void AddLoadMediaFutures(FCompletionFutureArray& FutureArray, TArray& LoadedMedia, + const TArray& InMedia, const TCHAR* InType, FName InDebugName, uint32 InShortId) const; + void AddUnloadMediaFutures(FCompletionFutureArray& FutureArray, TArray& LoadedMedia) const; + void AddLoadSoundBankFutures(FCompletionFutureArray& FutureArray, TArray& LoadedSoundBanks, + const TArray& InSoundBank, const TCHAR* InType, FName InDebugName, uint32 InShortId) const; + void AddUnloadSoundBankFutures(FCompletionFutureArray& FutureArray, TArray& LoadedSoundBanks) const; + void WaitForFutures(FCompletionFutureArray&& FutureArray, FCompletionCallback&& Callback, int NextId = 0) const; + + void LoadSoundBankFile(const FWwiseSoundBankCookedData& InSoundBank, FLoadFileCallback&& InCallback) const; + void UnloadSoundBankFile(const FWwiseSoundBankCookedData& InSoundBank, FUnloadFileCallback&& InCallback) const; + void LoadMediaFile(const FWwiseMediaCookedData& InMedia, FLoadFileCallback&& InCallback) const; + void UnloadMediaFile(const FWwiseMediaCookedData& InMedia, FUnloadFileCallback&& InCallback) const; + void LoadExternalSourceFile(const FWwiseExternalSourceCookedData& InExternalSource, FLoadFileCallback&& InCallback) const; + void UnloadExternalSourceFile(const FWwiseExternalSourceCookedData& InExternalSource, FUnloadFileCallback&& InCallback) const; + + template + inline const FWwiseLanguageCookedData* GetLanguageMapKey(const TMap& Map, const FWwiseLanguageCookedData* InLanguageOverride, const FName& InDebugName) const; + + template + static inline void LogLoad(const T& Object); + template + static inline void LogUnload(const T& Object); + template + static inline void LogLoadResources(const T& Object); + template + static inline void LogUnloadResources(const T& Object); + template + static inline void LogLoadResources(const T& Object, void* Ptr); + template + static inline void LogUnloadResources(const T& Object, void* Ptr); +}; + +template +void FWwiseResourceLoaderImpl::LogLoad(const T& Object) +{ + UE_LOG(LogWwiseResourceLoader, Verbose, TEXT("Loading %s"), *Object.GetDebugString()); +} + +template +void FWwiseResourceLoaderImpl::LogUnload(const T& Object) +{ + UE_LOG(LogWwiseResourceLoader, Verbose, TEXT("Unloading %s"), *Object.GetDebugString()); +} + +template +void FWwiseResourceLoaderImpl::LogLoadResources(const T& Object) +{ + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("Loading resources for %s"), *Object.GetDebugString()); +} + +template +void FWwiseResourceLoaderImpl::LogUnloadResources(const T& Object) +{ + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("Unloading resources for %s"), *Object.GetDebugString()); +} + +template +void FWwiseResourceLoaderImpl::LogLoadResources(const T& Object, void* Ptr) +{ + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("Loading resources for %s @ %p"), *Object.GetDebugString(), Ptr); +} + +template +void FWwiseResourceLoaderImpl::LogUnloadResources(const T& Object, void* Ptr) +{ + UE_LOG(LogWwiseResourceLoader, VeryVerbose, TEXT("Unloading resources for %s @ %p"), *Object.GetDebugString(), Ptr); +} + +template +inline const FWwiseLanguageCookedData* FWwiseResourceLoaderImpl::GetLanguageMapKey(const TMap& Map, const FWwiseLanguageCookedData* InLanguageOverride, const FName& InDebugName) const +{ + if (InLanguageOverride) + { + if (Map.Find(*InLanguageOverride)) + { + return InLanguageOverride; + } + UE_LOG(LogWwiseResourceLoader, Log, TEXT("GetLanguageMapKey: Could not find overridden language %s while processing asset %s. Defaulting to language %s"), + *InLanguageOverride->GetLanguageName().ToString(), *InDebugName.ToString(), *CurrentLanguage.GetLanguageName().ToString()); + } + + if (LIKELY(Map.Contains(FWwiseLanguageCookedData::Sfx))) + { + return &FWwiseLanguageCookedData::Sfx; + } + + if (Map.Find(CurrentLanguage)) + { + return &CurrentLanguage; + } + + UE_LOG(LogWwiseResourceLoader, Warning, TEXT("GetLanguageMapKey: Could not find language %s while processing asset %s."), + *CurrentLanguage.GetLanguageName().ToString(), *InDebugName.ToString()); + return nullptr; +} + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseResourceLoaderModule.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseResourceLoaderModule.h new file mode 100644 index 0000000..3c84131 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseResourceLoaderModule.h @@ -0,0 +1,94 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Misc/ConfigCacheIni.h" +#include "Modules/ModuleManager.h" +#include "Misc/ConfigCacheIni.h" + +class FWwiseResourceLoaderImpl; +class FWwiseResourceLoader; + +class IWwiseResourceLoaderModule : public IModuleInterface +{ +public: + static FName GetModuleName() + { + static const FName ModuleName = GetModuleNameFromConfig(); + return ModuleName; + } + + /** + * Checks to see if this module is loaded and ready. + * + * @return True if the module is loaded and ready to use + */ + static bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded(GetModuleName()); + } + + static IWwiseResourceLoaderModule* GetModule() + { + const auto ModuleName = GetModuleName(); + if (ModuleName.IsNone()) + { + return nullptr; + } +#if UE_SERVER + return nullptr; +#else + FModuleManager& ModuleManager = FModuleManager::Get(); + IWwiseResourceLoaderModule* Result = ModuleManager.GetModulePtr(ModuleName); + if (UNLIKELY(!Result)) + { + if (UNLIKELY(IsEngineExitRequested())) + { + UE_LOG(LogLoad, Verbose, TEXT("Skipping reloading missing WwiseResourceLoaderImpl module: Exiting.")); + } + else if (UNLIKELY(!IsInGameThread())) + { + UE_LOG(LogLoad, Warning, TEXT("Skipping loading missing WwiseResourceLoaderImpl module: Not in game thread")); + } + else + { + UE_LOG(LogLoad, Log, TEXT("Loading WwiseResourceLoaderImpl module: %s"), *ModuleName.GetPlainNameString()); + Result = ModuleManager.LoadModulePtr(ModuleName); + if (UNLIKELY(!Result)) + { + UE_LOG(LogLoad, Fatal, TEXT("Could not load WwiseResourceLoaderImpl module: %s not found"), *ModuleName.GetPlainNameString()); + } + } + } + + return Result; +#endif + } + + virtual FWwiseResourceLoader* GetResourceLoader() { return nullptr; } + virtual FWwiseResourceLoaderImpl* InstantiateResourceLoaderImpl() { return nullptr; } + virtual FWwiseResourceLoader* InstantiateResourceLoader() { return nullptr; } + +private: + static inline FName GetModuleNameFromConfig() + { + FString ModuleName = TEXT("WwiseResourceLoader"); + GConfig->GetString(TEXT("Audio"), TEXT("WwiseResourceLoaderModuleName"), ModuleName, GEngineIni); + return FName(ModuleName); + } +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseResourceLoaderModuleImpl.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseResourceLoaderModuleImpl.h new file mode 100644 index 0000000..05c7d9a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseResourceLoaderModuleImpl.h @@ -0,0 +1,35 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseResourceLoaderModule.h" +#include "Wwise/WwiseResourceLoader.h" + +class WWISERESOURCELOADER_API FWwiseResourceLoaderModule : public IWwiseResourceLoaderModule +{ +public: + FWwiseResourceLoader* GetResourceLoader() override; + FWwiseResourceLoaderImpl* InstantiateResourceLoaderImpl() override; + FWwiseResourceLoader* InstantiateResourceLoader() override; + + void ShutdownModule() override; + +protected: + FRWLock Lock; + TUniquePtr ResourceLoader; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseSharedGroupValueKey.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseSharedGroupValueKey.h new file mode 100644 index 0000000..93c5aa0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseSharedGroupValueKey.h @@ -0,0 +1,73 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/CookedData/WwiseGroupValueCookedData.h" + +#include "WwiseSharedGroupValueKey.generated.h" + +USTRUCT(BlueprintType) +struct WWISERESOURCELOADER_API FWwiseSharedGroupValueKey +{ + GENERATED_BODY() + + TSharedRef GroupValueCookedData; + + FWwiseSharedGroupValueKey() : + GroupValueCookedData(new FWwiseGroupValueCookedData) + {} + + FWwiseSharedGroupValueKey(const FWwiseGroupValueCookedData& InGroupValueCookedData) : + GroupValueCookedData(new FWwiseGroupValueCookedData(InGroupValueCookedData)) + {} + + bool operator==(const FWwiseSharedGroupValueKey& Rhs) const + { + return *GroupValueCookedData == *Rhs.GroupValueCookedData; + } + + bool operator!=(const FWwiseSharedGroupValueKey& Rhs) const + { + return *GroupValueCookedData != *Rhs.GroupValueCookedData; + } + + bool operator>=(const FWwiseSharedGroupValueKey& Rhs) const + { + return *GroupValueCookedData >= *Rhs.GroupValueCookedData; + } + + bool operator>(const FWwiseSharedGroupValueKey& Rhs) const + { + return *GroupValueCookedData > *Rhs.GroupValueCookedData; + } + + bool operator<=(const FWwiseSharedGroupValueKey& Rhs) const + { + return *GroupValueCookedData <= *Rhs.GroupValueCookedData; + } + + bool operator<(const FWwiseSharedGroupValueKey& Rhs) const + { + return *GroupValueCookedData < *Rhs.GroupValueCookedData; + } +}; + +inline uint32 GetTypeHash(const FWwiseSharedGroupValueKey& Id) +{ + return GetTypeHash(*Id.GroupValueCookedData); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseSharedLanguageId.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseSharedLanguageId.h new file mode 100644 index 0000000..bb984b1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseSharedLanguageId.h @@ -0,0 +1,91 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseLanguageId.h" +#include "Wwise/CookedData/WwiseLanguageCookedData.h" +#include "WwiseSharedLanguageId.generated.h" + +USTRUCT(BlueprintType) +struct WWISERESOURCELOADER_API FWwiseSharedLanguageId +{ + GENERATED_BODY() + + static const FWwiseSharedLanguageId Sfx; + + TSharedPtr Language; + + UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = "Wwise") + EWwiseLanguageRequirement LanguageRequirement = EWwiseLanguageRequirement::SFX; + + FWwiseSharedLanguageId(); + FWwiseSharedLanguageId(int32 InLanguageId, const FName& InLanguageName, EWwiseLanguageRequirement InLanguageRequirement); + FWwiseSharedLanguageId(const FWwiseLanguageId& InLanguageId, EWwiseLanguageRequirement InLanguageRequirement); + ~FWwiseSharedLanguageId(); + + int32 GetLanguageId() const + { + return Language->LanguageId; + } + + const FName& GetLanguageName() const + { + return Language->LanguageName; + } + + bool operator==(const FWwiseSharedLanguageId& Rhs) const + { + return (!Language.IsValid() && !Rhs.Language.IsValid()) + || (Language.IsValid() && Rhs.Language.IsValid() && *Language == *Rhs.Language); + } + + bool operator!=(const FWwiseSharedLanguageId& Rhs) const + { + return (Language.IsValid() != Rhs.Language.IsValid()) + || (Language.IsValid() && Rhs.Language.IsValid() && *Language != *Rhs.Language); + } + + bool operator>=(const FWwiseSharedLanguageId& Rhs) const + { + return (!Language.IsValid() && !Rhs.Language.IsValid()) + || (Language.IsValid() && Rhs.Language.IsValid() && *Language >= *Rhs.Language); + } + + bool operator>(const FWwiseSharedLanguageId& Rhs) const + { + return (Language.IsValid() && !Rhs.Language.IsValid()) + || (Language.IsValid() && Rhs.Language.IsValid() && *Language > *Rhs.Language); + } + + bool operator<=(const FWwiseSharedLanguageId& Rhs) const + { + return (!Language.IsValid() && !Rhs.Language.IsValid()) + || (Language.IsValid() && Rhs.Language.IsValid() && *Language <= *Rhs.Language); + } + + bool operator<(const FWwiseSharedLanguageId& Rhs) const + { + return (!Language.IsValid() && Rhs.Language.IsValid()) + || (Language.IsValid() && Rhs.Language.IsValid() && *Language < *Rhs.Language); + } +}; + +inline uint32 GetTypeHash(const FWwiseSharedLanguageId& Id) +{ + return GetTypeHash(Id.Language->LanguageId); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseSharedPlatformId.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseSharedPlatformId.h new file mode 100644 index 0000000..c7f0135 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/Public/Wwise/WwiseSharedPlatformId.h @@ -0,0 +1,69 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwisePlatformId.h" + +#include "WwiseSharedPlatformId.generated.h" + +USTRUCT(BlueprintType) +struct WWISERESOURCELOADER_API FWwiseSharedPlatformId +{ + GENERATED_BODY() + + TSharedRef Platform; + + FWwiseSharedPlatformId() : + Platform(new FWwisePlatformId) + {} + + FWwiseSharedPlatformId(const FGuid& InPlatformGuid, const FName& InPlatformName) : + Platform(new FWwisePlatformId(InPlatformGuid, InPlatformName)) + {} + +#if WITH_EDITORONLY_DATA + FWwiseSharedPlatformId(const FGuid& InPlatformGuid, const FName& InPlatformName, const FName& InRelativePath) : + Platform(new FWwisePlatformId(InPlatformGuid, InPlatformName, InRelativePath)) + {} +#endif + + const FGuid& GetPlatformGuid() const + { + return Platform->PlatformGuid; + } + + const FName& GetPlatformName() const + { + return Platform->PlatformName; + } + + bool operator==(const FWwiseSharedPlatformId& Rhs) const + { + return Platform->PlatformGuid == Rhs.Platform->PlatformGuid; + } + + bool operator!=(const FWwiseSharedPlatformId& Rhs) const + { + return Platform->PlatformGuid != Rhs.Platform->PlatformGuid; + } +}; + +inline uint32 GetTypeHash(const FWwiseSharedPlatformId& Id) +{ + return GetTypeHash(Id.Platform->PlatformGuid); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/WwiseResourceLoader.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/WwiseResourceLoader.Build.cs new file mode 100644 index 0000000..eed38b7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseResourceLoader/WwiseResourceLoader.Build.cs @@ -0,0 +1,39 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; + +public class WwiseResourceLoader : ModuleRules +{ + public WwiseResourceLoader(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange(new string[] { + "WwiseFileHandler" + }); + + PrivateDependencyModuleNames.AddRange(new string[] { + "Core", + "CoreUObject", + "Engine", + + "WwiseConcurrency", + "WwiseSoundEngine" + }); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Private/Wwise/SimpleExtSrc/WwiseSimpleExtScrManager.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Private/Wwise/SimpleExtSrc/WwiseSimpleExtScrManager.cpp new file mode 100644 index 0000000..cd6beb4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Private/Wwise/SimpleExtSrc/WwiseSimpleExtScrManager.cpp @@ -0,0 +1,487 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/SimpleExtSrc/WwiseSimpleExtSrcManager.h" +#include "Wwise/SimpleExtSrc/WwiseExternalSourceCookieDefaultMedia.h" +#include "Wwise/SimpleExtSrc/WwiseExternalSourceSettings.h" +#include "Wwise/SimpleExtSrc/WwiseExternalSourceMediaInfo.h" + +#include "Wwise/API/WwiseSoundEngineAPI.h" +#include "Wwise/Stats/SimpleExtSrc.h" +#include "Wwise/WwiseExternalSourceFileState.h" +#include "Wwise/WwiseSharedLanguageId.h" +#include "Wwise/WwiseSharedPlatformId.h" +#include "Wwise/WwiseResourceLoader.h" + +#include "Misc/FileHelper.h" +#include "Platforms/AkPlatformInfo.h" +#include "UObject/UObjectIterator.h" + +#include "AkAudioEvent.h" + +#include + +#if WITH_EDITORONLY_DATA +#include "Wwise/WwiseResourceCooker.h" +#include "Wwise/WwiseProjectDatabase.h" +#endif + + +FWwiseSimpleExtSrcManager::FWwiseSimpleExtSrcManager() +{ +#if WITH_EDITOR + auto* ExtSettings = GetMutableDefault(); + // When these settings change we will want to reset the External source manager and reload all external sources + ExtSettingsTableChangedDelegate = ExtSettings->OnTablesChanged.AddRaw(this, &FWwiseSimpleExtSrcManager::OnTablesChanged); +#endif + LoadMediaTables(); +} + +FWwiseSimpleExtSrcManager::~FWwiseSimpleExtSrcManager() +{ +} + +void FWwiseSimpleExtSrcManager::LoadMediaTables() +{ + const auto ExtSettings = GetDefault(); + + //This implementation of the WwiseExternalSourceManager uses data tables to keep track of all of the external source media in the project. + //This table could be automatically generated by external scripts. + //It would also be perfectly valid to use a different type of data structure or a handling class to retrieve external source media information. + if (ExtSettings->MediaInfoTable.IsValid()) + { + MediaInfoTable = TStrongObjectPtr(Cast(StreamableManager.LoadSynchronous(ExtSettings->MediaInfoTable))); + } + else + { + MediaInfoTable.Reset(); + } + + //The same goes for this (optional) table that sets the default media to be associated with each external source cookie + if (ExtSettings->ExternalSourceDefaultMedia.IsValid()) + { + ExternalSourceDefaultMedia = TStrongObjectPtr(Cast(StreamableManager.LoadSynchronous(ExtSettings->ExternalSourceDefaultMedia))); + } + else + { + ExternalSourceDefaultMedia.Reset(); + } + + if (MediaInfoTable.IsValid()) + { +#if WITH_EDITOR + MediaInfoTableChangedDelegate = MediaInfoTable->OnDataTableChanged().AddRaw(this, &FWwiseSimpleExtSrcManager::OnMediaInfoTableChanged); +#endif + FillMediaNameToIdMap(*MediaInfoTable.Get()); + } + else + { + UE_LOG(LogWwiseSimpleExtSrc, Warning, TEXT("Wwise Simple External Source: Media Info Table is not set. Please set table in the Project Settings.")); + } + + if (ExternalSourceDefaultMedia.IsValid()) + { +#if WITH_EDITOR + ExternalSourceDefaultMediaTableChangedDelegate = ExternalSourceDefaultMedia->OnDataTableChanged().AddRaw(this, &FWwiseSimpleExtSrcManager::OnDefaultExternalSourceTableChanged); +#endif + FillExternalSourceToMediaMap(*ExternalSourceDefaultMedia.Get()); + } + else + { + UE_LOG(LogWwiseSimpleExtSrc, Warning, TEXT("Wwise Simple External Source: External Source Default Media is not set. Please set table in the Project Settings.")); + } +} + +void FWwiseSimpleExtSrcManager::ReloadExternalSources() +{ + UE_LOG(LogWwiseSimpleExtSrc, Log, TEXT("Wwise Simple External Source: Reloading events with external sources")); + + //Unload all events containing external source data + TArray EventsToReload; + for (TObjectIterator EventAssetIt; EventAssetIt; ++EventAssetIt) + { + if (EventAssetIt->GetAllExternalSources().Num()>0) + { + EventAssetIt->UnloadData(); + EventsToReload.Add(*EventAssetIt); + } + } + + //Then reload them + for (UAkAudioEvent* Event : EventsToReload) + { + Event->LoadData(); + } + + UE_LOG(LogWwiseSimpleExtSrc, Verbose, TEXT("Wwise Simple External Source: %d events reloaded"), EventsToReload.Num()); +} + +FString FWwiseSimpleExtSrcManager::GetStagingDirectory() const +{ + return UWwiseExternalSourceSettings::GetExternalSourceStagingDirectory(); +} + +void FWwiseSimpleExtSrcManager::SetExternalSourceMediaById(const FName& ExternalSourceName, const int32 MediaId) +{ + AkUInt32 Cookie = FAkAudioDevice::GetShortIDFromString(ExternalSourceName.ToString()); + SetExternalSourceMedia(Cookie, MediaId, ExternalSourceName); +} + +void FWwiseSimpleExtSrcManager::SetExternalSourceMediaByName(const FName& ExternalSourceName, const FName& MediaName) +{ + AkUInt32 Cookie = FAkAudioDevice::GetShortIDFromString(ExternalSourceName.ToString()); + + if (const uint32* MediaId = MediaNameToId.Find(MediaName)) + { + SetExternalSourceMedia(Cookie, *MediaId, ExternalSourceName); + return; + } + + UE_LOG(LogWwiseSimpleExtSrc, Error, TEXT("Did not find media with name %s in MediaNameToId map."), *MediaName.ToString()); +} + +void FWwiseSimpleExtSrcManager::SetExternalSourceMediaWithIds(const int32 ExternalSourceId, const int32 MediaId) +{ + SetExternalSourceMedia(ExternalSourceId, MediaId); +} + +#if WITH_EDITORONLY_DATA +//This is called once per external source +void FWwiseSimpleExtSrcManager::Cook(FWwiseResourceCooker& InResourceCooker, const FWwiseExternalSourceCookedData& InCookedData, + TFunctionRef WriteAdditionalFile, + const FWwiseSharedPlatformId& InPlatform, const FWwiseSharedLanguageId& InLanguage) +{ + if (LIKELY(bCooked)) + { + UE_LOG(LogWwiseSimpleExtSrc, VeryVerbose, TEXT("FillExternalSourceToMediaMap: Media already packaged, Skipping media cook.")) + return; + } + bCooked = true; + + FString SourceDirectory = InResourceCooker.GetResourceLoader()->GetUnrealGeneratedSoundBanksPath(InPlatform.Platform->ExternalSourceRootPath); + + FString Context = TEXT("Iterating over default media"); + MediaInfoTable->ForeachRow(Context, + [this, SourceDirectory, &InResourceCooker, &WriteAdditionalFile](const FName& Key, const FWwiseExternalSourceMediaInfo& MediaInfo) + { + InResourceCooker.CookFileToSandbox(SourceDirectory / MediaInfo.MediaName.ToString(), FName(GetStagingDirectory() / MediaInfo.MediaName.ToString()), WriteAdditionalFile, true); + } + ); +} +#endif + + +void FWwiseSimpleExtSrcManager::LoadExternalSourceMedia(const uint32 InExternalSourceCookie, + const FName& InExternalSourceName, const FName& InRootPath, + FLoadExternalSourceCallback&& InCallback) +{ + uint32 MediaId; + { + FRWScopeLock Lock(CookieToMediaLock, FRWScopeLockType::SLT_ReadOnly); + const uint32* MediaIdPtr = CookieToMediaId.Find(InExternalSourceCookie); + if (UNLIKELY(!MediaIdPtr)) + { + UE_LOG(LogWwiseSimpleExtSrc, Warning, TEXT("LoadExternalSourceMedia: No media has been associated with External Source %" PRIu32 " (%s). No media will be loaded until the media is set."), + InExternalSourceCookie, *InExternalSourceName.ToString()); + InCallback(true); + return; + } + MediaId = *MediaIdPtr; + } + + UE_LOG(LogWwiseSimpleExtSrc, Verbose, TEXT("Loading External Source %" PRIu32 " (%s) Media %" PRIu32), + InExternalSourceCookie, *InExternalSourceName.ToString(), MediaId); + + IncrementFileStateUse(MediaId, EWwiseFileStateOperationOrigin::Loading, + [this, MediaId, &InRootPath]() mutable -> FWwiseFileStateSharedPtr + { + const FName RowName = FName(FString::FromInt(MediaId)); + const FString Context = TEXT("Find media info"); + if (UNLIKELY(!MediaInfoTable.IsValid())) + { + UE_LOG(LogWwiseSimpleExtSrc, Error, TEXT("Cannot read External Source Media information because datatable asset has not been loaded.")); + return {}; + } + else if (const FWwiseExternalSourceMediaInfo* ExternalSourceMediaInfoEntry = MediaInfoTable->FindRow(RowName, Context)) + { + return CreateOp(*ExternalSourceMediaInfoEntry, InRootPath); + } + else + { + UE_LOG(LogWwiseSimpleExtSrc, Warning, TEXT("LoadExternalSourceMedia: Could not find media info table entry for media id %" PRIu32), MediaId); + return {}; + } + }, [this, InExternalSourceCookie, MediaId, InCallback = MoveTemp(InCallback)](const FWwiseFileStateSharedPtr, bool bInResult) mutable + { + if (UNLIKELY(!bInResult)) + { + InCallback(false); + return; + } + FWwiseFileStateSharedPtr State; + { + FRWScopeLock StateLock(FileStatesByIdLock, FRWScopeLockType::SLT_ReadOnly); + const auto* StatePtr = FileStatesById.Find(MediaId); + if (UNLIKELY(!StatePtr || !StatePtr->IsValid())) + { + UE_LOG(LogWwiseSimpleExtSrc, Warning, TEXT("LoadExternalSourceMedia: Getting external source media state %" PRIu32 " failed after successful IncrementFileStateUse."), MediaId); + InCallback(false); + return; + } + State = *StatePtr; + } + auto* ExternalSourceFileState = State->GetStateAs(); + if (UNLIKELY(!ExternalSourceFileState)) + { + UE_LOG(LogWwiseSimpleExtSrc, Error, TEXT("LoadExternalSourceMedia: Getting external source media %" PRIu32 ": Could not cast to ExternalSourceState"), MediaId); + InCallback(false); + return; + } + + { + FRWScopeLock Lock(CookieToMediaLock, FRWScopeLockType::SLT_Write); + UE_LOG(LogWwiseSimpleExtSrc, Verbose, TEXT("Binding Cookie %" PRIu32 " to media %" PRIu32 "."), InExternalSourceCookie, MediaId); + CookieToMedia.Add(InExternalSourceCookie, ExternalSourceFileState); + } + InCallback(true); + }); +} + +void FWwiseSimpleExtSrcManager::UnloadExternalSourceMedia(const uint32 InExternalSourceCookie, + const FName& InExternalSourceName, const FName& InRootPath, + FUnloadExternalSourceCallback&& InCallback) +{ + uint32 MediaId; + { + FRWScopeLock Lock(CookieToMediaLock, FRWScopeLockType::SLT_ReadOnly); + const uint32* MediaIdPtr = CookieToMediaId.Find(InExternalSourceCookie); + if (UNLIKELY(!MediaIdPtr)) + { + UE_LOG(LogWwiseSimpleExtSrc, Warning, TEXT("UnloadExternalSourceMedia: No media has been associated with External Source %" PRIu32 " (%s). No media will be unloaded."), + InExternalSourceCookie, *InExternalSourceName.ToString()); + InCallback(); + return; + } + MediaId = *MediaIdPtr; + } + + UE_LOG(LogWwiseSimpleExtSrc, Verbose, TEXT("Unloading External Source %" PRIu32 " (%s) Media %" PRIu32), + InExternalSourceCookie, *InExternalSourceName.ToString(), MediaId); + + DecrementFileStateUse(MediaId, nullptr, EWwiseFileStateOperationOrigin::Loading, MoveTemp(InCallback)); +} + +void FWwiseSimpleExtSrcManager::OnTablesChanged() +{ + if (MediaInfoTable.IsValid()) + { + if (MediaInfoTable->OnDataTableChanged().IsBoundToObject(this)) + { + MediaInfoTable->OnDataTableChanged().RemoveAll(this); + } + } + + if (ExternalSourceDefaultMedia.IsValid()) + { + if (ExternalSourceDefaultMedia->OnDataTableChanged().IsBoundToObject(this)) + { + ExternalSourceDefaultMedia->OnDataTableChanged().RemoveAll(this); + } + } + + UE_LOG(LogWwiseSimpleExtSrc, Log, TEXT("Wwise Simple External Source: Change in external source tables settings detected. Reloading external source tables and events.")); + LoadMediaTables(); + ReloadExternalSources(); +} + +void FWwiseSimpleExtSrcManager::OnMediaInfoTableChanged() +{ + if (!MediaInfoTable.IsValid()) + { + return; + } + + UE_LOG(LogWwiseSimpleExtSrc, Log, TEXT("Wwise Simple External Source: Change in MediaInfoTable detected. Media name map will be refreshed and events with external sources will be reloaded.")); + FillMediaNameToIdMap(*MediaInfoTable.Get()); + ReloadExternalSources(); +} + +void FWwiseSimpleExtSrcManager::OnDefaultExternalSourceTableChanged() +{ + if (!ExternalSourceDefaultMedia.IsValid()) + { + return; + } + + UE_LOG(LogWwiseSimpleExtSrc, Log, TEXT("Wwise Simple External Source: Change in ExternalSourceDefaultMedia detected. External source cookie to media Id map will be refreshed and events with external sources will be reloaded.")); + FillExternalSourceToMediaMap(*ExternalSourceDefaultMedia.Get()); + ReloadExternalSources(); +} + +//It is possible for this to start empty, and for all media mappings to be set in blueprints +void FWwiseSimpleExtSrcManager::FillExternalSourceToMediaMap(const UDataTable& InMappingTable) +{ + if (CookieToMediaId.Num() > 0) + { + UE_LOG(LogWwiseSimpleExtSrc, VeryVerbose, TEXT("FillExternalSourceToMediaMap: Emptying external source to media map")); + CookieToMediaId.Empty(); + } + + FString Context = TEXT("Iterating over default media"); + UE_LOG(LogWwiseSimpleExtSrc, Verbose, TEXT("FillExternalSourceToMediaMap: Filling external source to media map")); + + InMappingTable.ForeachRow(Context, + [this](const FName& Key, const FWwiseExternalSourceCookieDefaultMedia& Value) + { + UE_LOG(LogWwiseSimpleExtSrc, VeryVerbose, TEXT("FillExternalSourceToMediaMap : External source %" PRIu32 " (%s) mapped to media %" PRIu32 " (%s)"), + Value.ExternalSourceCookie, *Value.ExternalSourceName, Value.MediaInfoId, *Value.MediaName); + CookieToMediaId.Add((uint32)Value.ExternalSourceCookie, Value.MediaInfoId); + } + ); +} + +// This is one way to make setting external source media by name work. +// An alternative approach would be to use a different structure than FWwiseExternalSourceMediaInfo where the media name is the "name" field in the data table (which is used for lookup), +// The media ID could then be generated dynamically using a hashing function such as FAkAudioDevice::GetShortIdFromString. +// In this case, we keep things simple by explicitly stating both the ID and media name in our Media Info Table. +void FWwiseSimpleExtSrcManager::FillMediaNameToIdMap(const UDataTable& InMediaTable) +{ + if (MediaNameToId.Num() > 0) + { + UE_LOG(LogWwiseSimpleExtSrc, VeryVerbose, TEXT("FillMediaNameToIdMap: Emptying Media Name To Id map")); + MediaNameToId.Empty(); + } + + FString Context = TEXT("Iterating over default media"); + UE_LOG(LogWwiseSimpleExtSrc, Verbose, TEXT("FillMediaNameToIdMap: Filling Media Name To Id map")); + + InMediaTable.ForeachRow(Context, + [this](const FName& Key, const FWwiseExternalSourceMediaInfo& Value) + { + UE_LOG(LogWwiseSimpleExtSrc, VeryVerbose, TEXT("FillMediaNameToIdMap: Adding media entry %" PRIu32 ": %s"), + Value.ExternalSourceMediaInfoId, *Value.MediaName.ToString()); + + if (UNLIKELY(MediaNameToId.Contains(Value.MediaName))) + { + UE_LOG(LogWwiseSimpleExtSrc, Warning, TEXT("FillMediaNameToIdMap: MediaNameToId already contains entry for %s mapped to ID %" PRIu32 ". It will not be updated."), + *Value.MediaName.ToString(), Value.ExternalSourceMediaInfoId); + return; + } + + MediaNameToId.Add(Value.MediaName, Value.ExternalSourceMediaInfoId); + } + ); +} + +FWwiseFileStateSharedPtr FWwiseSimpleExtSrcManager::CreateOp(const FWwiseExternalSourceMediaInfo& ExternalSourceMediaInfo, const FName& InRootPath) +{ + if (ExternalSourceMediaInfo.bIsStreamed) + { + return FWwiseFileStateSharedPtr(new FWwiseStreamedExternalSourceFileState( + ExternalSourceMediaInfo.MemoryAlignment, + ExternalSourceMediaInfo.bUseDeviceMemory, + ExternalSourceMediaInfo.PrefetchSize, + StreamingGranularity, + ExternalSourceMediaInfo.ExternalSourceMediaInfoId, + ExternalSourceMediaInfo.MediaName, + InRootPath, + ExternalSourceMediaInfo.CodecID)); + } + else + { + return FWwiseFileStateSharedPtr(new FWwiseInMemoryExternalSourceFileState( + ExternalSourceMediaInfo.MemoryAlignment, + ExternalSourceMediaInfo.bUseDeviceMemory, + ExternalSourceMediaInfo.ExternalSourceMediaInfoId, + ExternalSourceMediaInfo.MediaName, + InRootPath, + ExternalSourceMediaInfo.CodecID)); + } +} + +void FWwiseSimpleExtSrcManager::SetExternalSourceMedia(const uint32 ExternalSourceCookie, const uint32 MediaInfoId, const FName& ExternalSourceName) +{ + FEventRef Completed; + FileHandlerExecutionQueue.Async([this, ExternalSourceCookie, MediaInfoId, ExternalSourceName, &Completed]() mutable + { + if (!MediaInfoTable.IsValid()) + { + UE_LOG(LogWwiseSimpleExtSrc, Error, TEXT("Cannot read External Source Media information because datatable asset has not yet been loaded.")); + Completed->Trigger(); + return; + } + + FString LogExternalSourceName = ExternalSourceName.ToString(); + FName RowName = FName(FString::FromInt(MediaInfoId)); + FString Context = TEXT("Find external source media"); + const FWwiseExternalSourceMediaInfo* ExternalSourceMediaInfo = MediaInfoTable->FindRow(RowName, Context); + if (!ExternalSourceMediaInfo) + { + UE_LOG(LogWwiseSimpleExtSrc, Error, TEXT("Could not find media entry with id %" PRIu32 " in ExternalSourceMedia datatable."), MediaInfoId); + Completed->Trigger(); + return; + } + + bool bExternalSourceLoaded = false; + if (ExternalSourceStatesById.Contains(ExternalSourceCookie)) + { + bExternalSourceLoaded = true; + auto ExternalSourceCookedData = ExternalSourceStatesById.FindRef(ExternalSourceCookie); + LogExternalSourceName = ExternalSourceCookedData->DebugName.ToString(); + } + + if (bExternalSourceLoaded) + { + bool bPreviousMediaExists = false; + bPreviousMediaExists = CookieToMediaId.Contains(ExternalSourceCookie); + + if (bPreviousMediaExists && CookieToMediaId.FindRef(ExternalSourceCookie) == MediaInfoId) + { + UE_LOG(LogWwiseSimpleExtSrc, VeryVerbose, TEXT("SetExternalSourceMedia: MediaInfoId for %" PRIu32 " (%s) was already set to %" PRIu32 " (%s). Nothing to do."), + ExternalSourceCookie, *ExternalSourceName.ToString(), MediaInfoId, *ExternalSourceMediaInfo->MediaName.ToString()); + Completed->Trigger(); + return; + } + + if (bPreviousMediaExists) + { + UnloadExternalSourceMedia(ExternalSourceCookie, ExternalSourceName, FWwiseResourceLoader::Get()->GetUnrealExternalSourcePath(), []{}); + } + } + + { + FRWScopeLock Lock(CookieToMediaLock, FRWScopeLockType::SLT_Write); + CookieToMediaId.Add(ExternalSourceCookie, MediaInfoId); + } + + //We don't want to load the media if the external source with this cookie is not yet loaded + if (!bExternalSourceLoaded) + { + UE_LOG(LogWwiseSimpleExtSrc, Verbose, TEXT("SetExternalSourceMedia: Media %" PRIu32 " (%s) will be loaded when the external source %" PRIu32 " (%s) is loaded."), + MediaInfoId, *ExternalSourceMediaInfo->MediaName.ToString(), ExternalSourceCookie, *ExternalSourceName.ToString()) + Completed->Trigger(); + return; + } + + LoadExternalSourceMedia(ExternalSourceCookie, ExternalSourceName, FWwiseResourceLoader::Get()->GetUnrealExternalSourcePath(), [&Completed](bool) + { + Completed->Trigger(); + }); + }); + + Completed->Wait(); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Private/Wwise/Stats/SimpleExtSrc.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Private/Wwise/Stats/SimpleExtSrc.cpp new file mode 100644 index 0000000..a6d56d1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Private/Wwise/Stats/SimpleExtSrc.cpp @@ -0,0 +1,20 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Stats/SimpleExtSrc.h" + +DEFINE_LOG_CATEGORY(LogWwiseSimpleExtSrc); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Private/Wwise/WwiseSimpleExtSrcModule.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Private/Wwise/WwiseSimpleExtSrcModule.cpp new file mode 100644 index 0000000..bb50da3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Private/Wwise/WwiseSimpleExtSrcModule.cpp @@ -0,0 +1,65 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseSimpleExtSrcModule.h" +#include "Wwise/SimpleExtSrc/WwiseSimpleExtSrcManager.h" +#include "Wwise/Stats/SimpleExtSrc.h" + +#include "Modules/ModuleManager.h" + +#if WITH_EDITOR +#include "Wwise/SimpleExtSrc/WwiseExternalSourceSettings.h" +#include "ISettingsModule.h" +#endif + +#define LOCTEXT_NAMESPACE "SimpleExternalSourceModule" + +IMPLEMENT_MODULE(FWwiseSimpleExtSrcModule, WwiseSimpleExternalSource) + +IWwiseExternalSourceManager* FWwiseSimpleExtSrcModule::InstantiateExternalSourceManager() +{ + return new FWwiseSimpleExtSrcManager; +} + +void FWwiseSimpleExtSrcModule::StartupModule() +{ + FWwiseFileHandlerModule::StartupModule(); +#if WITH_EDITOR + + if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) + { + SettingsModule->RegisterSettings("Project", "Wwise", "Wwise External Sources", + LOCTEXT("RuntimeSettingsName", "Wwise Simple External Source Settings"), LOCTEXT("RuntimeSettingsDescription", "Set the external source data tables and staging directory"), + GetMutableDefault()); + } +#endif +} + +void FWwiseSimpleExtSrcModule::ShutdownModule() +{ + FWwiseFileHandlerModule::ShutdownModule(); + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +#if WITH_EDITOR + if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) + { + SettingsModule->UnregisterSettings("Project", "Wwise", "Wwise External Sources"); + } +#endif +} + +#undef LOCTEXT_NAMESPACE diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Public/Wwise/SimpleExtSrc/WwiseExternalSourceCookieDefaultMedia.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Public/Wwise/SimpleExtSrc/WwiseExternalSourceCookieDefaultMedia.h new file mode 100644 index 0000000..d95d231 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Public/Wwise/SimpleExtSrc/WwiseExternalSourceCookieDefaultMedia.h @@ -0,0 +1,51 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#include "Engine/DataTable.h" + +#include "WwiseExternalSourceCookieDefaultMedia.generated.h" + +//Maps from an external source cookie to an entry in the FWwiseExternalSourceMediaInfo table +// +USTRUCT(BlueprintType) +struct WWISESIMPLEEXTERNALSOURCE_API FWwiseExternalSourceCookieDefaultMedia : public FTableRowBase +{ + GENERATED_BODY() + +public: + + FWwiseExternalSourceCookieDefaultMedia(){} + + //Hash of the external source name, technically a uint32 + //Used as the search key external source in the default external source table + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ExternalSource) + int32 ExternalSourceCookie = 0; + + //Name of the external source name in Wwise + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ExternalSource) + FString ExternalSourceName; + + //Id of the media in the ExternalMediaInfoTable + //Used to lookup media in the ExternalMediaInfoTable + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ExternalSource) + int32 MediaInfoId = 0; + + // Not actually used, but helps keep track of what's pointing where. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ExternalSource) + FString MediaName; +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Public/Wwise/SimpleExtSrc/WwiseExternalSourceMediaInfo.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Public/Wwise/SimpleExtSrc/WwiseExternalSourceMediaInfo.h new file mode 100644 index 0000000..56037ba --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Public/Wwise/SimpleExtSrc/WwiseExternalSourceMediaInfo.h @@ -0,0 +1,53 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#include "Engine/DataTable.h" + +#include "WwiseExternalSourceMediaInfo.generated.h" + +//Contains the necessary info package and load an external source media +//There should be one entry for each external source media in the project +USTRUCT(BlueprintType) +struct WWISESIMPLEEXTERNALSOURCE_API FWwiseExternalSourceMediaInfo : public FTableRowBase +{ + GENERATED_BODY() + + FWwiseExternalSourceMediaInfo(){} + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ExternalSourceMedia) + int32 ExternalSourceMediaInfoId = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ExternalSourceMedia) + FName MediaName; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ExternalSourceMedia) + int32 CodecID = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ExternalSourceMedia) + bool bIsStreamed = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ExternalSourceMedia) + bool bUseDeviceMemory = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ExternalSourceMedia) + int32 MemoryAlignment = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ExternalSourceMedia) + int32 PrefetchSize = 0; + +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Public/Wwise/SimpleExtSrc/WwiseExternalSourceSettings.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Public/Wwise/SimpleExtSrc/WwiseExternalSourceSettings.h new file mode 100644 index 0000000..0d5b88e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Public/Wwise/SimpleExtSrc/WwiseExternalSourceSettings.h @@ -0,0 +1,81 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/EngineTypes.h" +#include "AkUnrealEditorHelper.h" +#include "Net/RepLayout.h" +#include "Wwise/Stats/SimpleExtSrc.h" + +#include "WwiseExternalSourceSettings.generated.h" + +DECLARE_MULTICAST_DELEGATE(FOnTablesChanged); + +UCLASS(config = Game) +class WWISESIMPLEEXTERNALSOURCE_API UWwiseExternalSourceSettings : public UObject +{ + GENERATED_BODY() + +public: + //Table of all information required to properly load all external source media in the project + //All files in this table are packaged in the built project + UPROPERTY(config, EditAnywhere, Category = ExternalSources, meta = (AllowedClasses = "/Script/Engine.DataTable")) + FSoftObjectPath MediaInfoTable; + + //Optional table that defines a default media entry in the MediaInfoTable to load when an External Source is loaded + UPROPERTY(config, EditAnywhere, Category = ExternalSources, meta = (AllowedClasses = "/Script/Engine.DataTable")) + FSoftObjectPath ExternalSourceDefaultMedia; + + //Staging location for External Source Media when cooking the project + //This is the location from which to load external source media in the built project + UPROPERTY(config, EditAnywhere, Category = ExternalSources, meta =(RelativeToGameContentDir)) + FDirectoryPath ExternalSourceStagingDirectory; + + FOnTablesChanged OnTablesChanged; + + static FString GetExternalSourceStagingDirectory() + { + if (const UWwiseExternalSourceSettings* ExtSettings = GetDefault()) + { + return ExtSettings->ExternalSourceStagingDirectory.Path; + } + UE_LOG(LogWwiseSimpleExtSrc, Error, + TEXT("UWwiseExternalSourceSettings::GetExternalSourceStagingDirectory : Could not get staging directory from external source settings")); + return {}; + } + +#if WITH_EDITOR + virtual void PostEditChangeProperty( struct FPropertyChangedEvent& PropertyChangedEvent ) override + { + Super::PostEditChangeProperty( PropertyChangedEvent ); + AkUnrealEditorHelper::SaveConfigFile(this); + const FName MemberPropertyName = (PropertyChangedEvent.MemberProperty != nullptr) ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None; + + if ( MemberPropertyName == GET_MEMBER_NAME_CHECKED(UWwiseExternalSourceSettings, MediaInfoTable) ) + { + OnTablesChanged.Broadcast(); + } + else if ( MemberPropertyName == GET_MEMBER_NAME_CHECKED(UWwiseExternalSourceSettings, ExternalSourceDefaultMedia)) + { + OnTablesChanged.Broadcast(); + } + } +#endif + +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Public/Wwise/SimpleExtSrc/WwiseSimpleExtSrcManager.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Public/Wwise/SimpleExtSrc/WwiseSimpleExtSrcManager.h new file mode 100644 index 0000000..03de8fd --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Public/Wwise/SimpleExtSrc/WwiseSimpleExtSrcManager.h @@ -0,0 +1,77 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkInclude.h" +#include "WwiseExternalSourceSettings.h" +#include "Wwise/WwiseExternalSourceManager.h" +#include "Engine/DataTable.h" +#include "Engine/StreamableManager.h" +#include "UObject/StrongObjectPtr.h" +#include "Wwise/WwiseExternalSourceManagerImpl.h" + +struct FWwiseExternalSourceMediaInfo; + +class WWISESIMPLEEXTERNALSOURCE_API FWwiseSimpleExtSrcManager : public FWwiseExternalSourceManagerImpl +{ +public: + FWwiseSimpleExtSrcManager(); + ~FWwiseSimpleExtSrcManager(); + + virtual void LoadMediaTables(); + virtual void ReloadExternalSources(); + + virtual FString GetStagingDirectory() const override; + + virtual void SetExternalSourceMediaById(const FName& ExternalSourceName, const int32 MediaId) override; + virtual void SetExternalSourceMediaByName(const FName& ExternalSourceName, const FName& MediaName) override; + virtual void SetExternalSourceMediaWithIds(const int32 ExternalSourceCookie, const int32 MediaId) override; + + #if WITH_EDITORONLY_DATA + virtual void Cook(FWwiseResourceCooker& InResourceCooker, const FWwiseExternalSourceCookedData& InCookedData, + TFunctionRef WriteAdditionalFile, + const FWwiseSharedPlatformId& InPlatform, const FWwiseSharedLanguageId& InLanguage) override; + #endif + +protected: + virtual void LoadExternalSourceMedia(const uint32 InExternalSourceCookie, const FName& InExternalSourceName, const FName& InRootPath, FLoadExternalSourceCallback&& InCallback) override; + virtual void UnloadExternalSourceMedia(const uint32 InExternalSourceCookie, const FName& InExternalSourceName, const FName& InRootPath, FUnloadExternalSourceCallback&& InCallback) override; + + virtual void OnTablesChanged(); + virtual void OnMediaInfoTableChanged(); + virtual void OnDefaultExternalSourceTableChanged(); + virtual void FillExternalSourceToMediaMap(const UDataTable& InMappingTable); + virtual void FillMediaNameToIdMap(const UDataTable& InMappingTable); + + virtual void SetExternalSourceMedia(const uint32 ExternalSourceCookie, const uint32 MediaInfoId, const FName& ExternalSourceName = FName()); + virtual FWwiseFileStateSharedPtr CreateOp(const FWwiseExternalSourceMediaInfo& ExternalSourceMediaInfo, const FName& InRootPath); + + TStrongObjectPtr MediaInfoTable; + TStrongObjectPtr ExternalSourceDefaultMedia; + FStreamableManager StreamableManager; + TMap CookieToMediaId; + TMap MediaNameToId; + + //We cook all media in one shot, so we use this to track whether this cooking has been performed yet + bool bCooked = false; +#if WITH_EDITOR + FDelegateHandle ExtSettingsTableChangedDelegate; + FDelegateHandle MediaInfoTableChangedDelegate; + FDelegateHandle ExternalSourceDefaultMediaTableChangedDelegate; +#endif +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Public/Wwise/Stats/SimpleExtSrc.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Public/Wwise/Stats/SimpleExtSrc.h new file mode 100644 index 0000000..fa305f5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Public/Wwise/Stats/SimpleExtSrc.h @@ -0,0 +1,22 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Logging/LogMacros.h" + +WWISESIMPLEEXTERNALSOURCE_API DECLARE_LOG_CATEGORY_EXTERN(LogWwiseSimpleExtSrc, Log, All); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Public/Wwise/WwiseSimpleExtSrcModule.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Public/Wwise/WwiseSimpleExtSrcModule.h new file mode 100644 index 0000000..cb3e140 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/Public/Wwise/WwiseSimpleExtSrcModule.h @@ -0,0 +1,30 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#include "Wwise/WwiseFileHandlerModuleImpl.h" + +class FWwiseSimpleExtSrcManager; + +class WWISESIMPLEEXTERNALSOURCE_API FWwiseSimpleExtSrcModule : public FWwiseFileHandlerModule +{ +public: + IWwiseExternalSourceManager* InstantiateExternalSourceManager() override; + + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/WwiseSimpleExternalSource.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/WwiseSimpleExternalSource.Build.cs new file mode 100644 index 0000000..2508e3d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSimpleExternalSource/WwiseSimpleExternalSource.Build.cs @@ -0,0 +1,63 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; + +public class WwiseSimpleExternalSource : ModuleRules +{ + public WwiseSimpleExternalSource(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + + "InputCore", + + "AkAudio", + "WwiseConcurrency", + "WwiseFileHandler", + "WwiseSoundEngine", + "WwiseResourceLoader" + } + ); + + if (Target.bBuildEditor) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "UnrealEd" + } + ); + } + if (Target.bBuildWithEditorOnlyData) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "WwiseProjectDatabase", + "WwiseResourceCooker" + } + ); + } + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Private/AkUnrealHelper.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Private/AkUnrealHelper.cpp new file mode 100644 index 0000000..545ef6d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Private/AkUnrealHelper.cpp @@ -0,0 +1,251 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "AkUnrealHelper.h" +#include "Wwise/Stats/SoundEngine.h" +#include "AkUEFeatures.h" +#if UE_5_1_OR_LATER +#include "Engine/HitResult.h" +#endif + +#include "Misc/Paths.h" + +namespace AkUnrealHelper +{ + const TCHAR* MediaFolderName = TEXT("Media"); + const TCHAR* ExternalSourceFolderName = TEXT("ExternalSources"); + constexpr auto SoundBankNamePrefix = TEXT("SB_"); + const FGuid InitBankID(0x701ECBBD, 0x9C7B4030, 0x8CDB749E, 0xE5D1C7B9); + + FString(*GetWwisePluginDirectoryPtr)(); + FString(*GetWwiseProjectPathPtr)(); + FString(*GetSoundBankDirectoryPtr)(); + FString(*GetStagePathPtr)(); + + void SetHelperFunctions(FString(* GetWwisePluginDirectoryImpl)(), FString(* GetWwiseProjectPathImpl)(), + FString(* GetSoundBankDirectoryImpl)(), FString(* GetStagePathImpl)()) + { + GetWwisePluginDirectoryPtr = GetWwisePluginDirectoryImpl; + GetWwiseProjectPathPtr = GetWwiseProjectPathImpl; + GetSoundBankDirectoryPtr = GetSoundBankDirectoryImpl; + GetStagePathPtr = GetStagePathImpl; + } + + FString GetWwisePluginDirectory() + { + if (!GetWwisePluginDirectoryPtr) + { + UE_LOG(LogWwiseSoundEngine, Error, TEXT("AkUnrealHelper::GetWwisePluginDirectory implementation not set.")); + return {}; + } + return GetWwisePluginDirectoryPtr(); + } + + FString GetWwiseProjectPath() + { + if (!GetWwiseProjectPathPtr) + { + UE_LOG(LogWwiseSoundEngine, Error, TEXT("AkUnrealHelper::GetWwiseProjectPath implementation not set.")); + return {}; + } + return GetWwiseProjectPathPtr(); + } + + FString GetSoundBankDirectory() + { + if (!GetSoundBankDirectoryPtr) + { + UE_LOG(LogWwiseSoundEngine, Error, TEXT("AkUnrealHelper::GetSoundBankDirectory implementation not set.")); + return {}; + } + return GetSoundBankDirectoryPtr(); + } + + FString GetStagePath() + { + if (!GetStagePathPtr) + { + UE_LOG(LogWwiseSoundEngine, Error, TEXT("AkUnrealHelper::GetStagePath implementation not set.")); + return {}; + } + return GetStagePathPtr(); + } + + void TrimPath(FString& Path) + { + Path.TrimStartAndEndInline(); + } + + FString GetProjectDirectory() + { + return FPaths::ConvertRelativePathToFull(FPaths::ProjectDir()); + } + + FString GetContentDirectory() + { + return FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir()); + } + + FString GetThirdPartyDirectory() + { + return FPaths::Combine(GetWwisePluginDirectory(), TEXT("ThirdParty")); + } + + FString GetExternalSourceDirectory() + { + return FPaths::Combine(GetSoundBankDirectory(), ExternalSourceFolderName); + } + + FString GetWwiseProjectDirectoryPath() + { + return FPaths::GetPath(GetWwiseProjectPath()) + TEXT("/"); + } + + bool MakePathRelativeToWwiseProject(FString& AbsolutePath) + { + + auto wwiseProjectRoot = AkUnrealHelper::GetWwiseProjectDirectoryPath(); +#if PLATFORM_WINDOWS + AbsolutePath.ReplaceInline(TEXT("/"), TEXT("\\")); + wwiseProjectRoot.ReplaceInline(TEXT("/"), TEXT("\\")); +#endif + bool success = FPaths::MakePathRelativeTo(AbsolutePath, *wwiseProjectRoot); +#if PLATFORM_WINDOWS + AbsolutePath.ReplaceInline(TEXT("/"), TEXT("\\")); +#endif + return success; + } + + FString GetWwiseSoundBankInfoCachePath() + { + return FPaths::Combine(FPaths::GetPath(GetWwiseProjectPath()), TEXT(".cache"), TEXT("SoundBankInfoCache.dat")); + } + + const TCHAR* GetResultString(AKRESULT InResult) + { + switch (InResult) + { + case AK_NotImplemented: return TEXT("This feature is not implemented."); + case AK_Success: return TEXT("The operation was successful."); + case AK_Fail: return TEXT("The operation failed."); + case AK_PartialSuccess: return TEXT("The operation succeeded partially."); + case AK_NotCompatible: return TEXT("Incompatible formats"); + case AK_AlreadyConnected: return TEXT("The stream is already connected to another node."); + case AK_InvalidFile: return TEXT("The provided file is the wrong format or unexpected values causes the file to be invalid."); + case AK_AudioFileHeaderTooLarge: return TEXT("The file header is too large."); + case AK_MaxReached: return TEXT("The maximum was reached."); + case AK_InvalidID: return TEXT("The ID is invalid."); + case AK_IDNotFound: return TEXT("The ID was not found."); + case AK_InvalidInstanceID: return TEXT("The InstanceID is invalid."); + case AK_NoMoreData: return TEXT("No more data is available from the source."); + case AK_InvalidStateGroup: return TEXT("The StateGroup is not a valid channel."); + case AK_ChildAlreadyHasAParent: return TEXT("The child already has a parent."); + case AK_InvalidLanguage: return TEXT("The language is invalid (applies to the Low-Level I/O)."); + case AK_CannotAddItseflAsAChild: return TEXT("It is not possible to add itself as its own child."); + case AK_InvalidParameter: return TEXT("Something is not within bounds, check the documentation of the function returning this code."); + case AK_ElementAlreadyInList: return TEXT("The item could not be added because it was already in the list."); + case AK_PathNotFound: return TEXT("This path is not known."); + case AK_PathNoVertices: return TEXT("Stuff in vertices before trying to start it"); + case AK_PathNotRunning: return TEXT("Only a running path can be paused."); + case AK_PathNotPaused: return TEXT("Only a paused path can be resumed."); + case AK_PathNodeAlreadyInList: return TEXT("This path is already there."); + case AK_PathNodeNotInList: return TEXT("This path is not there."); + case AK_DataNeeded: return TEXT("The consumer needs more."); + case AK_NoDataNeeded: return TEXT("The consumer does not need more."); + case AK_DataReady: return TEXT("The provider has available data."); + case AK_NoDataReady: return TEXT("The provider does not have available data."); + case AK_InsufficientMemory: return TEXT("Memory error."); + case AK_Cancelled: return TEXT("The requested action was cancelled (not an error)."); + case AK_UnknownBankID: return TEXT("Trying to load a bank using an ID which is not defined."); + case AK_BankReadError: return TEXT("Error while reading a bank."); + case AK_InvalidSwitchType: return TEXT("Invalid switch type (used with the switch container)"); + case AK_FormatNotReady: return TEXT("Source format not known yet."); + case AK_WrongBankVersion: return TEXT("The bank version is not compatible with the current bank reader."); + case AK_FileNotFound: return TEXT("File not found."); + case AK_DeviceNotReady: return TEXT("Specified ID doesn't match a valid hardware device: either the device doesn't exist or is disabled."); + case AK_BankAlreadyLoaded: return TEXT("The bank load failed because the bank is already loaded."); + case AK_RenderedFX: return TEXT("The effect on the node is rendered."); + case AK_ProcessNeeded: return TEXT("A routine needs to be executed on some CPU."); + case AK_ProcessDone: return TEXT("The executed routine has finished its execution."); + case AK_MemManagerNotInitialized: return TEXT("The memory manager should have been initialized at this point."); + case AK_StreamMgrNotInitialized: return TEXT("The stream manager should have been initialized at this point."); + case AK_SSEInstructionsNotSupported: return TEXT("The machine does not support SSE instructions (required on PC)."); + case AK_Busy: return TEXT("The system is busy and could not process the request."); + case AK_UnsupportedChannelConfig: return TEXT("Channel configuration is not supported in the current execution context."); + case AK_PluginMediaNotAvailable: return TEXT("Plugin media is not available for effect."); + case AK_MustBeVirtualized: return TEXT("Sound was Not Allowed to play."); + case AK_CommandTooLarge: return TEXT("SDK command is too large to fit in the command queue."); + case AK_RejectedByFilter: return TEXT("A play request was rejected due to the MIDI filter parameters."); + case AK_InvalidCustomPlatformName: return TEXT("Detecting incompatibility between Custom platform of banks and custom platform of connected application"); + case AK_DLLCannotLoad: return TEXT("Plugin DLL could not be loaded, either because it is not found or one dependency is missing."); + case AK_DLLPathNotFound: return TEXT("Plugin DLL search path could not be found."); + case AK_NoJavaVM: return TEXT("No Java VM provided in AkInitSettings."); + case AK_OpenSLError: return TEXT("OpenSL returned an error. Check error log for more details."); + case AK_PluginNotRegistered: return TEXT("Plugin is not registered. Make sure to implement a AK::PluginRegistration class for it and use AK_STATIC_LINK_PLUGIN in the game binary."); + case AK_DataAlignmentError: return TEXT("A pointer to audio data was not aligned to the platform's required alignment (check AkTypes.h in the platform-specific folder)"); + case AK_DeviceNotCompatible: return TEXT("Incompatible Audio device."); + case AK_DuplicateUniqueID: return TEXT("Two Wwise objects share the same ID."); + case AK_InitBankNotLoaded: return TEXT("The Init bank was not loaded yet, the sound engine isn't completely ready yet."); + case AK_DeviceNotFound: return TEXT("The specified device ID does not match with any of the output devices that the sound engine is currently using."); + case AK_PlayingIDNotFound: return TEXT("Calling a function with a playing ID that is not known."); + case AK_InvalidFloatValue: return TEXT("One parameter has a invalid float value such as NaN, INF or FLT_MAX."); + case AK_FileFormatMismatch: return TEXT("Media file format unexpected"); + case AK_NoDistinctListener: return TEXT("No distinct listener provided for AddOutput"); + case AK_ACP_Error: return TEXT("Generic XMA decoder error."); + case AK_ResourceInUse: return TEXT("Resource is in use and cannot be released."); + case AK_InvalidBankType: return TEXT("Invalid bank type. The bank type was either supplied through a function call (e.g. LoadBank) or obtained from a bank loaded from memory."); + case AK_AlreadyInitialized: return TEXT("Init() was called but that element was already initialized."); + case AK_NotInitialized: return TEXT("The component being used is not initialized. Most likely AK::SoundEngine::Init() was not called yet, or AK::SoundEngine::Term was called too early."); + case AK_FilePermissionError: return TEXT("The file access permissions prevent opening a file."); + case AK_UnknownFileError: return TEXT("Rare file error occured, as opposed to AK_FileNotFound or AK_FilePermissionError. This lumps all unrecognized OS file system errors."); + default: return TEXT("Unknown Error."); + } + } + + +#if WITH_EDITOR + FString GuidToBankName(const FGuid& Guid) + { + if (Guid == InitBankID) + { + return TEXT("Init"); + } + + return FString(SoundBankNamePrefix) + Guid.ToString(EGuidFormats::Digits); + } + + FGuid BankNameToGuid(const FString& BankName) + { + FString copy = BankName; + copy.RemoveFromStart(SoundBankNamePrefix); + + FGuid result; + FGuid::ParseExact(copy, EGuidFormats::Digits, result); + + return result; + } + + FString FormatFolderPath(const FString folderPath) + { + auto path = folderPath.Replace(TEXT("\\"), TEXT("/")); + if (path[0] == '/') { + path.RemoveAt(0); + } + return path; + } +#endif +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Private/Wwise/Stats/Global.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Private/Wwise/Stats/Global.cpp new file mode 100644 index 0000000..b2ad6a0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Private/Wwise/Stats/Global.cpp @@ -0,0 +1,20 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Stats/Global.h" + +DEFINE_LOG_CATEGORY(LogWwiseHints); \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Private/Wwise/Stats/NamedEvents.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Private/Wwise/Stats/NamedEvents.cpp new file mode 100644 index 0000000..d1a2610 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Private/Wwise/Stats/NamedEvents.cpp @@ -0,0 +1,27 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Stats/NamedEvents.h" +#include "Math/Color.h" + +namespace WwiseNamedEvents +{ + const FColor Color1{0, 84, 159}; + const FColor Color2{101, 45, 134}; + const FColor Color3{0, 159, 218}; + const FColor Color4{79, 157, 186}; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Private/Wwise/Stats/SoundEngine.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Private/Wwise/Stats/SoundEngine.cpp new file mode 100644 index 0000000..1b18be3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Private/Wwise/Stats/SoundEngine.cpp @@ -0,0 +1,20 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Stats/SoundEngine.h" + +DEFINE_LOG_CATEGORY(LogWwiseSoundEngine); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Private/Wwise/WwiseSoundEngineModule.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Private/Wwise/WwiseSoundEngineModule.cpp new file mode 100644 index 0000000..f55afc1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Private/Wwise/WwiseSoundEngineModule.cpp @@ -0,0 +1,100 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseSoundEngineModule.h" +#include "Wwise/API/WwiseCommAPI.h" +#include "Wwise/API/WwiseMemoryMgrAPI.h" +#include "Wwise/API/WwiseMonitorAPI.h" +#include "Wwise/API/WwiseMusicEngineAPI.h" +#include "Wwise/API/WwiseSoundEngineAPI.h" +#include "Wwise/API/WwiseSpatialAudioAPI.h" +#include "Wwise/API/WwiseStreamMgrAPI.h" +#include "Wwise/API/WwisePlatformAPI.h" +#include "Wwise/API/WAAPI.h" +#include "Wwise/Stats/SoundEngine.h" + +#include WWISE_SOUNDENGINE_VERSION_HEADER_PATH + +#include "Misc/ScopeLock.h" + +IMPLEMENT_MODULE(FWwiseSoundEngineModule, WwiseSoundEngine) + +IWwiseCommAPI* IWwiseSoundEngineModule::Comm = nullptr; +IWwiseMemoryMgrAPI* IWwiseSoundEngineModule::MemoryMgr = nullptr; +IWwiseMonitorAPI* IWwiseSoundEngineModule::Monitor = nullptr; +IWwiseMusicEngineAPI* IWwiseSoundEngineModule::MusicEngine = nullptr; +IWwiseSoundEngineAPI* IWwiseSoundEngineModule::SoundEngine = nullptr; +IWwiseSpatialAudioAPI* IWwiseSoundEngineModule::SpatialAudio = nullptr; +IWwiseStreamMgrAPI* IWwiseSoundEngineModule::StreamMgr = nullptr; + +IWwisePlatformAPI* IWwiseSoundEngineModule::Platform = nullptr; +IWAAPI* IWwiseSoundEngineModule::WAAPI = nullptr; + +IWwiseSoundEngineVersionModule* IWwiseSoundEngineModule::VersionInterface = nullptr; + +void FWwiseSoundEngineModule::StartupModule() +{ + UE_LOG(LogWwiseSoundEngine, Display, TEXT("Loading Wwise Sound Engine version %s"), TEXT(AK_WWISE_SOUNDENGINE_VERSION)); + + VersionInterface = new WWISE_SOUNDENGINE_VERSION_CLASS; + + Comm = VersionInterface->GetComm(); + MemoryMgr = VersionInterface->GetMemoryMgr(); + Monitor = VersionInterface->GetMonitor(); + MusicEngine = VersionInterface->GetMusicEngine(); + SoundEngine = VersionInterface->GetSoundEngine(); + SpatialAudio = VersionInterface->GetSpatialAudio(); + StreamMgr = VersionInterface->GetStreamMgr(); + + Platform = VersionInterface->GetPlatform(); + WAAPI = VersionInterface->GetWAAPI(); +} + +void FWwiseSoundEngineModule::ShutdownModule() +{ + DeleteInterface(); +} + +void FWwiseSoundEngineModule::DeleteInterface() +{ + static FCriticalSection CriticalSection; + FScopeLock Lock(&CriticalSection); + + if (!VersionInterface) + { + return; + } + delete VersionInterface; VersionInterface = nullptr; + + if (LIKELY(SoundEngine) && UNLIKELY(SoundEngine->IsInitialized())) + { + UE_LOG(LogWwiseSoundEngine, Error, TEXT("Shutting down Sound Engine module without deinitializing Low-Level Sound Engine")); + SoundEngine->Term(); + } + + UE_LOG(LogWwiseSoundEngine, Log, TEXT("Unloading Wwise Sound Engine")); + delete Comm; Comm = nullptr; + delete MemoryMgr; MemoryMgr = nullptr; + delete Monitor; Monitor = nullptr; + delete MusicEngine; MusicEngine = nullptr; + delete SoundEngine; SoundEngine = nullptr; + delete SpatialAudio; SpatialAudio = nullptr; + delete StreamMgr; StreamMgr = nullptr; + + delete Platform; Platform = nullptr; + delete WAAPI; WAAPI = nullptr; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/AkInclude.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/AkInclude.h new file mode 100644 index 0000000..e50e7f4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/AkInclude.h @@ -0,0 +1,31 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#include "HAL/PreprocessorHelpers.h" + +#if defined(AK_WWISE_SOUNDENGINE_2022_1) +#include "Wwise/AkInclude_2022_1.h" +#elif defined(AK_WWISE_SOUNDENGINE_2023_1) +#include "Wwise/AkInclude_2023_1.h" +#elif defined(AK_WWISE_SOUNDENGINE_2024_1) +#include "Wwise/AkInclude_2024_1.h" +#elif defined(AK_WWISE_SOUNDENGINE_2025_1) +#include "Wwise/AkInclude_2025_1.h" +#else +#error "This file should be updated so SN-DBS can support this version" +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/AkUEFeatures.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/AkUEFeatures.h new file mode 100644 index 0000000..c442f02 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/AkUEFeatures.h @@ -0,0 +1,51 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +// Defines which features of the Wwise-Unreal integration are supported in which version of UE. + +#pragma once + +#include "WwiseDefines.h" +#include "Containers/Ticker.h" + +// Styling naming changed between UE4 and UE5. +#if WITH_EDITOR +#if UE_5_0_OR_LATER +#include "Styling/AppStyle.h" +using FAkAppStyle = FAppStyle; +#else +#include "EditorStyleSet.h" +using FAkAppStyle = FEditorStyle; +#endif +#endif + +// UE 5.0 typedefs +#if UE_5_0_OR_LATER +using FUnrealFloatVector = FVector3f; +using FUnrealFloatVector2D = FVector2f; +using FUnrealFloatPlane = FPlane4f; +using FTickerDelegateHandle = FTSTicker::FDelegateHandle; +using FCoreTickerType = FTSTicker; +#else +using FUnrealFloatVector = FVector; +using FUnrealFloatVector2D = FVector2D; +using FCoreTickerType = FTicker; +using FUnrealFloatPlane = FPlane; +using FTickerDelegateHandle = FDelegateHandle; +#endif + + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/AkUnrealHelper.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/AkUnrealHelper.h new file mode 100644 index 0000000..5dec72c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/AkUnrealHelper.h @@ -0,0 +1,124 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkInclude.h" + +#include "AkUnrealHelper.generated.h" + +namespace AkUnrealHelper +{ + template + struct TMallocDelete + { + DECLARE_INLINE_TYPE_LAYOUT(TMallocDelete, NonVirtual); + + TMallocDelete() = default; + TMallocDelete(const TMallocDelete&) = default; + TMallocDelete& operator=(const TMallocDelete&) = default; + ~TMallocDelete() = default; + + template < + typename U, + typename = decltype(ImplicitConv((U*)nullptr)) + > + TMallocDelete(const TMallocDelete&) + { + } + + template < + typename U, + typename = decltype(ImplicitConv((U*)nullptr)) + > + TMallocDelete& operator=(const TMallocDelete&) + { + return *this; + } + + void operator()(T* Ptr) const + { + FMemory::Free(Ptr); + } + }; + + WWISESOUNDENGINE_API void SetHelperFunctions( + FString(*GetWwisePluginDirectoryImpl)(), + FString(*GetWwiseProjectPathImpl)(), + FString(*GetSoundBankDirectoryImpl)(), + FString(*GetStagePathImpl)() + ); + + WWISESOUNDENGINE_API FString GetWwisePluginDirectory(); + WWISESOUNDENGINE_API FString GetWwiseProjectPath(); + WWISESOUNDENGINE_API FString GetSoundBankDirectory(); + + WWISESOUNDENGINE_API void TrimPath(FString& Path); + + WWISESOUNDENGINE_API FString GetProjectDirectory(); + WWISESOUNDENGINE_API FString GetThirdPartyDirectory(); + WWISESOUNDENGINE_API FString GetContentDirectory(); + WWISESOUNDENGINE_API FString GetExternalSourceDirectory(); + + WWISESOUNDENGINE_API FString GetWwiseProjectDirectoryPath(); + WWISESOUNDENGINE_API FString GetWwiseSoundBankInfoCachePath(); + WWISESOUNDENGINE_API FString FormatFolderPath(FString folderPath); + WWISESOUNDENGINE_API bool MakePathRelativeToWwiseProject(FString& AbsolutePath); + + WWISESOUNDENGINE_API const TCHAR* GetResultString(AKRESULT InResult); + + extern WWISESOUNDENGINE_API const TCHAR* MediaFolderName; + + extern WWISESOUNDENGINE_API const FGuid InitBankID; + +#if WITH_EDITOR + WWISESOUNDENGINE_API FString GuidToBankName(const FGuid& Guid); + WWISESOUNDENGINE_API FGuid BankNameToGuid(const FString& BankName); +#endif +} + +USTRUCT(BlueprintType, Meta = (Category = "Wwise|Types", DisplayName = "AkUint64")) +struct FAkUInt64Wrapper +{ + GENERATED_BODY() + +public: + UPROPERTY(EditAnywhere, Category = "Value", DisplayName = "UInt64 Value") + uint64 UInt64Value = 0; +}; + +USTRUCT(BlueprintType, Meta = (Category = "Wwise|Types", DisplayName = "AkUInt32")) +struct FAkUInt32Wrapper +{ + GENERATED_BODY() + +public: + UPROPERTY(EditAnywhere, Category = "Value", DisplayName = "UInt32 Value") + uint32 UInt32Value = 0; +}; + +USTRUCT(BlueprintType, Meta = (Category = "Wwise|Types", DisplayName = "AkOutputDeviceID")) +struct FAkOutputDeviceID : public FAkUInt64Wrapper +{ + GENERATED_BODY() +}; + +USTRUCT(BlueprintType, Meta = (Category = "Wwise|Types", DisplayName = "AkUniqueID")) +struct FAkUniqueID : public FAkUInt32Wrapper +{ + GENERATED_BODY() +}; \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Generated/AkSwitchPlugins.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Generated/AkSwitchPlugins.h new file mode 100644 index 0000000..06923e7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Generated/AkSwitchPlugins.h @@ -0,0 +1,24 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#if PLATFORM_SWITCH +#include +#include +#include +#include +#include +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Generated/AkiOSPlugins.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Generated/AkiOSPlugins.h new file mode 100644 index 0000000..a4a758c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Generated/AkiOSPlugins.h @@ -0,0 +1,24 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#if PLATFORM_IOS +#include +#include +#include +#include +#include +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Generated/AktvOSPlugins.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Generated/AktvOSPlugins.h new file mode 100644 index 0000000..a9b6938 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Generated/AktvOSPlugins.h @@ -0,0 +1,21 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#if PLATFORM_TVOS +/// +/// +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/Platforms/Android/AndroidAPI.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/Platforms/Android/AndroidAPI.h new file mode 100644 index 0000000..7f8600c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/Platforms/Android/AndroidAPI.h @@ -0,0 +1,58 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "AkInclude.h" + +#include "Wwise/WwiseSoundEngineModule.h" + +#if defined(PLATFORM_ANDROID) && PLATFORM_ANDROID + +class IWwisePlatformAPI +{ +public: + inline static IWwisePlatformAPI* Get() + { + IWwiseSoundEngineModule::ForceLoadModule(); + return IWwiseSoundEngineModule::Platform; + } + + UE_NONCOPYABLE(IWwisePlatformAPI); + +protected: + IWwisePlatformAPI() = default; + +public: + virtual ~IWwisePlatformAPI() {} + + /// Get instance of OpenSL created by the sound engine at initialization. + /// \return NULL if sound engine is not initialized + virtual SLObjectItf GetWwiseOpenSLInterface() = 0; + + /// Gets specific settings for the fast audio path on Android. Call this function after AK::SoundEngine::GetDefaultSettings and AK::SoundEngine::GetPlatformDefaultSettings to modify settings for the fast path. + /// in_pfSettings.pJavaVM and in_pfSettings.jNativeActivity must be filled properly prior to calling GetFastPathSettings. + /// The fast path constraints are: + /// -The sample rate must match the hardware native sample rate + /// -The number of samples per frame must be a multiple of the hardware buffer size. + /// Not fulfilling these constraints makes the audio hardware less efficient. + /// In general, using the fast path means a higher CPU usage. Complex audio designs may not be feasible while using the fast path. + virtual AKRESULT GetFastPathSettings(AkInitSettings &in_settings, AkPlatformInitSettings &in_pfSettings) = 0; +}; + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/Platforms/HoloLensAPI.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/Platforms/HoloLensAPI.h new file mode 100644 index 0000000..96c6c14 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/Platforms/HoloLensAPI.h @@ -0,0 +1,96 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "AkInclude.h" + +#include "Wwise/WwiseSoundEngineModule.h" + +#if defined(PLATFORM_HOLOLENS) && PLATFORM_HOLOLENS + +class IWwisePlatformAPI +{ +public: + inline static IWwisePlatformAPI* Get() + { + IWwiseSoundEngineModule::ForceLoadModule(); + return IWwiseSoundEngineModule::Platform; + } + + UE_NONCOPYABLE(IWwisePlatformAPI); + +protected: + IWwisePlatformAPI() = default; + +public: + virtual ~IWwisePlatformAPI() {} + + /// Finds the device ID for particular Audio Endpoint. + /// \note CoInitialize must have been called for the calling thread. See Microsoft's documentation about CoInitialize for more details. + /// \return A device ID to use with AkPlatformInitSettings.idAudioDevice + virtual AkUInt32 GetDeviceID(IMMDevice* in_pDevice) = 0; + + /// Finds an audio endpoint that matches the token in the device name or device ID and returns an ID. + /// This is a helper function that searches in the device ID (as returned by IMMDevice->GetId) and + /// in the property PKEY_Device_FriendlyName. The token parameter is case-sensitive. If you need to do matching on different conditions, use IMMDeviceEnumerator directly and AK::GetDeviceID. + /// \note CoInitialize must have been called for the calling thread. See Microsoft's documentation about CoInitialize for more details. + /// \return The device ID as returned by IMMDevice->GetId, hashed by AK::SoundEngine::GetIDFromName() + virtual AkUInt32 GetDeviceIDFromName(wchar_t* in_szToken) = 0; + + /// Get the user-friendly name for the specified device. Call repeatedly with index starting at 0 and increasing to get all available devices, including disabled and unplugged devices, until the returned string is null and out_uDeviceID is zero. + /// The number of indexable devices for the given uDeviceStateMask can be fetched by calling AK::SoundEngine::GetWindowsDeviceCount(). + /// You can also get the default device information by specifying index=-1. The default device is the one with a green checkmark in the Audio Playback Device panel in Windows. + /// The returned out_uDeviceID parameter is the Device ID to use with Wwise. Use it to specify the main device in AkPlatformInitSettings.idAudioDevice. + /// \note CoInitialize must have been called for the calling thread. See Microsoft's documentation about CoInitialize for more details. + /// \return The name of the device at the "index" specified. The pointer is valid until the next call to GetWindowsDeviceName. + virtual const wchar_t* GetWindowsDeviceName( + AkInt32 index, ///< Index of the device in the array. -1 to get information on the default device. + AkUInt32 &out_uDeviceID, ///< Device ID for Wwise. This is the same as what is returned from AK::GetDeviceID and AK::GetDeviceIDFromName. Use it to specify the main device in AkPlatformInitSettings.idAudioDevice. + AkAudioDeviceState uDeviceStateMask = AkDeviceState_All ///< Optional bitmask used to filter the device based on their state. + ) = 0; + + /// Get the number of Audio Endpoints available for the specified device state mask. + /// \note CoInitialize must have been called for the calling thread. See Microsoft's documentation about CoInitialize for more details. + /// \return The number of Audio Endpoints available for the specified device state mask. + virtual AkUInt32 GetWindowsDeviceCount( + AkAudioDeviceState uDeviceStateMask = AkDeviceState_All ///< Optional bitmask used to filter the device based on their state. + ) = 0; + + /// Get the Audio Endpoint for the specified device index. Call repeatedly with index starting at 0 and increasing to get all available devices, including disabled and unplugged devices, until the false is returned. + /// You can also get the default device information by specifying index=-1. The default device is the one with a green checkmark in the Audio Playback Device panel in Windows. + /// The returned out_uDeviceID parameter is the Device ID to use with Wwise. Use it to specify the main device in AkPlatformInitSettings.idAudioDevice. + /// The returned out_ppDevice is a pointer to a pointer variable to which the method writes the address of the IMMDevice. out_ppDevice is optional; if it is null, then no action is taken. + /// If the method returns false, *out_ppDevice is null. If the method successed, *out_ppDevice will be a counted reference to the interface, and the caller is responsible for releasing the interface when it is no longer needed, by calling Release(), or encapsulating the device in a COM Smart Pointer. + /// \note CoInitialize must have been called for the calling thread. See Microsoft's documentation about CoInitialize for more details. + /// \return Whether or not a device was found at the given index. + virtual bool GetWindowsDevice( + AkInt32 in_index, ///< Index of the device in the array. -1 to get information on the default device. + AkUInt32& out_uDeviceID, ///< Device ID for Wwise. This is the same as what is returned from AK::GetDeviceID and AK::GetDeviceIDFromName. Use it to specify the main device in AkPlatformInitSettings.idAudioDevice. + IMMDevice** out_ppDevice, ///< pointer to a pointer variable to which the method writes the address of the IMMDevice in question. + AkAudioDeviceState uDeviceStateMask = AkDeviceState_All ///< Optional bitmask used to filter the device based on their state. + ) = 0; + + /// Get the device ID corresponding to a Universal Windows Platform Gamepad reference. This device ID can be used to add/remove motion output for that gamepad. + /// \note The ID returned is unique to Wwise and does not correspond to any sensible value outside of Wwise. + /// \note This function is only available for project code using C++/CX. + /// \return Unique device ID, or AK_INVALID_DEVICE_ID if the reference is no longer valid (such as if the gamepad was disconnected) + //virtual AkDeviceID GetDeviceIDFromGamepad(Windows::Gaming::Input::Gamepad^ rGamepad) = 0; +}; + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/Platforms/IOS/IOSAPI.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/Platforms/IOS/IOSAPI.h new file mode 100644 index 0000000..01785cc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/Platforms/IOS/IOSAPI.h @@ -0,0 +1,59 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "AkInclude.h" + +#include "Wwise/WwiseSoundEngineModule.h" + +#if defined(PLATFORM_TVOS) && PLATFORM_TVOS +#include "Wwise/API/Platforms/TVOS/TVOSAPI.h" +#elif defined(PLATFORM_IOS) && PLATFORM_IOS + +class IWwisePlatformAPI +{ +public: + inline static IWwisePlatformAPI* Get() + { + IWwiseSoundEngineModule::ForceLoadModule(); + return IWwiseSoundEngineModule::Platform; + } + + UE_NONCOPYABLE(IWwisePlatformAPI); + +protected: + IWwisePlatformAPI() = default; + +public: + virtual ~IWwisePlatformAPI() {} + + /// Change the category and options of the app's AVAudioSession without restarting the entire Sound Engine. + /// \remark + /// As per Apple recommendations, the AVAudioSession will be deactivated and then reactivated. Therefore, + /// the primary output device must be reinitialized, which causes all audio to stop for a short period of time. + /// For a seamless transition, call this API during moments of complete silence in your game. + /// + /// \sa + /// - \ref AkAudioSessionProperties + virtual void ChangeAudioSessionProperties( + const AkAudioSessionProperties &in_properties ///< New properties to apply to the app's AVAudioSession shared instance. + ) = 0; +}; + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/Platforms/Linux/LinuxAPI.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/Platforms/Linux/LinuxAPI.h new file mode 100644 index 0000000..35e5ade --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/Platforms/Linux/LinuxAPI.h @@ -0,0 +1,45 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "AkInclude.h" + +#include "Wwise/WwiseSoundEngineModule.h" + +#if defined(PLATFORM_LINUX) && PLATFORM_LINUX + +class IWwisePlatformAPI +{ +public: + inline static IWwisePlatformAPI* Get() + { + IWwiseSoundEngineModule::ForceLoadModule(); + return IWwiseSoundEngineModule::Platform; + } + + UE_NONCOPYABLE(IWwisePlatformAPI); + +protected: + IWwisePlatformAPI() = default; + +public: + virtual ~IWwisePlatformAPI() {} +}; + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/Platforms/Mac/MacAPI.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/Platforms/Mac/MacAPI.h new file mode 100644 index 0000000..c7b58c4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/Platforms/Mac/MacAPI.h @@ -0,0 +1,45 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "AkInclude.h" + +#include "Wwise/WwiseSoundEngineModule.h" + +#if defined(PLATFORM_MAC) && PLATFORM_MAC + +class IWwisePlatformAPI +{ +public: + inline static IWwisePlatformAPI* Get() + { + IWwiseSoundEngineModule::ForceLoadModule(); + return IWwiseSoundEngineModule::Platform; + } + + UE_NONCOPYABLE(IWwisePlatformAPI); + +protected: + IWwisePlatformAPI() = default; + +public: + virtual ~IWwisePlatformAPI() {} +}; + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/Platforms/TVOS/TVOSAPI.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/Platforms/TVOS/TVOSAPI.h new file mode 100644 index 0000000..1ac3fee --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/Platforms/TVOS/TVOSAPI.h @@ -0,0 +1,57 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "AkInclude.h" + +#include "Wwise/WwiseSoundEngineModule.h" + +#if defined(PLATFORM_TVOS) && PLATFORM_TVOS + +class IWwisePlatformAPI +{ +public: + inline static IWwisePlatformAPI* Get() + { + IWwiseSoundEngineModule::ForceLoadModule(); + return IWwiseSoundEngineModule::Platform; + } + + UE_NONCOPYABLE(IWwisePlatformAPI); + +protected: + IWwisePlatformAPI() = default; + +public: + virtual ~IWwisePlatformAPI() {} + + /// Change the category and options of the app's AVAudioSession without restarting the entire Sound Engine. + /// \remark + /// As per Apple recommendations, the AVAudioSession will be deactivated and then reactivated. Therefore, + /// the primary output device must be reinitialized, which causes all audio to stop for a short period of time. + /// For a seamless transition, call this API during moments of complete silence in your game. + /// + /// \sa + /// - \ref AkAudioSessionProperties + virtual void ChangeAudioSessionProperties( + const AkAudioSessionProperties &in_properties ///< New properties to apply to the app's AVAudioSession shared instance. + ) = 0; +}; + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/Platforms/Windows/WindowsAPI.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/Platforms/Windows/WindowsAPI.h new file mode 100644 index 0000000..8b3388f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/Platforms/Windows/WindowsAPI.h @@ -0,0 +1,90 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "AkInclude.h" + +#include "Wwise/WwiseSoundEngineModule.h" + +#if defined(PLATFORM_WINDOWS) && PLATFORM_WINDOWS && (!defined(PLATFORM_WINGDK) || !PLATFORM_WINGDK) + +class IWwisePlatformAPI +{ +public: + inline static IWwisePlatformAPI* Get() + { + IWwiseSoundEngineModule::ForceLoadModule(); + return IWwiseSoundEngineModule::Platform; + } + + UE_NONCOPYABLE(IWwisePlatformAPI); + +protected: + IWwisePlatformAPI() = default; + +public: + virtual ~IWwisePlatformAPI() {} + + /// Finds the device ID for particular Audio Endpoint. + /// \note CoInitialize must have been called for the calling thread. See Microsoft's documentation about CoInitialize for more details. + /// \return A device ID to use with AkPlatformInitSettings.idAudioDevice + virtual AkUInt32 GetDeviceID(IMMDevice* in_pDevice) = 0; + + /// Finds an audio endpoint that matches the token in the device name or device ID and returns an ID. + /// This is a helper function that searches in the device ID (as returned by IMMDevice->GetId) and + /// in the property PKEY_Device_FriendlyName. The token parameter is case-sensitive. If you need to do matching on different conditions, use IMMDeviceEnumerator directly and AK::GetDeviceID. + /// \note CoInitialize must have been called for the calling thread. See Microsoft's documentation about CoInitialize for more details. + /// \return The device ID as returned by IMMDevice->GetId, hashed by AK::SoundEngine::GetIDFromName() + virtual AkUInt32 GetDeviceIDFromName(wchar_t* in_szToken) = 0; + + /// Get the user-friendly name for the specified device. Call repeatedly with index starting at 0 and increasing to get all available devices, including disabled and unplugged devices, until the returned string is null and out_uDeviceID is zero. + /// The number of indexable devices for the given uDeviceStateMask can be fetched by calling AK::SoundEngine::GetWindowsDeviceCount(). + /// You can also get the default device information by specifying index=-1. The default device is the one with a green checkmark in the Audio Playback Device panel in Windows. + /// The returned out_uDeviceID parameter is the Device ID to use with Wwise. Use it to specify the main device in AkPlatformInitSettings.idAudioDevice. + /// \note CoInitialize must have been called for the calling thread. See Microsoft's documentation about CoInitialize for more details. + /// \return The name of the device at the "index" specified. The pointer is valid until the next call to GetWindowsDeviceName. + virtual const wchar_t* GetWindowsDeviceName( + AkInt32 index, ///< Index of the device in the array. -1 to get information on the default device. + AkUInt32 &out_uDeviceID, ///< Device ID for Wwise. This is the same as what is returned from AK::GetDeviceID and AK::GetDeviceIDFromName. Use it to specify the main device in AkPlatformInitSettings.idAudioDevice. + AkAudioDeviceState uDeviceStateMask = AkDeviceState_All ///< Optional bitmask used to filter the device based on their state. + ) = 0; + + /// Get the number of Audio Endpoints available for the specified device state mask. + /// \note CoInitialize must have been called for the calling thread. See Microsoft's documentation about CoInitialize for more details. + /// \return The number of Audio Endpoints available for the specified device state mask. + virtual AkUInt32 GetWindowsDeviceCount( + AkAudioDeviceState uDeviceStateMask = AkDeviceState_All ///< Optional bitmask used to filter the device based on their state. + ) = 0; + + /// Get the Audio Endpoint for the specified device index. Call repeatedly with index starting at 0 and increasing to get all available devices, including disabled and unplugged devices, until the false is returned. + /// You can also get the default device information by specifying index=-1. The default device is the one with a green checkmark in the Audio Playback Device panel in Windows. + /// The returned out_uDeviceID parameter is the Device ID to use with Wwise. Use it to specify the main device in AkPlatformInitSettings.idAudioDevice. + /// The returned out_ppDevice is a pointer to a pointer variable to which the method writes the address of the IMMDevice. out_ppDevice is optional; if it is null, then no action is taken. + /// If the method returns false, *out_ppDevice is null. If the method successed, *out_ppDevice will be a counted reference to the interface, and the caller is responsible for releasing the interface when it is no longer needed, by calling Release(), or encapsulating the device in a COM Smart Pointer. + /// \note CoInitialize must have been called for the calling thread. See Microsoft's documentation about CoInitialize for more details. + /// \return Whether or not a device was found at the given index. + virtual bool GetWindowsDevice( + AkInt32 in_index, ///< Index of the device in the array. -1 to get information on the default device. + AkUInt32& out_uDeviceID, ///< Device ID for Wwise. This is the same as what is returned from AK::GetDeviceID and AK::GetDeviceIDFromName. Use it to specify the main device in AkPlatformInitSettings.idAudioDevice. + IMMDevice** out_ppDevice, ///< pointer to a pointer variable to which the method writes the address of the IMMDevice in question. + AkAudioDeviceState uDeviceStateMask = AkDeviceState_All ///< Optional bitmask used to filter the device based on their state. + ) = 0; +}; + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WAAPI.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WAAPI.h new file mode 100644 index 0000000..45861e3 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WAAPI.h @@ -0,0 +1,65 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "AkInclude.h" + +#include "Wwise/WwiseSoundEngineModule.h" + +class WWISESOUNDENGINE_API IWAAPI +{ +public: + inline static IWAAPI* Get() + { + IWwiseSoundEngineModule::ForceLoadModule(); + return IWwiseSoundEngineModule::WAAPI; + } + + UE_NONCOPYABLE(IWAAPI); +protected: + IWAAPI() = default; +public: + virtual ~IWAAPI() {} + +#if AK_SUPPORT_WAAPI + class WWISESOUNDENGINE_API Client + { + public: + UE_NONCOPYABLE(Client); + Client() {} + virtual ~Client() {} + + virtual bool Connect(const char* in_uri, unsigned int in_port, AK::WwiseAuthoringAPI::disconnectHandler_t disconnectHandler = nullptr, int in_timeoutMs = -1) = 0; + virtual bool IsConnected() const = 0; + virtual void Disconnect() = 0; + + virtual bool Subscribe(const char* in_uri, const char* in_options, AK::WwiseAuthoringAPI::Client::WampEventCallback in_callback, uint64_t& out_subscriptionId, std::string& out_result, int in_timeoutMs = -1) = 0; + virtual bool Subscribe(const char* in_uri, const AK::WwiseAuthoringAPI::AkJson& in_options, AK::WwiseAuthoringAPI::Client::WampEventCallback in_callback, uint64_t& out_subscriptionId, AK::WwiseAuthoringAPI::AkJson& out_result, int in_timeoutMs = -1) = 0; + + virtual bool Unsubscribe(const uint64_t& in_subscriptionId, std::string& out_result, int in_timeoutMs = -1) = 0; + virtual bool Unsubscribe(const uint64_t& in_subscriptionId, AK::WwiseAuthoringAPI::AkJson& out_result, int in_timeoutMs = -1) = 0; + + virtual bool Call(const char* in_uri, const char* in_args, const char* in_options, std::string& out_result, int in_timeoutMs = -1) = 0; + virtual bool Call(const char* in_uri, const AK::WwiseAuthoringAPI::AkJson& in_args, const AK::WwiseAuthoringAPI::AkJson& in_options, AK::WwiseAuthoringAPI::AkJson& out_result, int in_timeoutMs = -1) = 0; + }; + virtual Client* NewClient() = 0; + + virtual std::string GetJsonString(const AK::WwiseAuthoringAPI::JsonProvider&) = 0; +#endif // AK_SUPPORT_WAAPI +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwiseCommAPI.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwiseCommAPI.h new file mode 100644 index 0000000..0542e59 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwiseCommAPI.h @@ -0,0 +1,107 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "AkInclude.h" + +#include "Wwise/WwiseSoundEngineModule.h" + +class IWwiseCommAPI +{ +public: + inline static IWwiseCommAPI* Get() + { + IWwiseSoundEngineModule::ForceLoadModule(); + return IWwiseSoundEngineModule::Comm; + } + + UE_NONCOPYABLE(IWwiseCommAPI); +protected: + IWwiseCommAPI() = default; +public: + virtual ~IWwiseCommAPI() {} + + /////////////////////////////////////////////////////////////////////// + /// @name Initialization + //@{ + + /// Initializes the communication module. When this is called, and AK::SoundEngine::RenderAudio() + /// is called periodically, you may use the authoring tool to connect to the sound engine. + /// + /// \warning This function must be called after the sound engine and memory manager have + /// been properly initialized. + /// + /// + /// \remark The AkCommSettings structure should be initialized with + /// AK::Comm::GetDefaultInitSettings(). You can then change some of the parameters + /// before calling this function. + /// + /// \return + /// - AK_Success if initialization was successful. + /// - AK_InvalidParameter if one of the settings is invalid. + /// - AK_InsufficientMemory if the specified pool size is too small for initialization. + /// - AK_Fail for other errors. + /// + /// \sa + /// - \ref initialization_comm + /// - AK::Comm::GetDefaultInitSettings() + /// - AkCommSettings::Ports + virtual AKRESULT Init( + const AkCommSettings& in_settings///< Initialization settings. + ) = 0; + + /// Gets the last error from the OS-specific communication library. + /// \return The system error code. Check the code in the platform manufacturer documentation for details about the error. + virtual AkInt32 GetLastError() = 0; + + /// Gets the communication module's default initialization settings values. + /// \sa + /// - \ref initialization_comm + /// - AK::Comm::Init() + virtual void GetDefaultInitSettings( + AkCommSettings& out_settings ///< Returned default initialization settings. + ) = 0; + + /// Terminates the communication module. + /// \warning This function must be called before the memory manager is terminated. + /// \sa + /// - \ref termination_comm + virtual void Term() = 0; + + /// Terminates and reinitialize the communication module using current settings. + /// + /// \return + /// - AK_Success if initialization was successful. + /// - AK_InvalidParameter if one of the settings is invalid. + /// - AK_InsufficientMemory if the specified pool size is too small for initialization. + /// - AK_Fail for other errors. + /// + /// \sa + /// - \ref AK::SoundEngine::iOS::WakeupFromSuspend() + virtual AKRESULT Reset() = 0; + + + /// Get the initialization settings currently in use by the CommunicationSystem + /// + /// \return + /// - AK_Success if initialization was successful. + virtual const AkCommSettings& GetCurrentSettings() = 0; + + //@} +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwiseMemoryMgrAPI.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwiseMemoryMgrAPI.h new file mode 100644 index 0000000..11cb009 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwiseMemoryMgrAPI.h @@ -0,0 +1,238 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "AkInclude.h" + +#include "Wwise/WwiseSoundEngineModule.h" + +class IWwiseMemoryMgrAPI +{ +public: + static IWwiseMemoryMgrAPI* Get() + { + IWwiseSoundEngineModule::ForceLoadModule(); + return IWwiseSoundEngineModule::MemoryMgr; + } + + UE_NONCOPYABLE(IWwiseMemoryMgrAPI); +protected: + inline IWwiseMemoryMgrAPI() = default; +public: + virtual ~IWwiseMemoryMgrAPI() {} + + /// Initialize the default implementation of the Memory Manager. + /// \sa AK::MemoryMgr + virtual AKRESULT Init( + AkMemSettings* in_pSettings ///< Memory manager initialization settings. + ) = 0; + + /// Obtain the default initialization settings for the default implementation of the Memory Manager. + virtual void GetDefaultSettings( + AkMemSettings& out_pMemSettings ///< Memory manager default initialization settings. + ) = 0; + + //////////////////////////////////////////////////////////////////////// + /// @name Initialization + //@{ + + /// Query whether the Memory Manager has been sucessfully initialized. + /// \warning This function is not thread-safe. It should not be called at the same time as MemoryMgr::Init or MemoryMgr::Term. + /// \return True if the Memory Manager is initialized, False otherwise + /// \sa + /// - AK::MemoryMgr::Init() + /// - \ref memorymanager + virtual bool IsInitialized() = 0; + + /// Terminate the Memory Manager. + /// \warning This function is not thread-safe. It is not valid to allocate memory or otherwise interact with the memory manager during or after this call. + /// \sa + /// - \ref memorymanager + virtual void Term() = 0; + + /// Performs whatever steps are required to initialize a thread for use with the memory manager. + /// For example initializing thread local storage that the allocator requires to work. + /// The default implementation of the memory manager performs thread initialization automatically and therefore this call is optional. + /// For implementations where the cost of automatically initializing a thread for use with an allocator would be prohibitively expensive + /// this call allows you to perform the initialization once during, for example, thread creation. + /// \sa + /// - AkMemInitForThread + virtual void InitForThread() = 0; + + /// Allows you to manually terminate a thread for use with the memory manager. + /// The default implementation of the memory manager requires that all threads that interact with the memory manager call this function prior + /// to either their termination or the termination of the memory manager. Threads not created by the sound engine itself will not have this + /// function called for them automatically. + /// Take care to call this function for any thread, not owned by wwise, that may have interacted with the memory manager. For example job system workers. + /// \sa + /// - AkMemTermForThread + virtual void TermForThread() = 0; + + /// Allows you to "trim" a thread being used with the memory manager. + /// This is a function that will be called periodically by some Wwise-owned threads, + /// so that any thread-local state can be cleaned up in order to return memory for other systems to use. + /// For example, this can be used to return thread-local heaps to global stores or to finalize other deferred operations. + /// This function is only required for optimization purposes and does not have to be defined. + /// Therefore, unlike TermForThread, this is not expected to be called in all scenarios by Wwise. + /// It is also recommended to be called by game engine integrations in any worker threads that run Wwise jobs. + /// Refer to \ref eventmgrthread_jobmgr_best_practices for more information. + /// \sa + /// - AkMemTrimForThread + virtual void TrimForThread() = 0; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Memory Allocation + //@{ + + /// Allocate memory: debug version. + /// \return A pointer to the start of the allocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + virtual void* dMalloc( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + size_t in_uSize, ///< Number of bytes to allocate + const char* in_pszFile, ///< Debug file name + AkUInt32 in_uLine ///< Debug line number + ) = 0; + + /// Allocate memory. + /// \return A pointer to the start of the allocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + virtual void* Malloc( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + size_t in_uSize ///< Number of bytes to allocate + ) = 0; + + /// Reallocate memory: debug version. + /// \return A pointer to the start of the reallocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + virtual void* dRealloc( + AkMemPoolId in_poolId, + void* in_pAlloc, + size_t in_uSize, + const char* in_pszFile, + AkUInt32 in_uLine + ) = 0; + + /// Reallocate memory. + /// \return A pointer to the start of the reallocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + virtual void* Realloc( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + void* in_pAlloc, ///< Pointer to the start of the allocated memory + size_t in_uSize ///< Number of bytes to allocate + ) = 0; + + /// Reallocate memory: debug version. + /// \return A pointer to the start of the reallocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + virtual void* dReallocAligned( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + void* in_pAlloc, ///< Pointer to the start of the allocated memory + size_t in_uSize, ///< Number of bytes to allocate + AkUInt32 in_uAlignment, ///< Alignment (in bytes) + const char* in_pszFile, ///< Debug file name + AkUInt32 in_uLine ///< Debug line number + ) = 0; + + /// Reallocate memory. + /// \return A pointer to the start of the reallocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + virtual void* ReallocAligned( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + void* in_pAlloc, ///< Pointer to the start of the allocated memory + size_t in_uSize, ///< Number of bytes to allocate + AkUInt32 in_uAlignment ///< Alignment (in bytes) + ) = 0; + + /// Free memory allocated with the memory manager. + /// \sa + /// - \ref memorymanager + virtual void Free( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + void* in_pMemAddress ///< Pointer to the start of memory + ) = 0; + + /// Allocate memory with a specific alignment. debug version. + /// \return A pointer to the start of the allocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + virtual void* dMalign( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + size_t in_uSize, ///< Number of bytes to allocate + AkUInt32 in_uAlignment, ///< Alignment (in bytes) + const char* in_pszFile, ///< Debug file name + AkUInt32 in_uLine ///< Debug line number + ) = 0; + + /// Allocate memory with a specific alignment. + /// \return A pointer to the start of the allocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + virtual void* Malign( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + size_t in_uSize, ///< Number of bytes to allocate + AkUInt32 in_uAlignment ///< Alignment (in bytes) + ) = 0; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Memory Profiling + //@{ + + /// Get statistics for a given memory category. + /// \note Be aware of the potentially incoherent nature of reporting such information during concurrent modification by multiple threads. + virtual void GetCategoryStats( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + AK::MemoryMgr::CategoryStats& out_poolStats ///< Returned statistics. + ) = 0; + + /// Get statistics for overall memory manager usage. + /// \note Be aware of the potentially incoherent nature of reporting such information during concurrent modification by multiple threads. + virtual void GetGlobalStats( + AK::MemoryMgr::GlobalStats& out_stats ///< Returned statistics. + ) = 0; + + /// Called to start profiling memory usage for one thread (the calling thread). + /// \note Not implementing this will result in the Soundbank tab of the Wwise Profiler to show 0 bytes for memory usage. + virtual void StartProfileThreadUsage( + ) = 0; + + /// Called to stop profiling memory usage for the current thread. + /// \return The amount of memory allocated by this thread since StartProfileThreadUsage was called. + /// \note Not implementing this will result in the Soundbank tab of the Wwise Profiler to show 0 bytes for memory usage. + virtual AkUInt64 StopProfileThreadUsage( + ) = 0; + + /// Dumps the currently tracked allocations to a file + /// \note AkMemSettings::uMemoryDebugLevel must be enabled and the build must define AK_MEMDEBUG for this to work + virtual void DumpToFile( + const AkOSChar* pszFilename ///< Filename. + ) = 0; + + //@} +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwiseMonitorAPI.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwiseMonitorAPI.h new file mode 100644 index 0000000..2efdcf4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwiseMonitorAPI.h @@ -0,0 +1,178 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "AkInclude.h" + +#include "Wwise/WwiseSoundEngineModule.h" + +class IWwiseMonitorAPI +{ +public: + static IWwiseMonitorAPI* Get() + { + IWwiseSoundEngineModule::ForceLoadModule(); + return IWwiseSoundEngineModule::Monitor; + } + + UE_NONCOPYABLE(IWwiseMonitorAPI); +protected: + IWwiseMonitorAPI() = default; +public: + virtual ~IWwiseMonitorAPI() {} + + /// Post a monitoring message or error code. This will be displayed in the Wwise capture + /// log. Since this function doesn't send variable arguments, be sure that the error code you're posting doesn't contain any tag. + /// Otherwise, there'll be an undefined behavior + /// \return AK_Success if successful, AK_Fail if there was a problem posting the message. + /// In optimized mode, this function returns AK_NotCompatible. + /// \remark This function is provided as a tracking tool only. It does nothing if it is + /// called in the optimized/release configuration and return AK_NotCompatible. + virtual AKRESULT PostCode( + AK::Monitor::ErrorCode in_eError, ///< Message or error code to be displayed + AK::Monitor::ErrorLevel in_eErrorLevel, ///< Specifies whether it should be displayed as a message or an error + AkPlayingID in_playingID = AK_INVALID_PLAYING_ID, ///< Related Playing ID if applicable + AkGameObjectID in_gameObjID = AK_INVALID_GAME_OBJECT, ///< Related Game Object ID if applicable, AK_INVALID_GAME_OBJECT otherwise + AkUniqueID in_audioNodeID = AK_INVALID_UNIQUE_ID, ///< Related Audio Node ID if applicable, AK_INVALID_UNIQUE_ID otherwise + bool in_bIsBus = false ///< true if in_audioNodeID is a bus + ) = 0; + + virtual AKRESULT PostCodeVarArg( + AK::Monitor::ErrorCode in_eError, ///< Error code to be displayed. This code corresponds to a predefined message, that may have parameters that can be passed in the variable arguments. Check the message format at the end of AkMonitorError.h. + AK::Monitor::ErrorLevel in_eErrorLevel, ///< Specifies whether it should be displayed as a message or an error + AK::Monitor::MsgContext msgContext, ///< The message context containing the following information : Related Playing ID if applicable, Related Game Object ID if applicable, AK_INVALID_GAME_OBJECT otherwise, Related Audio Node ID if applicable, AK_INVALID_UNIQUE_ID otherwise and whether if in_audioNodeID is a bus + ... ///< The variable arguments, depending on the ErrorCode posted. + ) = 0; + + /// Post a monitoring message. This will be displayed in the Wwise capture log. + /// \return AK_Success if successful, AK_Fail if there was a problem posting the message. + /// In optimized mode, this function returns AK_NotCompatible. + /// \remark This function is provided as a tracking tool only. It does nothing if it is + /// called in the optimized/release configuration and return AK_NotCompatible. + virtual AKRESULT PostCodeVaList( + AK::Monitor::ErrorCode in_eError, ///< Error code to be displayed. This code corresponds to a predefined message, that may have parameters that can be passed in the variable arguments. Check the message format at the end of AkMonitorError.h. + AK::Monitor::ErrorLevel in_eErrorLevel, ///< Specifies whether it should be displayed as a message or an error + AK::Monitor::MsgContext msgContext, ///< The message context containing the following information : Related Playing ID if applicable, Related Game Object ID if applicable, AK_INVALID_GAME_OBJECT otherwise, Related Audio Node ID if applicable, AK_INVALID_UNIQUE_ID otherwise and whether if in_audioNodeID is a bus + ::va_list args ///< The variable arguments, depending on the ErrorCode posted. + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Post a unicode monitoring message or error string. This will be displayed in the Wwise capture + /// log. + /// \return AK_Success if successful, AK_Fail if there was a problem posting the message. + /// In optimized mode, this function returns AK_NotCompatible. + /// \remark This function is provided as a tracking tool only. It does nothing if it is + /// called in the optimized/release configuration and return AK_NotCompatible. + virtual AKRESULT PostString( + const wchar_t* in_pszError, ///< Message or error string to be displayed + AK::Monitor::ErrorLevel in_eErrorLevel, ///< Specifies whether it should be displayed as a message or an error + AkPlayingID in_playingID = AK_INVALID_PLAYING_ID, ///< Related Playing ID if applicable + AkGameObjectID in_gameObjID = AK_INVALID_GAME_OBJECT, ///< Related Game Object ID if applicable, AK_INVALID_GAME_OBJECT otherwise + AkUniqueID in_audioNodeID = AK_INVALID_UNIQUE_ID, ///< Related Audio Node ID if applicable, AK_INVALID_UNIQUE_ID otherwise + bool in_bIsBus = false ///< true if in_audioNodeID is a bus + ) = 0; + +#endif // #ifdef AK_SUPPORT_WCHAR + + /// Post a monitoring message or error string. This will be displayed in the Wwise capture + /// log. + /// \return AK_Success if successful, AK_Fail if there was a problem posting the message. + /// In optimized mode, this function returns AK_NotCompatible. + /// \remark This function is provided as a tracking tool only. It does nothing if it is + /// called in the optimized/release configuration and return AK_NotCompatible. + virtual AKRESULT PostString( + const char* in_pszError, ///< Message or error string to be displayed + AK::Monitor::ErrorLevel in_eErrorLevel, ///< Specifies whether it should be displayed as a message or an error + AkPlayingID in_playingID = AK_INVALID_PLAYING_ID, ///< Related Playing ID if applicable + AkGameObjectID in_gameObjID = AK_INVALID_GAME_OBJECT, ///< Related Game Object ID if applicable, AK_INVALID_GAME_OBJECT otherwise + AkUniqueID in_audioNodeID = AK_INVALID_UNIQUE_ID, ///< Related Audio Node ID if applicable, AK_INVALID_UNIQUE_ID otherwise + bool in_bIsBus = false ///< true if in_audioNodeID is a bus + ) = 0; + + /// Enable/Disable local output of monitoring messages or errors. Pass 0 to disable, + /// or any combination of ErrorLevel_Message and ErrorLevel_Error to enable. + /// \return AK_Success. + /// In optimized/release configuration, this function returns AK_NotCompatible. + virtual AKRESULT SetLocalOutput( + AkUInt32 in_uErrorLevel = AK::Monitor::ErrorLevel_All, ///< ErrorLevel(s) to enable in output. Default parameters enable all. + AK::Monitor::LocalOutputFunc in_pMonitorFunc = 0 ///< Handler for local output. If NULL, the standard platform debug output method is used. + ) = 0; + + /// Add a translator to the wwiseErrorHandler + /// The additional translators increase the chance of a monitoring messages or errors + /// to be succeffully translated. + /// \return AK_Success. + /// In optimized/release configuration, this function returns AK_NotCompatible. + virtual AKRESULT AddTranslator( + AkErrorMessageTranslator* translator, ///< The AkErrorMessageTranslator to add to the WwiseErrorHandler + bool overridePreviousTranslators = false ///< Whether or not the newly added translator should override all the previous translators. + ///< In both cases, the default translator will remain + ) = 0; + + /// Reset the wwiseErrorHandler to only using the default translator + /// \return AK_Success. + /// In optimized/release configuration, this function returns AK_NotCompatible. + virtual AKRESULT ResetTranslator( + ) = 0; + + /// Get the time stamp shown in the capture log along with monitoring messages. + /// \return Time stamp in milliseconds. + /// In optimized/release configuration, this function returns 0. + virtual AkTimeMs GetTimeStamp() = 0; + + /// Add the streaming manager settings to the profiler capture. + virtual void MonitorStreamMgrInit( + const AkStreamMgrSettings& in_streamMgrSettings + ) = 0; + + /// Add device settings to the list of active streaming devices. + /// The list of streaming devices and their settings will be + /// sent to the profiler capture when remote connecting from Wwise. + /// + /// \remark \c AK::Monitor::MonitorStreamMgrTerm must be called to + /// clean-up memory used to keep track of active streaming devices. + virtual void MonitorStreamingDeviceInit( + AkDeviceID in_deviceID, + const AkDeviceSettings& in_deviceSettings + ) = 0; + + /// Remove streaming device entry from the list of devices + /// to send when remote connecting from Wwise. + virtual void MonitorStreamingDeviceDestroyed( + AkDeviceID in_deviceID + ) = 0; + + /// Monitor streaming manager destruction as part of the + /// profiler capture. + /// + /// \remark This function must be called to clean-up memory used by + /// \c AK::Monitor::MonitorStreamingDeviceInit and \c AK::Monitor::MonitorStreamingDeviceTerm + /// to keep track of active streaming devices. + virtual void MonitorStreamMgrTerm() = 0; + + /// Add the default, WwiseSDK-provided WAAPI error translator. + virtual void SetupDefaultWAAPIErrorTranslator( + const FString& WaapiIP, ///< IP Address of the WAAPI server + AkUInt32 WaapiPort, ///< Port of the WAAPI server + AkUInt32 Timeout ///< Maximum time that can be spent resolving the error parameters. Set to INT_MAX to wait infinitely or 0 to disable XML translation entirely. + ) = 0; + + /// Terminate the default, WwiseSDK-provided WAAPI error translator. + virtual void TerminateDefaultWAAPIErrorTranslator() = 0; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwiseMusicEngineAPI.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwiseMusicEngineAPI.h new file mode 100644 index 0000000..fb9f837 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwiseMusicEngineAPI.h @@ -0,0 +1,97 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "AkInclude.h" + +#include "Wwise/WwiseSoundEngineModule.h" + +class IWwiseMusicEngineAPI +{ +public: + static IWwiseMusicEngineAPI* Get() + { + IWwiseSoundEngineModule::ForceLoadModule(); + return IWwiseSoundEngineModule::MusicEngine; + } + + UE_NONCOPYABLE(IWwiseMusicEngineAPI); +protected: + IWwiseMusicEngineAPI() = default; +public: + virtual ~IWwiseMusicEngineAPI() {} + + /////////////////////////////////////////////////////////////////////// + /// @name Initialization + //@{ + + /// Initialize the music engine. + /// \warning This function must be called after the base sound engine has been properly initialized. + /// There should be no AK API call between AK::SoundEngine::Init() and this call. Any call done in between is potentially unsafe. + /// \return AK_Success if the Init was successful, AK_Fail otherwise. + /// \sa + /// - \ref workingwithsdks_initialization + virtual AKRESULT Init( + AkMusicSettings* in_pSettings ///< Initialization settings (can be NULL, to use the default values) + ) = 0; + + /// Get the music engine's default initialization settings values + /// \sa + /// - \ref soundengine_integration_init_advanced + /// - AK::MusicEngine::Init() + virtual void GetDefaultInitSettings( + AkMusicSettings& out_settings ///< Returned default platform-independent music engine settings + ) = 0; + + /// Terminate the music engine. + /// \warning This function must be called before calling Term() on the base sound engine. + /// \sa + /// - \ref workingwithsdks_termination + virtual void Term( + ) = 0; + + /// Query information on the active segment of a music object that is playing. Use the playing ID + /// that was returned from AK::SoundEngine::PostEvent(), provided that the event contained a play + /// action that was targetting a music object. For any configuration of interactive music hierarchy, + /// there is only one segment that is active at a time. + /// To be able to query segment information, you must pass the AK_EnableGetMusicPlayPosition flag + /// to the AK::SoundEngine::PostEvent() method. This informs the sound engine that the source associated + /// with this event should be given special consideration because GetPlayingSegmentInfo() can be called + /// at any time for this AkPlayingID. + /// Notes: + /// - If the music object is a single segment, you will get negative values for AkSegmentInfo::iCurrentPosition + /// during the pre-entry. This will never occur with other types of music objects because the + /// pre-entry of a segment always overlaps another active segment. + /// - The active segment during the pre-entry of the first segment of a Playlist Container or a Music Switch + /// Container is "nothing", as well as during the post-exit of the last segment of a Playlist (and beyond). + /// - When the active segment is "nothing", out_uSegmentInfo is filled with zeros. + /// - If in_bExtrapolate is true (default), AkSegmentInfo::iCurrentPosition is corrected by the amount of time elapsed + /// since the beginning of the audio frame. It is thus possible that it slightly overshoots the total segment length. + /// \return AK_Success if there is a playing music structure associated with the specified playing ID. + /// \sa + /// - AK::SoundEngine::PostEvent + /// - AkSegmentInfo + virtual AKRESULT GetPlayingSegmentInfo( + AkPlayingID in_PlayingID, ///< Playing ID returned by AK::SoundEngine::PostEvent(). + AkSegmentInfo& out_segmentInfo, ///< Structure containing information about the active segment of the music structure that is playing. + bool in_bExtrapolate = true ///< Position is extrapolated based on time elapsed since last sound engine update. + ) = 0; + + //@} +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwisePlatformAPI.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwisePlatformAPI.h new file mode 100644 index 0000000..bdfea65 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwisePlatformAPI.h @@ -0,0 +1,25 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "AkInclude.h" + +#include "Wwise/WwiseSoundEngineModule.h" + +#include COMPILED_PLATFORM_HEADER_WITH_PREFIX(Wwise/API/Platforms, API.h) diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwiseSoundEngineAPI.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwiseSoundEngineAPI.h new file mode 100644 index 0000000..e41c3ab --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwiseSoundEngineAPI.h @@ -0,0 +1,4543 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "AkInclude.h" + +#include "Wwise/WwiseSoundEngineModule.h" + +class IWwiseSoundEngineAPI +{ +public: + static IWwiseSoundEngineAPI* Get() + { + IWwiseSoundEngineModule::ForceLoadModule(); + return IWwiseSoundEngineModule::SoundEngine; + } + + class IQuery; + class IAudioInputPlugin; + UE_NONCOPYABLE(IWwiseSoundEngineAPI); +protected: + IWwiseSoundEngineAPI(IQuery* InQuery, IAudioInputPlugin* InAudioInputPlugin) : + Query(InQuery), + AudioInputPlugin(InAudioInputPlugin) + {} +public: + virtual ~IWwiseSoundEngineAPI() + { + delete Query; + delete AudioInputPlugin; + } + + /////////////////////////////////////////////////////////////////////// + /// @name Initialization + //@{ + + /// Query whether or not the sound engine has been successfully initialized. + /// \warning This function is not thread-safe. It should not be called at the same time as \c SoundEngine::Init() or \c SoundEngine::Term(). + /// \return \c True if the sound engine has been initialized, \c False otherwise. + /// \sa + /// - \ref soundengine_integration_init_advanced + /// - AK::SoundEngine::Init() + /// - AK::SoundEngine::Term() + virtual bool IsInitialized() = 0; + + /// Initialize the sound engine. + /// \warning This function is not thread-safe. + /// \remark The initial settings should be initialized using AK::SoundEngine::GetDefaultInitSettings() + /// and AK::SoundEngine::GetDefaultPlatformInitSettings() to fill the structures with their + /// default settings. This is not mandatory, but it helps avoid backward compatibility problems. + /// + /// \return + /// - \c AK_Success if the initialization was successful + /// - \c AK_MemManagerNotInitialized if the memory manager is not available or not properly initialized + /// - \c AK_StreamMgrNotInitialized if the stream manager is not available or not properly initialized + /// - \c AK_SSEInstructionsNotSupported if the machine does not support SSE instruction (only on the PC) + /// - \c AK_InsufficientMemory if there is not enough memory available to initialize the sound engine properly + /// - \c AK_InvalidParameter if some parameters are invalid + /// - \c AK_AlreadyInitialized if the sound engine is already initialized, or if the provided settings result in insufficient + /// - \c AK_Fail for unknown errors, check with AK Support. + /// resources for the initialization. + /// \sa + /// - \ref soundengine_integration_init_advanced + /// - \ref workingwithsdks_initialization + /// - AK::SoundEngine::Term() + /// - AK::SoundEngine::GetDefaultInitSettings() + /// - AK::SoundEngine::GetDefaultPlatformInitSettings() + virtual AKRESULT Init( + AkInitSettings* in_pSettings, ///< Initialization settings (can be NULL, to use the default values) + AkPlatformInitSettings* in_pPlatformSettings ///< Platform-specific settings (can be NULL, to use the default values) + ) = 0; + + /// Gets the default values of the platform-independent initialization settings. + /// \warning This function is not thread-safe. + /// \sa + /// - \ref soundengine_integration_init_advanced + /// - AK::SoundEngine::Init() + /// - AK::SoundEngine::GetDefaultPlatformInitSettings() + virtual void GetDefaultInitSettings( + AkInitSettings& out_settings ///< Returned default platform-independent sound engine settings + ) = 0; + + /// Gets the default values of the platform-specific initialization settings. + /// + /// Windows Specific: + /// When initializing for Windows platform, the HWND value returned in the + /// AkPlatformInitSettings structure is the foreground HWND at the moment of the + /// initialization of the sound engine and may not be the correct one for your need. + /// Each game must specify the HWND that will be passed to DirectSound initialization. + /// It is required that each game provides the correct HWND to be used or it could cause + /// one of the following problem: + /// - Random Sound engine initialization failure. + /// - Audio focus to be located on the wrong window. + /// + /// \warning This function is not thread-safe. + /// \sa + /// - \ref soundengine_integration_init_advanced + /// - AK::SoundEngine::Init() + /// - AK::SoundEngine::GetDefaultInitSettings() + virtual void GetDefaultPlatformInitSettings( + AkPlatformInitSettings& out_platformSettings ///< Returned default platform-specific sound engine settings + ) = 0; + + /// Terminates the sound engine. + /// If some sounds are still playing or events are still being processed when this function is + /// called, they will be stopped. + /// \warning This function is not thread-safe. + /// \warning Before calling Term, you must ensure that no other thread is accessing the sound engine. + /// \sa + /// - \ref soundengine_integration_init_advanced + /// - AK::SoundEngine::Init() + virtual void Term() = 0; + + /// Gets the configured audio settings. + /// Call this function to get the configured audio settings. + /// + /// \warning This function is not thread-safe. + /// \warning Call this function only after the sound engine has been properly initialized. + /// \return + /// - \c AK_NotInitialized if AK::SoundEngine::Init() was not called + /// - \c AK_Success otherwise. + virtual AKRESULT GetAudioSettings( + AkAudioSettings& out_audioSettings ///< Returned audio settings + ) = 0; + + /// Gets the output speaker configuration of the specified output. + /// Call this function to get the speaker configuration of the output (which may not correspond + /// to the physical output format of the platform, in the case of downmixing provided by the platform itself). + /// You may initialize the sound engine with a user-specified configuration, but the resulting + /// configuration is determined by the sound engine, based on the platform, output type and + /// platform settings (for e.g. system menu or control panel option). + /// If the speaker configuration of the output is object-based, the speaker configuration of the + /// main mix is returned. To query more information on object-based output devices, see AK::SoundEngine::GetOutputDeviceConfiguration. + /// + /// It is recommended to call GetSpeakerConfiguration anytime after receiving a callback from RegisterAudioDeviceStatusCallback to know if the channel configuration has changed. + /// + /// \warning Call this function only after the sound engine has been properly initialized. + /// If you are initializing the sound engine with AkInitSettings::bUseLEngineThread to false, it is required to call RenderAudio() at least once before calling this function to complete the sound engine initialization. + /// The Init.bnk must be loaded prior to this call. + /// \return The output configuration. An empty AkChannelConfig not AkChannelConfig::IsValid() if device does not exist or if the Init.bnk was not loaded yet. + /// \sa + /// - AkSpeakerConfig.h + /// - AkOutputSettings + /// - AK::SoundEngine::GetOutputDeviceConfiguration() + virtual AkChannelConfig GetSpeakerConfiguration( + AkOutputDeviceID in_idOutput = 0 ///< Output ID to set the bus on. As returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + ) = 0; + + /// Gets the configuration of the specified output device. + /// Call this function to get the channel configuration of the output device as well as its 3D audio capabilities. + /// If the configuration of the output device is object-based (io_channelConfig.eConfigType == AK_ChannelConfigType_Objects), + /// io_capabilities can be inspected to determine the channel configuration of the main mix (Ak3DAudioSinkCapabilities::channelConfig), + /// whether or not the output device uses a passthrough mix (Ak3DAudioSinkCapabilities::bPassthrough) and the maximum number of objects + /// that can play simultaneously on this output device (Ak3DAudioSinkCapabilities::uMax3DAudioObjects). Note that if + /// Ak3DAudioSinkCapabilities::bMultiChannelObjects is false, multi-channel objects will be split into multiple mono objects + /// before being sent to the output device. + /// + /// \warning Call this function only after the sound engine has been properly initialized. If you are initializing the sound engine with AkInitSettings::bUseLEngineThread to false, it is required to call RenderAudio() at least once before calling this function to complete the sound engine initialization. + /// \return + /// - \c AK_Success if successful + /// - \c AK_IDNotFound is the output was not found in the system. + /// - \c AK_NotInitialized if the sound engine is not initialized + /// \sa + /// - AkSpeakerConfig.h + /// - AkOutputSettings + /// - AK::SoundEngine::GetSpeakerConfiguration() + virtual AKRESULT GetOutputDeviceConfiguration( + AkOutputDeviceID in_idOutput, + AkChannelConfig& io_channelConfig, + Ak3DAudioSinkCapabilities& io_capabilities + ) = 0; + + /// Gets the panning rule of the specified output. + /// \warning Call this function only after the sound engine has been properly initialized. + /// Returns the supported configuration in out_ePanningRule: + /// - AkPanningRule_Speakers + /// - AkPanningRule_Headphone + /// \return + /// - \c AK_Success if successful + /// - \c AK_IDNotFound is the output was not found in the system. + /// - \c AK_NotInitialized if the sound engine is not initialized + /// \sa + /// - AkSpeakerConfig.h + virtual AKRESULT GetPanningRule( + AkPanningRule& out_ePanningRule, ///< Returned panning rule (AkPanningRule_Speakers or AkPanningRule_Headphone) for given output. + AkOutputDeviceID in_idOutput = 0 ///< Output ID to set the bus on. As returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + ) = 0; + + /// Sets the panning rule of the specified output. + /// This may be changed anytime once the sound engine is initialized. + /// \warning This function posts a message through the sound engine's internal message queue, whereas GetPanningRule() queries the current panning rule directly. + /// \aknote + /// The specified panning rule will only impact the sound if the processing format is downmixing to Stereo in the mixing process. It + /// will not impact the output if the audio stays in 5.1 until the end, for example. + /// \endaknote + virtual AKRESULT SetPanningRule( + AkPanningRule in_ePanningRule, ///< Panning rule. + AkOutputDeviceID in_idOutput = 0 ///< Output ID to set the bus on. As returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + ) = 0; + + /// Gets speaker angles of the specified device. Speaker angles are used for 3D positioning of sounds over standard configurations. + /// Note that the current version of Wwise only supports positioning on the plane. + /// The speaker angles are expressed as an array of loudspeaker pairs, in degrees, relative to azimuth ]0,180]. + /// Supported loudspeaker setups are always symmetric; the center speaker is always in the middle and thus not specified by angles. + /// Angles must be set in ascending order. + /// You may call this function with io_pfSpeakerAngles set to NULL to get the expected number of angle values in io_uNumAngles, + /// in order to allocate your array correctly. You may also obtain this number by calling + /// AK::GetNumberOfAnglesForConfig( AK_SPEAKER_SETUP_DEFAULT_PLANE ). + /// If io_pfSpeakerAngles is not NULL, the array is filled with up to io_uNumAngles. + /// Typical usage: + /// - AkUInt32 uNumAngles; + /// - GetSpeakerAngles( NULL, uNumAngles, AkOutput_Main ) = 0; + /// - AkReal32 * pfSpeakerAngles = AkAlloca( uNumAngles * sizeof(AkReal32) ) = 0; + /// - GetSpeakerAngles( pfSpeakerAngles, uNumAngles, AkOutput_Main ) = 0; + /// \aknote + /// On most platforms, the angle set on the plane consists of 3 angles, to account for 7.1. + /// - When panning to stereo (speaker mode, see AK::SoundEngine::SetPanningRule()), only angle[0] is used, and 3D sounds in the back of the listener are mirrored to the front. + /// - When panning to 5.1, the front speakers use angle[0], and the surround speakers use (angle[2] - angle[1]) / 2. + /// \endaknote + /// \warning Call this function only after the sound engine has been properly initialized. + /// \return AK_Success if device exists. + /// \sa SetSpeakerAngles() + virtual AKRESULT GetSpeakerAngles( + AkReal32* io_pfSpeakerAngles, ///< Returned array of loudspeaker pair angles, in degrees relative to azimuth [0,180]. Pass NULL to get the required size of the array. + AkUInt32& io_uNumAngles, ///< Returned number of angles in io_pfSpeakerAngles, which is the minimum between the value that you pass in, and the number of angles corresponding to AK::GetNumberOfAnglesForConfig( AK_SPEAKER_SETUP_DEFAULT_PLANE ), or just the latter if io_pfSpeakerAngles is NULL. + AkReal32& out_fHeightAngle, ///< Elevation of the height layer, in degrees relative to the plane [-90,90]. + AkOutputDeviceID in_idOutput = 0 ///< Output ID to set the bus on. As returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + ) = 0; + + /// Sets speaker angles of the specified device. Speaker angles are used for 3D positioning of sounds over standard configurations. + /// Note that the current version of Wwise only supports positioning on the plane. + /// The speaker angles are expressed as an array of loudspeaker pairs, in degrees, relative to azimuth ]0,180]. + /// Supported loudspeaker setups are always symmetric; the center speaker is always in the middle and thus not specified by angles. + /// Angles must be set in ascending order. + /// Note: + /// - This function requires that the minimum speaker angle is at least 5 degrees; as well as the subsequent speaker pairs are at least 5 degrees apart. + /// Typical usage: + /// - Initialize the sound engine and/or add secondary output(s). + /// - Get number of speaker angles and their value into an array using GetSpeakerAngles(). + /// - Modify the angles and call SetSpeakerAngles(). + /// This function posts a message to the audio thread through the command queue, so it is thread safe. However the result may not be immediately read with GetSpeakerAngles(). + /// \warning This function only applies to configurations (or subset of these configurations) that are standard and whose speakers are on the plane (2D). + /// \return + /// - \c AK_Success if successful. + /// - \c AK_InvalidFloatValue if the value specified was NaN or Inf + /// - \c AK_InsufficientMemory if there wasn't enough memory in the message queue + /// - \c AK_InvalidParameter one of the parameter is invalid, check the debug log. + /// \sa GetSpeakerAngles() + virtual AKRESULT SetSpeakerAngles( + const AkReal32* in_pfSpeakerAngles, ///< Array of loudspeaker pair angles, in degrees relative to azimuth [0,180]. + AkUInt32 in_uNumAngles, ///< Number of elements in in_pfSpeakerAngles. It must correspond to AK::GetNumberOfAnglesForConfig( AK_SPEAKER_SETUP_DEFAULT_PLANE ) (the value returned by GetSpeakerAngles()). + AkReal32 in_fHeightAngle, ///< Elevation of the height layer, in degrees relative to the plane [-90,90]. + AkOutputDeviceID in_idOutput = 0 ///< Output ID to set the bus on. As returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + ) = 0; + + /// Allows the game to set the volume threshold to be used by the sound engine to determine if a voice must go virtual. + /// This may be changed anytime once the sound engine was initialized. + /// If this function is not called, the used value will be the value specified in the platform specific project settings. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if the threshold was not between 0 and -96.3 dB. + /// - \c AK_InvalidFloatValue if the value specified was NaN or Inf + virtual AKRESULT SetVolumeThreshold( + AkReal32 in_fVolumeThresholdDB ///< Volume Threshold, must be a value between 0 and -96.3 dB + ) = 0; + + /// Allows the game to set the maximum number of non virtual voices to be played simultaneously. + /// This may be changed anytime once the sound engine was initialized. + /// If this function is not called, the used value will be the value specified in the platform specific project settings. + /// \return + /// - \c AK_InvalidParameter if the threshold was not between 1 and MaxUInt16. + /// - \c AK_Success if successful + virtual AKRESULT SetMaxNumVoicesLimit( + AkUInt16 in_maxNumberVoices ///< Maximum number of non-virtual voices. + ) = 0; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Rendering Audio + //@{ + + /// Processes all commands in the sound engine's command queue. + /// This method has to be called periodically (usually once per game frame). + /// \sa + /// - \ref concept_events + /// - \ref soundengine_events + /// - AK::SoundEngine::PostEvent() + /// \return Always returns AK_Success + virtual AKRESULT RenderAudio( + bool in_bAllowSyncRender = true ///< When AkInitSettings::bUseLEngineThread is false, RenderAudio may generate an audio buffer -- unless in_bAllowSyncRender is set to false. Use in_bAllowSyncRender=false when calling RenderAudio from a Sound Engine callback. + ) = 0; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Component Registration + //@{ + + /// Query interface to global plug-in context used for plug-in registration/initialization. + /// \return Global plug-in context. + virtual AK::IAkGlobalPluginContext* GetGlobalPluginContext() = 0; + + /// Registers a plug-in with the sound engine and sets the callback functions to create the + /// plug-in and its parameter node. + /// \aknote + /// This function is deprecated. Registration is now automatic if you link plug-ins statically. If plug-ins are dynamic libraries (such as DLLs or SOs), use \c RegisterPluginDLL. + /// \endaknote + /// \sa + /// - \ref register_effects + /// - \ref plugin_xml + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if invalid parameters were provided + /// - \c AK_InsufficientMemory if there isn't enough memory to register the plug-in + /// \remarks + /// Codecs and plug-ins must be registered before loading banks that use them.\n + /// Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + virtual AKRESULT RegisterPlugin( + AkPluginType in_eType, ///< Plug-in type (for example, source or effect) + AkUInt32 in_ulCompanyID, ///< Company identifier (as declared in the plug-in description XML file) + AkUInt32 in_ulPluginID, ///< Plug-in identifier (as declared in the plug-in description XML file) + AkCreatePluginCallback in_pCreateFunc, ///< Pointer to the plug-in's creation function + AkCreateParamCallback in_pCreateParamFunc, ///< Pointer to the plug-in's parameter node creation function + AkGetDeviceListCallback in_pGetDeviceList = NULL ///< Optional pointer to the plug-in's device enumeration function. Specify for a sink plug-in to support \ref AK::SoundEngine::GetDeviceList. + ) = 0; + + /// Loads a plug-in dynamic library and registers it with the sound engine. + /// With dynamic linking, all plugins are automatically registered. + /// The plug-in DLL must be in the OS-specific library path or in the same location as the executable. If not, set AkInitSettings.szPluginDLLPath. + /// \return + /// - \c AK_Success if successful. + /// - \c AK_FileNotFound if the DLL is not found in the OS path or if it has extraneous dependencies not found. + /// - \c AK_InsufficientMemory if the system ran out of resources while loading the dynamic library + /// - \c AK_NotCompatible if the file was found but is not binary-compatible with the system's expected executable format + /// - \c AK_InvalidFile if the symbol g_pAKPluginList is not exported by the dynamic library + /// - \c AK_Fail if an unexpected system error was encountered + virtual AKRESULT RegisterPluginDLL( + const AkOSChar* in_DllName, ///< Name of the DLL to load, without "lib" prefix or extension. + const AkOSChar* in_DllPath = NULL ///< Optional path to the DLL. Will override szPLuginDLLPath that was set in AkInitSettings. + ) = 0; + + /// Registers a codec type with the sound engine and set the callback functions to create the + /// codec's file source and bank source nodes. + /// \aknote + /// This function is deprecated. Registration is now automatic if you link plugins statically. If plugins are dynamic libraries (such as DLLs or SOs), use RegisterPluginDLL. + /// \endaknote + /// \sa + /// - \ref register_effects + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if invalid parameters were provided + /// - \c AK_InsufficientMemory if there isn't enough memory to register the plug-in + /// \remarks + /// Codecs and plug-ins must be registered before loading banks that use them.\n + /// Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the Event will fail. + virtual AKRESULT RegisterCodec( + AkUInt32 in_ulCompanyID, ///< Company identifier (as declared in the plug-in description XML file) + AkUInt32 in_ulCodecID, ///< Codec identifier (as declared in the plug-in description XML file) + AkCreateFileSourceCallback in_pFileCreateFunc, ///< Pointer to the codec's file source node creation function + AkCreateBankSourceCallback in_pBankCreateFunc ///< Pointer to the codec's bank source node creation function + ) = 0; + + /// Registers a global callback function. This function will be called from the audio rendering thread, at the + /// location specified by in_eLocation. This function will also be called from the thread calling + /// AK::SoundEngine::Term with in_eLocation set to AkGlobalCallbackLocation_Term. + /// For example, in order to be called at every audio rendering pass, and once during teardown for releasing resources, you would call + /// RegisterGlobalCallback(myCallback, AkGlobalCallbackLocation_BeginRender | AkGlobalCallbackLocation_Term, myCookie, AkPluginTypeNone, 0, 0) = 0; + /// \remarks + /// A Plugin Type, Company ID and Plugin ID can be provided to this function to enable timing in the performance monitor. + /// If the callback is being timed, it will contribute to the Total Plug-in CPU measurement, and also appear in the Plug-ins tab of the Advanced Profiler by plug-in type and ID. + /// It is illegal to call this function while already inside of a global callback. + /// This function might stall for several milliseconds before returning. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if parameters are out of range (check debug console or Wwise Profiler) + /// \sa + /// - AK::SoundEngine::UnregisterGlobalCallback() + /// - AkGlobalCallbackFunc + /// - AkGlobalCallbackLocation + virtual AKRESULT RegisterGlobalCallback( + AkGlobalCallbackFunc in_pCallback, ///< Function to register as a global callback. + AkUInt32 in_eLocation = AkGlobalCallbackLocation_BeginRender, ///< Callback location defined in AkGlobalCallbackLocation. Bitwise OR multiple locations if needed. + void* in_pCookie = NULL, ///< User cookie. + AkPluginType in_eType = AkPluginTypeNone, ///< Plug-in type (for example, source or effect). AkPluginTypeNone for no timing. + AkUInt32 in_ulCompanyID = 0, ///< Company identifier (as declared in the plug-in description XML file). 0 for no timing. + AkUInt32 in_ulPluginID = 0 ///< Plug-in identifier (as declared in the plug-in description XML file). 0 for no timing. + ) = 0; + + /// Unregisters a global callback function, previously registered using RegisterGlobalCallback. + /// \remarks + /// It is legal to call this function while already inside of a global callback, If it is unregistering itself and not + /// another callback. + /// This function might stall for several milliseconds before returning. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if parameters are out of range (check debug console or Wwise Profiler) + /// \sa + /// - AK::SoundEngine::RegisterGlobalCallback() + /// - AkGlobalCallbackFunc + /// - AkGlobalCallbackLocation + virtual AKRESULT UnregisterGlobalCallback( + AkGlobalCallbackFunc in_pCallback, ///< Function to unregister as a global callback. + AkUInt32 in_eLocation = AkGlobalCallbackLocation_BeginRender ///< Must match in_eLocation as passed to RegisterGlobalCallback for this callback. + ) = 0; + + /// Registers a resource monitor callback function that gets all of the resource usage data contained in the + /// AkResourceMonitorDataSummary structure. This includes general information about the system, such as CPU usage, + /// active Voices, and Events. This function will be called from the audio rendering thread at the end of each frame. + /// \remarks + /// If the callback is being timed, it will contribute to the Total Plug-in CPU measurement, and also appear in the Plug-ins tab of the Advanced Profiler by plug-in type and ID. + /// It is illegal to call this function while already inside of a resource callback. + /// This function might stall for several milliseconds before returning. + /// This function will return AK_Fail in Release + /// \sa + /// - AK::SoundEngine::UnregisterResourceMonitorCallback() + /// - AkResourceMonitorCallbackFunc + virtual AKRESULT RegisterResourceMonitorCallback( + AkResourceMonitorCallbackFunc in_pCallback ///< Function to register as a resource monitor callback. + ) = 0; + + /// Unregisters a resource monitor callback function, previously registered using RegisterResourceMonitorCallback. + /// \remarks + /// It is legal to call this function while already inside of a resource monitor callback, If it is unregistering itself and not + /// another callback. + /// This function might stall for several milliseconds before returning. + /// \sa + /// - AK::SoundEngine::RegisterResourceMonitorCallback() + /// - AkResourceMonitorCallbackFunc + virtual AKRESULT UnregisterResourceMonitorCallback( + AkResourceMonitorCallbackFunc in_pCallback ///< Function to unregister as a resource monitor callback. + ) = 0; + + /// Registers a callback for the Audio Device status changes. + /// The callback will be called from the audio thread + /// Can be called prior to AK::SoundEngine::Init + /// \sa AK::SoundEngine::AddOutput + virtual AKRESULT RegisterAudioDeviceStatusCallback( + AK::AkDeviceStatusCallbackFunc in_pCallback ///< Function to register as a status callback. + ) = 0; + + /// Unregisters the callback for the Audio Device status changes, registered by RegisterAudioDeviceStatusCallback + virtual AKRESULT UnregisterAudioDeviceStatusCallback() = 0; + //@} + +#ifdef AK_SUPPORT_WCHAR + //////////////////////////////////////////////////////////////////////// + /// @name Getting ID from strings + //@{ + + /// Universal converter from Unicode string to ID for the sound engine. + /// This function will hash the name based on a algorithm ( provided at : /AK/Tools/Common/AkFNVHash.h ) + /// Note: + /// This function does return a AkUInt32, which is totally compatible with: + /// AkUniqueID, AkStateGroupID, AkStateID, AkSwitchGroupID, AkSwitchStateID, AkRtpcID, and so on... + /// \sa + /// - AK::SoundEngine::PostEvent + /// - AK::SoundEngine::SetRTPCValue + /// - AK::SoundEngine::SetSwitch + /// - AK::SoundEngine::SetState + /// - AK::SoundEngine::PostTrigger + /// - AK::SoundEngine::SetGameObjectAuxSendValues + /// - AK::SoundEngine::LoadBank + /// - AK::SoundEngine::UnloadBank + /// - AK::SoundEngine::PrepareEvent + /// - AK::SoundEngine::PrepareGameSyncs + virtual AkUInt32 GetIDFromString(const wchar_t* in_pszString) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Universal converter from string to ID for the sound engine. + /// This function will hash the name based on a algorithm ( provided at : /AK/Tools/Common/AkFNVHash.h ) + /// Note: + /// This function does return a AkUInt32, which is totally compatible with: + /// AkUniqueID, AkStateGroupID, AkStateID, AkSwitchGroupID, AkSwitchStateID, AkRtpcID, and so on... + /// \sa + /// - AK::SoundEngine::PostEvent + /// - AK::SoundEngine::SetRTPCValue + /// - AK::SoundEngine::SetSwitch + /// - AK::SoundEngine::SetState + /// - AK::SoundEngine::PostTrigger + /// - AK::SoundEngine::SetGameObjectAuxSendValues + /// - AK::SoundEngine::LoadBank + /// - AK::SoundEngine::UnloadBank + /// - AK::SoundEngine::PrepareEvent + /// - AK::SoundEngine::PrepareGameSyncs + virtual AkUInt32 GetIDFromString(const char* in_pszString) = 0; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Event Management + //@{ + + /// Asynchronously posts an Event to the sound engine (by event ID). + /// The callback function can be used to be noticed when markers are reached or when the event is finished. + /// An array of wave file sources can be provided to resolve External Sources triggered by the event. + /// \return The playing ID of the event launched, or AK_INVALID_PLAYING_ID if posting the event failed and an error will be displayed in the debug console and the Wwise Profiler. + /// \remarks + /// If used, the array of external sources should contain the information for each external source triggered by the + /// event. When triggering an event with multiple external sources, you need to differentiate each source + /// by using the cookie property in the External Source in the Wwise project and in AkExternalSourceInfo. + /// \aknote If an event triggers the playback of more than one external source, they must be named uniquely in the project + /// (therefore have a unique cookie) in order to tell them apart when filling the AkExternalSourceInfo structures. + /// \endaknote + /// \sa + /// - \ref concept_events + /// - \ref integrating_external_sources + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::GetSourcePlayPosition() + virtual AkPlayingID PostEvent( + AkUniqueID in_eventID, ///< Unique ID of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID + AkUInt32 in_uFlags = 0, ///< Bitmask: see \ref AkCallbackType + AkCallbackFunc in_pfnCallback = NULL, ///< Callback function + void* in_pCookie = NULL, ///< Callback cookie that will be sent to the callback function along with additional information + AkUInt32 in_cExternals = 0, ///< Optional count of external source structures + AkExternalSourceInfo* in_pExternalSources = NULL,///< Optional array of external source resolution information + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID///< Optional (advanced users only) Specify the playing ID to target with the event. Will Cause active actions in this event to target an existing Playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any for normal playback. + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Posts an Event to the sound engine (by Event name), using callbacks. + /// The callback function can be used to be noticed when markers are reached or when the event is finished. + /// An array of wave file sources can be provided to resolve External Sources triggered by the event. + /// \return The playing ID of the event launched, or AK_INVALID_PLAYING_ID if posting the event failed and an error will be displayed in the debug console and the Wwise Profiler. + /// \remarks + /// If used, the array of external sources should contain the information for each external source triggered by the + /// event. When triggering an event with multiple external sources, you need to differentiate each source + /// by using the cookie property in the External Source in the Wwise project and in AkExternalSourceInfo. + /// \aknote If an event triggers the playback of more than one external source, they must be named uniquely in the project + /// (therefore have a unique cookie) in order to tell them appart when filling the AkExternalSourceInfo structures. + /// \endaknote + /// \sa + /// - \ref concept_events + /// - \ref integrating_external_sources + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::GetSourcePlayPosition() + virtual AkPlayingID PostEvent( + const wchar_t* in_pszEventName, ///< Name of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID + AkUInt32 in_uFlags = 0, ///< Bitmask: see \ref AkCallbackType + AkCallbackFunc in_pfnCallback = NULL, ///< Callback function + void* in_pCookie = NULL, ///< Callback cookie that will be sent to the callback function along with additional information. + AkUInt32 in_cExternals = 0, ///< Optional count of external source structures + AkExternalSourceInfo* in_pExternalSources = NULL,///< Optional array of external source resolution information + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID///< Optional (advanced users only) Specify the playing ID to target with the event. Will Cause active actions in this event to target an existing Playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any for normal playback. + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Posts an Event to the sound engine (by Event name), using callbacks. + /// The callback function can be used to be noticed when markers are reached or when the event is finished. + /// An array of Wave file sources can be provided to resolve External Sources triggered by the event. P + /// \return The playing ID of the event launched, or AK_INVALID_PLAYING_ID if posting the event failed and an error will be displayed in the debug console and the Wwise Profiler. + /// \remarks + /// If used, the array of external sources should contain the information for each external source triggered by the + /// event. When triggering an Event with multiple external sources, you need to differentiate each source + /// by using the cookie property in the External Source in the Wwise project and in AkExternalSourceInfo. + /// \aknote If an event triggers the playback of more than one external source, they must be named uniquely in the project + /// (therefore have a unique cookie) in order to tell them apart when filling the AkExternalSourceInfo structures. + /// \endaknote + /// \sa + /// - \ref concept_events + /// - \ref integrating_external_sources + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::GetSourcePlayPosition() + virtual AkPlayingID PostEvent( + const char* in_pszEventName, ///< Name of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID + AkUInt32 in_uFlags = 0, ///< Bitmask: see \ref AkCallbackType + AkCallbackFunc in_pfnCallback = NULL, ///< Callback function + void* in_pCookie = NULL, ///< Callback cookie that will be sent to the callback function along with additional information. + AkUInt32 in_cExternals = 0, ///< Optional count of external source structures + AkExternalSourceInfo* in_pExternalSources = NULL,///< Optional array of external source resolution information + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID///< Optional (advanced users only) Specify the playing ID to target with the event. Will Cause active actions in this event to target an existing Playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any for normal playback. + ) = 0; + + /// Executes an action on all nodes that are referenced in the specified event in an action of type play. + /// \return + /// - \c AK_Success if the action was successfully queued. + /// - \c AK_IDNotFound if the Event was not found (not loaded or there is a typo in the ID) + /// \sa + /// - AK::SoundEngine::AkActionOnEventType + virtual AKRESULT ExecuteActionOnEvent( + AkUniqueID in_eventID, ///< Unique ID of the event + AK::SoundEngine::AkActionOnEventType in_ActionType, ///< Action to execute on all the elements that were played using the specified event. + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT, ///< Associated game object ID + AkTimeMs in_uTransitionDuration = 0, ///< Fade duration + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the transition + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Associated PlayingID + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Executes an action on all nodes that are referenced in the specified event in an action of type play. + /// \return + /// - \c AK_Success if the action was successfully queued. + /// - \c AK_IDNotFound if the Event was not found (not loaded or there is a typo in the ID) + /// \sa + /// - AK::SoundEngine::AkActionOnEventType + virtual AKRESULT ExecuteActionOnEvent( + const wchar_t* in_pszEventName, ///< Name of the event + AK::SoundEngine::AkActionOnEventType in_ActionType, ///< Action to execute on all the elements that were played using the specified event. + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT, ///< Associated game object ID + AkTimeMs in_uTransitionDuration = 0, ///< Fade duration + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the transition + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Associated PlayingID + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Executes an Action on all nodes that are referenced in the specified Event in an Action of type Play. + /// \return + /// - \c AK_Success if the action was successfully queued. + /// - \c AK_IDNotFound if the Event was not found (not loaded or there is a typo in the ID) + /// \sa + /// - AK::SoundEngine::AkActionOnEventType + virtual AKRESULT ExecuteActionOnEvent( + const char* in_pszEventName, ///< Name of the event + AK::SoundEngine::AkActionOnEventType in_ActionType, ///< Action to execute on all the elements that were played using the specified event. + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT, ///< Associated game object ID + AkTimeMs in_uTransitionDuration = 0, ///< Fade duration + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the transition + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Associated PlayingID + ) = 0; + + + /// Executes a number of MIDI Events on all nodes that are referenced in the specified Event in an Action of type Play. + /// The time at which a MIDI Event is posted is determined by in_bAbsoluteOffsets. If false, each MIDI event will be + /// posted in AkMIDIPost::uOffset samples from the start of the current frame. If true, each MIDI event will be posted + /// at the absolute time AkMIDIPost::uOffset samples. + /// To obtain the current absolute time, see AK::SoundEngine::GetSampleTick. + /// The duration of a sample can be determined from the sound engine's audio settings, via a call to AK::SoundEngine::GetAudioSettings. + /// If a playing ID is specified then that playing ID must be active. Otherwise a new playing ID will be assigned. + /// \return The playing ID of the event launched, or AK_INVALID_PLAYING_ID if posting the event failed and an error will be displayed in the debug console and the Wwise Profiler. + /// \sa + /// - AK::SoundEngine::GetAudioSettings + /// - AK::SoundEngine::GetSampleTick + /// - AK::SoundEngine::StopMIDIOnEvent + /// - \ref soundengine_midi_event_playing_id + virtual AkPlayingID PostMIDIOnEvent( + AkUniqueID in_eventID, ///< Unique ID of the Event + AkGameObjectID in_gameObjectID, ///< Associated game object ID + AkMIDIPost* in_pPosts, ///< MIDI Events to post + AkUInt16 in_uNumPosts, ///< Number of MIDI Events to post + bool in_bAbsoluteOffsets = false, ///< Set to true when AkMIDIPost::uOffset are absolute, false when relative to current frame + AkUInt32 in_uFlags = 0, ///< Bitmask: see \ref AkCallbackType + AkCallbackFunc in_pfnCallback = NULL, ///< Callback function + void* in_pCookie = NULL, ///< Callback cookie that will be sent to the callback function along with additional information + AkPlayingID in_playingID = AK_INVALID_PLAYING_ID ///< Target playing ID + ) = 0; + + /// Stops MIDI notes on all nodes that are referenced in the specified event in an action of type play, + /// with the specified Game Object. Invalid parameters are interpreted as wildcards. For example, calling + /// this function with in_eventID set to AK_INVALID_UNIQUE_ID will stop all MIDI notes for Game Object + /// in_gameObjectID. + /// \return + /// - \c AK_Success if the stop command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::PostMIDIOnEvent + /// - \ref soundengine_midi_event_playing_id + virtual AKRESULT StopMIDIOnEvent( + AkUniqueID in_eventID = AK_INVALID_UNIQUE_ID, ///< Unique ID of the Event + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT, ///< Associated game object ID + AkPlayingID in_playingID = AK_INVALID_PLAYING_ID ///< Target playing ID + ) = 0; + + + /// Starts streaming the first part of all streamed files referenced by an Event into a cache buffer. Caching streams are serviced when no other streams require the + /// available bandwidth. The files will remain cached until UnpinEventInStreamCache is called, or a higher priority pinned file needs the space and the limit set by + /// uMaxCachePinnedBytes is exceeded. + /// \remarks The amount of data from the start of the file that will be pinned to cache is determined by the prefetch size. The prefetch size is set via the authoring tool and stored in the sound banks. + /// \remarks It is possible to override the prefetch size stored in the sound bank via the low level IO. For more information see AK::StreamMgr::IAkFileLocationResolver::Open() and AkFileSystemFlags. + /// \remarks If this function is called additional times with the same event, then the priority of the caching streams are updated. Note however that priority is passed down to the stream manager + /// on a file-by-file basis, and if another event is pinned to cache that references the same file but with a different priority, then the first priority will be updated with the most recent value. + /// \remarks If the event references files that are chosen based on a State Group (via a switch container), all files in all states will be cached. Those in the current active state + /// will get cached with active priority, while all other files will get cached with inactive priority. + /// \remarks in_uInactivePriority is only relevant for events that reference switch containers that are assigned to State Groups. This parameter is ignored for all other events, including events that only reference + /// switch containers that are assigned to Switch Groups. Files that are chosen based on a Switch Group have a different switch value per game object, and are all effectively considered active by the pin-to-cache system. + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::GetBufferStatusForPinnedEvent + /// - AK::SoundEngine::UnpinEventInStreamCache + /// - AK::StreamMgr::IAkFileLocationResolver::Open + /// - AkFileSystemFlags + virtual AKRESULT PinEventInStreamCache( + AkUniqueID in_eventID, ///< Unique ID of the event + AkPriority in_uActivePriority, ///< Priority of active stream caching I/O + AkPriority in_uInactivePriority ///< Priority of inactive stream caching I/O + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Starts streaming the first part of all streamed files referenced by an event into a cache buffer. Caching streams are serviced when no other streams require the + /// available bandwidth. The files will remain cached until UnpinEventInStreamCache is called, or a higher priority pinned file needs the space and the limit set by + /// uMaxCachePinnedBytes is exceeded. + /// \remarks The amount of data from the start of the file that will be pinned to cache is determined by the prefetch size. The prefetch size is set via the authoring tool and stored in the sound banks. + /// \remarks It is possible to override the prefetch size stored in the sound bank via the low level IO. For more information see AK::StreamMgr::IAkFileLocationResolver::Open() and AkFileSystemFlags. + /// \remarks If this function is called additional times with the same event, then the priority of the caching streams are updated. Note however that priority is passed down to the stream manager + /// on a file-by-file basis, and if another event is pinned to cache that references the same file but with a different priority, then the first priority will be updated with the most recent value. + /// \remarks If the event references files that are chosen based on a State Group (via a Switch Container), all files in all states will be cached. Those in the current active state + /// will get cached with active priority, while all other files will get cached with inactive priority. + /// \remarks in_uInactivePriority is only relevant for events that reference switch containers that are assigned to State Groups. This parameter is ignored for all other events, including events that only reference + /// switch containers that are assigned to Switch Groups. Files that are chosen based on a Switch Group have a different switch value per game object, and are all effectively considered active by the pin-to-cache system. + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::GetBufferStatusForPinnedEvent + /// - AK::SoundEngine::UnpinEventInStreamCache + /// - AK::StreamMgr::IAkFileLocationResolver::Open + /// - AkFileSystemFlags + virtual AKRESULT PinEventInStreamCache( + const wchar_t* in_pszEventName, ///< Name of the event + AkPriority in_uActivePriority, ///< Priority of active stream caching I/O + AkPriority in_uInactivePriority ///< Priority of inactive stream caching I/O + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Starts streaming the first part of all streamed files referenced by an event into a cache buffer. Caching streams are serviced when no other streams require the + /// available bandwidth. The files will remain cached until UnpinEventInStreamCache is called, or a higher priority pinned file needs the space and the limit set by + /// uMaxCachePinnedBytes is exceeded. + /// \remarks The amount of data from the start of the file that will be pinned to cache is determined by the prefetch size. The prefetch size is set via the authoring tool and stored in the sound banks. + /// \remarks It is possible to override the prefetch size stored in the sound bank via the low level IO. For more information see AK::StreamMgr::IAkFileLocationResolver::Open() and AkFileSystemFlags. + /// \remarks If this function is called additional times with the same event, then the priority of the caching streams are updated. Note however that priority is passed down to the stream manager + /// on a file-by-file basis, and if another event is pinned to cache that references the same file but with a different priority, then the first priority will be updated with the most recent value. + /// \remarks If the event references files that are chosen based on a State Group (via a switch container), all files in all states will be cached. Those in the current active state + /// will get cached with active priority, while all other files will get cached with inactive priority. + /// \remarks in_uInactivePriority is only relevant for events that reference switch containers that are assigned to State Groups. This parameter is ignored for all other events, including events that only reference + /// switch containers that are assigned to Switch Groups. Files that are chosen based on a Switch Group have a different switch value per game object, and are all effectively considered active by the pin-to-cache system. + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::GetBufferStatusForPinnedEvent + /// - AK::SoundEngine::UnpinEventInStreamCache + /// - AK::StreamMgr::IAkFileLocationResolver::Open + /// - AkFileSystemFlags + virtual AKRESULT PinEventInStreamCache( + const char* in_pszEventName, ///< Name of the event + AkPriority in_uActivePriority, ///< Priority of active stream caching I/O + AkPriority in_uInactivePriority ///< Priority of inactive stream caching I/O + ) = 0; + + /// Releases the set of files that were previously requested to be pinned into cache via AK::SoundEngine::PinEventInStreamCache(). The file may still remain in stream cache + /// after AK::SoundEngine::UnpinEventInStreamCache() is called, until the memory is reused by the streaming memory manager in accordance with to its cache management algorithm. + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::PinEventInStreamCache + /// - AK::SoundEngine::GetBufferStatusForPinnedEvent + virtual AKRESULT UnpinEventInStreamCache( + AkUniqueID in_eventID ///< Unique ID of the event + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Releases the set of files that were previously requested to be pinned into cache via AK::SoundEngine::PinEventInStreamCache(). The file may still remain in stream cache + /// after AK::SoundEngine::UnpinEventInStreamCache() is called, until the memory is reused by the streaming memory manager in accordance with to its cache management algorithm. + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::PinEventInStreamCache + /// - AK::SoundEngine::GetBufferStatusForPinnedEvent + virtual AKRESULT UnpinEventInStreamCache( + const wchar_t* in_pszEventName ///< Name of the event + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Releases the set of files that were previously requested to be pinned into cache via AK::SoundEngine::PinEventInStreamCache(). The file may still remain in stream cache + /// after AK::SoundEngine::UnpinEventInStreamCache() is called, until the memory is reused by the streaming memory manager in accordance with to its cache management algorithm. + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::PinEventInStreamCache + /// - AK::SoundEngine::GetBufferStatusForPinnedEvent + virtual AKRESULT UnpinEventInStreamCache( + const char* in_pszEventName ///< Name of the event + ) = 0; + + /// Returns information about an Event that was requested to be pinned into cache via AK::SoundEngine::PinEventInStreamCache(). + /// Retrieves the smallest buffer fill-percentage for each file referenced by the event, and whether + /// the cache-pinned memory limit is preventing any of the files from filling up their buffer. + /// \remarks To set the limit for the maximum number of bytes that can be pinned to cache, see \c AkDeviceSettings + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::PinEventInStreamCache + /// - AK::SoundEngine::UnpinEventInStreamCache + /// - AkDeviceSettings + virtual AKRESULT GetBufferStatusForPinnedEvent( + AkUniqueID in_eventID, ///< Unique ID of the event + AkReal32& out_fPercentBuffered, ///< Fill-percentage (out of 100) of requested buffer size for least buffered file in the event. + bool& out_bCachePinnedMemoryFull ///< True if at least one file can not complete buffering because the cache-pinned memory limit would be exceeded. + ) = 0; + + /// Returns information about an Event that was requested to be pinned into cache via \c AK::SoundEngine::PinEventInStreamCache(). + /// Retrieves the smallest buffer fill-percentage for each file referenced by the event, and whether + /// the cache-pinned memory limit is preventing any of the files from filling up their buffer. + /// \remarks To set the limit for the maximum number of bytes that can be pinned to cache, see AkDeviceSettings + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::PinEventInStreamCache + /// - AK::SoundEngine::UnpinEventInStreamCache + /// - AkDeviceSettings + virtual AKRESULT GetBufferStatusForPinnedEvent( + const char* in_pszEventName, ///< Name of the event + AkReal32& out_fPercentBuffered, ///< Fill-percentage (out of 100) of requested buffer size for least buffered file in the event. + bool& out_bCachePinnedMemoryFull ///< True if at least one file can not complete buffering because the cache-pinned memory limit would be exceeded. + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Returns information about an Event that was requested to be pinned into cache via \c AK::SoundEngine::PinEventInStreamCache(). + /// Retrieves the smallest buffer fill-percentage for each file referenced by the event, and whether + /// the cache-pinned memory limit is preventing any of the files from filling up their buffer. + /// \remarks To set the limit for the maximum number of bytes that can be pinned to cache, see AkDeviceSettings + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::PinEventInStreamCache + /// - AK::SoundEngine::UnpinEventInStreamCache + /// - AkDeviceSettings + virtual AKRESULT GetBufferStatusForPinnedEvent( + const wchar_t* in_pszEventName, ///< Name of the event + AkReal32& out_fPercentBuffered, ///< Fill-percentage (out of 100) of requested buffer size for least buffered file in the event. + bool& out_bCachePinnedMemoryFull ///< True if at least one file can not complete buffering because the cache-pinned memory limit would be exceeded. + ) = 0; +#endif + + /// Seeks inside all playing objects that are referenced in Play Actions of the specified Event. + /// + /// Notes: + /// - This works with all objects of the actor-mixer hierarchy, and also with Music Segments and Music Switch Containers. + /// - There is a restriction with sounds that play within a continuous sequence. Seeking is ignored + /// if one of their ancestors is a continuous (random or sequence) container with crossfade or + /// trigger rate transitions. Seeking is also ignored with sample-accurate transitions, unless + /// the sound that is currently playing is the first sound of the sequence. + /// - Seeking is also ignored with voices that can go virtual with "From Beginning" behavior. + /// - Sounds/segments are stopped if in_iPosition is greater than their duration. + /// - in_iPosition is clamped internally to the beginning of the sound/segment. + /// - If the option "Seek to nearest marker" is used, the seeking position snaps to the nearest marker. + /// With objects of the actor-mixer hierarchy, markers are embedded in wave files by an external wave editor. + /// Note that looping regions ("sampler loop") are not considered as markers. Also, the "add file name marker" of the + /// conversion settings dialog adds a marker at the beginning of the file, which is considered when seeking + /// to nearest marker. In the case of objects of the interactive music hierarchy, user (wave) markers are ignored: + /// seeking occurs to the nearest segment cue (authored in the segment editor), including the Entry Cue, but excluding the Exit Cue. + /// - This method posts a command in the sound engine queue, thus seeking will not occur before + /// the audio thread consumes it (after a call to RenderAudio()). + /// + /// Notes specific to Music Segments: + /// - With Music Segments, in_iPosition is relative to the Entry Cue, in milliseconds. Use a negative + /// value to seek within the Pre-Entry. + /// - Music segments cannot be looped. You may want to listen to the AK_EndOfEvent notification + /// in order to restart them if required. + /// - In order to restart at the correct location, with all their tracks synchronized, Music Segments + /// take the "look-ahead time" property of their streamed tracks into account for seeking. + /// Consequently, the resulting position after a call to SeekOnEvent() might be earlier than the + /// value that was passed to the method. Use AK::MusicEngine::GetPlayingSegmentInfo() to query + /// the exact position of a segment. Also, the segment will be silent during that time + /// (so that it restarts precisely at the position that you specified). AK::MusicEngine::GetPlayingSegmentInfo() + /// also informs you about the remaining look-ahead time. + /// + /// Notes specific to Music Switch Containers: + /// - Seeking triggers a music transition towards the current (or target) segment. + /// This transition is subject to the container's transition rule that matches the current and defined in the container, + /// so the moment when seeking occurs depends on the rule's "Exit At" property. On the other hand, the starting position + /// in the target segment depends on both the desired seeking position and the rule's "Sync To" property. + /// - If the specified time is greater than the destination segment's length, the modulo is taken. + /// + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::PostEvent() + /// - AK::SoundEngine::GetSourcePlayPosition() + /// - AK::MusicEngine::GetPlayingSegmentInfo() + virtual AKRESULT SeekOnEvent( + AkUniqueID in_eventID, ///< Unique ID of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID; use AK_INVALID_GAME_OBJECT to affect all game objects + AkTimeMs in_iPosition, ///< Desired position where playback should restart, in milliseconds + bool in_bSeekToNearestMarker = false, ///< If true, the final seeking position will be made equal to the nearest marker (see note above) + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Specify the playing ID for the seek to be applied to. Will result in the seek happening only on active actions of the playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any, to seek on all active actions of this event ID. + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Seeks inside all playing objects that are referenced in Play Actions of the specified Event. + /// + /// Notes: + /// - This works with all objects of the actor-mixer hierarchy, and also with Music Segments and Music Switch Containers. + /// - There is a restriction with sounds that play within a continuous sequence. Seeking is ignored + /// if one of their ancestors is a continuous (random or sequence) container with crossfade or + /// trigger rate transitions. Seeking is also ignored with sample-accurate transitions, unless + /// the sound that is currently playing is the first sound of the sequence. + /// - Seeking is also ignored with voices that can go virtual with "From Beginning" behavior. + /// - With Music Segments, in_iPosition is relative to the Entry Cue, in milliseconds. Use a negative + /// value to seek within the Pre-Entry. + /// - Sounds/segments are stopped if in_iPosition is greater than their duration. + /// - in_iPosition is clamped internally to the beginning of the sound/segment. + /// - If the option "Seek to nearest marker" is used, the seeking position snaps to the nearest marker. + /// With objects of the actor-mixer hierarchy, markers are embedded in wave files by an external wave editor. + /// Note that looping regions ("sampler loop") are not considered as markers. Also, the "add file name marker" of the + /// conversion settings dialog adds a marker at the beginning of the file, which is considered when seeking + /// to nearest marker. In the case of objects of the interactive music hierarchy, user (wave) markers are ignored: + /// seeking occurs to the nearest segment cue (authored in the segment editor), including the Entry Cue, but excluding the Exit Cue. + /// - This method posts a command in the sound engine queue, thus seeking will not occur before + /// the audio thread consumes it (after a call to RenderAudio()). + /// + /// Notes specific to Music Segments: + /// - With Music Segments, in_iPosition is relative to the Entry Cue, in milliseconds. Use a negative + /// value to seek within the Pre-Entry. + /// - Music segments cannot be looped. You may want to listen to the AK_EndOfEvent notification + /// in order to restart them if required. + /// - In order to restart at the correct location, with all their tracks synchronized, Music Segments + /// take the "look-ahead time" property of their streamed tracks into account for seeking. + /// Consequently, the resulting position after a call to SeekOnEvent() might be earlier than the + /// value that was passed to the method. Use AK::MusicEngine::GetPlayingSegmentInfo() to query + /// the exact position of a segment. Also, the segment will be silent during that time + /// (so that it restarts precisely at the position that you specified). AK::MusicEngine::GetPlayingSegmentInfo() + /// also informs you about the remaining look-ahead time. + /// + /// Notes specific to Music Switch Containers: + /// - Seeking triggers a music transition towards the current (or target) segment. + /// This transition is subject to the container's transition rule that matches the current and defined in the container, + /// so the moment when seeking occurs depends on the rule's "Exit At" property. On the other hand, the starting position + /// in the target segment depends on both the desired seeking position and the rule's "Sync To" property. + /// - If the specified time is greater than the destination segment's length, the modulo is taken. + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::PostEvent() + /// - AK::SoundEngine::GetSourcePlayPosition() + /// - AK::MusicEngine::GetPlayingSegmentInfo() + virtual AKRESULT SeekOnEvent( + const wchar_t* in_pszEventName, ///< Name of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID; use AK_INVALID_GAME_OBJECT to affect all game objects + AkTimeMs in_iPosition, ///< Desired position where playback should restart, in milliseconds + bool in_bSeekToNearestMarker = false, ///< If true, the final seeking position will be made equal to the nearest marker (see note above) + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Specify the playing ID for the seek to be applied to. Will result in the seek happening only on active actions of the playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any, to seek on all active actions of this event ID. + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Seeks inside all playing objects that are referenced in Play Actions of the specified Event. + /// + /// Notes: + /// - This works with all objects of the actor-mixer hierarchy, and also with Music Segments and Music Switch Containers. + /// - There is a restriction with sounds that play within a continuous sequence. Seeking is ignored + /// if one of their ancestors is a continuous (random or sequence) container with crossfade or + /// trigger rate transitions. Seeking is also ignored with sample-accurate transitions, unless + /// the sound that is currently playing is the first sound of the sequence. + /// - Seeking is also ignored with voices that can go virtual with "From Beginning" behavior. + /// - With Music Segments, in_iPosition is relative to the Entry Cue, in milliseconds. Use a negative + /// value to seek within the Pre-Entry. + /// - Sounds/segments are stopped if in_iPosition is greater than their duration. + /// - in_iPosition is clamped internally to the beginning of the sound/segment. + /// - If the option "Seek to nearest marker" is used, the seeking position snaps to the nearest marker. + /// With objects of the actor-mixer hierarchy, markers are embedded in wave files by an external wave editor. + /// Note that looping regions ("sampler loop") are not considered as markers. Also, the "add file name marker" of the + /// conversion settings dialog adds a marker at the beginning of the file, which is considered when seeking + /// to nearest marker. In the case of objects of the interactive music hierarchy, user (wave) markers are ignored: + /// seeking occurs to the nearest segment cue (authored in the segment editor), including the Entry Cue, but excluding the Exit Cue. + /// - This method posts a command in the sound engine queue, thus seeking will not occur before + /// the audio thread consumes it (after a call to RenderAudio()). + /// + /// Notes specific to Music Segments: + /// - With Music Segments, in_iPosition is relative to the Entry Cue, in milliseconds. Use a negative + /// value to seek within the Pre-Entry. + /// - Music segments cannot be looped. You may want to listen to the AK_EndOfEvent notification + /// in order to restart them if required. + /// - In order to restart at the correct location, with all their tracks synchronized, Music Segments + /// take the "look-ahead time" property of their streamed tracks into account for seeking. + /// Consequently, the resulting position after a call to SeekOnEvent() might be earlier than the + /// value that was passed to the method. Use AK::MusicEngine::GetPlayingSegmentInfo() to query + /// the exact position of a segment. Also, the segment will be silent during that time + /// (so that it restarts precisely at the position that you specified). AK::MusicEngine::GetPlayingSegmentInfo() + /// also informs you about the remaining look-ahead time. + /// + /// Notes specific to Music Switch Containers: + /// - Seeking triggers a music transition towards the current (or target) segment. + /// This transition is subject to the container's transition rule that matches the current and defined in the container, + /// so the moment when seeking occurs depends on the rule's "Exit At" property. On the other hand, the starting position + /// in the target segment depends on both the desired seeking position and the rule's "Sync To" property. + /// - If the specified time is greater than the destination segment's length, the modulo is taken. + /// + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::PostEvent() + /// - AK::SoundEngine::GetSourcePlayPosition() + /// - AK::MusicEngine::GetPlayingSegmentInfo() + virtual AKRESULT SeekOnEvent( + const char* in_pszEventName, ///< Name of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID; use AK_INVALID_GAME_OBJECT to affect all game objects + AkTimeMs in_iPosition, ///< Desired position where playback should restart, in milliseconds + bool in_bSeekToNearestMarker = false, ///< If true, the final seeking position will be made equal to the nearest marker (see note above) + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Specify the playing ID for the seek to be applied to. Will result in the seek happening only on active actions of the playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any, to seek on all active actions of this event ID. + ) = 0; + + /// Seeks inside all playing objects that are referenced in Play Actions of the specified Event. + /// Seek position is specified as a percentage of the sound's total duration, and takes looping into account. + /// + /// Notes: + /// - This works with all objects of the actor-mixer hierarchy, and also with Music Segments and Music Switch Containers. + /// - There is a restriction with sounds that play within a continuous sequence. Seeking is ignored + /// if one of their ancestors is a continuous (random or sequence) container with crossfade or + /// trigger rate transitions. Seeking is also ignored with sample-accurate transitions, unless + /// the sound that is currently playing is the first sound of the sequence. + /// - Seeking is also ignored with voices that can go virtual with "From Beginning" behavior. + /// - in_iPosition is clamped internally to the beginning of the sound/segment. + /// - If the option "Seek to nearest marker" is used, the seeking position snaps to the nearest marker. + /// With objects of the actor-mixer hierarchy, markers are embedded in wave files by an external wave editor. + /// Note that looping regions ("sampler loop") are not considered as markers. Also, the "add file name marker" of the + /// conversion settings dialog adds a marker at the beginning of the file, which is considered when seeking + /// to nearest marker. In the case of objects of the interactive music hierarchy, user (wave) markers are ignored: + /// seeking occurs to the nearest segment cue (authored in the segment editor), including the Entry Cue, but excluding the Exit Cue. + /// - This method posts a command in the sound engine queue, thus seeking will not occur before + /// the audio thread consumes it (after a call to RenderAudio()). + /// + /// Notes specific to Music Segments: + /// - With Music Segments, \c in_fPercent is relative to the Entry Cue, and the segment's duration is the + /// duration between its entry and exit cues. Consequently, you cannot seek within the pre-entry or + /// post-exit of a segment using this method. Use absolute values instead. + /// - Music Segments cannot be looped. You may want to listen to the \c AK_EndOfEvent notification + /// in order to restart them if required. + /// - In order to restart at the correct location, with all their tracks synchronized, Music Segments + /// take the "look-ahead time" property of their streamed tracks into account for seeking. + /// Consequently, the resulting position after a call to SeekOnEvent() might be earlier than the + /// value that was passed to the method. Use AK::MusicEngine::GetPlayingSegmentInfo() to query + /// the exact position of a segment. Also, the segment will be silent during the time that period + /// (so that it restarts precisely at the position that you specified). AK::MusicEngine::GetPlayingSegmentInfo() + /// also informs you about the remaining look-ahead time. + /// + /// Notes specific to Music Switch Containers: + /// - Seeking triggers a music transition towards the current (or target) segment. + /// This transition is subject to the container's transition rule that matches the current and defined in the container, + /// so the moment when seeking occurs depends on the rule's "Exit At" property. On the other hand, the starting position + /// in the target segment depends on both the desired seeking position and the rule's "Sync To" property. + /// - If the specified time is greater than the destination segment's length, the modulo is taken. + /// + /// \sa + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::PostEvent() + /// - AK::SoundEngine::GetSourcePlayPosition() + /// - AK::MusicEngine::GetPlayingSegmentInfo() + virtual AKRESULT SeekOnEvent( + AkUniqueID in_eventID, ///< Unique ID of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID; use AK_INVALID_GAME_OBJECT to affect all game objects + AkReal32 in_fPercent, ///< Desired position where playback should restart, expressed in a percentage of the file's total duration, between 0 and 1.f (see note above about infinite looping sounds) + bool in_bSeekToNearestMarker = false, ///< If true, the final seeking position will be made equal to the nearest marker (see note above) + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Specify the playing ID for the seek to be applied to. Will result in the seek happening only on active actions of the playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any, to seek on all active actions of this event ID. + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Seeks inside all playing objects that are referenced in Play Actions of the specified Event. + /// Seek position is specified as a percentage of the sound's total duration, and takes looping into account. + /// + /// Notes: + /// - This works with all objects of the actor-mixer hierarchy, and also with Music Segments and Music Switch Containers. + /// - There is a restriction with sounds that play within a continuous sequence. Seeking is ignored + /// if one of their ancestors is a continuous (random or sequence) container with crossfade or + /// trigger rate transitions. Seeking is also ignored with sample-accurate transitions, unless + /// the sound that is currently playing is the first sound of the sequence. + /// - Seeking is also ignored with voices that can go virtual with "From Beginning" behavior. + /// - If the option "Seek to nearest marker" is used, the seeking position snaps to the nearest marker. + /// With objects of the actor-mixer hierarchy, markers are embedded in wave files by an external wave editor. + /// Note that looping regions ("sampler loop") are not considered as markers. Also, the "add file name marker" of the + /// conversion settings dialog adds a marker at the beginning of the file, which is considered when seeking + /// to nearest marker. In the case of objects of the interactive music hierarchy, user (wave) markers are ignored: + /// seeking occurs to the nearest segment cue (authored in the segment editor), including the Entry Cue, but excluding the Exit Cue. + /// - This method posts a command in the sound engine queue, thus seeking will not occur before + /// the audio thread consumes it (after a call to RenderAudio()). + /// + /// Notes specific to Music Segments: + /// - With Music Segments, \c in_fPercent is relative to the Entry Cue, and the segment's duration is the + /// duration between its entry and exit cues. Consequently, you cannot seek within the pre-entry or + /// post-exit of a segment using this method. Use absolute values instead. + /// - Music Segments cannot be looped. You may want to listen to the \c AK_EndOfEvent notification + /// in order to restart them if required. + /// - In order to restart at the correct location, with all their tracks synchronized, Music Segments + /// take the "look-ahead time" property of their streamed tracks into account for seeking. + /// Consequently, the resulting position after a call to SeekOnEvent() might be earlier than the + /// value that was passed to the method. Use AK::MusicEngine::GetPlayingSegmentInfo() to query + /// the exact position of a segment. Also, the segment will be silent during the time that period + /// (so that it restarts precisely at the position that you specified). AK::MusicEngine::GetPlayingSegmentInfo() + /// also informs you about the remaining look-ahead time. + /// + /// Notes specific to Music Switch Containers: + /// - Seeking triggers a music transition towards the current (or target) segment. + /// This transition is subject to the container's transition rule that matches the current and defined in the container, + /// so the moment when seeking occurs depends on the rule's "Exit At" property. On the other hand, the starting position + /// in the target segment depends on both the desired seeking position and the rule's "Sync To" property. + /// - If the specified time is greater than the destination segment's length, the modulo is taken. + /// + /// \sa + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::PostEvent() + /// - AK::SoundEngine::GetSourcePlayPosition() + /// - AK::MusicEngine::GetPlayingSegmentInfo() + virtual AKRESULT SeekOnEvent( + const wchar_t* in_pszEventName, ///< Name of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID; use AK_INVALID_GAME_OBJECT to affect all game objects + AkReal32 in_fPercent, ///< Desired position where playback should restart, expressed in a percentage of the file's total duration, between 0 and 1.f (see note above about infinite looping sounds) + bool in_bSeekToNearestMarker = false, ///< If true, the final seeking position will be made equal to the nearest marker (see note above) + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Specify the playing ID for the seek to be applied to. Will result in the seek happening only on active actions of the playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any, to seek on all active actions of this event ID. + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Seeks inside all playing objects that are referenced in Play Actions of the specified Event. + /// Seek position is specified as a percentage of the sound's total duration, and takes looping into account. + /// + /// Notes: + /// - This works with all objects of the actor-mixer hierarchy, and also with Music Segments and Music Switch Containers. + /// - There is a restriction with sounds that play within a continuous sequence. Seeking is ignored + /// if one of their ancestors is a continuous (random or sequence) container with crossfade or + /// trigger rate transitions. Seeking is also ignored with sample-accurate transitions, unless + /// the sound that is currently playing is the first sound of the sequence. + /// - Seeking is also ignored with voices that can go virtual with "From Beginning" behavior. + /// - If the option "Seek to nearest marker" is used, the seeking position snaps to the nearest marker. + /// With objects of the actor-mixer hierarchy, markers are embedded in wave files by an external wave editor. + /// Note that looping regions ("sampler loop") are not considered as markers. Also, the "add file name marker" of the + /// conversion settings dialog adds a marker at the beginning of the file, which is considered when seeking + /// to nearest marker. In the case of objects of the interactive music hierarchy, user (wave) markers are ignored: + /// seeking occurs to the nearest segment cue (authored in the segment editor), including the Entry Cue, but excluding the Exit Cue. + /// - This method posts a command in the sound engine queue, thus seeking will not occur before + /// the audio thread consumes it (after a call to RenderAudio()). + /// + /// Notes specific to Music Segments: + /// - With Music Segments, in_fPercent is relative to the Entry Cue, and the segment's duration is the + /// duration between its entry and exit cues. Consequently, you cannot seek within the pre-entry or + /// post-exit of a segment using this method. Use absolute values instead. + /// - Music segments cannot be looped. You may want to listen to the AK_EndOfEvent notification + /// in order to restart them if required. + /// - In order to restart at the correct location, with all their tracks synchronized, Music Segments + /// take the "look-ahead time" property of their streamed tracks into account for seeking. + /// Consequently, the resulting position after a call to SeekOnEvent() might be earlier than the + /// value that was passed to the method. Use AK::MusicEngine::GetPlayingSegmentInfo() to query + /// the exact position of a segment. Also, the segment will be silent during the time that period + /// (so that it restarts precisely at the position that you specified). AK::MusicEngine::GetPlayingSegmentInfo() + /// also informs you about the remaining look-ahead time. + /// + /// Notes specific to Music Switch Containers: + /// - Seeking triggers a music transition towards the current (or target) segment. + /// This transition is subject to the container's transition rule that matches the current and defined in the container, + /// so the moment when seeking occurs depends on the rule's "Exit At" property. On the other hand, the starting position + /// in the target segment depends on both the desired seeking position and the rule's "Sync To" property. + /// - If the specified time is greater than the destination segment's length, the modulo is taken. + /// + /// \sa + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::PostEvent() + /// - AK::SoundEngine::GetSourcePlayPosition() + /// - AK::MusicEngine::GetPlayingSegmentInfo() + virtual AKRESULT SeekOnEvent( + const char* in_pszEventName, ///< Name of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID; use AK_INVALID_GAME_OBJECT to affect all game objects + AkReal32 in_fPercent, ///< Desired position where playback should restart, expressed in a percentage of the file's total duration, between 0 and 1.f (see note above about infinite looping sounds) + bool in_bSeekToNearestMarker = false, ///< If true, the final seeking position will be made equal to the nearest marker (see notes above). + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Specify the playing ID for the seek to be applied to. Will result in the seek happening only on active actions of the playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any, to seek on all active actions of this event ID. + ) = 0; + + /// Cancels all Event callbacks associated with a specific callback cookie.\n + /// \sa + /// - \c AK::SoundEngine::PostEvent() + virtual void CancelEventCallbackCookie( + void* in_pCookie ///< Callback cookie to be cancelled + ) = 0; + + /// Cancels all Event callbacks associated with a specific game object.\n + /// \sa + /// - \c AK::SoundEngine::PostEvent() + virtual void CancelEventCallbackGameObject( + AkGameObjectID in_gameObjectID ///< ID of the game object to be cancelled + ) = 0; + + /// Cancels all Event callbacks for a specific playing ID. + /// \sa + /// - \c AK::SoundEngine::PostEvent() + virtual void CancelEventCallback( + AkPlayingID in_playingID ///< Playing ID of the event that must not use callbacks + ) = 0; + + /// Gets the current position of the source associated with this playing ID, obtained from PostEvent(). If more than one source is playing, + /// the first to play is returned. + /// Notes: + /// - You need to pass AK_EnableGetSourcePlayPosition to PostEvent() in order to use this function, otherwise + /// it returns AK_Fail, even if the playing ID is valid. + /// - The source's position is updated at every audio frame, and the time at which this occurs is stored. + /// When you call this function from your thread, you therefore query the position that was updated in the previous audio frame. + /// If in_bExtrapolate is true (default), the returned position is extrapolated using the elapsed time since last + /// sound engine update and the source's playback rate. + /// \return + /// - \c AK_Success if successful. + /// - \c AK_InvalidParameter if the provided pointer is not valid. + /// - \c AK_PlayingIDNotFound if the playing ID is invalid (not playing yet, or finished playing). + /// \sa + /// - \ref soundengine_query_pos + /// - \ref concept_events + virtual AKRESULT GetSourcePlayPosition( + AkPlayingID in_PlayingID, ///< Playing ID returned by AK::SoundEngine::PostEvent() + AkTimeMs* out_puPosition, ///< Position of the source (in ms) associated with the specified playing ID + bool in_bExtrapolate = true ///< Position is extrapolated based on time elapsed since last sound engine update. + ) = 0; + + /// Gets the current position of the sources associated with this playing ID, obtained from PostEvent(). + /// Notes: + /// - You need to pass AK_EnableGetSourcePlayPosition to PostEvent() in order to use this function, otherwise + /// it returns AK_Fail, even if the playing ID is valid. + /// - The source's position is updated at every audio frame, and the time at which this occurs is stored. + /// When you call this function from your thread, you therefore query the position that was updated in the previous audio frame. + /// If in_bExtrapolate is true (default), the returned position is extrapolated using the elapsed time since last + /// sound engine update and the source's playback rate. + /// - If 0 is passed in for the number of entries (*in_pcPositions == 0) then only the number of positions will be returned and the + /// position array (out_puPositions) will not be updated. + /// - The io_pcPositions pointer must be non-NULL. + /// out_puPositions may be NULL if *io_pcPositions == 0, otherwise it must be non-NULL. + /// \return + /// - \c AK_Success if successful. + /// - \c AK_InvalidParameter if the provided pointer is not valid. + /// - \c AK_PlayingIDNotFound if the playing ID is invalid (not playing yet, or finished playing). + /// \sa + /// - \ref soundengine_query_pos + /// - \ref concept_events + virtual AKRESULT GetSourcePlayPositions( + AkPlayingID in_PlayingID, ///< Playing ID returned by AK::SoundEngine::PostEvent() + AkSourcePosition* out_puPositions, ///< Audio Node IDs and positions of sources associated with the specified playing ID + AkUInt32* io_pcPositions, ///< Number of entries in out_puPositions. Needs to be set to the size of the array: it is adjusted to the actual number of returned entries + bool in_bExtrapolate = true ///< Position is extrapolated based on time elapsed since last sound engine update + ) = 0; + + /// Gets the stream buffering of the sources associated with this playing ID, obtained from PostEvent(). + /// Notes: + /// - You need to pass AK_EnableGetSourceStreamBuffering to PostEvent() in order to use this function, otherwise + /// it returns AK_Fail, even if the playing ID is valid. + /// - The sources stream buffering is updated at every audio frame. If there are multiple sources associated with this playing ID, + /// the value returned corresponds to the least buffered source. + /// - The returned buffering status out_bIsBuffering will be true If any of the sources associated with the playing ID are actively being buffered. + /// It will be false if all of them have reached the end of file, or have reached a state where they are buffered enough and streaming is temporarily idle. + /// - Purely in-memory sources are excluded from this database. If all sources are in-memory, GetSourceStreamBuffering() will return AK_Fail. + /// - The returned buffering amount and state is not completely accurate with some hardware-accelerated codecs. In such cases, the amount of stream buffering is generally underestimated. + /// On the other hand, it is not guaranteed that the source will be ready to produce data at the next audio frame even if out_bIsBuffering has turned to false. + /// \return + /// - \c AK_Success if successful. + /// - \c AK_PlayingIDNotFound if the source data associated with this playing ID is not found, for example if PostEvent() was not called with AK_EnableGetSourceStreamBuffering, or if the header was not parsed. + /// \sa + /// - \ref concept_events + virtual AKRESULT GetSourceStreamBuffering( + AkPlayingID in_PlayingID, ///< Playing ID returned by AK::SoundEngine::PostEvent() + AkTimeMs& out_buffering, ///< Returned amount of buffering (in ms) of the source (or one of the sources) associated with that playing ID + bool& out_bIsBuffering ///< Returned buffering status of the source(s) associated with that playing ID + ) = 0; + + /// Stops the current content playing associated to the specified game object ID. + /// If no game object is specified, all sounds will be stopped. + virtual void StopAll( + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT ///< (Optional)Specify a game object to stop only playback associated to the provided game object ID. + ) = 0; + + /// Stop the current content playing associated to the specified playing ID. + /// \aknote + /// This function is deprecated. Please use ExecuteActionOnPlayingID() in its place. + /// \endaknote + /// \sa + /// - AK::SoundEngine::ExecuteActionOnPlayingID() + virtual void StopPlayingID( + AkPlayingID in_playingID, ///< Playing ID to be stopped. + AkTimeMs in_uTransitionDuration = 0, ///< Fade duration + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear ///< Curve type to be used for the transition + ) = 0; + + /// Executes an Action on the content associated to the specified playing ID. + /// \sa + /// - AK::SoundEngine::AkActionOnEventType + virtual void ExecuteActionOnPlayingID( + AK::SoundEngine::AkActionOnEventType in_ActionType, ///< Action to execute on the specified playing ID. + AkPlayingID in_playingID, ///< Playing ID on which to execute the action. + AkTimeMs in_uTransitionDuration = 0, ///< Fade duration + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear ///< Curve type to be used for the transition + ) = 0; + + /// Sets the random seed value. Can be used to synchronize randomness + /// across instances of the Sound Engine. + /// \remark This seeds the number generator used for all container randomizations + /// and the plug-in RNG; since it acts globally, this should be called right + /// before any PostEvent call where randomness synchronization is required, + /// and cannot guarantee similar results for continuous containers. + /// \sa + /// - AK::IAkPluginServiceRNG + virtual void SetRandomSeed( + AkUInt32 in_uSeed ///< Random seed. + ) = 0; + + /// Mutes/Unmutes the busses tagged as background music. + /// This is automatically called for platforms that have user-music support. + /// This function is provided to give the same behavior on platforms that don't have user-music support. + virtual void MuteBackgroundMusic( + bool in_bMute ///< Sets true to mute, false to unmute. + ) = 0; + //@} + + /// Gets the state of the Background Music busses. This state is either set directly + /// with \c AK::SoundEngine::MuteBackgroundMusic or by the OS, if it has User Music services. + /// \return true if the background music busses are muted, false if not. + virtual bool GetBackgroundMusicMute() = 0; + //@} + + + /// Sends custom game data to a plug-in that resides on a bus (insert Effect or mixer plug-in). + /// Data will be copied and stored into a separate list. + /// Previous entry is deleted when a new one is sent. + /// Sets the data pointer to NULL to clear item from the list. + /// \aknote The plug-in type and ID is passed and matched with plugins set on the desired bus. + /// This means that you cannot send different data to various instances of the plug-in on a same bus.\endaknote + /// \return AK_Success if data was sent successfully. + virtual AKRESULT SendPluginCustomGameData( + AkUniqueID in_busID, ///< Bus ID + AkGameObjectID in_busObjectID, ///< Bus Object ID. Pass AK_INVALID_GAME_OBJECT to send custom data with global scope. Game object scope supersedes global scope, as with RTPCs. + AkPluginType in_eType, ///< Plug-in type (for example, source or effect) + AkUInt32 in_uCompanyID, ///< Company identifier (as declared in the plug-in description XML file) + AkUInt32 in_uPluginID, ///< Plug-in identifier (as declared in the plug-in description XML file) + const void* in_pData, ///< The data blob + AkUInt32 in_uSizeInBytes ///< Size of data + ) = 0; + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Game Objects + //@{ + + /// Registers a game object. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if the specified AkGameObjectID is invalid (0 and -1 are invalid) + /// \remark Registering a game object twice does nothing. Unregistering it once unregisters it no + /// matter how many times it has been registered. + /// \sa + /// - AK::SoundEngine::UnregisterGameObj() + /// - AK::SoundEngine::UnregisterAllGameObj() + /// - \ref concept_gameobjects + virtual AKRESULT RegisterGameObj( + AkGameObjectID in_gameObjectID ///< ID of the game object to be registered + ) = 0; + + /// Registers a game object. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if the specified AkGameObjectID is invalid (0 and -1 are invalid) + /// \remark Registering a game object twice does nothing. Unregistering it once unregisters it no + /// matter how many times it has been registered. + /// \sa + /// - AK::SoundEngine::UnregisterGameObj() + /// - AK::SoundEngine::UnregisterAllGameObj() + /// - \ref concept_gameobjects + virtual AKRESULT RegisterGameObj( + AkGameObjectID in_gameObjectID, ///< ID of the game object to be registered + const char* in_pszObjName ///< Name of the game object (for monitoring purpose) + ) = 0; + + /// Unregisters a game object. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if the specified AkGameObjectID is invalid (0 is an invalid ID) + /// \remark Registering a game object twice does nothing. Unregistering it once unregisters it no + /// matter how many times it has been registered. Unregistering a game object while it is + /// in use is allowed, but the control over the parameters of this game object is lost. + /// For example, say a sound associated with this game object is a 3D moving sound. This sound will + /// stop moving when the game object is unregistered, and there will be no way to regain control over the game object. + /// \sa + /// - AK::SoundEngine::RegisterGameObj() + /// - AK::SoundEngine::UnregisterAllGameObj() + /// - \ref concept_gameobjects + virtual AKRESULT UnregisterGameObj( + AkGameObjectID in_gameObjectID ///< ID of the game object to be unregistered. Use + /// AK_INVALID_GAME_OBJECT to unregister all game objects. + ) = 0; + + /// Unregister all game objects, or all game objects with a particular matching set of property flags. + /// This function to can be used to unregister all game objects. + /// \return + /// - \c AK_Success if successful + /// \remark Registering a game object twice does nothing. Unregistering it once unregisters it no + /// matter how many times it has been registered. Unregistering a game object while it is + /// in use is allowed, but the control over the parameters of this game object is lost. + /// For example, if a sound associated with this game object is a 3D moving sound, it will + /// stop moving once the game object is unregistered, and there will be no way to recover + /// the control over this game object. + /// \sa + /// - AK::SoundEngine::RegisterGameObj() + /// - AK::SoundEngine::UnregisterGameObj() + /// - \ref concept_gameobjects + virtual AKRESULT UnregisterAllGameObj( + ) = 0; + + /// Sets the position of a game object. + /// \warning The object's orientation vector (in_Position.Orientation) must be normalized. + /// \return + /// - \c AK_Success when successful + /// - \c AK_InvalidParameter if parameters are not valid, for example: + /// + in_Position makes an invalid transform + /// + in_eFlags is not one of the valid enum values + /// + the game object ID is in the reserved ID range. + /// \sa + /// - \ref soundengine_3dpositions + virtual AKRESULT SetPosition( + AkGameObjectID in_GameObjectID, ///< Game Object identifier + const AkSoundPosition& in_Position,///< Position to set; in_Position.Orientation must be normalized. + AkSetPositionFlags in_eFlags = AkSetPositionFlags_Default ///< Optional flags to independently set the position of the emitter or listener component. + ) = 0; + + /// Sets multiple positions to a single game object. + /// Setting multiple positions on a single game object is a way to simulate multiple emission sources while using the resources of only one voice. + /// This can be used to simulate wall openings, area sounds, or multiple objects emitting the same sound in the same area. + /// \aknote Calling AK::SoundEngine::SetMultiplePositions() with only one position is the same as calling AK::SoundEngine::SetPosition() \endaknote + /// \return + /// - \c AK_Success when successful + /// - \c AK_CommandTooLarge if the number of positions is too large for the command queue. Reduce the number of positions. + /// - \c AK_InvalidParameter if parameters are not valid, for example: + /// + in_Position makes an invalid transform + /// + in_eFlags is not one of the valid enum values + /// + the game object ID is in the reserved ID range. + /// \sa + /// - \ref soundengine_3dpositions + /// - \ref soundengine_3dpositions_multiplepos + /// - \ref AK::SoundEngine::MultiPositionType + virtual AKRESULT SetMultiplePositions( + AkGameObjectID in_GameObjectID, ///< Game Object identifier. + const AkSoundPosition* in_pPositions, ///< Array of positions to apply. + AkUInt16 in_NumPositions, ///< Number of positions specified in the provided array. + AK::SoundEngine::MultiPositionType in_eMultiPositionType = AK::SoundEngine::MultiPositionType_MultiDirections, ///< \ref AK::SoundEngine::MultiPositionType + AkSetPositionFlags in_eFlags = AkSetPositionFlags_Default ///< Optional flags to independently set the position of the emitter or listener component. + ) = 0; + + /// Sets multiple positions to a single game object, with flexible assignment of input channels. + /// Setting multiple positions on a single game object is a way to simulate multiple emission sources while using the resources of only one voice. + /// This can be used to simulate wall openings, area sounds, or multiple objects emitting the same sound in the same area. + /// \aknote Calling AK::SoundEngine::SetMultiplePositions() with only one position is the same as calling AK::SoundEngine::SetPosition() \endaknote + /// \return + /// - \c AK_Success when successful + /// - \c AK_CommandTooLarge if the number of positions is too large for the command queue. Reduce the number of positions. + /// - \c AK_InvalidParameter if parameters are not valid. + /// \sa + /// - \ref soundengine_3dpositions + /// - \ref soundengine_3dpositions_multiplepos + /// - \ref AK::SoundEngine::MultiPositionType + virtual AKRESULT SetMultiplePositions( + AkGameObjectID in_GameObjectID, ///< Game Object identifier. + const AkChannelEmitter* in_pPositions, ///< Array of positions to apply, each using its own channel mask. + AkUInt16 in_NumPositions, ///< Number of positions specified in the provided array. + AK::SoundEngine::MultiPositionType in_eMultiPositionType = AK::SoundEngine::MultiPositionType_MultiDirections, ///< \ref AK::SoundEngine::MultiPositionType + AkSetPositionFlags in_eFlags = AkSetPositionFlags_Default ///< Optional flags to independently set the position of the emitter or listener component. + ) = 0; + + /// Sets the scaling factor of a Game Object. + /// Modify the attenuation computations on this Game Object to simulate sounds with a larger or smaller area of effect. + /// \return + /// - \c AK_Success when successful + /// - \c AK_InvalidParameter if the scaling factor specified was 0 or negative. + /// - \c AK_InvalidFloatValue if the value specified was NaN or Inf + virtual AKRESULT SetScalingFactor( + AkGameObjectID in_GameObjectID, ///< Game object identifier + AkReal32 in_fAttenuationScalingFactor ///< Scaling Factor, 1 means 100%, 0.5 means 50%, 2 means 200%, and so on. + ) = 0; + + /// Use the position of a separate game object for distance calculations for a specified listener. + /// When AK::SoundEngine::SetDistanceProbe() is called, Wwise calculates distance attenuation and filtering + /// based on the distance between the distance probe Game Object (\c in_distanceProbeGameObjectID) and the emitter Game Object's position. + /// In third-person perspective applications, the distance probe Game Object may be set to the player character's position, + /// and the listener Game Object's position to that of the camera. In this scenario, attenuation is based on + /// the distance between the character and the sound, whereas panning, spatialization, and spread and focus calculations are base on the camera. + /// Both Game Objects, \c in_listenerGameObjectID and \c in_distanceProbeGameObjectID must have been previously registered using AK::SoundEngine::RegisterGameObj. + /// This funciton is optional. if AK::SoundEngine::SetDistanceProbe() is never called, distance calculations are based on the listener Game Object position. + /// To clear the distance probe, and revert to using the listener position for distance calculations, pass \c AK_INVALID_GAME_OBJECT to \c in_distanceProbeGameObjectID. + /// \aknote If the distance probe Game Object is assigned multiple positions, then the first position is used for distance calculations by the listener. \endaknote + /// \return + /// - \c AK_Success when successful + /// \sa + /// - AK::SoundEngine::SetPosition() + virtual AKRESULT SetDistanceProbe( + AkGameObjectID in_listenerGameObjectID, ///< Game object identifier for the listener. Must have been previously registered via RegisterGameObj. + AkGameObjectID in_distanceProbeGameObjectID ///< Game object identifier for the distance probe, or \c AK_INVALID_GAME_OBJECT to reset distance probe. If valid, must have been previously registered via RegisterGameObj. + ) = 0; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Bank Management + //@{ + + /// Unload all currently loaded banks. + /// It also internally calls ClearPreparedEvents() since at least one bank must have been loaded to allow preparing events. + /// \return + /// - \c AK_Success if successful + /// - \c AK_NotInitialized if the sound engine was not correctly initialized or if there is not enough memory to handle the command + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::LoadBank() + /// - \ref soundengine_banks + virtual AKRESULT ClearBanks() = 0; + + /// Sets the I/O settings of the bank load and prepare event processes. + /// The sound engine uses default values unless explicitly set by calling this method. + /// \warning This function must be called before loading banks. + /// \return + /// - \c AK_Success if successful + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console + /// \sa + /// - \ref soundengine_banks + /// - \ref streamingdevicemanager + virtual AKRESULT SetBankLoadIOSettings( + AkReal32 in_fThroughput, ///< Average throughput of bank data streaming (bytes/ms) (the default value is AK_DEFAULT_BANK_THROUGHPUT) + AkPriority in_priority ///< Priority of bank streaming (the default value is AK_DEFAULT_PRIORITY) + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Load a bank synchronously (by Unicode string).\n + /// The bank name and type are passed to the Stream Manager. + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// A bank load request will be posted, and consumed by the Bank Manager thread. + /// The function returns when the request has been completely processed. + /// \return + /// The bank ID, which is obtained by hashing the bank name (see GetIDFromString()). + /// You may use this ID with UnloadBank(). + /// - \c AK_Success: Load or unload successful. + /// - \c AK_BankAlreadyLoaded: This bank is already loaded, nothing done. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_InvalidParameter: Invalid parameter, invalid memory alignment. + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console + /// - \c AK_InvalidBankType if the bank type parameter is out of range. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure, check the debug console) + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// - The sound engine internally calls GetIDFromString(in_pszString) to return the correct bank ID. + /// Therefore, in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. The path should be resolved in + /// your implementation of the Stream Manager, or in the Low-Level I/O module if you use the default Stream Manager's implementation. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AK::SoundEngine::GetIDFromString() + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref streamingdevicemanager + /// - \ref streamingmanager_lowlevel + /// - \ref sdk_bank_training + virtual AKRESULT LoadBank( + const wchar_t* in_pszString, ///< Name of the bank to load + AkBankID& out_bankID, ///< Returned bank ID + AkBankType in_bankType = AkBankType_User ///< Type of the bank to load + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Loads a bank synchronously.\n + /// The bank name and type are passed to the Stream Manager. + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// A bank load request will be posted, and consumed by the Bank Manager thread. + /// The function returns when the request has been completely processed. + /// \return + /// The bank ID, which is obtained by hashing the bank name (see GetIDFromString()). + /// You may use this ID with UnloadBank(). + /// - \c AK_Success: Load or unload successful. + /// - \c AK_BankAlreadyLoaded: This bank is already loaded, nothing done. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console + /// - \c AK_InvalidBankType if the bank type parameter is out of range. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// - The sound engine internally calls GetIDFromString(in_pszString) to return the correct bank ID. + /// Therefore, in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. The path should be resolved in + /// your implementation of the Stream Manager, or in the Low-Level I/O module if you use the default Stream Manager's implementation. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AK::SoundEngine::GetIDFromString + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref streamingdevicemanager + /// - \ref streamingmanager_lowlevel + /// - \ref sdk_bank_training + virtual AKRESULT LoadBank( + const char* in_pszString, ///< Name of the bank to load + AkBankID& out_bankID, ///< Returned bank ID + AkBankType in_bankType = AkBankType_User ///< Type of the bank to load + ) = 0; + + /// Loads a bank synchronously (by ID).\n + /// \aknote Requires that the "Use SoundBank names" option be unchecked in the Wwise Project Settings. \endaknote + /// The bank ID is passed to the Stream Manager. + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// A bank load request will be posted, and consumed by the Bank Manager thread. + /// The function returns when the request has been completely processed. + /// \return + /// - \c AK_Success: Load or unload successful. + /// - \c AK_BankAlreadyLoaded: This bank is already loaded, nothing done. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. The bank is either shorter than expected or its data corrupted. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console or Wwise Profiler + /// - \c AK_InvalidBankType if the bank type parameter is out of range. + /// - \c AK_FileNotFound if the bank file was not found on disk. + /// - \c AK_FilePermissionError if the file permissions are wrong for the file + /// - \c AK_Fail: Load or unload failed for any other reason. , check the debug console or Wwise Profiler + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + virtual AKRESULT LoadBank( + AkBankID in_bankID, ///< Bank ID of the bank to load + AkBankType in_bankType = AkBankType_User ///< Type of the bank to load + ) = 0; + + /// Loads a bank synchronously (from in-memory data, in-place, user bank only).\n + /// + /// IMPORTANT: Banks loaded from memory with in-place data MUST be unloaded using the UnloadBank function + /// providing the same memory pointer. Make sure you are using the correct UnloadBank(...) overload + /// + /// Use LoadBankMemoryView when you want to manage I/O on your side. Load the bank file + /// in a buffer and pass its address to the sound engine. + /// In-memory loading is in-place: *** the memory must be valid until the bank is unloaded. *** + /// A bank load request will be posted, and consumed by the Bank Manager thread. + /// The function returns when the request has been completely processed. + /// \return + /// The bank ID, which is stored in the first few bytes of the bank file. You may use this + /// ID with UnloadBank(). + /// - \c AK_Success: Load or unload successful. + /// - \c AK_BankAlreadyLoaded: This bank is already loaded, nothing done. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console + /// - \c AK_InvalidBankType if the bank is not a user-defined bank. + /// - \c AK_DataAlignmentError if the data pointer is not aligned properly + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// - The memory must be aligned on platform-specific AK_BANK_PLATFORM_DATA_ALIGNMENT bytes (see AkTypes.h). + /// - (XboxOne only): If the bank may contain XMA in memory data, the memory must be allocated using the Device memory allocator. + /// - Avoid using this function for banks containing a lot of events or structure data: this data will be unpacked into the sound engine heap, + /// making the supplied bank memory redundant. For event/structure-only banks, prefer LoadBankMemoryCopy(). + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + virtual AKRESULT LoadBankMemoryView( + const void * in_pInMemoryBankPtr, ///< Pointer to the in-memory bank to load (pointer is stored in sound engine, memory must remain valid) + AkUInt32 in_uInMemoryBankSize, ///< Size of the in-memory bank to load + AkBankID & out_bankID ///< Returned bank ID + ) = 0; + + /// Loads a bank synchronously (from in-memory data, in-place, any bank type).\n + /// + /// IMPORTANT: Banks loaded from memory with in-place data MUST be unloaded using the UnloadBank function + /// providing the same memory pointer. Make sure you are using the correct UnloadBank(...) overload + /// + /// Use LoadBankMemoryView when you want to manage I/O on your side. Load the bank file + /// in a buffer and pass its address to the sound engine. + /// In-memory loading is in-place: *** the memory must be valid until the bank is unloaded. *** + /// A bank load request will be posted, and consumed by the Bank Manager thread. + /// The function returns when the request has been completely processed. + /// \return + /// The bank ID, which is stored in the first few bytes of the bank file. You may use this + /// ID with UnloadBank(). + /// - \c AK_Success: Load or unload successful. + /// - \c AK_BankAlreadyLoaded: This bank is already loaded, nothing done. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console + /// - \c AK_InvalidBankType if the bank type parameter is out of range. + /// - \c AK_DataAlignmentError if the data pointer is not aligned properly + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// - The memory must be aligned on platform-specific AK_BANK_PLATFORM_DATA_ALIGNMENT bytes (see AkTypes.h). + /// - (XboxOne only): If the bank may contain XMA in memory data, the memory must be allocated using the Device memory allocator. + /// - Avoid using this function for banks containing a lot of events or structure data: this data will be unpacked into the sound engine heap, + /// making the supplied bank memory redundant. For event/structure-only banks, prefer LoadBankMemoryCopy(). + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + virtual AKRESULT LoadBankMemoryView( + const void* in_pInMemoryBankPtr, ///< Pointer to the in-memory bank to load (pointer is stored in sound engine, memory must remain valid) + AkUInt32 in_uInMemoryBankSize, ///< Size of the in-memory bank to load + AkBankID& out_bankID, ///< Returned bank ID + AkBankType& out_bankType ///< Returned bank type + ) = 0; + + /// Loads a bank synchronously (from in-memory data, out-of-place, user bank only).\n + /// + /// NOTE: Banks loaded from in-memory with out-of-place data must be unloaded using the standard UnloadBank function + /// (with no memory pointer). Make sure you are using the correct UnloadBank(...) overload + /// + /// Use LoadBankMemoryCopy when you want to manage I/O on your side. Load the bank file + /// in a buffer and pass its address to the sound engine, the media section of the bank will be copied into newly + /// allocated memory. + /// In-memory loading is out-of-place: the buffer can be release as soon as the function returns. The advantage of using this + /// over the in-place version is that there is no duplication of bank structures. + /// A bank load request will be posted, and consumed by the Bank Manager thread. + /// The function returns when the request has been completely processed. + /// \return + /// The bank ID, which is stored in the first few bytes of the bank file. You may use this + /// ID with UnloadBank(). + /// - \c AK_Success: Load or unload successful. + /// - \c AK_BankAlreadyLoaded: This bank is already loaded, nothing done. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console + /// - \c AK_InvalidBankType if the bank is not a user-defined bank. + /// - \c AK_DataAlignmentError if the data pointer is not aligned properly + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + virtual AKRESULT LoadBankMemoryCopy( + const void * in_pInMemoryBankPtr, ///< Pointer to the in-memory bank to load (pointer is not stored in sound engine, memory can be released after return) + AkUInt32 in_uInMemoryBankSize, ///< Size of the in-memory bank to load + AkBankID & out_bankID ///< Returned bank ID + ) = 0; + + /// Loads a bank synchronously (from in-memory data, out-of-place, any bank type).\n + /// + /// NOTE: Banks loaded from in-memory with out-of-place data must be unloaded using the standard UnloadBank function + /// (with no memory pointer). Make sure you are using the correct UnloadBank(...) overload + /// + /// Use LoadBankMemoryCopy when you want to manage I/O on your side. Load the bank file + /// in a buffer and pass its address to the sound engine, the media section of the bank will be copied into newly + /// allocated memory. + /// In-memory loading is out-of-place: the buffer can be release as soon as the function returns. The advantage of using this + /// over the in-place version is that there is no duplication of bank structures. + /// A bank load request will be posted, and consumed by the Bank Manager thread. + /// The function returns when the request has been completely processed. + /// \return + /// The bank ID, which is stored in the first few bytes of the bank file. You may use this + /// ID with UnloadBank(). + /// - \c AK_Success: Load or unload successful. + /// - \c AK_BankAlreadyLoaded: This bank is already loaded, nothing done. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console + /// - \c AK_DataAlignmentError if the data pointer is not aligned properly + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + virtual AKRESULT LoadBankMemoryCopy( + const void* in_pInMemoryBankPtr, ///< Pointer to the in-memory bank to load (pointer is not stored in sound engine, memory can be released after return) + AkUInt32 in_uInMemoryBankSize, ///< Size of the in-memory bank to load + AkBankID& out_bankID, ///< Returned bank ID + AkBankType& out_bankType ///< Returned bank type + ) = 0; + + /// Synchronously decodes Vorbis-encoded and Opus-encoded (Software version) media in a SoundBank. The file should already be read in memory before the decode operation. The out_pDecodedBankPtr can then be used with variants of LoadBank that load from in-memory data. + /// \n + /// CPU usage, RAM size, storage size and Internet bandwidth must be accounted for when developing a game, especially when it is aimed at mobile platforms. The DecodeBank function makes it possible to decode media at load time instead of decoding them every time they are played. + virtual AKRESULT DecodeBank( + const void* in_pInMemoryBankPtr, ///< Pointer to the in-memory bank to decode (pointer is not stored in sound engine, memory can be released after return) + AkUInt32 in_uInMemoryBankSize, ///< Size of the in-memory bank to decode + AkMemPoolId in_uPoolForDecodedBank, ///< Memory pool to allocate decoded bank into. Specify AK_INVALID_POOL_ID and out_pDecodedBankPtr=NULL to obtain decoded bank size without performing the decode operation. Pass AK_INVALID_POOL_ID and out_pDecodedBankPtr!=NULL to decode bank into specified pointer. + void*& out_pDecodedBankPtr, ///< Decoded bank memory location. + AkUInt32& out_uDecodedBankSize ///< Decoded bank memory size. + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Loads a bank asynchronously (by Unicode string).\n + /// The bank name is passed to the Stream Manager. + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// A bank load request will be posted to the Bank Manager consumer thread. + /// The function returns immediately. + /// \return + /// AK_Success if the scheduling was successful, AK_Fail otherwise. + /// Use a callback to be notified when completed, and get the status of the request. + /// The bank ID, which is obtained by hashing the bank name (see GetIDFromString()). + /// You may use this ID with UnloadBank(). + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// - The sound engine internally calls GetIDFromString(in_pszString) to return the correct bank ID. + /// Therefore, in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. The path should be resolved in + /// your implementation of the Stream Manager (AK::IAkStreamMgr::CreateStd()), or in the Low-Level I/O module + /// (AK::StreamMgr::IAkFileLocationResolver::Open()) if you use the default Stream Manager's implementation. + /// - The cookie (in_pCookie) is passed to the Low-Level I/O module for your convenience, in AK::StreamMgr::IAkFileLocationResolver::Open() + // as AkFileSystemFlags::pCustomParam. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref streamingdevicemanager + /// - \ref streamingmanager_lowlevel + /// - \ref sdk_bank_training + virtual AKRESULT LoadBank( + const wchar_t* in_pszString, ///< Name/path of the bank to load + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function, and also to AK::StreamMgr::IAkFileLocationResolver::Open() as AkFileSystemFlags::pCustomParam) + AkBankID& out_bankID, ///< Returned bank ID + AkBankType in_bankType = AkBankType_User ///< Type of the bank to load + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Loads a bank asynchronously.\n + /// The bank name is passed to the Stream Manager. + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// A bank load request will be posted to the Bank Manager consumer thread. + /// The function returns immediately. + /// \return + /// - \c AK_Success if the scheduling was successful, + /// - \c AK_InvalidBankType if in_bankType was invalid + /// Use a callback to be notified when completed, and get the status of the request. + /// The bank ID, which is obtained by hashing the bank name (see GetIDFromString()). + /// You may use this ID with UnloadBank(). + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the Event will fail. + /// - The sound engine internally calls GetIDFromString(in_pszString) to return the correct bank ID. + /// Therefore, \c in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. The path should be resolved in + /// your implementation of the Stream Manager (AK::IAkStreamMgr::CreateStd()), or in the Low-Level I/O module + /// (AK::StreamMgr::IAkFileLocationResolver::Open()) if you use the default Stream Manager's implementation. + /// - The cookie (in_pCookie) is passed to the Low-Level I/O module for your convenience, in AK::StreamMgr::IAkFileLocationResolver::Open() + // as AkFileSystemFlags::pCustomParam. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref streamingdevicemanager + /// - \ref streamingmanager_lowlevel + /// - \ref sdk_bank_training + virtual AKRESULT LoadBank( + const char* in_pszString, ///< Name/path of the bank to load + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function, and also to AK::StreamMgr::IAkFileLocationResolver::Open() as AkFileSystemFlags::pCustomParam) + AkBankID& out_bankID, ///< Returned bank ID + AkBankType in_bankType = AkBankType_User ///< Type of the bank to load + ) = 0; + + /// Loads a bank asynchronously (by ID).\n + /// \aknote Requires that the "Use SoundBank names" option be unchecked in the Wwise Project Settings. \endaknote + /// The bank ID is passed to the Stream Manager. + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// A bank load request will be posted to the Bank Manager consumer thread. + /// The function returns immediately. + /// \return + /// - \c AK_Success if the scheduling was successful, + /// - \c AK_InvalidBankType if in_bankType was invalid + /// Use a callback to be notified when completed, and get the status of the request. + /// The bank ID, which is obtained by hashing the bank name (see GetIDFromString()). + /// You may use this ID with \c UnloadBank(). + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// - The file path should be resolved in your implementation of the Stream Manager, or in the Low-Level I/O module if + /// you use the default Stream Manager's implementation. The ID overload of AK::IAkStreamMgr::CreateStd() and AK::StreamMgr::IAkFileLocationResolver::Open() are called. + /// - The cookie (in_pCookie) is passed to the Low-Level I/O module for your convenience, in AK::StreamMgr::IAkFileLocationResolver::Open() + // as AkFileSystemFlags::pCustomParam. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + virtual AKRESULT LoadBank( + AkBankID in_bankID, ///< Bank ID of the bank to load + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function, and also to AK::StreamMgr::IAkFileLocationResolver::Open() as AkFileSystemFlags::pCustomParam) + AkBankType in_bankType = AkBankType_User ///< Type of the bank to load + ) = 0; + + /// Loads a bank asynchronously (from in-memory data, in-place).\n + /// + /// IMPORTANT: Banks loaded from memory with in-place data MUST be unloaded using the UnloadBank function + /// providing the same memory pointer. Make sure you are using the correct UnloadBank(...) overload + /// + /// Use LoadBankMemoryView when you want to manage I/O on your side. Load the bank file + /// in a buffer and pass its address to the sound engine. + /// In-memory loading is in-place: *** the memory must be valid until the bank is unloaded. *** + /// A bank load request will be posted to the Bank Manager consumer thread. + /// The function returns immediately. + /// \return + /// - \c AK_Success if the scheduling was successful, + /// - \c AK_InvalidBankType if in_bankType was invalid + /// - \c AK_DataAlignmentError if the data pointer is not aligned properly + /// Use a callback to be notified when completed, and get the status of the request. + /// The bank ID, which is obtained by hashing the bank name (see GetIDFromString()). + /// You may use this ID with UnloadBank(). + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// - The memory must be aligned on platform-specific AK_BANK_PLATFORM_DATA_ALIGNMENT bytes (see AkTypes.h). + /// - (XboxOne only): If the bank may contain XMA in memory data, the memory must be allocated using the Device memory allocator. + /// - Avoid using this function for banks containing a lot of events or structure data: this data will be unpacked into the sound engine heap, + /// making the supplied bank memory redundant. For event/structure-only banks, prefer LoadBankMemoryCopy(). + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + virtual AKRESULT LoadBankMemoryView( + const void* in_pInMemoryBankPtr, ///< Pointer to the in-memory bank to load (pointer is stored in sound engine, memory must remain valid) + AkUInt32 in_uInMemoryBankSize, ///< Size of the in-memory bank to load + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie + AkBankID& out_bankID, ///< Returned bank ID + AkBankType& out_bankType ///< Returned bank type + ) = 0; + + /// Loads a bank asynchronously (from in-memory data, out-of-place).\n + /// + /// NOTE: Banks loaded from in-memory with out-of-place data must be unloaded using the standard UnloadBank function + /// (with no memory pointer). Make sure you are using the correct UnloadBank(...) overload + /// + /// Use LoadBankMemoryCopy when you want to manage I/O on your side. Load the bank file + /// in a buffer and pass its address to the sound engine, the media section of the bank will be copied into newly allocated + /// memory. + /// In-memory loading is out-of-place: the buffer can be released after the callback function is called. The advantage of using this + /// over the in-place version is that there is no duplication of bank structures. + /// A bank load request will be posted to the Bank Manager consumer thread. + /// The function returns immediately. + /// \return + /// - \c AK_Success if the scheduling was successful, + /// - \c AK_InvalidBankType if in_bankType was invalid + /// - \c AK_DataAlignmentError if the data pointer is not aligned properly + /// Use a callback to be notified when completed, and get the status of the request. + /// The bank ID, which is obtained by hashing the bank name (see GetIDFromString()). + /// You may use this ID with UnloadBank(). + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + virtual AKRESULT LoadBankMemoryCopy( + const void* in_pInMemoryBankPtr, ///< Pointer to the in-memory bank to load (pointer is not stored in sound engine, memory can be released after callback) + AkUInt32 in_uInMemoryBankSize, ///< Size of the in-memory bank to load + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie + AkBankID& out_bankID, ///< Returned bank ID + AkBankType& out_bankType ///< Returned bank type + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Unloads a bank synchronously (by Unicode string).\n + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// \return AK_Success if successful, AK_Fail otherwise. AK_Success is returned when the bank was not loaded. + /// \remarks + /// - The sound engine internally calls GetIDFromString(in_pszString) to retrieve the bank ID, + /// then it calls the synchronous version of UnloadBank() by ID. + /// Therefore, in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. + /// - In order to force the memory deallocation of the bank, sounds that use media from this bank will be stopped. + /// This means that streamed sounds or generated sounds will not be stopped. + /// \sa + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + virtual AKRESULT UnloadBank( + const wchar_t* in_pszString, ///< Name of the bank to unload + const void* in_pInMemoryBankPtr, ///< Memory pointer from where the bank was initially loaded from. (REQUIRED to determine which bank associated to a memory pointer must be unloaded). Pass NULL if NULL was passed when loading the bank or if LoadBankMemoryCopy was used to load the bank. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to unload + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Unloads a bank synchronously.\n + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// \return AK_Success if successful, AK_Fail otherwise. AK_Success is returned when the bank was not loaded. + /// \remarks + /// - The sound engine internally calls GetIDFromString(in_pszString) to retrieve the bank ID, + /// then it calls the synchronous version of UnloadBank() by ID. + /// Therefore, in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. + /// - In order to force the memory deallocation of the bank, sounds that use media from this bank will be stopped. + /// This means that streamed sounds or generated sounds will not be stopped. + /// \sa + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + virtual AKRESULT UnloadBank( + const char* in_pszString, ///< Name of the bank to unload + const void* in_pInMemoryBankPtr, ///< Memory pointer from where the bank was initially loaded from. (REQUIRED to determine which bank associated to a memory pointer must be unloaded). Pass NULL if NULL was passed when loading the bank or if LoadBankMemoryCopy was used to load the bank. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to unload + ) = 0; + + /// Unloads a bank synchronously (by ID and memory pointer).\n + /// \return AK_Success if successful, AK_Fail otherwise. AK_Success is returned when the bank was not loaded. + /// \remarks + /// - In order to force the memory deallocation of the bank, sounds that use media from this bank will be stopped. + /// This means that streamed sounds or generated sounds will not be stopped. + /// \sa + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + virtual AKRESULT UnloadBank( + AkBankID in_bankID, ///< ID of the bank to unload + const void* in_pInMemoryBankPtr, ///< Memory pointer from where the bank was initially loaded from. (REQUIRED to determine which bank associated to a memory pointer must be unloaded). Pass NULL if NULL was passed when loading the bank or if LoadBankMemoryCopy was used to load the bank. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to unload + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Unloads a bank asynchronously (by Unicode string).\n + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// \return AK_Success if scheduling successful (use a callback to be notified when completed) + /// \remarks + /// The sound engine internally calls GetIDFromString(in_pszString) to retrieve the bank ID, + /// then it calls the synchronous version of UnloadBank() by ID. + /// Therefore, in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. + /// - In order to force the memory deallocation of the bank, sounds that use media from this bank will be stopped. + /// This means that streamed sounds or generated sounds will not be stopped. + /// \sa + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + virtual AKRESULT UnloadBank( + const wchar_t* in_pszString, ///< Name of the bank to unload + const void* in_pInMemoryBankPtr, ///< Memory pointer from where the bank was initially loaded from. (REQUIRED to determine which bank associated to a memory pointer must be unloaded). Pass NULL if NULL was passed when loading the bank or if LoadBankMemoryCopy was used to load the bank. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function) + AkBankType in_bankType = AkBankType_User ///< Type of the bank to unload + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Unloads a bank asynchronously.\n + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// \return AK_Success if scheduling successful (use a callback to be notified when completed) + /// \remarks + /// The sound engine internally calls GetIDFromString(in_pszString) to retrieve the bank ID, + /// then it calls the synchronous version of UnloadBank() by ID. + /// Therefore, in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. + /// - In order to force the memory deallocation of the bank, sounds that use media from this bank will be stopped. + /// This means that streamed sounds or generated sounds will not be stopped. + /// \sa + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + virtual AKRESULT UnloadBank( + const char* in_pszString, ///< Name of the bank to unload + const void* in_pInMemoryBankPtr, ///< Memory pointer from where the bank was initially loaded from. (REQUIRED to determine which bank associated to a memory pointer must be unloaded). Pass NULL if NULL was passed when loading the bank or if LoadBankMemoryCopy was used to load the bank. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function) + AkBankType in_bankType = AkBankType_User ///< Type of the bank to unload + ) = 0; + + /// Unloads a bank asynchronously (by ID and memory pointer).\n + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// \return AK_Success if scheduling successful (use a callback to be notified when completed) + /// \remarks + /// - In order to force the memory deallocation of the bank, sounds that use media from this bank will be stopped. + /// This means that streamed sounds or generated sounds will not be stopped. + /// \sa + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + virtual AKRESULT UnloadBank( + AkBankID in_bankID, ///< ID of the bank to unload + const void* in_pInMemoryBankPtr, ///< Memory pointer from where the bank was initially loaded from. (REQUIRED to determine which bank associated to a memory pointer must be unloaded). Pass NULL if NULL was passed when loading the bank or if LoadBankMemoryCopy was used to load the bank. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function) + AkBankType in_bankType = AkBankType_User ///< Type of the bank to unload + ) = 0; + + /// Cancels all Event callbacks associated with a specific callback cookie specified while loading Banks of preparing Events.\n + /// \sa + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + virtual void CancelBankCallbackCookie( + void* in_pCookie ///< Callback cookie to be canceled + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// This function will load the Events, structural content, and optionally, the media content from the SoundBank. If the flag AkBankContent_All is specified, PrepareBank() will load the media content from + /// the bank; but, as opposed to LoadBank(), the media will be loaded one media item at a time instead of in one contiguous memory block. Using PrepareBank(), alone or in combination with PrepareEvent(), + /// will prevent in-memory duplication of media at the cost of some memory fragmentation. + /// Calling this function specifying the flag AkBankContent_StructureOnly will load only the structural part (including events) of this bank, + /// allowing using PrepareEvent() to load media on demand. + /// \sa + /// - \ref soundengine_banks_preparingbanks + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::PreparationType + /// \remarks + /// PrepareBank(), when called with the flag AkBankContent_StructureOnly, requires additional calls to PrepareEvent() to load the media for each event. PrepareEvent(), however, is unable to + /// access media content contained within SoundBanks and requires that the media be available as loose files in the file system. This flag may be useful to implement multiple loading configurations; + /// for example, a game may have a tool mode that uses PrepareEvent() to load loose files on-demand and, also, a game mode that uses LoadBank() to load the bank in entirety. + virtual AKRESULT PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const wchar_t* in_pszString, ///< Name of the bank to Prepare/Unprepare. + AK::SoundEngine::AkBankContent in_uFlags = AK::SoundEngine::AkBankContent_All, ///< Structures only (including events) or all content. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to Prepare/Unprepare. + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// This function will load the Events, structural content, and optionally, the media content from the SoundBank. If the flag AkBankContent_All is specified, PrepareBank() will load the media content from + /// the bank; but, as opposed to LoadBank(), the media will be loaded one media item at a time instead of in one contiguous memory block. Using PrepareBank(), alone or in combination with PrepareEvent(), + /// will prevent in-memory duplication of media at the cost of some memory fragmentation. + /// Calling this function specifying the flag AkBankContent_StructureOnly will load only the structural part (including events) of this bank, + /// allowing using PrepareEvent() to load media on demand. + /// \sa + /// - \ref soundengine_banks_preparingbanks + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::PreparationType + /// \remarks + /// \c PrepareBank(), when called with the flag \c AkBankContent_StructureOnly, requires additional calls to \c PrepareEvent() to load the media for each event. \c PrepareEvent(), however, is unable to + /// access media content contained within SoundBanks and requires that the media be available as loose files in the file system. This flag may be useful to implement multiple loading configurations; + /// for example, a game may have a tool mode that uses PrepareEvent() to load loose files on-demand and, also, a game mode that uses \c LoadBank() to load the bank in entirety. + virtual AKRESULT PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const char* in_pszString, ///< Name of the bank to Prepare/Unprepare. + AK::SoundEngine::AkBankContent in_uFlags = AK::SoundEngine::AkBankContent_All, ///< Structures only (including events) or all content. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to Prepare/Unprepare. + ) = 0; + + /// \n\aknote Requires that the "Use SoundBank names" option be unchecked in the Wwise Project Settings. \endaknote + /// This function will load the events, structural content, and optionally, the media content from the SoundBank. If the flag AkBankContent_All is specified, PrepareBank() will load the media content from + /// the bank, but as opposed to LoadBank(), the media will be loaded one media item at a time instead of in one contiguous memory block. Using PrepareBank(), alone or in combination with PrepareEvent(), + /// will prevent in-memory duplication of media at the cost of some memory fragmentation. + /// Calling this function specifying the flag AkBankContent_StructureOnly will load only the structural part (including events) of this bank, + /// allowing using PrepareEvent() to load media on demand. + /// \sa + /// - \ref soundengine_banks_preparingbanks + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::PreparationType + /// \remarks + /// \c PrepareBank(), when called with the flag AkBankContent_StructureOnly, requires additional calls to PrepareEvent() to load the media for each event. PrepareEvent(), however, is unable to + /// access media content contained within SoundBanks and requires that the media be available as loose files in the file system. This flag may be useful to implement multiple loading configurations; + /// for example, a game may have a tool mode that uses PrepareEvent() to load loose files on-demand and, also, a game mode that uses LoadBank() to load the bank in entirety. + virtual AKRESULT PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkBankID in_bankID, ///< ID of the bank to Prepare/Unprepare. + AK::SoundEngine::AkBankContent in_uFlags = AK::SoundEngine::AkBankContent_All, ///< Structures only (including events) or all content. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to Prepare/Unprepare. + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// This function will load the Events, structural content, and optionally, the media content from the SoundBank. If the flag AkBankContent_All is specified, PrepareBank() will load the media content from + /// the bank, but as opposed to LoadBank(), the media will be loaded one media item at a time instead of in one contiguous memory block. Using PrepareBank(), alone or in combination with PrepareEvent(), + /// will prevent in-memory duplication of media at the cost of some memory fragmentation. + /// Calling this function specifying the flag AkBankContent_StructureOnly will load only the structural part (including events) of this bank, + /// allowing using PrepareEvent() to load media on demand. + /// \sa + /// - \ref soundengine_banks_preparingbanks + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::PreparationType + /// \remarks + /// PrepareBank(), when called with the flag AkBankContent_StructureOnly, requires additional calls to PrepareEvent() to load the media for each event. PrepareEvent(), however, is unable to + /// access media content contained within SoundBanks and requires that the media be available as loose files in the file system. This flag may be useful to implement multiple loading configurations; + /// for example, a game may have a tool mode that uses PrepareEvent() to load loose files on-demand and, also, a game mode that uses LoadBank() to load the bank in entirety. + virtual AKRESULT PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const wchar_t* in_pszString, ///< Name of the bank to Prepare/Unprepare. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function) + AK::SoundEngine::AkBankContent in_uFlags = AK::SoundEngine::AkBankContent_All, ///< Structures only (including events) or all content. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to Prepare/Unprepare. + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// This function will load the events, structural content, and optionally, the media content from the SoundBank. If the flag \c AkBankContent_All is specified, \c PrepareBank() will load the media content from + /// the bank, but as opposed to \c LoadBank(), the media will be loaded one media item at a time instead of in one contiguous memory block. Using \c PrepareBank(), alone or in combination with PrepareEvent(), + /// will prevent in-memory duplication of media at the cost of some memory fragmentation. + /// Calling this function specifying the flag AkBankContent_StructureOnly will load only the structural part (including events) of this bank, + /// allowing using PrepareEvent() to load media on demand. + /// \sa + /// - \ref soundengine_banks_preparingbanks + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::PreparationType() + /// \remarks + /// PrepareBank(), when called with the flag AkBankContent_StructureOnly, requires additional calls to PrepareEvent() to load the media for each event. PrepareEvent(), however, is unable to + /// access media content contained within SoundBanks and requires that the media be available as loose files in the file system. This flag may be useful to implement multiple loading configurations; + /// for example, a game may have a tool mode that uses PrepareEvent() to load loose files on-demand and, also, a game mode that uses LoadBank() to load the bank in entirety. + virtual AKRESULT PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const char* in_pszString, ///< Name of the bank to Prepare/Unprepare. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function) + AK::SoundEngine::AkBankContent in_uFlags = AK::SoundEngine::AkBankContent_All, ///< Structures only (including events) or all content. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to Prepare/Unprepare. + ) = 0; + + /// \n\aknote Requires that the "Use SoundBank names" option be unchecked in the Wwise Project Settings. \endaknote + /// This function will load the events, structural content, and optionally, the media content from the SoundBank. If the flag AkBankContent_All is specified, \c PrepareBank() will load the media content from + /// the bank, but as opposed to \c LoadBank(), the media will be loaded one media item at a time instead of in one contiguous memory block. Using \c PrepareBank(), alone or in combination with \c PrepareEvent(), + /// will prevent in-memory duplication of media at the cost of some memory fragmentation. + /// Calling this function specifying the flag AkBankContent_StructureOnly will load only the structural part (including events) of this bank, + /// allowing using PrepareEvent() to load media on demand. + /// \sa + /// - \ref soundengine_banks_preparingbanks + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::PreparationType() + /// \remarks + /// \c PrepareBank(), when called with the flag AkBankContent_StructureOnly, requires additional calls to PrepareEvent() to load the media for each event. \c PrepareEvent(), however, is unable to + /// access media content contained within SoundBanks and requires that the media be available as loose files in the file system. This flag may be useful to implement multiple loading configurations; + /// for example, a game may have a tool mode that uses \c PrepareEvent() to load loose files on-demand and, also, a game mode that uses \c LoadBank() to load the bank in entirety. + virtual AKRESULT PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkBankID in_bankID, ///< ID of the bank to Prepare/Unprepare. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function) + AK::SoundEngine::AkBankContent in_uFlags = AK::SoundEngine::AkBankContent_All, ///< Structures only (including events) or all content. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to Prepare/Unprepare. + ) = 0; + + /// Clear all previously prepared events.\n + /// \return + /// - \c AK_Success if successful. + /// - \c AK_Fail if the sound engine was not correctly initialized or if there is not enough memory to handle the command. + /// \remarks + /// The function \c ClearBanks() also clears all prepared events. + /// \sa + /// - \c AK::SoundEngine::PrepareEvent() + /// - \c AK::SoundEngine::ClearBanks() + virtual AKRESULT ClearPreparedEvents() = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Prepares or unprepares Events synchronously (by Unicode string).\n + /// The Events are identified by strings, and converted to IDs internally + /// (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// Before invoking \c PrepareEvent(), use \c LoadBank() to explicitly load the SoundBank(s) + /// that contain the Events and structures. When a request is posted to the + /// Bank Manager consumer thread, it will resolve all dependencies needed to + /// successfully post the specified Events and load the required loose media files. + /// \aknote Before version 2015.1, the required media files could be included + /// in a separate media SoundBank. As described in \ref whatsnew_2015_1_migration, + /// however,\c PrepareEvent() now only looks for loose media files. + /// \endaknote + /// The function returns when the request is completely processed. + /// \return + /// - \c AK_Success: Prepare/un-prepare successful. + /// - \c AK_IDNotFound: At least one of the event/game sync identifiers passed to PrepareEvent() does not exist. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_InvalidParameter: Invalid parameter, invalid memory alignment. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// Whenever at least one event fails to be resolved, the actions performed for all + /// other events are cancelled. + /// \sa + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::ClearPreparedEvents() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::LoadBank() + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + virtual AKRESULT PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const wchar_t** in_ppszString, ///< Array of event names + AkUInt32 in_uNumEvent ///< Number of events in the array + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Prepares or unprepares events synchronously.\n + /// The Events are identified by strings and converted to IDs internally + /// (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// Before invoking PrepareEvent(), use LoadBank() to explicitly load the SoundBank(s) + /// that contain the Events and structures. When a request is posted to the + /// Bank Manager consumer thread, it will resolve all dependencies needed to + /// successfully post the specified Events and load the required loose media files. + /// \aknote Before version 2015.1, the required media files could be included + /// in a separate media SoundBank. As described in \ref whatsnew_2015_1_migration, + /// however, PrepareEvent() now only looks for loose media files. + /// \endaknote + /// The function returns when the request is completely processed. + /// \return + /// - \c AK_Success: Prepare/un-prepare successful. + /// - \c AK_IDNotFound: At least one of the event/game sync identifiers passed to PrepareEvent() does not exist. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_InvalidParameter: Invalid parameter, invalid memory alignment. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// Whenever at least one event fails to be resolved, the actions performed for all + /// other events are cancelled. + /// \aknote The use of PrepareEvent is incompatible with LoadBank, using in-memory data. + /// \endaknote + /// \sa + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::ClearPreparedEvents() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::LoadBank() + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + virtual AKRESULT PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const char** in_ppszString, ///< Array of event names + AkUInt32 in_uNumEvent ///< Number of events in the array + ) = 0; + + /// Prepares or unprepares events synchronously (by ID). + /// The Events are identified by their ID (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// Before invoking PrepareEvent(), use LoadBank() to explicitly load the SoundBank(s) + /// that contain the Events and structures. When a request is posted to the + /// Bank Manager consumer thread, it will resolve all dependencies needed to + /// successfully post the specified Events and load the required loose media files. + /// \aknote Before version 2015.1, the required media files could be included + /// in a separate media SoundBank. As described in \ref whatsnew_2015_1_migration, + /// however, PrepareEvent() now only looks for loose media files. + /// \endaknote + /// The function returns when the request is completely processed. + /// \return + /// - \c AK_Success: Prepare/un-prepare successful. + /// - \c AK_IDNotFound: At least one of the event/game sync identifiers passed to PrepareEvent() does not exist. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_InvalidParameter: Invalid parameter, invalid memory alignment. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// Whenever at least one event fails to be resolved, the actions performed for all + /// other events are cancelled. + /// \sa + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::ClearPreparedEvents() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::LoadBank() + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + virtual AKRESULT PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkUniqueID* in_pEventID, ///< Array of event IDs + AkUInt32 in_uNumEvent ///< Number of event IDs in the array + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Prepares or unprepares an event asynchronously (by Unicode string). + /// The Events are identified by string (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// Before invoking PrepareEvent(), use LoadBank() to explicitly load the SoundBank(s) + /// that contain the Events and structures. When a request is posted to the + /// Bank Manager consumer thread, it will resolve all dependencies needed to + /// successfully post the specified Events and load the required loose media files. + /// \aknote Before version 2015.1, the required media files could be included + /// in a separate media SoundBank. As described in \ref whatsnew_2015_1_migration, + /// however, \c PrepareEvent() now only looks for loose media files. + /// \endaknote + /// The function returns immediately. Use a callback to be notified when the request has finished being processed. + /// \return AK_Success if scheduling is was successful, AK_Fail otherwise. + /// \remarks + /// Whenever at least one Event fails to be resolved, the actions performed for all + /// other Events are cancelled. + /// \sa + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::ClearPreparedEvents() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::LoadBank() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + virtual AKRESULT PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const wchar_t** in_ppszString, ///< Array of event names + AkUInt32 in_uNumEvent, ///< Number of events in the array + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie ///< Callback cookie (reserved to user, passed to the callback function) + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Prepares or unprepares an event asynchronously. + /// The Events are identified by string (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// Before invoking PrepareEvent(), use LoadBank() to explicitly load the SoundBank(s) + /// that contain the Events and structures. When a request is posted to the + /// Bank Manager consumer thread, it will resolve all dependencies needed to + /// successfully post the specified Events and load the required loose media files. + /// \aknote Before version 2015.1, the required media files could be included + /// in a separate media SoundBank. As described in \ref whatsnew_2015_1_migration, + /// however, PrepareEvent() now only looks for loose media files. + /// \endaknote + /// The function returns immediately. Use a callback to be notified when the request has finished being processed. + /// \return AK_Success if scheduling is was successful, AK_Fail otherwise. + /// \remarks + /// Whenever at least one event fails to be resolved, the actions performed for all + /// other events are cancelled. + /// \sa + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::ClearPreparedEvents() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::LoadBank() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + virtual AKRESULT PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const char** in_ppszString, ///< Array of event names + AkUInt32 in_uNumEvent, ///< Number of events in the array + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie ///< Callback cookie (reserved to user, passed to the callback function) + ) = 0; + + /// Prepares or unprepares events asynchronously (by ID).\n + /// The Events are identified by their ID (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// Before invoking PrepareEvent(), use LoadBank() to explicitly load the SoundBank(s) + /// that contain the Events and structures. When a request is posted to the + /// Bank Manager consumer thread, it will resolve all dependencies needed to + /// successfully post the specified Events and load the required loose media files. + /// \aknote Before version 2015.1, the required media files could be included + /// in a separate media SoundBank. As described in \ref whatsnew_2015_1_migration, + /// however, PrepareEvent() now only looks for loose media files. + /// \endaknote + /// The function returns immediately. Use a callback to be notified when the request has finished being processed. + /// \return AK_Success if scheduling is was successful, AK_Fail otherwise. + /// \remarks + /// Whenever at least one event fails to be resolved, the actions performed for all + /// other events are cancelled. + /// \sa + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::ClearPreparedEvents() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::LoadBank() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + virtual AKRESULT PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkUniqueID* in_pEventID, ///< Array of event IDs + AkUInt32 in_uNumEvent, ///< Number of event IDs in the array + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie ///< Callback cookie (reserved to user, passed to the callback function) + ) = 0; + + /// Indicates the location of a specific Media ID in memory + /// The sources are identified by their ID (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// \return AK_Success if operation was successful, AK_InvalidParameter if in_pSourceSettings is invalid or media sizes are 0. + virtual AKRESULT SetMedia( + AkSourceSettings* in_pSourceSettings, ///< Array of Source Settings + AkUInt32 in_uNumSourceSettings ///< Number of Source Settings in the array + ) = 0; + + /// Removes the specified source from the list of loaded media, even if this media is already in use. + /// The sources are identified by their ID (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// \aknote This function is unsafe and deprecated. Use TryUnsetMedia() in its place. + /// Media that is still in use by the sound engine should not be unset by this function. + /// If the media is still in use, this function will attempt to forcibly kill all sounds and effects referencing this media, + /// and then return AK_ResourceInUse. The client should NOT presume that the memory can be safely released at this point. + /// The moment at which the memory can be safely released is unknown, and the only safe course of action is to keep the memory + /// alive until the sound engine is terminated. + /// \endaknote + /// \return + /// - \c AK_Success: Operation was successful, and the memory can be released on the client side. + /// - \c AK_ResourceInUse: Specified media is still in use by the sound engine, the client should not release the memory. + /// - \c AK_InvalidParameter: in_pSourceSettings is invalid + virtual AKRESULT UnsetMedia( + AkSourceSettings* in_pSourceSettings, ///< Array of Source Settings + AkUInt32 in_uNumSourceSettings ///< Number of Source Settings in the array + ) = 0; + + /// Removes the specified source from the list of loaded media, only if this media is not already in use. + /// The sources are identified by their ID (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// \aknote Media that is still in use by the sound engine should not be unset. It is marked for removal to prevent additional use. + /// If this function returns AK_ResourceInUse, then the client must not release memory for this media. + /// Instead, the client should retry the TryUnsetMedia operation later with the same parameters and check for AK_Success. + /// \endaknote + /// If out_pUnsetResults is not null, then it is assumed to point to an array of result codes of the same length as in_pSourceSettings. + /// out_pUnsetResults will be filled with either AK_Success or AK_ResourceInUse, indicating which media was still in use and not unset. + /// \return + /// - \c AK_Success: Operation was successful, and the memory can be released on the client side. + /// - \c AK_ResourceInUse: Specified media is still in use by the sound engine, and the media was not unset. Do not release memory, and try again later. + /// - \c AK_InvalidParameter: in_pSourceSettings is invalid + virtual AKRESULT TryUnsetMedia( + AkSourceSettings* in_pSourceSettings, ///< Array of Source Settings + AkUInt32 in_uNumSourceSettings, ///< Number of Source Settings in the array + AKRESULT* out_pUnsetResults ///< (optional, can be null) Array of result codes + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Prepares or unprepares game syncs synchronously (by Unicode string).\n + /// The group and game syncs are specified by string (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// The game syncs definitions must already exist in the sound engine by having + /// explicitly loaded the bank(s) that contain them (with LoadBank()). + /// A request is posted to the Bank Manager consumer thread. It will resolve all + /// dependencies needed to successfully set this game sync group to one of the + /// game sync values specified, and load the required banks, if applicable. + /// The function returns when the request has been completely processed. + /// \return + /// - \c AK_Success: Prepare/un-prepare successful. + /// - \c AK_IDNotFound: At least one of the event/game sync identifiers passed to PrepareGameSyncs() does not exist. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_InvalidParameter: Invalid parameter, invalid memory alignment. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// You need to call PrepareGameSyncs() if the sound engine was initialized with AkInitSettings::bEnableGameSyncPreparation + /// set to true. When set to false, the sound engine automatically prepares all game syncs when preparing events, + /// so you never need to call this function. + /// \sa + /// - \c AK::SoundEngine::GetIDFromString() + /// - \c AK::SoundEngine::PrepareEvent() + /// - \c AK::SoundEngine::LoadBank() + /// - \c AkInitSettings + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + virtual AKRESULT PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkGroupType in_eGameSyncType, ///< The type of game sync. + const wchar_t* in_pszGroupName, ///< The State Group Name or the Switch Group Name. + const wchar_t** in_ppszGameSyncName, ///< The specific ID of the state to either support or not support. + AkUInt32 in_uNumGameSyncs ///< The number of game sync in the string array. + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Prepares or unprepares game syncs synchronously.\n + /// The group and game syncs are specified by string (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// The game syncs definitions must already exist in the sound engine by having + /// explicitly loaded the bank(s) that contain them (with LoadBank()). + /// A request is posted to the Bank Manager consumer thread. It will resolve all + /// dependencies needed to successfully set this game sync group to one of the + /// game sync values specified, and load the required banks, if applicable. + /// The function returns when the request has been completely processed. + /// \return + /// - \c AK_Success: Prepare/un-prepare successful. + /// - \c AK_IDNotFound: At least one of the event/game sync identifiers passed to PrepareGameSyncs() does not exist. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_InvalidParameter: Invalid parameter, invalid memory alignment. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// You need to call PrepareGameSyncs() if the sound engine was initialized with AkInitSettings::bEnableGameSyncPreparation + /// set to true. When set to false, the sound engine automatically prepares all game syncs when preparing events, + /// so you never need to call this function. + /// \sa + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::LoadBank() + /// - AkInitSettings + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + virtual AKRESULT PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkGroupType in_eGameSyncType, ///< The type of game sync. + const char* in_pszGroupName, ///< The State Group Name or the Switch Group Name. + const char** in_ppszGameSyncName, ///< The specific ID of the state to either support or not support. + AkUInt32 in_uNumGameSyncs ///< The number of game sync in the string array. + ) = 0; + + /// Prepares or unprepares game syncs synchronously (by ID).\n + /// The group and game syncs are specified by ID (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// The game syncs definitions must already exist in the sound engine by having + /// explicitly loaded the bank(s) that contain them (with LoadBank()). + /// A request is posted to the Bank Manager consumer thread. It will resolve all + /// dependencies needed to successfully set this game sync group to one of the + /// game sync values specified, and load the required banks, if applicable. + /// The function returns when the request has been completely processed. + /// \return + /// - \c AK_Success: Prepare/un-prepare successful. + /// - \c AK_IDNotFound: At least one of the event/game sync identifiers passed to PrepareGameSyncs() does not exist. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_InvalidParameter: Invalid parameter, invalid memory alignment. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// You need to call \c PrepareGameSyncs() if the sound engine was initialized with \c AkInitSettings::bEnableGameSyncPreparation + /// set to \c true. When set to \c false, the sound engine automatically prepares all game syncs when preparing Events, + /// so you never need to call this function. + /// \sa + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::LoadBank() + /// - AkInitSettings + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + virtual AKRESULT PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkGroupType in_eGameSyncType, ///< The type of game sync. + AkUInt32 in_GroupID, ///< The State Group ID or the Switch Group ID. + AkUInt32* in_paGameSyncID, ///< Array of ID of the game syncs to either support or not support. + AkUInt32 in_uNumGameSyncs ///< The number of game sync ID in the array. + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Prepares or unprepares game syncs asynchronously (by Unicode string).\n + /// The group and game syncs are specified by string (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// The game syncs definitions must already exist in the sound engine by having + /// explicitly loaded the bank(s) that contain them (with LoadBank()). + /// A request is posted to the Bank Manager consumer thread. It will resolve all + /// dependencies needed to successfully set this game sync group to one of the + /// game sync values specified, and load the required banks, if applicable. + /// The function returns immediately. Use a callback to be notified when the request has finished being processed. + /// \return AK_Success if scheduling is was successful, AK_Fail otherwise. + /// \remarks + /// You need to call \c PrepareGameSyncs() if the sound engine was initialized with \c AkInitSettings::bEnableGameSyncPreparation + /// set to \c true. When set to \c false, the sound engine automatically prepares all game syncs when preparing Events, + /// so you never need to call this function. + /// \sa + /// - \c AK::SoundEngine::GetIDFromString() + /// - \c AK::SoundEngine::PrepareEvent() + /// - \c AK::SoundEngine::LoadBank() + /// - \c AkInitSettings + /// - \c AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + virtual AKRESULT PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkGroupType in_eGameSyncType, ///< The type of game sync. + const wchar_t* in_pszGroupName, ///< The State Group Name or the Switch Group Name. + const wchar_t** in_ppszGameSyncName, ///< The specific ID of the state to either support or not support. + AkUInt32 in_uNumGameSyncs, ///< The number of game sync in the string array. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie ///< Callback cookie (reserved to user, passed to the callback function) + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Prepares or unprepares game syncs asynchronously.\n + /// The group and game syncs are specified by string (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// The game syncs definitions must already exist in the sound engine by having + /// explicitly loaded the bank(s) that contain them (with LoadBank()). + /// A request is posted to the Bank Manager consumer thread. It will resolve all + /// dependencies needed to successfully set this game sync group to one of the + /// game sync values specified, and load the required banks, if applicable. + /// The function returns immediately. Use a callback to be notified when the request has finished being processed. + /// \return AK_Success if scheduling is was successful, AK_Fail otherwise. + /// \remarks + /// You need to call PrepareGameSyncs() if the sound engine was initialized with AkInitSettings::bEnableGameSyncPreparation + /// set to true. When set to false, the sound engine automatically prepares all game syncs when preparing events, + /// so you never need to call this function. + /// \sa + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::LoadBank() + /// - AkInitSettings + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + virtual AKRESULT PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkGroupType in_eGameSyncType, ///< The type of game sync. + const char* in_pszGroupName, ///< The State Group Name or the Switch Group Name. + const char** in_ppszGameSyncName, ///< The specific ID of the state to either support or not support. + AkUInt32 in_uNumGameSyncs, ///< The number of game sync in the string array. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie ///< Callback cookie (reserved to user, passed to the callback function) + ) = 0; + + /// Prepares or un-prepare game syncs asynchronously (by ID).\n + /// The group and game syncs are specified by ID (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// The game syncs definitions must already exist in the sound engine by having + /// explicitly loaded the bank(s) that contain them (with LoadBank()). + /// A request is posted to the Bank Manager consumer thread. It will resolve all + /// dependencies needed to successfully set this game sync group to one of the + /// game sync values specified, and load the required banks, if applicable. + /// The function returns immediately. Use a callback to be notified when the request has finished being processed. + /// \return AK_Success if scheduling is was successful, AK_Fail otherwise. + /// \remarks + /// You need to call PrepareGameSyncs() if the sound engine was initialized with AkInitSettings::bEnableGameSyncPreparation + /// set to true. When set to false, the sound engine automatically prepares all Game Syncs when preparing Events, + /// so you never need to call this function. + /// \sa + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::LoadBank() + /// - AkInitSettings + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + virtual AKRESULT PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkGroupType in_eGameSyncType, ///< The type of game sync. + AkUInt32 in_GroupID, ///< The State Group ID or the Switch Group ID. + AkUInt32* in_paGameSyncID, ///< Array of ID of the Game Syncs to either support or not support. + AkUInt32 in_uNumGameSyncs, ///< The number of game sync ID in the array. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie ///< Callback cookie (reserved to user, passed to the callback function) + ) = 0; + + //@} + + + //////////////////////////////////////////////////////////////////////// + /// @name Listeners + //@{ + + /// Sets a game object's associated listeners. + /// All listeners that have previously been added via AddListener or set via SetListeners will be removed and replaced with the listeners in the array in_pListenerGameObjs. + /// Calling this function will override the default set of listeners and in_emitterGameObj will now reference its own, unique set of listeners. + /// \return + /// - \c AK_Success if successful + /// - \c AK_CommandTooLarge if the number of positions is too large for the command queue. Reduce the number of positions. + /// \sa + /// - AK::SoundEngine::AddListener + /// - AK::SoundEngine::RemoveListener + /// - AK::SoundEngine::SetDefaultListeners + /// - \ref soundengine_listeners + virtual AKRESULT SetListeners( + AkGameObjectID in_emitterGameObj, ///< Emitter game object. Must have been previously registered via RegisterGameObj. + const AkGameObjectID* in_pListenerGameObjs, ///< Array of listener game object IDs that will be activated for in_emitterGameObj. + AkUInt32 in_uNumListeners ///< Length of array + ) = 0; + + /// Add a single listener to a game object's set of associated listeners. + /// Any listeners that have previously been added or set via AddListener or SetListeners will remain as listeners and in_listenerGameObj will be added as an additional listener. + /// Calling this function will override the default set of listeners and in_emitterGameObj will now reference its own, unique set of listeners. + /// \sa + /// - AK::SoundEngine::SetListeners + /// - AK::SoundEngine::RemoveListener + /// - AK::SoundEngine::SetDefaultListeners + /// - \ref soundengine_listeners + virtual AKRESULT AddListener( + AkGameObjectID in_emitterGameObj, ///< Emitter game object. Must have been previously registered via RegisterGameObj. + AkGameObjectID in_listenerGameObj ///< Listener game object IDs that will be activated for in_emitterGameObj. + ) = 0; + + /// Remove a single listener from a game object's set of active listeners. + /// Calling this function will override the default set of listeners and in_emitterGameObj will now reference its own, unique set of listeners. + /// \sa + /// - AK::SoundEngine::SetListeners + /// - AK::SoundEngine::AddListener + /// - AK::SoundEngine::SetDefaultListeners + /// - \ref soundengine_listeners + virtual AKRESULT RemoveListener( + AkGameObjectID in_emitterGameObj, ///< Emitter game object. + AkGameObjectID in_listenerGameObj ///< Listener game object IDs that will be deactivated for in_emitterGameObj. Game objects must have been previously registered. + ) = 0; + + /// Sets the default set of associated listeners for game objects that have not explicitly overridden their listener sets. Upon registration, all game objects reference the default listener set, until + /// a call to AddListener, RemoveListener, SetListeners or SetGameObjectOutputBusVolume is made on that game object. + /// All default listeners that have previously been added via AddDefaultListener or set via SetDefaultListeners will be removed and replaced with the listeners in the array in_pListenerGameObjs. + /// \return Always returns AK_Success + /// \sa + /// - \ref soundengine_listeners + virtual AKRESULT SetDefaultListeners( + const AkGameObjectID* in_pListenerObjs, ///< Array of listener game object IDs that will be activated for subsequent registrations. Game objects must have been previously registered. + AkUInt32 in_uNumListeners ///< Length of array + ) = 0; + + /// Add a single listener to the default set of listeners. Upon registration, all game objects reference the default listener set, until + /// a call to AddListener, RemoveListener, SetListeners or SetGameObjectOutputBusVolume is made on that game object. + /// \sa + /// - AK::SoundEngine::SetDefaultListeners + /// - AK::SoundEngine::RemoveDefaultListener + /// - \ref soundengine_listeners + virtual AKRESULT AddDefaultListener( + AkGameObjectID in_listenerGameObj ///< Listener game object IDs that will be added to the default set of listeners. + ) = 0; + + /// Remove a single listener from the default set of listeners. Upon registration, all game objects reference the default listener set, until + /// a call to AddListener, RemoveListener, SetListeners or SetGameObjectOutputBusVolume is made on that game object. + /// \sa + /// - AK::SoundEngine::SetDefaultListeners + /// - AK::SoundEngine::AddDefaultListener + /// - \ref soundengine_listeners + virtual AKRESULT RemoveDefaultListener( + AkGameObjectID in_listenerGameObj ///< Listener game object IDs that will be removed from the default set of listeners. + ) = 0; + + /// Resets the listener associations to the default listener(s), as set by SetDefaultListeners. This will also reset per-listener gains that have been set using SetGameObjectOutputBusVolume. + /// \return Always returns AK_Success + /// \sa + /// - AK::SoundEngine::SetListeners + /// - AK::SoundEngine::SetDefaultListeners + /// - AK::SoundEngine::SetGameObjectOutputBusVolume + /// - \ref soundengine_listeners + virtual AKRESULT ResetListenersToDefault( + AkGameObjectID in_emitterGameObj ///< Emitter game object. + ) = 0; + + /// Sets a listener's spatialization parameters. This lets you define listener-specific + /// volume offsets for each audio channel. + /// If \c in_bSpatialized is false, only \c in_pVolumeOffsets is used for this listener (3D positions + /// have no effect on the speaker distribution). Otherwise, \c in_pVolumeOffsets is added to the speaker + /// distribution computed for this listener. + /// Use helper functions of \c AK::SpeakerVolumes to manipulate the vector of volume offsets in_pVolumeOffsets. + /// + /// \remarks + /// - If a sound is mixed into a bus that has a different speaker configuration than in_channelConfig, + /// standard up/downmix rules apply. + /// - Sounds with 3D Spatialization set to None will not be affected by these parameters. + /// \return + /// - \c AK_Success if message was successfully posted to sound engine queue + /// - \c AK_InvalidFloatValue if the value specified was NaN or Inf + /// - \c AK_InsufficientMemory if there wasn't enough memory in the message queue + /// \sa + /// - \ref soundengine_listeners_spatial + virtual AKRESULT SetListenerSpatialization( + AkGameObjectID in_uListenerID, ///< Listener game object ID + bool in_bSpatialized, ///< Spatialization toggle (True : enable spatialization, False : disable spatialization) + AkChannelConfig in_channelConfig, ///< Channel configuration associated with volumes in_pVolumeOffsets. Ignored if in_pVolumeOffsets is NULL. + AK::SpeakerVolumes::VectorPtr in_pVolumeOffsets = NULL ///< Per-speaker volume offset, in dB. See AkSpeakerVolumes.h for how to manipulate this vector. + ) = 0; + + //@} + + + //////////////////////////////////////////////////////////////////////// + /// @name Game Syncs + //@{ + + /// Sets the value of a real-time parameter control (by ID). + /// With this function, you may set a game parameter value with global scope or with game object scope. + /// Game object scope supersedes global scope. (Once a value is set for the game object scope, it will not be affected by changes to the global scope value.) Game parameter values set with a global scope are applied to all + /// game objects that not yet registered, or already registered but not overridden with a value with game object scope. + /// To set a game parameter value with global scope, pass \c AK_INVALID_GAME_OBJECT as the game object. + /// With this function, you may also change the value of a game parameter over time. To do so, specify a non-zero + /// value for \c in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call \c SetRTPCValue() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. Thus, if you call this + /// function at every game frame, you should not use \c in_uValueChangeDuration, as it would have no effect and it is less efficient. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// \return + /// - \c AK_Success if the value was successfully set + /// - \c AK_InvalidFloatValue if the value specified was NaN, Inf or FLT_MAX (3.402823e+38) + /// - \c AK_InvalidID if in_rtpcID is AK_INVALID_UNIQUE_ID (0) + /// \sa + /// - \ref soundengine_rtpc + /// - AK::SoundEngine::GetIDFromString() + virtual AKRESULT SetRTPCValue( + AkRtpcID in_rtpcID, ///< ID of the game parameter + AkRtpcValue in_value, ///< Value to set + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT,///< Associated game object ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards in_value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when for example loading a level and you dont want the values to interpolate. + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Sets the value of a real-time parameter control (by Unicode string name). + /// With this function, you may set a game parameter value to global scope or to game object scope. + /// Game object scope supersedes global scope. (Once a value is set for the game object scope, it will not be affected by changes to the global scope value.) Game parameter values set with global scope are applied to all + /// game objects that not yet registered, or already registered but not overridden with a value with game object scope. + /// To set a game parameter value with global scope, pass AK_INVALID_GAME_OBJECT as the game object. + /// With this function, you may also change the value of a game parameter over time. To do so, specify a non-zero + /// value for in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValue() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. Thus, if you call this + /// function at every game frame, you should not use in_uValueChangeDuration, as it would have no effect and it is less efficient. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// \return + /// - \c AK_Success if the value was successfully set + /// - \c AK_InvalidFloatValue if the value specified was NaN, Inf or FLT_MAX (3.402823e+38) + /// - \c AK_InvalidID if in_pszRtpcName is NULL. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_rtpc + virtual AKRESULT SetRTPCValue( + const wchar_t* in_pszRtpcName, ///< Name of the game parameter + AkRtpcValue in_value, ///< Value to set + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT,///< Associated game object ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards in_value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when for example loading a level and you dont want the values to interpolate. + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Sets the value of a real-time parameter control. + /// With this function, you may set a game parameter value with global scope or with game object scope. + /// Game object scope supersedes global scope. (Once a value is set for the game object scope, it will not be affected by changes to the global scope value.) Game parameter values set with global scope are applied to all + /// game objects that not yet registered, or already registered but not overridden with a value with game object scope. + /// To set a game parameter value with global scope, pass AK_INVALID_GAME_OBJECT as the game object. + /// With this function, you may also change the value of a game parameter over time. To do so, specify a non-zero + /// value for \c in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValue() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. Thus, if you call this + /// function at every game frame, you should not use in_uValueChangeDuration, as it would have no effect and it is less efficient. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// \return + /// - \c AK_Success if the value was successfully set + /// - \c AK_InvalidFloatValue if the value specified was NaN, Inf or FLT_MAX (3.402823e+38) + /// - \c AK_InvalidID if in_pszRtpcName is NULL. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_rtpc + virtual AKRESULT SetRTPCValue( + const char* in_pszRtpcName, ///< Name of the game parameter + AkRtpcValue in_value, ///< Value to set + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT,///< Associated game object ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards in_value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when for example loading a level and you dont want the values to interpolate. + ) = 0; + + /// Sets the value of a real-time parameter control (by ID). + /// With this function, you may set a game parameter value on playing id scope. + /// Playing id scope supersedes both game object scope and global scope. + /// With this function, you may also change the value of a game parameter over time. To do so, specify a non-zero + /// value for in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValueByPlayingID() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. Thus, if you call this + /// function at every game frame, you should not use in_uValueChangeDuration, as it would have no effect and it is less efficient. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// - \c AK_Success if successful + /// - \c AK_PlayingIDNotFound if in_playingID is not found. + /// - \c AK_InvalidID if in_pszRtpcName is NULL. + /// - \c AK_InvalidFloatValue if the value specified was NaN, Inf or FLT_MAX (3.402823e+38) + /// \sa + /// - \ref soundengine_rtpc + /// - AK::SoundEngine::GetIDFromString() + virtual AKRESULT SetRTPCValueByPlayingID( + AkRtpcID in_rtpcID, ///< ID of the game parameter + AkRtpcValue in_value, ///< Value to set + AkPlayingID in_playingID, ///< Associated playing ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards in_value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when, for example, loading a level and you don't want the values to interpolate. + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Sets the value of a real-time parameter control (by Unicode string name). + /// With this function, you may set a game parameter value on playing ID scope. + /// Playing id scope supersedes both game object scope and global scope. + /// With this function, you may also change the value of a game parameter over time. To do so, specify a non-zero + /// value for in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValueByPlayingID() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. Thus, if you call this + /// function at every game frame, you should not use in_uValueChangeDuration, as it would have no effect and it is less efficient. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// - \c AK_Success if successful + /// - \c AK_PlayingIDNotFound if in_playingID is not found. + /// - \c AK_InvalidID if in_pszRtpcName is NULL. + /// - \c AK_InvalidFloatValue if the value specified was NaN, Inf or FLT_MAX (3.402823e+38) + /// \sa + /// - \ref soundengine_rtpc + /// - AK::SoundEngine::GetIDFromString() + virtual AKRESULT SetRTPCValueByPlayingID( + const wchar_t* in_pszRtpcName, ///< Name of the game parameter + AkRtpcValue in_value, ///< Value to set + AkPlayingID in_playingID, ///< Associated playing ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards in_value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when, for example, loading a level and you don't want the values to interpolate. + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Sets the value of a real-time parameter control (by string name). + /// With this function, you may set a game parameter value on playing id scope. + /// Playing id scope supersedes both game object scope and global scope. + /// With this function, you may also change the value of a game parameter over time. To do so, specify a non-zero + /// value for in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValueByPlayingID() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. Thus, if you call this + /// function at every game frame, you should not use in_uValueChangeDuration, as it would have no effect and it is less efficient. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// - \c AK_Success if successful + /// - \c AK_PlayingIDNotFound if in_playingID is not found. + /// - \c AK_InvalidID if in_pszRtpcName is NULL. + /// - \c AK_InvalidFloatValue if the value specified was NaN, Inf or FLT_MAX (3.402823e+38) + /// \sa + /// - \ref soundengine_rtpc + /// - AK::SoundEngine::GetIDFromString() + virtual AKRESULT SetRTPCValueByPlayingID( + const char* in_pszRtpcName, ///< Name of the game parameter + AkRtpcValue in_value, ///< Value to set + AkPlayingID in_playingID, ///< Associated playing ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards in_value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when for example loading a level and you dont want the values to interpolate. + ) = 0; + + /// Resets the value of the game parameter to its default value, as specified in the Wwise project. + /// With this function, you may reset a game parameter to its default value with global scope or with game object scope. + /// Game object scope supersedes global scope. Game parameter values reset with global scope are applied to all + /// game objects that were not overridden with a value with game object scope. + /// To reset a game parameter value with global scope, pass AK_INVALID_GAME_OBJECT as the game object. + /// With this function, you may also reset the value of a game parameter over time. To do so, specify a non-zero + /// value for in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValue() or ResetRTPCValue() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// \return + /// - \c AK_Success when successful + /// - \c AK_InvalidID if in_rtpcID is AK_INVALID_UNIQUE_ID (0) + /// \sa + /// - \ref soundengine_rtpc + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::SetRTPCValue() + virtual AKRESULT ResetRTPCValue( + AkRtpcID in_rtpcID, ///< ID of the game parameter + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT,///< Associated game object ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards its default value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when for example loading a level and you dont want the values to interpolate. + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Resets the value of the game parameter to its default value, as specified in the Wwise project. + /// With this function, you may reset a game parameter to its default value with global scope or with game object scope. + /// Game object scope supersedes global scope. Game parameter values reset with global scope are applied to all + /// game objects that were not overridden with a value with game object scope. + /// To reset a game parameter value with global scope, pass AK_INVALID_GAME_OBJECT as the game object. + /// With this function, you may also reset the value of a game parameter over time. To do so, specify a non-zero + /// value for in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValue() or ResetRTPCValue() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if in_pszParamName is NULL. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_rtpc + /// - AK::SoundEngine::SetRTPCValue() + virtual AKRESULT ResetRTPCValue( + const wchar_t* in_pszRtpcName, ///< Name of the game parameter + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT,///< Associated game object ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards its default value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when for example loading a level and you dont want the values to interpolate. + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Resets the value of the game parameter to its default value, as specified in the Wwise project. + /// With this function, you may reset a game parameter to its default value with global scope or with game object scope. + /// Game object scope supersedes global scope. Game parameter values reset with global scope are applied to all + /// game objects that were not overridden with a value with game object scope. + /// To reset a game parameter value with global scope, pass AK_INVALID_GAME_OBJECT as the game object. + /// With this function, you may also reset the value of a game parameter over time. To do so, specify a non-zero + /// value for in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValue() or ResetRTPCValue() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if in_pszParamName is NULL. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_rtpc + /// - AK::SoundEngine::SetRTPCValue() + virtual AKRESULT ResetRTPCValue( + const char* in_pszRtpcName, ///< Name of the game parameter + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT,///< Associated game object ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards its default value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when for example loading a level and you dont want the values to interpolate. + ) = 0; + + /// Sets the State of a Switch Group (by IDs). + /// \return Always returns AK_Success + /// \sa + /// - \ref soundengine_switch + /// - AK::SoundEngine::GetIDFromString() + virtual AKRESULT SetSwitch( + AkSwitchGroupID in_switchGroup, ///< ID of the Switch Group + AkSwitchStateID in_switchState, ///< ID of the Switch + AkGameObjectID in_gameObjectID ///< Associated game object ID + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Sets the State of a Switch Group (by Unicode string names). + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if the switch or Switch Group name was not resolved to an existing ID\n + /// Make sure that the banks were generated with the "include string" option. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_switch + virtual AKRESULT SetSwitch( + const wchar_t* in_pszSwitchGroup, ///< Name of the Switch Group + const wchar_t* in_pszSwitchState, ///< Name of the Switch + AkGameObjectID in_gameObjectID ///< Associated game object ID + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Sets the state of a Switch Group. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if the switch or Switch Group name was not resolved to an existing ID\n + /// Make sure that the banks were generated with the "include string" option. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_switch + virtual AKRESULT SetSwitch( + const char* in_pszSwitchGroup, ///< Name of the Switch Group + const char* in_pszSwitchState, ///< Name of the Switch + AkGameObjectID in_gameObjectID ///< Associated game object ID + ) = 0; + + /// Post the specified trigger (by IDs). + /// \return Always returns AK_Success + /// \sa + /// - \ref soundengine_triggers + /// - AK::SoundEngine::GetIDFromString() + virtual AKRESULT PostTrigger( + AkTriggerID in_triggerID, ///< ID of the trigger + AkGameObjectID in_gameObjectID ///< Associated game object ID + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Posts the specified trigger (by Unicode string name). + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if the trigger name was null + /// Make sure that the banks were generated with the "include string" option. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_triggers + virtual AKRESULT PostTrigger( + const wchar_t* in_pszTrigger, ///< Name of the trigger + AkGameObjectID in_gameObjectID ///< Associated game object ID + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Posts the specified trigger. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if the trigger name was null + /// Make sure that the banks were generated with the "include string" option. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_triggers + virtual AKRESULT PostTrigger( + const char* in_pszTrigger, ///< Name of the trigger + AkGameObjectID in_gameObjectID ///< Associated game object ID + ) = 0; + + /// Sets the state of a State Group (by IDs). + /// \return Always returns AK_Success + /// \sa + /// - \ref soundengine_states + /// - AK::SoundEngine::GetIDFromString() + virtual AKRESULT SetState( + AkStateGroupID in_stateGroup, ///< ID of the State Group + AkStateID in_state ///< ID of the state + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Sets the state of a State Group (by Unicode string names). + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if the state or State Group name was null + /// Make sure that the banks were generated with the "include string" option. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_states + /// - AK::SoundEngine::GetIDFromString() + virtual AKRESULT SetState( + const wchar_t* in_pszStateGroup, ///< Name of the State Group + const wchar_t* in_pszState ///< Name of the state + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Sets the state of a State Group. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if the state or State Group name was null + /// Make sure that the banks were generated with the "include string" option. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_states + /// - AK::SoundEngine::GetIDFromString() + virtual AKRESULT SetState( + const char* in_pszStateGroup, ///< Name of the State Group + const char* in_pszState ///< Name of the state + ) = 0; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Environments + //@{ + + /// Sets the Auxiliary Busses to route the specified game object + /// To clear the game object's auxiliary sends, \c in_uNumSendValues must be 0. + /// \sa + /// - \ref soundengine_environments + /// - \ref soundengine_environments_dynamic_aux_bus_routing + /// - \ref soundengine_environments_id_vs_string + /// - AK::SoundEngine::GetIDFromString() + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if a duplicated environment is found in the array + /// - \c AK_InvalidFloatValue if the value specified was NaN or Inf + virtual AKRESULT SetGameObjectAuxSendValues( + AkGameObjectID in_gameObjectID, ///< Associated game object ID + AkAuxSendValue* in_aAuxSendValues, ///< Variable-size array of AkAuxSendValue structures + ///< (it may be NULL if no environment must be set) + AkUInt32 in_uNumSendValues ///< The number of auxiliary busses at the pointer's address + ///< (it must be 0 if no environment is set) + ) = 0; + + /// Registers a callback to allow the game to modify or override the volume to be applied at the output of an audio bus. + /// The callback must be registered once per bus ID. + /// Call with in_pfnCallback = NULL to unregister. + /// \aknote The bus in_busID needs to be a mixing bus.\endaknote + /// \aknote The callback function will not be called for the Master Audio Bus, since the output of this bus is not a bus, but is instead an Audio Device.\endaknote + /// \sa + /// - \ref goingfurther_speakermatrixcallback + /// - \ref soundengine_environments + /// - AkSpeakerVolumeMatrixCallbackInfo + /// - AK::IAkMixerInputContext + /// - AK::IAkMixerPluginContext + /// \return + /// - \c AK_Success if successful + /// - \c AK_IDNotFound if the bus is not found + /// - \c AK_NotInitialized if the sound engine is not initialized + /// - \c AK_InsufficientMemory if there is not enough memory to complete the operation + virtual AKRESULT RegisterBusVolumeCallback( + AkUniqueID in_busID, ///< Bus ID, as obtained by GetIDFromString( bus_name ). + AkBusCallbackFunc in_pfnCallback, ///< Callback function. + void* in_pCookie = NULL ///< User cookie. + ) = 0; + + /// Registers a callback to be called to allow the game to access metering data from any mixing bus. You may use this to monitor loudness at any point of the mixing hierarchy + /// by accessing the peak, RMS, True Peak and K-weighted power (according to loudness standard ITU BS.1770). See \ref goingfurther_speakermatrixcallback for an example. + /// The callback must be registered once per bus ID. + /// Call with in_pfnCallback = NULL to unregister. + /// \aknote The bus in_busID needs to be a mixing bus.\endaknote + /// \sa + /// - \ref goingfurther_speakermatrixcallback + /// - AkBusMeteringCallbackFunc + /// - AK::AkMetering + /// \return + /// - \c AK_Success if successful + /// - \c AK_IDNotFound if the bus is not found + /// - \c AK_NotInitialized if the sound engine is not initialized + /// - \c AK_InsufficientMemory if there is not enough memory to complete the operation + virtual AKRESULT RegisterBusMeteringCallback( + AkUniqueID in_busID, ///< Bus ID, as obtained by GetIDFromString( bus_name ). + AkBusMeteringCallbackFunc in_pfnCallback, ///< Callback function. + AkMeteringFlags in_eMeteringFlags, ///< Metering flags. + void* in_pCookie = NULL ///< User cookie. + ) = 0; + + /// Registers a callback to be called to allow the game to access metering data from any output device. You may use this to monitor loudness as sound leaves the Wwise sound engine + /// by accessing the peak, RMS, True Peak and K-weighted power (according to loudness standard ITU BS.1770). See \ref goingfurther_speakermatrixcallback for an example. + /// The callback must be registered once per device ShareSet ID. + /// Call with in_pfnCallback = NULL to unregister. + /// \sa + /// - \ref goingfurther_speakermatrixcallback + /// - AkOutputDeviceMeteringCallbackFunc + /// - AK::AkMetering + /// \return + /// - \c AK_Success if successful + /// - \c AK_DeviceNotFound if the device is not found + /// - \c AK_NotInitialized if the sound engine is not initialized + /// - \c AK_InsufficientMemory if there is not enough memory to complete the operation + virtual AKRESULT RegisterOutputDeviceMeteringCallback( + AkOutputDeviceID in_idOutput, ///< Output ID, as returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + AkOutputDeviceMeteringCallbackFunc in_pfnCallback, ///< Callback function. + AkMeteringFlags in_eMeteringFlags, ///< Metering flags. + void* in_pCookie = NULL ///< User cookie. + ) = 0; + + /// Sets the Output Bus Volume (direct) to be used for the specified game object. + /// The control value is a number ranging from 0.0f to 1.0f. + /// Output Bus Volumes are stored per listener association, so calling this function will override the default set of listeners. The game object in_emitterObjID will now reference its own set of listeners which will + /// be the same as the old set of listeners, but with the new associated gain. Future changes to the default listener set will not be picked up by this game object unless ResetListenersToDefault() is called. + /// \sa + /// - \ref AK::SoundEngine::ResetListenersToDefault + /// - \ref soundengine_environments + /// - \ref soundengine_environments_setting_dry_environment + /// - \ref soundengine_environments_id_vs_string + /// \return + /// - \c AK_Success when successful + /// - \c AK_InvalidFloatValue if the value specified was NaN or Inf + virtual AKRESULT SetGameObjectOutputBusVolume( + AkGameObjectID in_emitterObjID, ///< Associated emitter game object ID + AkGameObjectID in_listenerObjID, ///< Associated listener game object ID. Pass AK_INVALID_GAME_OBJECT to set the Output Bus Volume for all connected listeners. + AkReal32 in_fControlValue ///< A multiplier in the range [0.0f:16.0f] ( -inf dB to +24 dB). + ///< A value greater than 1.0f will amplify the sound. + ) = 0; + + /// Sets an Effect ShareSet at the specified audio node and Effect slot index. + /// \aknote + /// Replacing effects is preferably done through a Set Effect Event Action. + /// \endaknote + /// The target node cannot be a Bus, to set effects on a bus, use SetBusEffect() instead. + /// \aknote The option "Override Parent" in + /// the Effect section in Wwise must be enabled for this node, otherwise the parent's effect will + /// still be the one in use and the call to SetActorMixerEffect will have no impact. + /// \endaknote + /// \return Always returns AK_Success + virtual AKRESULT SetActorMixerEffect( + AkUniqueID in_audioNodeID, ///< Can be a member of the Actor-Mixer or Interactive Music Hierarchy (not a bus). + AkUInt32 in_uFXIndex, ///< Effect slot index (0-3) + AkUniqueID in_ShareSetID ///< ShareSets ID; pass AK_INVALID_UNIQUE_ID to clear the effect slot + ) = 0; + + /// Sets an Effect ShareSet at the specified bus and Effect slot index. + /// \aknote + /// Replacing effects is preferably done through a Set Effect Event Action. + /// \endaknote + /// The Bus can either be an Audio Bus or an Auxiliary Bus. + /// This adds a reference on the audio node to an existing ShareSet. + /// \aknote This function has unspecified behavior when adding an Effect to a currently playing + /// Bus which does not have any Effects, or removing the last Effect on a currently playing bus. + /// \endaknote + /// \aknote This function will replace existing Effects on the node. If the target node is not at + /// the top of the hierarchy and is in the actor-mixer hierarchy, the option "Override Parent" in + /// the Effect section in Wwise must be enabled for this node, otherwise the parent's Effect will + /// still be the one in use and the call to SetBusEffect will have no impact. + /// \endaknote + /// \return + /// - \c AK_Success when successfully posted. + /// - \c AK_IDNotFound if the Bus isn't found by in_audioNodeID + /// - \c AK_InvalidParameter if in_uFXIndex isn't in range + virtual AKRESULT SetBusEffect( + AkUniqueID in_audioNodeID, ///< Bus Short ID. + AkUInt32 in_uFXIndex, ///< Effect slot index (0-3) + AkUniqueID in_ShareSetID ///< ShareSets ID; pass AK_INVALID_UNIQUE_ID to clear the Effect slot + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Sets an Effect ShareSet at the specified Bus and Effect slot index. + /// \aknote + /// Replacing effects is preferably done through a Set Effect Event Action. + /// \endaknote + /// The Bus can either be an Audio Bus or an Auxiliary Bus. + /// This adds a reference on the audio node to an existing ShareSet. + /// \aknote This function has unspecified behavior when adding an Effect to a currently playing + /// bus which does not have any Effects, or removing the last Effect on a currently playing Bus. + /// \endaknote + /// \aknote This function will replace existing Effects on the node. If the target node is not at + /// the top of the hierarchy and is in the Actor-Mixer Hierarchy, the option "Override Parent" in + /// the Effect section in Wwise must be enabled for this node, otherwise the parent's Effect will + /// still be the one in use and the call to \c SetBusEffect will have no impact. + /// \endaknote + /// \returns + /// - \c AK_Success when successfully posted. + /// - \c AK_IDNotFound if the Bus name doesn't point to a valid bus. + /// - \c AK_InvalidID if in_pszBusName is null + /// - \c AK_InvalidParameter if in_uFXIndex isn't in range or in_pszBusName is null + virtual AKRESULT SetBusEffect( + const wchar_t* in_pszBusName, ///< Bus name + AkUInt32 in_uFXIndex, ///< Effect slot index (0-3) + AkUniqueID in_ShareSetID ///< ShareSets ID; pass AK_INVALID_UNIQUE_ID to clear the effect slot + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Sets an Effect ShareSet at the specified Bus and Effect slot index. + /// \aknote + /// Replacing effects is preferably done through a Set Effect Event Action. + /// \endaknote + /// The Bus can either be an Audio Bus or an Auxiliary Bus. + /// This adds a reference on the audio node to an existing ShareSet. + /// \aknote This function has unspecified behavior when adding an Effect to a currently playing + /// Bus which does not have any effects, or removing the last Effect on a currently playing bus. + /// \endaknote + /// \aknote Make sure the new effect ShareSet is included in a soundbank, and that sound bank is loaded. Otherwise you will see errors in the Capture Log.\endaknote + /// \aknote This function will replace existing Effects on the node. If the target node is not at + /// the top of the hierarchy and is in the Actor-Mixer Hierarchy, the option "Override Parent" in + /// the Effect section in Wwise must be enabled for this node, otherwise the parent's Effect will + /// still be the one in use and the call to SetBusEffect will have no impact. + /// \endaknote + /// \returns + /// - \c AK_Success when successfully posted. + /// - \c AK_IDNotFound if the Bus name doesn't point to a valid bus. + /// - \c AK_InvalidParameter if in_uFXIndex isn't in range + /// - \c AK_InvalidID if in_pszBusName is null + virtual AKRESULT SetBusEffect( + const char* in_pszBusName, ///< Bus name + AkUInt32 in_uFXIndex, ///< Effect slot index (0-3) + AkUniqueID in_ShareSetID ///< ShareSets ID; pass AK_INVALID_UNIQUE_ID to clear the effect slot + ) = 0; + + /// Sets an audio device effect ShareSet on the specified output device and effect slot index. + /// \aknote + /// Replacing effects is preferably done through a Set Effect Event Action. + /// \endaknote + /// \aknote Make sure the new effect ShareSet is included in a soundbank, and that sound bank is loaded. Otherwise you will see errors in the Capture Log.\endaknote + /// \aknote This function will replace existing effects of the audio device ShareSet. \endaknote + /// \aknote Audio device effects support is limited to one ShareSet per plug-in type at any time. \endaknote + /// \aknote Errors are reported in the Wwise Capture Log if the effect cannot be set on the output device. \endaknote + + /// \returns Always returns AK_Success + virtual AKRESULT SetOutputDeviceEffect( + AkOutputDeviceID in_outputDeviceID, ///< Output ID, as returned from AddOutput or GetOutputID. Most of the time this should be 0 to designate the main (default) output + AkUInt32 in_uFXIndex, ///< Effect slot index (0-3) + AkUniqueID in_FXShareSetID ///< Effect ShareSet ID + ) = 0; + + /// Sets a Mixer ShareSet at the specified bus. + /// \aknote This function has unspecified behavior when adding a mixer to a currently playing + /// Bus which does not have any Effects or mixer, or removing the last mixer on a currently playing Bus. + /// \endaknote + /// \aknote Make sure the new mixer ShareSet is included in a soundbank, and that sound bank is loaded. Otherwise you will see errors in the Capture Log.\endaknote + /// \aknote This function will replace existing mixers on the node. + /// \endaknote + /// \return Always returns AK_Success + virtual AKRESULT SetMixer( + AkUniqueID in_audioNodeID, ///< Bus Short ID. + AkUniqueID in_ShareSetID ///< ShareSets ID; pass AK_INVALID_UNIQUE_ID to remove. + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Sets a Mixer ShareSet at the specified bus. + /// \aknote This function has unspecified behavior when adding a mixer to a currently playing + /// bus which does not have any effects nor mixer, or removing the last mixer on a currently playing bus. + /// \endaknote + /// \aknote Make sure the new mixer ShareSet is included in a soundbank, and that sound bank is loaded. Otherwise you will see errors in the Capture Log.\endaknote + /// \aknote This function will replace existing mixers on the node. + /// \endaknote + /// \returns + /// - \c AK_Success when successful + /// - \c AK_InvalidID if in_pszBusName is null + virtual AKRESULT SetMixer( + const wchar_t* in_pszBusName, ///< Bus name + AkUniqueID in_ShareSetID ///< ShareSets ID; pass AK_INVALID_UNIQUE_ID to remove. + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Sets a Mixer ShareSet at the specified bus. + /// \aknote This function has unspecified behavior when adding a mixer to a currently playing + /// bus which does not have any effects nor mixer, or removing the last mixer on a currently playing bus. + /// \endaknote + /// \aknote Make sure the new mixer ShareSet is included in a soundbank, and that sound bank is loaded. Otherwise you will see errors in the Capture Log.\endaknote + /// \aknote This function will replace existing mixers on the node. + /// \endaknote + /// \returns + /// - \c AK_Success when successful + /// - \c AK_InvalidID if in_pszBusName is null + virtual AKRESULT SetMixer( + const char* in_pszBusName, ///< Bus name + AkUniqueID in_ShareSetID ///< ShareSets ID; pass AK_INVALID_UNIQUE_ID to remove. + ) = 0; + + /// Forces channel configuration for the specified bus. + /// \aknote You cannot change the configuration of the master bus.\endaknote + /// + /// \return Always returns AK_Success + virtual AKRESULT SetBusConfig( + AkUniqueID in_audioNodeID, ///< Bus Short ID. + AkChannelConfig in_channelConfig ///< Desired channel configuration. An invalid configuration (from default constructor) means "as parent". + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Forces channel configuration for the specified bus. + /// \aknote You cannot change the configuration of the master bus.\endaknote + /// + /// \returns + /// - \c AK_Success when successful + /// - \c AK_InvalidID if in_pszBusName is null + virtual AKRESULT SetBusConfig( + const wchar_t* in_pszBusName, ///< Bus name + AkChannelConfig in_channelConfig ///< Desired channel configuration. An invalid configuration (from default constructor) means "as parent". + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Forces channel configuration for the specified bus. + /// \aknote You cannot change the configuration of the master bus.\endaknote + /// + /// \returns + /// - \c AK_Success when successful + /// - \c AK_InvalidID if in_pszBusName is null + virtual AKRESULT SetBusConfig( + const char* in_pszBusName, ///< Bus name + AkChannelConfig in_channelConfig ///< Desired channel configuration. An invalid configuration (from default constructor) means "as parent". + ) = 0; + + /// Sets a game object's obstruction and occlusion levels. If SetMultiplePositions were used, values are set for all positions. + /// This function is used to affect how an object should be heard by a specific listener. + /// \sa + /// - \ref soundengine_obsocc + /// - \ref soundengine_environments + /// \return Always returns AK_Success + virtual AKRESULT SetObjectObstructionAndOcclusion( + AkGameObjectID in_EmitterID, ///< Emitter game object ID + AkGameObjectID in_ListenerID, ///< Listener game object ID + AkReal32 in_fObstructionLevel, ///< ObstructionLevel: [0.0f..1.0f] + AkReal32 in_fOcclusionLevel ///< OcclusionLevel: [0.0f..1.0f] + ) = 0; + + /// Sets a game object's obstruction and occlusion level for each positions defined by SetMultiplePositions. + /// This function differs from SetObjectObstructionAndOcclusion as a list of obstruction/occlusion pair is provided + /// and each obstruction/occlusion pair will affect the corresponding position defined at the same index. + /// \aknote In the case the number of obstruction/occlusion pairs is smaller than the number of positions, remaining positions' + /// obstrucion/occlusion values are set to 0.0. \endaknote + /// \return + /// - \c AK_Success if successful + /// - \c AK_CommandTooLarge if the number of obstruction values is too large for the command queue. + /// - \c AK_InvalidParameter if one of the parameter is out of range (check the debug console) + /// - \c AK_InvalidFloatValue if one of the occlusion/obstruction values is NaN or Inf. + /// \sa + /// - \ref soundengine_obsocc + /// - \ref soundengine_environments + /// \return AK_Success if occlusion/obstruction values are successfully stored for this emitter + virtual AKRESULT SetMultipleObstructionAndOcclusion( + AkGameObjectID in_EmitterID, ///< Emitter game object ID + AkGameObjectID in_uListenerID, ///< Listener game object ID + AkObstructionOcclusionValues* in_fObstructionAndOcclusionValues, ///< Array of obstruction/occlusion pairs to apply + ///< ObstructionLevel: [0.0f..1.0f] + ///< OcclusionLevel: [0.0f..1.0f] + AkUInt32 in_uNumObstructionAndOcclusion ///< Number of obstruction/occlusion pairs specified in the provided array + ) = 0; + + /// Saves the playback history of container structures. + /// This function will write history data for all currently loaded containers and instantiated game + /// objects (for example, current position in Sequence Containers and previously played elements in + /// Random Containers). + /// \remarks + /// This function acquires the main audio lock, and may block the caller for several milliseconds. + /// \return + /// - \c AK_Success when successful + /// - \c AK_Fail is in_pBytes could not be parsed (corruption or data is truncated) + /// \sa + /// - AK::SoundEngine::SetContainerHistory() + virtual AKRESULT GetContainerHistory( + AK::IWriteBytes* in_pBytes ///< Pointer to IWriteBytes interface used to save the history. + ) = 0; + + /// Restores the playback history of container structures. + /// This function will read history data from the passed-in stream reader interface, and apply it to all + /// currently loaded containers and instantiated game objects. Game objects are matched by + /// ID. History for unloaded structures and unknown game objects will be skipped. + /// \remarks + /// This function acquires the main audio lock, and may block the caller for several milliseconds. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InsufficientMemory if not enough memory is available for IReadBytes operation + /// \sa + /// - AK::SoundEngine::GetContainerHistory() + virtual AKRESULT SetContainerHistory( + AK::IReadBytes* in_pBytes ///< Pointer to IReadBytes interface used to load the history. + ) = 0; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Capture + //@{ + + /// Starts recording the sound engine audio output. + /// StartOutputCapture outputs a wav file per current output device of the sound engine. + /// If more than one device is active, the system will create multiple files in the same output + /// directory and will append numbers at the end of the provided filename. + /// + /// If no device is running yet, the system will return success AK_Success despite doing nothing. + /// Use RegisterAudioDeviceStatusCallback to get notified when devices are created/destructed. + /// + /// \return AK_Success if successful, AK_Fail if there was a problem starting the output capture. + /// \remark + /// - The sound engine opens a stream for writing using AK::IAkStreamMgr::CreateStd(). If you are using the + /// default implementation of the Stream Manager, file opening is executed in your implementation of + /// the Low-Level IO interface AK::StreamMgr::IAkFileLocationResolver::Open(). The following + /// AkFileSystemFlags are passed: uCompanyID = AKCOMPANYID_AUDIOKINETIC and uCodecID = AKCODECID_PCM, + /// and the AkOpenMode is AK_OpenModeWriteOvrwr. Refer to \ref streamingmanager_lowlevel_location for + /// more details on managing the deployment of your Wwise generated data. + /// \return + /// - \c AK_Success when successful + /// - \c AK_InvalidParameter if in_CaptureFileName is null. + /// - \c AK_InsufficientMemory if not enough memory is available. + /// \sa + /// - AK::SoundEngine::StopOutputCapture() + /// - AK::StreamMgr::SetFileLocationResolver() + /// - \ref streamingdevicemanager + /// - \ref streamingmanager_lowlevel_location + /// - RegisterAudioDeviceStatusCallback + virtual AKRESULT StartOutputCapture( + const AkOSChar* in_CaptureFileName ///< Name of the output capture file + ) = 0; + + /// Stops recording the sound engine audio output. + /// \return AK_Success if successful, AK_Fail if there was a problem stopping the output capture. + /// \sa + /// - AK::SoundEngine::StartOutputCapture() + virtual AKRESULT StopOutputCapture() = 0; + + /// Adds text marker in audio output file. + /// \return + /// - \c AK_Success when successful + /// - \c AK_InvalidParameter if in_MarkerText is null. + /// - \c AK_InsufficientMemory if not enough memory is available. + /// \sa + /// - AK::SoundEngine::StartOutputCapture() + virtual AKRESULT AddOutputCaptureMarker( + const char* in_MarkerText ///< Text of the marker + ) = 0; + + /// Gets the system sample rate. + /// \return The sample rate. + virtual AkUInt32 GetSampleRate() = 0; + + /// Registers a callback used for retrieving audio samples. + /// The callback will be called from the audio thread during real-time rendering and from the main thread during offline rendering. + /// \return + /// - \c AK_Success when successful + /// - \c AK_DeviceNotFound if the audio device ID doesn't match to a device in use. + /// - \c AK_InvalidParameter when in_pfnCallback is null + /// - \c AK_NotInitialized if the sound engine is not initialized at this time + /// \sa + /// - AK::SoundEngine::AddOutput() + /// - AK::SoundEngine::GetOutputID() + /// - AK::SoundEngine::UnregisterCaptureCallback() + virtual AKRESULT RegisterCaptureCallback( + AkCaptureCallbackFunc in_pfnCallback, ///< Capture callback function to register. + AkOutputDeviceID in_idOutput = AK_INVALID_OUTPUT_DEVICE_ID, ///< The audio device specific id, return by AK::SoundEngine::AddOutput or AK::SoundEngine::GetOutputID + void* in_pCookie = NULL ///< Callback cookie that will be sent to the callback function along with additional information + ) = 0; + + /// Unregisters a callback used for retrieving audio samples. + /// \return + /// - \c AK_Success when successful + /// - \c AK_DeviceNotFound if the audio device ID doesn't match to a device in use. + /// - \c AK_InvalidParameter when in_pfnCallback is null + /// - \c AK_NotInitialized if the sound engine is not initialized at this time + /// \sa + /// - AK::SoundEngine::AddOutput() + /// - AK::SoundEngine::GetOutputID() + /// - AK::SoundEngine::RegisterCaptureCallback() + virtual AKRESULT UnregisterCaptureCallback( + AkCaptureCallbackFunc in_pfnCallback, ///< Capture callback function to unregister. + AkOutputDeviceID in_idOutput = AK_INVALID_OUTPUT_DEVICE_ID, ///< The audio device specific id, return by AK::SoundEngine::AddOutput or AK::SoundEngine::GetOutputID + void* in_pCookie = NULL ///< Callback cookie that will be sent to the callback function along with additional information + ) = 0; + + /// Starts recording the sound engine profiling information into a file. This file can be read + /// by Wwise Authoring. The file is created at the base path. If you have integrated Wwise I/O, + /// you can use CAkDefaultIOHookBlocking::SetBasePath() (or CAkDefaultIOHookBlocking::AddBasePath()) + /// to change the location where the file is saved. The profiling session records all data types possible. + /// Note that this call captures peak metering for all the busses loaded and mixing + /// while this call is invoked. + /// \remark This function is provided as a utility tool only. It does nothing if it is + /// called in the release configuration and returns AK_NotCompatible. + virtual AKRESULT StartProfilerCapture( + const AkOSChar* in_CaptureFileName ///< Name of the output profiler file (.prof extension recommended) + ) = 0; + + /// Stops recording the sound engine profiling information. + /// \remark This function is provided as a utility tool only. It does nothing if it is + /// called in the release configuration and returns AK_NotCompatible. + virtual AKRESULT StopProfilerCapture() = 0; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Offline Rendering + //@{ + + /// Sets the offline rendering frame time in seconds. + /// When offline rendering is enabled, every call to \ref RenderAudio() will generate sample data as if this much time has elapsed. If the frame time argument is less than or equal to zero, every call to RenderAudio() will generate one audio buffer. + /// \return Always returns AK_Success + virtual AKRESULT SetOfflineRenderingFrameTime( + AkReal32 in_fFrameTimeInSeconds ///< frame time in seconds used during offline rendering + ) = 0; + + /// Enables/disables offline rendering. + /// \return Always returns AK_Success + virtual AKRESULT SetOfflineRendering( + bool in_bEnableOfflineRendering ///< enables/disables offline rendering + ) = 0; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Secondary Outputs + //@{ + + /// Adds an output to the sound engine. Use this to add controller-attached headphones, controller speakers, DVR output, etc. + /// The in_Settings parameter contains an Audio Device ShareSet to specify the output plugin to use and a device ID to specify the instance, if necessary (e.g. which game controller). + /// + /// Like most functions of AK::SoundEngine, AddOutput is asynchronous. A successful return code merely indicates that the request is properly queued. + /// Error codes returned by this function indicate various invalid parameters. To know if this function succeeds or not, and the failure code, + /// register an AkDeviceStatusCallbackFunc callback with RegisterAudioDeviceStatusCallback. + /// + /// \sa AkOutputSettings for more details. + /// \sa \ref integrating_secondary_outputs + /// \sa \ref default_audio_devices + /// \sa AK::SoundEngine::RegisterAudioDeviceStatusCallback + /// \sa AK::AkDeviceStatusCallbackFunc + /// \return + /// The following codes are returned directly from the function, as opposed to the AkDeviceStatusCallback + /// - \c AK_NotImplemented: Feature not supported, some platforms don't have other outputs. + /// - \c AK_InvalidParameter: Out of range parameters or unsupported parameter combinations (see parameter list below). + /// - \c AK_IDNotFound: The audioDeviceShareSet on in_settings doesn't exist. Possibly, the Init bank isn't loaded yet or was not updated with latest changes. + /// - \c AK_DeviceNotReady: The idDevice on in_settings doesn't match with a valid hardware device. Either the device doesn't exist or is disabled. Disconnected devices (headphones) are not considered "not ready" therefore won't cause this error. + /// - \c AK_NotInitialized: If AK::SoundEngine::Init was not called or if the Init.bnk was not loaded before the call. + /// - \c AK_Success: Parameters are valid. + /// + /// The following codes are returned from the callback. + /// - \c AK_InsufficientMemory : Not enough memory to complete the operation. + /// - \c AK_IDNotFound: The audioDeviceShareSet on in_settings doesn't exist. Possibly, the Init bank isn't loaded yet or was not updated with latest changes. + /// - \c AK_PluginNotRegistered: The audioDeviceShareSet exists but the plug-in it refers to is not installed or statically linked with the game. + /// - \c AK_NotCompatible: The hardware does not support this type of output. Wwise will try to use the System output instead, and a separate callback will fire when that completes. + /// - \c AK_DeviceNotCompatible: The hardware does not support this type of output. Wwise will NOT fallback to any other type of output. + /// - \c AK_Fail: Generic code for any non-permanent conditions (e.g. disconnection) that prevent the use of the output. Wwise has created the output and sounds will be routed to it, but this output is currently silent until the temporary condition resolves. + /// - \c AK_NoDistinctListener: Outputs of the same type (same ShareSet, like controller speakers) must have distinct Listeners to make a proper routing. This doesn't happen if there is only one output of that type. + virtual AKRESULT AddOutput( + const AkOutputSettings& in_Settings, ///< Creation parameters for this output. \ref AkOutputSettings + AkOutputDeviceID* out_pDeviceID = NULL, ///< (Optional) Output ID to use with all other Output management functions. Leave to NULL if not required. \ref AK::SoundEngine::GetOutputID + const AkGameObjectID* in_pListenerIDs = NULL, ///< Specific listener(s) to attach to this device. + ///< If specified, only the sounds routed to game objects linked to those listeners will play in this device. + ///< It is necessary to have separate listeners if multiple devices of the same type can coexist (e.g. controller speakers) + ///< If not specified, sound routing simply obey the associations between Master Busses and Audio Devices setup in the Wwise Project. + AkUInt32 in_uNumListeners = 0 ///< The number of elements in the in_pListenerIDs array. + ) = 0; + + /// Removes one output added through AK::SoundEngine::AddOutput + /// If a listener was associated with the device, you should consider unregistering the listener prior to call RemoveOutput + /// so that Game Object/Listener routing is properly updated according to your game scenario. + /// \sa \ref integrating_secondary_outputs + /// \sa AK::SoundEngine::AddOutput + /// \return AK_Success: Parameters are valid. + virtual AKRESULT RemoveOutput( + AkOutputDeviceID in_idOutput ///< ID of the output to remove. Use the returned ID from AddOutput, GetOutputID, or ReplaceOutput + ) = 0; + + /// Replaces an output device previously created during engine initialization or from AddOutput, with a new output device. + /// In addition to simply removing one output device and adding a new one, the new output device will also be used on all of the master buses + /// that the old output device was associated with, and preserve all listeners that were attached to the old output device. + /// + /// Like most functions of AK::SoundEngine, AddOutput is asynchronous. A successful return code merely indicates that the request is properly queued. + /// Error codes returned by this function indicate various invalid parameters. To know if this function succeeds or not, and the failure code, + /// register an AkDeviceStatusCallbackFunc callback with RegisterAudioDeviceStatusCallback. + /// + /// \sa AK::SoundEngine::AddOutput + /// \sa AK::SoundEngine::RegisterAudioDeviceStatusCallback + /// \sa AK::AkDeviceStatusCallbackFunc + /// \return + /// - \c AK_InvalidID: The audioDeviceShareSet on in_settings was not valid. + /// - \c AK_IDNotFound: The audioDeviceShareSet on in_settings doesn't exist. Possibly, the Init bank isn't loaded yet or was not updated with latest changes. + /// - \c AK_DeviceNotReady: The idDevice on in_settings doesn't match with a valid hardware device. Either the device doesn't exist or is disabled. Disconnected devices (headphones) are not considered "not ready" therefore won't cause this error. + /// - \c AK_DeviceNotFound: The in_outputDeviceId provided does not match with any of the output devices that the sound engine is currently using. + /// - \c AK_InvalidParameter: Out of range parameters or unsupported parameter combinations on in_settings + /// - \c AK_Success: parameters were valid, and the remove and add will occur. + virtual AKRESULT ReplaceOutput( + const AkOutputSettings& in_Settings, ///< Creation parameters for this output. \ref AkOutputSettings + AkOutputDeviceID in_outputDeviceId, ///< AkOutputDeviceID of the output to replace. Use 0 to target the current main output, regardless of its id. Otherwise, use the AkOuptutDeviceID returned from AddOutput() or ReplaceOutput(), or generated by GetOutputID() + AkOutputDeviceID* out_pOutputDeviceId = NULL ///< (Optional) Pointer into which the method writes the AkOutputDeviceID of the new output device. If the call fails, the value pointed to will not be modified. + ) = 0; + + /// Gets the compounded output ID from ShareSet and device id. + /// Outputs are defined by their type (Audio Device ShareSet) and their specific system ID. A system ID could be reused for other device types on some OS or platforms, hence the compounded ID. + /// Use 0 for in_idShareSet & in_idDevice to get the Main Output ID (the one usually initialized during AK::SoundEngine::Init) + /// \return The id of the output + virtual AkOutputDeviceID GetOutputID( + AkUniqueID in_idShareSet, ///< Audio Device ShareSet ID, as defined in the Wwise Project. If needed, use AK::SoundEngine::GetIDFromString() to convert from a string. Set to AK_INVALID_UNIQUE_ID to use the default. + AkUInt32 in_idDevice ///< Device specific identifier, when multiple devices of the same type are possible. If only one device is possible, leave to 0. + ///< - PS4 Controller-Speakers: UserID as returned from sceUserServiceGetLoginUserIdList + ///< - XBoxOne Controller-Headphones: Use the AK::GetDeviceID function to get the ID from an IMMDevice. Find the player's device with the WASAPI API (IMMDeviceEnumerator, see Microsoft documentation) or use AK::GetDeviceIDFromName. + ///< - Windows: Use AK::GetDeviceID or AK::GetDeviceIDFromName to get the correct ID. + ///< - All others output: use 0 to select the default device for that type. + ) = 0; + + virtual AkOutputDeviceID GetOutputID( + const char* in_szShareSet, ///< Audio Device ShareSet Name, as defined in the Wwise Project. If Null, will select the Default Output ShareSet (always available) + AkUInt32 in_idDevice ///< Device specific identifier, when multiple devices of the same type are possible. If only one device is possible, leave to 0. + ///< - PS4 Controller-Speakers: UserID as returned from sceUserServiceGetLoginUserIdList + ///< - XBoxOne Controller-Headphones: Use the AK::GetDeviceID function to get the ID from an IMMDevice. Find the player's device with the WASAPI API (IMMDeviceEnumerator, see Microsoft documentation) or use AK::GetDeviceIDFromName. + ///< - Windows: Use AK::GetDeviceID or AK::GetDeviceIDFromName to get the correct ID. + ///< - All others output: use 0 to select the default device for that type. + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + virtual AkOutputDeviceID GetOutputID( + const wchar_t* in_szShareSet, ///< Audio Device ShareSet Name, as defined in the Wwise Project. If Null, will select the Default Output ShareSet (always available) + AkUInt32 in_idDevice ///< Device specific identifier, when multiple devices of the same type are possible. If only one device is possible, leave to 0. + ///< - PS4 Controller-Speakers: UserID as returned from sceUserServiceGetLoginUserIdList + ///< - XBoxOne Controller-Headphones: Use the AK::GetDeviceID function to get the ID from an IMMDevice. Find the player's device with the WASAPI API (IMMDeviceEnumerator, see Microsoft documentation) or use AK::GetDeviceIDFromName. + ///< - Windows: Use AK::GetDeviceID or AK::GetDeviceIDFromName to get the correct ID. + ///< - All others output: use 0 to select the default device for that type. + ) = 0; +#endif + + /// Sets the Audio Device to which a master bus outputs. This overrides the setting in the Wwise project. + /// Can only be set on top-level busses. The Init bank should be successfully loaded prior to this call. + /// \aknote This function is useful only if used before the creation of an output, at the beginning of the sound engine setup. + /// Once active outputs using this Bus have been created, it is imperative to use AK::SoundEngine:ReplaceOutput instead to change the type of output. + /// \return + /// AK_IDNotFound when either the Bus ID or the Device ID are not present in the Init bank or the bank was not loaded + /// AK_InvalidParameter when the specified bus is not a Master Bus. This function can be called only on busses that have no parent bus. + virtual AKRESULT SetBusDevice( + AkUniqueID in_idBus, ///< Id of the master bus + AkUniqueID in_idNewDevice ///< New device ShareSet to replace with. + ) = 0; + + /// Sets the Audio Device to which a master bus outputs. This overrides the setting in the Wwise project. + /// Can only be set on top-level busses. The Init bank should be successfully loaded prior to this call. + /// \aknote This function is useful only if used before the creation of an output, at the beginning of the sound engine setup. + /// Once active outputs using this Bus have been created, it is imperative to use AK::SoundEngine:ReplaceOutput instead to change the type of output. + /// \return + /// AK_IDNotFound when either the Bus ID or the Device ID are not present in the Init bank or the bank was not loaded + /// AK_InvalidParameter when the specified bus is not a Master Bus. This function can be called only on busses that have no parent bus. + virtual AKRESULT SetBusDevice( + const char* in_BusName, ///< Name of the master bus + const char* in_DeviceName ///< New device ShareSet to replace with. + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Sets the Audio Device to which a master bus outputs. This overrides the setting in the Wwise project. + /// Can only be set on top-level busses. The Init bank should be successfully loaded prior to this call. + /// SetBusDevice must be preceded by a call to AddOutput for the new device ShareSet to be registered as an output. + /// \return + /// AK_IDNotFound when either the Bus ID or the Device ID are not present in the Init bank or the bank was not loaded + /// AK_InvalidParameter when the specified bus is not a Master Bus. This function can be called only on busses that have no parent bus. + virtual AKRESULT SetBusDevice( + const wchar_t* in_BusName, ///< Name of the master bus + const wchar_t* in_DeviceName ///< New device ShareSet to replace with. + ) = 0; +#endif + + /// Returns a listing of the current devices for a given sink plug-in, including Device ID, friendly name, and state. + /// This call is only valid for sink plug-ins that support device enumeration. + /// Prerequisites: the plug-in must have been initialized by loading the init bank or by calling \ref AK::SoundEngine::RegisterPlugin. + /// \return + /// - \c AK_NotImplemented if the sink plug-in does not implement device enumeration + /// - \c AK_PluginNotRegistered if the plug-in has not been registered yet either by loading the init bank or by calling RegisterPluginDLL. + /// - \c AK_NotCompatible if no device of this type are supported on the current platform + /// - \c AK_Fail in case of system device manager failure (OS related) + /// + virtual AKRESULT GetDeviceList( + AkUInt32 in_ulCompanyID, ///< Company identifier (as declared in the plug-in description XML file) + AkUInt32 in_ulPluginID, ///< Plug-in identifier (as declared in the plug-in description XML file) + AkUInt32& io_maxNumDevices, ///< In: The length of the out_deviceDescriptions array. Out: If out_deviceDescriptions is not-null, this will be set to the number of entries in out_deviceDescriptions that was populated. If out_deviceDescriptions is null, this will be set to the number of devices that may be available. + AkDeviceDescription* out_deviceDescriptions ///< The output array of device descriptions. + ) = 0; + + /// Returns a listing of the current devices for a given sink plug-in, including Device ID, friendly name, and state. + /// This call is only valid for sink plug-ins that support device enumeration. + /// Prerequisites: + /// * The plug-in must have been initialized by loading the init bank or by calling \ref AK::SoundEngine::RegisterPlugin. + /// * The audio device ShareSet must have been loaded from a soundbank and a device with this ShareSet must exist in the pipeline. + /// \return + /// AK_NotImplemented if the sink plug-in does not implement device enumeration + /// AK_PluginNotRegistered if the plug-in has not been registered yet either by loading the init bank or by calling RegisterPluginDLL. + virtual AKRESULT GetDeviceList( + AkUniqueID in_audioDeviceShareSetID, ///< In: The audio device ShareSet ID for which to list the sink plug-in devices. + AkUInt32& io_maxNumDevices, ///< In: The length of the out_deviceDescriptions array. Out: If out_deviceDescriptions is not-null, this will be set to the number of entries in out_deviceDescriptions that was populated. If out_deviceDescriptions is null, this will be set to the number of devices that may be available. + AkDeviceDescription* out_deviceDescriptions ///< The output array of device descriptions. + ) = 0; + + /// Sets the volume of a output device. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidFloatValue if the value specified was NaN or Inf + virtual AKRESULT SetOutputVolume( + AkOutputDeviceID in_idOutput, ///< Output ID to set the volume on. As returned from AddOutput or GetOutputID + AkReal32 in_fVolume ///< Volume (0.0 = Muted, 1.0 = Volume max) + ) = 0; + + /// Returns whether or not the audio device matching the device ID provided supports spatial audio (i.e. the functionality is enabled, and more than 0 dynamic objects are supported). + /// If Spatial Audio is supported, then you can call Init, AddOutput, or ReplaceOutput with an Audio Device ShareSet corresponding to the respective platform-specific plug-in that + /// provides spatial audio, such as the Microsoft Spatial Sound Platform for Windows. Note that on Xbox One, you need to call EnableSpatialAudio() before the sound engine is + /// initialized, or initialize the sound engine with AkPlatformInitSettings::bEnableSpatialAudio set to true if you want spatial audio support; otherwise this will always return AK_NotCompatible. + /// \return + /// AK_NotCompatible when the device ID provided does not support spatial audio, or the platform does not support spatial audio + /// AK_Fail when there is some other miscellaneous failure, or the device ID provided does not match a device that the system knows about + /// AK_Success when the device ID provided does support spatial audio + virtual AKRESULT GetDeviceSpatialAudioSupport( + AkUInt32 in_idDevice ///< Device specific identifier, when multiple devices of the same type are possible. If only one device is possible, leave to 0. + ///< - PS4 Controller-Speakers: UserID as returned from sceUserServiceGetLoginUserIdList + ///< - XBoxOne Controller-Headphones: Use the AK::GetDeviceID function to get the ID from an IMMDevice. Find the player's device with the WASAPI API (IMMDeviceEnumerator, see Microsoft documentation) or use AK::GetDeviceIDFromName. + ///< - Windows: Use AK::GetDeviceID or AK::GetDeviceIDFromName to get the correct ID. + ///< - All others output: use 0 to select the default device for that type. + ) = 0; + + + //@} + + /// This function should be called to put the sound engine in background mode, where audio isn't processed anymore. This needs to be called if the console has a background mode or some suspended state. + /// Call \c WakeupFromSuspend when your application receives the message from the OS that the process is back in foreground. + /// When suspended, the sound engine will process API messages (like PostEvent and SetSwitch) only when \ref RenderAudio() is called. + /// It is recommended to match the in_bRenderAnyway parameter with the behavior of the rest of your game: + /// if your game still runs in background and you must keep some kind of coherent state between the audio engine and game, then allow rendering. + /// If you want to minimize CPU when in background, then don't allow rendering and never call RenderAudio from the game. + /// + /// Consult \ref workingwithsdks_system_calls to learn when it is appropriate to call this function for each platform. + /// \sa WakeupFromSuspend + /// \sa \ref workingwithsdks_system_calls + virtual AKRESULT Suspend( + bool in_bRenderAnyway = false, ///< If set to true, audio processing will still occur, but not outputted. When set to false, no audio will be processed at all, even upon reception of RenderAudio(). + bool in_bFadeOut = true ///< Delay the suspend by one audio frame in order to fade-out. When false, the suspend takes effect immediately but audio may glitch. + ) = 0; + + /// This function should be called to wake up the sound engine and start processing audio again. This needs to be called if the console has a background mode or some suspended state. + /// + /// Consult \ref workingwithsdks_system_calls to learn when it is appropriate to call this function for each platform. + /// \sa Suspend + /// \sa \ref workingwithsdks_system_calls + virtual AKRESULT WakeupFromSuspend( + AkUInt32 in_uDelayMs = 0 /// Delay (in milliseconds) before the wake up occurs. Rounded up to audio frame granularity. Adding a delay is useful if there is a possibility that another OS event may override the wake-up in the near future. + ) = 0; + + /// Obtains the current audio output buffer tick. This corresponds to the number of buffers produced by + /// the sound engine since initialization. + /// \return Tick count. + virtual AkUInt32 GetBufferTick() = 0; + + /// Obtains the current audio output sample tick. This corresponds to the number of sapmles produced by + /// the sound engine since initialization. + /// \return Sample count. + virtual AkUInt64 GetSampleTick() = 0; + + class IQuery + { + public: + UE_NONCOPYABLE(IQuery); + + protected: + IQuery() = default; + + public: + virtual ~IQuery() {} + + //////////////////////////////////////////////////////////////////////// + /// @name Game Objects + //@{ + + /// Get the position of a game object. + /// \return AK_Success if succeeded, or AK_IDNotFound if the game object was not registered + /// \sa + /// - \ref soundengine_3dpositions + virtual AKRESULT GetPosition( + AkGameObjectID in_GameObjectID, ///< Game object identifier + AkSoundPosition& out_rPosition ///< Position to get + ) = 0; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Listeners + //@{ + + /// Get a game object's listeners. + /// \return AK_Success if succeeded, or AK_IDNotFound if the game object was not registered + /// \sa + /// - \ref soundengine_listeners + virtual AKRESULT GetListeners( + AkGameObjectID in_GameObjectID, ///< Source game object identifier + AkGameObjectID* out_ListenerObjectIDs, ///< Pointer to an array of AkGameObjectID's. Will be populated with the IDs of the listeners of in_GameObjectID. Pass NULL to querry the size required. + AkUInt32& oi_uNumListeners ///< Pass in the the available number of elements in the array 'out_ListenerObjectIDs'. After return, the number of valid elements filled in the array. + ) = 0; + + /// Get a listener's position. + /// \return AK_Success if succeeded, or AK_InvalidParameter if the index is out of range + /// \sa + /// - \ref soundengine_listeners_settingpos + virtual AKRESULT GetListenerPosition( + AkGameObjectID in_uIndex, ///< Listener index (0: first listener, 7: 8th listener) + AkListenerPosition& out_rPosition ///< Position set + ) = 0; + + /// Get a listener's spatialization parameters. + /// \return AK_Success if succeeded, or AK_InvalidParameter if the index is out of range + /// \sa + /// - AK::SoundEngine::SetListenerSpatialization(). + /// - \ref soundengine_listeners_spatial + virtual AKRESULT GetListenerSpatialization( + AkUInt32 in_uIndex, ///< Listener index (0: first listener, 7: 8th listener) + bool& out_rbSpatialized, ///< Spatialization enabled + AK::SpeakerVolumes::VectorPtr& out_pVolumeOffsets, ///< Per-speaker vector of volume offsets, in decibels. Use the functions of AK::SpeakerVolumes::Vector to interpret it. + AkChannelConfig& out_channelConfig ///< Channel configuration associated with out_rpVolumeOffsets. + ) = 0; + + //@} + + + //////////////////////////////////////////////////////////////////////// + /// @name Game Syncs + //@{ + + /// Get the value of a real-time parameter control (by ID) + /// An RTPC can have a any combination of a global value, a unique value for each game object, or a unique value for each playing ID. + /// The value requested is determined by RTPCValue_type, in_gameObjectID and in_playingID. + /// If a value at the requested scope (determined by RTPCValue_type) is not found, the value that is available at the the next broadest scope will be returned, and io_rValueType will be changed to indicate this. + /// \note + /// When looking up RTPC values via playing ID (ie. io_rValueType is RTPC_PlayingID), in_gameObjectID can be set to a specific game object (if it is available to the caller) to use as a fall back value. + /// If the game object is unknown or unavailable, AK_INVALID_GAME_OBJECT can be passed in in_gameObjectID, and the game object will be looked up via in_playingID. + /// However in this case, it is not possible to retrieve a game object value as a fall back value if the playing id does not exist. It is best to pass in the game object if possible. + /// + /// \return AK_Success if succeeded, AK_IDNotFound if the RTPC does not exist + /// \sa + /// - \ref soundengine_rtpc + /// - RTPCValue_type + virtual AKRESULT GetRTPCValue( + AkRtpcID in_rtpcID, ///< ID of the RTPC + AkGameObjectID in_gameObjectID, ///< Associated game object ID, ignored if io_rValueType is RTPCValue_Global. + AkPlayingID in_playingID, ///< Associated playing ID, ignored if io_rValueType is not RTPC_PlayingID. + AkRtpcValue& out_rValue, ///< Value returned + AK::SoundEngine::Query::RTPCValue_type& io_rValueType ///< In/Out value, the user must specify the requested type. The function will return in this variable the type of the returned value. + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + + /// Get the value of a real-time parameter control (by ID) + /// An RTPC can have a any combination of a global value, a unique value for each game object, or a unique value for each playing ID. + /// The value requested is determined by RTPCValue_type, in_gameObjectID and in_playingID. + /// If a value at the requested scope (determined by RTPCValue_type) is not found, the value that is available at the the next broadest scope will be returned, and io_rValueType will be changed to indicate this. + /// \note + /// When looking up RTPC values via playing ID (ie. io_rValueType is RTPC_PlayingID), in_gameObjectID can be set to a specific game object (if it is available to the caller) to use as a fall back value. + /// If the game object is unknown or unavailable, AK_INVALID_GAME_OBJECT can be passed in in_gameObjectID, and the game object will be looked up via in_playingID. + /// However in this case, it is not possible to retrieve a game object value as a fall back value if the playing id does not exist. It is best to pass in the game object if possible. + /// + /// \return AK_Success if succeeded, AK_IDNotFound if the RTPC does not exist + /// \sa + /// - \ref soundengine_rtpc + /// - RTPCValue_type + virtual AKRESULT GetRTPCValue( + const wchar_t* in_pszRtpcName, ///< String name of the RTPC + AkGameObjectID in_gameObjectID, ///< Associated game object ID, ignored if io_rValueType is RTPCValue_Global. + AkPlayingID in_playingID, ///< Associated playing ID, ignored if io_rValueType is not RTPC_PlayingID. + AkRtpcValue& out_rValue, ///< Value returned + AK::SoundEngine::Query::RTPCValue_type& io_rValueType ///< In/Out value, the user must specify the requested type. The function will return in this variable the type of the returned value. ) = 0; + ) = 0; + +#endif //AK_SUPPORT_WCHAR + + /// Get the value of a real-time parameter control (by ID) + /// An RTPC can have a any combination of a global value, a unique value for each game object, or a unique value for each playing ID. + /// The value requested is determined by RTPCValue_type, in_gameObjectID and in_playingID. + /// If a value at the requested scope (determined by RTPCValue_type) is not found, the value that is available at the the next broadest scope will be returned, and io_rValueType will be changed to indicate this. + /// \note + /// When looking up RTPC values via playing ID (ie. io_rValueType is RTPC_PlayingID), in_gameObjectID can be set to a specific game object (if it is available to the caller) to use as a fall back value. + /// If the game object is unknown or unavailable, AK_INVALID_GAME_OBJECT can be passed in in_gameObjectID, and the game object will be looked up via in_playingID. + /// However in this case, it is not possible to retrieve a game object value as a fall back value if the playing id does not exist. It is best to pass in the game object if possible. + /// + /// \return AK_Success if succeeded, AK_IDNotFound if the RTPC does not exist + /// \sa + /// - \ref soundengine_rtpc + /// - RTPCValue_type + virtual AKRESULT GetRTPCValue( + const char* in_pszRtpcName, ///< String name of the RTPC + AkGameObjectID in_gameObjectID, ///< Associated game object ID, ignored if io_rValueType is RTPCValue_Global. + AkPlayingID in_playingID, ///< Associated playing ID, ignored if io_rValueType is not RTPC_PlayingID. + AkRtpcValue& out_rValue, ///< Value returned + AK::SoundEngine::Query::RTPCValue_type& io_rValueType ///< In/Out value, the user must specify the requested type. The function will return in this variable the type of the returned value. ) = 0; + ) = 0; + + /// Get the state of a switch group (by IDs). + /// \return AK_Success if succeeded, or AK_IDNotFound if the game object was not registered + /// \sa + /// - \ref soundengine_switch + virtual AKRESULT GetSwitch( + AkSwitchGroupID in_switchGroup, ///< ID of the switch group + AkGameObjectID in_gameObjectID, ///< Associated game object ID + AkSwitchStateID& out_rSwitchState ///< ID of the switch + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Get the state of a switch group. + /// \return AK_Success if succeeded, or AK_IDNotFound if the game object was not registered or the switch group name can not be found + /// \sa + /// - \ref soundengine_switch + virtual AKRESULT GetSwitch( + const wchar_t* in_pstrSwitchGroupName, ///< String name of the switch group + AkGameObjectID in_GameObj, ///< Associated game object ID + AkSwitchStateID& out_rSwitchState ///< ID of the switch + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Get the state of a switch group. + /// \return AK_Success if succeeded, or AK_IDNotFound if the game object was not registered or the switch group name can not be found + /// \sa + /// - \ref soundengine_switch + virtual AKRESULT GetSwitch( + const char* in_pstrSwitchGroupName, ///< String name of the switch group + AkGameObjectID in_GameObj, ///< Associated game object ID + AkSwitchStateID& out_rSwitchState ///< ID of the switch + ) = 0; + + /// Get the state of a state group (by IDs). + /// \return AK_Success if succeeded + /// \sa + /// - \ref soundengine_states + virtual AKRESULT GetState( + AkStateGroupID in_stateGroup, ///< ID of the state group + AkStateID& out_rState ///< ID of the state + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Get the state of a state group. + /// \return AK_Success if succeeded, or AK_IDNotFound if the state group name can not be found + /// \sa + /// - \ref soundengine_states + virtual AKRESULT GetState( + const wchar_t* in_pstrStateGroupName, ///< String name of the state group + AkStateID& out_rState ///< ID of the state + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Get the state of a state group. + /// \return AK_Success if succeeded, or AK_IDNotFound if the state group name can not be found + /// \sa + /// - \ref soundengine_states + virtual AKRESULT GetState( + const char* in_pstrStateGroupName, ///< String name of the state group + AkStateID& out_rState ///< ID of the state + ) = 0; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Environments + //@{ + + /// Get the environmental ratios used by the specified game object. + /// To clear the game object's environments, in_uNumEnvValues must be 0. + /// \sa + /// - \ref soundengine_environments + /// - \ref soundengine_environments_dynamic_aux_bus_routing + /// - \ref soundengine_environments_id_vs_string + /// \return AK_Success if succeeded, or AK_InvalidParameter if io_ruNumEnvValues is 0 or out_paEnvironmentValues is NULL, or AK_PartialSuccess if more environments exist than io_ruNumEnvValues + /// AK_InvalidParameter + virtual AKRESULT GetGameObjectAuxSendValues( + AkGameObjectID in_gameObjectID, ///< Associated game object ID + AkAuxSendValue* out_paAuxSendValues, ///< Variable-size array of AkAuxSendValue structures + ///< (it may be NULL if no aux send must be set) + AkUInt32& io_ruNumSendValues ///< The number of Auxilliary busses at the pointer's address + ///< (it must be 0 if no aux bus is set) + ) = 0; + + /// Get the environmental dry level to be used for the specified game object + /// The control value is a number ranging from 0.0f to 1.0f. + /// 0.0f stands for 0% dry, while 1.0f stands for 100% dry. + /// \aknote Reducing the dry level does not mean increasing the wet level. \endaknote + /// \sa + /// - \ref soundengine_environments + /// - \ref soundengine_environments_setting_dry_environment + /// - \ref soundengine_environments_id_vs_string + /// \return AK_Success if succeeded, or AK_IDNotFound if the game object was not registered + virtual AKRESULT GetGameObjectDryLevelValue( + AkGameObjectID in_EmitterID, ///< Associated emitter game object ID + AkGameObjectID in_ListenerID, ///< Associated listener game object ID + AkReal32& out_rfControlValue ///< Dry level control value, ranging from 0.0f to 1.0f + ///< (0.0f stands for 0% dry, while 1.0f stands for 100% dry) + ) = 0; + + /// Get a game object's obstruction and occlusion levels. + /// \sa + /// - \ref soundengine_obsocc + /// - \ref soundengine_environments + /// \return AK_Success if succeeded, AK_IDNotFound if the game object was not registered + virtual AKRESULT GetObjectObstructionAndOcclusion( + AkGameObjectID in_EmitterID, ///< Associated game object ID + AkGameObjectID in_ListenerID, ///< Listener object ID + AkReal32& out_rfObstructionLevel, ///< ObstructionLevel: [0.0f..1.0f] + AkReal32& out_rfOcclusionLevel ///< OcclusionLevel: [0.0f..1.0f] + ) = 0; + + //@} + + /// Get the list of audio object IDs associated to an event. + /// \aknote It is possible to call QueryAudioObjectIDs with io_ruNumItems = 0 to get the total size of the + /// structure that should be allocated for out_aObjectInfos. \endaknote + /// \return AK_Success if succeeded, AK_IDNotFound if the eventID cannot be found, AK_InvalidParameter if out_aObjectInfos is NULL while io_ruNumItems > 0 + /// or AK_PartialSuccess if io_ruNumItems was set to 0 to query the number of available items. + virtual AKRESULT QueryAudioObjectIDs( + AkUniqueID in_eventID, ///< Event ID + AkUInt32& io_ruNumItems, ///< Number of items in array provided / Number of items filled in array + AkObjectInfo* out_aObjectInfos ///< Array of AkObjectInfo items to fill + ) = 0; + +#ifdef AK_SUPPORT_WCHAR + /// Get the list of audio object IDs associated to a event name. + /// \aknote It is possible to call QueryAudioObjectIDs with io_ruNumItems = 0 to get the total size of the + /// structure that should be allocated for out_aObjectInfos. \endaknote + /// \return AK_Success if succeeded, AK_IDNotFound if the event name cannot be found, AK_InvalidParameter if out_aObjectInfos is NULL while io_ruNumItems > 0 + /// or AK_PartialSuccess if io_ruNumItems was set to 0 to query the number of available items. + virtual AKRESULT QueryAudioObjectIDs( + const wchar_t* in_pszEventName, ///< Event name + AkUInt32& io_ruNumItems, ///< Number of items in array provided / Number of items filled in array + AkObjectInfo* out_aObjectInfos ///< Array of AkObjectInfo items to fill + ) = 0; +#endif //AK_SUPPORT_WCHAR + + /// Get the list of audio object IDs associated to an event name. + /// \aknote It is possible to call QueryAudioObjectIDs with io_ruNumItems = 0 to get the total size of the + /// structure that should be allocated for out_aObjectInfos. \endaknote + /// \return AK_Success if succeeded, AK_IDNotFound if the event name cannot be found, AK_InvalidParameter if out_aObjectInfos is NULL while io_ruNumItems > 0 + /// or AK_PartialSuccess if io_ruNumItems was set to 0 to query the number of available items. + virtual AKRESULT QueryAudioObjectIDs( + const char* in_pszEventName, ///< Event name + AkUInt32& io_ruNumItems, ///< Number of items in array provided / Number of items filled in array + AkObjectInfo* out_aObjectInfos ///< Array of AkObjectInfo items to fill + ) = 0; + + /// Get positioning information associated to an audio object. + /// \return AK_Success if succeeded, AK_IDNotFound if the object ID cannot be found, AK_NotCompatible if the audio object cannot expose positioning + virtual AKRESULT GetPositioningInfo( + AkUniqueID in_ObjectID, ///< Audio object ID + AkPositioningInfo& out_rPositioningInfo ///< Positioning information structure to be filled + ) = 0; + + using FAkGameObjectsList = TArray; + + /// Fill the provided list with all the game object IDs that are currently active in the sound engine. + /// The function may be used to avoid updating game objects positions that are not required at the moment. + /// After calling this function, the list will contain the list of all game objects that are currently active in the sound engine. + /// Being active means that either a sound is playing or pending to be played using this game object. + /// \sa + /// - AkGameObjectsList + virtual AKRESULT GetActiveGameObjects( + FAkGameObjectsList& io_GameObjectList ///< returned list of active game objects. + ) = 0; + + /// Query if the specified game object is currently active. + /// Being active means that either a sound is playing or pending to be played using this game object. + virtual bool GetIsGameObjectActive( + AkGameObjectID in_GameObjId ///< Game object ID + ) = 0; + + using FAkRadiusList = TArray; + + /// Returns the maximum distance used in attenuations associated to all sounds currently playing. + /// This may be used for example by the game to know if some processing need to be performed on the game side, that would not be required + /// if the object is out of reach anyway. + /// + /// Example usage: + /// \code + /// /*******************************************************/ + /// AkRadiusList RadLst; //creating the list( array ). + /// // Do not reserve any size for the array, + /// // the system will reserve the correct size. + /// + /// GetMaxRadius( RadLst ) = 0; + /// // Use the content of the list + /// (...) + /// + /// RadLst.Term() = 0;// the user is responsible to free the memory allocated + /// /*******************************************************/ + /// \endcode + /// + /// \aknote The returned value is NOT the distance from a listener to an object but + /// the maximum attenuation distance of all sounds playing on this object. This is + /// not related in any way to the curent 3D position of the object. \endaknote + /// + /// \return + /// - AK_Success if succeeded + /// - AK_InsuficientMemory if there was not enough memory + /// + /// \aknote + /// The Scaling factor (if one was specified on the game object) is included in the return value. + /// The Scaling factor is not updated once a sound starts playing since it + /// is computed only when the playback starts with the initial scaling factor of this game object. Scaling factor will + /// be re-computed for every playback instance, always using the scaling factor available at this time. + /// \endaknote + /// + /// \sa + /// - AkRadiusList + virtual AKRESULT GetMaxRadius( + FAkRadiusList& io_RadiusList ///< List that will be filled with AK::SoundEngine::Query::GameObjDst objects. + ) = 0; + + /// Returns the maximum distance used in attenuations associated to sounds playing using the specified game object. + /// This may be used for example by the game to know if some processing need to be performed on the game side, that would not be required + /// if the object is out of reach anyway. + /// + /// \aknote The returned value is NOT the distance from a listener to an object but the maximum attenuation distance of all sounds playing on this object. \endaknote + /// + /// \return + /// - A negative number if the game object specified is not playing. + /// - 0, if the game object was only associated to sounds playing using no distance attenuation. + /// - A positive number represents the maximum of all the distance attenuations playing on this game object. + /// + /// \aknote + /// The Scaling factor (if one was specified on the game object) is included in the return value. + /// The Scaling factor is not updated once a sound starts playing since it + /// is computed only when the playback starts with the initial scaling factor of this game object. Scaling factor will + /// be re-computed for every playback instance, always using the scaling factor available at this time. + /// \endaknote + /// + /// \sa + /// - \ref AK::SoundEngine::SetScalingFactor + virtual AkReal32 GetMaxRadius( + AkGameObjectID in_GameObjId ///< Game object ID + ) = 0; + + /// Get the Event ID associated to the specified PlayingID. + /// This function does not acquire the main audio lock. + /// + /// \return AK_INVALID_UNIQUE_ID on failure. + virtual AkUniqueID GetEventIDFromPlayingID( + AkPlayingID in_playingID ///< Associated PlayingID + ) = 0; + + /// Get the ObjectID associated to the specified PlayingID. + /// This function does not acquire the main audio lock. + /// + /// \return AK_INVALID_GAME_OBJECT on failure. + virtual AkGameObjectID GetGameObjectFromPlayingID( + AkPlayingID in_playingID ///< Associated PlayingID + ) = 0; + + /// Get the list PlayingIDs associated with the given game object. + /// This function does not acquire the main audio lock. + /// + /// \aknote It is possible to call GetPlayingIDsFromGameObject with io_ruNumItems = 0 to get the total size of the + /// structure that should be allocated for out_aPlayingIDs. \endaknote + /// \return AK_Success if succeeded, AK_InvalidParameter if out_aPlayingIDs is NULL while io_ruNumItems > 0 + virtual AKRESULT GetPlayingIDsFromGameObject( + AkGameObjectID in_GameObjId, ///< Game object ID + AkUInt32& io_ruNumIDs, ///< Number of items in array provided / Number of items filled in array + AkPlayingID* out_aPlayingIDs ///< Array of AkPlayingID items to fill + ) = 0; + + /// Get the value of a custom property of integer or boolean type. + /// \return AK_PartialSuccess if the object was found but no matching custom property was found on this object. Note that it could mean this value is the default value. + virtual AKRESULT GetCustomPropertyValue( + AkUniqueID in_ObjectID, ///< Object ID, this is the 32bit ShortID of the AudioFileSource or Sound object found in the .wwu XML file. At runtime it can only be retrieved by the AK_Duration callback when registered with PostEvent(), or by calling Query::QueryAudioObjectIDs() to get all the shortIDs associated with an event. + AkUInt32 in_uPropID, ///< Property ID of your custom property found under the Custom Properties tab of the Wwise project settings. + AkInt32& out_iValue ///< Property Value + ) = 0; + + /// Get the value of a custom property of real type. + /// \return AK_PartialSuccess if the object was found but no matching custom property was found on this object. Note that it could mean this value is the default value. + virtual AKRESULT GetCustomPropertyValue( + AkUniqueID in_ObjectID, ///< Object ID, this is the 32bit ShortID of the AudioFileSource or Sound object found in the .wwu XML file. At runtime it can only be retrieved by the AK_Duration callback when registered with PostEvent(), or by calling Query::QueryAudioObjectIDs() to get all the shortIDs associated with an event. + AkUInt32 in_uPropID, ///< Property ID of your custom property found under the Custom Properties tab of the Wwise project settings. + AkReal32& out_fValue ///< Property Value + ) = 0; + }* const Query; + + class IAudioInputPlugin + { + public: + UE_NONCOPYABLE(IAudioInputPlugin); + + protected: + IAudioInputPlugin() = default; + + public: + virtual ~IAudioInputPlugin() {} + + virtual void SetAudioInputCallbacks( + AkAudioInputPluginExecuteCallbackFunc in_pfnExecCallback, + AkAudioInputPluginGetFormatCallbackFunc in_pfnGetFormatCallback = nullptr, // Optional + AkAudioInputPluginGetGainCallbackFunc in_pfnGetGainCallback = nullptr // Optional + ) = 0; + }* const AudioInputPlugin; + +#if WITH_EDITORONLY_DATA + class TagInformationBridge : private AkErrorMessageTranslator + { + public: + using Type = TagInformation; + }; +#else + class TagInformationBridge + { + public: + using Type = nullptr_t; + }; +#endif + + using FGetInfoErrorMessageTranslatorFunction = TFunction; + virtual AkErrorMessageTranslator* NewErrorMessageTranslator(FGetInfoErrorMessageTranslatorFunction InMessageTranslatorFunction) = 0; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwiseSpatialAudioAPI.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwiseSpatialAudioAPI.h new file mode 100644 index 0000000..2ad9ebf --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwiseSpatialAudioAPI.h @@ -0,0 +1,449 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "AkInclude.h" + +#include "Wwise/WwiseSoundEngineModule.h" + +class IWwiseSpatialAudioAPI +{ +public: + static IWwiseSpatialAudioAPI* Get() + { + IWwiseSoundEngineModule::ForceLoadModule(); + return IWwiseSoundEngineModule::SpatialAudio; + } + + class IReverbEstimation; + UE_NONCOPYABLE(IWwiseSpatialAudioAPI); + +protected: + IWwiseSpatialAudioAPI(IReverbEstimation* InReverbEstimation) : + ReverbEstimation(InReverbEstimation) + {} +public: + virtual ~IWwiseSpatialAudioAPI() + { + delete ReverbEstimation; + } + + //////////////////////////////////////////////////////////////////////// + /// @name Basic functions. + /// In order to use SpatialAudio, you need to initalize it using Init, and register the listeners that you plan on using with any of the services offered by SpatialAudio, using + /// RegisterListener respectively, _after_ having registered their corresponding game object to the sound engine. + /// \akwarning At the moment, there can be only one Spatial Audio listener registered at any given time. + //@{ + + /// Initialize the SpatialAudio API. + virtual AKRESULT Init(const AkSpatialAudioInitSettings& in_initSettings) = 0; + + /// Assign a game object as the Spatial Audio listener. There can be only one Spatial Audio listener registered at any given time; in_gameObjectID will replace any previously set Spatial Audio listener. + /// The game object passed in must be registered by the client, at some point, for sound to be heard. It is not necessary to be registered at the time of calling this function. + /// If not listener is explicitly registered to spatial audio, then a default listener (set via \c AK::SoundEngine::SetDefaultListeners()) is selected. If the are no default listeners, or there are more than one + /// default listeners, then it is necessary to call RegisterListener() to specify which listener to use with Spatial Audio. + virtual AKRESULT RegisterListener( + AkGameObjectID in_gameObjectID ///< Game object ID + ) = 0; + + /// Unregister a game object as a listener in the SpatialAudio API; clean up Spatial Audio listener data associated with in_gameObjectID. + /// If in_gameObjectID is the current registered listener, calling this function will clear the Spatial Audio listener and + /// Spatial Audio features will be disabled until another listener is registered. + /// This function is optional - listener are automatically unregistered when their game object is deleted in the sound engine. + /// \sa + /// - \ref AK::SpatialAudio::RegisterListener + virtual AKRESULT UnregisterListener( + AkGameObjectID in_gameObjectID ///< Game object ID + ) = 0; + + /// Define a inner and outer radius around each sound position for a specified game object. + /// The radii are used in spread and distance calculations, simulating a radial sound source. + /// When applying attenuation curves, the distance between the listener and the inner sphere (defined by the sound position and \c in_innerRadius) is used. + /// The spread for each sound position is calculated as follows: + /// - If the listener is outside the outer radius, then the spread is defined by the area that the sphere takes in the listener field of view. Specifically, this angle is calculated as 2.0*asinf( \c in_outerRadius / distance ), where distance is the distance between the listener and the sound position. + /// - When the listener intersects the outer radius (the listener is exactly \c in_outerRadius units away from the sound position), the spread is exactly 50%. + /// - When the listener is in between the inner and outer radius, the spread interpolates linearly from 50% to 100% as the listener transitions from the outer radius towards the inner radius. + /// - If the listener is inside the inner radius, the spread is 100%. + /// \aknote Transmission and diffraction calculations in Spatial Audio always use the center of the sphere (the position(s) passed into \c AK::SoundEngine::SetPosition or \c AK::SoundEngine::SetMultiplePositions) for raycasting. + /// To obtain accurate diffraction and transmission calculations for radial sources, where different parts of the volume may take different paths through or around geometry, + /// it is necessary to pass multiple sound positions into \c AK::SoundEngine::SetMultiplePositions to allow the engine to 'sample' the area at different points. + /// - \ref AK::SoundEngine::SetPosition + /// - \ref AK::SoundEngine::SetMultiplePositions + virtual AKRESULT SetGameObjectRadius( + AkGameObjectID in_gameObjectID, ///< Game object ID + AkReal32 in_outerRadius, ///< Outer radius around each sound position, defining 50% spread. Must satisfy \c in_innerRadius <= \c in outerRadius. + AkReal32 in_innerRadius ///< Inner radius around each sound position, defining 100% spread and 0 attenuation distance. Must satisfy \c in_innerRadius <= \c in outerRadius. + ) = 0; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Helper functions for passing game data to the Reflect plug-in. + /// Use this API for detailed placement of reflection image sources. + /// \aknote These functions are low-level and useful when your game engine already implements a geometrical approach to sound propagation such as an image-source or a ray tracing algorithm. + /// Functions of Geometry are preferred and easier to use with the Reflect plug-in. \endaknote + //@{ + + /// Add or update an individual image source for processing via the AkReflect plug-in. Use this API for detailed placement of + /// reflection image sources, whose positions have been determined by the client, such as from the results of a ray cast, computation or by manual placement. One possible + /// use case is generating reflections that originate far enough away that they can be modeled as a static point source, for example, off of a distant mountain. + /// The SpatialAudio API manages image sources added via SetImageSource() and sends them to the AkReflect plug-in that is on the aux bus with ID \c in_AuxBusID. + /// The image source applies all game objects that have a reflections aux send defined in the authoring tool, or only to a specific game object if \c in_gameObjectID is used. + /// \aknote The \c AkImageSourceSettings struct passed in \c in_info must contain a unique image source ID to be able to identify this image source across frames and when updating and/or removing it later. + /// Each instance of AkReflect has its own set of data, so you may reuse ID, if desired, as long as \c in_gameObjectID and \c in_AuxBusID are different. + /// \aknote It is possible for the AkReflect plugin to process reflections from both \c SetImageSource and the geometric reflections API on the same aux bus and game object, but be aware that image source ID collisions are possible. + /// The image source IDs used by the geometric reflections API are generated from hashed data that uniquely identifies the reflecting surfaces. If a collision occurs, one of the reflections will not be heard. + /// While collision are rare, to ensure that it never occurs use an aux bus for \c SetImageSource that is unique from the aux bus(es) defined in the authoring tool, and from those passed to \c SetEarlyReflectionsAuxSend. + /// \endaknote + /// \aknote For proper operation with AkReflect and the SpatialAudio API, any aux bus using AkReflect should have 'Listener Relative Routing' checked and the 3D Spatialization set to None in the Wwise authoring tool. See \ref spatial_audio_wwiseprojectsetup_businstances for more details. \endaknote + /// \sa + /// - \ref AK::SpatialAudio::RemoveImageSource + /// - \ref AK::SpatialAudio::ClearImageSources + /// - \ref AK::SpatialAudio::SetGameObjectInRoom + /// - \ref AK::SpatialAudio::SetEarlyReflectionsAuxSend + virtual AKRESULT SetImageSource( + AkImageSourceID in_srcID, ///< The ID of the image source being added. + const AkImageSourceSettings& in_info, ///< Image source information. + const char* in_name, ///< Name given to image source, can be used to identify the image source in the AK Reflect plugin UI. + AkUniqueID in_AuxBusID = AK_INVALID_AUX_ID, ///< Aux bus that has the AkReflect plug in for early reflection DSP. + ///< Pass AK_INVALID_AUX_ID to use the reflections aux bus defined in the authoring tool. + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT ///< The ID of the emitter game object to which the image source applies. + ///< Pass AK_INVALID_GAME_OBJECT to apply to all game objects that have a reflections aux bus assigned in the authoring tool. + ) = 0; + + /// Remove an individual reflection image source that was previously added via \c SetImageSource. + /// \sa + /// - \ref AK::SpatialAudio::SetImageSource + /// - \ref AK::SpatialAudio::ClearImageSources + virtual AKRESULT RemoveImageSource( + AkImageSourceID in_srcID, ///< The ID of the image source to remove. + AkUniqueID in_AuxBusID = AK_INVALID_AUX_ID, ///< Aux bus that was passed to SetImageSource. + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT ///< Game object ID that was passed to SetImageSource. + ) = 0; + + /// Remove all image sources matching \c in_AuxBusID and \c in_gameObjectID that were previously added via \c SetImageSource. + /// Both \c in_AuxBusID and \c in_gameObjectID can be treated as wild cards matching all aux buses and/or all game object, by passing \c AK_INVALID_AUX_ID and/or \c AK_INVALID_GAME_OBJECT, respectively. + /// \sa + /// - \ref AK::SpatialAudio::SetImageSource + /// - \ref AK::SpatialAudio::RemoveImageSource + virtual AKRESULT ClearImageSources( + AkUniqueID in_AuxBusID = AK_INVALID_AUX_ID, ///< Aux bus that was passed to SetImageSource, or AK_INVALID_AUX_ID to match all aux buses. + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT ///< Game object ID that was passed to SetImageSource, or AK_INVALID_GAME_OBJECT to match all game objects. + ) = 0; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Geometry + /// Geometry API for early reflection processing using Reflect. + //@{ + + /// Add or update a set of geometry from the \c SpatialAudio module for geometric reflection and diffraction processing. A geometry set is a logical set of vertices, triangles, and acoustic surfaces, + /// which are referenced by the same \c AkGeometrySetID. The ID (\c in_GeomSetID) must be unique and is also chosen by the client in a manner similar to \c AkGameObjectID's. + /// It is necessary to create at least one geometry instance for each geometry set that is to be used for diffraction and reflection simulation. + /// \sa + /// - \ref AkGeometryParams + /// - \ref AK::SpatialAudio::SetGeometryInstance + /// - \ref AK::SpatialAudio::RemoveGeometry + virtual AKRESULT SetGeometry( + AkGeometrySetID in_GeomSetID, ///< Unique geometry set ID, chosen by client. + const AkGeometryParams& in_params ///< Geometry parameters to set. + ) = 0; + + /// Remove a set of geometry to the SpatialAudio API. + /// Calling \c AK::SpatialAudio::RemoveGeometry will remove all instances of the geometry from the scene. + /// \sa + /// - \ref AK::SpatialAudio::SetGeometry + virtual AKRESULT RemoveGeometry( + AkGeometrySetID in_SetID ///< ID of geometry set to be removed. + ) = 0; + + /// Add or update a geometry instance from the \c SpatialAudio module for geometric reflection and diffraction processing. + /// A geometry instance is a unique instance of a geometry set with a specified transform (position, rotation and scale). + /// It is necessary to create at least one geometry instance for each geometry set that is to be used for diffraction and reflection simulation. + /// The ID (\c in_GeomSetInstanceID) must be unique amongst all geometry instances, including geometry instances referencing different geometry sets. The ID is chosen by the client in a manner similar to \c AkGameObjectID's. + /// To update the transform of an existing geometry instance, call SetGeometryInstance again, passing the same \c AkGeometryInstanceID, with the updated transform. + /// \sa + /// - \ref AkGeometryInstanceParams + /// - \ref AK::SpatialAudio::RemoveGeometryInstance + virtual AKRESULT SetGeometryInstance( + AkGeometryInstanceID in_GeometryInstanceID, ///< Unique geometry set instance ID, chosen by client. + const AkGeometryInstanceParams& in_params ///< Geometry instance parameters to set. + ) = 0; + + /// Remove a geometry instance from the SpatialAudio API. + /// \sa + /// - \ref AK::SpatialAudio::SetGeometryInstance + virtual AKRESULT RemoveGeometryInstance( + AkGeometryInstanceID in_GeometryInstanceID ///< ID of geometry set instance to be removed. + ) = 0; + + /// Query information about the reflection paths that have been calculated via geometric reflection processing in the SpatialAudio API. This function can be used for debugging purposes. + /// This function must acquire the global sound engine lock and therefore, may block waiting for the lock. + /// \sa + /// - \ref AkReflectionPathInfo + virtual AKRESULT QueryReflectionPaths( + AkGameObjectID in_gameObjectID, ///< The ID of the game object that the client wishes to query. + AkUInt32 in_positionIndex, ///< The index of the associated game object position. + AkVector64& out_listenerPos, ///< Returns the position of the listener game object that is associated with the game object \c in_gameObjectID. + AkVector64& out_emitterPos, ///< Returns the position of the emitter game object \c in_gameObjectID. + AkReflectionPathInfo* out_aPaths, ///< Pointer to an array of \c AkReflectionPathInfo's which will be filled after returning. + AkUInt32& io_uArraySize ///< The number of slots in \c out_aPaths, after returning the number of valid elements written. + ) = 0; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Rooms and Portals + /// Sound Propagation API using rooms and portals. + //@{ + + /// Add or update a room. Rooms are used to connect portals and define an orientation for oriented reverbs. This function may be called multiple times with the same ID to update the parameters of the room. + /// \akwarning The ID (\c in_RoomID) must be chosen in the same manner as \c AkGameObjectID's, as they are in the same ID-space. The spatial audio lib manages the + /// registration/unregistration of internal game objects for rooms that use these IDs and, therefore, must not collide. + /// Also, the room ID must not be in the reserved range (AkUInt64)(-32) to (AkUInt64)(-2) inclusively. You may, however, explicitly add the default room ID AK::SpatialAudio::kOutdoorRoomID (-1) + /// in order to customize its AkRoomParams, to provide a valid auxiliary bus, for example.\endakwarning + /// \sa + /// - \ref AkRoomID + /// - \ref AkRoomParams + /// - \ref AK::SpatialAudio::RemoveRoom + virtual AKRESULT SetRoom( + AkRoomID in_RoomID, ///< Unique room ID, chosen by the client. + const AkRoomParams& in_Params, ///< Parameter for the room. + const char* in_RoomName = nullptr ///< Name used to identify the room (optional) + ) = 0; + + /// Remove a room. + /// \sa + /// - \ref AkRoomID + /// - \ref AK::SpatialAudio::SetRoom + virtual AKRESULT RemoveRoom( + AkRoomID in_RoomID ///< Room ID that was passed to \c SetRoom. + ) = 0; + + /// Add or update an acoustic portal. A portal is an opening that connects two or more rooms to simulate the transmission of reverberated (indirect) sound between the rooms. + /// This function may be called multiple times with the same ID to update the parameters of the portal. The ID (\c in_PortalID) must be chosen in the same manner as \c AkGameObjectID's, + /// as they are in the same ID-space. The spatial audio lib manages the registration/unregistration of internal game objects for portals that use these IDs, and therefore must not collide. + /// \sa + /// - \ref AkPortalID + /// - \ref AkPortalParams + /// - \ref AK::SpatialAudio::RemovePortal + virtual AKRESULT SetPortal( + AkPortalID in_PortalID, ///< Unique portal ID, chosen by the client. + const AkPortalParams& in_Params, ///< Parameter for the portal. + const char* in_PortalName = nullptr ///< Name used to identify portal (optional) + ) = 0; + + /// Remove a portal. + /// \sa + /// - \ref AkPortalID + /// - \ref AK::SpatialAudio::SetPortal + virtual AKRESULT RemovePortal( + AkPortalID in_PortalID ///< ID of portal to be removed, which was originally passed to SetPortal. + ) = 0; + + /// Set the room that the game object is currently located in - usually the result of a containment test performed by the client. The room must have been registered with \c SetRoom. + /// Setting the room for a game object provides the basis for the sound propagation service, and also sets which room's reverb aux bus to send to. The sound propagation service traces the path + /// of the sound from the emitter to the listener, and calculates the diffraction as the sound passes through each portal. The portals are used to define the spatial location of the diffracted and reverberated + /// audio. + /// \sa + /// - \ref AK::SpatialAudio::SetRoom + /// - \ref AK::SpatialAudio::RemoveRoom + virtual AKRESULT SetGameObjectInRoom( + AkGameObjectID in_gameObjectID, ///< Game object ID + AkRoomID in_CurrentRoomID ///< RoomID that was passed to \c AK::SpatialAudio::SetRoom + ) = 0; + + /// Set the early reflections order for reflection calculation. The reflections order indicates the number of times sound can bounce off of a surface. + /// A higher number requires more CPU resources but results in denser early reflections. Set to 0 to globally disable reflections processing. + virtual AKRESULT SetReflectionsOrder( + AkUInt32 in_uReflectionsOrder, ///< Number of reflections to calculate. Valid range [0,4] + bool in_bUpdatePaths ///< Set to true to clear existing higher-order paths and to force the re-computation of new paths. If false, existing paths will remain and new paths will be computed when the emitter or listener moves. + ) = 0; + + /// Set the diffraction order for geometric path calculation. The diffraction order indicates the number of edges a sound can diffract around. + /// A higher number requires more CPU resources but results in paths found around more complex geometry. Set to 0 to globally disable geometric diffraction processing. + /// \sa + /// - \ref AkSpatialAudioInitSettings::uMaxDiffractionOrder + virtual AKRESULT SetDiffractionOrder( + AkUInt32 in_uDiffractionOrder, ///< Number of diffraction edges to consider in path calculations. Valid range [0,8] + bool in_bUpdatePaths ///< Set to true to clear existing diffraction paths and to force the re-computation of new paths. If false, existing paths will remain and new paths will be computed when the emitter or listener moves. + ) = 0; + + /// Set the number of rays cast from the listener by the stochastic ray casting engine. + /// A higher number requires more CPU resources but provides more accurate results. Default value (100) should be good for most applications. + /// + virtual AKRESULT SetNumberOfPrimaryRays( + AkUInt32 in_uNbPrimaryRays ///< Number of rays cast from the listener + ) = 0; + + /// Set the number of frames on which the path validation phase will be spread. Value between [1..[ + /// High values delay the validation of paths. A value of 1 indicates no spread at all. + /// + virtual AKRESULT SetLoadBalancingSpread( + AkUInt32 in_uNbFrames ///< Number of spread frames + ) = 0; + + /// Set an early reflections auxiliary bus for a particular game object. + /// Geometrical reflection calculation inside spatial audio is enabled for a game object if any sound playing on the game object has a valid early reflections aux bus specified in the authoring tool, + /// or if an aux bus is specified via \c SetEarlyReflectionsAuxSend. + /// The \c in_auxBusID parameter of SetEarlyReflectionsAuxSend applies to sounds playing on the game object \c in_gameObjectID which have not specified an early reflection bus in the authoring tool - + /// the parameter specified on individual sounds' reflection bus takes priority over the value passed in to \c SetEarlyReflectionsAuxSend. + /// \aknote + /// Users may apply this function to avoid duplicating sounds in the actor-mixer hierarchy solely for the sake of specifying a unique early reflection bus, or in any situation where the same + /// sound should be played on different game objects with different early reflection aux buses (the early reflection bus must be left blank in the authoring tool if the user intends to specify it through the API). \endaknote + virtual AKRESULT SetEarlyReflectionsAuxSend( + AkGameObjectID in_gameObjectID, ///< Game object ID + AkAuxBusID in_auxBusID ///< Auxiliary bus ID. Applies only to sounds which have not specified an early reflection bus in the authoring tool. Pass \c AK_INVALID_AUX_ID to set only the send volume. + ) = 0; + + /// Set an early reflections send volume for a particular game object. + /// The \c in_fSendVolume parameter is used to control the volume of the early reflections send. It is combined with the early reflections volume specified in the authoring tool, and is applied to all sounds + /// playing on the game object. + /// Setting \c in_fSendVolume to 0.f will disable all reflection processing for this game object. + virtual AKRESULT SetEarlyReflectionsVolume( + AkGameObjectID in_gameObjectID, ///< Game object ID + AkReal32 in_fSendVolume ///< Send volume (linear) for auxiliary send. Set 0.f to disable reflection processing. Valid range 0.f-1.f. + ) = 0; + + /// Set the obstruction and occlusion value for a portal that has been registered with Spatial Audio. + /// Portal obstruction is used to simulate objects between the portal and the listener that are obstructing the sound coming from the portal. + /// The obstruction value affects only the portals dry path, and should relate to how much of the opening + /// is obstructed, and must be calculated by the client. It is applied to the room's game object, as well as to all the emitters virtual positions + /// which propagate from that room through this portal. + /// Portal occlusion is applied only on the room game object, and affects both the wet and dry path of the signal emitted from the room's bus. + virtual AKRESULT SetPortalObstructionAndOcclusion( + AkPortalID in_PortalID, ///< Portal ID. + AkReal32 in_fObstruction, ///< Obstruction value. Valid range 0.f-1.f + AkReal32 in_fOcclusion ///< Occlusion value. Valid range 0.f-1.f + ) = 0; + + /// Set the obstruction value of the path between a game object and a portal that has been created by Spatial Audio. + /// The obstruction value is applied on one of the path segments of the sound between the emitter and the listener. + /// Diffraction must be enabled on the sound for a diffraction path to be created. + /// Also, there should not be any portals between the provided game object and portal ID parameters. + /// The obstruction value is used to simulate objects between the portal and the game object that are obstructing the sound. + /// Send an obstruction value of 0 to ensure the value is removed from the internal data structure. + virtual AKRESULT SetGameObjectToPortalObstruction( + AkGameObjectID in_gameObjectID, ///< Game object ID + AkPortalID in_PortalID, ///< Portal ID + AkReal32 in_fObstruction ///< Obstruction value. Valid range 0.f-1.f + ) = 0; + + /// Set the obstruction value of the path between two portals that has been created by Spatial Audio. + /// The obstruction value is applied on one of the path segments of the sound between the emitter and the listener. + /// Diffraction must be enabled on the sound for a diffraction path to be created. + /// Also, there should not be any portals between the two provided ID parameters. + /// The obstruction value is used to simulate objects between the portals that are obstructing the sound. + /// Send an obstruction value of 0 to ensure the value is removed from the internal data structure. + virtual AKRESULT SetPortalToPortalObstruction( + AkPortalID in_PortalID0, ///< Portal ID + AkPortalID in_PortalID1, ///< Portal ID + AkReal32 in_fObstruction ///< Obstruction value. Valid range 0.f-1.f + ) = 0; + + /// Query information about the wet diffraction amount for the portal \c in_portal, returned as a normalized value \c out_wetDiffraction in the range [0,1]. + /// The wet diffraction is calculated from how far into the 'shadow region' the listener is from the portal. Unlike dry diffraction, the + /// wet diffraction does not depend on the incident angle, but only the normal of the portal. + /// This value is applied by spatial audio, to the Diffraction value and built-in game parameter of the room game object that is + /// on the other side of the portal (relative to the listener). + /// This function must acquire the global sound engine lock and therefore, may block waiting for the lock. + /// \sa + /// - \ref AkSpatialAudioInitSettings + virtual AKRESULT QueryWetDiffraction( + AkPortalID in_portal, ///< The ID of the game object that the client wishes to query. + AkReal32& out_wetDiffraction ///< The number of slots in \c out_aPaths, after returning the number of valid elements written. + ) = 0; + + /// Query information about the diffraction state for a particular listener and emitter, which has been calculated using the data provided via the spatial audio emitter API. This function can be used for debugging purposes. + /// Returned in \c out_aPaths, this array contains the sound paths calculated from diffraction around a geometric edge and/or diffraction through portals connecting rooms. + /// No paths will be returned in any of the following conditions: (1) the emitter game object has a direct line of sight to the listener game object, (2) the emitter and listener are in the same room, and the listener is completely outside the radius of the emitter, + /// or (3) The emitter and listener are in different rooms, but there are no paths found via portals between the emitter and the listener. + /// A single path with zero diffraction nodes is returned when all of the following conditions are met: (1) the emitter and listener are in the same room, (2) there is no direct line of sight, and (3) either the Voice's Attenuation's curve max distance is exceeded or the accumulated diffraction coefficient exceeds 1.0. + /// This function must acquire the global sound engine lock and, therefore, may block waiting for the lock. + /// \sa + /// - \ref AkDiffractionPathInfo + virtual AKRESULT QueryDiffractionPaths( + AkGameObjectID in_gameObjectID, ///< The ID of the game object that the client wishes to query. + AkUInt32 in_positionIndex, ///< The index of the associated game object position. + AkVector64& out_listenerPos, ///< Returns the position of the listener game object that is associated with the game object \c in_gameObjectID. + AkVector64& out_emitterPos, ///< Returns the position of the emitter game object \c in_gameObjectID. + AkDiffractionPathInfo* out_aPaths, ///< Pointer to an array of \c AkDiffractionPathInfo's which will be filled on return. + AkUInt32& io_uArraySize ///< The number of slots in \c out_aPaths, after returning the number of valid elements written. + ) = 0; + + /// Reset the stochastic engine state by re-initializing the random seeds. + /// + virtual AKRESULT ResetStochasticEngine() = 0; + + //@} + + class IReverbEstimation + { + public: + UE_NONCOPYABLE(IReverbEstimation); + protected: + IReverbEstimation() = default; + public: + virtual ~IReverbEstimation() {} + + //////////////////////////////////////////////////////////////////////// + /// @name Reverb estimation. + /// These functions can be used to estimate the reverb parameters of a physical environment, using its volume and surface area + //@{ + + /// This is used to estimate the line of best fit through the absorption values of an Acoustic Texture. + /// This value is what's known as the HFDamping. + /// return Gradient of line of best fit through y = mx + c. + virtual float CalculateSlope(const AkAcousticTexture& texture) = 0; + + /// Calculate average absorption values using each of the textures in in_textures, weighted by their corresponding surface area. + virtual void GetAverageAbsorptionValues(AkAcousticTexture* in_textures, float* in_surfaceAreas, int in_numTextures, AkAcousticTexture& out_average) = 0; + + /// Estimate the time taken (in seconds) for the sound reverberation in a physical environment to decay by 60 dB. + /// This is estimated using the Sabine equation. The T60 decay time can be used to drive the decay parameter of a reverb effect. + virtual AKRESULT EstimateT60Decay( + AkReal32 in_volumeCubicMeters, ///< The volume (in cubic meters) of the physical environment. 0 volume or negative volume will give a decay estimate of 0. + AkReal32 in_surfaceAreaSquaredMeters, ///< The surface area (in squared meters) of the physical environment. Must be >= AK_SA_MIN_ENVIRONMENT_SURFACE_AREA + AkReal32 in_environmentAverageAbsorption, ///< The average absorption amount of the surfaces in the environment. Must be between AK_SA_MIN_ENVIRONMENT_ABSORPTION and 1. + AkReal32& out_decayEstimate ///< Returns the time taken (in seconds) for the reverberation to decay bu 60 dB. + ) = 0; + + /// Estimate the time taken (in milliseconds) for the first reflection to reach the listener. + /// This assumes the emitter and listener are both positioned in the centre of the environment. + virtual AKRESULT EstimateTimeToFirstReflection( + AkVector in_environmentExtentMeters, ///< Defines the dimensions of the environment (in meters) relative to the center; all components must be positive numbers. + AkReal32& out_timeToFirstReflectionMs, ///< Returns the time taken (in milliseconds) for the first reflection to reach the listener. + AkReal32 in_speedOfSound = 343.0f ///< Defaults to 343.0 - the speed of sound in dry air. Must be > 0. + ) = 0; + + /// Estimate the high frequency damping from a collection of AkAcousticTextures and corresponding surface areas. + /// The high frequency damping is a measure of how much high frequencies are dampened compared to low frequencies. > 0 indicates more high frequency damping than low frequency damping. < 0 indicates more low frequency damping than high frequency damping. 0 indicates uniform damping. + /// The average absorption values are calculated using each of the textures in the collection, weighted by their corresponding surface area. + /// The HFDamping is then calculated as the line-of-best-fit through the average absorption values. + virtual AKRESULT EstimateHFDamping( + AkAcousticTexture* in_textures, ///< A collection of AkAcousticTexture structs from which to calculate the average high frequency damping. + float* in_surfaceAreas, ///< Surface area values for each of the textures in in_textures. + int in_numTextures, ///< The number of textures in in_textures (and the number of surface area values in in_surfaceAreas). + AkReal32& out_hfDamping ///< Returns the high frequency damping value. > 0 indicates more high frequency damping than low frequency damping. < 0 indicates more low frequency damping than high frequency damping. 0 indicates uniform damping. + ) = 0; + + //@} + }* const ReverbEstimation; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwiseStreamMgrAPI.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwiseStreamMgrAPI.h new file mode 100644 index 0000000..49fc78b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/API/WwiseStreamMgrAPI.h @@ -0,0 +1,201 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreMinimal.h" +#include "AkInclude.h" + +#include "Wwise/WwiseSoundEngineModule.h" + +class IWwiseStreamMgrAPI +{ +public: + static IWwiseStreamMgrAPI* Get() + { + IWwiseSoundEngineModule::ForceLoadModule(); + return IWwiseSoundEngineModule::StreamMgr; + } + + static AK::IAkStreamMgr* GetAkStreamMgr() + { + if (auto* WwiseStreamMgr = Get()) + { + return WwiseStreamMgr->GetAkStreamMgrInstance(); + } + return nullptr; + } + + UE_NONCOPYABLE(IWwiseStreamMgrAPI); +protected: + IWwiseStreamMgrAPI() = default; +public: + virtual ~IWwiseStreamMgrAPI() {} + + virtual AK::IAkStreamMgr* GetAkStreamMgrInstance() = 0; + + /// \name Audiokinetic implementation-specific Stream Manager factory. + //@{ + /// Stream Manager factory. + /// \remarks + /// - In order for the Stream Manager to work properly, you also need to create + /// at least one streaming device (and implement its I/O hook), and register the + /// File Location Resolver with AK::StreamMgr::SetFileLocationResolver(). + /// - Use AK::StreamMgr::GetDefaultSettings(), then modify the settings you want, + /// then feed this function with them. + /// \sa + /// - AK::IAkStreamMgr + /// - AK::StreamMgr::SetFileLocationResolver() + /// - AK::StreamMgr::GetDefaultSettings() + virtual AK::IAkStreamMgr* Create( + const AkStreamMgrSettings& in_settings ///< Stream manager initialization settings. + ) = 0; + + /// Get the default values for the Stream Manager's settings. + /// \sa + /// - AK::StreamMgr::Create() + /// - AkStreamMgrSettings + /// - \ref streamingmanager_settings + virtual void GetDefaultSettings( + AkStreamMgrSettings& out_settings ///< Returned AkStreamMgrSettings structure with default values. + ) = 0; + + /// Get the one and only File Location Resolver registered to the Stream Manager. + /// \sa + /// - AK::StreamMgr::IAkFileLocationResolver + /// - AK::StreamMgr::SetFileLocationResolver() + virtual AK::StreamMgr::IAkFileLocationResolver* GetFileLocationResolver() = 0; + + /// Register the one and only File Location Resolver to the Stream Manager. + /// \sa + /// - AK::StreamMgr::IAkFileLocationResolver + virtual void SetFileLocationResolver( + AK::StreamMgr::IAkFileLocationResolver* in_pFileLocationResolver ///< Interface to your File Location Resolver + ) = 0; + + //@} + + /// \name Stream Manager: High-level I/O devices management. + //@{ + /// Streaming device creation. + /// Creates a high-level device, with specific settings. + /// You need to provide the associated low-level I/O hook, implemented on your side. + /// \return The device ID. AK_INVALID_DEVICE_ID if there was an error and it could not be created. + /// \warning + /// - This function is not thread-safe. + /// - Use a blocking hook (IAkIOHookBlocking) with SCHEDULER_BLOCKING devices, and a + /// deferred hook (IAkIOHookDeferredBatch) with SCHEDULER_DEFERRED_LINED_UP devices (these flags are + /// specified in the device settings (AkDeviceSettings). The pointer to IAkLowLevelIOHook is + /// statically cast internally into one of these hooks. Implementing the wrong (or no) interface + /// will result into a crash. + /// \remarks + /// - You may use AK::StreamMgr::GetDefaultDeviceSettings() first to get default values for the + /// settings, change those you want, then feed the structure to this function. + /// - The returned device ID should be kept by the Low-Level IO, to assign it to file descriptors + /// in AK::StreamMgr::IAkFileLocationResolver::Open(). + /// \sa + /// - AK::StreamMgr::IAkLowLevelIOHook + /// - AK::StreamMgr::GetDefaultDeviceSettings() + /// - \ref streamingmanager_settings + virtual AkDeviceID CreateDevice( + const AkDeviceSettings& in_settings, ///< Device settings. + AK::StreamMgr::IAkLowLevelIOHook* in_pLowLevelHook ///< Associated low-level I/O hook. Pass either a IAkIOHookBlocking or a IAkIOHookDeferredBatch interface, consistent with the type of the scheduler. + ) = 0; + /// Streaming device destruction. + /// \return AK_Success if the device was successfully destroyed. + /// \warning This function is not thread-safe. No stream should exist for that device when it is destroyed. + virtual AKRESULT DestroyDevice( + AkDeviceID in_deviceID ///< Device ID of the device to destroy. + ) = 0; + + /// Execute pending I/O operations on all created I/O devices. + /// This should only be called in single-threaded environments where an I/O device cannot spawn a thread. + /// \return AK_Success when called from an appropriate environment, AK_NotCompatible otherwise. + /// \sa + /// - AK::StreamMgr::CreateDevice() + virtual AKRESULT PerformIO() = 0; + + /// Get the default values for the streaming device's settings. Recommended usage + /// is to call this function first, then pass the settings to AK::StreamMgr::CreateDevice(). + /// \sa + /// - AK::StreamMgr::CreateDevice() + /// - AkDeviceSettings + /// - \ref streamingmanager_settings + virtual void GetDefaultDeviceSettings( + AkDeviceSettings& out_settings ///< Returned AkDeviceSettings structure with default values. + ) = 0; + //@} + + /// \name Language management. + //@{ + /// Set the current language once and only once, here. The language name is stored in a static buffer + /// inside the Stream Manager. In order to resolve localized (language-specific) file location, + /// AK::StreamMgr::IAkFileLocationResolver implementations query this string. They may use it to + /// construct a file path (for e.g. SDK/samples/SoundEngine/Common/AkFileLocationBase.cpp), or to + /// find a language-specific file within a look-up table (for e.g. SDK/samples/SoundEngine/Common/AkFilePackageLUT.cpp). + /// Pass a valid null-terminated string, without a trailing slash or backslash. Empty strings are accepted. + /// You may register for language changes (see RegisterToLanguageChangeNotification()). + /// After changing the current language, all observers are notified. + /// \return AK_Success if successful (if language string has less than AK_MAX_LANGUAGE_NAME_SIZE characters). AK_Fail otherwise. + /// \warning Not multithread safe. + /// \sa + /// - AK::StreamMgr::GetCurrentLanguage() + /// - AK::StreamMgr::AddLanguageChangeObserver() + virtual AKRESULT SetCurrentLanguage( + const AkOSChar* in_pszLanguageName ///< Language name. + ) = 0; + + /// Get the current language. The language name is stored in a static buffer inside the Stream Manager, + /// with AK::StreamMgr::SetCurrentLanguage(). In order to resolve localized (language-specific) file location, + /// AK::StreamMgr::IAkFileLocationResolver implementations query this string. They may use it to + /// construct a file path (for e.g. SDK/samples/SoundEngine/Common/AkFileLocationBase.cpp), or to + /// find a language-specific file within a look-up table (for e.g. SDK/samples/SoundEngine/Common/AkFilePackageLUT.cpp). + /// \return Current language. + /// \sa AK::StreamMgr::SetCurrentLanguage() + virtual const AkOSChar* GetCurrentLanguage() = 0; + + /// Register to language change notifications. + /// \return AK_Success if successful, AK_Fail otherwise (no memory or no cookie). + /// \warning Not multithread safe. + /// \sa + /// - AK::StreamMgr::SetCurrentLanguage() + /// - AK::StreamMgr::RemoveLanguageChangeObserver() + virtual AKRESULT AddLanguageChangeObserver( + AK::StreamMgr::AkLanguageChangeHandler in_handler, ///< Callback function. + void* in_pCookie ///< Cookie, passed back to AkLanguageChangeHandler. Must set. + ) = 0; + + /// Unregister to language change notifications. Use the cookie you have passed to + /// AddLanguageChangeObserver() to identify the observer. + /// \warning Not multithread safe. + /// \sa + /// - AK::StreamMgr::SetCurrentLanguage() + /// - AK::StreamMgr::AddLanguageChangeObserver() + virtual void RemoveLanguageChangeObserver( + void* in_pCookie ///< Cookie that was passed to AddLanguageChangeObserver(). + ) = 0; + + /// \name Stream Manager: Cache management. + //@{ + /// Flush cache of all devices. This function has no effect for devices where + /// AkDeviceSettings::bUseStreamCache was set to false (no caching). + /// \sa + /// - \ref streamingmanager_settings + virtual void FlushAllCaches() = 0; + + //@} +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/PostSoundEngineInclude.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/PostSoundEngineInclude.h new file mode 100644 index 0000000..b530b51 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/PostSoundEngineInclude.h @@ -0,0 +1,155 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +// Allows usage of the Atomics and Windows macro-based types. +// +// In some circumstances, under some platforms, the definitions for Windows macros are +// already done. This code makes sure we safeguard what is already set up, before including +// the official Allow header. +// +// This code should be used after any #include operation, and the attached Pre +// operation should be done prior to the includes. + +#if !defined(AK_PRESOUNDENGINEINCLUDE_GUARD) + #error PostSoundEngineInclude is not reentrant. Please include "Wwise/PreSoundEngineInclude.h" before including your files +#endif +#undef AK_PRESOUNDENGINEINCLUDE_GUARD + +#ifdef PRAGMA_POP_PLATFORM_DEFAULT_PACKING +PRAGMA_POP_PLATFORM_DEFAULT_PACKING +#endif + +#ifdef THIRD_PARTY_INCLUDES_END +THIRD_PARTY_INCLUDES_END +#endif + +#if defined(PLATFORM_MICROSOFT) && PLATFORM_MICROSOFT + +#include "Microsoft/HideMicrosoftPlatformTypes.h" +#include "Microsoft/HideMicrosoftPlatformAtomics.h" + +#if defined(AK_KEEP_InterlockedIncrement) +#undef InterlockedIncrement +#define InterlockedIncrement _InterlockedIncrement +#undef AK_KEEP_InterlockedIncrement +#endif +#if defined(AK_KEEP_InterlockedDecrement) +#undef InterlockedDecrement +#define InterlockedDecrement _InterlockedDecrement +#undef AK_KEEP_InterlockedDecrement +#endif +#if defined(AK_KEEP_InterlockedAdd) +#undef InterlockedAdd +#define InterlockedAdd _InterlockedAdd +#undef AK_KEEP_InterlockedAdd +#endif +#if defined(AK_KEEP_InterlockedExchange) +#undef InterlockedExchange +#define InterlockedExchange _InterlockedExchange +#undef AK_KEEP_InterlockedExchange +#endif +#if defined(AK_KEEP_InterlockedExchangeAdd) +#undef InterlockedExchangeAdd +#define InterlockedExchangeAdd _InterlockedExchangeAdd +#undef AK_KEEP_InterlockedExchangeAdd +#endif +#if defined(AK_KEEP_InterlockedCompareExchange) +#undef InterlockedCompareExchange +#define InterlockedCompareExchange _InterlockedCompareExchange +#undef AK_KEEP_InterlockedCompareExchange +#endif +#if defined(AK_KEEP_InterlockedCompareExchangePointer) +#undef InterlockedCompareExchangePointer +#if PLATFORM_64BITS + #define InterlockedCompareExchangePointer _InterlockedCompareExchangePointer +#else + #define InterlockedCompareExchangePointer __InlineInterlockedCompareExchangePointer +#endif +#undef AK_KEEP_InterlockedCompareExchangePointer +#endif +#if defined(AK_KEEP_InterlockedExchange64) +#undef InterlockedExchange64 +#define InterlockedExchange64 _InterlockedExchange64 +#undef AK_KEEP_InterlockedExchange64 +#endif +#if defined(AK_KEEP_InterlockedExchangeAdd64) +#undef InterlockedExchangeAdd64 +#define InterlockedExchangeAdd64 _InterlockedExchangeAdd64 +#undef AK_KEEP_InterlockedExchangeAdd64 +#endif +#if defined(AK_KEEP_InterlockedCompareExchange64) +#undef InterlockedCompareExchange64 +#define InterlockedCompareExchange64 _InterlockedCompareExchange64 +#undef AK_KEEP_InterlockedCompareExchange64 +#endif +#if defined(AK_KEEP_InterlockedIncrement64) +#undef InterlockedIncrement64 +#define InterlockedIncrement64 _InterlockedIncrement64 +#undef AK_KEEP_InterlockedIncrement64 +#endif +#if defined(AK_KEEP_InterlockedDecrement64) +#undef InterlockedDecrement64 +#define InterlockedDecrement64 _InterlockedDecrement64 +#undef AK_KEEP_InterlockedDecrement64 +#endif +#if defined(AK_KEEP_InterlockedAnd) +#undef InterlockedAnd +#define InterlockedAnd _InterlockedAnd +#undef AK_KEEP_InterlockedAnd +#endif +#if defined(AK_KEEP_InterlockedOr) +#undef InterlockedOr +#define InterlockedOr _InterlockedOr +#undef AK_KEEP_InterlockedOr +#endif +#if defined(AK_KEEP_InterlockedXor) +#undef InterlockedXor +#define InterlockedXor _InterlockedXor +#undef AK_KEEP_InterlockedXor +#endif +#if defined(AK_KEEP_INT) +#undef INT +#define INT ::INT +#undef AK_KEEP_INT +#endif +#if defined(AK_KEEP_UINT) +#undef UINT +#define UINT ::UINT +#undef AK_KEEP_UINT +#endif +#if defined(AK_KEEP_DWORD) +#undef DWORD +#define DWORD ::DWORD +#undef AK_KEEP_DWORD +#endif +#if defined(AK_KEEP_FLOAT) +#undef FLOAT +#define FLOAT ::FLOAT +#undef AK_KEEP_FLOAT +#endif +#if defined(AK_KEEP_TRUE) +#undef TRUE +#define TRUE 1 +#undef AK_KEEP_TRUE +#endif +#if defined(AK_KEEP_FALSE) +#undef FALSE +#define FALSE 0 +#undef AK_KEEP_FALSE +#endif + +#endif \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/PreSoundEngineInclude.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/PreSoundEngineInclude.h new file mode 100644 index 0000000..63c8308 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/PreSoundEngineInclude.h @@ -0,0 +1,111 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +// Allows usage of the Atomics and Windows macro-based types. +// +// In some circumstances, under some platforms, the definitions for Windows macros are +// already done. This code makes sure we safeguard what is already set up, before including +// the official Allow header. +// +// This code should be used before any #include operation, and the attached Post +// operation should be done afterwards. + +#if defined(AK_PRESOUNDENGINEINCLUDE_GUARD) + #error PreSoundEngineInclude is not reentrant. Please include "Wwise/PostSoundEngineInclude.h" after including your files +#endif +#define AK_PRESOUNDENGINEINCLUDE_GUARD 1 + +#include "CoreTypes.h" + +#if defined(PLATFORM_MICROSOFT) && PLATFORM_MICROSOFT + +#if defined(InterlockedIncrement) +#define AK_KEEP_InterlockedIncrement 1 +#endif +#if defined(InterlockedDecrement) +#define AK_KEEP_InterlockedDecrement 1 +#endif +#if defined(InterlockedAdd) +#define AK_KEEP_InterlockedAdd 1 +#endif +#if defined(InterlockedExchange) +#define AK_KEEP_InterlockedExchange 1 +#endif +#if defined(InterlockedExchangeAdd) +#define AK_KEEP_InterlockedExchangeAdd 1 +#endif +#if defined(InterlockedCompareExchange) +#define AK_KEEP_InterlockedCompareExchange 1 +#endif +#if defined(InterlockedCompareExchangePointer) +#define AK_KEEP_InterlockedCompareExchangePointer 1 +#endif +#if defined(InterlockedExchange64) +#define AK_KEEP_InterlockedExchange64 1 +#endif +#if defined(InterlockedExchangeAdd64) +#define AK_KEEP_InterlockedExchangeAdd64 1 +#endif +#if defined(InterlockedCompareExchange64) +#define AK_KEEP_InterlockedCompareExchange64 1 +#endif +#if defined(InterlockedIncrement64) +#define AK_KEEP_InterlockedIncrement64 1 +#endif +#if defined(InterlockedDecrement64) +#define AK_KEEP_InterlockedDecrement64 1 +#endif +#if defined(InterlockedAnd) +#define AK_KEEP_InterlockedAnd 1 +#endif +#if defined(InterlockedOr) +#define AK_KEEP_InterlockedOr 1 +#endif +#if defined(InterlockedXor) +#define AK_KEEP_InterlockedXor 1 +#endif +#if defined(INT) +#define AK_KEEP_INT 1 +#endif +#if defined(UINT) +#define AK_KEEP_UINT 1 +#endif +#if defined(DWORD) +#define AK_KEEP_DWORD 1 +#endif +#if defined(FLOAT) +#define AK_KEEP_FLOAT 1 +#endif +#if defined(TRUE) +#define AK_KEEP_TRUE 1 +#endif +#if defined(FALSE) +#define AK_KEEP_FALSE 1 +#endif + +#include "Microsoft/AllowMicrosoftPlatformTypes.h" +#include "Microsoft/AllowMicrosoftPlatformAtomics.h" + +#endif + +#ifdef THIRD_PARTY_INCLUDES_START +THIRD_PARTY_INCLUDES_START +#endif + +#ifdef PRAGMA_PUSH_PLATFORM_DEFAULT_PACKING +PRAGMA_PUSH_PLATFORM_DEFAULT_PACKING +#endif \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/Stats/AsyncStats.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/Stats/AsyncStats.h new file mode 100644 index 0000000..33139eb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/Stats/AsyncStats.h @@ -0,0 +1,126 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Async/TaskGraphInterfaces.h" +#include "HAL/ThreadManager.h" +#include "Stats/Stats.h" + +// Unreal Stat system assumes it runs on an Unreal thread. A lot of low-level I/O doesn't follow that assumption. + +#if STATS + +FORCEINLINE static void ASYNC_LAMBDA_STAT(TUniqueFunction&& Lambda) +{ + DECLARE_CYCLE_STAT(TEXT("Wwise Async Stat"), STAT_WwiseAsyncStat, STATGROUP_StatSystem); + + if (FThreadManager::GetThreadName(FPlatformTLS::GetCurrentThreadId()).IsEmpty()) + { + FFunctionGraphTask::CreateAndDispatchWhenReady(MoveTemp(Lambda), GET_STATID(STAT_WwiseAsyncStat)); + } + else + { + Lambda(); + } +} +#define ASYNC_OP_STAT(...) ASYNC_LAMBDA_STAT([]__VA_ARGS__) + +class FWwiseAsyncCycleCounter +{ +public: + FName StatName; + FStatMessage StartStatMessage; + + FORCEINLINE_STATS static FName GetName(TStatId InStatId) + { + FMinimalName StatMinimalName = InStatId.GetMinimalName(EMemoryOrder::Relaxed); + if (UNLIKELY(StatMinimalName.IsNone())) + { + return {}; + } + return MinimalNameToName(StatMinimalName); + } + + FORCEINLINE_STATS static FStatMessage GetStartMessage(FName InStatName) + { + if (UNLIKELY(InStatName.IsNone())) + { + return {}; + } + return FStatMessage(InStatName, EStatOperation::CycleScopeStart); + } + + void Stop() + { + if (StatName.IsNone()) + { + return; + } + + FName Name; + Swap(Name, StatName); + + FStatMessage EndStatMessage(Name, EStatOperation::CycleScopeEnd); + ASYNC_LAMBDA_STAT([ + Name = MoveTemp(Name), + StartStatMessage = MoveTemp(StartStatMessage), + EndStatMessage = MoveTemp(EndStatMessage)] + { + FThreadStats* Stats = FThreadStats::GetThreadStats(); + Stats->AddStatMessage(StartStatMessage); + Stats->AddStatMessage(EndStatMessage); + Stats->Flush(); + }); + } + + FWwiseAsyncCycleCounter(TStatId InStatId) : + StatName(GetName(InStatId)), + StartStatMessage(GetStartMessage(StatName)) + { + } + + ~FWwiseAsyncCycleCounter() + { + Stop(); + } +}; +#else +#define ASYNC_LAMBDA_STAT(...) (void)0 +#define ASYNC_OP_STAT(...) (void)0 + + +class FWwiseAsyncCycleCounter +{ +public: + void Stop() + { + } + + FWwiseAsyncCycleCounter(TStatId InStatId) + { + } + + ~FWwiseAsyncCycleCounter() + { + } +}; +#endif + +#define ASYNC_INC_DWORD_STAT(x) ASYNC_OP_STAT({ INC_DWORD_STAT(x); }) +#define ASYNC_DEC_DWORD_STAT(x) ASYNC_OP_STAT({ DEC_DWORD_STAT(x); }) +#define ASYNC_INC_FLOAT_STAT_BY(x, y) ASYNC_LAMBDA_STAT([Value = y]{ INC_FLOAT_STAT_BY(x, Value); }) \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/Stats/Global.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/Stats/Global.h new file mode 100644 index 0000000..d033e4d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/Stats/Global.h @@ -0,0 +1,23 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Stats/Stats.h" +#include "Logging/LogMacros.h" + +WWISESOUNDENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogWwiseHints, Error, All); \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/Stats/NamedEvents.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/Stats/NamedEvents.h new file mode 100644 index 0000000..a09e20c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/Stats/NamedEvents.h @@ -0,0 +1,54 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "HAL/PlatformMisc.h" +#include "Logging/LogMacros.h" + +namespace WwiseNamedEvents +{ + /** + * @brief Highest level color. Used for user-executed operations, and high-level operations. Also used for SoundEngine operations. The Wwise Color. + */ + extern WWISESOUNDENGINE_API const FColor Color1; + + /** + * @brief Lower level color. Used for inner algorithms as defined by high-level operations, recognizable by users. + */ + extern WWISESOUNDENGINE_API const FColor Color2; + + /** + * @brief Accessory, dulled-down color. Used for main asynchronous operations, such as loading and unloading. + */ + extern WWISESOUNDENGINE_API const FColor Color3; + + /** + * @brief Accessory, off color. Used for containers of other operations, lowest-level calls, and Synchronous waiting spots (Futures, Getters, EventRef Wait). + */ + extern WWISESOUNDENGINE_API const FColor Color4; +} + +#define SCOPED_WWISE_NAMED_EVENT_F(Prefix, Format, ...) SCOPED_NAMED_EVENT_F(TEXT("%s ") Format, WwiseNamedEvents::Color1, Prefix, __VA_ARGS__) +#define SCOPED_WWISE_NAMED_EVENT_F_2(Prefix, Format, ...) SCOPED_NAMED_EVENT_F(TEXT("%s ") Format, WwiseNamedEvents::Color2, Prefix, __VA_ARGS__) +#define SCOPED_WWISE_NAMED_EVENT_F_3(Prefix, Format, ...) SCOPED_NAMED_EVENT_F(TEXT("%s ") Format, WwiseNamedEvents::Color3, Prefix, __VA_ARGS__) +#define SCOPED_WWISE_NAMED_EVENT_F_4(Prefix, Format, ...) SCOPED_NAMED_EVENT_F(TEXT("%s ") Format, WwiseNamedEvents::Color4, Prefix, __VA_ARGS__) + +#define SCOPED_WWISE_NAMED_EVENT(Prefix, Text) SCOPED_WWISE_NAMED_EVENT_F(Prefix, TEXT("%s"), Text) +#define SCOPED_WWISE_NAMED_EVENT_2(Prefix, Text) SCOPED_WWISE_NAMED_EVENT_F_2(Prefix, TEXT("%s"), Text) +#define SCOPED_WWISE_NAMED_EVENT_3(Prefix, Text) SCOPED_WWISE_NAMED_EVENT_F_3(Prefix, TEXT("%s"), Text) +#define SCOPED_WWISE_NAMED_EVENT_4(Prefix, Text) SCOPED_WWISE_NAMED_EVENT_F_4(Prefix, TEXT("%s"), Text) diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/Stats/SoundEngine.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/Stats/SoundEngine.h new file mode 100644 index 0000000..b5e7186 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/Stats/SoundEngine.h @@ -0,0 +1,34 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Stats/Stats.h" +#include "Wwise/Stats/NamedEvents.h" + +DECLARE_STATS_GROUP(TEXT("SoundEngine"), STATGROUP_WwiseSoundEngine, STATCAT_Wwise); + +WWISESOUNDENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogWwiseSoundEngine, Log, All); + +#define SCOPED_WWISESOUNDENGINE_EVENT(Text) SCOPED_WWISE_NAMED_EVENT(TEXT("WwiseSoundEngine"), Text) +#define SCOPED_WWISESOUNDENGINE_EVENT_2(Text) SCOPED_WWISE_NAMED_EVENT_2(TEXT("WwiseSoundEngine"), Text) +#define SCOPED_WWISESOUNDENGINE_EVENT_3(Text) SCOPED_WWISE_NAMED_EVENT_3(TEXT("WwiseSoundEngine"), Text) +#define SCOPED_WWISESOUNDENGINE_EVENT_4(Text) SCOPED_WWISE_NAMED_EVENT_4(TEXT("WwiseSoundEngine"), Text) +#define SCOPED_WWISESOUNDENGINE_EVENT_F(Format, ...) SCOPED_WWISE_NAMED_EVENT_F(TEXT("WwiseSoundEngine"), Format, __VA_ARGS__) +#define SCOPED_WWISESOUNDENGINE_EVENT_F_2(Format, ...) SCOPED_WWISE_NAMED_EVENT_F_2(TEXT("WwiseSoundEngine"), Format, __VA_ARGS__) +#define SCOPED_WWISESOUNDENGINE_EVENT_F_3(Format, ...) SCOPED_WWISE_NAMED_EVENT_F_3(TEXT("WwiseSoundEngine"), Format, __VA_ARGS__) +#define SCOPED_WWISESOUNDENGINE_EVENT_F_4(Format, ...) SCOPED_WWISE_NAMED_EVENT_F_4(TEXT("WwiseSoundEngine"), Format, __VA_ARGS__) diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/WwiseFileLocationResolver.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/WwiseFileLocationResolver.h new file mode 100644 index 0000000..a8ad913 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/WwiseFileLocationResolver.h @@ -0,0 +1,31 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#include "HAL/PreprocessorHelpers.h" + +#if defined(AK_WWISE_SOUNDENGINE_2022_1) +#include "Wwise/WwiseFileLocationResolver_2022_1.h" +#elif defined(AK_WWISE_SOUNDENGINE_2023_1) +#include "Wwise/WwiseFileLocationResolver_2023_1.h" +#elif defined(AK_WWISE_SOUNDENGINE_2024_1) +#include "Wwise/WwiseFileLocationResolver_2024_1.h" +#elif defined(AK_WWISE_SOUNDENGINE_2025_1) +#include "Wwise/WwiseFileLocationResolver_2025_1.h" +#else +#error "This file should be updated so SN-DBS can support this version" +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/WwiseLowLevelIOHook.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/WwiseLowLevelIOHook.h new file mode 100644 index 0000000..4112555 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/WwiseLowLevelIOHook.h @@ -0,0 +1,31 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once +#include "HAL/PreprocessorHelpers.h" + +#if defined(AK_WWISE_SOUNDENGINE_2022_1) +#include "Wwise/WwiseLowLevelIOHook_2022_1.h" +#elif defined(AK_WWISE_SOUNDENGINE_2023_1) +#include "Wwise/WwiseLowLevelIOHook_2023_1.h" +#elif defined(AK_WWISE_SOUNDENGINE_2024_1) +#include "Wwise/WwiseLowLevelIOHook_2024_1.h" +#elif defined(AK_WWISE_SOUNDENGINE_2025_1) +#include "Wwise/WwiseLowLevelIOHook_2025_1.h" +#else +#error "This file should be updated so SN-DBS can support this version" +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/WwiseSoundEngineModule.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/WwiseSoundEngineModule.h new file mode 100644 index 0000000..1646d5c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/WwiseSoundEngineModule.h @@ -0,0 +1,77 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseSoundEngineVersionModule.h" +#include "Modules/ModuleManager.h" + +class FWwiseGlobalCallbacks; + +class IWwiseSoundEngineModule : public IModuleInterface +{ +public: + static WWISESOUNDENGINE_API IWwiseCommAPI* Comm; + static WWISESOUNDENGINE_API IWwiseMemoryMgrAPI* MemoryMgr; + static WWISESOUNDENGINE_API IWwiseMonitorAPI* Monitor; + static WWISESOUNDENGINE_API IWwiseMusicEngineAPI* MusicEngine; + static WWISESOUNDENGINE_API IWwiseSoundEngineAPI* SoundEngine; + static WWISESOUNDENGINE_API IWwiseSpatialAudioAPI* SpatialAudio; + static WWISESOUNDENGINE_API IWwiseStreamMgrAPI* StreamMgr; + + static WWISESOUNDENGINE_API IWwisePlatformAPI* Platform; + static WWISESOUNDENGINE_API IWAAPI* WAAPI; + + static WWISESOUNDENGINE_API IWwiseSoundEngineVersionModule* VersionInterface; + + /** + * Checks to see if this module and the appropriate Sound Engine API are loaded and ready. + * + * @return True if the module is loaded and ready to use + */ + static bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded(TEXT("WwiseSoundEngine")); + } + + static void ForceLoadModule() + { + FModuleManager& ModuleManager = FModuleManager::Get(); + if (!IsAvailable()) + { + if (IsEngineExitRequested()) + { + UE_LOG(LogLoad, Verbose, TEXT("Skipping reloading missing WwiseSoundEngine: Exiting.")); + } + else if (!IsInGameThread()) + { + UE_LOG(LogLoad, Warning, TEXT("Skipping loading missing WwiseSoundEngine: Not in game thread")); + } + else + { + ModuleManager.LoadModule("WwiseSoundEngine"); + } + } + } +}; + +class WWISESOUNDENGINE_API FWwiseSoundEngineModule : public IWwiseSoundEngineModule +{ + void StartupModule() override; + void ShutdownModule() override; + static void DeleteInterface(); +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/WwiseSoundEngineVersionModule.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/WwiseSoundEngineVersionModule.h new file mode 100644 index 0000000..34d2033 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/WwiseSoundEngineVersionModule.h @@ -0,0 +1,45 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +class IWwiseStreamMgrAPI; +class IWwiseSpatialAudioAPI; +class IWwiseSoundEngineAPI; +class IWwiseMusicEngineAPI; +class IWwiseMonitorAPI; +class IWwiseMemoryMgrAPI; +class IWwiseCommAPI; +class IWwisePlatformAPI; +class IWAAPI; + +class IWwiseSoundEngineVersionModule +{ +public: + virtual ~IWwiseSoundEngineVersionModule() {} + + virtual IWwiseCommAPI* GetComm() = 0; + virtual IWwiseMemoryMgrAPI* GetMemoryMgr() = 0; + virtual IWwiseMonitorAPI* GetMonitor() = 0; + virtual IWwiseMusicEngineAPI* GetMusicEngine() = 0; + virtual IWwiseSoundEngineAPI* GetSoundEngine() = 0; + virtual IWwiseSpatialAudioAPI* GetSpatialAudio() = 0; + virtual IWwiseStreamMgrAPI* GetStreamMgr() = 0; + + virtual IWwisePlatformAPI* GetPlatform() = 0; + virtual IWAAPI* GetWAAPI() = 0; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/WwiseUnitTests.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/WwiseUnitTests.h new file mode 100644 index 0000000..8068cdd --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/Wwise/WwiseUnitTests.h @@ -0,0 +1,51 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "WwiseDefines.h" + +// We use Catch2 Test Harness, available starting UE5.1 +#if UE_5_2_OR_LATER +#include "Tests/TestHarnessAdapter.h" +#elif UE_5_1_OR_LATER +#include "Tests/TestHarness.h" +#endif // UE_5_2_OR_LATER + +#if UE_5_1_OR_LATER +#if WITH_LOW_LEVEL_TESTS || defined(WITH_AUTOMATION_TESTS) && WITH_AUTOMATION_TESTS + +#define WWISE_UNIT_TESTS 1 + +#endif +#endif // UE_5_1_OR_LATER + +#ifndef WWISE_UNIT_TESTS +#define WWISE_UNIT_TESTS 0 +#endif + +#if WWISE_UNIT_TESTS +#include + +// Add logging facilities when running in Automation +#if defined(WITH_AUTOMATION_TESTS) && WITH_AUTOMATION_TESTS +#define WWISETEST_LOG(Format, ...) FAutomationTestFramework::Get().GetCurrentTest()->AddInfo(FString::Printf(TEXT(Format), __VA_ARGS__)); +#else +#define WWISETEST_LOG(Format, ...) (void)0 +#endif + +#endif // WWISE_UNIT_TESTS \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/WwiseDefines.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/WwiseDefines.h new file mode 100644 index 0000000..e0f2cb1 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/Public/WwiseDefines.h @@ -0,0 +1,55 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Runtime/Launch/Resources/Version.h" + +#define UE_4_26_OR_LATER ((ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 26) || (ENGINE_MAJOR_VERSION >= 5)) +#define UE_4_27_OR_LATER ((ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 27) || (ENGINE_MAJOR_VERSION >= 5)) +#define UE_5_0_OR_LATER (ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 0) +#define UE_5_1_OR_LATER (ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1) +#define UE_5_2_OR_LATER (ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 2) + +#define WWISE_2022_1_OR_LATER ((AK_WWISE_SOUNDENGINE_MAJOR_VERSION == 2022 && AK_WWISE_SOUNDENGINE_MINOR_VERSION >= 1) || (AK_WWISE_SOUNDENGINE_MAJOR_VERSION >= 2023)) +#define WWISE_2023_1_OR_LATER ((AK_WWISE_SOUNDENGINE_MAJOR_VERSION == 2023 && AK_WWISE_SOUNDENGINE_MINOR_VERSION >= 1) || (AK_WWISE_SOUNDENGINE_MAJOR_VERSION >= 2024)) +#define WWISE_2024_1_OR_LATER ((AK_WWISE_SOUNDENGINE_MAJOR_VERSION == 2024 && AK_WWISE_SOUNDENGINE_MINOR_VERSION >= 1) || (AK_WWISE_SOUNDENGINE_MAJOR_VERSION >= 2025)) +#define WWISE_2025_1_OR_LATER ((AK_WWISE_SOUNDENGINE_MAJOR_VERSION == 2025 && AK_WWISE_SOUNDENGINE_MINOR_VERSION >= 1) || (AK_WWISE_SOUNDENGINE_MAJOR_VERSION >= 2026)) + +#if !WWISE_2022_1_OR_LATER +#error "Unsupported SoundEngine version" +#endif + +#define AK_DEPRECATED UE_DEPRECATED + +// PhysX (deprecated in UE5.0) and Chaos (mandatory in UE5.1) selection +#if UE_5_1_OR_LATER +#define AK_USE_CHAOS 1 +#define AK_USE_PHYSX 0 +#else +#if defined(PHYSICS_INTERFACE_PHYSX) && PHYSICS_INTERFACE_PHYSX +#define AK_USE_PHYSX 1 +#else +#define AK_USE_PHYSX 0 +#endif +#if defined(WITH_CHAOS) && WITH_CHAOS +#define AK_USE_CHAOS 1 +#else +#define AK_USE_CHAOS 0 +#endif +#endif + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/WwiseSoundEngine.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/WwiseSoundEngine.Build.cs new file mode 100644 index 0000000..8366afe --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/WwiseSoundEngine.Build.cs @@ -0,0 +1,84 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using System.IO; +using UnrealBuildTool; + +#if UE_5_0_OR_LATER +using EpicGames.Core; +#else +using Tools.DotNETCommon; +#endif + +public class WwiseSoundEngine : ModuleRules +{ + public WwiseSoundEngine(ReadOnlyTargetRules Target) : base(Target) + { + WwiseSoundEngine_2022_1.Apply(this, Target); + WwiseSoundEngine_Null.Apply(this, Target); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine" + } + ); + + ExternalDependencies.Add("WwiseSoundEngineVersion.Build.cs"); + ExternalDependencies.Add("WwiseUEPlatform.Build.cs"); + + bAllowConfidentialPlatformDefines = true; + } + + public void AddSoundEngineDirectory(string Module, bool TargetSupported) + { + if (TargetSupported) + { +#if UE_5_0_OR_LATER + ConditionalAddModuleDirectory( + EpicGames.Core.DirectoryReference.Combine(new EpicGames.Core.DirectoryReference(ModuleDirectory), "..", + Module)); +#else + ConditionalAddModuleDirectory( + Tools.DotNETCommon.DirectoryReference.Combine(new Tools.DotNETCommon.DirectoryReference(ModuleDirectory), "..", Module)); +#endif + } + + ExternalDependencies.Add(Path.Combine(ModuleDirectory, "..", Module, Module + "_OptionalModule.Build.cs")); + PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "..", Module, "Public")); + PrivateIncludePaths.Add(Path.Combine(ModuleDirectory, "..", Module, "Private")); + } + + public void AddVersionHeaders(string Module, bool TargetSupported) + { + if (TargetSupported) + { + PrivateDefinitions.Add("WWISE_SOUNDENGINE_VERSION_HEADER_PATH=\"Wwise/" + Module + ".h\""); + PrivateDefinitions.Add("WWISE_SOUNDENGINE_VERSION_CLASS=F" + Module); + PublicDefinitions.Add("AK_USE_NULL_SOUNDENGINE=0"); + } + else + { + Log.TraceInformation("Wwise SoundEngine is disabled: Using the null SoundEngine."); + PrivateDefinitions.Add("WWISE_SOUNDENGINE_VERSION_HEADER_PATH=\"Wwise/WwiseSoundEngine_Null.h\""); + PrivateDefinitions.Add("WWISE_SOUNDENGINE_VERSION_CLASS=FWwiseSoundEngine_Null"); + PublicDefinitions.Add("AK_USE_NULL_SOUNDENGINE=1"); + } + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/WwiseSoundEngineVersion.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/WwiseSoundEngineVersion.Build.cs new file mode 100644 index 0000000..859754f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/WwiseSoundEngineVersion.Build.cs @@ -0,0 +1,138 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; +using System; +using System.IO; +using System.Linq; + +public class WwiseSoundEngineVersion +{ + public int Major = -1; + public int Minor = -1; + public int SubMinor = -1; + public int Build = -1; + private static WwiseSoundEngineVersion Instance = null; + + private WwiseSoundEngineVersion(string PluginDirectory) + { + var WwiseSdkVersionPath = Path.Combine(PluginDirectory, "ThirdParty/include/AK/", "AkWwiseSDKVersion.h"); + if (!System.IO.File.Exists(WwiseSdkVersionPath)) + { + throw new BuildException(string.Format("Wwise ThirdParty is not installed. Unable to find \"{0}\" file.", WwiseSdkVersionPath)); + } + + var WwiseSdkVersionLines = System.IO.File.ReadAllLines(WwiseSdkVersionPath); + foreach (var Line in WwiseSdkVersionLines) + { + try + { + if (Line.StartsWith("#define AK_WWISESDK_VERSION_MAJOR")) + { + Major = int.Parse(Line.Split('\t').Last()); + } + else if (Line.StartsWith("#define AK_WWISESDK_VERSION_MINOR")) + { + Minor = int.Parse(Line.Split('\t').Last()); + } + else if (Line.StartsWith("#define AK_WWISESDK_VERSION_SUBMINOR")) + { + SubMinor = int.Parse(Line.Split('\t').Last()); + } + else if (Line.StartsWith("#define AK_WWISESDK_VERSION_BUILD")) + { + Build = int.Parse(Line.Split('\t').Last()); + } + } + catch (Exception) + { + throw new BuildException(string.Format("Invalid numeral at end of line \"{}\"", Line)); + } + } + + if (Major == -1 || Minor == -1 || SubMinor == -1 || Build == -1) + { + throw new BuildException(string.Format("Could not find full Wwise version in \"{0}\" file.", WwiseSdkVersionPath)); + } + } + + public static WwiseSoundEngineVersion GetInstance(string PluginDirectory) + { + return Instance != null ? Instance : Instance = new WwiseSoundEngineVersion(PluginDirectory); + } + + public static string[] GetVersionFromClassName(string ClassName) + { + return ClassName.Split('_').Skip(1).ToArray(); + } + + public static string[] GetVersionDefinesFromClassName(string ClassName) + { + var VersionArray = GetVersionFromClassName(ClassName); + var VersionMajor = VersionArray.Length >= 1 ? VersionArray[0] : "0"; + var VersionMinor = VersionArray.Length >= 2 ? VersionArray[1] : "1"; + var VersionSubMinor = VersionArray.Length >= 3 ? VersionArray[2] : "0"; + + return new[] + { + string.Format("AK_WWISE_SOUNDENGINE_VERSION=\"{0}\"", string.Join(".", VersionArray)), + string.Format("AK_WWISE_SOUNDENGINE_{0}", string.Join("_", VersionArray)), + string.Format("AK_WWISE_SOUNDENGINE_MAJOR_VERSION={0}", VersionMajor), + string.Format("AK_WWISE_SOUNDENGINE_MINOR_VERSION={0}", VersionMinor), + string.Format("AK_WWISE_SOUNDENGINE_SUBMINOR_VERSION={0}", VersionSubMinor) + }; + } + + public static bool IsSoundEngineVersionSupported(string PluginDirectory, string ClassName) + { + var ClassSplit = ClassName.Split('_'); + int RequiredMajor = -1, RequiredMinor = -1, RequiredSubMinor = -1, RequiredBuild = -1; + switch (ClassSplit.Length) + { + case 5: RequiredBuild = int.Parse(ClassSplit[4]); + goto case 4; + case 4: RequiredSubMinor = int.Parse(ClassSplit[3]); + goto case 3; + case 3: RequiredMinor = int.Parse(ClassSplit[2]); + goto case 2; + case 2: RequiredMajor = int.Parse(ClassSplit[1]); + break; + default: + throw new BuildException(string.Format("WwiseSoundEngine class name is invalid: {0}", ClassName)); + } + + var Version = GetInstance(PluginDirectory); + return Version.Major == RequiredMajor + && (RequiredMinor == -1 || Version.Minor == RequiredMinor) + && (RequiredSubMinor == -1 || Version.SubMinor == RequiredSubMinor) + && (RequiredBuild == -1 || Version.Build == RequiredBuild); + } + + public static string GetSoundEngineVersion(string PluginDirectory, string[] Modules) + { + foreach (var Module in Modules) + { + if (IsSoundEngineVersionSupported(PluginDirectory, Module)) + { + return Module; + } + } + + var Version = GetInstance(PluginDirectory); + throw new BuildException(string.Format("WwiseSoundEngine does not support the current SDK version: {0}.{1}.{2}.{3}", Version.Major, Version.Minor, Version.SubMinor, Version.Build)); + } +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/WwiseUEPlatform.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/WwiseUEPlatform.Build.cs new file mode 100644 index 0000000..81b56ae --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine/WwiseUEPlatform.Build.cs @@ -0,0 +1,149 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; +using System; +using System.IO; +using System.Collections.Generic; + +// Platform-specific files implement this interface, returning their particular dependencies, defines, etc. +public abstract class WwiseUEPlatform +{ + protected ReadOnlyTargetRules Target; + protected string ThirdPartyFolder; + + public WwiseUEPlatform(ReadOnlyTargetRules in_Target, string in_ThirdPartyFolder) + { + Target = in_Target; + ThirdPartyFolder = in_ThirdPartyFolder; + } + + public bool IsWwiseTargetSupported() + { + var platformPath = Path.Combine(ThirdPartyFolder, AkPlatformLibDir); + var hasPlatform = Directory.Exists(platformPath); + var supportedTargetType = Target.Type != TargetRules.TargetType.Server && Target.Type != TargetRules.TargetType.Program; + return hasPlatform && supportedTargetType; + } + + public static WwiseUEPlatform GetWwiseUEPlatformInstance(ReadOnlyTargetRules Target, string VersionNumber, string ThirdPartyFolder) + { + var WwiseUEPlatformType = System.Type.GetType( + VersionNumber == "Null" ? "WwiseUEPlatform_Null" : + "WwiseUEPlatform_" + VersionNumber + "_" + Target.Platform.ToString()); + if (WwiseUEPlatformType == null) + { + throw new BuildException("Wwise does not support platform " + Target.Platform.ToString() + " on " + VersionNumber); + } + + var PlatformInstance = Activator.CreateInstance(WwiseUEPlatformType, Target, ThirdPartyFolder) as WwiseUEPlatform; + if (PlatformInstance == null) + { + throw new BuildException("Wwise could not instantiate platform " + Target.Platform.ToString() + " on " + VersionNumber); + } + + return PlatformInstance; + } + + protected static List GetAllLibrariesInFolder(string LibFolder, string Extension, bool RemoveLibPrefix = true, bool GetFullPath = false) + { + List ret = null; + var FoundLibs = Directory.GetFiles(LibFolder, "*."+Extension); + + if (GetFullPath) + { + ret = new List(FoundLibs); + } + else + { + ret = new List(); + foreach (var Library in FoundLibs) + { + var LibName = Path.GetFileNameWithoutExtension(Library); + if (RemoveLibPrefix && LibName.StartsWith("lib")) + { + LibName = LibName.Remove(0, 3); + } + ret.Add(LibName); + } + + } + return ret; + } + + public virtual string AkConfigurationDir + { + get + { + switch (Target.Configuration) + { + case UnrealTargetConfiguration.Debug: + var akConfiguration = Target.bDebugBuildsActuallyUseDebugCRT ? "Debug" : "Profile"; + return akConfiguration; + + case UnrealTargetConfiguration.Development: + case UnrealTargetConfiguration.Test: + case UnrealTargetConfiguration.DebugGame: + return "Profile"; + default: + return "Release"; + } + } + } + + public abstract string GetLibraryFullPath(string LibName, string LibPath); + public abstract bool SupportsAkAutobahn { get; } + public abstract bool SupportsCommunication { get; } + public abstract bool SupportsDeviceMemory { get; } + public abstract string AkPlatformLibDir { get; } + public abstract string DynamicLibExtension { get; } + public virtual bool SupportsOpus { get { return true; } } + + public virtual List GetPublicLibraryPaths() + { + return new List + { + Path.Combine(ThirdPartyFolder, AkPlatformLibDir, AkConfigurationDir, "lib") + }; + } + + public virtual List GetRuntimeDependencies() + { + return GetAllLibrariesInFolder(Path.Combine(ThirdPartyFolder, AkPlatformLibDir, AkConfigurationDir, "bin"), DynamicLibExtension, false, true); + } + + public abstract List GetAdditionalWwiseLibs(); + public abstract List GetPublicSystemLibraries(); + public abstract List GetPublicDelayLoadDLLs(); + public abstract List GetPublicDefinitions(); + public abstract Tuple GetAdditionalPropertyForReceipt(string ModuleDirectory); + public abstract List GetPublicFrameworks(); + + public virtual List GetSanitizedAkLibList(List AkLibs) + { + List SanitizedLibs = new List(); + foreach(var lib in AkLibs) + { + foreach(var libPath in GetPublicLibraryPaths()) + { + SanitizedLibs.Add(GetLibraryFullPath(lib, libPath)); + } + } + + return SanitizedLibs; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/Platforms/Android/AndroidAPI_2022_1.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/Platforms/Android/AndroidAPI_2022_1.cpp new file mode 100644 index 0000000..cd16b3d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/Platforms/Android/AndroidAPI_2022_1.cpp @@ -0,0 +1,33 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_2022_1/Platforms/Android/AndroidAPI_2022_1.h" +#include "Wwise/Stats/SoundEngine_2022_1.h" + +#if defined(PLATFORM_ANDROID) && PLATFORM_ANDROID +SLObjectItf FWwisePlatformAPI_2022_1_Android::GetWwiseOpenSLInterface() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetWwiseOpenSLInterface(); +} + +AKRESULT FWwisePlatformAPI_2022_1_Android::GetFastPathSettings(AkInitSettings &in_settings, AkPlatformInitSettings &in_pfSettings) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetFastPathSettings(in_settings, in_pfSettings); +} +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/Platforms/HoloLensAPI_2022_1.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/Platforms/HoloLensAPI_2022_1.cpp new file mode 100644 index 0000000..2f508c8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/Platforms/HoloLensAPI_2022_1.cpp @@ -0,0 +1,59 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_2022_1/Platforms/HoloLensAPI_2022_1.h" +#include "Wwise/Stats/SoundEngine_2022_1.h" + +#if defined(PLATFORM_HOLOLENS) && PLATFORM_HOLOLENS +AkUInt32 FWwisePlatformAPI_2022_1_HoloLens::GetDeviceID(IMMDevice* in_pDevice) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::GetDeviceID(in_pDevice); +} + +AkUInt32 FWwisePlatformAPI_2022_1_HoloLens::GetDeviceIDFromName(wchar_t* in_szToken) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::GetDeviceIDFromName(in_szToken); +} + +const wchar_t* FWwisePlatformAPI_2022_1_HoloLens::GetWindowsDeviceName(AkInt32 index, AkUInt32& out_uDeviceID, + AkAudioDeviceState uDeviceStateMask) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::GetWindowsDeviceName(index, out_uDeviceID, uDeviceStateMask); +} + +AkUInt32 FWwisePlatformAPI_2022_1_HoloLens::GetWindowsDeviceCount(AkAudioDeviceState uDeviceStateMask) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::GetWindowsDeviceCount(uDeviceStateMask); +} + +bool FWwisePlatformAPI_2022_1_HoloLens::GetWindowsDevice(AkInt32 in_index, AkUInt32& out_uDeviceID, + IMMDevice** out_ppDevice, AkAudioDeviceState uDeviceStateMask) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::GetWindowsDevice(in_index, out_uDeviceID, out_ppDevice, uDeviceStateMask); +} + +//AkDeviceID FWwisePlatformAPI_2022_1_HoloLens::GetDeviceIDFromGamepad(Windows::Gaming::Input::Gamepad^ rGamepad) +//{ +// SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); +// return AK::SoundEngine::GetWindowsDevice(rGamepad); +//} +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/Platforms/IOS/IOSAPI_2022_1.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/Platforms/IOS/IOSAPI_2022_1.cpp new file mode 100644 index 0000000..58c72af --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/Platforms/IOS/IOSAPI_2022_1.cpp @@ -0,0 +1,29 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_2022_1/Platforms/IOS/IOSAPI_2022_1.h" +#include "Wwise/Stats/SoundEngine_2022_1.h" + +#if defined(PLATFORM_IOS) && PLATFORM_IOS && !(defined(PLATFORM_TVOS) && PLATFORM_TVOS) +void FWwisePlatformAPI_2022_1_IOS::ChangeAudioSessionProperties( + const AkAudioSessionProperties &in_properties + ) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::SoundEngine::iOS::ChangeAudioSessionProperties(in_properties); +} +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/Platforms/Linux/LinuxAPI_2022_1.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/Platforms/Linux/LinuxAPI_2022_1.cpp new file mode 100644 index 0000000..568cf28 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/Platforms/Linux/LinuxAPI_2022_1.cpp @@ -0,0 +1,23 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_2022_1/Platforms/Linux/LinuxAPI_2022_1.h" +#include "Wwise/Stats/SoundEngine_2022_1.h" + +#if defined(PLATFORM_LINUX) && PLATFORM_LINUX + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/Platforms/Mac/MacAPI_2022_1.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/Platforms/Mac/MacAPI_2022_1.cpp new file mode 100644 index 0000000..b7c078b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/Platforms/Mac/MacAPI_2022_1.cpp @@ -0,0 +1,23 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_2022_1/Platforms/Mac/MacAPI_2022_1.h" +#include "Wwise/Stats/SoundEngine_2022_1.h" + +#if defined(PLATFORM_MAC) && PLATFORM_MAC + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/Platforms/TVOS/TVOSAPI_2022_1.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/Platforms/TVOS/TVOSAPI_2022_1.cpp new file mode 100644 index 0000000..9cb4962 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/Platforms/TVOS/TVOSAPI_2022_1.cpp @@ -0,0 +1,29 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_2022_1/Platforms/TVOS/TVOSAPI_2022_1.h" +#include "Wwise/Stats/SoundEngine_2022_1.h" + +#if defined(PLATFORM_TVOS) && PLATFORM_TVOS +void FWwisePlatformAPI_2022_1_TVOS::ChangeAudioSessionProperties( + const AkAudioSessionProperties &in_properties + ) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::SoundEngine::iOS::ChangeAudioSessionProperties(in_properties); +} +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/Platforms/Windows/WindowsAPI_2022_1.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/Platforms/Windows/WindowsAPI_2022_1.cpp new file mode 100644 index 0000000..b140f8c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/Platforms/Windows/WindowsAPI_2022_1.cpp @@ -0,0 +1,54 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_2022_1/Platforms/Windows/WindowsAPI_2022_1.h" +#include "Wwise/Stats/SoundEngine_2022_1.h" + +#if defined(PLATFORM_WINDOWS) && PLATFORM_WINDOWS && (!defined(PLATFORM_WINGDK) || !PLATFORM_WINGDK) +AkUInt32 FWwisePlatformAPI_2022_1_Windows::GetDeviceID(IMMDevice* in_pDevice) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::GetDeviceID(in_pDevice); +} + +AkUInt32 FWwisePlatformAPI_2022_1_Windows::GetDeviceIDFromName(wchar_t* in_szToken) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::GetDeviceIDFromName(in_szToken); +} + +const wchar_t* FWwisePlatformAPI_2022_1_Windows::GetWindowsDeviceName(AkInt32 index, AkUInt32& out_uDeviceID, + AkAudioDeviceState uDeviceStateMask) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::GetWindowsDeviceName(index, out_uDeviceID, uDeviceStateMask); +} + +AkUInt32 FWwisePlatformAPI_2022_1_Windows::GetWindowsDeviceCount(AkAudioDeviceState uDeviceStateMask) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::GetWindowsDeviceCount(uDeviceStateMask); +} + +bool FWwisePlatformAPI_2022_1_Windows::GetWindowsDevice(AkInt32 in_index, AkUInt32& out_uDeviceID, + IMMDevice** out_ppDevice, AkAudioDeviceState uDeviceStateMask) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::GetWindowsDevice(in_index, out_uDeviceID, out_ppDevice, uDeviceStateMask); +} +#endif + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WAAPI_2022_1.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WAAPI_2022_1.cpp new file mode 100644 index 0000000..6a8a2ad --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WAAPI_2022_1.cpp @@ -0,0 +1,87 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_2022_1/WAAPI_2022_1.h" +#include "Wwise/Stats/SoundEngine_2022_1.h" + +#if AK_SUPPORT_WAAPI + +bool FWAAPI_2022_1::Client_2022_1::Connect(const char* in_uri, unsigned in_port, + AK::WwiseAuthoringAPI::disconnectHandler_t disconnectHandler, int in_timeoutMs) +{ + return Client.Connect(in_uri, in_port, disconnectHandler, in_timeoutMs); +} + +bool FWAAPI_2022_1::Client_2022_1::IsConnected() const +{ + return Client.IsConnected(); +} + +void FWAAPI_2022_1::Client_2022_1::Disconnect() +{ + Client.Disconnect(); +} + +bool FWAAPI_2022_1::Client_2022_1::Subscribe(const char* in_uri, const char* in_options, + AK::WwiseAuthoringAPI::Client::WampEventCallback in_callback, uint64_t& out_subscriptionId, std::string& out_result, + int in_timeoutMs) +{ + return Client.Subscribe(in_uri, in_options, in_callback, out_subscriptionId, out_result, in_timeoutMs); +} + +bool FWAAPI_2022_1::Client_2022_1::Subscribe(const char* in_uri, const AK::WwiseAuthoringAPI::AkJson& in_options, + AK::WwiseAuthoringAPI::Client::WampEventCallback in_callback, uint64_t& out_subscriptionId, + AK::WwiseAuthoringAPI::AkJson& out_result, int in_timeoutMs) +{ + return Client.Subscribe(in_uri, in_options, in_callback, out_subscriptionId, out_result, in_timeoutMs); +} + +bool FWAAPI_2022_1::Client_2022_1::Unsubscribe(const uint64_t& in_subscriptionId, std::string& out_result, + int in_timeoutMs) +{ + return Client.Unsubscribe(in_subscriptionId, out_result, in_timeoutMs); +} + +bool FWAAPI_2022_1::Client_2022_1::Unsubscribe(const uint64_t& in_subscriptionId, + AK::WwiseAuthoringAPI::AkJson& out_result, int in_timeoutMs) +{ + return Client.Unsubscribe(in_subscriptionId, out_result, in_timeoutMs); +} + +bool FWAAPI_2022_1::Client_2022_1::Call(const char* in_uri, const char* in_args, const char* in_options, + std::string& out_result, int in_timeoutMs) +{ + return Client.Call(in_uri, in_args, in_options, out_result, in_timeoutMs); +} + +bool FWAAPI_2022_1::Client_2022_1::Call(const char* in_uri, const AK::WwiseAuthoringAPI::AkJson& in_args, + const AK::WwiseAuthoringAPI::AkJson& in_options, AK::WwiseAuthoringAPI::AkJson& out_result, int in_timeoutMs) +{ + return Client.Call(in_uri, in_args, in_options, out_result, in_timeoutMs); +} + +IWAAPI::Client* FWAAPI_2022_1::NewClient() +{ + return new Client_2022_1; +} + +std::string FWAAPI_2022_1::GetJsonString(const AK::WwiseAuthoringAPI::JsonProvider& InJsonProvider) +{ + return InJsonProvider.GetJsonString(); +} + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WwiseCommAPI_2022_1.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WwiseCommAPI_2022_1.cpp new file mode 100644 index 0000000..a4a1069 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WwiseCommAPI_2022_1.cpp @@ -0,0 +1,87 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_2022_1/WwiseCommAPI_2022_1.h" +#include "Wwise/Stats/SoundEngine_2022_1.h" + +AKRESULT FWwiseCommAPI_2022_1::Init( + const AkCommSettings& in_settings +) +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseCommAPI_2022_1::Init")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); +#ifdef AK_OPTIMIZED + return AK_NotImplemented; +#else + return AK::Comm::Init(in_settings); +#endif +} + +AkInt32 FWwiseCommAPI_2022_1::GetLastError() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); +#ifdef AK_OPTIMIZED + return 0; +#else + return 0; + //return AK::Comm::GetLastError(); +#endif +} + +void FWwiseCommAPI_2022_1::GetDefaultInitSettings( + AkCommSettings& out_settings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); +#ifdef AK_OPTIMIZED + return; +#else + AK::Comm::GetDefaultInitSettings(out_settings); +#endif +} + +void FWwiseCommAPI_2022_1::Term() +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseCommAPI_2022_1::Term")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); +#ifdef AK_OPTIMIZED + return; +#else + AK::Comm::Term(); +#endif +} + +AKRESULT FWwiseCommAPI_2022_1::Reset() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); +#ifdef AK_OPTIMIZED + return AK_NotImplemented; +#else + return AK::Comm::Reset(); +#endif +} + +const AkCommSettings& FWwiseCommAPI_2022_1::GetCurrentSettings() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); +#ifdef AK_OPTIMIZED + static const AkCommSettings StaticSettings; + return StaticSettings; +#else + return AK::Comm::GetCurrentSettings(); +#endif +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WwiseMemoryMgrAPI_2022_1.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WwiseMemoryMgrAPI_2022_1.cpp new file mode 100644 index 0000000..5115d2e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WwiseMemoryMgrAPI_2022_1.cpp @@ -0,0 +1,222 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_2022_1/WwiseMemoryMgrAPI_2022_1.h" +#include "Wwise/Stats/SoundEngine_2022_1.h" + +AKRESULT FWwiseMemoryMgrAPI_2022_1::Init( + AkMemSettings* in_pSettings +) +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseMemoryMgrAPI_2022_1::Init")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::MemoryMgr::Init(in_pSettings); +} + +void FWwiseMemoryMgrAPI_2022_1::GetDefaultSettings( + AkMemSettings& out_pMemSettings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::MemoryMgr::GetDefaultSettings(out_pMemSettings); +} + +bool FWwiseMemoryMgrAPI_2022_1::IsInitialized() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::MemoryMgr::IsInitialized(); +} + +void FWwiseMemoryMgrAPI_2022_1::Term() +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseMemoryMgrAPI_2022_1::Term")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::MemoryMgr::Term(); +} + +void FWwiseMemoryMgrAPI_2022_1::InitForThread() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::MemoryMgr::InitForThread(); +} + +void FWwiseMemoryMgrAPI_2022_1::TermForThread() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::MemoryMgr::TermForThread(); +} + +void FWwiseMemoryMgrAPI_2022_1::TrimForThread() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); +#if AK_WWISESDK_VERSION_MAJOR == 2022 && AK_WWISESDK_VERSION_MINOR == 1 && AK_WWISESDK_VERSION_SUBMINOR >= 2 + AK::MemoryMgr::TrimForThread(); +#endif +} + +void* FWwiseMemoryMgrAPI_2022_1::dMalloc( + AkMemPoolId in_poolId, + size_t in_uSize, + const char* in_pszFile, + AkUInt32 in_uLine +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); +#ifdef AK_MEMDEBUG + return AK::MemoryMgr::dMalloc(in_poolId, in_uSize, in_pszFile, in_uLine); +#else + return AK::MemoryMgr::Malloc(in_poolId, in_uSize); +#endif +} + +void* FWwiseMemoryMgrAPI_2022_1::Malloc( + AkMemPoolId in_poolId, + size_t in_uSize +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::MemoryMgr::Malloc(in_poolId, in_uSize); +} + +void* FWwiseMemoryMgrAPI_2022_1::dRealloc( + AkMemPoolId in_poolId, + void* in_pAlloc, + size_t in_uSize, + const char* in_pszFile, + AkUInt32 in_uLine +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); +#ifdef AK_MEMDEBUG + return AK::MemoryMgr::dRealloc(in_poolId, in_pAlloc, in_uSize, in_pszFile, in_uLine); +#else + return AK::MemoryMgr::Realloc(in_poolId, in_pAlloc, in_uSize); +#endif +} + +void* FWwiseMemoryMgrAPI_2022_1::Realloc( + AkMemPoolId in_poolId, + void* in_pAlloc, + size_t in_uSize +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::MemoryMgr::Realloc(in_poolId, in_pAlloc, in_uSize); +} + +void* FWwiseMemoryMgrAPI_2022_1::dReallocAligned( + AkMemPoolId in_poolId, + void* in_pAlloc, + size_t in_uSize, + AkUInt32 in_uAlignment, + const char* in_pszFile, + AkUInt32 in_uLine +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); +#ifdef AK_MEMDEBUG + return AK::MemoryMgr::dReallocAligned(in_poolId, in_pAlloc, in_uSize, in_uAlignment, in_pszFile, in_uLine); +#else + return AK::MemoryMgr::ReallocAligned(in_poolId, in_pAlloc, in_uSize, in_uAlignment); +#endif +} + +void* FWwiseMemoryMgrAPI_2022_1::ReallocAligned( + AkMemPoolId in_poolId, + void* in_pAlloc, + size_t in_uSize, + AkUInt32 in_uAlignment +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::MemoryMgr::ReallocAligned(in_poolId, in_pAlloc, in_uSize, in_uAlignment); +} + +void FWwiseMemoryMgrAPI_2022_1::Free( + AkMemPoolId in_poolId, + void* in_pMemAddress +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::MemoryMgr::Free(in_poolId, in_pMemAddress); +} + +void* FWwiseMemoryMgrAPI_2022_1::dMalign( + AkMemPoolId in_poolId, + size_t in_uSize, + AkUInt32 in_uAlignment, + const char* in_pszFile, + AkUInt32 in_uLine +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); +#ifdef AK_MEMDEBUG + return AK::MemoryMgr::dMalign(in_poolId, in_uSize, in_uAlignment, in_pszFile, in_uLine); +#else + return AK::MemoryMgr::Malign(in_poolId, in_uSize, in_uAlignment); +#endif +} + +void* FWwiseMemoryMgrAPI_2022_1::Malign( + AkMemPoolId in_poolId, + size_t in_uSize, + AkUInt32 in_uAlignment +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::MemoryMgr::Malign(in_poolId, in_uSize, in_uAlignment); +} + +void FWwiseMemoryMgrAPI_2022_1::GetCategoryStats( + AkMemPoolId in_poolId, + AK::MemoryMgr::CategoryStats& out_poolStats +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::MemoryMgr::GetCategoryStats(in_poolId, out_poolStats); +} + +void FWwiseMemoryMgrAPI_2022_1::GetGlobalStats( + AK::MemoryMgr::GlobalStats& out_stats +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::MemoryMgr::GetGlobalStats(out_stats); +} + +void FWwiseMemoryMgrAPI_2022_1::StartProfileThreadUsage( +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::MemoryMgr::StartProfileThreadUsage(); +} + +AkUInt64 FWwiseMemoryMgrAPI_2022_1::StopProfileThreadUsage( +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::MemoryMgr::StopProfileThreadUsage(); +} + +void FWwiseMemoryMgrAPI_2022_1::DumpToFile( + const AkOSChar* pszFilename +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::MemoryMgr::DumpToFile(pszFilename); +} + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WwiseMonitorAPI_2022_1.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WwiseMonitorAPI_2022_1.cpp new file mode 100644 index 0000000..05f34b0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WwiseMonitorAPI_2022_1.cpp @@ -0,0 +1,169 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_2022_1/WwiseMonitorAPI_2022_1.h" +#include "Wwise/Stats/SoundEngine_2022_1.h" + +#if AK_SUPPORT_WAAPI && WITH_EDITORONLY_DATA && !defined(AK_OPTIMIZED) +AkWAAPIErrorMessageTranslator FWwiseMonitorAPI_2022_1::WaapiErrorMessageTranslator; +#endif + +AKRESULT FWwiseMonitorAPI_2022_1::PostCode( + AK::Monitor::ErrorCode in_eError, + AK::Monitor::ErrorLevel in_eErrorLevel, + AkPlayingID in_playingID, + AkGameObjectID in_gameObjID, + AkUniqueID in_audioNodeID, + bool in_bIsBus +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::Monitor::PostCode(in_eError, in_eErrorLevel, in_playingID, in_gameObjID, in_audioNodeID, in_bIsBus); +} + +AKRESULT FWwiseMonitorAPI_2022_1::PostCodeVarArg( + AK::Monitor::ErrorCode in_eError, + AK::Monitor::ErrorLevel in_eErrorLevel, + AK::Monitor::MsgContext msgContext, + ... +) +{ + va_list Args; + va_start(Args, msgContext); + auto Result = PostCodeVaList(in_eError, in_eErrorLevel, msgContext, Args); + va_end(Args); + return Result; +} + +AKRESULT FWwiseMonitorAPI_2022_1::PostCodeVaList( + AK::Monitor::ErrorCode in_eError, + AK::Monitor::ErrorLevel in_eErrorLevel, + AK::Monitor::MsgContext msgContext, + ::va_list args +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::Monitor::PostCodeVaList(in_eError, in_eErrorLevel, msgContext, args); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseMonitorAPI_2022_1::PostString( + const wchar_t* in_pszError, + AK::Monitor::ErrorLevel in_eErrorLevel, + AkPlayingID in_playingID, + AkGameObjectID in_gameObjID, + AkUniqueID in_audioNodeID, + bool in_bIsBus +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::Monitor::PostString(in_pszError, in_eErrorLevel, in_playingID, in_gameObjID, in_audioNodeID, in_bIsBus); +} +#endif // #ifdef AK_SUPPORT_WCHAR + +AKRESULT FWwiseMonitorAPI_2022_1::PostString( + const char* in_pszError, + AK::Monitor::ErrorLevel in_eErrorLevel, + AkPlayingID in_playingID, + AkGameObjectID in_gameObjID, + AkUniqueID in_audioNodeID, + bool in_bIsBus +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::Monitor::PostString(in_pszError, in_eErrorLevel, in_playingID, in_gameObjID, in_audioNodeID, in_bIsBus); +} + +AKRESULT FWwiseMonitorAPI_2022_1::SetLocalOutput( + AkUInt32 in_uErrorLevel, + AK::Monitor::LocalOutputFunc in_pMonitorFunc +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::Monitor::SetLocalOutput(in_uErrorLevel, in_pMonitorFunc); +} + +AKRESULT FWwiseMonitorAPI_2022_1::AddTranslator( + AkErrorMessageTranslator* translator, + bool overridePreviousTranslators +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::Monitor::AddTranslator(translator, overridePreviousTranslators); +} + +AKRESULT FWwiseMonitorAPI_2022_1::ResetTranslator( +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::Monitor::ResetTranslator(); +} + +AkTimeMs FWwiseMonitorAPI_2022_1::GetTimeStamp() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::Monitor::GetTimeStamp(); +} + +void FWwiseMonitorAPI_2022_1::MonitorStreamMgrInit( + const AkStreamMgrSettings& in_streamMgrSettings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::Monitor::MonitorStreamMgrInit(in_streamMgrSettings); +} + +void FWwiseMonitorAPI_2022_1::MonitorStreamingDeviceInit( + AkDeviceID in_deviceID, + const AkDeviceSettings& in_deviceSettings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::Monitor::MonitorStreamingDeviceInit(in_deviceID, in_deviceSettings); +} + +void FWwiseMonitorAPI_2022_1::MonitorStreamingDeviceDestroyed( + AkDeviceID in_deviceID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::Monitor::MonitorStreamingDeviceDestroyed(in_deviceID); +} + +void FWwiseMonitorAPI_2022_1::MonitorStreamMgrTerm() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::Monitor::MonitorStreamMgrTerm(); +} + +void FWwiseMonitorAPI_2022_1::SetupDefaultWAAPIErrorTranslator(const FString& WaapiIP, AkUInt32 WaapiPort, AkUInt32 Timeout) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); +#if AK_SUPPORT_WAAPI && WITH_EDITORONLY_DATA && !defined(AK_OPTIMIZED) + WaapiErrorMessageTranslator.SetConnectionIP(TCHAR_TO_ANSI(*WaapiIP), WaapiPort, Timeout); + AddTranslator(&WaapiErrorMessageTranslator); +#endif +} + +void FWwiseMonitorAPI_2022_1::TerminateDefaultWAAPIErrorTranslator() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); +#if AK_SUPPORT_WAAPI && WITH_EDITORONLY_DATA && !defined(AK_OPTIMIZED) + WaapiErrorMessageTranslator.Term(); +#endif +} + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WwiseMusicEngineAPI_2022_1.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WwiseMusicEngineAPI_2022_1.cpp new file mode 100644 index 0000000..35bcfc9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WwiseMusicEngineAPI_2022_1.cpp @@ -0,0 +1,54 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_2022_1/WwiseMusicEngineAPI_2022_1.h" +#include "Wwise/Stats/SoundEngine_2022_1.h" + +AKRESULT FWwiseMusicEngineAPI_2022_1::Init( + AkMusicSettings* in_pSettings +) +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseMusicEngineAPI_2022_1::Init")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::MusicEngine::Init(in_pSettings); +} + +void FWwiseMusicEngineAPI_2022_1::GetDefaultInitSettings( + AkMusicSettings& out_settings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::MusicEngine::GetDefaultInitSettings(out_settings); +} + +void FWwiseMusicEngineAPI_2022_1::Term( +) +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseMusicEngineAPI_2022_1::Term")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::MusicEngine::Term(); +} + +AKRESULT FWwiseMusicEngineAPI_2022_1::GetPlayingSegmentInfo( + AkPlayingID in_PlayingID, + AkSegmentInfo& out_segmentInfo, + bool in_bExtrapolate +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::MusicEngine::GetPlayingSegmentInfo(in_PlayingID, out_segmentInfo, in_bExtrapolate); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WwiseSoundEngineAPI_2022_1.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WwiseSoundEngineAPI_2022_1.cpp new file mode 100644 index 0000000..2010e3f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WwiseSoundEngineAPI_2022_1.cpp @@ -0,0 +1,2340 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_2022_1/WwiseSoundEngineAPI_2022_1.h" +#include "Wwise/Stats/SoundEngine_2022_1.h" + +#include "Wwise/PreSoundEngineInclude.h" + +#include +#include +#include + +#if AK_SUPPORT_OPUS +#include +#endif // AK_SUPPORT_OPUS + +#if PLATFORM_IOS && !PLATFORM_TVOS +#include "Generated/AkiOSPlugins.h" +#endif + +#if PLATFORM_SWITCH +#include "Generated/AkSwitchPlugins.h" +#if AK_SUPPORT_OPUS +#include +#endif +#endif + +#if PLATFORM_TVOS +#include "Generated/AkTVOSPlugins.h" +#endif +#include "Wwise/PostSoundEngineInclude.h" + +FWwiseSoundEngineAPI_2022_1::FWwiseSoundEngineAPI_2022_1(): + IWwiseSoundEngineAPI(new FQuery, new FAudioInputPlugin) +{} + +bool FWwiseSoundEngineAPI_2022_1::IsInitialized() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::IsInitialized(); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::Init( + AkInitSettings* in_pSettings, + AkPlatformInitSettings* in_pPlatformSettings +) +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseSoundEngineAPI_2022_1::Init")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Init(in_pSettings, in_pPlatformSettings); +} + +void FWwiseSoundEngineAPI_2022_1::GetDefaultInitSettings( + AkInitSettings& out_settings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::SoundEngine::GetDefaultInitSettings(out_settings); +} + +void FWwiseSoundEngineAPI_2022_1::GetDefaultPlatformInitSettings( + AkPlatformInitSettings& out_platformSettings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::SoundEngine::GetDefaultPlatformInitSettings(out_platformSettings); +} + +void FWwiseSoundEngineAPI_2022_1::Term() +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseSoundEngineAPI_2022_1::Term")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::SoundEngine::Term(); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::GetAudioSettings( + AkAudioSettings& out_audioSettings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetAudioSettings(out_audioSettings); +} + +AkChannelConfig FWwiseSoundEngineAPI_2022_1::GetSpeakerConfiguration( + AkOutputDeviceID in_idOutput +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetSpeakerConfiguration(in_idOutput); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::GetOutputDeviceConfiguration( + AkOutputDeviceID in_idOutput, + AkChannelConfig& io_channelConfig, + Ak3DAudioSinkCapabilities& io_capabilities +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetOutputDeviceConfiguration(in_idOutput, io_channelConfig, io_capabilities); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::GetPanningRule( + AkPanningRule& out_ePanningRule, + AkOutputDeviceID in_idOutput +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetPanningRule(out_ePanningRule, in_idOutput); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetPanningRule( + AkPanningRule in_ePanningRule, + AkOutputDeviceID in_idOutput +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetPanningRule(in_ePanningRule, in_idOutput); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::GetSpeakerAngles( + AkReal32* io_pfSpeakerAngles, + AkUInt32& io_uNumAngles, + AkReal32& out_fHeightAngle, + AkOutputDeviceID in_idOutput +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetSpeakerAngles(io_pfSpeakerAngles, io_uNumAngles, out_fHeightAngle, in_idOutput); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetSpeakerAngles( + const AkReal32* in_pfSpeakerAngles, + AkUInt32 in_uNumAngles, + AkReal32 in_fHeightAngle, + AkOutputDeviceID in_idOutput +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetSpeakerAngles(in_pfSpeakerAngles, in_uNumAngles, in_fHeightAngle, in_idOutput); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetVolumeThreshold( + AkReal32 in_fVolumeThresholdDB +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetVolumeThreshold(in_fVolumeThresholdDB); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetMaxNumVoicesLimit( + AkUInt16 in_maxNumberVoices +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetMaxNumVoicesLimit(in_maxNumberVoices); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::RenderAudio( + bool in_bAllowSyncRender +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::RenderAudio(in_bAllowSyncRender); +} + +AK::IAkGlobalPluginContext* FWwiseSoundEngineAPI_2022_1::GetGlobalPluginContext() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetGlobalPluginContext(); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::RegisterPlugin( + AkPluginType in_eType, + AkUInt32 in_ulCompanyID, + AkUInt32 in_ulPluginID, + AkCreatePluginCallback in_pCreateFunc, + AkCreateParamCallback in_pCreateParamFunc, + AkGetDeviceListCallback in_pGetDeviceList +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::RegisterPlugin(in_eType, in_ulCompanyID, in_ulPluginID, in_pCreateFunc, in_pCreateParamFunc, in_pGetDeviceList); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::RegisterPluginDLL( + const AkOSChar* in_DllName, + const AkOSChar* in_DllPath +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::RegisterPluginDLL(in_DllName, in_DllPath); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::RegisterCodec( + AkUInt32 in_ulCompanyID, + AkUInt32 in_ulCodecID, + AkCreateFileSourceCallback in_pFileCreateFunc, + AkCreateBankSourceCallback in_pBankCreateFunc +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::RegisterCodec(in_ulCompanyID, in_ulCodecID, in_pFileCreateFunc, in_pBankCreateFunc); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::RegisterGlobalCallback( + AkGlobalCallbackFunc in_pCallback, + AkUInt32 in_eLocation, + void* in_pCookie, + AkPluginType in_eType, + AkUInt32 in_ulCompanyID, + AkUInt32 in_ulPluginID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::RegisterGlobalCallback(in_pCallback, in_eLocation, in_pCookie, in_eType, in_ulCompanyID, in_ulPluginID); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::UnregisterGlobalCallback( + AkGlobalCallbackFunc in_pCallback, + AkUInt32 in_eLocation +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::UnregisterGlobalCallback(in_pCallback, in_eLocation); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::RegisterResourceMonitorCallback( + AkResourceMonitorCallbackFunc in_pCallback +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::RegisterResourceMonitorCallback(in_pCallback); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::UnregisterResourceMonitorCallback( + AkResourceMonitorCallbackFunc in_pCallback +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::UnregisterResourceMonitorCallback(in_pCallback); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::RegisterAudioDeviceStatusCallback( + AK::AkDeviceStatusCallbackFunc in_pCallback +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::RegisterAudioDeviceStatusCallback(in_pCallback); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::UnregisterAudioDeviceStatusCallback() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::UnregisterAudioDeviceStatusCallback(); +} + +#ifdef AK_SUPPORT_WCHAR +AkUInt32 FWwiseSoundEngineAPI_2022_1::GetIDFromString(const wchar_t* in_pszString) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetIDFromString(in_pszString); +} +#endif //AK_SUPPORT_WCHAR + +AkUInt32 FWwiseSoundEngineAPI_2022_1::GetIDFromString(const char* in_pszString) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetIDFromString(in_pszString); +} + +AkPlayingID FWwiseSoundEngineAPI_2022_1::PostEvent( + AkUniqueID in_eventID, + AkGameObjectID in_gameObjectID, + AkUInt32 in_uFlags, + AkCallbackFunc in_pfnCallback, + void* in_pCookie, + AkUInt32 in_cExternals, + AkExternalSourceInfo* in_pExternalSources, + AkPlayingID in_PlayingID +) +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseSoundEngineAPI_2022_1::PostEvent")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PostEvent(in_eventID, in_gameObjectID, in_uFlags, in_pfnCallback, in_pCookie, in_cExternals, in_pExternalSources, in_PlayingID); +} + +#ifdef AK_SUPPORT_WCHAR +AkPlayingID FWwiseSoundEngineAPI_2022_1::PostEvent( + const wchar_t* in_pszEventName, + AkGameObjectID in_gameObjectID, + AkUInt32 in_uFlags, + AkCallbackFunc in_pfnCallback, + void* in_pCookie, + AkUInt32 in_cExternals, + AkExternalSourceInfo* in_pExternalSources, + AkPlayingID in_PlayingID +) +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseSoundEngineAPI_2022_1::PostEvent")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PostEvent(in_pszEventName, in_gameObjectID, in_uFlags, in_pfnCallback, in_pCookie, in_cExternals, in_pExternalSources, in_PlayingID); +} +#endif //AK_SUPPORT_WCHAR + +AkPlayingID FWwiseSoundEngineAPI_2022_1::PostEvent( + const char* in_pszEventName, + AkGameObjectID in_gameObjectID, + AkUInt32 in_uFlags, + AkCallbackFunc in_pfnCallback, + void* in_pCookie, + AkUInt32 in_cExternals, + AkExternalSourceInfo* in_pExternalSources, + AkPlayingID in_PlayingID +) +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseSoundEngineAPI_2022_1::PostEvent")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PostEvent(in_pszEventName, in_gameObjectID, in_uFlags, in_pfnCallback, in_pCookie, in_cExternals, in_pExternalSources, in_PlayingID); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::ExecuteActionOnEvent( + AkUniqueID in_eventID, + AK::SoundEngine::AkActionOnEventType in_ActionType, + AkGameObjectID in_gameObjectID, + AkTimeMs in_uTransitionDuration, + AkCurveInterpolation in_eFadeCurve, + AkPlayingID in_PlayingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::ExecuteActionOnEvent(in_eventID, in_ActionType, in_gameObjectID, in_uTransitionDuration, in_eFadeCurve, in_PlayingID); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::ExecuteActionOnEvent( + const wchar_t* in_pszEventName, + AK::SoundEngine::AkActionOnEventType in_ActionType, + AkGameObjectID in_gameObjectID, + AkTimeMs in_uTransitionDuration, + AkCurveInterpolation in_eFadeCurve, + AkPlayingID in_PlayingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::ExecuteActionOnEvent(in_pszEventName, in_ActionType, in_gameObjectID, in_uTransitionDuration, in_eFadeCurve, in_PlayingID); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::ExecuteActionOnEvent( + const char* in_pszEventName, + AK::SoundEngine::AkActionOnEventType in_ActionType, + AkGameObjectID in_gameObjectID, + AkTimeMs in_uTransitionDuration, + AkCurveInterpolation in_eFadeCurve, + AkPlayingID in_PlayingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::ExecuteActionOnEvent(in_pszEventName, in_ActionType, in_gameObjectID, in_uTransitionDuration, in_eFadeCurve, in_PlayingID); +} + +AkPlayingID FWwiseSoundEngineAPI_2022_1::PostMIDIOnEvent( + AkUniqueID in_eventID, + AkGameObjectID in_gameObjectID, + AkMIDIPost* in_pPosts, + AkUInt16 in_uNumPosts, + bool in_bAbsoluteOffsets, + AkUInt32 in_uFlags, + AkCallbackFunc in_pfnCallback, + void* in_pCookie, + AkPlayingID in_playingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PostMIDIOnEvent(in_eventID, in_gameObjectID, in_pPosts, in_uNumPosts, in_bAbsoluteOffsets, in_uFlags, in_pfnCallback, in_pCookie, in_playingID); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::StopMIDIOnEvent( + AkUniqueID in_eventID, + AkGameObjectID in_gameObjectID, + AkPlayingID in_playingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::StopMIDIOnEvent(in_eventID, in_gameObjectID, in_playingID); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::PinEventInStreamCache( + AkUniqueID in_eventID, + AkPriority in_uActivePriority, + AkPriority in_uInactivePriority +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PinEventInStreamCache(in_eventID, in_uActivePriority, in_uInactivePriority); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::PinEventInStreamCache( + const wchar_t* in_pszEventName, + AkPriority in_uActivePriority, + AkPriority in_uInactivePriority +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PinEventInStreamCache(in_pszEventName, in_uActivePriority, in_uInactivePriority); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::PinEventInStreamCache( + const char* in_pszEventName, + AkPriority in_uActivePriority, + AkPriority in_uInactivePriority +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PinEventInStreamCache(in_pszEventName, in_uActivePriority, in_uInactivePriority); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::UnpinEventInStreamCache( + AkUniqueID in_eventID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::UnpinEventInStreamCache(in_eventID); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::UnpinEventInStreamCache( + const wchar_t* in_pszEventName +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::UnpinEventInStreamCache(in_pszEventName); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::UnpinEventInStreamCache( + const char* in_pszEventName +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::UnpinEventInStreamCache(in_pszEventName); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::GetBufferStatusForPinnedEvent( + AkUniqueID in_eventID, + AkReal32& out_fPercentBuffered, + bool& out_bCachePinnedMemoryFull +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetBufferStatusForPinnedEvent(in_eventID, out_fPercentBuffered, out_bCachePinnedMemoryFull); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::GetBufferStatusForPinnedEvent( + const char* in_pszEventName, + AkReal32& out_fPercentBuffered, + bool& out_bCachePinnedMemoryFull +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetBufferStatusForPinnedEvent(in_pszEventName, out_fPercentBuffered, out_bCachePinnedMemoryFull); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::GetBufferStatusForPinnedEvent( + const wchar_t* in_pszEventName, + AkReal32& out_fPercentBuffered, + bool& out_bCachePinnedMemoryFull +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetBufferStatusForPinnedEvent(in_pszEventName, out_fPercentBuffered, out_bCachePinnedMemoryFull); +} +#endif + +AKRESULT FWwiseSoundEngineAPI_2022_1::SeekOnEvent( + AkUniqueID in_eventID, + AkGameObjectID in_gameObjectID, + AkTimeMs in_iPosition, + bool in_bSeekToNearestMarker, + AkPlayingID in_PlayingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SeekOnEvent(in_eventID, in_gameObjectID, in_iPosition, in_bSeekToNearestMarker, in_PlayingID); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::SeekOnEvent( + const wchar_t* in_pszEventName, + AkGameObjectID in_gameObjectID, + AkTimeMs in_iPosition, + bool in_bSeekToNearestMarker, + AkPlayingID in_PlayingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SeekOnEvent(in_pszEventName, in_gameObjectID, in_iPosition, in_bSeekToNearestMarker, in_PlayingID); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::SeekOnEvent( + const char* in_pszEventName, + AkGameObjectID in_gameObjectID, + AkTimeMs in_iPosition, + bool in_bSeekToNearestMarker, + AkPlayingID in_PlayingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SeekOnEvent(in_pszEventName, in_gameObjectID, in_iPosition, in_bSeekToNearestMarker, in_PlayingID); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SeekOnEvent( + AkUniqueID in_eventID, + AkGameObjectID in_gameObjectID, + AkReal32 in_fPercent, + bool in_bSeekToNearestMarker, + AkPlayingID in_PlayingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SeekOnEvent(in_eventID, in_gameObjectID, in_fPercent, in_bSeekToNearestMarker, in_PlayingID); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::SeekOnEvent( + const wchar_t* in_pszEventName, + AkGameObjectID in_gameObjectID, + AkReal32 in_fPercent, + bool in_bSeekToNearestMarker, + AkPlayingID in_PlayingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SeekOnEvent(in_pszEventName, in_gameObjectID, in_fPercent, in_bSeekToNearestMarker, in_PlayingID); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::SeekOnEvent( + const char* in_pszEventName, + AkGameObjectID in_gameObjectID, + AkReal32 in_fPercent, + bool in_bSeekToNearestMarker, + AkPlayingID in_PlayingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SeekOnEvent(in_pszEventName, in_gameObjectID, in_fPercent, in_bSeekToNearestMarker, in_PlayingID); +} + +void FWwiseSoundEngineAPI_2022_1::CancelEventCallbackCookie( + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::SoundEngine::CancelEventCallbackCookie(in_pCookie); +} + +void FWwiseSoundEngineAPI_2022_1::CancelEventCallbackGameObject( + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::SoundEngine::CancelEventCallbackGameObject(in_gameObjectID); +} + +void FWwiseSoundEngineAPI_2022_1::CancelEventCallback( + AkPlayingID in_playingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::SoundEngine::CancelEventCallback(in_playingID); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::GetSourcePlayPosition( + AkPlayingID in_PlayingID, + AkTimeMs* out_puPosition, + bool in_bExtrapolate +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetSourcePlayPosition(in_PlayingID, out_puPosition, in_bExtrapolate); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::GetSourcePlayPositions( + AkPlayingID in_PlayingID, + AkSourcePosition* out_puPositions, + AkUInt32* io_pcPositions, + bool in_bExtrapolate +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetSourcePlayPositions(in_PlayingID, out_puPositions, io_pcPositions, in_bExtrapolate); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::GetSourceStreamBuffering( + AkPlayingID in_PlayingID, + AkTimeMs& out_buffering, + bool& out_bIsBuffering +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetSourceStreamBuffering(in_PlayingID, out_buffering, out_bIsBuffering); +} + +void FWwiseSoundEngineAPI_2022_1::StopAll( + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::SoundEngine::StopAll(in_gameObjectID); +} + +void FWwiseSoundEngineAPI_2022_1::StopPlayingID( + AkPlayingID in_playingID, + AkTimeMs in_uTransitionDuration, + AkCurveInterpolation in_eFadeCurve +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::SoundEngine::StopPlayingID(in_playingID, in_uTransitionDuration, in_eFadeCurve); +} + +void FWwiseSoundEngineAPI_2022_1::ExecuteActionOnPlayingID( + AK::SoundEngine::AkActionOnEventType in_ActionType, + AkPlayingID in_playingID, + AkTimeMs in_uTransitionDuration, + AkCurveInterpolation in_eFadeCurve +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::SoundEngine::ExecuteActionOnPlayingID(in_ActionType, in_playingID, in_uTransitionDuration, in_eFadeCurve); +} + +void FWwiseSoundEngineAPI_2022_1::SetRandomSeed( + AkUInt32 in_uSeed +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::SoundEngine::SetRandomSeed(in_uSeed); +} + +void FWwiseSoundEngineAPI_2022_1::MuteBackgroundMusic( + bool in_bMute +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::SoundEngine::MuteBackgroundMusic(in_bMute); +} + +bool FWwiseSoundEngineAPI_2022_1::GetBackgroundMusicMute() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetBackgroundMusicMute(); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SendPluginCustomGameData( + AkUniqueID in_busID, + AkGameObjectID in_busObjectID, + AkPluginType in_eType, + AkUInt32 in_uCompanyID, + AkUInt32 in_uPluginID, + const void* in_pData, + AkUInt32 in_uSizeInBytes +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SendPluginCustomGameData(in_busID, in_busObjectID, in_eType, in_uCompanyID, in_uPluginID, in_pData, in_uSizeInBytes); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::RegisterGameObj( + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::RegisterGameObj(in_gameObjectID); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::RegisterGameObj( + AkGameObjectID in_gameObjectID, + const char* in_pszObjName +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::RegisterGameObj(in_gameObjectID, in_pszObjName); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::UnregisterGameObj( + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::UnregisterGameObj(in_gameObjectID); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::UnregisterAllGameObj( +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::UnregisterAllGameObj(); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetPosition( + AkGameObjectID in_GameObjectID, + const AkSoundPosition& in_Position, + AkSetPositionFlags in_eFlags +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetPosition(in_GameObjectID, in_Position, in_eFlags); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetMultiplePositions( + AkGameObjectID in_GameObjectID, + const AkSoundPosition* in_pPositions, + AkUInt16 in_NumPositions, + AK::SoundEngine::MultiPositionType in_eMultiPositionType, + AkSetPositionFlags in_eFlags +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetMultiplePositions(in_GameObjectID, in_pPositions, in_NumPositions, in_eMultiPositionType, in_eFlags); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetMultiplePositions( + AkGameObjectID in_GameObjectID, + const AkChannelEmitter* in_pPositions, + AkUInt16 in_NumPositions, + AK::SoundEngine::MultiPositionType in_eMultiPositionType, + AkSetPositionFlags in_eFlags +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetMultiplePositions(in_GameObjectID, in_pPositions, in_NumPositions, in_eMultiPositionType, in_eFlags); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetScalingFactor( + AkGameObjectID in_GameObjectID, + AkReal32 in_fAttenuationScalingFactor +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetScalingFactor(in_GameObjectID, in_fAttenuationScalingFactor); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetDistanceProbe( + AkGameObjectID in_listenerGameObjectID, + AkGameObjectID in_distanceProbeGameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetDistanceProbe(in_listenerGameObjectID, in_distanceProbeGameObjectID); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::ClearBanks() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::ClearBanks(); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetBankLoadIOSettings( + AkReal32 in_fThroughput, + AkPriority in_priority +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetBankLoadIOSettings(in_fThroughput, in_priority); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::LoadBank( + const wchar_t* in_pszString, + AkBankID& out_bankID, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::LoadBank(in_pszString, out_bankID, in_bankType); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::LoadBank( + const char* in_pszString, + AkBankID& out_bankID, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::LoadBank(in_pszString, out_bankID, in_bankType); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::LoadBank( + AkBankID in_bankID, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::LoadBank(in_bankID, in_bankType); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::LoadBankMemoryView( + const void* in_pInMemoryBankPtr, + AkUInt32 in_uInMemoryBankSize, + AkBankID& out_bankID) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::LoadBankMemoryView(in_pInMemoryBankPtr, in_uInMemoryBankSize, out_bankID); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::LoadBankMemoryView( + const void* in_pInMemoryBankPtr, + AkUInt32 in_uInMemoryBankSize, + AkBankID& out_bankID, + AkBankType& out_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::LoadBankMemoryView(in_pInMemoryBankPtr, in_uInMemoryBankSize, out_bankID, out_bankType); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::LoadBankMemoryCopy( + const void* in_pInMemoryBankPtr, + AkUInt32 in_uInMemoryBankSize, + AkBankID& out_bankID) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::LoadBankMemoryCopy(in_pInMemoryBankPtr, in_uInMemoryBankSize, out_bankID); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::LoadBankMemoryCopy( + const void* in_pInMemoryBankPtr, + AkUInt32 in_uInMemoryBankSize, + AkBankID& out_bankID, + AkBankType& out_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::LoadBankMemoryCopy(in_pInMemoryBankPtr, in_uInMemoryBankSize, out_bankID, out_bankType); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::DecodeBank( + const void* in_pInMemoryBankPtr, + AkUInt32 in_uInMemoryBankSize, + AkMemPoolId in_uPoolForDecodedBank, + void*& out_pDecodedBankPtr, + AkUInt32& out_uDecodedBankSize +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::DecodeBank(in_pInMemoryBankPtr, in_uInMemoryBankSize, in_uPoolForDecodedBank, out_pDecodedBankPtr, out_uDecodedBankSize); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::LoadBank( + const wchar_t* in_pszString, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + AkBankID& out_bankID, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::LoadBank(in_pszString, in_pfnBankCallback, in_pCookie, out_bankID, in_bankType); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::LoadBank( + const char* in_pszString, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + AkBankID& out_bankID, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::LoadBank(in_pszString, in_pfnBankCallback, in_pCookie, out_bankID, in_bankType); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::LoadBank( + AkBankID in_bankID, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::LoadBank(in_bankID, in_pfnBankCallback, in_pCookie, in_bankType); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::LoadBankMemoryView( + const void* in_pInMemoryBankPtr, + AkUInt32 in_uInMemoryBankSize, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + AkBankID& out_bankID, + AkBankType& out_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::LoadBankMemoryView(in_pInMemoryBankPtr, in_uInMemoryBankSize, in_pfnBankCallback, in_pCookie, out_bankID, out_bankType); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::LoadBankMemoryCopy( + const void* in_pInMemoryBankPtr, + AkUInt32 in_uInMemoryBankSize, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + AkBankID& out_bankID, + AkBankType& out_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::LoadBankMemoryCopy(in_pInMemoryBankPtr, in_uInMemoryBankSize, in_pfnBankCallback, in_pCookie, out_bankID, out_bankType); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::UnloadBank( + const wchar_t* in_pszString, + const void* in_pInMemoryBankPtr, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::UnloadBank(in_pszString, in_pInMemoryBankPtr, in_bankType); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::UnloadBank( + const char* in_pszString, + const void* in_pInMemoryBankPtr, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::UnloadBank(in_pszString, in_pInMemoryBankPtr, in_bankType); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::UnloadBank( + AkBankID in_bankID, + const void* in_pInMemoryBankPtr, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::UnloadBank(in_bankID, in_pInMemoryBankPtr, in_bankType); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::UnloadBank( + const wchar_t* in_pszString, + const void* in_pInMemoryBankPtr, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::UnloadBank(in_pszString, in_pInMemoryBankPtr, in_pfnBankCallback, in_pCookie, in_bankType); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::UnloadBank( + const char* in_pszString, + const void* in_pInMemoryBankPtr, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::UnloadBank(in_pszString, in_pInMemoryBankPtr, in_pfnBankCallback, in_pCookie, in_bankType); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::UnloadBank( + AkBankID in_bankID, + const void* in_pInMemoryBankPtr, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::UnloadBank(in_bankID, in_pInMemoryBankPtr, in_pfnBankCallback, in_pCookie, in_bankType); +} + +void FWwiseSoundEngineAPI_2022_1::CancelBankCallbackCookie( + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::CancelBankCallbackCookie(in_pCookie); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, + const wchar_t* in_pszString, + AK::SoundEngine::AkBankContent in_uFlags, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PrepareBank(in_PreparationType, in_pszString, in_uFlags, in_bankType); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, + const char* in_pszString, + AK::SoundEngine::AkBankContent in_uFlags, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PrepareBank(in_PreparationType, in_pszString, in_uFlags, in_bankType); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, + AkBankID in_bankID, + AK::SoundEngine::AkBankContent in_uFlags, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PrepareBank(in_PreparationType, in_bankID, in_uFlags, in_bankType); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, + const wchar_t* in_pszString, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + AK::SoundEngine::AkBankContent in_uFlags, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PrepareBank(in_PreparationType, in_pszString, in_pfnBankCallback, in_pCookie, in_uFlags, in_bankType); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, + const char* in_pszString, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + AK::SoundEngine::AkBankContent in_uFlags, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PrepareBank(in_PreparationType, in_pszString, in_pfnBankCallback, in_pCookie, in_uFlags, in_bankType); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, + AkBankID in_bankID, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + AK::SoundEngine::AkBankContent in_uFlags, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PrepareBank(in_PreparationType, in_bankID, in_pfnBankCallback, in_pCookie, in_uFlags, in_bankType); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::ClearPreparedEvents() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::ClearPreparedEvents(); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, + const wchar_t** in_ppszString, + AkUInt32 in_uNumEvent +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PrepareEvent(in_PreparationType, in_ppszString, in_uNumEvent); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, + const char** in_ppszString, + AkUInt32 in_uNumEvent +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PrepareEvent(in_PreparationType, in_ppszString, in_uNumEvent); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, + AkUniqueID* in_pEventID, + AkUInt32 in_uNumEvent +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PrepareEvent(in_PreparationType, in_pEventID, in_uNumEvent); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, + const wchar_t** in_ppszString, + AkUInt32 in_uNumEvent, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PrepareEvent(in_PreparationType, in_ppszString, in_uNumEvent, in_pfnBankCallback, in_pCookie); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, + const char** in_ppszString, + AkUInt32 in_uNumEvent, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PrepareEvent(in_PreparationType, in_ppszString, in_uNumEvent, in_pfnBankCallback, in_pCookie); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, + AkUniqueID* in_pEventID, + AkUInt32 in_uNumEvent, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PrepareEvent(in_PreparationType, in_pEventID, in_uNumEvent, in_pfnBankCallback, in_pCookie); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetMedia( + AkSourceSettings* in_pSourceSettings, + AkUInt32 in_uNumSourceSettings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetMedia(in_pSourceSettings, in_uNumSourceSettings); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::UnsetMedia( + AkSourceSettings* in_pSourceSettings, + AkUInt32 in_uNumSourceSettings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::UnsetMedia(in_pSourceSettings, in_uNumSourceSettings); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::TryUnsetMedia( + AkSourceSettings* in_pSourceSettings, + AkUInt32 in_uNumSourceSettings, + AKRESULT* out_pUnsetResults +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::TryUnsetMedia(in_pSourceSettings, in_uNumSourceSettings, out_pUnsetResults); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, + AkGroupType in_eGameSyncType, + const wchar_t* in_pszGroupName, + const wchar_t** in_ppszGameSyncName, + AkUInt32 in_uNumGameSyncs +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PrepareGameSyncs(in_PreparationType, in_eGameSyncType, in_pszGroupName, in_ppszGameSyncName, in_uNumGameSyncs); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, + AkGroupType in_eGameSyncType, + const char* in_pszGroupName, + const char** in_ppszGameSyncName, + AkUInt32 in_uNumGameSyncs +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PrepareGameSyncs(in_PreparationType, in_eGameSyncType, in_pszGroupName, in_ppszGameSyncName, in_uNumGameSyncs); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, + AkGroupType in_eGameSyncType, + AkUInt32 in_GroupID, + AkUInt32* in_paGameSyncID, + AkUInt32 in_uNumGameSyncs +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PrepareGameSyncs(in_PreparationType, in_eGameSyncType, in_GroupID, in_paGameSyncID, in_uNumGameSyncs); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, + AkGroupType in_eGameSyncType, + const wchar_t* in_pszGroupName, + const wchar_t** in_ppszGameSyncName, + AkUInt32 in_uNumGameSyncs, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PrepareGameSyncs(in_PreparationType, in_eGameSyncType, in_pszGroupName, in_ppszGameSyncName, in_uNumGameSyncs, in_pfnBankCallback, in_pCookie); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, + AkGroupType in_eGameSyncType, + const char* in_pszGroupName, + const char** in_ppszGameSyncName, + AkUInt32 in_uNumGameSyncs, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PrepareGameSyncs(in_PreparationType, in_eGameSyncType, in_pszGroupName, in_ppszGameSyncName, in_uNumGameSyncs, in_pfnBankCallback, in_pCookie); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, + AkGroupType in_eGameSyncType, + AkUInt32 in_GroupID, + AkUInt32* in_paGameSyncID, + AkUInt32 in_uNumGameSyncs, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PrepareGameSyncs(in_PreparationType, in_eGameSyncType, in_GroupID, in_paGameSyncID, in_uNumGameSyncs, in_pfnBankCallback, in_pCookie); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetListeners( + AkGameObjectID in_emitterGameObj, + const AkGameObjectID* in_pListenerGameObjs, + AkUInt32 in_uNumListeners +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetListeners(in_emitterGameObj, in_pListenerGameObjs, in_uNumListeners); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::AddListener( + AkGameObjectID in_emitterGameObj, + AkGameObjectID in_listenerGameObj +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::AddListener(in_emitterGameObj, in_listenerGameObj); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::RemoveListener( + AkGameObjectID in_emitterGameObj, + AkGameObjectID in_listenerGameObj +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::RemoveListener(in_emitterGameObj, in_listenerGameObj); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetDefaultListeners( + const AkGameObjectID* in_pListenerObjs, + AkUInt32 in_uNumListeners +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetDefaultListeners(in_pListenerObjs, in_uNumListeners); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::AddDefaultListener( + AkGameObjectID in_listenerGameObj +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::AddDefaultListener(in_listenerGameObj); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::RemoveDefaultListener( + AkGameObjectID in_listenerGameObj +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::RemoveDefaultListener(in_listenerGameObj); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::ResetListenersToDefault( + AkGameObjectID in_emitterGameObj +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::ResetListenersToDefault(in_emitterGameObj); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetListenerSpatialization( + AkGameObjectID in_uListenerID, + bool in_bSpatialized, + AkChannelConfig in_channelConfig, + AK::SpeakerVolumes::VectorPtr in_pVolumeOffsets +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetListenerSpatialization(in_uListenerID, in_bSpatialized, in_channelConfig, in_pVolumeOffsets); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetRTPCValue( + AkRtpcID in_rtpcID, + AkRtpcValue in_value, + AkGameObjectID in_gameObjectID, + AkTimeMs in_uValueChangeDuration, + AkCurveInterpolation in_eFadeCurve, + bool in_bBypassInternalValueInterpolation +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetRTPCValue(in_rtpcID, in_value, in_gameObjectID, in_uValueChangeDuration, in_eFadeCurve, in_bBypassInternalValueInterpolation); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::SetRTPCValue( + const wchar_t* in_pszRtpcName, + AkRtpcValue in_value, + AkGameObjectID in_gameObjectID, + AkTimeMs in_uValueChangeDuration, + AkCurveInterpolation in_eFadeCurve, + bool in_bBypassInternalValueInterpolation +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetRTPCValue(in_pszRtpcName, in_value, in_gameObjectID, in_uValueChangeDuration, in_eFadeCurve, in_bBypassInternalValueInterpolation); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetRTPCValue( + const char* in_pszRtpcName, + AkRtpcValue in_value, + AkGameObjectID in_gameObjectID, + AkTimeMs in_uValueChangeDuration, + AkCurveInterpolation in_eFadeCurve, + bool in_bBypassInternalValueInterpolation +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetRTPCValue(in_pszRtpcName, in_value, in_gameObjectID, in_uValueChangeDuration, in_eFadeCurve, in_bBypassInternalValueInterpolation); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetRTPCValueByPlayingID( + AkRtpcID in_rtpcID, + AkRtpcValue in_value, + AkPlayingID in_playingID, + AkTimeMs in_uValueChangeDuration, + AkCurveInterpolation in_eFadeCurve, + bool in_bBypassInternalValueInterpolation +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetRTPCValueByPlayingID(in_rtpcID, in_value, in_playingID, in_uValueChangeDuration, in_eFadeCurve, in_bBypassInternalValueInterpolation); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::SetRTPCValueByPlayingID( + const wchar_t* in_pszRtpcName, + AkRtpcValue in_value, + AkPlayingID in_playingID, + AkTimeMs in_uValueChangeDuration, + AkCurveInterpolation in_eFadeCurve, + bool in_bBypassInternalValueInterpolation +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetRTPCValueByPlayingID(in_pszRtpcName, in_value, in_playingID, in_uValueChangeDuration, in_eFadeCurve, in_bBypassInternalValueInterpolation); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetRTPCValueByPlayingID( + const char* in_pszRtpcName, + AkRtpcValue in_value, + AkPlayingID in_playingID, + AkTimeMs in_uValueChangeDuration, + AkCurveInterpolation in_eFadeCurve, + bool in_bBypassInternalValueInterpolation +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetRTPCValueByPlayingID(in_pszRtpcName, in_value, in_playingID, in_uValueChangeDuration, in_eFadeCurve, in_bBypassInternalValueInterpolation); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::ResetRTPCValue( + AkRtpcID in_rtpcID, + AkGameObjectID in_gameObjectID, + AkTimeMs in_uValueChangeDuration, + AkCurveInterpolation in_eFadeCurve, + bool in_bBypassInternalValueInterpolation +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::ResetRTPCValue(in_rtpcID, in_gameObjectID, in_uValueChangeDuration, in_eFadeCurve, in_bBypassInternalValueInterpolation); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::ResetRTPCValue( + const wchar_t* in_pszRtpcName, + AkGameObjectID in_gameObjectID, + AkTimeMs in_uValueChangeDuration, + AkCurveInterpolation in_eFadeCurve, + bool in_bBypassInternalValueInterpolation +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::ResetRTPCValue(in_pszRtpcName, in_gameObjectID, in_uValueChangeDuration, in_eFadeCurve, in_bBypassInternalValueInterpolation); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::ResetRTPCValue( + const char* in_pszRtpcName, + AkGameObjectID in_gameObjectID, + AkTimeMs in_uValueChangeDuration, + AkCurveInterpolation in_eFadeCurve, + bool in_bBypassInternalValueInterpolation +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::ResetRTPCValue(in_pszRtpcName, in_gameObjectID, in_uValueChangeDuration, in_eFadeCurve, in_bBypassInternalValueInterpolation); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetSwitch( + AkSwitchGroupID in_switchGroup, + AkSwitchStateID in_switchState, + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetSwitch(in_switchGroup, in_switchState, in_gameObjectID); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::SetSwitch( + const wchar_t* in_pszSwitchGroup, + const wchar_t* in_pszSwitchState, + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetSwitch(in_pszSwitchGroup, in_pszSwitchState, in_gameObjectID); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetSwitch( + const char* in_pszSwitchGroup, + const char* in_pszSwitchState, + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetSwitch(in_pszSwitchGroup, in_pszSwitchState, in_gameObjectID); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::PostTrigger( + AkTriggerID in_triggerID, + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PostTrigger(in_triggerID, in_gameObjectID); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::PostTrigger( + const wchar_t* in_pszTrigger, + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PostTrigger(in_pszTrigger, in_gameObjectID); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::PostTrigger( + const char* in_pszTrigger, + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::PostTrigger(in_pszTrigger, in_gameObjectID); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetState( + AkStateGroupID in_stateGroup, + AkStateID in_state +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetState(in_stateGroup, in_state); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::SetState( + const wchar_t* in_pszStateGroup, + const wchar_t* in_pszState +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetState(in_pszStateGroup, in_pszState); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetState( + const char* in_pszStateGroup, + const char* in_pszState +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetState(in_pszStateGroup, in_pszState); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetGameObjectAuxSendValues( + AkGameObjectID in_gameObjectID, + AkAuxSendValue* in_aAuxSendValues, + AkUInt32 in_uNumSendValues +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetGameObjectAuxSendValues(in_gameObjectID, in_aAuxSendValues, in_uNumSendValues); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::RegisterBusVolumeCallback( + AkUniqueID in_busID, + AkBusCallbackFunc in_pfnCallback, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::RegisterBusVolumeCallback(in_busID, in_pfnCallback, in_pCookie); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::RegisterBusMeteringCallback( + AkUniqueID in_busID, + AkBusMeteringCallbackFunc in_pfnCallback, + AkMeteringFlags in_eMeteringFlags, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::RegisterBusMeteringCallback(in_busID, in_pfnCallback, in_eMeteringFlags, in_pCookie); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::RegisterOutputDeviceMeteringCallback( + AkOutputDeviceID in_idOutput, + AkOutputDeviceMeteringCallbackFunc in_pfnCallback, + AkMeteringFlags in_eMeteringFlags, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::RegisterOutputDeviceMeteringCallback(in_idOutput, in_pfnCallback, in_eMeteringFlags, in_pCookie); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetGameObjectOutputBusVolume( + AkGameObjectID in_emitterObjID, + AkGameObjectID in_listenerObjID, + AkReal32 in_fControlValue +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetGameObjectOutputBusVolume(in_emitterObjID, in_listenerObjID, in_fControlValue); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetActorMixerEffect( + AkUniqueID in_audioNodeID, + AkUInt32 in_uFXIndex, + AkUniqueID in_shareSetID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetActorMixerEffect(in_audioNodeID, in_uFXIndex, in_shareSetID); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetBusEffect( + AkUniqueID in_audioNodeID, + AkUInt32 in_uFXIndex, + AkUniqueID in_shareSetID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetBusEffect(in_audioNodeID, in_uFXIndex, in_shareSetID); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::SetBusEffect( + const wchar_t* in_pszBusName, + AkUInt32 in_uFXIndex, + AkUniqueID in_shareSetID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetBusEffect(in_pszBusName, in_uFXIndex, in_shareSetID); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetBusEffect( + const char* in_pszBusName, + AkUInt32 in_uFXIndex, + AkUniqueID in_shareSetID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetBusEffect(in_pszBusName, in_uFXIndex, in_shareSetID); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetOutputDeviceEffect( + AkOutputDeviceID in_outputDeviceID, + AkUInt32 in_uFXIndex, + AkUniqueID in_FXShareSetID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetOutputDeviceEffect(in_outputDeviceID, in_uFXIndex, in_FXShareSetID); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetMixer( + AkUniqueID in_audioNodeID, + AkUniqueID in_shareSetID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetMixer(in_audioNodeID, in_shareSetID); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::SetMixer( + const wchar_t* in_pszBusName, + AkUniqueID in_shareSetID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetMixer(in_pszBusName, in_shareSetID); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetMixer( + const char* in_pszBusName, + AkUniqueID in_shareSetID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetMixer(in_pszBusName, in_shareSetID); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetBusConfig( + AkUniqueID in_audioNodeID, + AkChannelConfig in_channelConfig +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetBusConfig(in_audioNodeID, in_channelConfig); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::SetBusConfig( + const wchar_t* in_pszBusName, + AkChannelConfig in_channelConfig +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetBusConfig(in_pszBusName, in_channelConfig); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetBusConfig( + const char* in_pszBusName, + AkChannelConfig in_channelConfig +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetBusConfig(in_pszBusName, in_channelConfig); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetObjectObstructionAndOcclusion( + AkGameObjectID in_EmitterID, + AkGameObjectID in_ListenerID, + AkReal32 in_fObstructionLevel, + AkReal32 in_fOcclusionLevel +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetObjectObstructionAndOcclusion(in_EmitterID, in_ListenerID, in_fObstructionLevel, in_fOcclusionLevel); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetMultipleObstructionAndOcclusion( + AkGameObjectID in_EmitterID, + AkGameObjectID in_uListenerID, + AkObstructionOcclusionValues* in_fObstructionAndOcclusionValues, + AkUInt32 in_uNumObstructionAndOcclusion +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetMultipleObstructionAndOcclusion(in_EmitterID, in_uListenerID, in_fObstructionAndOcclusionValues, in_uNumObstructionAndOcclusion); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::GetContainerHistory( + AK::IWriteBytes* in_pBytes +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetContainerHistory(in_pBytes); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetContainerHistory( + AK::IReadBytes* in_pBytes +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetContainerHistory(in_pBytes); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::StartOutputCapture( + const AkOSChar* in_CaptureFileName +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::StartOutputCapture(in_CaptureFileName); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::StopOutputCapture() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::StopOutputCapture(); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::AddOutputCaptureMarker( + const char* in_MarkerText +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::AddOutputCaptureMarker(in_MarkerText); +} + +AkUInt32 FWwiseSoundEngineAPI_2022_1::GetSampleRate() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetSampleRate(); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::RegisterCaptureCallback( + AkCaptureCallbackFunc in_pfnCallback, + AkOutputDeviceID in_idOutput, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::RegisterCaptureCallback(in_pfnCallback, in_idOutput, in_pCookie); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::UnregisterCaptureCallback( + AkCaptureCallbackFunc in_pfnCallback, + AkOutputDeviceID in_idOutput, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::UnregisterCaptureCallback(in_pfnCallback, in_idOutput, in_pCookie); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::StartProfilerCapture( + const AkOSChar* in_CaptureFileName +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::StartProfilerCapture(in_CaptureFileName); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::StopProfilerCapture() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::StopProfilerCapture(); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetOfflineRenderingFrameTime( + AkReal32 in_fFrameTimeInSeconds +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetOfflineRenderingFrameTime(in_fFrameTimeInSeconds); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetOfflineRendering( + bool in_bEnableOfflineRendering +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetOfflineRendering(in_bEnableOfflineRendering); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::AddOutput( + const AkOutputSettings& in_Settings, + AkOutputDeviceID* out_pDeviceID, + const AkGameObjectID* in_pListenerIDs, + AkUInt32 in_uNumListeners +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::AddOutput(in_Settings, out_pDeviceID, in_pListenerIDs, in_uNumListeners); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::RemoveOutput( + AkOutputDeviceID in_idOutput +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::RemoveOutput(in_idOutput); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::ReplaceOutput( + const AkOutputSettings& in_Settings, + AkOutputDeviceID in_outputDeviceId, + AkOutputDeviceID* out_pOutputDeviceId +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::ReplaceOutput(in_Settings, in_outputDeviceId, out_pOutputDeviceId); +} + +AkOutputDeviceID FWwiseSoundEngineAPI_2022_1::GetOutputID( + AkUniqueID in_idShareSet, + AkUInt32 in_idDevice +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetOutputID(in_idShareSet, in_idDevice); +} + +AkOutputDeviceID FWwiseSoundEngineAPI_2022_1::GetOutputID( + const char* in_szShareSet, + AkUInt32 in_idDevice +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetOutputID(in_szShareSet, in_idDevice); +} + +#ifdef AK_SUPPORT_WCHAR +AkOutputDeviceID FWwiseSoundEngineAPI_2022_1::GetOutputID( + const wchar_t* in_szShareSet, + AkUInt32 in_idDevice +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetOutputID(in_szShareSet, in_idDevice); +} +#endif + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetBusDevice( + AkUniqueID in_idBus, + AkUniqueID in_idNewDevice +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetBusDevice(in_idBus, in_idNewDevice); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetBusDevice( + const char* in_BusName, + const char* in_DeviceName +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetBusDevice(in_BusName, in_DeviceName); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::SetBusDevice( + const wchar_t* in_BusName, + const wchar_t* in_DeviceName +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetBusDevice(in_BusName, in_DeviceName); +} +#endif + +AKRESULT FWwiseSoundEngineAPI_2022_1::GetDeviceList( + AkUInt32 in_ulCompanyID, + AkUInt32 in_ulPluginID, + AkUInt32& io_maxNumDevices, + AkDeviceDescription* out_deviceDescriptions +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetDeviceList(in_ulCompanyID, in_ulPluginID, io_maxNumDevices, out_deviceDescriptions); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::GetDeviceList( + AkUniqueID in_audioDeviceShareSetID, + AkUInt32& io_maxNumDevices, + AkDeviceDescription* out_deviceDescriptions +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetDeviceList(in_audioDeviceShareSetID, io_maxNumDevices, out_deviceDescriptions); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::SetOutputVolume( + AkOutputDeviceID in_idOutput, + AkReal32 in_fVolume +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::SetOutputVolume(in_idOutput, in_fVolume); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::GetDeviceSpatialAudioSupport( + AkUInt32 in_idDevice) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetDeviceSpatialAudioSupport(in_idDevice); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::Suspend( + bool in_bRenderAnyway, + bool in_bFadeOut +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Suspend(in_bRenderAnyway, in_bFadeOut); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::WakeupFromSuspend( + AkUInt32 in_uDelayMs +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::WakeupFromSuspend(in_uDelayMs); +} + +AkUInt32 FWwiseSoundEngineAPI_2022_1::GetBufferTick() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetBufferTick(); +} + +AkUInt64 FWwiseSoundEngineAPI_2022_1::GetSampleTick() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::GetSampleTick(); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::GetPosition( + AkGameObjectID in_GameObjectID, + AkSoundPosition& out_rPosition +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetPosition(in_GameObjectID, out_rPosition); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::GetListeners( + AkGameObjectID in_GameObjectID, + AkGameObjectID* out_ListenerObjectIDs, + AkUInt32& oi_uNumListeners +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetListeners(in_GameObjectID, out_ListenerObjectIDs, oi_uNumListeners); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::GetListenerPosition( + AkGameObjectID in_uIndex, + AkListenerPosition& out_rPosition +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetListenerPosition(in_uIndex, out_rPosition); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::GetListenerSpatialization( + AkUInt32 in_uIndex, + bool& out_rbSpatialized, + AK::SpeakerVolumes::VectorPtr& out_pVolumeOffsets, + AkChannelConfig& out_channelConfig +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetListenerSpatialization(in_uIndex, out_rbSpatialized, out_pVolumeOffsets, out_channelConfig); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::GetRTPCValue( + AkRtpcID in_rtpcID, + AkGameObjectID in_gameObjectID, + AkPlayingID in_playingID, + AkRtpcValue& out_rValue, + AK::SoundEngine::Query::RTPCValue_type& io_rValueType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetRTPCValue(in_rtpcID, in_gameObjectID, in_playingID, out_rValue, io_rValueType); +} + +#ifdef AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::GetRTPCValue( + const wchar_t* in_pszRtpcName, + AkGameObjectID in_gameObjectID, + AkPlayingID in_playingID, + AkRtpcValue& out_rValue, + AK::SoundEngine::Query::RTPCValue_type& io_rValueType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetRTPCValue(in_pszRtpcName, in_gameObjectID, in_playingID, out_rValue, io_rValueType); +} + +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::GetRTPCValue( + const char* in_pszRtpcName, + AkGameObjectID in_gameObjectID, + AkPlayingID in_playingID, + AkRtpcValue& out_rValue, + AK::SoundEngine::Query::RTPCValue_type& io_rValueType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetRTPCValue(in_pszRtpcName, in_gameObjectID, in_playingID, out_rValue, io_rValueType); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::GetSwitch( + AkSwitchGroupID in_switchGroup, + AkGameObjectID in_gameObjectID, + AkSwitchStateID& out_rSwitchState +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetSwitch(in_switchGroup, in_gameObjectID, out_rSwitchState); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::GetSwitch( + const wchar_t* in_pstrSwitchGroupName, + AkGameObjectID in_GameObj, + AkSwitchStateID& out_rSwitchState +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetSwitch(in_pstrSwitchGroupName, in_GameObj, out_rSwitchState); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::GetSwitch( + const char* in_pstrSwitchGroupName, + AkGameObjectID in_GameObj, + AkSwitchStateID& out_rSwitchState +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetSwitch(in_pstrSwitchGroupName, in_GameObj, out_rSwitchState); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::GetState( + AkStateGroupID in_stateGroup, + AkStateID& out_rState +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetState(in_stateGroup, out_rState); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::GetState( + const wchar_t* in_pstrStateGroupName, + AkStateID& out_rState +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetState(in_pstrStateGroupName, out_rState); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::GetState( + const char* in_pstrStateGroupName, + AkStateID& out_rState +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetState(in_pstrStateGroupName, out_rState); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::GetGameObjectAuxSendValues( + AkGameObjectID in_gameObjectID, + AkAuxSendValue* out_paAuxSendValues, + AkUInt32& io_ruNumSendValues +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetGameObjectAuxSendValues(in_gameObjectID, out_paAuxSendValues, io_ruNumSendValues); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::GetGameObjectDryLevelValue( + AkGameObjectID in_EmitterID, + AkGameObjectID in_ListenerID, + AkReal32& out_rfControlValue +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetGameObjectDryLevelValue(in_EmitterID, in_ListenerID, out_rfControlValue); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::GetObjectObstructionAndOcclusion( + AkGameObjectID in_EmitterID, + AkGameObjectID in_ListenerID, + AkReal32& out_rfObstructionLevel, + AkReal32& out_rfOcclusionLevel +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetObjectObstructionAndOcclusion(in_EmitterID, in_ListenerID, out_rfObstructionLevel, out_rfOcclusionLevel); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::QueryAudioObjectIDs( + AkUniqueID in_eventID, + AkUInt32& io_ruNumItems, + AkObjectInfo* out_aObjectInfos +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::QueryAudioObjectIDs(in_eventID, io_ruNumItems, out_aObjectInfos); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::QueryAudioObjectIDs( + const wchar_t* in_pszEventName, + AkUInt32& io_ruNumItems, + AkObjectInfo* out_aObjectInfos +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::QueryAudioObjectIDs(in_pszEventName, io_ruNumItems, out_aObjectInfos); +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::QueryAudioObjectIDs( + const char* in_pszEventName, + AkUInt32& io_ruNumItems, + AkObjectInfo* out_aObjectInfos +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::QueryAudioObjectIDs(in_pszEventName, io_ruNumItems, out_aObjectInfos); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::GetPositioningInfo( + AkUniqueID in_ObjectID, + AkPositioningInfo& out_rPositioningInfo +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetPositioningInfo(in_ObjectID, out_rPositioningInfo); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::GetActiveGameObjects( + FAkGameObjectsList& io_GameObjectList +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::SoundEngine::Query::AkGameObjectsList GameObjectsList; + auto Result = AK::SoundEngine::Query::GetActiveGameObjects(GameObjectsList); + if (UNLIKELY(Result != AK_Success) || GameObjectsList.IsEmpty()) + { + return Result; + } + io_GameObjectList.Reserve(GameObjectsList.Length()); + for (auto It = GameObjectsList.Begin(); It != GameObjectsList.End(); ++It) + { + io_GameObjectList.Add(*It); + } + GameObjectsList.Term(); + return Result; +} + +bool FWwiseSoundEngineAPI_2022_1::FQuery::GetIsGameObjectActive( + AkGameObjectID in_GameObjId +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetIsGameObjectActive(in_GameObjId); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::GetMaxRadius( + FAkRadiusList& io_RadiusList +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::SoundEngine::Query::AkRadiusList RadiusList; + auto Result = AK::SoundEngine::Query::GetMaxRadius(RadiusList); + if (UNLIKELY(Result != AK_Success) || RadiusList.IsEmpty()) + { + return Result; + } + io_RadiusList.Reserve(RadiusList.Length()); + for (auto It = RadiusList.Begin(); It != RadiusList.End(); ++It) + { + io_RadiusList.Add(*It); + } + return Result; +} + +AkReal32 FWwiseSoundEngineAPI_2022_1::FQuery::GetMaxRadius( + AkGameObjectID in_GameObjId +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetMaxRadius(in_GameObjId); +} + +AkUniqueID FWwiseSoundEngineAPI_2022_1::FQuery::GetEventIDFromPlayingID( + AkPlayingID in_playingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetEventIDFromPlayingID(in_playingID); +} + +AkGameObjectID FWwiseSoundEngineAPI_2022_1::FQuery::GetGameObjectFromPlayingID( + AkPlayingID in_playingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetGameObjectFromPlayingID(in_playingID); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::GetPlayingIDsFromGameObject( + AkGameObjectID in_GameObjId, + AkUInt32& io_ruNumIDs, + AkPlayingID* out_aPlayingIDs +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetPlayingIDsFromGameObject(in_GameObjId, io_ruNumIDs, out_aPlayingIDs); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::GetCustomPropertyValue( + AkUniqueID in_ObjectID, + AkUInt32 in_uPropID, + AkInt32& out_iValue +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetCustomPropertyValue(in_ObjectID, in_uPropID, out_iValue); +} + +AKRESULT FWwiseSoundEngineAPI_2022_1::FQuery::GetCustomPropertyValue( + AkUniqueID in_ObjectID, + AkUInt32 in_uPropID, + AkReal32& out_fValue +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SoundEngine::Query::GetCustomPropertyValue(in_ObjectID, in_uPropID, out_fValue); +} + +void FWwiseSoundEngineAPI_2022_1::FAudioInputPlugin::SetAudioInputCallbacks( + AkAudioInputPluginExecuteCallbackFunc in_pfnExecCallback, + AkAudioInputPluginGetFormatCallbackFunc in_pfnGetFormatCallback /*= nullptr */, + AkAudioInputPluginGetGainCallbackFunc in_pfnGetGainCallback /*= nullptr*/ +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + ::SetAudioInputCallbacks(in_pfnExecCallback, in_pfnGetFormatCallback, in_pfnGetGainCallback); +} + + +#if WITH_EDITORONLY_DATA +FWwiseSoundEngineAPI_2022_1::FErrorTranslator::FErrorTranslator(FGetInfoErrorMessageTranslatorFunction InMessageTranslatorFunction) : + GetInfoErrorMessageTranslatorFunction(InMessageTranslatorFunction) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); +} + +bool FWwiseSoundEngineAPI_2022_1::FErrorTranslator::GetInfo(TagInformation* in_pTagList, AkUInt32 in_uCount, + AkUInt32& out_uTranslated) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + if (LIKELY(GetInfoErrorMessageTranslatorFunction)) + { + return GetInfoErrorMessageTranslatorFunction(in_pTagList, in_uCount, out_uTranslated); + } + else + { + return false; + } +} +#endif + +AkErrorMessageTranslator* FWwiseSoundEngineAPI_2022_1::NewErrorMessageTranslator(FGetInfoErrorMessageTranslatorFunction InMessageTranslatorFunction) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); +#if WITH_EDITORONLY_DATA + return new FErrorTranslator(InMessageTranslatorFunction); +#else + return nullptr; +#endif +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WwiseSpatialAudioAPI_2022_1.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WwiseSpatialAudioAPI_2022_1.cpp new file mode 100644 index 0000000..d85381e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WwiseSpatialAudioAPI_2022_1.cpp @@ -0,0 +1,336 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_2022_1/WwiseSpatialAudioAPI_2022_1.h" +#include "Wwise/Stats/SoundEngine_2022_1.h" + +#include "Wwise/PreSoundEngineInclude.h" +#include +#include "Wwise/PostSoundEngineInclude.h" + +FWwiseSpatialAudioAPI_2022_1::FWwiseSpatialAudioAPI_2022_1() : + IWwiseSpatialAudioAPI(new FReverbEstimation) +{} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::Init(const AkSpatialAudioInitSettings& in_initSettings) +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseSpatialAudioAPI_2022_1::Init")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::Init(in_initSettings); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::RegisterListener( + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::RegisterListener(in_gameObjectID); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::UnregisterListener( + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::UnregisterListener(in_gameObjectID); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::SetGameObjectRadius( + AkGameObjectID in_gameObjectID, + AkReal32 in_outerRadius, + AkReal32 in_innerRadius +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::SetGameObjectRadius(in_gameObjectID, in_outerRadius, in_innerRadius); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::SetImageSource( + AkImageSourceID in_srcID, + const AkImageSourceSettings& in_info, + const char* in_name, + AkUniqueID in_AuxBusID, + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::SetImageSource(in_srcID, in_info, in_name, in_AuxBusID, in_gameObjectID); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::RemoveImageSource( + AkImageSourceID in_srcID, + AkUniqueID in_AuxBusID, + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::RemoveImageSource(in_srcID, in_AuxBusID, in_gameObjectID); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::ClearImageSources( + AkUniqueID in_AuxBusID, + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::ClearImageSources(in_AuxBusID, in_gameObjectID); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::SetGeometry( + AkGeometrySetID in_GeomSetID, + const AkGeometryParams& in_params +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::SetGeometry(in_GeomSetID, in_params); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::RemoveGeometry( + AkGeometrySetID in_SetID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::RemoveGeometry(in_SetID); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::SetGeometryInstance( + AkGeometryInstanceID in_GeometryInstanceID, + const AkGeometryInstanceParams& in_params +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::SetGeometryInstance(in_GeometryInstanceID, in_params); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::RemoveGeometryInstance( + AkGeometryInstanceID in_GeometryInstanceID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::RemoveGeometryInstance(in_GeometryInstanceID); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::QueryReflectionPaths( + AkGameObjectID in_gameObjectID, + AkUInt32 in_positionIndex, + AkVector64& out_listenerPos, + AkVector64& out_emitterPos, + AkReflectionPathInfo* out_aPaths, + AkUInt32& io_uArraySize +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::QueryReflectionPaths(in_gameObjectID, in_positionIndex, out_listenerPos, out_emitterPos, out_aPaths, io_uArraySize); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::SetRoom( + AkRoomID in_RoomID, + const AkRoomParams& in_Params, + const char* in_RoomName +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::SetRoom(in_RoomID, in_Params, in_RoomName); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::RemoveRoom( + AkRoomID in_RoomID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::RemoveRoom(in_RoomID); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::SetPortal( + AkPortalID in_PortalID, + const AkPortalParams& in_Params, + const char* in_PortalName +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::SetPortal(in_PortalID, in_Params, in_PortalName); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::RemovePortal( + AkPortalID in_PortalID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::RemovePortal(in_PortalID); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::SetGameObjectInRoom( + AkGameObjectID in_gameObjectID, + AkRoomID in_CurrentRoomID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::SetGameObjectInRoom(in_gameObjectID, in_CurrentRoomID); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::SetReflectionsOrder( + AkUInt32 in_uReflectionsOrder, + bool in_bUpdatePaths +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::SetReflectionsOrder(in_uReflectionsOrder, in_bUpdatePaths); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::SetDiffractionOrder( + AkUInt32 in_uDiffractionOrder, + bool in_bUpdatePaths +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::SetDiffractionOrder(in_uDiffractionOrder, in_bUpdatePaths); +} +AKRESULT FWwiseSpatialAudioAPI_2022_1::SetNumberOfPrimaryRays( + AkUInt32 in_uNbPrimaryRays +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::SetNumberOfPrimaryRays(in_uNbPrimaryRays); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::SetLoadBalancingSpread( + AkUInt32 in_uNbFrames +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::SetLoadBalancingSpread(in_uNbFrames); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::SetEarlyReflectionsAuxSend( + AkGameObjectID in_gameObjectID, + AkAuxBusID in_auxBusID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::SetEarlyReflectionsAuxSend(in_gameObjectID, in_auxBusID); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::SetEarlyReflectionsVolume( + AkGameObjectID in_gameObjectID, + AkReal32 in_fSendVolume +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::SetEarlyReflectionsVolume(in_gameObjectID, in_fSendVolume); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::SetPortalObstructionAndOcclusion( + AkPortalID in_PortalID, + AkReal32 in_fObstruction, + AkReal32 in_fOcclusion +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::SetPortalObstructionAndOcclusion(in_PortalID, in_fObstruction, in_fOcclusion); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::SetGameObjectToPortalObstruction( + AkGameObjectID in_gameObjectID, + AkPortalID in_PortalID, + AkReal32 in_fObstruction +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::SetGameObjectToPortalObstruction(in_gameObjectID, in_PortalID, in_fObstruction); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::SetPortalToPortalObstruction( + AkPortalID in_PortalID0, + AkPortalID in_PortalID1, + AkReal32 in_fObstruction +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::SetPortalToPortalObstruction(in_PortalID0, in_PortalID1, in_fObstruction); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::QueryWetDiffraction( + AkPortalID in_portal, + AkReal32& out_wetDiffraction +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::QueryWetDiffraction(in_portal, out_wetDiffraction); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::QueryDiffractionPaths( + AkGameObjectID in_gameObjectID, + AkUInt32 in_positionIndex, + AkVector64& out_listenerPos, + AkVector64& out_emitterPos, + AkDiffractionPathInfo* out_aPaths, + AkUInt32& io_uArraySize +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::QueryDiffractionPaths(in_gameObjectID, in_positionIndex, out_listenerPos, out_emitterPos, out_aPaths, io_uArraySize); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::ResetStochasticEngine() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::ResetStochasticEngine(); +} + +float FWwiseSpatialAudioAPI_2022_1::FReverbEstimation::CalculateSlope(const AkAcousticTexture& texture) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::ReverbEstimation::CalculateSlope(texture); +} + +void FWwiseSpatialAudioAPI_2022_1::FReverbEstimation::GetAverageAbsorptionValues(AkAcousticTexture* in_textures, float* in_surfaceAreas, int in_numTextures, AkAcousticTexture& out_average) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::SpatialAudio::ReverbEstimation::GetAverageAbsorptionValues(in_textures, in_surfaceAreas, in_numTextures, out_average); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::FReverbEstimation::EstimateT60Decay( + AkReal32 in_volumeCubicMeters, + AkReal32 in_surfaceAreaSquaredMeters, + AkReal32 in_environmentAverageAbsorption, + AkReal32& out_decayEstimate +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::ReverbEstimation::EstimateT60Decay(in_volumeCubicMeters, in_surfaceAreaSquaredMeters, in_environmentAverageAbsorption, out_decayEstimate); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::FReverbEstimation::EstimateTimeToFirstReflection( + AkVector in_environmentExtentMeters, + AkReal32& out_timeToFirstReflectionMs, + AkReal32 in_speedOfSound +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::ReverbEstimation::EstimateTimeToFirstReflection(in_environmentExtentMeters, out_timeToFirstReflectionMs, in_speedOfSound); +} + +AKRESULT FWwiseSpatialAudioAPI_2022_1::FReverbEstimation::EstimateHFDamping( + AkAcousticTexture* in_textures, + float* in_surfaceAreas, + int in_numTextures, + AkReal32& out_hfDamping +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::SpatialAudio::ReverbEstimation::EstimateHFDamping(in_textures, in_surfaceAreas, in_numTextures, out_hfDamping); +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WwiseStreamMgrAPI_2022_1.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WwiseStreamMgrAPI_2022_1.cpp new file mode 100644 index 0000000..dad6e50 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/API_2022_1/WwiseStreamMgrAPI_2022_1.cpp @@ -0,0 +1,125 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_2022_1/WwiseStreamMgrAPI_2022_1.h" +#include "Wwise/Stats/SoundEngine_2022_1.h" + +AK::IAkStreamMgr* FWwiseStreamMgrAPI_2022_1::GetAkStreamMgrInstance() +{ + IWwiseSoundEngineModule::ForceLoadModule(); + return AK::IAkStreamMgr::Get(); +} + +AK::IAkStreamMgr* FWwiseStreamMgrAPI_2022_1::Create( + const AkStreamMgrSettings& in_settings +) +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseStreamMgrAPI_2022_1::Create")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::StreamMgr::Create(in_settings); +} + +void FWwiseStreamMgrAPI_2022_1::GetDefaultSettings( + AkStreamMgrSettings& out_settings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::StreamMgr::GetDefaultSettings(out_settings); +} + +AK::StreamMgr::IAkFileLocationResolver* FWwiseStreamMgrAPI_2022_1::GetFileLocationResolver() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::StreamMgr::GetFileLocationResolver(); +} + +void FWwiseStreamMgrAPI_2022_1::SetFileLocationResolver( + AK::StreamMgr::IAkFileLocationResolver* in_pFileLocationResolver +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::StreamMgr::SetFileLocationResolver(in_pFileLocationResolver); +} + +AkDeviceID FWwiseStreamMgrAPI_2022_1::CreateDevice( + const AkDeviceSettings& in_settings, + AK::StreamMgr::IAkLowLevelIOHook* in_pLowLevelHook +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::StreamMgr::CreateDevice(in_settings, in_pLowLevelHook); +} + +AKRESULT FWwiseStreamMgrAPI_2022_1::DestroyDevice( + AkDeviceID in_deviceID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::StreamMgr::DestroyDevice(in_deviceID); +} + +AKRESULT FWwiseStreamMgrAPI_2022_1::PerformIO() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::StreamMgr::PerformIO(); +} + +void FWwiseStreamMgrAPI_2022_1::GetDefaultDeviceSettings( + AkDeviceSettings& out_settings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::StreamMgr::GetDefaultDeviceSettings(out_settings); +} + +AKRESULT FWwiseStreamMgrAPI_2022_1::SetCurrentLanguage( + const AkOSChar* in_pszLanguageName +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::StreamMgr::SetCurrentLanguage(in_pszLanguageName); +} + +const AkOSChar* FWwiseStreamMgrAPI_2022_1::GetCurrentLanguage() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::StreamMgr::GetCurrentLanguage(); +} + +AKRESULT FWwiseStreamMgrAPI_2022_1::AddLanguageChangeObserver( + AK::StreamMgr::AkLanguageChangeHandler in_handler, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + return AK::StreamMgr::AddLanguageChangeObserver(in_handler, in_pCookie); +} + +void FWwiseStreamMgrAPI_2022_1::RemoveLanguageChangeObserver( + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::StreamMgr::RemoveLanguageChangeObserver(in_pCookie); +} + +void FWwiseStreamMgrAPI_2022_1::FlushAllCaches() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_2022_1); + AK::StreamMgr::FlushAllCaches(); +} + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/DefineNullBridges.inl b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/DefineNullBridges.inl new file mode 100644 index 0000000..6582a63 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/DefineNullBridges.inl @@ -0,0 +1,20 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Stats/SoundEngine_2022_1.cpp" +#include "Wwise/WwiseFileLocationResolver_2022_1.cpp" +#include "Wwise/WwiseLowLevelIOHook_2022_1.cpp" diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/Stats/SoundEngine_2022_1.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/Stats/SoundEngine_2022_1.cpp new file mode 100644 index 0000000..d4a6723 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/Stats/SoundEngine_2022_1.cpp @@ -0,0 +1,20 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Stats/SoundEngine_2022_1.h" + +DEFINE_STAT(STAT_WwiseSoundEngineAPI_2022_1); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/WwiseFileLocationResolver_2022_1.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/WwiseFileLocationResolver_2022_1.cpp new file mode 100644 index 0000000..2100858 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/WwiseFileLocationResolver_2022_1.cpp @@ -0,0 +1,187 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseFileLocationResolver_2022_1.h" +#include "Wwise/Stats/SoundEngine.h" +#include "AkUnrealHelper.h" + +#include "Misc/ScopeLock.h" + +#include + +void FWwiseFileLocationResolver::FileOpenDataBridge::Callback(AkAsyncFileOpenData* in_rOpenInfo, AKRESULT in_eResult) +{ + if (in_rOpenInfo) + { + auto* Data = (FileOpenDataBridge*)in_rOpenInfo->pCookie; + Data->Result = in_eResult; + Data->Done->Trigger(); + } +} + +AKRESULT FWwiseFileLocationResolver::Open(AkFileID in_fileID, AkOpenMode in_eOpenMode, AkFileSystemFlags* in_pFlags, + bool& io_bSyncOpen, AkFileDesc& io_fileDesc) +{ + if (io_bSyncOpen) + { + SCOPED_WWISESOUNDENGINE_EVENT_2(TEXT("FWwiseDefaultIOHook::Open SyncOpen")); + FileOpenDataBridge* Data; + { + FScopeLock Lock(&MapLock); + auto** DataPtr = OpenFileIDMap.Find(in_fileID); + if (!DataPtr) + { + if (in_eOpenMode != AkOpenMode::AK_OpenModeRead) + { + Lock.Unlock(); + UE_LOG(LogWwiseSoundEngine, VeryVerbose, TEXT("FWwiseDefaultIOHook::Open Bridge not yet created. Synchronous write file open of File ID %" PRIu32), in_fileID); + bool bSyncOpen = false; + auto Result = Open(in_fileID, in_eOpenMode, in_pFlags, bSyncOpen, io_fileDesc); + if (Result != AK_Success) + { + return Result; + } + bSyncOpen = true; + Result = Open(in_fileID, in_eOpenMode, in_pFlags, bSyncOpen, io_fileDesc); + return Result; + } + UE_LOG(LogWwiseSoundEngine, Error, TEXT("FWwiseDefaultIOHook::Open Could not find Bridge for file ID %" PRIu32), in_fileID); + return AK_FileNotFound; + } + Data = *DataPtr; + auto RemoveCount = OpenFileIDMap.Remove(in_fileID, Data); + if (UNLIKELY(RemoveCount != 1)) + { + UE_LOG(LogWwiseSoundEngine, Error, TEXT("FWwiseDefaultIOHook::Open Removed %" PRIi32 " OpenFileNameMap Bridges for file ID %" PRIu32), in_fileID); + } + + if (!Data) + { + UE_LOG(LogWwiseSoundEngine, Error, TEXT("FWwiseDefaultIOHook::Open Empty Bridge for file ID %" PRIu32), in_fileID); + return AK_FileNotFound; + } + } + + { + SCOPED_WWISESOUNDENGINE_EVENT_4(TEXT("FWwiseDefaultIOHook::Open SyncOpen Wait")); + Data->Done->Wait(); + } + FMemory::Memset(io_fileDesc, 0); + io_fileDesc.iFileSize = Data->pFileDesc->iFileSize; + io_fileDesc.pCustomParam = Data->pFileDesc; + auto Result = Data->Result; + delete Data; + UE_LOG(LogWwiseSoundEngine, Verbose, TEXT("FWwiseDefaultIOHook::Opened file ID %" PRIu32 ": (%" PRIu32 ") %s"), in_fileID, Result, AkUnrealHelper::GetResultString(Result)); + return Result; + } + else + { + SCOPED_WWISESOUNDENGINE_EVENT_2(TEXT("FWwiseDefaultIOHook::Open Async Init")); + UE_LOG(LogWwiseSoundEngine, VeryVerbose, TEXT("FWwiseDefaultIOHook::Open Creating Bridge for file %" PRIu32), in_fileID); + auto Data = new FileOpenDataBridge; + Data->pszFileName = nullptr; + Data->fileID = in_fileID; + Data->pFlags = in_pFlags; + Data->eOpenMode = in_eOpenMode; + Data->pCookie = Data; + Data->pFileDesc = new AkFileDesc(io_fileDesc); + Data->pCustomData = nullptr; + Data->pCallback = &FileOpenDataBridge::Callback; + { + FScopeLock Lock(&MapLock); + OpenFileIDMap.Add(in_fileID, Data); + } + return Open(Data); + } +} + +AKRESULT FWwiseFileLocationResolver::Open(const AkOSChar* in_pszFileName, AkOpenMode in_eOpenMode, + AkFileSystemFlags* in_pFlags, bool& io_bSyncOpen, AkFileDesc& io_fileDesc) +{ + const FString Filename(in_pszFileName); + + if (io_bSyncOpen) + { + SCOPED_WWISESOUNDENGINE_EVENT_2(TEXT("FWwiseDefaultIOHook::Open SyncOpen")); + FileOpenDataBridge* Data; + { + FScopeLock Lock(&MapLock); + auto** DataPtr = OpenFileNameMap.Find(Filename); + if (!DataPtr) + { + if (in_eOpenMode != AkOpenMode::AK_OpenModeRead) + { + Lock.Unlock(); + UE_LOG(LogWwiseSoundEngine, VeryVerbose, TEXT("FWwiseDefaultIOHook::Open Bridge not yet created. Synchronous write file open of file %s"), *Filename); + bool bSyncOpen = false; + auto Result = Open(in_pszFileName, in_eOpenMode, in_pFlags, bSyncOpen, io_fileDesc); + if (Result != AK_Success) + { + return Result; + } + bSyncOpen = true; + Result = Open(in_pszFileName, in_eOpenMode, in_pFlags, bSyncOpen, io_fileDesc); + return Result; + } + UE_LOG(LogWwiseSoundEngine, Error, TEXT("FWwiseDefaultIOHook::Open Could not find Bridge for file %s"), *Filename); + return AK_FileNotFound; + } + Data = *DataPtr; + auto RemoveCount = OpenFileNameMap.Remove(Filename, Data); + if (UNLIKELY(RemoveCount != 1)) + { + UE_LOG(LogWwiseSoundEngine, Error, TEXT("FWwiseDefaultIOHook::Open Removed %" PRIi32 " OpenFileNameMap Bridges for file %s"), *Filename); + } + if (!Data) + { + UE_LOG(LogWwiseSoundEngine, Error, TEXT("FWwiseDefaultIOHook::Open Empty Bridge for file %s"), *Filename); + return AK_FileNotFound; + } + } + + { + SCOPED_WWISESOUNDENGINE_EVENT_4(TEXT("FWwiseDefaultIOHook::Open SyncOpen Wait")); + Data->Done->Wait(); + } + FMemory::Memset(io_fileDesc, 0); + io_fileDesc.iFileSize = Data->pFileDesc->iFileSize; + io_fileDesc.pCustomParam = Data->pFileDesc; + auto Result = Data->Result; + delete Data; + UE_LOG(LogWwiseSoundEngine, Verbose, TEXT("FWwiseDefaultIOHook::Opened file %s: (%" PRIu32 ") %s"), *Filename, Result, AkUnrealHelper::GetResultString(Result)); + return Result; + } + else + { + SCOPED_WWISESOUNDENGINE_EVENT_2(TEXT("FWwiseDefaultIOHook::Open Async Init")); + UE_LOG(LogWwiseSoundEngine, VeryVerbose, TEXT("FWwiseDefaultIOHook::Open Creating Bridge for file %s"), *Filename); + auto Data = new FileOpenDataBridge; + Data->pszFileName = in_pszFileName; + Data->fileID = 0; + Data->pFlags = in_pFlags; + Data->eOpenMode = in_eOpenMode; + Data->pCookie = Data; + Data->pFileDesc = nullptr; + Data->pCustomData = nullptr; + Data->pCallback = &FileOpenDataBridge::Callback; + { + FScopeLock Lock(&MapLock); + OpenFileNameMap.Add(Filename, Data); + } + return Open(Data); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/WwiseLowLevelIOHook_2022_1.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/WwiseLowLevelIOHook_2022_1.cpp new file mode 100644 index 0000000..ffa44f8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/WwiseLowLevelIOHook_2022_1.cpp @@ -0,0 +1,45 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseLowLevelIOHook_2022_1.h" + +AKRESULT FWwiseLowLevelIOHook::Close(AkFileDesc& in_fileDesc) +{ + return Close(&in_fileDesc); +} + +AKRESULT FWwiseLowLevelIOHook::BatchRead( + AkUInt32 in_uNumTransfers, + BatchIoTransferItem* in_pTransferItems, + AkBatchIOCallback /*in_pBatchIoCallback*/, + AKRESULT* /*io_pDispatchResults*/ +) +{ + BatchRead(in_uNumTransfers, in_pTransferItems); + return AK_Success; +} + +AKRESULT FWwiseLowLevelIOHook::BatchWrite( + AkUInt32 in_uNumTransfers, + BatchIoTransferItem* in_pTransferItems, + AkBatchIOCallback /*in_pBatchIoCallback*/, + AKRESULT* /*io_pDispatchResults*/ +) +{ + BatchWrite(in_uNumTransfers, in_pTransferItems); + return AK_Success; +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/WwiseSoundEngine_2022_1.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/WwiseSoundEngine_2022_1.cpp new file mode 100644 index 0000000..41ab6c6 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Private/Wwise/WwiseSoundEngine_2022_1.cpp @@ -0,0 +1,77 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseSoundEngine_2022_1.h" +#include "Wwise/WwiseSoundEngineModule.h" +#include "Wwise/API_2022_1/WwiseCommAPI_2022_1.h" +#include "Wwise/API_2022_1/WwiseMemoryMgrAPI_2022_1.h" +#include "Wwise/API_2022_1/WwiseMonitorAPI_2022_1.h" +#include "Wwise/API_2022_1/WwiseMusicEngineAPI_2022_1.h" +#include "Wwise/API_2022_1/WwiseSoundEngineAPI_2022_1.h" +#include "Wwise/API_2022_1/WwiseSpatialAudioAPI_2022_1.h" +#include "Wwise/API_2022_1/WwiseStreamMgrAPI_2022_1.h" +#include "Wwise/API_2022_1/WwisePlatformAPI_2022_1.h" +#include "Wwise/API_2022_1/WAAPI_2022_1.h" + +IWwiseCommAPI* FWwiseSoundEngine_2022_1::GetComm() +{ + return new FWwiseCommAPI_2022_1; +} + +IWwiseMemoryMgrAPI* FWwiseSoundEngine_2022_1::GetMemoryMgr() +{ + return new FWwiseMemoryMgrAPI_2022_1; +} + +IWwiseMonitorAPI* FWwiseSoundEngine_2022_1::GetMonitor() +{ + return new FWwiseMonitorAPI_2022_1; +} + +IWwiseMusicEngineAPI* FWwiseSoundEngine_2022_1::GetMusicEngine() +{ + return new FWwiseMusicEngineAPI_2022_1; +} + +IWwiseSoundEngineAPI* FWwiseSoundEngine_2022_1::GetSoundEngine() +{ + return new FWwiseSoundEngineAPI_2022_1; +} + +IWwiseSpatialAudioAPI* FWwiseSoundEngine_2022_1::GetSpatialAudio() +{ + return new FWwiseSpatialAudioAPI_2022_1; +} + +IWwiseStreamMgrAPI* FWwiseSoundEngine_2022_1::GetStreamMgr() +{ + return new FWwiseStreamMgrAPI_2022_1; +} + +IWwisePlatformAPI* FWwiseSoundEngine_2022_1::GetPlatform() +{ + return new FWwisePlatformAPI; +} + +IWAAPI* FWwiseSoundEngine_2022_1::GetWAAPI() +{ +#if AK_SUPPORT_WAAPI + return new FWAAPI_2022_1; +#else + return nullptr; +#endif +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/Platforms/Android/AndroidAPI_2022_1.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/Platforms/Android/AndroidAPI_2022_1.h new file mode 100644 index 0000000..5565c7b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/Platforms/Android/AndroidAPI_2022_1.h @@ -0,0 +1,47 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/Platforms/Android/AndroidAPI.h" + +#if defined(PLATFORM_ANDROID) && PLATFORM_ANDROID + +class FWwisePlatformAPI_2022_1_Android : public IWwisePlatformAPI +{ +public: + UE_NONCOPYABLE(FWwisePlatformAPI_2022_1_Android); + FWwisePlatformAPI_2022_1_Android() = default; + virtual ~FWwisePlatformAPI_2022_1_Android() override {} + + /// Get instance of OpenSL created by the sound engine at initialization. + /// \return NULL if sound engine is not initialized + virtual SLObjectItf GetWwiseOpenSLInterface() override; + + /// Gets specific settings for the fast audio path on Android. Call this function after AK::SoundEngine::GetDefaultSettings and AK::SoundEngine::GetPlatformDefaultSettings to modify settings for the fast path. + /// in_pfSettings.pJavaVM and in_pfSettings.jNativeActivity must be filled properly prior to calling GetFastPathSettings. + /// The fast path constraints are: + /// -The sample rate must match the hardware native sample rate + /// -The number of samples per frame must be a multiple of the hardware buffer size. + /// Not fulfilling these constraints makes the audio hardware less efficient. + /// In general, using the fast path means a higher CPU usage. Complex audio designs may not be feasible while using the fast path. + virtual AKRESULT GetFastPathSettings(AkInitSettings &in_settings, AkPlatformInitSettings &in_pfSettings) override; +}; + +using FWwisePlatformAPI = FWwisePlatformAPI_2022_1_Android; + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/Platforms/HoloLensAPI_2022_1.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/Platforms/HoloLensAPI_2022_1.h new file mode 100644 index 0000000..e1c437e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/Platforms/HoloLensAPI_2022_1.h @@ -0,0 +1,85 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/Platforms/HoloLensAPI.h" + +#if defined(PLATFORM_HOLOLENS) && PLATFORM_HOLOLENS + +class FWwisePlatformAPI_2022_1_HoloLens : public IWwisePlatformAPI +{ +public: + UE_NONCOPYABLE(FWwisePlatformAPI_2022_1_HoloLens); + FWwisePlatformAPI_2022_1_HoloLens() = default; + virtual ~FWwisePlatformAPI_2022_1_HoloLens() override {} + + /// Finds the device ID for particular Audio Endpoint. + /// \note CoInitialize must have been called for the calling thread. See Microsoft's documentation about CoInitialize for more details. + /// \return A device ID to use with AkPlatformInitSettings.idAudioDevice + virtual AkUInt32 GetDeviceID(IMMDevice* in_pDevice) override; + + /// Finds an audio endpoint that matches the token in the device name or device ID and returns an ID. + /// This is a helper function that searches in the device ID (as returned by IMMDevice->GetId) and + /// in the property PKEY_Device_FriendlyName. The token parameter is case-sensitive. If you need to do matching on different conditions, use IMMDeviceEnumerator directly and AK::GetDeviceID. + /// \note CoInitialize must have been called for the calling thread. See Microsoft's documentation about CoInitialize for more details. + /// \return The device ID as returned by IMMDevice->GetId, hashed by AK::SoundEngine::GetIDFromName() + virtual AkUInt32 GetDeviceIDFromName(wchar_t* in_szToken) override; + + /// Get the user-friendly name for the specified device. Call repeatedly with index starting at 0 and increasing to get all available devices, including disabled and unplugged devices, until the returned string is null and out_uDeviceID is zero. + /// The number of indexable devices for the given uDeviceStateMask can be fetched by calling AK::SoundEngine::GetWindowsDeviceCount(). + /// You can also get the default device information by specifying index=-1. The default device is the one with a green checkmark in the Audio Playback Device panel in Windows. + /// The returned out_uDeviceID parameter is the Device ID to use with Wwise. Use it to specify the main device in AkPlatformInitSettings.idAudioDevice. + /// \note CoInitialize must have been called for the calling thread. See Microsoft's documentation about CoInitialize for more details. + /// \return The name of the device at the "index" specified. The pointer is valid until the next call to GetWindowsDeviceName. + virtual const wchar_t* GetWindowsDeviceName( + AkInt32 index, ///< Index of the device in the array. -1 to get information on the default device. + AkUInt32 &out_uDeviceID, ///< Device ID for Wwise. This is the same as what is returned from AK::GetDeviceID and AK::GetDeviceIDFromName. Use it to specify the main device in AkPlatformInitSettings.idAudioDevice. + AkAudioDeviceState uDeviceStateMask = AkDeviceState_All ///< Optional bitmask used to filter the device based on their state. + ) override; + + /// Get the number of Audio Endpoints available for the specified device state mask. + /// \note CoInitialize must have been called for the calling thread. See Microsoft's documentation about CoInitialize for more details. + /// \return The number of Audio Endpoints available for the specified device state mask. + virtual AkUInt32 GetWindowsDeviceCount( + AkAudioDeviceState uDeviceStateMask = AkDeviceState_All ///< Optional bitmask used to filter the device based on their state. + ) override; + + /// Get the Audio Endpoint for the specified device index. Call repeatedly with index starting at 0 and increasing to get all available devices, including disabled and unplugged devices, until the false is returned. + /// You can also get the default device information by specifying index=-1. The default device is the one with a green checkmark in the Audio Playback Device panel in Windows. + /// The returned out_uDeviceID parameter is the Device ID to use with Wwise. Use it to specify the main device in AkPlatformInitSettings.idAudioDevice. + /// The returned out_ppDevice is a pointer to a pointer variable to which the method writes the address of the IMMDevice. out_ppDevice is optional; if it is null, then no action is taken. + /// If the method returns false, *out_ppDevice is null. If the method successed, *out_ppDevice will be a counted reference to the interface, and the caller is responsible for releasing the interface when it is no longer needed, by calling Release(), or encapsulating the device in a COM Smart Pointer. + /// \note CoInitialize must have been called for the calling thread. See Microsoft's documentation about CoInitialize for more details. + /// \return Whether or not a device was found at the given index. + virtual bool GetWindowsDevice( + AkInt32 in_index, ///< Index of the device in the array. -1 to get information on the default device. + AkUInt32& out_uDeviceID, ///< Device ID for Wwise. This is the same as what is returned from AK::GetDeviceID and AK::GetDeviceIDFromName. Use it to specify the main device in AkPlatformInitSettings.idAudioDevice. + IMMDevice** out_ppDevice, ///< pointer to a pointer variable to which the method writes the address of the IMMDevice in question. + AkAudioDeviceState uDeviceStateMask = AkDeviceState_All ///< Optional bitmask used to filter the device based on their state. + ) override; + + /// Get the device ID corresponding to a Universal Windows Platform Gamepad reference. This device ID can be used to add/remove motion output for that gamepad. + /// \note The ID returned is unique to Wwise and does not correspond to any sensible value outside of Wwise. + /// \note This function is only available for project code using C++/CX. + /// \return Unique device ID, or AK_INVALID_DEVICE_ID if the reference is no longer valid (such as if the gamepad was disconnected) + //virtual AkDeviceID GetDeviceIDFromGamepad(Windows::Gaming::Input::Gamepad^ rGamepad) override; +}; + +using FWwisePlatformAPI = FWwisePlatformAPI_2022_1_HoloLens; + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/Platforms/IOS/IOSAPI_2022_1.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/Platforms/IOS/IOSAPI_2022_1.h new file mode 100644 index 0000000..6d0720c --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/Platforms/IOS/IOSAPI_2022_1.h @@ -0,0 +1,48 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/Platforms/IOS/IOSAPI.h" + +#if defined(PLATFORM_TVOS) && PLATFORM_TVOS +#include "Wwise/API_2022_1/Platforms/TVOS/TVOSAPI_2022_1.h" +#elif defined(PLATFORM_IOS) && PLATFORM_IOS + +class FWwisePlatformAPI_2022_1_IOS : public IWwisePlatformAPI +{ +public: + UE_NONCOPYABLE(FWwisePlatformAPI_2022_1_IOS); + FWwisePlatformAPI_2022_1_IOS() = default; + virtual ~FWwisePlatformAPI_2022_1_IOS() override {} + + /// Change the category and options of the app's AVAudioSession without restarting the entire Sound Engine. + /// \remark + /// As per Apple recommendations, the AVAudioSession will be deactivated and then reactivated. Therefore, + /// the primary output device must be reinitialized, which causes all audio to stop for a short period of time. + /// For a seamless transition, call this API during moments of complete silence in your game. + /// + /// \sa + /// - \ref AkAudioSessionProperties + virtual void ChangeAudioSessionProperties( + const AkAudioSessionProperties &in_properties ///< New properties to apply to the app's AVAudioSession shared instance. + ) override; +}; + +using FWwisePlatformAPI = FWwisePlatformAPI_2022_1_IOS; + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/Platforms/Linux/LinuxAPI_2022_1.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/Platforms/Linux/LinuxAPI_2022_1.h new file mode 100644 index 0000000..a8bdc5b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/Platforms/Linux/LinuxAPI_2022_1.h @@ -0,0 +1,34 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/Platforms/Linux/LinuxAPI.h" + +#if defined(PLATFORM_LINUX) && PLATFORM_LINUX + +class FWwisePlatformAPI_2022_1_Linux : public IWwisePlatformAPI +{ +public: + UE_NONCOPYABLE(FWwisePlatformAPI_2022_1_Linux); + FWwisePlatformAPI_2022_1_Linux() = default; + virtual ~FWwisePlatformAPI_2022_1_Linux() override {} +}; + +using FWwisePlatformAPI = FWwisePlatformAPI_2022_1_Linux; + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/Platforms/Mac/MacAPI_2022_1.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/Platforms/Mac/MacAPI_2022_1.h new file mode 100644 index 0000000..7bdc010 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/Platforms/Mac/MacAPI_2022_1.h @@ -0,0 +1,34 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/Platforms/Mac/MacAPI.h" + +#if defined(PLATFORM_MAC) && PLATFORM_MAC + +class FWwisePlatformAPI_2022_1_Mac : public IWwisePlatformAPI +{ +public: + UE_NONCOPYABLE(FWwisePlatformAPI_2022_1_Mac); + FWwisePlatformAPI_2022_1_Mac() = default; + virtual ~FWwisePlatformAPI_2022_1_Mac() override {} +}; + +using FWwisePlatformAPI = FWwisePlatformAPI_2022_1_Mac; + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/Platforms/TVOS/TVOSAPI_2022_1.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/Platforms/TVOS/TVOSAPI_2022_1.h new file mode 100644 index 0000000..cc3e059 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/Platforms/TVOS/TVOSAPI_2022_1.h @@ -0,0 +1,46 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/Platforms/TVOS/TVOSAPI.h" + +#if defined(PLATFORM_TVOS) && PLATFORM_TVOS + +class FWwisePlatformAPI_2022_1_TVOS : public IWwisePlatformAPI +{ +public: + UE_NONCOPYABLE(FWwisePlatformAPI_2022_1_TVOS); + FWwisePlatformAPI_2022_1_TVOS() = default; + virtual ~FWwisePlatformAPI_2022_1_TVOS() override {} + + /// Change the category and options of the app's AVAudioSession without restarting the entire Sound Engine. + /// \remark + /// As per Apple recommendations, the AVAudioSession will be deactivated and then reactivated. Therefore, + /// the primary output device must be reinitialized, which causes all audio to stop for a short period of time. + /// For a seamless transition, call this API during moments of complete silence in your game. + /// + /// \sa + /// - \ref AkAudioSessionProperties + virtual void ChangeAudioSessionProperties( + const AkAudioSessionProperties &in_properties ///< New properties to apply to the app's AVAudioSession shared instance. + ) override; +}; + +using FWwisePlatformAPI = FWwisePlatformAPI_2022_1_TVOS; + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/Platforms/Windows/WindowsAPI_2022_1.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/Platforms/Windows/WindowsAPI_2022_1.h new file mode 100644 index 0000000..892d236 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/Platforms/Windows/WindowsAPI_2022_1.h @@ -0,0 +1,79 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/Platforms/Windows/WindowsAPI.h" + +#if defined(PLATFORM_WINDOWS) && PLATFORM_WINDOWS && (!defined(PLATFORM_WINGDK) || !PLATFORM_WINGDK) + +class FWwisePlatformAPI_2022_1_Windows : public IWwisePlatformAPI +{ +public: + UE_NONCOPYABLE(FWwisePlatformAPI_2022_1_Windows); + FWwisePlatformAPI_2022_1_Windows() = default; + virtual ~FWwisePlatformAPI_2022_1_Windows() override {} + + /// Finds the device ID for particular Audio Endpoint. + /// \note CoInitialize must have been called for the calling thread. See Microsoft's documentation about CoInitialize for more details. + /// \return A device ID to use with AkPlatformInitSettings.idAudioDevice + virtual AkUInt32 GetDeviceID(IMMDevice* in_pDevice) override; + + /// Finds an audio endpoint that matches the token in the device name or device ID and returns an ID. + /// This is a helper function that searches in the device ID (as returned by IMMDevice->GetId) and + /// in the property PKEY_Device_FriendlyName. The token parameter is case-sensitive. If you need to do matching on different conditions, use IMMDeviceEnumerator directly and AK::GetDeviceID. + /// \note CoInitialize must have been called for the calling thread. See Microsoft's documentation about CoInitialize for more details. + /// \return The device ID as returned by IMMDevice->GetId, hashed by AK::SoundEngine::GetIDFromName() + virtual AkUInt32 GetDeviceIDFromName(wchar_t* in_szToken) override; + + /// Get the user-friendly name for the specified device. Call repeatedly with index starting at 0 and increasing to get all available devices, including disabled and unplugged devices, until the returned string is null and out_uDeviceID is zero. + /// The number of indexable devices for the given uDeviceStateMask can be fetched by calling AK::SoundEngine::GetWindowsDeviceCount(). + /// You can also get the default device information by specifying index=-1. The default device is the one with a green checkmark in the Audio Playback Device panel in Windows. + /// The returned out_uDeviceID parameter is the Device ID to use with Wwise. Use it to specify the main device in AkPlatformInitSettings.idAudioDevice. + /// \note CoInitialize must have been called for the calling thread. See Microsoft's documentation about CoInitialize for more details. + /// \return The name of the device at the "index" specified. The pointer is valid until the next call to GetWindowsDeviceName. + virtual const wchar_t* GetWindowsDeviceName( + AkInt32 index, ///< Index of the device in the array. -1 to get information on the default device. + AkUInt32 &out_uDeviceID, ///< Device ID for Wwise. This is the same as what is returned from AK::GetDeviceID and AK::GetDeviceIDFromName. Use it to specify the main device in AkPlatformInitSettings.idAudioDevice. + AkAudioDeviceState uDeviceStateMask = AkDeviceState_All ///< Optional bitmask used to filter the device based on their state. + ) override; + + /// Get the number of Audio Endpoints available for the specified device state mask. + /// \note CoInitialize must have been called for the calling thread. See Microsoft's documentation about CoInitialize for more details. + /// \return The number of Audio Endpoints available for the specified device state mask. + virtual AkUInt32 GetWindowsDeviceCount( + AkAudioDeviceState uDeviceStateMask = AkDeviceState_All ///< Optional bitmask used to filter the device based on their state. + ) override; + + /// Get the Audio Endpoint for the specified device index. Call repeatedly with index starting at 0 and increasing to get all available devices, including disabled and unplugged devices, until the false is returned. + /// You can also get the default device information by specifying index=-1. The default device is the one with a green checkmark in the Audio Playback Device panel in Windows. + /// The returned out_uDeviceID parameter is the Device ID to use with Wwise. Use it to specify the main device in AkPlatformInitSettings.idAudioDevice. + /// The returned out_ppDevice is a pointer to a pointer variable to which the method writes the address of the IMMDevice. out_ppDevice is optional; if it is null, then no action is taken. + /// If the method returns false, *out_ppDevice is null. If the method successed, *out_ppDevice will be a counted reference to the interface, and the caller is responsible for releasing the interface when it is no longer needed, by calling Release(), or encapsulating the device in a COM Smart Pointer. + /// \note CoInitialize must have been called for the calling thread. See Microsoft's documentation about CoInitialize for more details. + /// \return Whether or not a device was found at the given index. + virtual bool GetWindowsDevice( + AkInt32 in_index, ///< Index of the device in the array. -1 to get information on the default device. + AkUInt32& out_uDeviceID, ///< Device ID for Wwise. This is the same as what is returned from AK::GetDeviceID and AK::GetDeviceIDFromName. Use it to specify the main device in AkPlatformInitSettings.idAudioDevice. + IMMDevice** out_ppDevice, ///< pointer to a pointer variable to which the method writes the address of the IMMDevice in question. + AkAudioDeviceState uDeviceStateMask = AkDeviceState_All ///< Optional bitmask used to filter the device based on their state. + ) override; +}; + +using FWwisePlatformAPI = FWwisePlatformAPI_2022_1_Windows; + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WAAPI_2022_1.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WAAPI_2022_1.h new file mode 100644 index 0000000..4c7be74 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WAAPI_2022_1.h @@ -0,0 +1,57 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/WAAPI.h" + +#if AK_SUPPORT_WAAPI +class WWISESOUNDENGINE_API FWAAPI_2022_1 : public IWAAPI +{ +public: + UE_NONCOPYABLE(FWAAPI_2022_1); + FWAAPI_2022_1() = default; + ~FWAAPI_2022_1() override {} + + class WWISESOUNDENGINE_API Client_2022_1 : public Client + { + public: + UE_NONCOPYABLE(Client_2022_1); + Client_2022_1() = default; + ~Client_2022_1() override {} + + bool Connect(const char* in_uri, unsigned int in_port, AK::WwiseAuthoringAPI::disconnectHandler_t disconnectHandler = nullptr, int in_timeoutMs = -1) override; + bool IsConnected() const override; + void Disconnect() override; + + bool Subscribe(const char* in_uri, const char* in_options, AK::WwiseAuthoringAPI::Client::WampEventCallback in_callback, uint64_t& out_subscriptionId, std::string& out_result, int in_timeoutMs = -1) override; + bool Subscribe(const char* in_uri, const AK::WwiseAuthoringAPI::AkJson& in_options, AK::WwiseAuthoringAPI::Client::WampEventCallback in_callback, uint64_t& out_subscriptionId, AK::WwiseAuthoringAPI::AkJson& out_result, int in_timeoutMs = -1) override; + + bool Unsubscribe(const uint64_t& in_subscriptionId, std::string& out_result, int in_timeoutMs = -1) override; + bool Unsubscribe(const uint64_t& in_subscriptionId, AK::WwiseAuthoringAPI::AkJson& out_result, int in_timeoutMs = -1) override; + + bool Call(const char* in_uri, const char* in_args, const char* in_options, std::string& out_result, int in_timeoutMs = -1) override; + bool Call(const char* in_uri, const AK::WwiseAuthoringAPI::AkJson& in_args, const AK::WwiseAuthoringAPI::AkJson& in_options, AK::WwiseAuthoringAPI::AkJson& out_result, int in_timeoutMs = -1) override; + + private: + AK::WwiseAuthoringAPI::Client Client; + }; + Client* NewClient() override; + + std::string GetJsonString(const AK::WwiseAuthoringAPI::JsonProvider&) override; +}; +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwiseCommAPI_2022_1.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwiseCommAPI_2022_1.h new file mode 100644 index 0000000..375067f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwiseCommAPI_2022_1.h @@ -0,0 +1,95 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/WwiseCommAPI.h" + +class WWISESOUNDENGINE_API FWwiseCommAPI_2022_1 : public IWwiseCommAPI +{ +public: + UE_NONCOPYABLE(FWwiseCommAPI_2022_1); + FWwiseCommAPI_2022_1() = default; + + /////////////////////////////////////////////////////////////////////// + /// @name Initialization + //@{ + + /// Initializes the communication module. When this is called, and AK::SoundEngine::RenderAudio() + /// is called periodically, you may use the authoring tool to connect to the sound engine. + /// + /// \warning This function must be called after the sound engine and memory manager have + /// been properly initialized. + /// + /// + /// \remark The AkCommSettings structure should be initialized with + /// AK::Comm::GetDefaultInitSettings(). You can then change some of the parameters + /// before calling this function. + /// + /// \return + /// - AK_Success if initialization was successful. + /// - AK_InvalidParameter if one of the settings is invalid. + /// - AK_InsufficientMemory if the specified pool size is too small for initialization. + /// - AK_Fail for other errors. + /// + /// \sa + /// - \ref initialization_comm + /// - AK::Comm::GetDefaultInitSettings() + /// - AkCommSettings::Ports + AKRESULT Init( + const AkCommSettings& in_settings///< Initialization settings. + ) override; + + /// Gets the last error from the OS-specific communication library. + /// \return The system error code. Check the code in the platform manufacturer documentation for details about the error. + AkInt32 GetLastError() override; + + /// Gets the communication module's default initialization settings values. + /// \sa + /// - \ref initialization_comm + /// - AK::Comm::Init() + void GetDefaultInitSettings( + AkCommSettings& out_settings ///< Returned default initialization settings. + ) override; + + /// Terminates the communication module. + /// \warning This function must be called before the memory manager is terminated. + /// \sa + /// - \ref termination_comm + void Term() override; + + /// Terminates and reinitialize the communication module using current settings. + /// + /// \return + /// - AK_Success if initialization was successful. + /// - AK_InvalidParameter if one of the settings is invalid. + /// - AK_InsufficientMemory if the specified pool size is too small for initialization. + /// - AK_Fail for other errors. + /// + /// \sa + /// - \ref AK::SoundEngine::iOS::WakeupFromSuspend() + AKRESULT Reset() override; + + + /// Get the initialization settings currently in use by the CommunicationSystem + /// + /// \return + /// - AK_Success if initialization was successful. + const AkCommSettings& GetCurrentSettings() override; + + //@} +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwiseMemoryMgrAPI_2022_1.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwiseMemoryMgrAPI_2022_1.h new file mode 100644 index 0000000..860d068 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwiseMemoryMgrAPI_2022_1.h @@ -0,0 +1,226 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/WwiseMemoryMgrAPI.h" + +class WWISESOUNDENGINE_API FWwiseMemoryMgrAPI_2022_1 : public IWwiseMemoryMgrAPI +{ +public: + UE_NONCOPYABLE(FWwiseMemoryMgrAPI_2022_1); + FWwiseMemoryMgrAPI_2022_1() = default; + + /// Initialize the default implementation of the Memory Manager. + /// \sa AK::MemoryMgr + AKRESULT Init( + AkMemSettings* in_pSettings ///< Memory manager initialization settings. + ) override; + + /// Obtain the default initialization settings for the default implementation of the Memory Manager. + void GetDefaultSettings( + AkMemSettings& out_pMemSettings ///< Memory manager default initialization settings. + ) override; + + //////////////////////////////////////////////////////////////////////// + /// @name Initialization + //@{ + + /// Query whether the Memory Manager has been sucessfully initialized. + /// \warning This function is not thread-safe. It should not be called at the same time as MemoryMgr::Init or MemoryMgr::Term. + /// \return True if the Memory Manager is initialized, False otherwise + /// \sa + /// - AK::MemoryMgr::Init() + /// - \ref memorymanager + bool IsInitialized() override; + + /// Terminate the Memory Manager. + /// \warning This function is not thread-safe. It is not valid to allocate memory or otherwise interact with the memory manager during or after this call. + /// \sa + /// - \ref memorymanager + void Term() override; + + /// Performs whatever steps are required to initialize a thread for use with the memory manager. + /// For example initializing thread local storage that the allocator requires to work. + /// The default implementation of the memory manager performs thread initialization automatically and therefore this call is optional. + /// For implementations where the cost of automatically initializing a thread for use with an allocator would be prohibitively expensive + /// this call allows you to perform the initialization once during, for example, thread creation. + /// \sa + /// - AkMemInitForThread + void InitForThread() override; + + /// Allows you to manually terminate a thread for use with the memory manager. + /// The default implementation of the memory manager requires that all threads that interact with the memory manager call this function prior + /// to either their termination or the termination of the memory manager. Threads not created by the sound engine itself will not have this + /// function called for them automatically. + /// Take care to call this function for any thread, not owned by wwise, that may have interacted with the memory manager. For example job system workers. + /// \sa + /// - AkMemTermForThread + void TermForThread() override; + + /// Allows you to "trim" a thread being used with the memory manager. + /// This is a function that will be called periodically by some Wwise-owned threads, + /// so that any thread-local state can be cleaned up in order to return memory for other systems to use. + /// For example, this can be used to return thread-local heaps to global stores or to finalize other deferred operations. + /// This function is only required for optimization purposes and does not have to be defined. + /// Therefore, unlike TermForThread, this is not expected to be called in all scenarios by Wwise. + /// It is also recommended to be called by game engine integrations in any worker threads that run Wwise jobs. + /// Refer to \ref eventmgrthread_jobmgr_best_practices for more information. + /// \sa + /// - AkMemTrimForThread + void TrimForThread() override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Memory Allocation + //@{ + + /// Allocate memory: debug version. + /// \return A pointer to the start of the allocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + void* dMalloc( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + size_t in_uSize, ///< Number of bytes to allocate + const char* in_pszFile, ///< Debug file name + AkUInt32 in_uLine ///< Debug line number + ) override; + + /// Allocate memory. + /// \return A pointer to the start of the allocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + void* Malloc( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + size_t in_uSize ///< Number of bytes to allocate + ) override; + + /// Reallocate memory: debug version. + /// \return A pointer to the start of the reallocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + void* dRealloc( + AkMemPoolId in_poolId, + void* in_pAlloc, + size_t in_uSize, + const char* in_pszFile, + AkUInt32 in_uLine + ) override; + + /// Reallocate memory. + /// \return A pointer to the start of the reallocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + void* Realloc( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + void* in_pAlloc, ///< Pointer to the start of the allocated memory + size_t in_uSize ///< Number of bytes to allocate + ) override; + + /// Reallocate memory: debug version. + /// \return A pointer to the start of the reallocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + void* dReallocAligned( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + void* in_pAlloc, ///< Pointer to the start of the allocated memory + size_t in_uSize, ///< Number of bytes to allocate + AkUInt32 in_uAlignment, ///< Alignment (in bytes) + const char* in_pszFile, ///< Debug file name + AkUInt32 in_uLine ///< Debug line number + ) override; + + /// Reallocate memory. + /// \return A pointer to the start of the reallocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + void* ReallocAligned( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + void* in_pAlloc, ///< Pointer to the start of the allocated memory + size_t in_uSize, ///< Number of bytes to allocate + AkUInt32 in_uAlignment ///< Alignment (in bytes) + ) override; + + /// Free memory allocated with the memory manager. + /// \sa + /// - \ref memorymanager + void Free( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + void* in_pMemAddress ///< Pointer to the start of memory + ) override; + + /// Allocate memory with a specific alignment. debug version. + /// \return A pointer to the start of the allocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + void* dMalign( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + size_t in_uSize, ///< Number of bytes to allocate + AkUInt32 in_uAlignment, ///< Alignment (in bytes) + const char* in_pszFile, ///< Debug file name + AkUInt32 in_uLine ///< Debug line number + ) override; + + /// Allocate memory with a specific alignment. + /// \return A pointer to the start of the allocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + void* Malign( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + size_t in_uSize, ///< Number of bytes to allocate + AkUInt32 in_uAlignment ///< Alignment (in bytes) + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Memory Profiling + //@{ + + /// Get statistics for a given memory category. + /// \note Be aware of the potentially incoherent nature of reporting such information during concurrent modification by multiple threads. + void GetCategoryStats( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + AK::MemoryMgr::CategoryStats& out_poolStats ///< Returned statistics. + ) override; + + /// Get statistics for overall memory manager usage. + /// \note Be aware of the potentially incoherent nature of reporting such information during concurrent modification by multiple threads. + void GetGlobalStats( + AK::MemoryMgr::GlobalStats& out_stats ///< Returned statistics. + ) override; + + /// Called to start profiling memory usage for one thread (the calling thread). + /// \note Not implementing this will result in the Soundbank tab of the Wwise Profiler to show 0 bytes for memory usage. + void StartProfileThreadUsage( + ) override; + + /// Called to stop profiling memory usage for the current thread. + /// \return The amount of memory allocated by this thread since StartProfileThreadUsage was called. + /// \note Not implementing this will result in the Soundbank tab of the Wwise Profiler to show 0 bytes for memory usage. + AkUInt64 StopProfileThreadUsage( + ) override; + + /// Dumps the currently tracked allocations to a file + /// \note AkMemSettings::uMemoryDebugLevel must be enabled and the build must define AK_MEMDEBUG for this to work + void DumpToFile( + const AkOSChar* pszFilename ///< Filename. + ) override; + + //@} +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwiseMonitorAPI_2022_1.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwiseMonitorAPI_2022_1.h new file mode 100644 index 0000000..cb0e6b6 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwiseMonitorAPI_2022_1.h @@ -0,0 +1,170 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/WwiseMonitorAPI.h" + +class WWISESOUNDENGINE_API FWwiseMonitorAPI_2022_1 : public IWwiseMonitorAPI +{ +public: + UE_NONCOPYABLE(FWwiseMonitorAPI_2022_1); + FWwiseMonitorAPI_2022_1() = default; + + /// Post a monitoring message or error code. This will be displayed in the Wwise capture + /// log. Since this function doesn't send variable arguments, be sure that the error code you're posting doesn't contain any tag. + /// Otherwise, there'll be an undefined behavior + /// \return AK_Success if successful, AK_Fail if there was a problem posting the message. + /// In optimized mode, this function returns AK_NotCompatible. + /// \remark This function is provided as a tracking tool only. It does nothing if it is + /// called in the optimized/release configuration and return AK_NotCompatible. + AKRESULT PostCode( + AK::Monitor::ErrorCode in_eError, ///< Message or error code to be displayed + AK::Monitor::ErrorLevel in_eErrorLevel, ///< Specifies whether it should be displayed as a message or an error + AkPlayingID in_playingID = AK_INVALID_PLAYING_ID, ///< Related Playing ID if applicable + AkGameObjectID in_gameObjID = AK_INVALID_GAME_OBJECT, ///< Related Game Object ID if applicable, AK_INVALID_GAME_OBJECT otherwise + AkUniqueID in_audioNodeID = AK_INVALID_UNIQUE_ID, ///< Related Audio Node ID if applicable, AK_INVALID_UNIQUE_ID otherwise + bool in_bIsBus = false ///< true if in_audioNodeID is a bus + ) override; + + AKRESULT PostCodeVarArg( + AK::Monitor::ErrorCode in_eError, ///< Error code to be displayed. This code corresponds to a predefined message, that may have parameters that can be passed in the variable arguments. Check the message format at the end of AkMonitorError.h. + AK::Monitor::ErrorLevel in_eErrorLevel, ///< Specifies whether it should be displayed as a message or an error + AK::Monitor::MsgContext msgContext, ///< The message context containing the following information : Related Playing ID if applicable, Related Game Object ID if applicable, AK_INVALID_GAME_OBJECT otherwise, Related Audio Node ID if applicable, AK_INVALID_UNIQUE_ID otherwise and whether if in_audioNodeID is a bus + ... ///< The variable arguments, depending on the ErrorCode posted. + ) override; + + /// Post a monitoring message. This will be displayed in the Wwise capture log. + /// \return AK_Success if successful, AK_Fail if there was a problem posting the message. + /// In optimized mode, this function returns AK_NotCompatible. + /// \remark This function is provided as a tracking tool only. It does nothing if it is + /// called in the optimized/release configuration and return AK_NotCompatible. + AKRESULT PostCodeVaList( + AK::Monitor::ErrorCode in_eError, ///< Error code to be displayed. This code corresponds to a predefined message, that may have parameters that can be passed in the variable arguments. Check the message format at the end of AkMonitorError.h. + AK::Monitor::ErrorLevel in_eErrorLevel, ///< Specifies whether it should be displayed as a message or an error + AK::Monitor::MsgContext msgContext, ///< The message context containing the following information : Related Playing ID if applicable, Related Game Object ID if applicable, AK_INVALID_GAME_OBJECT otherwise, Related Audio Node ID if applicable, AK_INVALID_UNIQUE_ID otherwise and whether if in_audioNodeID is a bus + ::va_list args ///< The variable arguments, depending on the ErrorCode posted. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Post a unicode monitoring message or error string. This will be displayed in the Wwise capture + /// log. + /// \return AK_Success if successful, AK_Fail if there was a problem posting the message. + /// In optimized mode, this function returns AK_NotCompatible. + /// \remark This function is provided as a tracking tool only. It does nothing if it is + /// called in the optimized/release configuration and return AK_NotCompatible. + AKRESULT PostString( + const wchar_t* in_pszError, ///< Message or error string to be displayed + AK::Monitor::ErrorLevel in_eErrorLevel, ///< Specifies whether it should be displayed as a message or an error + AkPlayingID in_playingID = AK_INVALID_PLAYING_ID, ///< Related Playing ID if applicable + AkGameObjectID in_gameObjID = AK_INVALID_GAME_OBJECT, ///< Related Game Object ID if applicable, AK_INVALID_GAME_OBJECT otherwise + AkUniqueID in_audioNodeID = AK_INVALID_UNIQUE_ID, ///< Related Audio Node ID if applicable, AK_INVALID_UNIQUE_ID otherwise + bool in_bIsBus = false ///< true if in_audioNodeID is a bus + ) override; + +#endif // #ifdef AK_SUPPORT_WCHAR + + /// Post a monitoring message or error string. This will be displayed in the Wwise capture + /// log. + /// \return AK_Success if successful, AK_Fail if there was a problem posting the message. + /// In optimized mode, this function returns AK_NotCompatible. + /// \remark This function is provided as a tracking tool only. It does nothing if it is + /// called in the optimized/release configuration and return AK_NotCompatible. + AKRESULT PostString( + const char* in_pszError, ///< Message or error string to be displayed + AK::Monitor::ErrorLevel in_eErrorLevel, ///< Specifies whether it should be displayed as a message or an error + AkPlayingID in_playingID = AK_INVALID_PLAYING_ID, ///< Related Playing ID if applicable + AkGameObjectID in_gameObjID = AK_INVALID_GAME_OBJECT, ///< Related Game Object ID if applicable, AK_INVALID_GAME_OBJECT otherwise + AkUniqueID in_audioNodeID = AK_INVALID_UNIQUE_ID, ///< Related Audio Node ID if applicable, AK_INVALID_UNIQUE_ID otherwise + bool in_bIsBus = false ///< true if in_audioNodeID is a bus + ) override; + + /// Enable/Disable local output of monitoring messages or errors. Pass 0 to disable, + /// or any combination of ErrorLevel_Message and ErrorLevel_Error to enable. + /// \return AK_Success. + /// In optimized/release configuration, this function returns AK_NotCompatible. + AKRESULT SetLocalOutput( + AkUInt32 in_uErrorLevel = AK::Monitor::ErrorLevel_All, ///< ErrorLevel(s) to enable in output. Default parameters enable all. + AK::Monitor::LocalOutputFunc in_pMonitorFunc = 0 ///< Handler for local output. If NULL, the standard platform debug output method is used. + ) override; + + /// Add a translator to the wwiseErrorHandler + /// The additional translators increase the chance of a monitoring messages or errors + /// to be succeffully translated. + /// \return AK_Success. + /// In optimized/release configuration, this function returns AK_NotCompatible. + AKRESULT AddTranslator( + AkErrorMessageTranslator* translator, ///< The AkErrorMessageTranslator to add to the WwiseErrorHandler + bool overridePreviousTranslators = false ///< Whether or not the newly added translator should override all the previous translators. + ///< In both cases, the default translator will remain + ) override; + + /// Reset the wwiseErrorHandler to only using the default translator + /// \return AK_Success. + /// In optimized/release configuration, this function returns AK_NotCompatible. + AKRESULT ResetTranslator( + ) override; + + /// Get the time stamp shown in the capture log along with monitoring messages. + /// \return Time stamp in milliseconds. + /// In optimized/release configuration, this function returns 0. + AkTimeMs GetTimeStamp() override; + + /// Add the streaming manager settings to the profiler capture. + void MonitorStreamMgrInit( + const AkStreamMgrSettings& in_streamMgrSettings + ) override; + + /// Add device settings to the list of active streaming devices. + /// The list of streaming devices and their settings will be + /// sent to the profiler capture when remote connecting from Wwise. + /// + /// \remark \c AK::Monitor::MonitorStreamMgrTerm must be called to + /// clean-up memory used to keep track of active streaming devices. + void MonitorStreamingDeviceInit( + AkDeviceID in_deviceID, + const AkDeviceSettings& in_deviceSettings + ) override; + + /// Remove streaming device entry from the list of devices + /// to send when remote connecting from Wwise. + void MonitorStreamingDeviceDestroyed( + AkDeviceID in_deviceID + ) override; + + /// Monitor streaming manager destruction as part of the + /// profiler capture. + /// + /// \remark This function must be called to clean-up memory used by + /// \c AK::Monitor::MonitorStreamingDeviceInit and \c AK::Monitor::MonitorStreamingDeviceTerm + /// to keep track of active streaming devices. + void MonitorStreamMgrTerm() override; + + /// Add the default, WwiseSDK-provided WAAPI error translator. + void SetupDefaultWAAPIErrorTranslator( + const FString& WaapiIP, ///< IP Address of the WAAPI server + AkUInt32 WaapiPort, ///< Port of the WAAPI server + AkUInt32 Timeout ///< Maximum time that can be spent resolving the error parameters. Set to INT_MAX to wait infinitely or 0 to disable XML translation entirely. + ) override; + + /// Terminate the default, WwiseSDK-provided WAAPI error translator. + void TerminateDefaultWAAPIErrorTranslator() override; +private: +#if AK_SUPPORT_WAAPI && WITH_EDITORONLY_DATA && !defined(AK_OPTIMIZED) + static AkWAAPIErrorMessageTranslator WaapiErrorMessageTranslator; +#endif +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwiseMusicEngineAPI_2022_1.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwiseMusicEngineAPI_2022_1.h new file mode 100644 index 0000000..181269d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwiseMusicEngineAPI_2022_1.h @@ -0,0 +1,85 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/WwiseMusicEngineAPI.h" + +class WWISESOUNDENGINE_API FWwiseMusicEngineAPI_2022_1 : public IWwiseMusicEngineAPI +{ +public: + UE_NONCOPYABLE(FWwiseMusicEngineAPI_2022_1); + FWwiseMusicEngineAPI_2022_1() = default; + + /////////////////////////////////////////////////////////////////////// + /// @name Initialization + //@{ + + /// Initialize the music engine. + /// \warning This function must be called after the base sound engine has been properly initialized. + /// There should be no AK API call between AK::SoundEngine::Init() and this call. Any call done in between is potentially unsafe. + /// \return AK_Success if the Init was successful, AK_Fail otherwise. + /// \sa + /// - \ref workingwithsdks_initialization + AKRESULT Init( + AkMusicSettings* in_pSettings ///< Initialization settings (can be NULL, to use the default values) + ) override; + + /// Get the music engine's default initialization settings values + /// \sa + /// - \ref soundengine_integration_init_advanced + /// - AK::MusicEngine::Init() + void GetDefaultInitSettings( + AkMusicSettings& out_settings ///< Returned default platform-independent music engine settings + ) override; + + /// Terminate the music engine. + /// \warning This function must be called before calling Term() on the base sound engine. + /// \sa + /// - \ref workingwithsdks_termination + void Term( + ) override; + + /// Query information on the active segment of a music object that is playing. Use the playing ID + /// that was returned from AK::SoundEngine::PostEvent(), provided that the event contained a play + /// action that was targetting a music object. For any configuration of interactive music hierarchy, + /// there is only one segment that is active at a time. + /// To be able to query segment information, you must pass the AK_EnableGetMusicPlayPosition flag + /// to the AK::SoundEngine::PostEvent() method. This informs the sound engine that the source associated + /// with this event should be given special consideration because GetPlayingSegmentInfo() can be called + /// at any time for this AkPlayingID. + /// Notes: + /// - If the music object is a single segment, you will get negative values for AkSegmentInfo::iCurrentPosition + /// during the pre-entry. This will never occur with other types of music objects because the + /// pre-entry of a segment always overlaps another active segment. + /// - The active segment during the pre-entry of the first segment of a Playlist Container or a Music Switch + /// Container is "nothing", as well as during the post-exit of the last segment of a Playlist (and beyond). + /// - When the active segment is "nothing", out_uSegmentInfo is filled with zeros. + /// - If in_bExtrapolate is true (default), AkSegmentInfo::iCurrentPosition is corrected by the amount of time elapsed + /// since the beginning of the audio frame. It is thus possible that it slightly overshoots the total segment length. + /// \return AK_Success if there is a playing music structure associated with the specified playing ID. + /// \sa + /// - AK::SoundEngine::PostEvent + /// - AkSegmentInfo + AKRESULT GetPlayingSegmentInfo( + AkPlayingID in_PlayingID, ///< Playing ID returned by AK::SoundEngine::PostEvent(). + AkSegmentInfo& out_segmentInfo, ///< Structure containing information about the active segment of the music structure that is playing. + bool in_bExtrapolate = true ///< Position is extrapolated based on time elapsed since last sound engine update. + ) override; + + //@} +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwisePlatformAPI_2022_1.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwisePlatformAPI_2022_1.h new file mode 100644 index 0000000..eb6f787 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwisePlatformAPI_2022_1.h @@ -0,0 +1,22 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/WwisePlatformAPI.h" + +#include COMPILED_PLATFORM_HEADER_WITH_PREFIX(Wwise/API_2022_1/Platforms, API_2022_1.h) diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwiseSoundEngineAPI_2022_1.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwiseSoundEngineAPI_2022_1.h new file mode 100644 index 0000000..eeb01cc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwiseSoundEngineAPI_2022_1.h @@ -0,0 +1,4506 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/WwiseSoundEngineAPI.h" + +class WWISESOUNDENGINE_API FWwiseSoundEngineAPI_2022_1 : public IWwiseSoundEngineAPI +{ +public: + UE_NONCOPYABLE(FWwiseSoundEngineAPI_2022_1); + FWwiseSoundEngineAPI_2022_1(); + + /////////////////////////////////////////////////////////////////////// + /// @name Initialization + //@{ + + /// Query whether or not the sound engine has been successfully initialized. + /// \warning This function is not thread-safe. It should not be called at the same time as \c SoundEngine::Init() or \c SoundEngine::Term(). + /// \return \c True if the sound engine has been initialized, \c False otherwise. + /// \sa + /// - \ref soundengine_integration_init_advanced + /// - AK::SoundEngine::Init() + /// - AK::SoundEngine::Term() + bool IsInitialized() override; + + /// Initialize the sound engine. + /// \warning This function is not thread-safe. + /// \remark The initial settings should be initialized using AK::SoundEngine::GetDefaultInitSettings() + /// and AK::SoundEngine::GetDefaultPlatformInitSettings() to fill the structures with their + /// default settings. This is not mandatory, but it helps avoid backward compatibility problems. + /// + /// \return + /// - \c AK_Success if the initialization was successful + /// - \c AK_MemManagerNotInitialized if the memory manager is not available or not properly initialized + /// - \c AK_StreamMgrNotInitialized if the stream manager is not available or not properly initialized + /// - \c AK_SSEInstructionsNotSupported if the machine does not support SSE instruction (only on the PC) + /// - \c AK_InsufficientMemory if there is not enough memory available to initialize the sound engine properly + /// - \c AK_InvalidParameter if some parameters are invalid + /// - \c AK_AlreadyInitialized if the sound engine is already initialized, or if the provided settings result in insufficient + /// - \c AK_Fail for unknown errors, check with AK Support. + /// resources for the initialization. + /// \sa + /// - \ref soundengine_integration_init_advanced + /// - \ref workingwithsdks_initialization + /// - AK::SoundEngine::Term() + /// - AK::SoundEngine::GetDefaultInitSettings() + /// - AK::SoundEngine::GetDefaultPlatformInitSettings() + AKRESULT Init( + AkInitSettings* in_pSettings, ///< Initialization settings (can be NULL, to use the default values) + AkPlatformInitSettings* in_pPlatformSettings ///< Platform-specific settings (can be NULL, to use the default values) + ) override; + + /// Gets the default values of the platform-independent initialization settings. + /// \warning This function is not thread-safe. + /// \sa + /// - \ref soundengine_integration_init_advanced + /// - AK::SoundEngine::Init() + /// - AK::SoundEngine::GetDefaultPlatformInitSettings() + void GetDefaultInitSettings( + AkInitSettings& out_settings ///< Returned default platform-independent sound engine settings + ) override; + + /// Gets the default values of the platform-specific initialization settings. + /// + /// Windows Specific: + /// When initializing for Windows platform, the HWND value returned in the + /// AkPlatformInitSettings structure is the foreground HWND at the moment of the + /// initialization of the sound engine and may not be the correct one for your need. + /// Each game must specify the HWND that will be passed to DirectSound initialization. + /// It is required that each game provides the correct HWND to be used or it could cause + /// one of the following problem: + /// - Random Sound engine initialization failure. + /// - Audio focus to be located on the wrong window. + /// + /// \warning This function is not thread-safe. + /// \sa + /// - \ref soundengine_integration_init_advanced + /// - AK::SoundEngine::Init() + /// - AK::SoundEngine::GetDefaultInitSettings() + void GetDefaultPlatformInitSettings( + AkPlatformInitSettings& out_platformSettings ///< Returned default platform-specific sound engine settings + ) override; + + /// Terminates the sound engine. + /// If some sounds are still playing or events are still being processed when this function is + /// called, they will be stopped. + /// \warning This function is not thread-safe. + /// \warning Before calling Term, you must ensure that no other thread is accessing the sound engine. + /// \sa + /// - \ref soundengine_integration_init_advanced + /// - AK::SoundEngine::Init() + void Term() override; + + /// Gets the configured audio settings. + /// Call this function to get the configured audio settings. + /// + /// \warning This function is not thread-safe. + /// \warning Call this function only after the sound engine has been properly initialized. + /// \return + /// - \c AK_NotInitialized if AK::SoundEngine::Init() was not called + /// - \c AK_Success otherwise. + AKRESULT GetAudioSettings( + AkAudioSettings& out_audioSettings ///< Returned audio settings + ) override; + + /// Gets the output speaker configuration of the specified output. + /// Call this function to get the speaker configuration of the output (which may not correspond + /// to the physical output format of the platform, in the case of downmixing provided by the platform itself). + /// You may initialize the sound engine with a user-specified configuration, but the resulting + /// configuration is determined by the sound engine, based on the platform, output type and + /// platform settings (for e.g. system menu or control panel option). + /// If the speaker configuration of the output is object-based, the speaker configuration of the + /// main mix is returned. To query more information on object-based output devices, see AK::SoundEngine::GetOutputDeviceConfiguration. + /// + /// It is recommended to call GetSpeakerConfiguration anytime after receiving a callback from RegisterAudioDeviceStatusCallback to know if the channel configuration has changed. + /// + /// \warning Call this function only after the sound engine has been properly initialized. + /// If you are initializing the sound engine with AkInitSettings::bUseLEngineThread to false, it is required to call RenderAudio() at least once before calling this function to complete the sound engine initialization. + /// The Init.bnk must be loaded prior to this call. + /// \return The output configuration. An empty AkChannelConfig not AkChannelConfig::IsValid() if device does not exist or if the Init.bnk was not loaded yet. + /// \sa + /// - AkSpeakerConfig.h + /// - AkOutputSettings + /// - AK::SoundEngine::GetOutputDeviceConfiguration() + AkChannelConfig GetSpeakerConfiguration( + AkOutputDeviceID in_idOutput = 0 ///< Output ID to set the bus on. As returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + ) override; + + /// Gets the configuration of the specified output device. + /// Call this function to get the channel configuration of the output device as well as its 3D audio capabilities. + /// If the configuration of the output device is object-based (io_channelConfig.eConfigType == AK_ChannelConfigType_Objects), + /// io_capabilities can be inspected to determine the channel configuration of the main mix (Ak3DAudioSinkCapabilities::channelConfig), + /// whether or not the output device uses a passthrough mix (Ak3DAudioSinkCapabilities::bPassthrough) and the maximum number of objects + /// that can play simultaneously on this output device (Ak3DAudioSinkCapabilities::uMax3DAudioObjects). Note that if + /// Ak3DAudioSinkCapabilities::bMultiChannelObjects is false, multi-channel objects will be split into multiple mono objects + /// before being sent to the output device. + /// + /// \warning Call this function only after the sound engine has been properly initialized. If you are initializing the sound engine with AkInitSettings::bUseLEngineThread to false, it is required to call RenderAudio() at least once before calling this function to complete the sound engine initialization. + /// \return + /// - \c AK_Success if successful + /// - \c AK_IDNotFound is the output was not found in the system. + /// - \c AK_NotInitialized if the sound engine is not initialized + /// \sa + /// - AkSpeakerConfig.h + /// - AkOutputSettings + /// - AK::SoundEngine::GetSpeakerConfiguration() + AKRESULT GetOutputDeviceConfiguration( + AkOutputDeviceID in_idOutput, + AkChannelConfig& io_channelConfig, + Ak3DAudioSinkCapabilities& io_capabilities + ) override; + + /// Gets the panning rule of the specified output. + /// \warning Call this function only after the sound engine has been properly initialized. + /// Returns the supported configuration in out_ePanningRule: + /// - AkPanningRule_Speakers + /// - AkPanningRule_Headphone + /// \return + /// - \c AK_Success if successful + /// - \c AK_IDNotFound is the output was not found in the system. + /// - \c AK_NotInitialized if the sound engine is not initialized + /// \sa + /// - AkSpeakerConfig.h + AKRESULT GetPanningRule( + AkPanningRule& out_ePanningRule, ///< Returned panning rule (AkPanningRule_Speakers or AkPanningRule_Headphone) for given output. + AkOutputDeviceID in_idOutput = 0 ///< Output ID to set the bus on. As returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + ) override; + + /// Sets the panning rule of the specified output. + /// This may be changed anytime once the sound engine is initialized. + /// \warning This function posts a message through the sound engine's internal message queue, whereas GetPanningRule() queries the current panning rule directly. + /// \aknote + /// The specified panning rule will only impact the sound if the processing format is downmixing to Stereo in the mixing process. It + /// will not impact the output if the audio stays in 5.1 until the end, for example. + /// \endaknote + AKRESULT SetPanningRule( + AkPanningRule in_ePanningRule, ///< Panning rule. + AkOutputDeviceID in_idOutput = 0 ///< Output ID to set the bus on. As returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + ) override; + + /// Gets speaker angles of the specified device. Speaker angles are used for 3D positioning of sounds over standard configurations. + /// Note that the current version of Wwise only supports positioning on the plane. + /// The speaker angles are expressed as an array of loudspeaker pairs, in degrees, relative to azimuth ]0,180]. + /// Supported loudspeaker setups are always symmetric; the center speaker is always in the middle and thus not specified by angles. + /// Angles must be set in ascending order. + /// You may call this function with io_pfSpeakerAngles set to NULL to get the expected number of angle values in io_uNumAngles, + /// in order to allocate your array correctly. You may also obtain this number by calling + /// AK::GetNumberOfAnglesForConfig( AK_SPEAKER_SETUP_DEFAULT_PLANE ). + /// If io_pfSpeakerAngles is not NULL, the array is filled with up to io_uNumAngles. + /// Typical usage: + /// - AkUInt32 uNumAngles; + /// - GetSpeakerAngles( NULL, uNumAngles, AkOutput_Main ) override; + /// - AkReal32 * pfSpeakerAngles = AkAlloca( uNumAngles * sizeof(AkReal32) ) override; + /// - GetSpeakerAngles( pfSpeakerAngles, uNumAngles, AkOutput_Main ) override; + /// \aknote + /// On most platforms, the angle set on the plane consists of 3 angles, to account for 7.1. + /// - When panning to stereo (speaker mode, see AK::SoundEngine::SetPanningRule()), only angle[0] is used, and 3D sounds in the back of the listener are mirrored to the front. + /// - When panning to 5.1, the front speakers use angle[0], and the surround speakers use (angle[2] - angle[1]) / 2. + /// \endaknote + /// \warning Call this function only after the sound engine has been properly initialized. + /// \return AK_Success if device exists. + /// \sa SetSpeakerAngles() + AKRESULT GetSpeakerAngles( + AkReal32* io_pfSpeakerAngles, ///< Returned array of loudspeaker pair angles, in degrees relative to azimuth [0,180]. Pass NULL to get the required size of the array. + AkUInt32& io_uNumAngles, ///< Returned number of angles in io_pfSpeakerAngles, which is the minimum between the value that you pass in, and the number of angles corresponding to AK::GetNumberOfAnglesForConfig( AK_SPEAKER_SETUP_DEFAULT_PLANE ), or just the latter if io_pfSpeakerAngles is NULL. + AkReal32& out_fHeightAngle, ///< Elevation of the height layer, in degrees relative to the plane [-90,90]. + AkOutputDeviceID in_idOutput = 0 ///< Output ID to set the bus on. As returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + ) override; + + /// Sets speaker angles of the specified device. Speaker angles are used for 3D positioning of sounds over standard configurations. + /// Note that the current version of Wwise only supports positioning on the plane. + /// The speaker angles are expressed as an array of loudspeaker pairs, in degrees, relative to azimuth ]0,180]. + /// Supported loudspeaker setups are always symmetric; the center speaker is always in the middle and thus not specified by angles. + /// Angles must be set in ascending order. + /// Note: + /// - This function requires that the minimum speaker angle is at least 5 degrees; as well as the subsequent speaker pairs are at least 5 degrees apart. + /// Typical usage: + /// - Initialize the sound engine and/or add secondary output(s). + /// - Get number of speaker angles and their value into an array using GetSpeakerAngles(). + /// - Modify the angles and call SetSpeakerAngles(). + /// This function posts a message to the audio thread through the command queue, so it is thread safe. However the result may not be immediately read with GetSpeakerAngles(). + /// \warning This function only applies to configurations (or subset of these configurations) that are standard and whose speakers are on the plane (2D). + /// \return + /// - \c AK_Success if successful. + /// - \c AK_InvalidFloatValue if the value specified was NaN or Inf + /// - \c AK_InsufficientMemory if there wasn't enough memory in the message queue + /// - \c AK_InvalidParameter one of the parameter is invalid, check the debug log. + /// \sa GetSpeakerAngles() + AKRESULT SetSpeakerAngles( + const AkReal32* in_pfSpeakerAngles, ///< Array of loudspeaker pair angles, in degrees relative to azimuth [0,180]. + AkUInt32 in_uNumAngles, ///< Number of elements in in_pfSpeakerAngles. It must correspond to AK::GetNumberOfAnglesForConfig( AK_SPEAKER_SETUP_DEFAULT_PLANE ) (the value returned by GetSpeakerAngles()). + AkReal32 in_fHeightAngle, ///< Elevation of the height layer, in degrees relative to the plane [-90,90]. + AkOutputDeviceID in_idOutput = 0 ///< Output ID to set the bus on. As returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + ) override; + + /// Allows the game to set the volume threshold to be used by the sound engine to determine if a voice must go virtual. + /// This may be changed anytime once the sound engine was initialized. + /// If this function is not called, the used value will be the value specified in the platform specific project settings. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if the threshold was not between 0 and -96.3 dB. + /// - \c AK_InvalidFloatValue if the value specified was NaN or Inf + AKRESULT SetVolumeThreshold( + AkReal32 in_fVolumeThresholdDB ///< Volume Threshold, must be a value between 0 and -96.3 dB + ) override; + + /// Allows the game to set the maximum number of non virtual voices to be played simultaneously. + /// This may be changed anytime once the sound engine was initialized. + /// If this function is not called, the used value will be the value specified in the platform specific project settings. + /// \return + /// - \c AK_InvalidParameter if the threshold was not between 1 and MaxUInt16. + /// - \c AK_Success if successful + AKRESULT SetMaxNumVoicesLimit( + AkUInt16 in_maxNumberVoices ///< Maximum number of non-virtual voices. + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Rendering Audio + //@{ + + /// Processes all commands in the sound engine's command queue. + /// This method has to be called periodically (usually once per game frame). + /// \sa + /// - \ref concept_events + /// - \ref soundengine_events + /// - AK::SoundEngine::PostEvent() + /// \return Always returns AK_Success + AKRESULT RenderAudio( + bool in_bAllowSyncRender = true ///< When AkInitSettings::bUseLEngineThread is false, RenderAudio may generate an audio buffer -- unless in_bAllowSyncRender is set to false. Use in_bAllowSyncRender=false when calling RenderAudio from a Sound Engine callback. + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Component Registration + //@{ + + /// Query interface to global plug-in context used for plug-in registration/initialization. + /// \return Global plug-in context. + AK::IAkGlobalPluginContext* GetGlobalPluginContext() override; + + /// Registers a plug-in with the sound engine and sets the callback functions to create the + /// plug-in and its parameter node. + /// \aknote + /// This function is deprecated. Registration is now automatic if you link plug-ins statically. If plug-ins are dynamic libraries (such as DLLs or SOs), use \c RegisterPluginDLL. + /// \endaknote + /// \sa + /// - \ref register_effects + /// - \ref plugin_xml + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if invalid parameters were provided + /// - \c AK_InsufficientMemory if there isn't enough memory to register the plug-in + /// \remarks + /// Codecs and plug-ins must be registered before loading banks that use them.\n + /// Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + AKRESULT RegisterPlugin( + AkPluginType in_eType, ///< Plug-in type (for example, source or effect) + AkUInt32 in_ulCompanyID, ///< Company identifier (as declared in the plug-in description XML file) + AkUInt32 in_ulPluginID, ///< Plug-in identifier (as declared in the plug-in description XML file) + AkCreatePluginCallback in_pCreateFunc, ///< Pointer to the plug-in's creation function + AkCreateParamCallback in_pCreateParamFunc, ///< Pointer to the plug-in's parameter node creation function + AkGetDeviceListCallback in_pGetDeviceList = NULL ///< Optional pointer to the plug-in's device enumeration function. Specify for a sink plug-in to support \ref AK::SoundEngine::GetDeviceList. + ) override; + + /// Loads a plug-in dynamic library and registers it with the sound engine. + /// With dynamic linking, all plugins are automatically registered. + /// The plug-in DLL must be in the OS-specific library path or in the same location as the executable. If not, set AkInitSettings.szPluginDLLPath. + /// \return + /// - \c AK_Success if successful. + /// - \c AK_FileNotFound if the DLL is not found in the OS path or if it has extraneous dependencies not found. + /// - \c AK_InsufficientMemory if the system ran out of resources while loading the dynamic library + /// - \c AK_NotCompatible if the file was found but is not binary-compatible with the system's expected executable format + /// - \c AK_InvalidFile if the symbol g_pAKPluginList is not exported by the dynamic library + /// - \c AK_Fail if an unexpected system error was encountered + AKRESULT RegisterPluginDLL( + const AkOSChar* in_DllName, ///< Name of the DLL to load, without "lib" prefix or extension. + const AkOSChar* in_DllPath = NULL ///< Optional path to the DLL. Will override szPLuginDLLPath that was set in AkInitSettings. + ) override; + + /// Registers a codec type with the sound engine and set the callback functions to create the + /// codec's file source and bank source nodes. + /// \aknote + /// This function is deprecated. Registration is now automatic if you link plugins statically. If plugins are dynamic libraries (such as DLLs or SOs), use RegisterPluginDLL. + /// \endaknote + /// \sa + /// - \ref register_effects + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if invalid parameters were provided + /// - \c AK_InsufficientMemory if there isn't enough memory to register the plug-in + /// \remarks + /// Codecs and plug-ins must be registered before loading banks that use them.\n + /// Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the Event will fail. + AKRESULT RegisterCodec( + AkUInt32 in_ulCompanyID, ///< Company identifier (as declared in the plug-in description XML file) + AkUInt32 in_ulCodecID, ///< Codec identifier (as declared in the plug-in description XML file) + AkCreateFileSourceCallback in_pFileCreateFunc, ///< Pointer to the codec's file source node creation function + AkCreateBankSourceCallback in_pBankCreateFunc ///< Pointer to the codec's bank source node creation function + ) override; + + /// Registers a global callback function. This function will be called from the audio rendering thread, at the + /// location specified by in_eLocation. This function will also be called from the thread calling + /// AK::SoundEngine::Term with in_eLocation set to AkGlobalCallbackLocation_Term. + /// For example, in order to be called at every audio rendering pass, and once during teardown for releasing resources, you would call + /// RegisterGlobalCallback(myCallback, AkGlobalCallbackLocation_BeginRender | AkGlobalCallbackLocation_Term, myCookie, AkPluginTypeNone, 0, 0) override; + /// \remarks + /// A Plugin Type, Company ID and Plugin ID can be provided to this function to enable timing in the performance monitor. + /// If the callback is being timed, it will contribute to the Total Plug-in CPU measurement, and also appear in the Plug-ins tab of the Advanced Profiler by plug-in type and ID. + /// It is illegal to call this function while already inside of a global callback. + /// This function might stall for several milliseconds before returning. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if parameters are out of range (check debug console or Wwise Profiler) + /// \sa + /// - AK::SoundEngine::UnregisterGlobalCallback() + /// - AkGlobalCallbackFunc + /// - AkGlobalCallbackLocation + AKRESULT RegisterGlobalCallback( + AkGlobalCallbackFunc in_pCallback, ///< Function to register as a global callback. + AkUInt32 in_eLocation = AkGlobalCallbackLocation_BeginRender, ///< Callback location defined in AkGlobalCallbackLocation. Bitwise OR multiple locations if needed. + void* in_pCookie = NULL, ///< User cookie. + AkPluginType in_eType = AkPluginTypeNone, ///< Plug-in type (for example, source or effect). AkPluginTypeNone for no timing. + AkUInt32 in_ulCompanyID = 0, ///< Company identifier (as declared in the plug-in description XML file). 0 for no timing. + AkUInt32 in_ulPluginID = 0 ///< Plug-in identifier (as declared in the plug-in description XML file). 0 for no timing. + ) override; + + /// Unregisters a global callback function, previously registered using RegisterGlobalCallback. + /// \remarks + /// It is legal to call this function while already inside of a global callback, If it is unregistering itself and not + /// another callback. + /// This function might stall for several milliseconds before returning. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if parameters are out of range (check debug console or Wwise Profiler) + /// \sa + /// - AK::SoundEngine::RegisterGlobalCallback() + /// - AkGlobalCallbackFunc + /// - AkGlobalCallbackLocation + AKRESULT UnregisterGlobalCallback( + AkGlobalCallbackFunc in_pCallback, ///< Function to unregister as a global callback. + AkUInt32 in_eLocation = AkGlobalCallbackLocation_BeginRender ///< Must match in_eLocation as passed to RegisterGlobalCallback for this callback. + ) override; + + /// Registers a resource monitor callback function that gets all of the resource usage data contained in the + /// AkResourceMonitorDataSummary structure. This includes general information about the system, such as CPU usage, + /// active Voices, and Events. This function will be called from the audio rendering thread at the end of each frame. + /// \remarks + /// If the callback is being timed, it will contribute to the Total Plug-in CPU measurement, and also appear in the Plug-ins tab of the Advanced Profiler by plug-in type and ID. + /// It is illegal to call this function while already inside of a resource callback. + /// This function might stall for several milliseconds before returning. + /// This function will return AK_Fail in Release + /// \sa + /// - AK::SoundEngine::UnregisterResourceMonitorCallback() + /// - AkResourceMonitorCallbackFunc + AKRESULT RegisterResourceMonitorCallback( + AkResourceMonitorCallbackFunc in_pCallback ///< Function to register as a resource monitor callback. + ) override; + + /// Unregisters a resource monitor callback function, previously registered using RegisterResourceMonitorCallback. + /// \remarks + /// It is legal to call this function while already inside of a resource monitor callback, If it is unregistering itself and not + /// another callback. + /// This function might stall for several milliseconds before returning. + /// \sa + /// - AK::SoundEngine::RegisterResourceMonitorCallback() + /// - AkResourceMonitorCallbackFunc + AKRESULT UnregisterResourceMonitorCallback( + AkResourceMonitorCallbackFunc in_pCallback ///< Function to unregister as a resource monitor callback. + ) override; + + /// Registers a callback for the Audio Device status changes. + /// The callback will be called from the audio thread + /// Can be called prior to AK::SoundEngine::Init + /// \sa AK::SoundEngine::AddOutput + AKRESULT RegisterAudioDeviceStatusCallback( + AK::AkDeviceStatusCallbackFunc in_pCallback ///< Function to register as a status callback. + ) override; + + /// Unregisters the callback for the Audio Device status changes, registered by RegisterAudioDeviceStatusCallback + AKRESULT UnregisterAudioDeviceStatusCallback() override; + //@} + +#ifdef AK_SUPPORT_WCHAR + //////////////////////////////////////////////////////////////////////// + /// @name Getting ID from strings + //@{ + + /// Universal converter from Unicode string to ID for the sound engine. + /// This function will hash the name based on a algorithm ( provided at : /AK/Tools/Common/AkFNVHash.h ) + /// Note: + /// This function does return a AkUInt32, which is totally compatible with: + /// AkUniqueID, AkStateGroupID, AkStateID, AkSwitchGroupID, AkSwitchStateID, AkRtpcID, and so on... + /// \sa + /// - AK::SoundEngine::PostEvent + /// - AK::SoundEngine::SetRTPCValue + /// - AK::SoundEngine::SetSwitch + /// - AK::SoundEngine::SetState + /// - AK::SoundEngine::PostTrigger + /// - AK::SoundEngine::SetGameObjectAuxSendValues + /// - AK::SoundEngine::LoadBank + /// - AK::SoundEngine::UnloadBank + /// - AK::SoundEngine::PrepareEvent + /// - AK::SoundEngine::PrepareGameSyncs + AkUInt32 GetIDFromString(const wchar_t* in_pszString) override; +#endif //AK_SUPPORT_WCHAR + + /// Universal converter from string to ID for the sound engine. + /// This function will hash the name based on a algorithm ( provided at : /AK/Tools/Common/AkFNVHash.h ) + /// Note: + /// This function does return a AkUInt32, which is totally compatible with: + /// AkUniqueID, AkStateGroupID, AkStateID, AkSwitchGroupID, AkSwitchStateID, AkRtpcID, and so on... + /// \sa + /// - AK::SoundEngine::PostEvent + /// - AK::SoundEngine::SetRTPCValue + /// - AK::SoundEngine::SetSwitch + /// - AK::SoundEngine::SetState + /// - AK::SoundEngine::PostTrigger + /// - AK::SoundEngine::SetGameObjectAuxSendValues + /// - AK::SoundEngine::LoadBank + /// - AK::SoundEngine::UnloadBank + /// - AK::SoundEngine::PrepareEvent + /// - AK::SoundEngine::PrepareGameSyncs + AkUInt32 GetIDFromString(const char* in_pszString) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Event Management + //@{ + + /// Asynchronously posts an Event to the sound engine (by event ID). + /// The callback function can be used to be noticed when markers are reached or when the event is finished. + /// An array of wave file sources can be provided to resolve External Sources triggered by the event. + /// \return The playing ID of the event launched, or AK_INVALID_PLAYING_ID if posting the event failed and an error will be displayed in the debug console and the Wwise Profiler. + /// \remarks + /// If used, the array of external sources should contain the information for each external source triggered by the + /// event. When triggering an event with multiple external sources, you need to differentiate each source + /// by using the cookie property in the External Source in the Wwise project and in AkExternalSourceInfo. + /// \aknote If an event triggers the playback of more than one external source, they must be named uniquely in the project + /// (therefore have a unique cookie) in order to tell them apart when filling the AkExternalSourceInfo structures. + /// \endaknote + /// \sa + /// - \ref concept_events + /// - \ref integrating_external_sources + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::GetSourcePlayPosition() + AkPlayingID PostEvent( + AkUniqueID in_eventID, ///< Unique ID of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID + AkUInt32 in_uFlags = 0, ///< Bitmask: see \ref AkCallbackType + AkCallbackFunc in_pfnCallback = NULL, ///< Callback function + void* in_pCookie = NULL, ///< Callback cookie that will be sent to the callback function along with additional information + AkUInt32 in_cExternals = 0, ///< Optional count of external source structures + AkExternalSourceInfo* in_pExternalSources = NULL,///< Optional array of external source resolution information + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID///< Optional (advanced users only) Specify the playing ID to target with the event. Will Cause active actions in this event to target an existing Playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any for normal playback. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Posts an Event to the sound engine (by Event name), using callbacks. + /// The callback function can be used to be noticed when markers are reached or when the event is finished. + /// An array of wave file sources can be provided to resolve External Sources triggered by the event. + /// \return The playing ID of the event launched, or AK_INVALID_PLAYING_ID if posting the event failed and an error will be displayed in the debug console and the Wwise Profiler. + /// \remarks + /// If used, the array of external sources should contain the information for each external source triggered by the + /// event. When triggering an event with multiple external sources, you need to differentiate each source + /// by using the cookie property in the External Source in the Wwise project and in AkExternalSourceInfo. + /// \aknote If an event triggers the playback of more than one external source, they must be named uniquely in the project + /// (therefore have a unique cookie) in order to tell them appart when filling the AkExternalSourceInfo structures. + /// \endaknote + /// \sa + /// - \ref concept_events + /// - \ref integrating_external_sources + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::GetSourcePlayPosition() + AkPlayingID PostEvent( + const wchar_t* in_pszEventName, ///< Name of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID + AkUInt32 in_uFlags = 0, ///< Bitmask: see \ref AkCallbackType + AkCallbackFunc in_pfnCallback = NULL, ///< Callback function + void* in_pCookie = NULL, ///< Callback cookie that will be sent to the callback function along with additional information. + AkUInt32 in_cExternals = 0, ///< Optional count of external source structures + AkExternalSourceInfo* in_pExternalSources = NULL,///< Optional array of external source resolution information + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID///< Optional (advanced users only) Specify the playing ID to target with the event. Will Cause active actions in this event to target an existing Playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any for normal playback. + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Posts an Event to the sound engine (by Event name), using callbacks. + /// The callback function can be used to be noticed when markers are reached or when the event is finished. + /// An array of Wave file sources can be provided to resolve External Sources triggered by the event. P + /// \return The playing ID of the event launched, or AK_INVALID_PLAYING_ID if posting the event failed and an error will be displayed in the debug console and the Wwise Profiler. + /// \remarks + /// If used, the array of external sources should contain the information for each external source triggered by the + /// event. When triggering an Event with multiple external sources, you need to differentiate each source + /// by using the cookie property in the External Source in the Wwise project and in AkExternalSourceInfo. + /// \aknote If an event triggers the playback of more than one external source, they must be named uniquely in the project + /// (therefore have a unique cookie) in order to tell them apart when filling the AkExternalSourceInfo structures. + /// \endaknote + /// \sa + /// - \ref concept_events + /// - \ref integrating_external_sources + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::GetSourcePlayPosition() + AkPlayingID PostEvent( + const char* in_pszEventName, ///< Name of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID + AkUInt32 in_uFlags = 0, ///< Bitmask: see \ref AkCallbackType + AkCallbackFunc in_pfnCallback = NULL, ///< Callback function + void* in_pCookie = NULL, ///< Callback cookie that will be sent to the callback function along with additional information. + AkUInt32 in_cExternals = 0, ///< Optional count of external source structures + AkExternalSourceInfo* in_pExternalSources = NULL,///< Optional array of external source resolution information + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID///< Optional (advanced users only) Specify the playing ID to target with the event. Will Cause active actions in this event to target an existing Playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any for normal playback. + ) override; + + /// Executes an action on all nodes that are referenced in the specified event in an action of type play. + /// \return + /// - \c AK_Success if the action was successfully queued. + /// - \c AK_IDNotFound if the Event was not found (not loaded or there is a typo in the ID) + /// \sa + /// - AK::SoundEngine::AkActionOnEventType + AKRESULT ExecuteActionOnEvent( + AkUniqueID in_eventID, ///< Unique ID of the event + AK::SoundEngine::AkActionOnEventType in_ActionType, ///< Action to execute on all the elements that were played using the specified event. + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT, ///< Associated game object ID + AkTimeMs in_uTransitionDuration = 0, ///< Fade duration + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the transition + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Associated PlayingID + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Executes an action on all nodes that are referenced in the specified event in an action of type play. + /// \return + /// - \c AK_Success if the action was successfully queued. + /// - \c AK_IDNotFound if the Event was not found (not loaded or there is a typo in the ID) + /// \sa + /// - AK::SoundEngine::AkActionOnEventType + AKRESULT ExecuteActionOnEvent( + const wchar_t* in_pszEventName, ///< Name of the event + AK::SoundEngine::AkActionOnEventType in_ActionType, ///< Action to execute on all the elements that were played using the specified event. + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT, ///< Associated game object ID + AkTimeMs in_uTransitionDuration = 0, ///< Fade duration + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the transition + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Associated PlayingID + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Executes an Action on all nodes that are referenced in the specified Event in an Action of type Play. + /// \return + /// - \c AK_Success if the action was successfully queued. + /// - \c AK_IDNotFound if the Event was not found (not loaded or there is a typo in the ID) + /// \sa + /// - AK::SoundEngine::AkActionOnEventType + AKRESULT ExecuteActionOnEvent( + const char* in_pszEventName, ///< Name of the event + AK::SoundEngine::AkActionOnEventType in_ActionType, ///< Action to execute on all the elements that were played using the specified event. + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT, ///< Associated game object ID + AkTimeMs in_uTransitionDuration = 0, ///< Fade duration + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the transition + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Associated PlayingID + ) override; + + + /// Executes a number of MIDI Events on all nodes that are referenced in the specified Event in an Action of type Play. + /// The time at which a MIDI Event is posted is determined by in_bAbsoluteOffsets. If false, each MIDI event will be + /// posted in AkMIDIPost::uOffset samples from the start of the current frame. If true, each MIDI event will be posted + /// at the absolute time AkMIDIPost::uOffset samples. + /// To obtain the current absolute time, see AK::SoundEngine::GetSampleTick. + /// The duration of a sample can be determined from the sound engine's audio settings, via a call to AK::SoundEngine::GetAudioSettings. + /// If a playing ID is specified then that playing ID must be active. Otherwise a new playing ID will be assigned. + /// \return The playing ID of the event launched, or AK_INVALID_PLAYING_ID if posting the event failed and an error will be displayed in the debug console and the Wwise Profiler. + /// \sa + /// - AK::SoundEngine::GetAudioSettings + /// - AK::SoundEngine::GetSampleTick + /// - AK::SoundEngine::StopMIDIOnEvent + /// - \ref soundengine_midi_event_playing_id + AkPlayingID PostMIDIOnEvent( + AkUniqueID in_eventID, ///< Unique ID of the Event + AkGameObjectID in_gameObjectID, ///< Associated game object ID + AkMIDIPost* in_pPosts, ///< MIDI Events to post + AkUInt16 in_uNumPosts, ///< Number of MIDI Events to post + bool in_bAbsoluteOffsets = false, ///< Set to true when AkMIDIPost::uOffset are absolute, false when relative to current frame + AkUInt32 in_uFlags = 0, ///< Bitmask: see \ref AkCallbackType + AkCallbackFunc in_pfnCallback = NULL, ///< Callback function + void* in_pCookie = NULL, ///< Callback cookie that will be sent to the callback function along with additional information + AkPlayingID in_playingID = AK_INVALID_PLAYING_ID ///< Target playing ID + ) override; + + /// Stops MIDI notes on all nodes that are referenced in the specified event in an action of type play, + /// with the specified Game Object. Invalid parameters are interpreted as wildcards. For example, calling + /// this function with in_eventID set to AK_INVALID_UNIQUE_ID will stop all MIDI notes for Game Object + /// in_gameObjectID. + /// \return + /// - \c AK_Success if the stop command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::PostMIDIOnEvent + /// - \ref soundengine_midi_event_playing_id + AKRESULT StopMIDIOnEvent( + AkUniqueID in_eventID = AK_INVALID_UNIQUE_ID, ///< Unique ID of the Event + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT, ///< Associated game object ID + AkPlayingID in_playingID = AK_INVALID_PLAYING_ID ///< Target playing ID + ) override; + + + /// Starts streaming the first part of all streamed files referenced by an Event into a cache buffer. Caching streams are serviced when no other streams require the + /// available bandwidth. The files will remain cached until UnpinEventInStreamCache is called, or a higher priority pinned file needs the space and the limit set by + /// uMaxCachePinnedBytes is exceeded. + /// \remarks The amount of data from the start of the file that will be pinned to cache is determined by the prefetch size. The prefetch size is set via the authoring tool and stored in the sound banks. + /// \remarks It is possible to override the prefetch size stored in the sound bank via the low level IO. For more information see AK::StreamMgr::IAkFileLocationResolver::Open() and AkFileSystemFlags. + /// \remarks If this function is called additional times with the same event, then the priority of the caching streams are updated. Note however that priority is passed down to the stream manager + /// on a file-by-file basis, and if another event is pinned to cache that references the same file but with a different priority, then the first priority will be updated with the most recent value. + /// \remarks If the event references files that are chosen based on a State Group (via a switch container), all files in all states will be cached. Those in the current active state + /// will get cached with active priority, while all other files will get cached with inactive priority. + /// \remarks in_uInactivePriority is only relevant for events that reference switch containers that are assigned to State Groups. This parameter is ignored for all other events, including events that only reference + /// switch containers that are assigned to Switch Groups. Files that are chosen based on a Switch Group have a different switch value per game object, and are all effectively considered active by the pin-to-cache system. + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::GetBufferStatusForPinnedEvent + /// - AK::SoundEngine::UnpinEventInStreamCache + /// - AK::StreamMgr::IAkFileLocationResolver::Open + /// - AkFileSystemFlags + AKRESULT PinEventInStreamCache( + AkUniqueID in_eventID, ///< Unique ID of the event + AkPriority in_uActivePriority, ///< Priority of active stream caching I/O + AkPriority in_uInactivePriority ///< Priority of inactive stream caching I/O + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Starts streaming the first part of all streamed files referenced by an event into a cache buffer. Caching streams are serviced when no other streams require the + /// available bandwidth. The files will remain cached until UnpinEventInStreamCache is called, or a higher priority pinned file needs the space and the limit set by + /// uMaxCachePinnedBytes is exceeded. + /// \remarks The amount of data from the start of the file that will be pinned to cache is determined by the prefetch size. The prefetch size is set via the authoring tool and stored in the sound banks. + /// \remarks It is possible to override the prefetch size stored in the sound bank via the low level IO. For more information see AK::StreamMgr::IAkFileLocationResolver::Open() and AkFileSystemFlags. + /// \remarks If this function is called additional times with the same event, then the priority of the caching streams are updated. Note however that priority is passed down to the stream manager + /// on a file-by-file basis, and if another event is pinned to cache that references the same file but with a different priority, then the first priority will be updated with the most recent value. + /// \remarks If the event references files that are chosen based on a State Group (via a Switch Container), all files in all states will be cached. Those in the current active state + /// will get cached with active priority, while all other files will get cached with inactive priority. + /// \remarks in_uInactivePriority is only relevant for events that reference switch containers that are assigned to State Groups. This parameter is ignored for all other events, including events that only reference + /// switch containers that are assigned to Switch Groups. Files that are chosen based on a Switch Group have a different switch value per game object, and are all effectively considered active by the pin-to-cache system. + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::GetBufferStatusForPinnedEvent + /// - AK::SoundEngine::UnpinEventInStreamCache + /// - AK::StreamMgr::IAkFileLocationResolver::Open + /// - AkFileSystemFlags + AKRESULT PinEventInStreamCache( + const wchar_t* in_pszEventName, ///< Name of the event + AkPriority in_uActivePriority, ///< Priority of active stream caching I/O + AkPriority in_uInactivePriority ///< Priority of inactive stream caching I/O + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Starts streaming the first part of all streamed files referenced by an event into a cache buffer. Caching streams are serviced when no other streams require the + /// available bandwidth. The files will remain cached until UnpinEventInStreamCache is called, or a higher priority pinned file needs the space and the limit set by + /// uMaxCachePinnedBytes is exceeded. + /// \remarks The amount of data from the start of the file that will be pinned to cache is determined by the prefetch size. The prefetch size is set via the authoring tool and stored in the sound banks. + /// \remarks It is possible to override the prefetch size stored in the sound bank via the low level IO. For more information see AK::StreamMgr::IAkFileLocationResolver::Open() and AkFileSystemFlags. + /// \remarks If this function is called additional times with the same event, then the priority of the caching streams are updated. Note however that priority is passed down to the stream manager + /// on a file-by-file basis, and if another event is pinned to cache that references the same file but with a different priority, then the first priority will be updated with the most recent value. + /// \remarks If the event references files that are chosen based on a State Group (via a switch container), all files in all states will be cached. Those in the current active state + /// will get cached with active priority, while all other files will get cached with inactive priority. + /// \remarks in_uInactivePriority is only relevant for events that reference switch containers that are assigned to State Groups. This parameter is ignored for all other events, including events that only reference + /// switch containers that are assigned to Switch Groups. Files that are chosen based on a Switch Group have a different switch value per game object, and are all effectively considered active by the pin-to-cache system. + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::GetBufferStatusForPinnedEvent + /// - AK::SoundEngine::UnpinEventInStreamCache + /// - AK::StreamMgr::IAkFileLocationResolver::Open + /// - AkFileSystemFlags + AKRESULT PinEventInStreamCache( + const char* in_pszEventName, ///< Name of the event + AkPriority in_uActivePriority, ///< Priority of active stream caching I/O + AkPriority in_uInactivePriority ///< Priority of inactive stream caching I/O + ) override; + + /// Releases the set of files that were previously requested to be pinned into cache via AK::SoundEngine::PinEventInStreamCache(). The file may still remain in stream cache + /// after AK::SoundEngine::UnpinEventInStreamCache() is called, until the memory is reused by the streaming memory manager in accordance with to its cache management algorithm. + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::PinEventInStreamCache + /// - AK::SoundEngine::GetBufferStatusForPinnedEvent + AKRESULT UnpinEventInStreamCache( + AkUniqueID in_eventID ///< Unique ID of the event + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Releases the set of files that were previously requested to be pinned into cache via AK::SoundEngine::PinEventInStreamCache(). The file may still remain in stream cache + /// after AK::SoundEngine::UnpinEventInStreamCache() is called, until the memory is reused by the streaming memory manager in accordance with to its cache management algorithm. + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::PinEventInStreamCache + /// - AK::SoundEngine::GetBufferStatusForPinnedEvent + AKRESULT UnpinEventInStreamCache( + const wchar_t* in_pszEventName ///< Name of the event + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Releases the set of files that were previously requested to be pinned into cache via AK::SoundEngine::PinEventInStreamCache(). The file may still remain in stream cache + /// after AK::SoundEngine::UnpinEventInStreamCache() is called, until the memory is reused by the streaming memory manager in accordance with to its cache management algorithm. + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::PinEventInStreamCache + /// - AK::SoundEngine::GetBufferStatusForPinnedEvent + AKRESULT UnpinEventInStreamCache( + const char* in_pszEventName ///< Name of the event + ) override; + + /// Returns information about an Event that was requested to be pinned into cache via AK::SoundEngine::PinEventInStreamCache(). + /// Retrieves the smallest buffer fill-percentage for each file referenced by the event, and whether + /// the cache-pinned memory limit is preventing any of the files from filling up their buffer. + /// \remarks To set the limit for the maximum number of bytes that can be pinned to cache, see \c AkDeviceSettings + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::PinEventInStreamCache + /// - AK::SoundEngine::UnpinEventInStreamCache + /// - AkDeviceSettings + AKRESULT GetBufferStatusForPinnedEvent( + AkUniqueID in_eventID, ///< Unique ID of the event + AkReal32& out_fPercentBuffered, ///< Fill-percentage (out of 100) of requested buffer size for least buffered file in the event. + bool& out_bCachePinnedMemoryFull ///< True if at least one file can not complete buffering because the cache-pinned memory limit would be exceeded. + ) override; + + /// Returns information about an Event that was requested to be pinned into cache via \c AK::SoundEngine::PinEventInStreamCache(). + /// Retrieves the smallest buffer fill-percentage for each file referenced by the event, and whether + /// the cache-pinned memory limit is preventing any of the files from filling up their buffer. + /// \remarks To set the limit for the maximum number of bytes that can be pinned to cache, see AkDeviceSettings + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::PinEventInStreamCache + /// - AK::SoundEngine::UnpinEventInStreamCache + /// - AkDeviceSettings + AKRESULT GetBufferStatusForPinnedEvent( + const char* in_pszEventName, ///< Name of the event + AkReal32& out_fPercentBuffered, ///< Fill-percentage (out of 100) of requested buffer size for least buffered file in the event. + bool& out_bCachePinnedMemoryFull ///< True if at least one file can not complete buffering because the cache-pinned memory limit would be exceeded. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Returns information about an Event that was requested to be pinned into cache via \c AK::SoundEngine::PinEventInStreamCache(). + /// Retrieves the smallest buffer fill-percentage for each file referenced by the event, and whether + /// the cache-pinned memory limit is preventing any of the files from filling up their buffer. + /// \remarks To set the limit for the maximum number of bytes that can be pinned to cache, see AkDeviceSettings + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::PinEventInStreamCache + /// - AK::SoundEngine::UnpinEventInStreamCache + /// - AkDeviceSettings + AKRESULT GetBufferStatusForPinnedEvent( + const wchar_t* in_pszEventName, ///< Name of the event + AkReal32& out_fPercentBuffered, ///< Fill-percentage (out of 100) of requested buffer size for least buffered file in the event. + bool& out_bCachePinnedMemoryFull ///< True if at least one file can not complete buffering because the cache-pinned memory limit would be exceeded. + ) override; +#endif + + /// Seeks inside all playing objects that are referenced in Play Actions of the specified Event. + /// + /// Notes: + /// - This works with all objects of the actor-mixer hierarchy, and also with Music Segments and Music Switch Containers. + /// - There is a restriction with sounds that play within a continuous sequence. Seeking is ignored + /// if one of their ancestors is a continuous (random or sequence) container with crossfade or + /// trigger rate transitions. Seeking is also ignored with sample-accurate transitions, unless + /// the sound that is currently playing is the first sound of the sequence. + /// - Seeking is also ignored with voices that can go virtual with "From Beginning" behavior. + /// - Sounds/segments are stopped if in_iPosition is greater than their duration. + /// - in_iPosition is clamped internally to the beginning of the sound/segment. + /// - If the option "Seek to nearest marker" is used, the seeking position snaps to the nearest marker. + /// With objects of the actor-mixer hierarchy, markers are embedded in wave files by an external wave editor. + /// Note that looping regions ("sampler loop") are not considered as markers. Also, the "add file name marker" of the + /// conversion settings dialog adds a marker at the beginning of the file, which is considered when seeking + /// to nearest marker. In the case of objects of the interactive music hierarchy, user (wave) markers are ignored: + /// seeking occurs to the nearest segment cue (authored in the segment editor), including the Entry Cue, but excluding the Exit Cue. + /// - This method posts a command in the sound engine queue, thus seeking will not occur before + /// the audio thread consumes it (after a call to RenderAudio()). + /// + /// Notes specific to Music Segments: + /// - With Music Segments, in_iPosition is relative to the Entry Cue, in milliseconds. Use a negative + /// value to seek within the Pre-Entry. + /// - Music segments cannot be looped. You may want to listen to the AK_EndOfEvent notification + /// in order to restart them if required. + /// - In order to restart at the correct location, with all their tracks synchronized, Music Segments + /// take the "look-ahead time" property of their streamed tracks into account for seeking. + /// Consequently, the resulting position after a call to SeekOnEvent() might be earlier than the + /// value that was passed to the method. Use AK::MusicEngine::GetPlayingSegmentInfo() to query + /// the exact position of a segment. Also, the segment will be silent during that time + /// (so that it restarts precisely at the position that you specified). AK::MusicEngine::GetPlayingSegmentInfo() + /// also informs you about the remaining look-ahead time. + /// + /// Notes specific to Music Switch Containers: + /// - Seeking triggers a music transition towards the current (or target) segment. + /// This transition is subject to the container's transition rule that matches the current and defined in the container, + /// so the moment when seeking occurs depends on the rule's "Exit At" property. On the other hand, the starting position + /// in the target segment depends on both the desired seeking position and the rule's "Sync To" property. + /// - If the specified time is greater than the destination segment's length, the modulo is taken. + /// + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::PostEvent() + /// - AK::SoundEngine::GetSourcePlayPosition() + /// - AK::MusicEngine::GetPlayingSegmentInfo() + AKRESULT SeekOnEvent( + AkUniqueID in_eventID, ///< Unique ID of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID; use AK_INVALID_GAME_OBJECT to affect all game objects + AkTimeMs in_iPosition, ///< Desired position where playback should restart, in milliseconds + bool in_bSeekToNearestMarker = false, ///< If true, the final seeking position will be made equal to the nearest marker (see note above) + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Specify the playing ID for the seek to be applied to. Will result in the seek happening only on active actions of the playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any, to seek on all active actions of this event ID. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Seeks inside all playing objects that are referenced in Play Actions of the specified Event. + /// + /// Notes: + /// - This works with all objects of the actor-mixer hierarchy, and also with Music Segments and Music Switch Containers. + /// - There is a restriction with sounds that play within a continuous sequence. Seeking is ignored + /// if one of their ancestors is a continuous (random or sequence) container with crossfade or + /// trigger rate transitions. Seeking is also ignored with sample-accurate transitions, unless + /// the sound that is currently playing is the first sound of the sequence. + /// - Seeking is also ignored with voices that can go virtual with "From Beginning" behavior. + /// - With Music Segments, in_iPosition is relative to the Entry Cue, in milliseconds. Use a negative + /// value to seek within the Pre-Entry. + /// - Sounds/segments are stopped if in_iPosition is greater than their duration. + /// - in_iPosition is clamped internally to the beginning of the sound/segment. + /// - If the option "Seek to nearest marker" is used, the seeking position snaps to the nearest marker. + /// With objects of the actor-mixer hierarchy, markers are embedded in wave files by an external wave editor. + /// Note that looping regions ("sampler loop") are not considered as markers. Also, the "add file name marker" of the + /// conversion settings dialog adds a marker at the beginning of the file, which is considered when seeking + /// to nearest marker. In the case of objects of the interactive music hierarchy, user (wave) markers are ignored: + /// seeking occurs to the nearest segment cue (authored in the segment editor), including the Entry Cue, but excluding the Exit Cue. + /// - This method posts a command in the sound engine queue, thus seeking will not occur before + /// the audio thread consumes it (after a call to RenderAudio()). + /// + /// Notes specific to Music Segments: + /// - With Music Segments, in_iPosition is relative to the Entry Cue, in milliseconds. Use a negative + /// value to seek within the Pre-Entry. + /// - Music segments cannot be looped. You may want to listen to the AK_EndOfEvent notification + /// in order to restart them if required. + /// - In order to restart at the correct location, with all their tracks synchronized, Music Segments + /// take the "look-ahead time" property of their streamed tracks into account for seeking. + /// Consequently, the resulting position after a call to SeekOnEvent() might be earlier than the + /// value that was passed to the method. Use AK::MusicEngine::GetPlayingSegmentInfo() to query + /// the exact position of a segment. Also, the segment will be silent during that time + /// (so that it restarts precisely at the position that you specified). AK::MusicEngine::GetPlayingSegmentInfo() + /// also informs you about the remaining look-ahead time. + /// + /// Notes specific to Music Switch Containers: + /// - Seeking triggers a music transition towards the current (or target) segment. + /// This transition is subject to the container's transition rule that matches the current and defined in the container, + /// so the moment when seeking occurs depends on the rule's "Exit At" property. On the other hand, the starting position + /// in the target segment depends on both the desired seeking position and the rule's "Sync To" property. + /// - If the specified time is greater than the destination segment's length, the modulo is taken. + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::PostEvent() + /// - AK::SoundEngine::GetSourcePlayPosition() + /// - AK::MusicEngine::GetPlayingSegmentInfo() + AKRESULT SeekOnEvent( + const wchar_t* in_pszEventName, ///< Name of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID; use AK_INVALID_GAME_OBJECT to affect all game objects + AkTimeMs in_iPosition, ///< Desired position where playback should restart, in milliseconds + bool in_bSeekToNearestMarker = false, ///< If true, the final seeking position will be made equal to the nearest marker (see note above) + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Specify the playing ID for the seek to be applied to. Will result in the seek happening only on active actions of the playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any, to seek on all active actions of this event ID. + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Seeks inside all playing objects that are referenced in Play Actions of the specified Event. + /// + /// Notes: + /// - This works with all objects of the actor-mixer hierarchy, and also with Music Segments and Music Switch Containers. + /// - There is a restriction with sounds that play within a continuous sequence. Seeking is ignored + /// if one of their ancestors is a continuous (random or sequence) container with crossfade or + /// trigger rate transitions. Seeking is also ignored with sample-accurate transitions, unless + /// the sound that is currently playing is the first sound of the sequence. + /// - Seeking is also ignored with voices that can go virtual with "From Beginning" behavior. + /// - With Music Segments, in_iPosition is relative to the Entry Cue, in milliseconds. Use a negative + /// value to seek within the Pre-Entry. + /// - Sounds/segments are stopped if in_iPosition is greater than their duration. + /// - in_iPosition is clamped internally to the beginning of the sound/segment. + /// - If the option "Seek to nearest marker" is used, the seeking position snaps to the nearest marker. + /// With objects of the actor-mixer hierarchy, markers are embedded in wave files by an external wave editor. + /// Note that looping regions ("sampler loop") are not considered as markers. Also, the "add file name marker" of the + /// conversion settings dialog adds a marker at the beginning of the file, which is considered when seeking + /// to nearest marker. In the case of objects of the interactive music hierarchy, user (wave) markers are ignored: + /// seeking occurs to the nearest segment cue (authored in the segment editor), including the Entry Cue, but excluding the Exit Cue. + /// - This method posts a command in the sound engine queue, thus seeking will not occur before + /// the audio thread consumes it (after a call to RenderAudio()). + /// + /// Notes specific to Music Segments: + /// - With Music Segments, in_iPosition is relative to the Entry Cue, in milliseconds. Use a negative + /// value to seek within the Pre-Entry. + /// - Music segments cannot be looped. You may want to listen to the AK_EndOfEvent notification + /// in order to restart them if required. + /// - In order to restart at the correct location, with all their tracks synchronized, Music Segments + /// take the "look-ahead time" property of their streamed tracks into account for seeking. + /// Consequently, the resulting position after a call to SeekOnEvent() might be earlier than the + /// value that was passed to the method. Use AK::MusicEngine::GetPlayingSegmentInfo() to query + /// the exact position of a segment. Also, the segment will be silent during that time + /// (so that it restarts precisely at the position that you specified). AK::MusicEngine::GetPlayingSegmentInfo() + /// also informs you about the remaining look-ahead time. + /// + /// Notes specific to Music Switch Containers: + /// - Seeking triggers a music transition towards the current (or target) segment. + /// This transition is subject to the container's transition rule that matches the current and defined in the container, + /// so the moment when seeking occurs depends on the rule's "Exit At" property. On the other hand, the starting position + /// in the target segment depends on both the desired seeking position and the rule's "Sync To" property. + /// - If the specified time is greater than the destination segment's length, the modulo is taken. + /// + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::PostEvent() + /// - AK::SoundEngine::GetSourcePlayPosition() + /// - AK::MusicEngine::GetPlayingSegmentInfo() + AKRESULT SeekOnEvent( + const char* in_pszEventName, ///< Name of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID; use AK_INVALID_GAME_OBJECT to affect all game objects + AkTimeMs in_iPosition, ///< Desired position where playback should restart, in milliseconds + bool in_bSeekToNearestMarker = false, ///< If true, the final seeking position will be made equal to the nearest marker (see note above) + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Specify the playing ID for the seek to be applied to. Will result in the seek happening only on active actions of the playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any, to seek on all active actions of this event ID. + ) override; + + /// Seeks inside all playing objects that are referenced in Play Actions of the specified Event. + /// Seek position is specified as a percentage of the sound's total duration, and takes looping into account. + /// + /// Notes: + /// - This works with all objects of the actor-mixer hierarchy, and also with Music Segments and Music Switch Containers. + /// - There is a restriction with sounds that play within a continuous sequence. Seeking is ignored + /// if one of their ancestors is a continuous (random or sequence) container with crossfade or + /// trigger rate transitions. Seeking is also ignored with sample-accurate transitions, unless + /// the sound that is currently playing is the first sound of the sequence. + /// - Seeking is also ignored with voices that can go virtual with "From Beginning" behavior. + /// - in_iPosition is clamped internally to the beginning of the sound/segment. + /// - If the option "Seek to nearest marker" is used, the seeking position snaps to the nearest marker. + /// With objects of the actor-mixer hierarchy, markers are embedded in wave files by an external wave editor. + /// Note that looping regions ("sampler loop") are not considered as markers. Also, the "add file name marker" of the + /// conversion settings dialog adds a marker at the beginning of the file, which is considered when seeking + /// to nearest marker. In the case of objects of the interactive music hierarchy, user (wave) markers are ignored: + /// seeking occurs to the nearest segment cue (authored in the segment editor), including the Entry Cue, but excluding the Exit Cue. + /// - This method posts a command in the sound engine queue, thus seeking will not occur before + /// the audio thread consumes it (after a call to RenderAudio()). + /// + /// Notes specific to Music Segments: + /// - With Music Segments, \c in_fPercent is relative to the Entry Cue, and the segment's duration is the + /// duration between its entry and exit cues. Consequently, you cannot seek within the pre-entry or + /// post-exit of a segment using this method. Use absolute values instead. + /// - Music Segments cannot be looped. You may want to listen to the \c AK_EndOfEvent notification + /// in order to restart them if required. + /// - In order to restart at the correct location, with all their tracks synchronized, Music Segments + /// take the "look-ahead time" property of their streamed tracks into account for seeking. + /// Consequently, the resulting position after a call to SeekOnEvent() might be earlier than the + /// value that was passed to the method. Use AK::MusicEngine::GetPlayingSegmentInfo() to query + /// the exact position of a segment. Also, the segment will be silent during the time that period + /// (so that it restarts precisely at the position that you specified). AK::MusicEngine::GetPlayingSegmentInfo() + /// also informs you about the remaining look-ahead time. + /// + /// Notes specific to Music Switch Containers: + /// - Seeking triggers a music transition towards the current (or target) segment. + /// This transition is subject to the container's transition rule that matches the current and defined in the container, + /// so the moment when seeking occurs depends on the rule's "Exit At" property. On the other hand, the starting position + /// in the target segment depends on both the desired seeking position and the rule's "Sync To" property. + /// - If the specified time is greater than the destination segment's length, the modulo is taken. + /// + /// \sa + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::PostEvent() + /// - AK::SoundEngine::GetSourcePlayPosition() + /// - AK::MusicEngine::GetPlayingSegmentInfo() + AKRESULT SeekOnEvent( + AkUniqueID in_eventID, ///< Unique ID of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID; use AK_INVALID_GAME_OBJECT to affect all game objects + AkReal32 in_fPercent, ///< Desired position where playback should restart, expressed in a percentage of the file's total duration, between 0 and 1.f (see note above about infinite looping sounds) + bool in_bSeekToNearestMarker = false, ///< If true, the final seeking position will be made equal to the nearest marker (see note above) + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Specify the playing ID for the seek to be applied to. Will result in the seek happening only on active actions of the playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any, to seek on all active actions of this event ID. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Seeks inside all playing objects that are referenced in Play Actions of the specified Event. + /// Seek position is specified as a percentage of the sound's total duration, and takes looping into account. + /// + /// Notes: + /// - This works with all objects of the actor-mixer hierarchy, and also with Music Segments and Music Switch Containers. + /// - There is a restriction with sounds that play within a continuous sequence. Seeking is ignored + /// if one of their ancestors is a continuous (random or sequence) container with crossfade or + /// trigger rate transitions. Seeking is also ignored with sample-accurate transitions, unless + /// the sound that is currently playing is the first sound of the sequence. + /// - Seeking is also ignored with voices that can go virtual with "From Beginning" behavior. + /// - If the option "Seek to nearest marker" is used, the seeking position snaps to the nearest marker. + /// With objects of the actor-mixer hierarchy, markers are embedded in wave files by an external wave editor. + /// Note that looping regions ("sampler loop") are not considered as markers. Also, the "add file name marker" of the + /// conversion settings dialog adds a marker at the beginning of the file, which is considered when seeking + /// to nearest marker. In the case of objects of the interactive music hierarchy, user (wave) markers are ignored: + /// seeking occurs to the nearest segment cue (authored in the segment editor), including the Entry Cue, but excluding the Exit Cue. + /// - This method posts a command in the sound engine queue, thus seeking will not occur before + /// the audio thread consumes it (after a call to RenderAudio()). + /// + /// Notes specific to Music Segments: + /// - With Music Segments, \c in_fPercent is relative to the Entry Cue, and the segment's duration is the + /// duration between its entry and exit cues. Consequently, you cannot seek within the pre-entry or + /// post-exit of a segment using this method. Use absolute values instead. + /// - Music Segments cannot be looped. You may want to listen to the \c AK_EndOfEvent notification + /// in order to restart them if required. + /// - In order to restart at the correct location, with all their tracks synchronized, Music Segments + /// take the "look-ahead time" property of their streamed tracks into account for seeking. + /// Consequently, the resulting position after a call to SeekOnEvent() might be earlier than the + /// value that was passed to the method. Use AK::MusicEngine::GetPlayingSegmentInfo() to query + /// the exact position of a segment. Also, the segment will be silent during the time that period + /// (so that it restarts precisely at the position that you specified). AK::MusicEngine::GetPlayingSegmentInfo() + /// also informs you about the remaining look-ahead time. + /// + /// Notes specific to Music Switch Containers: + /// - Seeking triggers a music transition towards the current (or target) segment. + /// This transition is subject to the container's transition rule that matches the current and defined in the container, + /// so the moment when seeking occurs depends on the rule's "Exit At" property. On the other hand, the starting position + /// in the target segment depends on both the desired seeking position and the rule's "Sync To" property. + /// - If the specified time is greater than the destination segment's length, the modulo is taken. + /// + /// \sa + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::PostEvent() + /// - AK::SoundEngine::GetSourcePlayPosition() + /// - AK::MusicEngine::GetPlayingSegmentInfo() + AKRESULT SeekOnEvent( + const wchar_t* in_pszEventName, ///< Name of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID; use AK_INVALID_GAME_OBJECT to affect all game objects + AkReal32 in_fPercent, ///< Desired position where playback should restart, expressed in a percentage of the file's total duration, between 0 and 1.f (see note above about infinite looping sounds) + bool in_bSeekToNearestMarker = false, ///< If true, the final seeking position will be made equal to the nearest marker (see note above) + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Specify the playing ID for the seek to be applied to. Will result in the seek happening only on active actions of the playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any, to seek on all active actions of this event ID. + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Seeks inside all playing objects that are referenced in Play Actions of the specified Event. + /// Seek position is specified as a percentage of the sound's total duration, and takes looping into account. + /// + /// Notes: + /// - This works with all objects of the actor-mixer hierarchy, and also with Music Segments and Music Switch Containers. + /// - There is a restriction with sounds that play within a continuous sequence. Seeking is ignored + /// if one of their ancestors is a continuous (random or sequence) container with crossfade or + /// trigger rate transitions. Seeking is also ignored with sample-accurate transitions, unless + /// the sound that is currently playing is the first sound of the sequence. + /// - Seeking is also ignored with voices that can go virtual with "From Beginning" behavior. + /// - If the option "Seek to nearest marker" is used, the seeking position snaps to the nearest marker. + /// With objects of the actor-mixer hierarchy, markers are embedded in wave files by an external wave editor. + /// Note that looping regions ("sampler loop") are not considered as markers. Also, the "add file name marker" of the + /// conversion settings dialog adds a marker at the beginning of the file, which is considered when seeking + /// to nearest marker. In the case of objects of the interactive music hierarchy, user (wave) markers are ignored: + /// seeking occurs to the nearest segment cue (authored in the segment editor), including the Entry Cue, but excluding the Exit Cue. + /// - This method posts a command in the sound engine queue, thus seeking will not occur before + /// the audio thread consumes it (after a call to RenderAudio()). + /// + /// Notes specific to Music Segments: + /// - With Music Segments, in_fPercent is relative to the Entry Cue, and the segment's duration is the + /// duration between its entry and exit cues. Consequently, you cannot seek within the pre-entry or + /// post-exit of a segment using this method. Use absolute values instead. + /// - Music segments cannot be looped. You may want to listen to the AK_EndOfEvent notification + /// in order to restart them if required. + /// - In order to restart at the correct location, with all their tracks synchronized, Music Segments + /// take the "look-ahead time" property of their streamed tracks into account for seeking. + /// Consequently, the resulting position after a call to SeekOnEvent() might be earlier than the + /// value that was passed to the method. Use AK::MusicEngine::GetPlayingSegmentInfo() to query + /// the exact position of a segment. Also, the segment will be silent during the time that period + /// (so that it restarts precisely at the position that you specified). AK::MusicEngine::GetPlayingSegmentInfo() + /// also informs you about the remaining look-ahead time. + /// + /// Notes specific to Music Switch Containers: + /// - Seeking triggers a music transition towards the current (or target) segment. + /// This transition is subject to the container's transition rule that matches the current and defined in the container, + /// so the moment when seeking occurs depends on the rule's "Exit At" property. On the other hand, the starting position + /// in the target segment depends on both the desired seeking position and the rule's "Sync To" property. + /// - If the specified time is greater than the destination segment's length, the modulo is taken. + /// + /// \sa + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::PostEvent() + /// - AK::SoundEngine::GetSourcePlayPosition() + /// - AK::MusicEngine::GetPlayingSegmentInfo() + AKRESULT SeekOnEvent( + const char* in_pszEventName, ///< Name of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID; use AK_INVALID_GAME_OBJECT to affect all game objects + AkReal32 in_fPercent, ///< Desired position where playback should restart, expressed in a percentage of the file's total duration, between 0 and 1.f (see note above about infinite looping sounds) + bool in_bSeekToNearestMarker = false, ///< If true, the final seeking position will be made equal to the nearest marker (see notes above). + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Specify the playing ID for the seek to be applied to. Will result in the seek happening only on active actions of the playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any, to seek on all active actions of this event ID. + ) override; + + /// Cancels all Event callbacks associated with a specific callback cookie.\n + /// \sa + /// - \c AK::SoundEngine::PostEvent() + void CancelEventCallbackCookie( + void* in_pCookie ///< Callback cookie to be cancelled + ) override; + + /// Cancels all Event callbacks associated with a specific game object.\n + /// \sa + /// - \c AK::SoundEngine::PostEvent() + void CancelEventCallbackGameObject( + AkGameObjectID in_gameObjectID ///< ID of the game object to be cancelled + ) override; + + /// Cancels all Event callbacks for a specific playing ID. + /// \sa + /// - \c AK::SoundEngine::PostEvent() + void CancelEventCallback( + AkPlayingID in_playingID ///< Playing ID of the event that must not use callbacks + ) override; + + /// Gets the current position of the source associated with this playing ID, obtained from PostEvent(). If more than one source is playing, + /// the first to play is returned. + /// Notes: + /// - You need to pass AK_EnableGetSourcePlayPosition to PostEvent() in order to use this function, otherwise + /// it returns AK_Fail, even if the playing ID is valid. + /// - The source's position is updated at every audio frame, and the time at which this occurs is stored. + /// When you call this function from your thread, you therefore query the position that was updated in the previous audio frame. + /// If in_bExtrapolate is true (default), the returned position is extrapolated using the elapsed time since last + /// sound engine update and the source's playback rate. + /// \return + /// - \c AK_Success if successful. + /// - \c AK_InvalidParameter if the provided pointer is not valid. + /// - \c AK_PlayingIDNotFound if the playing ID is invalid (not playing yet, or finished playing). + /// \sa + /// - \ref soundengine_query_pos + /// - \ref concept_events + AKRESULT GetSourcePlayPosition( + AkPlayingID in_PlayingID, ///< Playing ID returned by AK::SoundEngine::PostEvent() + AkTimeMs* out_puPosition, ///< Position of the source (in ms) associated with the specified playing ID + bool in_bExtrapolate = true ///< Position is extrapolated based on time elapsed since last sound engine update. + ) override; + + /// Gets the current position of the sources associated with this playing ID, obtained from PostEvent(). + /// Notes: + /// - You need to pass AK_EnableGetSourcePlayPosition to PostEvent() in order to use this function, otherwise + /// it returns AK_Fail, even if the playing ID is valid. + /// - The source's position is updated at every audio frame, and the time at which this occurs is stored. + /// When you call this function from your thread, you therefore query the position that was updated in the previous audio frame. + /// If in_bExtrapolate is true (default), the returned position is extrapolated using the elapsed time since last + /// sound engine update and the source's playback rate. + /// - If 0 is passed in for the number of entries (*in_pcPositions == 0) then only the number of positions will be returned and the + /// position array (out_puPositions) will not be updated. + /// - The io_pcPositions pointer must be non-NULL. + /// out_puPositions may be NULL if *io_pcPositions == 0, otherwise it must be non-NULL. + /// \return + /// - \c AK_Success if successful. + /// - \c AK_InvalidParameter if the provided pointer is not valid. + /// - \c AK_PlayingIDNotFound if the playing ID is invalid (not playing yet, or finished playing). + /// \sa + /// - \ref soundengine_query_pos + /// - \ref concept_events + AKRESULT GetSourcePlayPositions( + AkPlayingID in_PlayingID, ///< Playing ID returned by AK::SoundEngine::PostEvent() + AkSourcePosition* out_puPositions, ///< Audio Node IDs and positions of sources associated with the specified playing ID + AkUInt32* io_pcPositions, ///< Number of entries in out_puPositions. Needs to be set to the size of the array: it is adjusted to the actual number of returned entries + bool in_bExtrapolate = true ///< Position is extrapolated based on time elapsed since last sound engine update + ) override; + + /// Gets the stream buffering of the sources associated with this playing ID, obtained from PostEvent(). + /// Notes: + /// - You need to pass AK_EnableGetSourceStreamBuffering to PostEvent() in order to use this function, otherwise + /// it returns AK_Fail, even if the playing ID is valid. + /// - The sources stream buffering is updated at every audio frame. If there are multiple sources associated with this playing ID, + /// the value returned corresponds to the least buffered source. + /// - The returned buffering status out_bIsBuffering will be true If any of the sources associated with the playing ID are actively being buffered. + /// It will be false if all of them have reached the end of file, or have reached a state where they are buffered enough and streaming is temporarily idle. + /// - Purely in-memory sources are excluded from this database. If all sources are in-memory, GetSourceStreamBuffering() will return AK_Fail. + /// - The returned buffering amount and state is not completely accurate with some hardware-accelerated codecs. In such cases, the amount of stream buffering is generally underestimated. + /// On the other hand, it is not guaranteed that the source will be ready to produce data at the next audio frame even if out_bIsBuffering has turned to false. + /// \return + /// - \c AK_Success if successful. + /// - \c AK_PlayingIDNotFound if the source data associated with this playing ID is not found, for example if PostEvent() was not called with AK_EnableGetSourceStreamBuffering, or if the header was not parsed. + /// \sa + /// - \ref concept_events + AKRESULT GetSourceStreamBuffering( + AkPlayingID in_PlayingID, ///< Playing ID returned by AK::SoundEngine::PostEvent() + AkTimeMs& out_buffering, ///< Returned amount of buffering (in ms) of the source (or one of the sources) associated with that playing ID + bool& out_bIsBuffering ///< Returned buffering status of the source(s) associated with that playing ID + ) override; + + /// Stops the current content playing associated to the specified game object ID. + /// If no game object is specified, all sounds will be stopped. + void StopAll( + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT ///< (Optional)Specify a game object to stop only playback associated to the provided game object ID. + ) override; + + /// Stop the current content playing associated to the specified playing ID. + /// \aknote + /// This function is deprecated. Please use ExecuteActionOnPlayingID() in its place. + /// \endaknote + /// \sa + /// - AK::SoundEngine::ExecuteActionOnPlayingID() + void StopPlayingID( + AkPlayingID in_playingID, ///< Playing ID to be stopped. + AkTimeMs in_uTransitionDuration = 0, ///< Fade duration + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear ///< Curve type to be used for the transition + ) override; + + /// Executes an Action on the content associated to the specified playing ID. + /// \sa + /// - AK::SoundEngine::AkActionOnEventType + void ExecuteActionOnPlayingID( + AK::SoundEngine::AkActionOnEventType in_ActionType, ///< Action to execute on the specified playing ID. + AkPlayingID in_playingID, ///< Playing ID on which to execute the action. + AkTimeMs in_uTransitionDuration = 0, ///< Fade duration + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear ///< Curve type to be used for the transition + ) override; + + /// Sets the random seed value. Can be used to synchronize randomness + /// across instances of the Sound Engine. + /// \remark This seeds the number generator used for all container randomizations + /// and the plug-in RNG; since it acts globally, this should be called right + /// before any PostEvent call where randomness synchronization is required, + /// and cannot guarantee similar results for continuous containers. + /// \sa + /// - AK::IAkPluginServiceRNG + void SetRandomSeed( + AkUInt32 in_uSeed ///< Random seed. + ) override; + + /// Mutes/Unmutes the busses tagged as background music. + /// This is automatically called for platforms that have user-music support. + /// This function is provided to give the same behavior on platforms that don't have user-music support. + void MuteBackgroundMusic( + bool in_bMute ///< Sets true to mute, false to unmute. + ) override; + //@} + + /// Gets the state of the Background Music busses. This state is either set directly + /// with \c AK::SoundEngine::MuteBackgroundMusic or by the OS, if it has User Music services. + /// \return true if the background music busses are muted, false if not. + bool GetBackgroundMusicMute() override; + //@} + + + /// Sends custom game data to a plug-in that resides on a bus (insert Effect or mixer plug-in). + /// Data will be copied and stored into a separate list. + /// Previous entry is deleted when a new one is sent. + /// Sets the data pointer to NULL to clear item from the list. + /// \aknote The plug-in type and ID is passed and matched with plugins set on the desired bus. + /// This means that you cannot send different data to various instances of the plug-in on a same bus.\endaknote + /// \return AK_Success if data was sent successfully. + AKRESULT SendPluginCustomGameData( + AkUniqueID in_busID, ///< Bus ID + AkGameObjectID in_busObjectID, ///< Bus Object ID. Pass AK_INVALID_GAME_OBJECT to send custom data with global scope. Game object scope supersedes global scope, as with RTPCs. + AkPluginType in_eType, ///< Plug-in type (for example, source or effect) + AkUInt32 in_uCompanyID, ///< Company identifier (as declared in the plug-in description XML file) + AkUInt32 in_uPluginID, ///< Plug-in identifier (as declared in the plug-in description XML file) + const void* in_pData, ///< The data blob + AkUInt32 in_uSizeInBytes ///< Size of data + ) override; + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Game Objects + //@{ + + /// Registers a game object. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if the specified AkGameObjectID is invalid (0 and -1 are invalid) + /// \remark Registering a game object twice does nothing. Unregistering it once unregisters it no + /// matter how many times it has been registered. + /// \sa + /// - AK::SoundEngine::UnregisterGameObj() + /// - AK::SoundEngine::UnregisterAllGameObj() + /// - \ref concept_gameobjects + AKRESULT RegisterGameObj( + AkGameObjectID in_gameObjectID ///< ID of the game object to be registered + ) override; + + /// Registers a game object. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if the specified AkGameObjectID is invalid (0 and -1 are invalid) + /// \remark Registering a game object twice does nothing. Unregistering it once unregisters it no + /// matter how many times it has been registered. + /// \sa + /// - AK::SoundEngine::UnregisterGameObj() + /// - AK::SoundEngine::UnregisterAllGameObj() + /// - \ref concept_gameobjects + AKRESULT RegisterGameObj( + AkGameObjectID in_gameObjectID, ///< ID of the game object to be registered + const char* in_pszObjName ///< Name of the game object (for monitoring purpose) + ) override; + + /// Unregisters a game object. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if the specified AkGameObjectID is invalid (0 is an invalid ID) + /// \remark Registering a game object twice does nothing. Unregistering it once unregisters it no + /// matter how many times it has been registered. Unregistering a game object while it is + /// in use is allowed, but the control over the parameters of this game object is lost. + /// For example, say a sound associated with this game object is a 3D moving sound. This sound will + /// stop moving when the game object is unregistered, and there will be no way to regain control over the game object. + /// \sa + /// - AK::SoundEngine::RegisterGameObj() + /// - AK::SoundEngine::UnregisterAllGameObj() + /// - \ref concept_gameobjects + AKRESULT UnregisterGameObj( + AkGameObjectID in_gameObjectID ///< ID of the game object to be unregistered. Use + /// AK_INVALID_GAME_OBJECT to unregister all game objects. + ) override; + + /// Unregister all game objects, or all game objects with a particular matching set of property flags. + /// This function to can be used to unregister all game objects. + /// \return + /// - \c AK_Success if successful + /// \remark Registering a game object twice does nothing. Unregistering it once unregisters it no + /// matter how many times it has been registered. Unregistering a game object while it is + /// in use is allowed, but the control over the parameters of this game object is lost. + /// For example, if a sound associated with this game object is a 3D moving sound, it will + /// stop moving once the game object is unregistered, and there will be no way to recover + /// the control over this game object. + /// \sa + /// - AK::SoundEngine::RegisterGameObj() + /// - AK::SoundEngine::UnregisterGameObj() + /// - \ref concept_gameobjects + AKRESULT UnregisterAllGameObj( + ) override; + + /// Sets the position of a game object. + /// \warning The object's orientation vector (in_Position.Orientation) must be normalized. + /// \return + /// - \c AK_Success when successful + /// - \c AK_InvalidParameter if parameters are not valid, for example: + /// + in_Position makes an invalid transform + /// + in_eFlags is not one of the valid enum values + /// + the game object ID is in the reserved ID range. + /// \sa + /// - \ref soundengine_3dpositions + AKRESULT SetPosition( + AkGameObjectID in_GameObjectID, ///< Game Object identifier + const AkSoundPosition& in_Position,///< Position to set; in_Position.Orientation must be normalized. + AkSetPositionFlags in_eFlags = AkSetPositionFlags_Default ///< Optional flags to independently set the position of the emitter or listener component. + ) override; + + /// Sets multiple positions to a single game object. + /// Setting multiple positions on a single game object is a way to simulate multiple emission sources while using the resources of only one voice. + /// This can be used to simulate wall openings, area sounds, or multiple objects emitting the same sound in the same area. + /// \aknote Calling AK::SoundEngine::SetMultiplePositions() with only one position is the same as calling AK::SoundEngine::SetPosition() \endaknote + /// \return + /// - \c AK_Success when successful + /// - \c AK_CommandTooLarge if the number of positions is too large for the command queue. Reduce the number of positions. + /// - \c AK_InvalidParameter if parameters are not valid, for example: + /// + in_Position makes an invalid transform + /// + in_eFlags is not one of the valid enum values + /// + the game object ID is in the reserved ID range. + /// \sa + /// - \ref soundengine_3dpositions + /// - \ref soundengine_3dpositions_multiplepos + /// - \ref AK::SoundEngine::MultiPositionType + AKRESULT SetMultiplePositions( + AkGameObjectID in_GameObjectID, ///< Game Object identifier. + const AkSoundPosition* in_pPositions, ///< Array of positions to apply. + AkUInt16 in_NumPositions, ///< Number of positions specified in the provided array. + AK::SoundEngine::MultiPositionType in_eMultiPositionType = AK::SoundEngine::MultiPositionType_MultiDirections, ///< \ref AK::SoundEngine::MultiPositionType + AkSetPositionFlags in_eFlags = AkSetPositionFlags_Default ///< Optional flags to independently set the position of the emitter or listener component. + ) override; + + /// Sets multiple positions to a single game object, with flexible assignment of input channels. + /// Setting multiple positions on a single game object is a way to simulate multiple emission sources while using the resources of only one voice. + /// This can be used to simulate wall openings, area sounds, or multiple objects emitting the same sound in the same area. + /// \aknote Calling AK::SoundEngine::SetMultiplePositions() with only one position is the same as calling AK::SoundEngine::SetPosition() \endaknote + /// \return + /// - \c AK_Success when successful + /// - \c AK_CommandTooLarge if the number of positions is too large for the command queue. Reduce the number of positions. + /// - \c AK_InvalidParameter if parameters are not valid. + /// \sa + /// - \ref soundengine_3dpositions + /// - \ref soundengine_3dpositions_multiplepos + /// - \ref AK::SoundEngine::MultiPositionType + AKRESULT SetMultiplePositions( + AkGameObjectID in_GameObjectID, ///< Game Object identifier. + const AkChannelEmitter* in_pPositions, ///< Array of positions to apply, each using its own channel mask. + AkUInt16 in_NumPositions, ///< Number of positions specified in the provided array. + AK::SoundEngine::MultiPositionType in_eMultiPositionType = AK::SoundEngine::MultiPositionType_MultiDirections, ///< \ref AK::SoundEngine::MultiPositionType + AkSetPositionFlags in_eFlags = AkSetPositionFlags_Default ///< Optional flags to independently set the position of the emitter or listener component. + ) override; + + /// Sets the scaling factor of a Game Object. + /// Modify the attenuation computations on this Game Object to simulate sounds with a larger or smaller area of effect. + /// \return + /// - \c AK_Success when successful + /// - \c AK_InvalidParameter if the scaling factor specified was 0 or negative. + /// - \c AK_InvalidFloatValue if the value specified was NaN or Inf + AKRESULT SetScalingFactor( + AkGameObjectID in_GameObjectID, ///< Game object identifier + AkReal32 in_fAttenuationScalingFactor ///< Scaling Factor, 1 means 100%, 0.5 means 50%, 2 means 200%, and so on. + ) override; + + /// Use the position of a separate game object for distance calculations for a specified listener. + /// When AK::SoundEngine::SetDistanceProbe() is called, Wwise calculates distance attenuation and filtering + /// based on the distance between the distance probe Game Object (\c in_distanceProbeGameObjectID) and the emitter Game Object's position. + /// In third-person perspective applications, the distance probe Game Object may be set to the player character's position, + /// and the listener Game Object's position to that of the camera. In this scenario, attenuation is based on + /// the distance between the character and the sound, whereas panning, spatialization, and spread and focus calculations are base on the camera. + /// Both Game Objects, \c in_listenerGameObjectID and \c in_distanceProbeGameObjectID must have been previously registered using AK::SoundEngine::RegisterGameObj. + /// This funciton is optional. if AK::SoundEngine::SetDistanceProbe() is never called, distance calculations are based on the listener Game Object position. + /// To clear the distance probe, and revert to using the listener position for distance calculations, pass \c AK_INVALID_GAME_OBJECT to \c in_distanceProbeGameObjectID. + /// \aknote If the distance probe Game Object is assigned multiple positions, then the first position is used for distance calculations by the listener. \endaknote + /// \return + /// - \c AK_Success when successful + /// \sa + /// - AK::SoundEngine::SetPosition() + AKRESULT SetDistanceProbe( + AkGameObjectID in_listenerGameObjectID, ///< Game object identifier for the listener. Must have been previously registered via RegisterGameObj. + AkGameObjectID in_distanceProbeGameObjectID ///< Game object identifier for the distance probe, or \c AK_INVALID_GAME_OBJECT to reset distance probe. If valid, must have been previously registered via RegisterGameObj. + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Bank Management + //@{ + + /// Unload all currently loaded banks. + /// It also internally calls ClearPreparedEvents() since at least one bank must have been loaded to allow preparing events. + /// \return + /// - \c AK_Success if successful + /// - \c AK_NotInitialized if the sound engine was not correctly initialized or if there is not enough memory to handle the command + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::LoadBank() + /// - \ref soundengine_banks + AKRESULT ClearBanks() override; + + /// Sets the I/O settings of the bank load and prepare event processes. + /// The sound engine uses default values unless explicitly set by calling this method. + /// \warning This function must be called before loading banks. + /// \return + /// - \c AK_Success if successful + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console + /// \sa + /// - \ref soundengine_banks + /// - \ref streamingdevicemanager + AKRESULT SetBankLoadIOSettings( + AkReal32 in_fThroughput, ///< Average throughput of bank data streaming (bytes/ms) (the default value is AK_DEFAULT_BANK_THROUGHPUT) + AkPriority in_priority ///< Priority of bank streaming (the default value is AK_DEFAULT_PRIORITY) + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Load a bank synchronously (by Unicode string).\n + /// The bank name and type are passed to the Stream Manager. + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// A bank load request will be posted, and consumed by the Bank Manager thread. + /// The function returns when the request has been completely processed. + /// \return + /// The bank ID, which is obtained by hashing the bank name (see GetIDFromString()). + /// You may use this ID with UnloadBank(). + /// - \c AK_Success: Load or unload successful. + /// - \c AK_BankAlreadyLoaded: This bank is already loaded, nothing done. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_InvalidParameter: Invalid parameter, invalid memory alignment. + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console + /// - \c AK_InvalidBankType if the bank type parameter is out of range. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure, check the debug console) + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// - The sound engine internally calls GetIDFromString(in_pszString) to return the correct bank ID. + /// Therefore, in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. The path should be resolved in + /// your implementation of the Stream Manager, or in the Low-Level I/O module if you use the default Stream Manager's implementation. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AK::SoundEngine::GetIDFromString() + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref streamingdevicemanager + /// - \ref streamingmanager_lowlevel + /// - \ref sdk_bank_training + AKRESULT LoadBank( + const wchar_t* in_pszString, ///< Name of the bank to load + AkBankID& out_bankID, ///< Returned bank ID + AkBankType in_bankType = AkBankType_User ///< Type of the bank to load + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Loads a bank synchronously.\n + /// The bank name and type are passed to the Stream Manager. + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// A bank load request will be posted, and consumed by the Bank Manager thread. + /// The function returns when the request has been completely processed. + /// \return + /// The bank ID, which is obtained by hashing the bank name (see GetIDFromString()). + /// You may use this ID with UnloadBank(). + /// - \c AK_Success: Load or unload successful. + /// - \c AK_BankAlreadyLoaded: This bank is already loaded, nothing done. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console + /// - \c AK_InvalidBankType if the bank type parameter is out of range. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// - The sound engine internally calls GetIDFromString(in_pszString) to return the correct bank ID. + /// Therefore, in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. The path should be resolved in + /// your implementation of the Stream Manager, or in the Low-Level I/O module if you use the default Stream Manager's implementation. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AK::SoundEngine::GetIDFromString + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref streamingdevicemanager + /// - \ref streamingmanager_lowlevel + /// - \ref sdk_bank_training + AKRESULT LoadBank( + const char* in_pszString, ///< Name of the bank to load + AkBankID& out_bankID, ///< Returned bank ID + AkBankType in_bankType = AkBankType_User ///< Type of the bank to load + ) override; + + /// Loads a bank synchronously (by ID).\n + /// \aknote Requires that the "Use SoundBank names" option be unchecked in the Wwise Project Settings. \endaknote + /// The bank ID is passed to the Stream Manager. + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// A bank load request will be posted, and consumed by the Bank Manager thread. + /// The function returns when the request has been completely processed. + /// \return + /// - \c AK_Success: Load or unload successful. + /// - \c AK_BankAlreadyLoaded: This bank is already loaded, nothing done. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. The bank is either shorter than expected or its data corrupted. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console or Wwise Profiler + /// - \c AK_InvalidBankType if the bank type parameter is out of range. + /// - \c AK_FileNotFound if the bank file was not found on disk. + /// - \c AK_FilePermissionError if the file permissions are wrong for the file + /// - \c AK_Fail: Load or unload failed for any other reason. , check the debug console or Wwise Profiler + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + AKRESULT LoadBank( + AkBankID in_bankID, ///< Bank ID of the bank to load + AkBankType in_bankType = AkBankType_User ///< Type of the bank to load + ) override; + + /// Loads a bank synchronously (from in-memory data, in-place, user bank only).\n + /// + /// IMPORTANT: Banks loaded from memory with in-place data MUST be unloaded using the UnloadBank function + /// providing the same memory pointer. Make sure you are using the correct UnloadBank(...) overload + /// + /// Use LoadBankMemoryView when you want to manage I/O on your side. Load the bank file + /// in a buffer and pass its address to the sound engine. + /// In-memory loading is in-place: *** the memory must be valid until the bank is unloaded. *** + /// A bank load request will be posted, and consumed by the Bank Manager thread. + /// The function returns when the request has been completely processed. + /// \return + /// The bank ID, which is stored in the first few bytes of the bank file. You may use this + /// ID with UnloadBank(). + /// - \c AK_Success: Load or unload successful. + /// - \c AK_BankAlreadyLoaded: This bank is already loaded, nothing done. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console + /// - \c AK_InvalidBankType if the bank is not a user-defined bank. + /// - \c AK_DataAlignmentError if the data pointer is not aligned properly + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// - The memory must be aligned on platform-specific AK_BANK_PLATFORM_DATA_ALIGNMENT bytes (see AkTypes.h). + /// - (XboxOne only): If the bank may contain XMA in memory data, the memory must be allocated using the Device memory allocator. + /// - Avoid using this function for banks containing a lot of events or structure data: this data will be unpacked into the sound engine heap, + /// making the supplied bank memory redundant. For event/structure-only banks, prefer LoadBankMemoryCopy(). + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + AKRESULT LoadBankMemoryView( + const void * in_pInMemoryBankPtr, ///< Pointer to the in-memory bank to load (pointer is stored in sound engine, memory must remain valid) + AkUInt32 in_uInMemoryBankSize, ///< Size of the in-memory bank to load + AkBankID & out_bankID ///< Returned bank ID + ) override; + + /// Loads a bank synchronously (from in-memory data, in-place, any bank type).\n + /// + /// IMPORTANT: Banks loaded from memory with in-place data MUST be unloaded using the UnloadBank function + /// providing the same memory pointer. Make sure you are using the correct UnloadBank(...) overload + /// + /// Use LoadBankMemoryView when you want to manage I/O on your side. Load the bank file + /// in a buffer and pass its address to the sound engine. + /// In-memory loading is in-place: *** the memory must be valid until the bank is unloaded. *** + /// A bank load request will be posted, and consumed by the Bank Manager thread. + /// The function returns when the request has been completely processed. + /// \return + /// The bank ID, which is stored in the first few bytes of the bank file. You may use this + /// ID with UnloadBank(). + /// - \c AK_Success: Load or unload successful. + /// - \c AK_BankAlreadyLoaded: This bank is already loaded, nothing done. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console + /// - \c AK_InvalidBankType if the bank type parameter is out of range. + /// - \c AK_DataAlignmentError if the data pointer is not aligned properly + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// - The memory must be aligned on platform-specific AK_BANK_PLATFORM_DATA_ALIGNMENT bytes (see AkTypes.h). + /// - (XboxOne only): If the bank may contain XMA in memory data, the memory must be allocated using the Device memory allocator. + /// - Avoid using this function for banks containing a lot of events or structure data: this data will be unpacked into the sound engine heap, + /// making the supplied bank memory redundant. For event/structure-only banks, prefer LoadBankMemoryCopy(). + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + AKRESULT LoadBankMemoryView( + const void* in_pInMemoryBankPtr, ///< Pointer to the in-memory bank to load (pointer is stored in sound engine, memory must remain valid) + AkUInt32 in_uInMemoryBankSize, ///< Size of the in-memory bank to load + AkBankID& out_bankID, ///< Returned bank ID + AkBankType& out_bankType ///< Returned bank type + ) override; + + /// Loads a bank synchronously (from in-memory data, out-of-place).\n + /// + /// NOTE: Banks loaded from in-memory with out-of-place data must be unloaded using the standard UnloadBank function + /// (with no memory pointer). Make sure you are using the correct UnloadBank(...) overload + /// + /// Use LoadBankMemoryCopy when you want to manage I/O on your side. Load the bank file + /// in a buffer and pass its address to the sound engine, the media section of the bank will be copied into newly + /// allocated memory. + /// In-memory loading is out-of-place: the buffer can be release as soon as the function returns. The advantage of using this + /// over the in-place version is that there is no duplication of bank structures. + /// A bank load request will be posted, and consumed by the Bank Manager thread. + /// The function returns when the request has been completely processed. + /// \return + /// The bank ID, which is stored in the first few bytes of the bank file. You may use this + /// ID with UnloadBank(). + /// - \c AK_Success: Load or unload successful. + /// - \c AK_BankAlreadyLoaded: This bank is already loaded, nothing done. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console + /// - \c AK_InvalidBankType if the bank is not a user-defined bank. + /// - \c AK_DataAlignmentError if the data pointer is not aligned properly + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + AKRESULT LoadBankMemoryCopy( + const void * in_pInMemoryBankPtr, ///< Pointer to the in-memory bank to load (pointer is not stored in sound engine, memory can be released after return) + AkUInt32 in_uInMemoryBankSize, ///< Size of the in-memory bank to load + AkBankID & out_bankID ///< Returned bank ID + ) override; + + /// Loads a bank synchronously (from in-memory data, out-of-place, any bank type).\n + /// + /// NOTE: Banks loaded from in-memory with out-of-place data must be unloaded using the standard UnloadBank function + /// (with no memory pointer). Make sure you are using the correct UnloadBank(...) overload + /// + /// Use LoadBankMemoryCopy when you want to manage I/O on your side. Load the bank file + /// in a buffer and pass its address to the sound engine, the media section of the bank will be copied into newly + /// allocated memory. + /// In-memory loading is out-of-place: the buffer can be release as soon as the function returns. The advantage of using this + /// over the in-place version is that there is no duplication of bank structures. + /// A bank load request will be posted, and consumed by the Bank Manager thread. + /// The function returns when the request has been completely processed. + /// \return + /// The bank ID, which is stored in the first few bytes of the bank file. You may use this + /// ID with UnloadBank(). + /// - \c AK_Success: Load or unload successful. + /// - \c AK_BankAlreadyLoaded: This bank is already loaded, nothing done. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console + /// - \c AK_DataAlignmentError if the data pointer is not aligned properly + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + AKRESULT LoadBankMemoryCopy( + const void* in_pInMemoryBankPtr, ///< Pointer to the in-memory bank to load (pointer is not stored in sound engine, memory can be released after return) + AkUInt32 in_uInMemoryBankSize, ///< Size of the in-memory bank to load + AkBankID& out_bankID, ///< Returned bank ID + AkBankType& out_bankType ///< Returned bank type + ) override; + + /// Synchronously decodes Vorbis-encoded and Opus-encoded (Software version) media in a SoundBank. The file should already be read in memory before the decode operation. The out_pDecodedBankPtr can then be used with variants of LoadBank that load from in-memory data. + /// \n + /// CPU usage, RAM size, storage size and Internet bandwidth must be accounted for when developing a game, especially when it is aimed at mobile platforms. The DecodeBank function makes it possible to decode media at load time instead of decoding them every time they are played. + AKRESULT DecodeBank( + const void* in_pInMemoryBankPtr, ///< Pointer to the in-memory bank to decode (pointer is not stored in sound engine, memory can be released after return) + AkUInt32 in_uInMemoryBankSize, ///< Size of the in-memory bank to decode + AkMemPoolId in_uPoolForDecodedBank, ///< Memory pool to allocate decoded bank into. Specify AK_INVALID_POOL_ID and out_pDecodedBankPtr=NULL to obtain decoded bank size without performing the decode operation. Pass AK_INVALID_POOL_ID and out_pDecodedBankPtr!=NULL to decode bank into specified pointer. + void*& out_pDecodedBankPtr, ///< Decoded bank memory location. + AkUInt32& out_uDecodedBankSize ///< Decoded bank memory size. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Loads a bank asynchronously (by Unicode string).\n + /// The bank name is passed to the Stream Manager. + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// A bank load request will be posted to the Bank Manager consumer thread. + /// The function returns immediately. + /// \return + /// AK_Success if the scheduling was successful, AK_Fail otherwise. + /// Use a callback to be notified when completed, and get the status of the request. + /// The bank ID, which is obtained by hashing the bank name (see GetIDFromString()). + /// You may use this ID with UnloadBank(). + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// - The sound engine internally calls GetIDFromString(in_pszString) to return the correct bank ID. + /// Therefore, in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. The path should be resolved in + /// your implementation of the Stream Manager (AK::IAkStreamMgr::CreateStd()), or in the Low-Level I/O module + /// (AK::StreamMgr::IAkFileLocationResolver::Open()) if you use the default Stream Manager's implementation. + /// - The cookie (in_pCookie) is passed to the Low-Level I/O module for your convenience, in AK::StreamMgr::IAkFileLocationResolver::Open() + // as AkFileSystemFlags::pCustomParam. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref streamingdevicemanager + /// - \ref streamingmanager_lowlevel + /// - \ref sdk_bank_training + AKRESULT LoadBank( + const wchar_t* in_pszString, ///< Name/path of the bank to load + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function, and also to AK::StreamMgr::IAkFileLocationResolver::Open() as AkFileSystemFlags::pCustomParam) + AkBankID& out_bankID, ///< Returned bank ID + AkBankType in_bankType = AkBankType_User ///< Type of the bank to load + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Loads a bank asynchronously.\n + /// The bank name is passed to the Stream Manager. + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// A bank load request will be posted to the Bank Manager consumer thread. + /// The function returns immediately. + /// \return + /// - \c AK_Success if the scheduling was successful, + /// - \c AK_InvalidBankType if in_bankType was invalid + /// Use a callback to be notified when completed, and get the status of the request. + /// The bank ID, which is obtained by hashing the bank name (see GetIDFromString()). + /// You may use this ID with UnloadBank(). + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the Event will fail. + /// - The sound engine internally calls GetIDFromString(in_pszString) to return the correct bank ID. + /// Therefore, \c in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. The path should be resolved in + /// your implementation of the Stream Manager (AK::IAkStreamMgr::CreateStd()), or in the Low-Level I/O module + /// (AK::StreamMgr::IAkFileLocationResolver::Open()) if you use the default Stream Manager's implementation. + /// - The cookie (in_pCookie) is passed to the Low-Level I/O module for your convenience, in AK::StreamMgr::IAkFileLocationResolver::Open() + // as AkFileSystemFlags::pCustomParam. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref streamingdevicemanager + /// - \ref streamingmanager_lowlevel + /// - \ref sdk_bank_training + AKRESULT LoadBank( + const char* in_pszString, ///< Name/path of the bank to load + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function, and also to AK::StreamMgr::IAkFileLocationResolver::Open() as AkFileSystemFlags::pCustomParam) + AkBankID& out_bankID, ///< Returned bank ID + AkBankType in_bankType = AkBankType_User ///< Type of the bank to load + ) override; + + /// Loads a bank asynchronously (by ID).\n + /// \aknote Requires that the "Use SoundBank names" option be unchecked in the Wwise Project Settings. \endaknote + /// The bank ID is passed to the Stream Manager. + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// A bank load request will be posted to the Bank Manager consumer thread. + /// The function returns immediately. + /// \return + /// - \c AK_Success if the scheduling was successful, + /// - \c AK_InvalidBankType if in_bankType was invalid + /// Use a callback to be notified when completed, and get the status of the request. + /// The bank ID, which is obtained by hashing the bank name (see GetIDFromString()). + /// You may use this ID with \c UnloadBank(). + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// - The file path should be resolved in your implementation of the Stream Manager, or in the Low-Level I/O module if + /// you use the default Stream Manager's implementation. The ID overload of AK::IAkStreamMgr::CreateStd() and AK::StreamMgr::IAkFileLocationResolver::Open() are called. + /// - The cookie (in_pCookie) is passed to the Low-Level I/O module for your convenience, in AK::StreamMgr::IAkFileLocationResolver::Open() + // as AkFileSystemFlags::pCustomParam. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + AKRESULT LoadBank( + AkBankID in_bankID, ///< Bank ID of the bank to load + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function, and also to AK::StreamMgr::IAkFileLocationResolver::Open() as AkFileSystemFlags::pCustomParam) + AkBankType in_bankType = AkBankType_User ///< Type of the bank to load + ) override; + + /// Loads a bank asynchronously (from in-memory data, in-place).\n + /// + /// IMPORTANT: Banks loaded from memory with in-place data MUST be unloaded using the UnloadBank function + /// providing the same memory pointer. Make sure you are using the correct UnloadBank(...) overload + /// + /// Use LoadBankMemoryView when you want to manage I/O on your side. Load the bank file + /// in a buffer and pass its address to the sound engine. + /// In-memory loading is in-place: *** the memory must be valid until the bank is unloaded. *** + /// A bank load request will be posted to the Bank Manager consumer thread. + /// The function returns immediately. + /// \return + /// - \c AK_Success if the scheduling was successful, + /// - \c AK_InvalidBankType if in_bankType was invalid + /// - \c AK_DataAlignmentError if the data pointer is not aligned properly + /// Use a callback to be notified when completed, and get the status of the request. + /// The bank ID, which is obtained by hashing the bank name (see GetIDFromString()). + /// You may use this ID with UnloadBank(). + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// - The memory must be aligned on platform-specific AK_BANK_PLATFORM_DATA_ALIGNMENT bytes (see AkTypes.h). + /// - (XboxOne only): If the bank may contain XMA in memory data, the memory must be allocated using the Device memory allocator. + /// - Avoid using this function for banks containing a lot of events or structure data: this data will be unpacked into the sound engine heap, + /// making the supplied bank memory redundant. For event/structure-only banks, prefer LoadBankMemoryCopy(). + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + AKRESULT LoadBankMemoryView( + const void* in_pInMemoryBankPtr, ///< Pointer to the in-memory bank to load (pointer is stored in sound engine, memory must remain valid) + AkUInt32 in_uInMemoryBankSize, ///< Size of the in-memory bank to load + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie + AkBankID& out_bankID, ///< Returned bank ID + AkBankType& out_bankType ///< Returned bank type + ) override; + + /// Loads a bank asynchronously (from in-memory data, out-of-place).\n + /// + /// NOTE: Banks loaded from in-memory with out-of-place data must be unloaded using the standard UnloadBank function + /// (with no memory pointer). Make sure you are using the correct UnloadBank(...) overload + /// + /// Use LoadBankMemoryCopy when you want to manage I/O on your side. Load the bank file + /// in a buffer and pass its address to the sound engine, the media section of the bank will be copied into newly allocated + /// memory. + /// In-memory loading is out-of-place: the buffer can be released after the callback function is called. The advantage of using this + /// over the in-place version is that there is no duplication of bank structures. + /// A bank load request will be posted to the Bank Manager consumer thread. + /// The function returns immediately. + /// \return + /// - \c AK_Success if the scheduling was successful, + /// - \c AK_InvalidBankType if in_bankType was invalid + /// - \c AK_DataAlignmentError if the data pointer is not aligned properly + /// Use a callback to be notified when completed, and get the status of the request. + /// The bank ID, which is obtained by hashing the bank name (see GetIDFromString()). + /// You may use this ID with UnloadBank(). + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + AKRESULT LoadBankMemoryCopy( + const void* in_pInMemoryBankPtr, ///< Pointer to the in-memory bank to load (pointer is not stored in sound engine, memory can be released after callback) + AkUInt32 in_uInMemoryBankSize, ///< Size of the in-memory bank to load + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie + AkBankID& out_bankID, ///< Returned bank ID + AkBankType& out_bankType ///< Returned bank type + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Unloads a bank synchronously (by Unicode string).\n + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// \return AK_Success if successful, AK_Fail otherwise. AK_Success is returned when the bank was not loaded. + /// \remarks + /// - The sound engine internally calls GetIDFromString(in_pszString) to retrieve the bank ID, + /// then it calls the synchronous version of UnloadBank() by ID. + /// Therefore, in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. + /// - In order to force the memory deallocation of the bank, sounds that use media from this bank will be stopped. + /// This means that streamed sounds or generated sounds will not be stopped. + /// \sa + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + AKRESULT UnloadBank( + const wchar_t* in_pszString, ///< Name of the bank to unload + const void* in_pInMemoryBankPtr, ///< Memory pointer from where the bank was initially loaded from. (REQUIRED to determine which bank associated to a memory pointer must be unloaded). Pass NULL if NULL was passed when loading the bank or if LoadBankMemoryCopy was used to load the bank. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to unload + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Unloads a bank synchronously.\n + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// \return AK_Success if successful, AK_Fail otherwise. AK_Success is returned when the bank was not loaded. + /// \remarks + /// - The sound engine internally calls GetIDFromString(in_pszString) to retrieve the bank ID, + /// then it calls the synchronous version of UnloadBank() by ID. + /// Therefore, in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. + /// - In order to force the memory deallocation of the bank, sounds that use media from this bank will be stopped. + /// This means that streamed sounds or generated sounds will not be stopped. + /// \sa + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + AKRESULT UnloadBank( + const char* in_pszString, ///< Name of the bank to unload + const void* in_pInMemoryBankPtr, ///< Memory pointer from where the bank was initially loaded from. (REQUIRED to determine which bank associated to a memory pointer must be unloaded). Pass NULL if NULL was passed when loading the bank or if LoadBankMemoryCopy was used to load the bank. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to unload + ) override; + + /// Unloads a bank synchronously (by ID and memory pointer).\n + /// \return AK_Success if successful, AK_Fail otherwise. AK_Success is returned when the bank was not loaded. + /// \remarks + /// - In order to force the memory deallocation of the bank, sounds that use media from this bank will be stopped. + /// This means that streamed sounds or generated sounds will not be stopped. + /// \sa + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + AKRESULT UnloadBank( + AkBankID in_bankID, ///< ID of the bank to unload + const void* in_pInMemoryBankPtr, ///< Memory pointer from where the bank was initially loaded from. (REQUIRED to determine which bank associated to a memory pointer must be unloaded). Pass NULL if NULL was passed when loading the bank or if LoadBankMemoryCopy was used to load the bank. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to unload + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Unloads a bank asynchronously (by Unicode string).\n + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// \return AK_Success if scheduling successful (use a callback to be notified when completed) + /// \remarks + /// The sound engine internally calls GetIDFromString(in_pszString) to retrieve the bank ID, + /// then it calls the synchronous version of UnloadBank() by ID. + /// Therefore, in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. + /// - In order to force the memory deallocation of the bank, sounds that use media from this bank will be stopped. + /// This means that streamed sounds or generated sounds will not be stopped. + /// \sa + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + AKRESULT UnloadBank( + const wchar_t* in_pszString, ///< Name of the bank to unload + const void* in_pInMemoryBankPtr, ///< Memory pointer from where the bank was initially loaded from. (REQUIRED to determine which bank associated to a memory pointer must be unloaded). Pass NULL if NULL was passed when loading the bank or if LoadBankMemoryCopy was used to load the bank. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function) + AkBankType in_bankType = AkBankType_User ///< Type of the bank to unload + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Unloads a bank asynchronously.\n + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// \return AK_Success if scheduling successful (use a callback to be notified when completed) + /// \remarks + /// The sound engine internally calls GetIDFromString(in_pszString) to retrieve the bank ID, + /// then it calls the synchronous version of UnloadBank() by ID. + /// Therefore, in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. + /// - In order to force the memory deallocation of the bank, sounds that use media from this bank will be stopped. + /// This means that streamed sounds or generated sounds will not be stopped. + /// \sa + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + AKRESULT UnloadBank( + const char* in_pszString, ///< Name of the bank to unload + const void* in_pInMemoryBankPtr, ///< Memory pointer from where the bank was initially loaded from. (REQUIRED to determine which bank associated to a memory pointer must be unloaded). Pass NULL if NULL was passed when loading the bank or if LoadBankMemoryCopy was used to load the bank. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function) + AkBankType in_bankType = AkBankType_User ///< Type of the bank to unload + ) override; + + /// Unloads a bank asynchronously (by ID and memory pointer).\n + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// \return AK_Success if scheduling successful (use a callback to be notified when completed) + /// \remarks + /// - In order to force the memory deallocation of the bank, sounds that use media from this bank will be stopped. + /// This means that streamed sounds or generated sounds will not be stopped. + /// \sa + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + AKRESULT UnloadBank( + AkBankID in_bankID, ///< ID of the bank to unload + const void* in_pInMemoryBankPtr, ///< Memory pointer from where the bank was initially loaded from. (REQUIRED to determine which bank associated to a memory pointer must be unloaded). Pass NULL if NULL was passed when loading the bank or if LoadBankMemoryCopy was used to load the bank. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function) + AkBankType in_bankType = AkBankType_User ///< Type of the bank to unload + ) override; + + /// Cancels all Event callbacks associated with a specific callback cookie specified while loading Banks of preparing Events.\n + /// \sa + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + void CancelBankCallbackCookie( + void* in_pCookie ///< Callback cookie to be canceled + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// This function will load the Events, structural content, and optionally, the media content from the SoundBank. If the flag AkBankContent_All is specified, PrepareBank() will load the media content from + /// the bank; but, as opposed to LoadBank(), the media will be loaded one media item at a time instead of in one contiguous memory block. Using PrepareBank(), alone or in combination with PrepareEvent(), + /// will prevent in-memory duplication of media at the cost of some memory fragmentation. + /// Calling this function specifying the flag AkBankContent_StructureOnly will load only the structural part (including events) of this bank, + /// allowing using PrepareEvent() to load media on demand. + /// \sa + /// - \ref soundengine_banks_preparingbanks + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::PreparationType + /// \remarks + /// PrepareBank(), when called with the flag AkBankContent_StructureOnly, requires additional calls to PrepareEvent() to load the media for each event. PrepareEvent(), however, is unable to + /// access media content contained within SoundBanks and requires that the media be available as loose files in the file system. This flag may be useful to implement multiple loading configurations; + /// for example, a game may have a tool mode that uses PrepareEvent() to load loose files on-demand and, also, a game mode that uses LoadBank() to load the bank in entirety. + AKRESULT PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const wchar_t* in_pszString, ///< Name of the bank to Prepare/Unprepare. + AK::SoundEngine::AkBankContent in_uFlags = AK::SoundEngine::AkBankContent_All, ///< Structures only (including events) or all content. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to Prepare/Unprepare. + ) override; +#endif //AK_SUPPORT_WCHAR + + /// This function will load the Events, structural content, and optionally, the media content from the SoundBank. If the flag AkBankContent_All is specified, PrepareBank() will load the media content from + /// the bank; but, as opposed to LoadBank(), the media will be loaded one media item at a time instead of in one contiguous memory block. Using PrepareBank(), alone or in combination with PrepareEvent(), + /// will prevent in-memory duplication of media at the cost of some memory fragmentation. + /// Calling this function specifying the flag AkBankContent_StructureOnly will load only the structural part (including events) of this bank, + /// allowing using PrepareEvent() to load media on demand. + /// \sa + /// - \ref soundengine_banks_preparingbanks + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::PreparationType + /// \remarks + /// \c PrepareBank(), when called with the flag \c AkBankContent_StructureOnly, requires additional calls to \c PrepareEvent() to load the media for each event. \c PrepareEvent(), however, is unable to + /// access media content contained within SoundBanks and requires that the media be available as loose files in the file system. This flag may be useful to implement multiple loading configurations; + /// for example, a game may have a tool mode that uses PrepareEvent() to load loose files on-demand and, also, a game mode that uses \c LoadBank() to load the bank in entirety. + AKRESULT PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const char* in_pszString, ///< Name of the bank to Prepare/Unprepare. + AK::SoundEngine::AkBankContent in_uFlags = AK::SoundEngine::AkBankContent_All, ///< Structures only (including events) or all content. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to Prepare/Unprepare. + ) override; + + /// \n\aknote Requires that the "Use SoundBank names" option be unchecked in the Wwise Project Settings. \endaknote + /// This function will load the events, structural content, and optionally, the media content from the SoundBank. If the flag AkBankContent_All is specified, PrepareBank() will load the media content from + /// the bank, but as opposed to LoadBank(), the media will be loaded one media item at a time instead of in one contiguous memory block. Using PrepareBank(), alone or in combination with PrepareEvent(), + /// will prevent in-memory duplication of media at the cost of some memory fragmentation. + /// Calling this function specifying the flag AkBankContent_StructureOnly will load only the structural part (including events) of this bank, + /// allowing using PrepareEvent() to load media on demand. + /// \sa + /// - \ref soundengine_banks_preparingbanks + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::PreparationType + /// \remarks + /// \c PrepareBank(), when called with the flag AkBankContent_StructureOnly, requires additional calls to PrepareEvent() to load the media for each event. PrepareEvent(), however, is unable to + /// access media content contained within SoundBanks and requires that the media be available as loose files in the file system. This flag may be useful to implement multiple loading configurations; + /// for example, a game may have a tool mode that uses PrepareEvent() to load loose files on-demand and, also, a game mode that uses LoadBank() to load the bank in entirety. + AKRESULT PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkBankID in_bankID, ///< ID of the bank to Prepare/Unprepare. + AK::SoundEngine::AkBankContent in_uFlags = AK::SoundEngine::AkBankContent_All, ///< Structures only (including events) or all content. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to Prepare/Unprepare. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// This function will load the Events, structural content, and optionally, the media content from the SoundBank. If the flag AkBankContent_All is specified, PrepareBank() will load the media content from + /// the bank, but as opposed to LoadBank(), the media will be loaded one media item at a time instead of in one contiguous memory block. Using PrepareBank(), alone or in combination with PrepareEvent(), + /// will prevent in-memory duplication of media at the cost of some memory fragmentation. + /// Calling this function specifying the flag AkBankContent_StructureOnly will load only the structural part (including events) of this bank, + /// allowing using PrepareEvent() to load media on demand. + /// \sa + /// - \ref soundengine_banks_preparingbanks + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::PreparationType + /// \remarks + /// PrepareBank(), when called with the flag AkBankContent_StructureOnly, requires additional calls to PrepareEvent() to load the media for each event. PrepareEvent(), however, is unable to + /// access media content contained within SoundBanks and requires that the media be available as loose files in the file system. This flag may be useful to implement multiple loading configurations; + /// for example, a game may have a tool mode that uses PrepareEvent() to load loose files on-demand and, also, a game mode that uses LoadBank() to load the bank in entirety. + AKRESULT PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const wchar_t* in_pszString, ///< Name of the bank to Prepare/Unprepare. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function) + AK::SoundEngine::AkBankContent in_uFlags = AK::SoundEngine::AkBankContent_All, ///< Structures only (including events) or all content. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to Prepare/Unprepare. + ) override; +#endif //AK_SUPPORT_WCHAR + + /// This function will load the events, structural content, and optionally, the media content from the SoundBank. If the flag \c AkBankContent_All is specified, \c PrepareBank() will load the media content from + /// the bank, but as opposed to \c LoadBank(), the media will be loaded one media item at a time instead of in one contiguous memory block. Using \c PrepareBank(), alone or in combination with PrepareEvent(), + /// will prevent in-memory duplication of media at the cost of some memory fragmentation. + /// Calling this function specifying the flag AkBankContent_StructureOnly will load only the structural part (including events) of this bank, + /// allowing using PrepareEvent() to load media on demand. + /// \sa + /// - \ref soundengine_banks_preparingbanks + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::PreparationType() + /// \remarks + /// PrepareBank(), when called with the flag AkBankContent_StructureOnly, requires additional calls to PrepareEvent() to load the media for each event. PrepareEvent(), however, is unable to + /// access media content contained within SoundBanks and requires that the media be available as loose files in the file system. This flag may be useful to implement multiple loading configurations; + /// for example, a game may have a tool mode that uses PrepareEvent() to load loose files on-demand and, also, a game mode that uses LoadBank() to load the bank in entirety. + AKRESULT PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const char* in_pszString, ///< Name of the bank to Prepare/Unprepare. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function) + AK::SoundEngine::AkBankContent in_uFlags = AK::SoundEngine::AkBankContent_All, ///< Structures only (including events) or all content. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to Prepare/Unprepare. + ) override; + + /// \n\aknote Requires that the "Use SoundBank names" option be unchecked in the Wwise Project Settings. \endaknote + /// This function will load the events, structural content, and optionally, the media content from the SoundBank. If the flag AkBankContent_All is specified, \c PrepareBank() will load the media content from + /// the bank, but as opposed to \c LoadBank(), the media will be loaded one media item at a time instead of in one contiguous memory block. Using \c PrepareBank(), alone or in combination with \c PrepareEvent(), + /// will prevent in-memory duplication of media at the cost of some memory fragmentation. + /// Calling this function specifying the flag AkBankContent_StructureOnly will load only the structural part (including events) of this bank, + /// allowing using PrepareEvent() to load media on demand. + /// \sa + /// - \ref soundengine_banks_preparingbanks + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::PreparationType() + /// \remarks + /// \c PrepareBank(), when called with the flag AkBankContent_StructureOnly, requires additional calls to PrepareEvent() to load the media for each event. \c PrepareEvent(), however, is unable to + /// access media content contained within SoundBanks and requires that the media be available as loose files in the file system. This flag may be useful to implement multiple loading configurations; + /// for example, a game may have a tool mode that uses \c PrepareEvent() to load loose files on-demand and, also, a game mode that uses \c LoadBank() to load the bank in entirety. + AKRESULT PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkBankID in_bankID, ///< ID of the bank to Prepare/Unprepare. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function) + AK::SoundEngine::AkBankContent in_uFlags = AK::SoundEngine::AkBankContent_All, ///< Structures only (including events) or all content. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to Prepare/Unprepare. + ) override; + + /// Clear all previously prepared events.\n + /// \return + /// - \c AK_Success if successful. + /// - \c AK_Fail if the sound engine was not correctly initialized or if there is not enough memory to handle the command. + /// \remarks + /// The function \c ClearBanks() also clears all prepared events. + /// \sa + /// - \c AK::SoundEngine::PrepareEvent() + /// - \c AK::SoundEngine::ClearBanks() + AKRESULT ClearPreparedEvents() override; + +#ifdef AK_SUPPORT_WCHAR + /// Prepares or unprepares Events synchronously (by Unicode string).\n + /// The Events are identified by strings, and converted to IDs internally + /// (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// Before invoking \c PrepareEvent(), use \c LoadBank() to explicitly load the SoundBank(s) + /// that contain the Events and structures. When a request is posted to the + /// Bank Manager consumer thread, it will resolve all dependencies needed to + /// successfully post the specified Events and load the required loose media files. + /// \aknote Before version 2015.1, the required media files could be included + /// in a separate media SoundBank. As described in \ref whatsnew_2015_1_migration, + /// however,\c PrepareEvent() now only looks for loose media files. + /// \endaknote + /// The function returns when the request is completely processed. + /// \return + /// - \c AK_Success: Prepare/un-prepare successful. + /// - \c AK_IDNotFound: At least one of the event/game sync identifiers passed to PrepareEvent() does not exist. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_InvalidParameter: Invalid parameter, invalid memory alignment. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// Whenever at least one event fails to be resolved, the actions performed for all + /// other events are cancelled. + /// \sa + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::ClearPreparedEvents() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::LoadBank() + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const wchar_t** in_ppszString, ///< Array of event names + AkUInt32 in_uNumEvent ///< Number of events in the array + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Prepares or unprepares events synchronously.\n + /// The Events are identified by strings and converted to IDs internally + /// (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// Before invoking PrepareEvent(), use LoadBank() to explicitly load the SoundBank(s) + /// that contain the Events and structures. When a request is posted to the + /// Bank Manager consumer thread, it will resolve all dependencies needed to + /// successfully post the specified Events and load the required loose media files. + /// \aknote Before version 2015.1, the required media files could be included + /// in a separate media SoundBank. As described in \ref whatsnew_2015_1_migration, + /// however, PrepareEvent() now only looks for loose media files. + /// \endaknote + /// The function returns when the request is completely processed. + /// \return + /// - \c AK_Success: Prepare/un-prepare successful. + /// - \c AK_IDNotFound: At least one of the event/game sync identifiers passed to PrepareEvent() does not exist. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_InvalidParameter: Invalid parameter, invalid memory alignment. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// Whenever at least one event fails to be resolved, the actions performed for all + /// other events are cancelled. + /// \aknote The use of PrepareEvent is incompatible with LoadBank, using in-memory data. + /// \endaknote + /// \sa + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::ClearPreparedEvents() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::LoadBank() + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const char** in_ppszString, ///< Array of event names + AkUInt32 in_uNumEvent ///< Number of events in the array + ) override; + + /// Prepares or unprepares events synchronously (by ID). + /// The Events are identified by their ID (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// Before invoking PrepareEvent(), use LoadBank() to explicitly load the SoundBank(s) + /// that contain the Events and structures. When a request is posted to the + /// Bank Manager consumer thread, it will resolve all dependencies needed to + /// successfully post the specified Events and load the required loose media files. + /// \aknote Before version 2015.1, the required media files could be included + /// in a separate media SoundBank. As described in \ref whatsnew_2015_1_migration, + /// however, PrepareEvent() now only looks for loose media files. + /// \endaknote + /// The function returns when the request is completely processed. + /// \return + /// - \c AK_Success: Prepare/un-prepare successful. + /// - \c AK_IDNotFound: At least one of the event/game sync identifiers passed to PrepareEvent() does not exist. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_InvalidParameter: Invalid parameter, invalid memory alignment. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// Whenever at least one event fails to be resolved, the actions performed for all + /// other events are cancelled. + /// \sa + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::ClearPreparedEvents() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::LoadBank() + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkUniqueID* in_pEventID, ///< Array of event IDs + AkUInt32 in_uNumEvent ///< Number of event IDs in the array + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Prepares or unprepares an event asynchronously (by Unicode string). + /// The Events are identified by string (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// Before invoking PrepareEvent(), use LoadBank() to explicitly load the SoundBank(s) + /// that contain the Events and structures. When a request is posted to the + /// Bank Manager consumer thread, it will resolve all dependencies needed to + /// successfully post the specified Events and load the required loose media files. + /// \aknote Before version 2015.1, the required media files could be included + /// in a separate media SoundBank. As described in \ref whatsnew_2015_1_migration, + /// however, \c PrepareEvent() now only looks for loose media files. + /// \endaknote + /// The function returns immediately. Use a callback to be notified when the request has finished being processed. + /// \return AK_Success if scheduling is was successful, AK_Fail otherwise. + /// \remarks + /// Whenever at least one Event fails to be resolved, the actions performed for all + /// other Events are cancelled. + /// \sa + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::ClearPreparedEvents() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::LoadBank() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const wchar_t** in_ppszString, ///< Array of event names + AkUInt32 in_uNumEvent, ///< Number of events in the array + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie ///< Callback cookie (reserved to user, passed to the callback function) + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Prepares or unprepares an event asynchronously. + /// The Events are identified by string (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// Before invoking PrepareEvent(), use LoadBank() to explicitly load the SoundBank(s) + /// that contain the Events and structures. When a request is posted to the + /// Bank Manager consumer thread, it will resolve all dependencies needed to + /// successfully post the specified Events and load the required loose media files. + /// \aknote Before version 2015.1, the required media files could be included + /// in a separate media SoundBank. As described in \ref whatsnew_2015_1_migration, + /// however, PrepareEvent() now only looks for loose media files. + /// \endaknote + /// The function returns immediately. Use a callback to be notified when the request has finished being processed. + /// \return AK_Success if scheduling is was successful, AK_Fail otherwise. + /// \remarks + /// Whenever at least one event fails to be resolved, the actions performed for all + /// other events are cancelled. + /// \sa + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::ClearPreparedEvents() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::LoadBank() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const char** in_ppszString, ///< Array of event names + AkUInt32 in_uNumEvent, ///< Number of events in the array + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie ///< Callback cookie (reserved to user, passed to the callback function) + ) override; + + /// Prepares or unprepares events asynchronously (by ID).\n + /// The Events are identified by their ID (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// Before invoking PrepareEvent(), use LoadBank() to explicitly load the SoundBank(s) + /// that contain the Events and structures. When a request is posted to the + /// Bank Manager consumer thread, it will resolve all dependencies needed to + /// successfully post the specified Events and load the required loose media files. + /// \aknote Before version 2015.1, the required media files could be included + /// in a separate media SoundBank. As described in \ref whatsnew_2015_1_migration, + /// however, PrepareEvent() now only looks for loose media files. + /// \endaknote + /// The function returns immediately. Use a callback to be notified when the request has finished being processed. + /// \return AK_Success if scheduling is was successful, AK_Fail otherwise. + /// \remarks + /// Whenever at least one event fails to be resolved, the actions performed for all + /// other events are cancelled. + /// \sa + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::ClearPreparedEvents() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::LoadBank() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkUniqueID* in_pEventID, ///< Array of event IDs + AkUInt32 in_uNumEvent, ///< Number of event IDs in the array + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie ///< Callback cookie (reserved to user, passed to the callback function) + ) override; + + /// Indicates the location of a specific Media ID in memory + /// The sources are identified by their ID (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// \return AK_Success if operation was successful, AK_InvalidParameter if in_pSourceSettings is invalid or media sizes are 0. + AKRESULT SetMedia( + AkSourceSettings* in_pSourceSettings, ///< Array of Source Settings + AkUInt32 in_uNumSourceSettings ///< Number of Source Settings in the array + ) override; + + /// Removes the specified source from the list of loaded media, even if this media is already in use. + /// The sources are identified by their ID (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// \aknote This function is unsafe and deprecated. Use TryUnsetMedia() in its place. + /// Media that is still in use by the sound engine should not be unset by this function. + /// If the media is still in use, this function will attempt to forcibly kill all sounds and effects referencing this media, + /// and then return AK_ResourceInUse. The client should NOT presume that the memory can be safely released at this point. + /// The moment at which the memory can be safely released is unknown, and the only safe course of action is to keep the memory + /// alive until the sound engine is terminated. + /// \endaknote + /// \return + /// - \c AK_Success: Operation was successful, and the memory can be released on the client side. + /// - \c AK_ResourceInUse: Specified media is still in use by the sound engine, the client should not release the memory. + /// - \c AK_InvalidParameter: in_pSourceSettings is invalid + AKRESULT UnsetMedia( + AkSourceSettings* in_pSourceSettings, ///< Array of Source Settings + AkUInt32 in_uNumSourceSettings ///< Number of Source Settings in the array + ) override; + + /// Removes the specified source from the list of loaded media, only if this media is not already in use. + /// The sources are identified by their ID (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// \aknote Media that is still in use by the sound engine should not be unset. It is marked for removal to prevent additional use. + /// If this function returns AK_ResourceInUse, then the client must not release memory for this media. + /// Instead, the client should retry the TryUnsetMedia operation later with the same parameters and check for AK_Success. + /// \endaknote + /// If out_pUnsetResults is not null, then it is assumed to point to an array of result codes of the same length as in_pSourceSettings. + /// out_pUnsetResults will be filled with either AK_Success or AK_ResourceInUse, indicating which media was still in use and not unset. + /// \return + /// - \c AK_Success: Operation was successful, and the memory can be released on the client side. + /// - \c AK_ResourceInUse: Specified media is still in use by the sound engine, and the media was not unset. Do not release memory, and try again later. + /// - \c AK_InvalidParameter: in_pSourceSettings is invalid + AKRESULT TryUnsetMedia( + AkSourceSettings* in_pSourceSettings, ///< Array of Source Settings + AkUInt32 in_uNumSourceSettings, ///< Number of Source Settings in the array + AKRESULT* out_pUnsetResults ///< (optional, can be null) Array of result codes + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Prepares or unprepares game syncs synchronously (by Unicode string).\n + /// The group and game syncs are specified by string (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// The game syncs definitions must already exist in the sound engine by having + /// explicitly loaded the bank(s) that contain them (with LoadBank()). + /// A request is posted to the Bank Manager consumer thread. It will resolve all + /// dependencies needed to successfully set this game sync group to one of the + /// game sync values specified, and load the required banks, if applicable. + /// The function returns when the request has been completely processed. + /// \return + /// - \c AK_Success: Prepare/un-prepare successful. + /// - \c AK_IDNotFound: At least one of the event/game sync identifiers passed to PrepareGameSyncs() does not exist. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_InvalidParameter: Invalid parameter, invalid memory alignment. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// You need to call PrepareGameSyncs() if the sound engine was initialized with AkInitSettings::bEnableGameSyncPreparation + /// set to true. When set to false, the sound engine automatically prepares all game syncs when preparing events, + /// so you never need to call this function. + /// \sa + /// - \c AK::SoundEngine::GetIDFromString() + /// - \c AK::SoundEngine::PrepareEvent() + /// - \c AK::SoundEngine::LoadBank() + /// - \c AkInitSettings + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkGroupType in_eGameSyncType, ///< The type of game sync. + const wchar_t* in_pszGroupName, ///< The State Group Name or the Switch Group Name. + const wchar_t** in_ppszGameSyncName, ///< The specific ID of the state to either support or not support. + AkUInt32 in_uNumGameSyncs ///< The number of game sync in the string array. + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Prepares or unprepares game syncs synchronously.\n + /// The group and game syncs are specified by string (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// The game syncs definitions must already exist in the sound engine by having + /// explicitly loaded the bank(s) that contain them (with LoadBank()). + /// A request is posted to the Bank Manager consumer thread. It will resolve all + /// dependencies needed to successfully set this game sync group to one of the + /// game sync values specified, and load the required banks, if applicable. + /// The function returns when the request has been completely processed. + /// \return + /// - \c AK_Success: Prepare/un-prepare successful. + /// - \c AK_IDNotFound: At least one of the event/game sync identifiers passed to PrepareGameSyncs() does not exist. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_InvalidParameter: Invalid parameter, invalid memory alignment. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// You need to call PrepareGameSyncs() if the sound engine was initialized with AkInitSettings::bEnableGameSyncPreparation + /// set to true. When set to false, the sound engine automatically prepares all game syncs when preparing events, + /// so you never need to call this function. + /// \sa + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::LoadBank() + /// - AkInitSettings + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkGroupType in_eGameSyncType, ///< The type of game sync. + const char* in_pszGroupName, ///< The State Group Name or the Switch Group Name. + const char** in_ppszGameSyncName, ///< The specific ID of the state to either support or not support. + AkUInt32 in_uNumGameSyncs ///< The number of game sync in the string array. + ) override; + + /// Prepares or unprepares game syncs synchronously (by ID).\n + /// The group and game syncs are specified by ID (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// The game syncs definitions must already exist in the sound engine by having + /// explicitly loaded the bank(s) that contain them (with LoadBank()). + /// A request is posted to the Bank Manager consumer thread. It will resolve all + /// dependencies needed to successfully set this game sync group to one of the + /// game sync values specified, and load the required banks, if applicable. + /// The function returns when the request has been completely processed. + /// \return + /// - \c AK_Success: Prepare/un-prepare successful. + /// - \c AK_IDNotFound: At least one of the event/game sync identifiers passed to PrepareGameSyncs() does not exist. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_InvalidParameter: Invalid parameter, invalid memory alignment. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// You need to call \c PrepareGameSyncs() if the sound engine was initialized with \c AkInitSettings::bEnableGameSyncPreparation + /// set to \c true. When set to \c false, the sound engine automatically prepares all game syncs when preparing Events, + /// so you never need to call this function. + /// \sa + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::LoadBank() + /// - AkInitSettings + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkGroupType in_eGameSyncType, ///< The type of game sync. + AkUInt32 in_GroupID, ///< The State Group ID or the Switch Group ID. + AkUInt32* in_paGameSyncID, ///< Array of ID of the game syncs to either support or not support. + AkUInt32 in_uNumGameSyncs ///< The number of game sync ID in the array. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Prepares or unprepares game syncs asynchronously (by Unicode string).\n + /// The group and game syncs are specified by string (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// The game syncs definitions must already exist in the sound engine by having + /// explicitly loaded the bank(s) that contain them (with LoadBank()). + /// A request is posted to the Bank Manager consumer thread. It will resolve all + /// dependencies needed to successfully set this game sync group to one of the + /// game sync values specified, and load the required banks, if applicable. + /// The function returns immediately. Use a callback to be notified when the request has finished being processed. + /// \return AK_Success if scheduling is was successful, AK_Fail otherwise. + /// \remarks + /// You need to call \c PrepareGameSyncs() if the sound engine was initialized with \c AkInitSettings::bEnableGameSyncPreparation + /// set to \c true. When set to \c false, the sound engine automatically prepares all game syncs when preparing Events, + /// so you never need to call this function. + /// \sa + /// - \c AK::SoundEngine::GetIDFromString() + /// - \c AK::SoundEngine::PrepareEvent() + /// - \c AK::SoundEngine::LoadBank() + /// - \c AkInitSettings + /// - \c AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkGroupType in_eGameSyncType, ///< The type of game sync. + const wchar_t* in_pszGroupName, ///< The State Group Name or the Switch Group Name. + const wchar_t** in_ppszGameSyncName, ///< The specific ID of the state to either support or not support. + AkUInt32 in_uNumGameSyncs, ///< The number of game sync in the string array. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie ///< Callback cookie (reserved to user, passed to the callback function) + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Prepares or unprepares game syncs asynchronously.\n + /// The group and game syncs are specified by string (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// The game syncs definitions must already exist in the sound engine by having + /// explicitly loaded the bank(s) that contain them (with LoadBank()). + /// A request is posted to the Bank Manager consumer thread. It will resolve all + /// dependencies needed to successfully set this game sync group to one of the + /// game sync values specified, and load the required banks, if applicable. + /// The function returns immediately. Use a callback to be notified when the request has finished being processed. + /// \return AK_Success if scheduling is was successful, AK_Fail otherwise. + /// \remarks + /// You need to call PrepareGameSyncs() if the sound engine was initialized with AkInitSettings::bEnableGameSyncPreparation + /// set to true. When set to false, the sound engine automatically prepares all game syncs when preparing events, + /// so you never need to call this function. + /// \sa + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::LoadBank() + /// - AkInitSettings + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkGroupType in_eGameSyncType, ///< The type of game sync. + const char* in_pszGroupName, ///< The State Group Name or the Switch Group Name. + const char** in_ppszGameSyncName, ///< The specific ID of the state to either support or not support. + AkUInt32 in_uNumGameSyncs, ///< The number of game sync in the string array. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie ///< Callback cookie (reserved to user, passed to the callback function) + ) override; + + /// Prepares or un-prepare game syncs asynchronously (by ID).\n + /// The group and game syncs are specified by ID (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// The game syncs definitions must already exist in the sound engine by having + /// explicitly loaded the bank(s) that contain them (with LoadBank()). + /// A request is posted to the Bank Manager consumer thread. It will resolve all + /// dependencies needed to successfully set this game sync group to one of the + /// game sync values specified, and load the required banks, if applicable. + /// The function returns immediately. Use a callback to be notified when the request has finished being processed. + /// \return AK_Success if scheduling is was successful, AK_Fail otherwise. + /// \remarks + /// You need to call PrepareGameSyncs() if the sound engine was initialized with AkInitSettings::bEnableGameSyncPreparation + /// set to true. When set to false, the sound engine automatically prepares all Game Syncs when preparing Events, + /// so you never need to call this function. + /// \sa + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::LoadBank() + /// - AkInitSettings + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkGroupType in_eGameSyncType, ///< The type of game sync. + AkUInt32 in_GroupID, ///< The State Group ID or the Switch Group ID. + AkUInt32* in_paGameSyncID, ///< Array of ID of the Game Syncs to either support or not support. + AkUInt32 in_uNumGameSyncs, ///< The number of game sync ID in the array. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie ///< Callback cookie (reserved to user, passed to the callback function) + ) override; + + //@} + + + //////////////////////////////////////////////////////////////////////// + /// @name Listeners + //@{ + + /// Sets a game object's associated listeners. + /// All listeners that have previously been added via AddListener or set via SetListeners will be removed and replaced with the listeners in the array in_pListenerGameObjs. + /// Calling this function will override the default set of listeners and in_emitterGameObj will now reference its own, unique set of listeners. + /// \return + /// - \c AK_Success if successful + /// - \c AK_CommandTooLarge if the number of positions is too large for the command queue. Reduce the number of positions. + /// \sa + /// - AK::SoundEngine::AddListener + /// - AK::SoundEngine::RemoveListener + /// - AK::SoundEngine::SetDefaultListeners + /// - \ref soundengine_listeners + AKRESULT SetListeners( + AkGameObjectID in_emitterGameObj, ///< Emitter game object. Must have been previously registered via RegisterGameObj. + const AkGameObjectID* in_pListenerGameObjs, ///< Array of listener game object IDs that will be activated for in_emitterGameObj. + AkUInt32 in_uNumListeners ///< Length of array + ) override; + + /// Add a single listener to a game object's set of associated listeners. + /// Any listeners that have previously been added or set via AddListener or SetListeners will remain as listeners and in_listenerGameObj will be added as an additional listener. + /// Calling this function will override the default set of listeners and in_emitterGameObj will now reference its own, unique set of listeners. + /// \sa + /// - AK::SoundEngine::SetListeners + /// - AK::SoundEngine::RemoveListener + /// - AK::SoundEngine::SetDefaultListeners + /// - \ref soundengine_listeners + AKRESULT AddListener( + AkGameObjectID in_emitterGameObj, ///< Emitter game object. Must have been previously registered via RegisterGameObj. + AkGameObjectID in_listenerGameObj ///< Listener game object IDs that will be activated for in_emitterGameObj. + ) override; + + /// Remove a single listener from a game object's set of active listeners. + /// Calling this function will override the default set of listeners and in_emitterGameObj will now reference its own, unique set of listeners. + /// \sa + /// - AK::SoundEngine::SetListeners + /// - AK::SoundEngine::AddListener + /// - AK::SoundEngine::SetDefaultListeners + /// - \ref soundengine_listeners + AKRESULT RemoveListener( + AkGameObjectID in_emitterGameObj, ///< Emitter game object. + AkGameObjectID in_listenerGameObj ///< Listener game object IDs that will be deactivated for in_emitterGameObj. Game objects must have been previously registered. + ) override; + + /// Sets the default set of associated listeners for game objects that have not explicitly overridden their listener sets. Upon registration, all game objects reference the default listener set, until + /// a call to AddListener, RemoveListener, SetListeners or SetGameObjectOutputBusVolume is made on that game object. + /// All default listeners that have previously been added via AddDefaultListener or set via SetDefaultListeners will be removed and replaced with the listeners in the array in_pListenerGameObjs. + /// \return Always returns AK_Success + /// \sa + /// - \ref soundengine_listeners + AKRESULT SetDefaultListeners( + const AkGameObjectID* in_pListenerObjs, ///< Array of listener game object IDs that will be activated for subsequent registrations. Game objects must have been previously registered. + AkUInt32 in_uNumListeners ///< Length of array + ) override; + + /// Add a single listener to the default set of listeners. Upon registration, all game objects reference the default listener set, until + /// a call to AddListener, RemoveListener, SetListeners or SetGameObjectOutputBusVolume is made on that game object. + /// \sa + /// - AK::SoundEngine::SetDefaultListeners + /// - AK::SoundEngine::RemoveDefaultListener + /// - \ref soundengine_listeners + AKRESULT AddDefaultListener( + AkGameObjectID in_listenerGameObj ///< Listener game object IDs that will be added to the default set of listeners. + ) override; + + /// Remove a single listener from the default set of listeners. Upon registration, all game objects reference the default listener set, until + /// a call to AddListener, RemoveListener, SetListeners or SetGameObjectOutputBusVolume is made on that game object. + /// \sa + /// - AK::SoundEngine::SetDefaultListeners + /// - AK::SoundEngine::AddDefaultListener + /// - \ref soundengine_listeners + AKRESULT RemoveDefaultListener( + AkGameObjectID in_listenerGameObj ///< Listener game object IDs that will be removed from the default set of listeners. + ) override; + + /// Resets the listener associations to the default listener(s), as set by SetDefaultListeners. This will also reset per-listener gains that have been set using SetGameObjectOutputBusVolume. + /// \return Always returns AK_Success + /// \sa + /// - AK::SoundEngine::SetListeners + /// - AK::SoundEngine::SetDefaultListeners + /// - AK::SoundEngine::SetGameObjectOutputBusVolume + /// - \ref soundengine_listeners + AKRESULT ResetListenersToDefault( + AkGameObjectID in_emitterGameObj ///< Emitter game object. + ) override; + + /// Sets a listener's spatialization parameters. This lets you define listener-specific + /// volume offsets for each audio channel. + /// If \c in_bSpatialized is false, only \c in_pVolumeOffsets is used for this listener (3D positions + /// have no effect on the speaker distribution). Otherwise, \c in_pVolumeOffsets is added to the speaker + /// distribution computed for this listener. + /// Use helper functions of \c AK::SpeakerVolumes to manipulate the vector of volume offsets in_pVolumeOffsets. + /// + /// \remarks + /// - If a sound is mixed into a bus that has a different speaker configuration than in_channelConfig, + /// standard up/downmix rules apply. + /// - Sounds with 3D Spatialization set to None will not be affected by these parameters. + /// \return + /// - \c AK_Success if message was successfully posted to sound engine queue + /// - \c AK_InvalidFloatValue if the value specified was NaN or Inf + /// - \c AK_InsufficientMemory if there wasn't enough memory in the message queue + /// \sa + /// - \ref soundengine_listeners_spatial + AKRESULT SetListenerSpatialization( + AkGameObjectID in_uListenerID, ///< Listener game object ID + bool in_bSpatialized, ///< Spatialization toggle (True : enable spatialization, False : disable spatialization) + AkChannelConfig in_channelConfig, ///< Channel configuration associated with volumes in_pVolumeOffsets. Ignored if in_pVolumeOffsets is NULL. + AK::SpeakerVolumes::VectorPtr in_pVolumeOffsets = NULL ///< Per-speaker volume offset, in dB. See AkSpeakerVolumes.h for how to manipulate this vector. + ) override; + + //@} + + + //////////////////////////////////////////////////////////////////////// + /// @name Game Syncs + //@{ + + /// Sets the value of a real-time parameter control (by ID). + /// With this function, you may set a game parameter value with global scope or with game object scope. + /// Game object scope supersedes global scope. (Once a value is set for the game object scope, it will not be affected by changes to the global scope value.) Game parameter values set with a global scope are applied to all + /// game objects that not yet registered, or already registered but not overridden with a value with game object scope. + /// To set a game parameter value with global scope, pass \c AK_INVALID_GAME_OBJECT as the game object. + /// With this function, you may also change the value of a game parameter over time. To do so, specify a non-zero + /// value for \c in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call \c SetRTPCValue() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. Thus, if you call this + /// function at every game frame, you should not use \c in_uValueChangeDuration, as it would have no effect and it is less efficient. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// \return + /// - \c AK_Success if the value was successfully set + /// - \c AK_InvalidFloatValue if the value specified was NaN, Inf or FLT_MAX (3.402823e+38) + /// - \c AK_InvalidID if in_rtpcID is AK_INVALID_UNIQUE_ID (0) + /// \sa + /// - \ref soundengine_rtpc + /// - AK::SoundEngine::GetIDFromString() + AKRESULT SetRTPCValue( + AkRtpcID in_rtpcID, ///< ID of the game parameter + AkRtpcValue in_value, ///< Value to set + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT,///< Associated game object ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards in_value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when for example loading a level and you dont want the values to interpolate. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Sets the value of a real-time parameter control (by Unicode string name). + /// With this function, you may set a game parameter value to global scope or to game object scope. + /// Game object scope supersedes global scope. (Once a value is set for the game object scope, it will not be affected by changes to the global scope value.) Game parameter values set with global scope are applied to all + /// game objects that not yet registered, or already registered but not overridden with a value with game object scope. + /// To set a game parameter value with global scope, pass AK_INVALID_GAME_OBJECT as the game object. + /// With this function, you may also change the value of a game parameter over time. To do so, specify a non-zero + /// value for in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValue() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. Thus, if you call this + /// function at every game frame, you should not use in_uValueChangeDuration, as it would have no effect and it is less efficient. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// \return + /// - \c AK_Success if the value was successfully set + /// - \c AK_InvalidFloatValue if the value specified was NaN, Inf or FLT_MAX (3.402823e+38) + /// - \c AK_InvalidID if in_pszRtpcName is NULL. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_rtpc + AKRESULT SetRTPCValue( + const wchar_t* in_pszRtpcName, ///< Name of the game parameter + AkRtpcValue in_value, ///< Value to set + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT,///< Associated game object ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards in_value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when for example loading a level and you dont want the values to interpolate. + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Sets the value of a real-time parameter control. + /// With this function, you may set a game parameter value with global scope or with game object scope. + /// Game object scope supersedes global scope. (Once a value is set for the game object scope, it will not be affected by changes to the global scope value.) Game parameter values set with global scope are applied to all + /// game objects that not yet registered, or already registered but not overridden with a value with game object scope. + /// To set a game parameter value with global scope, pass AK_INVALID_GAME_OBJECT as the game object. + /// With this function, you may also change the value of a game parameter over time. To do so, specify a non-zero + /// value for \c in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValue() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. Thus, if you call this + /// function at every game frame, you should not use in_uValueChangeDuration, as it would have no effect and it is less efficient. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// \return + /// - \c AK_Success if the value was successfully set + /// - \c AK_InvalidFloatValue if the value specified was NaN, Inf or FLT_MAX (3.402823e+38) + /// - \c AK_InvalidID if in_pszRtpcName is NULL. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_rtpc + AKRESULT SetRTPCValue( + const char* in_pszRtpcName, ///< Name of the game parameter + AkRtpcValue in_value, ///< Value to set + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT,///< Associated game object ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards in_value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when for example loading a level and you dont want the values to interpolate. + ) override; + + /// Sets the value of a real-time parameter control (by ID). + /// With this function, you may set a game parameter value on playing id scope. + /// Playing id scope supersedes both game object scope and global scope. + /// With this function, you may also change the value of a game parameter over time. To do so, specify a non-zero + /// value for in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValueByPlayingID() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. Thus, if you call this + /// function at every game frame, you should not use in_uValueChangeDuration, as it would have no effect and it is less efficient. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// - \c AK_Success if successful + /// - \c AK_PlayingIDNotFound if in_playingID is not found. + /// - \c AK_InvalidID if in_pszRtpcName is NULL. + /// - \c AK_InvalidFloatValue if the value specified was NaN, Inf or FLT_MAX (3.402823e+38) + /// \sa + /// - \ref soundengine_rtpc + /// - AK::SoundEngine::GetIDFromString() + AKRESULT SetRTPCValueByPlayingID( + AkRtpcID in_rtpcID, ///< ID of the game parameter + AkRtpcValue in_value, ///< Value to set + AkPlayingID in_playingID, ///< Associated playing ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards in_value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when, for example, loading a level and you don't want the values to interpolate. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Sets the value of a real-time parameter control (by Unicode string name). + /// With this function, you may set a game parameter value on playing ID scope. + /// Playing id scope supersedes both game object scope and global scope. + /// With this function, you may also change the value of a game parameter over time. To do so, specify a non-zero + /// value for in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValueByPlayingID() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. Thus, if you call this + /// function at every game frame, you should not use in_uValueChangeDuration, as it would have no effect and it is less efficient. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// - \c AK_Success if successful + /// - \c AK_PlayingIDNotFound if in_playingID is not found. + /// - \c AK_InvalidID if in_pszRtpcName is NULL. + /// - \c AK_InvalidFloatValue if the value specified was NaN, Inf or FLT_MAX (3.402823e+38) + /// \sa + /// - \ref soundengine_rtpc + /// - AK::SoundEngine::GetIDFromString() + AKRESULT SetRTPCValueByPlayingID( + const wchar_t* in_pszRtpcName, ///< Name of the game parameter + AkRtpcValue in_value, ///< Value to set + AkPlayingID in_playingID, ///< Associated playing ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards in_value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when, for example, loading a level and you don't want the values to interpolate. + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Sets the value of a real-time parameter control (by string name). + /// With this function, you may set a game parameter value on playing id scope. + /// Playing id scope supersedes both game object scope and global scope. + /// With this function, you may also change the value of a game parameter over time. To do so, specify a non-zero + /// value for in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValueByPlayingID() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. Thus, if you call this + /// function at every game frame, you should not use in_uValueChangeDuration, as it would have no effect and it is less efficient. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// - \c AK_Success if successful + /// - \c AK_PlayingIDNotFound if in_playingID is not found. + /// - \c AK_InvalidID if in_pszRtpcName is NULL. + /// - \c AK_InvalidFloatValue if the value specified was NaN, Inf or FLT_MAX (3.402823e+38) + /// \sa + /// - \ref soundengine_rtpc + /// - AK::SoundEngine::GetIDFromString() + AKRESULT SetRTPCValueByPlayingID( + const char* in_pszRtpcName, ///< Name of the game parameter + AkRtpcValue in_value, ///< Value to set + AkPlayingID in_playingID, ///< Associated playing ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards in_value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when for example loading a level and you dont want the values to interpolate. + ) override; + + /// Resets the value of the game parameter to its default value, as specified in the Wwise project. + /// With this function, you may reset a game parameter to its default value with global scope or with game object scope. + /// Game object scope supersedes global scope. Game parameter values reset with global scope are applied to all + /// game objects that were not overridden with a value with game object scope. + /// To reset a game parameter value with global scope, pass AK_INVALID_GAME_OBJECT as the game object. + /// With this function, you may also reset the value of a game parameter over time. To do so, specify a non-zero + /// value for in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValue() or ResetRTPCValue() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// \return + /// - \c AK_Success when successful + /// - \c AK_InvalidID if in_rtpcID is AK_INVALID_UNIQUE_ID (0) + /// \sa + /// - \ref soundengine_rtpc + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::SetRTPCValue() + AKRESULT ResetRTPCValue( + AkRtpcID in_rtpcID, ///< ID of the game parameter + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT,///< Associated game object ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards its default value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when for example loading a level and you dont want the values to interpolate. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Resets the value of the game parameter to its default value, as specified in the Wwise project. + /// With this function, you may reset a game parameter to its default value with global scope or with game object scope. + /// Game object scope supersedes global scope. Game parameter values reset with global scope are applied to all + /// game objects that were not overridden with a value with game object scope. + /// To reset a game parameter value with global scope, pass AK_INVALID_GAME_OBJECT as the game object. + /// With this function, you may also reset the value of a game parameter over time. To do so, specify a non-zero + /// value for in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValue() or ResetRTPCValue() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if in_pszParamName is NULL. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_rtpc + /// - AK::SoundEngine::SetRTPCValue() + AKRESULT ResetRTPCValue( + const wchar_t* in_pszRtpcName, ///< Name of the game parameter + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT,///< Associated game object ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards its default value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when for example loading a level and you dont want the values to interpolate. + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Resets the value of the game parameter to its default value, as specified in the Wwise project. + /// With this function, you may reset a game parameter to its default value with global scope or with game object scope. + /// Game object scope supersedes global scope. Game parameter values reset with global scope are applied to all + /// game objects that were not overridden with a value with game object scope. + /// To reset a game parameter value with global scope, pass AK_INVALID_GAME_OBJECT as the game object. + /// With this function, you may also reset the value of a game parameter over time. To do so, specify a non-zero + /// value for in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValue() or ResetRTPCValue() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if in_pszParamName is NULL. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_rtpc + /// - AK::SoundEngine::SetRTPCValue() + AKRESULT ResetRTPCValue( + const char* in_pszRtpcName, ///< Name of the game parameter + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT,///< Associated game object ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards its default value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when for example loading a level and you dont want the values to interpolate. + ) override; + + /// Sets the State of a Switch Group (by IDs). + /// \return Always returns AK_Success + /// \sa + /// - \ref soundengine_switch + /// - AK::SoundEngine::GetIDFromString() + AKRESULT SetSwitch( + AkSwitchGroupID in_switchGroup, ///< ID of the Switch Group + AkSwitchStateID in_switchState, ///< ID of the Switch + AkGameObjectID in_gameObjectID ///< Associated game object ID + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Sets the State of a Switch Group (by Unicode string names). + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if the switch or Switch Group name was not resolved to an existing ID\n + /// Make sure that the banks were generated with the "include string" option. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_switch + AKRESULT SetSwitch( + const wchar_t* in_pszSwitchGroup, ///< Name of the Switch Group + const wchar_t* in_pszSwitchState, ///< Name of the Switch + AkGameObjectID in_gameObjectID ///< Associated game object ID + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Sets the state of a Switch Group. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if the switch or Switch Group name was not resolved to an existing ID\n + /// Make sure that the banks were generated with the "include string" option. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_switch + AKRESULT SetSwitch( + const char* in_pszSwitchGroup, ///< Name of the Switch Group + const char* in_pszSwitchState, ///< Name of the Switch + AkGameObjectID in_gameObjectID ///< Associated game object ID + ) override; + + /// Post the specified trigger (by IDs). + /// \return Always returns AK_Success + /// \sa + /// - \ref soundengine_triggers + /// - AK::SoundEngine::GetIDFromString() + AKRESULT PostTrigger( + AkTriggerID in_triggerID, ///< ID of the trigger + AkGameObjectID in_gameObjectID ///< Associated game object ID + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Posts the specified trigger (by Unicode string name). + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if the trigger name was null + /// Make sure that the banks were generated with the "include string" option. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_triggers + AKRESULT PostTrigger( + const wchar_t* in_pszTrigger, ///< Name of the trigger + AkGameObjectID in_gameObjectID ///< Associated game object ID + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Posts the specified trigger. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if the trigger name was null + /// Make sure that the banks were generated with the "include string" option. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_triggers + AKRESULT PostTrigger( + const char* in_pszTrigger, ///< Name of the trigger + AkGameObjectID in_gameObjectID ///< Associated game object ID + ) override; + + /// Sets the state of a State Group (by IDs). + /// \return Always returns AK_Success + /// \sa + /// - \ref soundengine_states + /// - AK::SoundEngine::GetIDFromString() + AKRESULT SetState( + AkStateGroupID in_stateGroup, ///< ID of the State Group + AkStateID in_state ///< ID of the state + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Sets the state of a State Group (by Unicode string names). + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if the state or State Group name was null + /// Make sure that the banks were generated with the "include string" option. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_states + /// - AK::SoundEngine::GetIDFromString() + AKRESULT SetState( + const wchar_t* in_pszStateGroup, ///< Name of the State Group + const wchar_t* in_pszState ///< Name of the state + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Sets the state of a State Group. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if the state or State Group name was null + /// Make sure that the banks were generated with the "include string" option. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_states + /// - AK::SoundEngine::GetIDFromString() + AKRESULT SetState( + const char* in_pszStateGroup, ///< Name of the State Group + const char* in_pszState ///< Name of the state + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Environments + //@{ + + /// Sets the Auxiliary Busses to route the specified game object + /// To clear the game object's auxiliary sends, \c in_uNumSendValues must be 0. + /// \sa + /// - \ref soundengine_environments + /// - \ref soundengine_environments_dynamic_aux_bus_routing + /// - \ref soundengine_environments_id_vs_string + /// - AK::SoundEngine::GetIDFromString() + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if a duplicated environment is found in the array + /// - \c AK_InvalidFloatValue if the value specified was NaN or Inf + AKRESULT SetGameObjectAuxSendValues( + AkGameObjectID in_gameObjectID, ///< Associated game object ID + AkAuxSendValue* in_aAuxSendValues, ///< Variable-size array of AkAuxSendValue structures + ///< (it may be NULL if no environment must be set) + AkUInt32 in_uNumSendValues ///< The number of auxiliary busses at the pointer's address + ///< (it must be 0 if no environment is set) + ) override; + + /// Registers a callback to allow the game to modify or override the volume to be applied at the output of an audio bus. + /// The callback must be registered once per bus ID. + /// Call with in_pfnCallback = NULL to unregister. + /// \aknote The bus in_busID needs to be a mixing bus.\endaknote + /// \aknote The callback function will not be called for the Master Audio Bus, since the output of this bus is not a bus, but is instead an Audio Device.\endaknote + /// \sa + /// - \ref goingfurther_speakermatrixcallback + /// - \ref soundengine_environments + /// - AkSpeakerVolumeMatrixCallbackInfo + /// - AK::IAkMixerInputContext + /// - AK::IAkMixerPluginContext + /// \return + /// - \c AK_Success if successful + /// - \c AK_IDNotFound if the bus is not found + /// - \c AK_NotInitialized if the sound engine is not initialized + /// - \c AK_InsufficientMemory if there is not enough memory to complete the operation + AKRESULT RegisterBusVolumeCallback( + AkUniqueID in_busID, ///< Bus ID, as obtained by GetIDFromString( bus_name ). + AkBusCallbackFunc in_pfnCallback, ///< Callback function. + void* in_pCookie = NULL ///< User cookie. + ) override; + + /// Registers a callback to be called to allow the game to access metering data from any mixing bus. You may use this to monitor loudness at any point of the mixing hierarchy + /// by accessing the peak, RMS, True Peak and K-weighted power (according to loudness standard ITU BS.1770). See \ref goingfurther_speakermatrixcallback for an example. + /// The callback must be registered once per bus ID. + /// Call with in_pfnCallback = NULL to unregister. + /// \aknote The bus in_busID needs to be a mixing bus.\endaknote + /// \sa + /// - \ref goingfurther_speakermatrixcallback + /// - AkBusMeteringCallbackFunc + /// - AK::AkMetering + /// \return + /// - \c AK_Success if successful + /// - \c AK_IDNotFound if the bus is not found + /// - \c AK_NotInitialized if the sound engine is not initialized + /// - \c AK_InsufficientMemory if there is not enough memory to complete the operation + AKRESULT RegisterBusMeteringCallback( + AkUniqueID in_busID, ///< Bus ID, as obtained by GetIDFromString( bus_name ). + AkBusMeteringCallbackFunc in_pfnCallback, ///< Callback function. + AkMeteringFlags in_eMeteringFlags, ///< Metering flags. + void* in_pCookie = NULL ///< User cookie. + ) override; + + /// Registers a callback to be called to allow the game to access metering data from any output device. You may use this to monitor loudness as sound leaves the Wwise sound engine + /// by accessing the peak, RMS, True Peak and K-weighted power (according to loudness standard ITU BS.1770). See \ref goingfurther_speakermatrixcallback for an example. + /// The callback must be registered once per device ShareSet ID. + /// Call with in_pfnCallback = NULL to unregister. + /// \sa + /// - \ref goingfurther_speakermatrixcallback + /// - AkOutputDeviceMeteringCallbackFunc + /// - AK::AkMetering + /// \return + /// - \c AK_Success if successful + /// - \c AK_DeviceNotFound if the device is not found + /// - \c AK_NotInitialized if the sound engine is not initialized + /// - \c AK_InsufficientMemory if there is not enough memory to complete the operation + AKRESULT RegisterOutputDeviceMeteringCallback( + AkOutputDeviceID in_idOutput, ///< Output ID, as returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + AkOutputDeviceMeteringCallbackFunc in_pfnCallback, ///< Callback function. + AkMeteringFlags in_eMeteringFlags, ///< Metering flags. + void* in_pCookie = NULL ///< User cookie. + ) override; + + /// Sets the Output Bus Volume (direct) to be used for the specified game object. + /// The control value is a number ranging from 0.0f to 1.0f. + /// Output Bus Volumes are stored per listener association, so calling this function will override the default set of listeners. The game object in_emitterObjID will now reference its own set of listeners which will + /// be the same as the old set of listeners, but with the new associated gain. Future changes to the default listener set will not be picked up by this game object unless ResetListenersToDefault() is called. + /// \sa + /// - \ref AK::SoundEngine::ResetListenersToDefault + /// - \ref soundengine_environments + /// - \ref soundengine_environments_setting_dry_environment + /// - \ref soundengine_environments_id_vs_string + /// \return + /// - \c AK_Success when successful + /// - \c AK_InvalidFloatValue if the value specified was NaN or Inf + AKRESULT SetGameObjectOutputBusVolume( + AkGameObjectID in_emitterObjID, ///< Associated emitter game object ID + AkGameObjectID in_listenerObjID, ///< Associated listener game object ID. Pass AK_INVALID_GAME_OBJECT to set the Output Bus Volume for all connected listeners. + AkReal32 in_fControlValue ///< A multiplier in the range [0.0f:16.0f] ( -inf dB to +24 dB). + ///< A value greater than 1.0f will amplify the sound. + ) override; + + /// Sets an Effect ShareSet at the specified audio node and Effect slot index. + /// \aknote + /// Replacing effects is preferably done through a Set Effect Event Action. + /// \endaknote + /// The target node cannot be a Bus, to set effects on a bus, use SetBusEffect() instead. + /// \aknote The option "Override Parent" in + /// the Effect section in Wwise must be enabled for this node, otherwise the parent's effect will + /// still be the one in use and the call to SetActorMixerEffect will have no impact. + /// \endaknote + /// \return Always returns AK_Success + AKRESULT SetActorMixerEffect( + AkUniqueID in_audioNodeID, ///< Can be a member of the Actor-Mixer or Interactive Music Hierarchy (not a bus). + AkUInt32 in_uFXIndex, ///< Effect slot index (0-3) + AkUniqueID in_ShareSetID ///< ShareSets ID; pass AK_INVALID_UNIQUE_ID to clear the effect slot + ) override; + + /// Sets an Effect ShareSet at the specified bus and Effect slot index. + /// \aknote + /// Replacing effects is preferably done through a Set Effect Event Action. + /// \endaknote + /// The Bus can either be an Audio Bus or an Auxiliary Bus. + /// This adds a reference on the audio node to an existing ShareSet. + /// \aknote This function has unspecified behavior when adding an Effect to a currently playing + /// Bus which does not have any Effects, or removing the last Effect on a currently playing bus. + /// \endaknote + /// \aknote This function will replace existing Effects on the node. If the target node is not at + /// the top of the hierarchy and is in the actor-mixer hierarchy, the option "Override Parent" in + /// the Effect section in Wwise must be enabled for this node, otherwise the parent's Effect will + /// still be the one in use and the call to SetBusEffect will have no impact. + /// \endaknote + /// \return + /// - \c AK_Success when successfully posted. + /// - \c AK_IDNotFound if the Bus isn't found by in_audioNodeID + /// - \c AK_InvalidParameter if in_uFXIndex isn't in range + AKRESULT SetBusEffect( + AkUniqueID in_audioNodeID, ///< Bus Short ID. + AkUInt32 in_uFXIndex, ///< Effect slot index (0-3) + AkUniqueID in_ShareSetID ///< ShareSets ID; pass AK_INVALID_UNIQUE_ID to clear the Effect slot + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Sets an Effect ShareSet at the specified Bus and Effect slot index. + /// \aknote + /// Replacing effects is preferably done through a Set Effect Event Action. + /// \endaknote + /// The Bus can either be an Audio Bus or an Auxiliary Bus. + /// This adds a reference on the audio node to an existing ShareSet. + /// \aknote This function has unspecified behavior when adding an Effect to a currently playing + /// bus which does not have any Effects, or removing the last Effect on a currently playing Bus. + /// \endaknote + /// \aknote This function will replace existing Effects on the node. If the target node is not at + /// the top of the hierarchy and is in the Actor-Mixer Hierarchy, the option "Override Parent" in + /// the Effect section in Wwise must be enabled for this node, otherwise the parent's Effect will + /// still be the one in use and the call to \c SetBusEffect will have no impact. + /// \endaknote + /// \returns + /// - \c AK_Success when successfully posted. + /// - \c AK_IDNotFound if the Bus name doesn't point to a valid bus. + /// - \c AK_InvalidID if in_pszBusName is null + /// - \c AK_InvalidParameter if in_uFXIndex isn't in range or in_pszBusName is null + AKRESULT SetBusEffect( + const wchar_t* in_pszBusName, ///< Bus name + AkUInt32 in_uFXIndex, ///< Effect slot index (0-3) + AkUniqueID in_ShareSetID ///< ShareSets ID; pass AK_INVALID_UNIQUE_ID to clear the effect slot + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Sets an Effect ShareSet at the specified Bus and Effect slot index. + /// \aknote + /// Replacing effects is preferably done through a Set Effect Event Action. + /// \endaknote + /// The Bus can either be an Audio Bus or an Auxiliary Bus. + /// This adds a reference on the audio node to an existing ShareSet. + /// \aknote This function has unspecified behavior when adding an Effect to a currently playing + /// Bus which does not have any effects, or removing the last Effect on a currently playing bus. + /// \endaknote + /// \aknote Make sure the new effect ShareSet is included in a soundbank, and that sound bank is loaded. Otherwise you will see errors in the Capture Log.\endaknote + /// \aknote This function will replace existing Effects on the node. If the target node is not at + /// the top of the hierarchy and is in the Actor-Mixer Hierarchy, the option "Override Parent" in + /// the Effect section in Wwise must be enabled for this node, otherwise the parent's Effect will + /// still be the one in use and the call to SetBusEffect will have no impact. + /// \endaknote + /// \returns + /// - \c AK_Success when successfully posted. + /// - \c AK_IDNotFound if the Bus name doesn't point to a valid bus. + /// - \c AK_InvalidParameter if in_uFXIndex isn't in range + /// - \c AK_InvalidID if in_pszBusName is null + AKRESULT SetBusEffect( + const char* in_pszBusName, ///< Bus name + AkUInt32 in_uFXIndex, ///< Effect slot index (0-3) + AkUniqueID in_ShareSetID ///< ShareSets ID; pass AK_INVALID_UNIQUE_ID to clear the effect slot + ) override; + + /// Sets an audio device effect ShareSet on the specified output device and effect slot index. + /// \aknote + /// Replacing effects is preferably done through a Set Effect Event Action. + /// \endaknote + /// \aknote Make sure the new effect ShareSet is included in a soundbank, and that sound bank is loaded. Otherwise you will see errors in the Capture Log.\endaknote + /// \aknote This function will replace existing effects of the audio device ShareSet. \endaknote + /// \aknote Audio device effects support is limited to one ShareSet per plug-in type at any time. \endaknote + /// \aknote Errors are reported in the Wwise Capture Log if the effect cannot be set on the output device. \endaknote + + /// \returns Always returns AK_Success + AKRESULT SetOutputDeviceEffect( + AkOutputDeviceID in_outputDeviceID, ///< Output ID, as returned from AddOutput or GetOutputID. Most of the time this should be 0 to designate the main (default) output + AkUInt32 in_uFXIndex, ///< Effect slot index (0-3) + AkUniqueID in_FXShareSetID ///< Effect ShareSet ID + ) override; + + /// Sets a Mixer ShareSet at the specified bus. + /// \aknote This function has unspecified behavior when adding a mixer to a currently playing + /// Bus which does not have any Effects or mixer, or removing the last mixer on a currently playing Bus. + /// \endaknote + /// \aknote Make sure the new mixer ShareSet is included in a soundbank, and that sound bank is loaded. Otherwise you will see errors in the Capture Log.\endaknote + /// \aknote This function will replace existing mixers on the node. + /// \endaknote + /// \return Always returns AK_Success + AKRESULT SetMixer( + AkUniqueID in_audioNodeID, ///< Bus Short ID. + AkUniqueID in_ShareSetID ///< ShareSets ID; pass AK_INVALID_UNIQUE_ID to remove. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Sets a Mixer ShareSet at the specified bus. + /// \aknote This function has unspecified behavior when adding a mixer to a currently playing + /// bus which does not have any effects nor mixer, or removing the last mixer on a currently playing bus. + /// \endaknote + /// \aknote Make sure the new mixer ShareSet is included in a soundbank, and that sound bank is loaded. Otherwise you will see errors in the Capture Log.\endaknote + /// \aknote This function will replace existing mixers on the node. + /// \endaknote + /// \returns + /// - \c AK_Success when successful + /// - \c AK_InvalidID if in_pszBusName is null + AKRESULT SetMixer( + const wchar_t* in_pszBusName, ///< Bus name + AkUniqueID in_ShareSetID ///< ShareSets ID; pass AK_INVALID_UNIQUE_ID to remove. + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Sets a Mixer ShareSet at the specified bus. + /// \aknote This function has unspecified behavior when adding a mixer to a currently playing + /// bus which does not have any effects nor mixer, or removing the last mixer on a currently playing bus. + /// \endaknote + /// \aknote Make sure the new mixer ShareSet is included in a soundbank, and that sound bank is loaded. Otherwise you will see errors in the Capture Log.\endaknote + /// \aknote This function will replace existing mixers on the node. + /// \endaknote + /// \returns + /// - \c AK_Success when successful + /// - \c AK_InvalidID if in_pszBusName is null + AKRESULT SetMixer( + const char* in_pszBusName, ///< Bus name + AkUniqueID in_ShareSetID ///< ShareSets ID; pass AK_INVALID_UNIQUE_ID to remove. + ) override; + + /// Forces channel configuration for the specified bus. + /// \aknote You cannot change the configuration of the master bus.\endaknote + /// + /// \return Always returns AK_Success + AKRESULT SetBusConfig( + AkUniqueID in_audioNodeID, ///< Bus Short ID. + AkChannelConfig in_channelConfig ///< Desired channel configuration. An invalid configuration (from default constructor) means "as parent". + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Forces channel configuration for the specified bus. + /// \aknote You cannot change the configuration of the master bus.\endaknote + /// + /// \returns + /// - \c AK_Success when successful + /// - \c AK_InvalidID if in_pszBusName is null + AKRESULT SetBusConfig( + const wchar_t* in_pszBusName, ///< Bus name + AkChannelConfig in_channelConfig ///< Desired channel configuration. An invalid configuration (from default constructor) means "as parent". + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Forces channel configuration for the specified bus. + /// \aknote You cannot change the configuration of the master bus.\endaknote + /// + /// \returns + /// - \c AK_Success when successful + /// - \c AK_InvalidID if in_pszBusName is null + AKRESULT SetBusConfig( + const char* in_pszBusName, ///< Bus name + AkChannelConfig in_channelConfig ///< Desired channel configuration. An invalid configuration (from default constructor) means "as parent". + ) override; + + /// Sets a game object's obstruction and occlusion levels. If SetMultiplePositions were used, values are set for all positions. + /// This function is used to affect how an object should be heard by a specific listener. + /// \sa + /// - \ref soundengine_obsocc + /// - \ref soundengine_environments + /// \return Always returns AK_Success + AKRESULT SetObjectObstructionAndOcclusion( + AkGameObjectID in_EmitterID, ///< Emitter game object ID + AkGameObjectID in_ListenerID, ///< Listener game object ID + AkReal32 in_fObstructionLevel, ///< ObstructionLevel: [0.0f..1.0f] + AkReal32 in_fOcclusionLevel ///< OcclusionLevel: [0.0f..1.0f] + ) override; + + /// Sets a game object's obstruction and occlusion level for each positions defined by SetMultiplePositions. + /// This function differs from SetObjectObstructionAndOcclusion as a list of obstruction/occlusion pair is provided + /// and each obstruction/occlusion pair will affect the corresponding position defined at the same index. + /// \aknote In the case the number of obstruction/occlusion pairs is smaller than the number of positions, remaining positions' + /// obstrucion/occlusion values are set to 0.0. \endaknote + /// \return + /// - \c AK_Success if successful + /// - \c AK_CommandTooLarge if the number of obstruction values is too large for the command queue. + /// - \c AK_InvalidParameter if one of the parameter is out of range (check the debug console) + /// - \c AK_InvalidFloatValue if one of the occlusion/obstruction values is NaN or Inf. + /// \sa + /// - \ref soundengine_obsocc + /// - \ref soundengine_environments + /// \return AK_Success if occlusion/obstruction values are successfully stored for this emitter + AKRESULT SetMultipleObstructionAndOcclusion( + AkGameObjectID in_EmitterID, ///< Emitter game object ID + AkGameObjectID in_uListenerID, ///< Listener game object ID + AkObstructionOcclusionValues* in_fObstructionAndOcclusionValues, ///< Array of obstruction/occlusion pairs to apply + ///< ObstructionLevel: [0.0f..1.0f] + ///< OcclusionLevel: [0.0f..1.0f] + AkUInt32 in_uNumObstructionAndOcclusion ///< Number of obstruction/occlusion pairs specified in the provided array + ) override; + + /// Saves the playback history of container structures. + /// This function will write history data for all currently loaded containers and instantiated game + /// objects (for example, current position in Sequence Containers and previously played elements in + /// Random Containers). + /// \remarks + /// This function acquires the main audio lock, and may block the caller for several milliseconds. + /// \return + /// - \c AK_Success when successful + /// - \c AK_Fail is in_pBytes could not be parsed (corruption or data is truncated) + /// \sa + /// - AK::SoundEngine::SetContainerHistory() + AKRESULT GetContainerHistory( + AK::IWriteBytes* in_pBytes ///< Pointer to IWriteBytes interface used to save the history. + ) override; + + /// Restores the playback history of container structures. + /// This function will read history data from the passed-in stream reader interface, and apply it to all + /// currently loaded containers and instantiated game objects. Game objects are matched by + /// ID. History for unloaded structures and unknown game objects will be skipped. + /// \remarks + /// This function acquires the main audio lock, and may block the caller for several milliseconds. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InsufficientMemory if not enough memory is available for IReadBytes operation + /// \sa + /// - AK::SoundEngine::GetContainerHistory() + AKRESULT SetContainerHistory( + AK::IReadBytes* in_pBytes ///< Pointer to IReadBytes interface used to load the history. + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Capture + //@{ + + /// Starts recording the sound engine audio output. + /// StartOutputCapture outputs a wav file per current output device of the sound engine. + /// If more than one device is active, the system will create multiple files in the same output + /// directory and will append numbers at the end of the provided filename. + /// + /// If no device is running yet, the system will return success AK_Success despite doing nothing. + /// Use RegisterAudioDeviceStatusCallback to get notified when devices are created/destructed. + /// + /// \return AK_Success if successful, AK_Fail if there was a problem starting the output capture. + /// \remark + /// - The sound engine opens a stream for writing using AK::IAkStreamMgr::CreateStd(). If you are using the + /// default implementation of the Stream Manager, file opening is executed in your implementation of + /// the Low-Level IO interface AK::StreamMgr::IAkFileLocationResolver::Open(). The following + /// AkFileSystemFlags are passed: uCompanyID = AKCOMPANYID_AUDIOKINETIC and uCodecID = AKCODECID_PCM, + /// and the AkOpenMode is AK_OpenModeWriteOvrwr. Refer to \ref streamingmanager_lowlevel_location for + /// more details on managing the deployment of your Wwise generated data. + /// \return + /// - \c AK_Success when successful + /// - \c AK_InvalidParameter if in_CaptureFileName is null. + /// - \c AK_InsufficientMemory if not enough memory is available. + /// \sa + /// - AK::SoundEngine::StopOutputCapture() + /// - AK::StreamMgr::SetFileLocationResolver() + /// - \ref streamingdevicemanager + /// - \ref streamingmanager_lowlevel_location + /// - RegisterAudioDeviceStatusCallback + AKRESULT StartOutputCapture( + const AkOSChar* in_CaptureFileName ///< Name of the output capture file + ) override; + + /// Stops recording the sound engine audio output. + /// \return AK_Success if successful, AK_Fail if there was a problem stopping the output capture. + /// \sa + /// - AK::SoundEngine::StartOutputCapture() + AKRESULT StopOutputCapture() override; + + /// Adds text marker in audio output file. + /// \return + /// - \c AK_Success when successful + /// - \c AK_InvalidParameter if in_MarkerText is null. + /// - \c AK_InsufficientMemory if not enough memory is available. + /// \sa + /// - AK::SoundEngine::StartOutputCapture() + AKRESULT AddOutputCaptureMarker( + const char* in_MarkerText ///< Text of the marker + ) override; + + /// Gets the system sample rate. + /// \return The sample rate. + AkUInt32 GetSampleRate() override; + + /// Registers a callback used for retrieving audio samples. + /// The callback will be called from the audio thread during real-time rendering and from the main thread during offline rendering. + /// \return + /// - \c AK_Success when successful + /// - \c AK_DeviceNotFound if the audio device ID doesn't match to a device in use. + /// - \c AK_InvalidParameter when in_pfnCallback is null + /// - \c AK_NotInitialized if the sound engine is not initialized at this time + /// \sa + /// - AK::SoundEngine::AddOutput() + /// - AK::SoundEngine::GetOutputID() + /// - AK::SoundEngine::UnregisterCaptureCallback() + AKRESULT RegisterCaptureCallback( + AkCaptureCallbackFunc in_pfnCallback, ///< Capture callback function to register. + AkOutputDeviceID in_idOutput = AK_INVALID_OUTPUT_DEVICE_ID, ///< The audio device specific id, return by AK::SoundEngine::AddOutput or AK::SoundEngine::GetOutputID + void* in_pCookie = NULL ///< Callback cookie that will be sent to the callback function along with additional information + ) override; + + /// Unregisters a callback used for retrieving audio samples. + /// \return + /// - \c AK_Success when successful + /// - \c AK_DeviceNotFound if the audio device ID doesn't match to a device in use. + /// - \c AK_InvalidParameter when in_pfnCallback is null + /// - \c AK_NotInitialized if the sound engine is not initialized at this time + /// \sa + /// - AK::SoundEngine::AddOutput() + /// - AK::SoundEngine::GetOutputID() + /// - AK::SoundEngine::RegisterCaptureCallback() + AKRESULT UnregisterCaptureCallback( + AkCaptureCallbackFunc in_pfnCallback, ///< Capture callback function to unregister. + AkOutputDeviceID in_idOutput = AK_INVALID_OUTPUT_DEVICE_ID, ///< The audio device specific id, return by AK::SoundEngine::AddOutput or AK::SoundEngine::GetOutputID + void* in_pCookie = NULL ///< Callback cookie that will be sent to the callback function along with additional information + ) override; + + /// Starts recording the sound engine profiling information into a file. This file can be read + /// by Wwise Authoring. The file is created at the base path. If you have integrated Wwise I/O, + /// you can use CAkDefaultIOHookBlocking::SetBasePath() (or CAkDefaultIOHookBlocking::AddBasePath()) + /// to change the location where the file is saved. The profiling session records all data types possible. + /// Note that this call captures peak metering for all the busses loaded and mixing + /// while this call is invoked. + /// \remark This function is provided as a utility tool only. It does nothing if it is + /// called in the release configuration and returns AK_NotCompatible. + AKRESULT StartProfilerCapture( + const AkOSChar* in_CaptureFileName ///< Name of the output profiler file (.prof extension recommended) + ) override; + + /// Stops recording the sound engine profiling information. + /// \remark This function is provided as a utility tool only. It does nothing if it is + /// called in the release configuration and returns AK_NotCompatible. + AKRESULT StopProfilerCapture() override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Offline Rendering + //@{ + + /// Sets the offline rendering frame time in seconds. + /// When offline rendering is enabled, every call to \ref RenderAudio() will generate sample data as if this much time has elapsed. If the frame time argument is less than or equal to zero, every call to RenderAudio() will generate one audio buffer. + /// \return Always returns AK_Success + AKRESULT SetOfflineRenderingFrameTime( + AkReal32 in_fFrameTimeInSeconds ///< frame time in seconds used during offline rendering + ) override; + + /// Enables/disables offline rendering. + /// \return Always returns AK_Success + AKRESULT SetOfflineRendering( + bool in_bEnableOfflineRendering ///< enables/disables offline rendering + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Secondary Outputs + //@{ + + /// Adds an output to the sound engine. Use this to add controller-attached headphones, controller speakers, DVR output, etc. + /// The in_Settings parameter contains an Audio Device ShareSet to specify the output plugin to use and a device ID to specify the instance, if necessary (e.g. which game controller). + /// + /// Like most functions of AK::SoundEngine, AddOutput is asynchronous. A successful return code merely indicates that the request is properly queued. + /// Error codes returned by this function indicate various invalid parameters. To know if this function succeeds or not, and the failure code, + /// register an AkDeviceStatusCallbackFunc callback with RegisterAudioDeviceStatusCallback. + /// + /// \sa AkOutputSettings for more details. + /// \sa \ref integrating_secondary_outputs + /// \sa \ref default_audio_devices + /// \sa AK::SoundEngine::RegisterAudioDeviceStatusCallback + /// \sa AK::AkDeviceStatusCallbackFunc + /// \return + /// The following codes are returned directly from the function, as opposed to the AkDeviceStatusCallback + /// - \c AK_NotImplemented: Feature not supported, some platforms don't have other outputs. + /// - \c AK_InvalidParameter: Out of range parameters or unsupported parameter combinations (see parameter list below). + /// - \c AK_IDNotFound: The audioDeviceShareSet on in_settings doesn't exist. Possibly, the Init bank isn't loaded yet or was not updated with latest changes. + /// - \c AK_DeviceNotReady: The idDevice on in_settings doesn't match with a valid hardware device. Either the device doesn't exist or is disabled. Disconnected devices (headphones) are not considered "not ready" therefore won't cause this error. + /// - \c AK_NotInitialized: If AK::SoundEngine::Init was not called or if the Init.bnk was not loaded before the call. + /// - \c AK_Success: Parameters are valid. + /// + /// The following codes are returned from the callback. + /// - \c AK_InsufficientMemory : Not enough memory to complete the operation. + /// - \c AK_IDNotFound: The audioDeviceShareSet on in_settings doesn't exist. Possibly, the Init bank isn't loaded yet or was not updated with latest changes. + /// - \c AK_PluginNotRegistered: The audioDeviceShareSet exists but the plug-in it refers to is not installed or statically linked with the game. + /// - \c AK_NotCompatible: The hardware does not support this type of output. Wwise will try to use the System output instead, and a separate callback will fire when that completes. + /// - \c AK_DeviceNotCompatible: The hardware does not support this type of output. Wwise will NOT fallback to any other type of output. + /// - \c AK_Fail: Generic code for any non-permanent conditions (e.g. disconnection) that prevent the use of the output. Wwise has created the output and sounds will be routed to it, but this output is currently silent until the temporary condition resolves. + /// - \c AK_NoDistinctListener: Outputs of the same type (same ShareSet, like controller speakers) must have distinct Listeners to make a proper routing. This doesn't happen if there is only one output of that type. + AKRESULT AddOutput( + const AkOutputSettings& in_Settings, ///< Creation parameters for this output. \ref AkOutputSettings + AkOutputDeviceID* out_pDeviceID = NULL, ///< (Optional) Output ID to use with all other Output management functions. Leave to NULL if not required. \ref AK::SoundEngine::GetOutputID + const AkGameObjectID* in_pListenerIDs = NULL, ///< Specific listener(s) to attach to this device. + ///< If specified, only the sounds routed to game objects linked to those listeners will play in this device. + ///< It is necessary to have separate listeners if multiple devices of the same type can coexist (e.g. controller speakers) + ///< If not specified, sound routing simply obey the associations between Master Busses and Audio Devices setup in the Wwise Project. + AkUInt32 in_uNumListeners = 0 ///< The number of elements in the in_pListenerIDs array. + ) override; + + /// Removes one output added through AK::SoundEngine::AddOutput + /// If a listener was associated with the device, you should consider unregistering the listener prior to call RemoveOutput + /// so that Game Object/Listener routing is properly updated according to your game scenario. + /// \sa \ref integrating_secondary_outputs + /// \sa AK::SoundEngine::AddOutput + /// \return AK_Success: Parameters are valid. + AKRESULT RemoveOutput( + AkOutputDeviceID in_idOutput ///< ID of the output to remove. Use the returned ID from AddOutput, GetOutputID, or ReplaceOutput + ) override; + + /// Replaces an output device previously created during engine initialization or from AddOutput, with a new output device. + /// In addition to simply removing one output device and adding a new one, the new output device will also be used on all of the master buses + /// that the old output device was associated with, and preserve all listeners that were attached to the old output device. + /// + /// Like most functions of AK::SoundEngine, AddOutput is asynchronous. A successful return code merely indicates that the request is properly queued. + /// Error codes returned by this function indicate various invalid parameters. To know if this function succeeds or not, and the failure code, + /// register an AkDeviceStatusCallbackFunc callback with RegisterAudioDeviceStatusCallback. + /// + /// \sa AK::SoundEngine::AddOutput + /// \sa AK::SoundEngine::RegisterAudioDeviceStatusCallback + /// \sa AK::AkDeviceStatusCallbackFunc + /// \return + /// - \c AK_InvalidID: The audioDeviceShareSet on in_settings was not valid. + /// - \c AK_IDNotFound: The audioDeviceShareSet on in_settings doesn't exist. Possibly, the Init bank isn't loaded yet or was not updated with latest changes. + /// - \c AK_DeviceNotReady: The idDevice on in_settings doesn't match with a valid hardware device. Either the device doesn't exist or is disabled. Disconnected devices (headphones) are not considered "not ready" therefore won't cause this error. + /// - \c AK_DeviceNotFound: The in_outputDeviceId provided does not match with any of the output devices that the sound engine is currently using. + /// - \c AK_InvalidParameter: Out of range parameters or unsupported parameter combinations on in_settings + /// - \c AK_Success: parameters were valid, and the remove and add will occur. + AKRESULT ReplaceOutput( + const AkOutputSettings& in_Settings, ///< Creation parameters for this output. \ref AkOutputSettings + AkOutputDeviceID in_outputDeviceId, ///< AkOutputDeviceID of the output to replace. Use 0 to target the current main output, regardless of its id. Otherwise, use the AkOuptutDeviceID returned from AddOutput() or ReplaceOutput(), or generated by GetOutputID() + AkOutputDeviceID* out_pOutputDeviceId = NULL ///< (Optional) Pointer into which the method writes the AkOutputDeviceID of the new output device. If the call fails, the value pointed to will not be modified. + ) override; + + /// Gets the compounded output ID from ShareSet and device id. + /// Outputs are defined by their type (Audio Device ShareSet) and their specific system ID. A system ID could be reused for other device types on some OS or platforms, hence the compounded ID. + /// Use 0 for in_idShareSet & in_idDevice to get the Main Output ID (the one usually initialized during AK::SoundEngine::Init) + /// \return The id of the output + AkOutputDeviceID GetOutputID( + AkUniqueID in_idShareSet, ///< Audio Device ShareSet ID, as defined in the Wwise Project. If needed, use AK::SoundEngine::GetIDFromString() to convert from a string. Set to AK_INVALID_UNIQUE_ID to use the default. + AkUInt32 in_idDevice ///< Device specific identifier, when multiple devices of the same type are possible. If only one device is possible, leave to 0. + ///< - PS4 Controller-Speakers: UserID as returned from sceUserServiceGetLoginUserIdList + ///< - XBoxOne Controller-Headphones: Use the AK::GetDeviceID function to get the ID from an IMMDevice. Find the player's device with the WASAPI API (IMMDeviceEnumerator, see Microsoft documentation) or use AK::GetDeviceIDFromName. + ///< - Windows: Use AK::GetDeviceID or AK::GetDeviceIDFromName to get the correct ID. + ///< - All others output: use 0 to select the default device for that type. + ) override; + + AkOutputDeviceID GetOutputID( + const char* in_szShareSet, ///< Audio Device ShareSet Name, as defined in the Wwise Project. If Null, will select the Default Output ShareSet (always available) + AkUInt32 in_idDevice ///< Device specific identifier, when multiple devices of the same type are possible. If only one device is possible, leave to 0. + ///< - PS4 Controller-Speakers: UserID as returned from sceUserServiceGetLoginUserIdList + ///< - XBoxOne Controller-Headphones: Use the AK::GetDeviceID function to get the ID from an IMMDevice. Find the player's device with the WASAPI API (IMMDeviceEnumerator, see Microsoft documentation) or use AK::GetDeviceIDFromName. + ///< - Windows: Use AK::GetDeviceID or AK::GetDeviceIDFromName to get the correct ID. + ///< - All others output: use 0 to select the default device for that type. + ) override; + +#ifdef AK_SUPPORT_WCHAR + AkOutputDeviceID GetOutputID( + const wchar_t* in_szShareSet, ///< Audio Device ShareSet Name, as defined in the Wwise Project. If Null, will select the Default Output ShareSet (always available) + AkUInt32 in_idDevice ///< Device specific identifier, when multiple devices of the same type are possible. If only one device is possible, leave to 0. + ///< - PS4 Controller-Speakers: UserID as returned from sceUserServiceGetLoginUserIdList + ///< - XBoxOne Controller-Headphones: Use the AK::GetDeviceID function to get the ID from an IMMDevice. Find the player's device with the WASAPI API (IMMDeviceEnumerator, see Microsoft documentation) or use AK::GetDeviceIDFromName. + ///< - Windows: Use AK::GetDeviceID or AK::GetDeviceIDFromName to get the correct ID. + ///< - All others output: use 0 to select the default device for that type. + ) override; +#endif + + /// Sets the Audio Device to which a master bus outputs. This overrides the setting in the Wwise project. + /// Can only be set on top-level busses. The Init bank should be successfully loaded prior to this call. + /// \aknote This function is useful only if used before the creation of an output, at the beginning of the sound engine setup. + /// Once active outputs using this Bus have been created, it is imperative to use AK::SoundEngine:ReplaceOutput instead to change the type of output. + /// \return + /// AK_IDNotFound when either the Bus ID or the Device ID are not present in the Init bank or the bank was not loaded + /// AK_InvalidParameter when the specified bus is not a Master Bus. This function can be called only on busses that have no parent bus. + AKRESULT SetBusDevice( + AkUniqueID in_idBus, ///< Id of the master bus + AkUniqueID in_idNewDevice ///< New device ShareSet to replace with. + ) override; + + /// Sets the Audio Device to which a master bus outputs. This overrides the setting in the Wwise project. + /// Can only be set on top-level busses. The Init bank should be successfully loaded prior to this call. + /// \aknote This function is useful only if used before the creation of an output, at the beginning of the sound engine setup. + /// Once active outputs using this Bus have been created, it is imperative to use AK::SoundEngine:ReplaceOutput instead to change the type of output. + /// \return + /// AK_IDNotFound when either the Bus ID or the Device ID are not present in the Init bank or the bank was not loaded + /// AK_InvalidParameter when the specified bus is not a Master Bus. This function can be called only on busses that have no parent bus. + AKRESULT SetBusDevice( + const char* in_BusName, ///< Name of the master bus + const char* in_DeviceName ///< New device ShareSet to replace with. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Sets the Audio Device to which a master bus outputs. This overrides the setting in the Wwise project. + /// Can only be set on top-level busses. The Init bank should be successfully loaded prior to this call. + /// SetBusDevice must be preceded by a call to AddOutput for the new device ShareSet to be registered as an output. + /// \return + /// AK_IDNotFound when either the Bus ID or the Device ID are not present in the Init bank or the bank was not loaded + /// AK_InvalidParameter when the specified bus is not a Master Bus. This function can be called only on busses that have no parent bus. + AKRESULT SetBusDevice( + const wchar_t* in_BusName, ///< Name of the master bus + const wchar_t* in_DeviceName ///< New device ShareSet to replace with. + ) override; +#endif + + /// Returns a listing of the current devices for a given sink plug-in, including Device ID, friendly name, and state. + /// This call is only valid for sink plug-ins that support device enumeration. + /// Prerequisites: the plug-in must have been initialized by loading the init bank or by calling \ref AK::SoundEngine::RegisterPlugin. + /// \return + /// - \c AK_NotImplemented if the sink plug-in does not implement device enumeration + /// - \c AK_PluginNotRegistered if the plug-in has not been registered yet either by loading the init bank or by calling RegisterPluginDLL. + /// - \c AK_NotCompatible if no device of this type are supported on the current platform + /// - \c AK_Fail in case of system device manager failure (OS related) + /// + AKRESULT GetDeviceList( + AkUInt32 in_ulCompanyID, ///< Company identifier (as declared in the plug-in description XML file) + AkUInt32 in_ulPluginID, ///< Plug-in identifier (as declared in the plug-in description XML file) + AkUInt32& io_maxNumDevices, ///< In: The length of the out_deviceDescriptions array. Out: If out_deviceDescriptions is not-null, this will be set to the number of entries in out_deviceDescriptions that was populated. If out_deviceDescriptions is null, this will be set to the number of devices that may be available. + AkDeviceDescription* out_deviceDescriptions ///< The output array of device descriptions. + ) override; + + /// Returns a listing of the current devices for a given sink plug-in, including Device ID, friendly name, and state. + /// This call is only valid for sink plug-ins that support device enumeration. + /// Prerequisites: + /// * The plug-in must have been initialized by loading the init bank or by calling \ref AK::SoundEngine::RegisterPlugin. + /// * The audio device ShareSet must have been loaded from a soundbank and a device with this ShareSet must exist in the pipeline. + /// \return + /// AK_NotImplemented if the sink plug-in does not implement device enumeration + /// AK_PluginNotRegistered if the plug-in has not been registered yet either by loading the init bank or by calling RegisterPluginDLL. + AKRESULT GetDeviceList( + AkUniqueID in_audioDeviceShareSetID, ///< In: The audio device ShareSet ID for which to list the sink plug-in devices. + AkUInt32& io_maxNumDevices, ///< In: The length of the out_deviceDescriptions array. Out: If out_deviceDescriptions is not-null, this will be set to the number of entries in out_deviceDescriptions that was populated. If out_deviceDescriptions is null, this will be set to the number of devices that may be available. + AkDeviceDescription* out_deviceDescriptions ///< The output array of device descriptions. + ) override; + + /// Sets the volume of a output device. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidFloatValue if the value specified was NaN or Inf + AKRESULT SetOutputVolume( + AkOutputDeviceID in_idOutput, ///< Output ID to set the volume on. As returned from AddOutput or GetOutputID + AkReal32 in_fVolume ///< Volume (0.0 = Muted, 1.0 = Volume max) + ) override; + + /// Returns whether or not the audio device matching the device ID provided supports spatial audio (i.e. the functionality is enabled, and more than 0 dynamic objects are supported). + /// If Spatial Audio is supported, then you can call Init, AddOutput, or ReplaceOutput with an Audio Device ShareSet corresponding to the respective platform-specific plug-in that + /// provides spatial audio, such as the Microsoft Spatial Sound Platform for Windows. Note that on Xbox One, you need to call EnableSpatialAudio() before the sound engine is + /// initialized, or initialize the sound engine with AkPlatformInitSettings::bEnableSpatialAudio set to true if you want spatial audio support; otherwise this will always return AK_NotCompatible. + /// \return + /// AK_NotCompatible when the device ID provided does not support spatial audio, or the platform does not support spatial audio + /// AK_Fail when there is some other miscellaneous failure, or the device ID provided does not match a device that the system knows about + /// AK_Success when the device ID provided does support spatial audio + AKRESULT GetDeviceSpatialAudioSupport( + AkUInt32 in_idDevice ///< Device specific identifier, when multiple devices of the same type are possible. If only one device is possible, leave to 0. + ///< - PS4 Controller-Speakers: UserID as returned from sceUserServiceGetLoginUserIdList + ///< - XBoxOne Controller-Headphones: Use the AK::GetDeviceID function to get the ID from an IMMDevice. Find the player's device with the WASAPI API (IMMDeviceEnumerator, see Microsoft documentation) or use AK::GetDeviceIDFromName. + ///< - Windows: Use AK::GetDeviceID or AK::GetDeviceIDFromName to get the correct ID. + ///< - All others output: use 0 to select the default device for that type. + ) override; + + + //@} + + /// This function should be called to put the sound engine in background mode, where audio isn't processed anymore. This needs to be called if the console has a background mode or some suspended state. + /// Call \c WakeupFromSuspend when your application receives the message from the OS that the process is back in foreground. + /// When suspended, the sound engine will process API messages (like PostEvent and SetSwitch) only when \ref RenderAudio() is called. + /// It is recommended to match the in_bRenderAnyway parameter with the behavior of the rest of your game: + /// if your game still runs in background and you must keep some kind of coherent state between the audio engine and game, then allow rendering. + /// If you want to minimize CPU when in background, then don't allow rendering and never call RenderAudio from the game. + /// + /// Consult \ref workingwithsdks_system_calls to learn when it is appropriate to call this function for each platform. + /// \sa WakeupFromSuspend + /// \sa \ref workingwithsdks_system_calls + AKRESULT Suspend( + bool in_bRenderAnyway = false, ///< If set to true, audio processing will still occur, but not outputted. When set to false, no audio will be processed at all, even upon reception of RenderAudio(). + bool in_bFadeOut = true ///< Delay the suspend by one audio frame in order to fade-out. When false, the suspend takes effect immediately but audio may glitch. + ) override; + + /// This function should be called to wake up the sound engine and start processing audio again. This needs to be called if the console has a background mode or some suspended state. + /// + /// Consult \ref workingwithsdks_system_calls to learn when it is appropriate to call this function for each platform. + /// \sa Suspend + /// \sa \ref workingwithsdks_system_calls + AKRESULT WakeupFromSuspend( + AkUInt32 in_uDelayMs = 0 /// Delay (in milliseconds) before the wake up occurs. Rounded up to audio frame granularity. Adding a delay is useful if there is a possibility that another OS event may override the wake-up in the near future. + ) override; + + /// Obtains the current audio output buffer tick. This corresponds to the number of buffers produced by + /// the sound engine since initialization. + /// \return Tick count. + AkUInt32 GetBufferTick() override; + + /// Obtains the current audio output sample tick. This corresponds to the number of sapmles produced by + /// the sound engine since initialization. + /// \return Sample count. + AkUInt64 GetSampleTick() override; + + class WWISESOUNDENGINE_API FQuery : public IQuery + { + public: + UE_NONCOPYABLE(FQuery); + FQuery() = default; + + //////////////////////////////////////////////////////////////////////// + /// @name Game Objects + //@{ + + /// Get the position of a game object. + /// \return AK_Success if succeeded, or AK_IDNotFound if the game object was not registered + /// \sa + /// - \ref soundengine_3dpositions + AKRESULT GetPosition( + AkGameObjectID in_GameObjectID, ///< Game object identifier + AkSoundPosition& out_rPosition ///< Position to get + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Listeners + //@{ + + /// Get a game object's listeners. + /// \return AK_Success if succeeded, or AK_IDNotFound if the game object was not registered + /// \sa + /// - \ref soundengine_listeners + AKRESULT GetListeners( + AkGameObjectID in_GameObjectID, ///< Source game object identifier + AkGameObjectID* out_ListenerObjectIDs, ///< Pointer to an array of AkGameObjectID's. Will be populated with the IDs of the listeners of in_GameObjectID. Pass NULL to querry the size required. + AkUInt32& oi_uNumListeners ///< Pass in the the available number of elements in the array 'out_ListenerObjectIDs'. After return, the number of valid elements filled in the array. + ) override; + + /// Get a listener's position. + /// \return AK_Success if succeeded, or AK_InvalidParameter if the index is out of range + /// \sa + /// - \ref soundengine_listeners_settingpos + AKRESULT GetListenerPosition( + AkGameObjectID in_uIndex, ///< Listener index (0: first listener, 7: 8th listener) + AkListenerPosition& out_rPosition ///< Position set + ) override; + + /// Get a listener's spatialization parameters. + /// \return AK_Success if succeeded, or AK_InvalidParameter if the index is out of range + /// \sa + /// - AK::SoundEngine::SetListenerSpatialization(). + /// - \ref soundengine_listeners_spatial + AKRESULT GetListenerSpatialization( + AkUInt32 in_uIndex, ///< Listener index (0: first listener, 7: 8th listener) + bool& out_rbSpatialized, ///< Spatialization enabled + AK::SpeakerVolumes::VectorPtr& out_pVolumeOffsets, ///< Per-speaker vector of volume offsets, in decibels. Use the functions of AK::SpeakerVolumes::Vector to interpret it. + AkChannelConfig& out_channelConfig ///< Channel configuration associated with out_rpVolumeOffsets. + ) override; + + //@} + + + //////////////////////////////////////////////////////////////////////// + /// @name Game Syncs + //@{ + + /// Get the value of a real-time parameter control (by ID) + /// An RTPC can have a any combination of a global value, a unique value for each game object, or a unique value for each playing ID. + /// The value requested is determined by RTPCValue_type, in_gameObjectID and in_playingID. + /// If a value at the requested scope (determined by RTPCValue_type) is not found, the value that is available at the the next broadest scope will be returned, and io_rValueType will be changed to indicate this. + /// \note + /// When looking up RTPC values via playing ID (ie. io_rValueType is RTPC_PlayingID), in_gameObjectID can be set to a specific game object (if it is available to the caller) to use as a fall back value. + /// If the game object is unknown or unavailable, AK_INVALID_GAME_OBJECT can be passed in in_gameObjectID, and the game object will be looked up via in_playingID. + /// However in this case, it is not possible to retrieve a game object value as a fall back value if the playing id does not exist. It is best to pass in the game object if possible. + /// + /// \return AK_Success if succeeded, AK_IDNotFound if the RTPC does not exist + /// \sa + /// - \ref soundengine_rtpc + /// - RTPCValue_type + AKRESULT GetRTPCValue( + AkRtpcID in_rtpcID, ///< ID of the RTPC + AkGameObjectID in_gameObjectID, ///< Associated game object ID, ignored if io_rValueType is RTPCValue_Global. + AkPlayingID in_playingID, ///< Associated playing ID, ignored if io_rValueType is not RTPC_PlayingID. + AkRtpcValue& out_rValue, ///< Value returned + AK::SoundEngine::Query::RTPCValue_type& io_rValueType ///< In/Out value, the user must specify the requested type. The function will return in this variable the type of the returned value. + ) override; + +#ifdef AK_SUPPORT_WCHAR + + /// Get the value of a real-time parameter control (by ID) + /// An RTPC can have a any combination of a global value, a unique value for each game object, or a unique value for each playing ID. + /// The value requested is determined by RTPCValue_type, in_gameObjectID and in_playingID. + /// If a value at the requested scope (determined by RTPCValue_type) is not found, the value that is available at the the next broadest scope will be returned, and io_rValueType will be changed to indicate this. + /// \note + /// When looking up RTPC values via playing ID (ie. io_rValueType is RTPC_PlayingID), in_gameObjectID can be set to a specific game object (if it is available to the caller) to use as a fall back value. + /// If the game object is unknown or unavailable, AK_INVALID_GAME_OBJECT can be passed in in_gameObjectID, and the game object will be looked up via in_playingID. + /// However in this case, it is not possible to retrieve a game object value as a fall back value if the playing id does not exist. It is best to pass in the game object if possible. + /// + /// \return AK_Success if succeeded, AK_IDNotFound if the RTPC does not exist + /// \sa + /// - \ref soundengine_rtpc + /// - RTPCValue_type + AKRESULT GetRTPCValue( + const wchar_t* in_pszRtpcName, ///< String name of the RTPC + AkGameObjectID in_gameObjectID, ///< Associated game object ID, ignored if io_rValueType is RTPCValue_Global. + AkPlayingID in_playingID, ///< Associated playing ID, ignored if io_rValueType is not RTPC_PlayingID. + AkRtpcValue& out_rValue, ///< Value returned + AK::SoundEngine::Query::RTPCValue_type& io_rValueType ///< In/Out value, the user must specify the requested type. The function will return in this variable the type of the returned value. ) override; + ) override; + +#endif //AK_SUPPORT_WCHAR + + /// Get the value of a real-time parameter control (by ID) + /// An RTPC can have a any combination of a global value, a unique value for each game object, or a unique value for each playing ID. + /// The value requested is determined by RTPCValue_type, in_gameObjectID and in_playingID. + /// If a value at the requested scope (determined by RTPCValue_type) is not found, the value that is available at the the next broadest scope will be returned, and io_rValueType will be changed to indicate this. + /// \note + /// When looking up RTPC values via playing ID (ie. io_rValueType is RTPC_PlayingID), in_gameObjectID can be set to a specific game object (if it is available to the caller) to use as a fall back value. + /// If the game object is unknown or unavailable, AK_INVALID_GAME_OBJECT can be passed in in_gameObjectID, and the game object will be looked up via in_playingID. + /// However in this case, it is not possible to retrieve a game object value as a fall back value if the playing id does not exist. It is best to pass in the game object if possible. + /// + /// \return AK_Success if succeeded, AK_IDNotFound if the RTPC does not exist + /// \sa + /// - \ref soundengine_rtpc + /// - RTPCValue_type + AKRESULT GetRTPCValue( + const char* in_pszRtpcName, ///< String name of the RTPC + AkGameObjectID in_gameObjectID, ///< Associated game object ID, ignored if io_rValueType is RTPCValue_Global. + AkPlayingID in_playingID, ///< Associated playing ID, ignored if io_rValueType is not RTPC_PlayingID. + AkRtpcValue& out_rValue, ///< Value returned + AK::SoundEngine::Query::RTPCValue_type& io_rValueType ///< In/Out value, the user must specify the requested type. The function will return in this variable the type of the returned value. ) override; + ) override; + + /// Get the state of a switch group (by IDs). + /// \return AK_Success if succeeded, or AK_IDNotFound if the game object was not registered + /// \sa + /// - \ref soundengine_switch + AKRESULT GetSwitch( + AkSwitchGroupID in_switchGroup, ///< ID of the switch group + AkGameObjectID in_gameObjectID, ///< Associated game object ID + AkSwitchStateID& out_rSwitchState ///< ID of the switch + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Get the state of a switch group. + /// \return AK_Success if succeeded, or AK_IDNotFound if the game object was not registered or the switch group name can not be found + /// \sa + /// - \ref soundengine_switch + AKRESULT GetSwitch( + const wchar_t* in_pstrSwitchGroupName, ///< String name of the switch group + AkGameObjectID in_GameObj, ///< Associated game object ID + AkSwitchStateID& out_rSwitchState ///< ID of the switch + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Get the state of a switch group. + /// \return AK_Success if succeeded, or AK_IDNotFound if the game object was not registered or the switch group name can not be found + /// \sa + /// - \ref soundengine_switch + AKRESULT GetSwitch( + const char* in_pstrSwitchGroupName, ///< String name of the switch group + AkGameObjectID in_GameObj, ///< Associated game object ID + AkSwitchStateID& out_rSwitchState ///< ID of the switch + ) override; + + /// Get the state of a state group (by IDs). + /// \return AK_Success if succeeded + /// \sa + /// - \ref soundengine_states + AKRESULT GetState( + AkStateGroupID in_stateGroup, ///< ID of the state group + AkStateID& out_rState ///< ID of the state + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Get the state of a state group. + /// \return AK_Success if succeeded, or AK_IDNotFound if the state group name can not be found + /// \sa + /// - \ref soundengine_states + AKRESULT GetState( + const wchar_t* in_pstrStateGroupName, ///< String name of the state group + AkStateID& out_rState ///< ID of the state + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Get the state of a state group. + /// \return AK_Success if succeeded, or AK_IDNotFound if the state group name can not be found + /// \sa + /// - \ref soundengine_states + AKRESULT GetState( + const char* in_pstrStateGroupName, ///< String name of the state group + AkStateID& out_rState ///< ID of the state + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Environments + //@{ + + /// Get the environmental ratios used by the specified game object. + /// To clear the game object's environments, in_uNumEnvValues must be 0. + /// \sa + /// - \ref soundengine_environments + /// - \ref soundengine_environments_dynamic_aux_bus_routing + /// - \ref soundengine_environments_id_vs_string + /// \return AK_Success if succeeded, or AK_InvalidParameter if io_ruNumEnvValues is 0 or out_paEnvironmentValues is NULL, or AK_PartialSuccess if more environments exist than io_ruNumEnvValues + /// AK_InvalidParameter + AKRESULT GetGameObjectAuxSendValues( + AkGameObjectID in_gameObjectID, ///< Associated game object ID + AkAuxSendValue* out_paAuxSendValues, ///< Variable-size array of AkAuxSendValue structures + ///< (it may be NULL if no aux send must be set) + AkUInt32& io_ruNumSendValues ///< The number of Auxilliary busses at the pointer's address + ///< (it must be 0 if no aux bus is set) + ) override; + + /// Get the environmental dry level to be used for the specified game object + /// The control value is a number ranging from 0.0f to 1.0f. + /// 0.0f stands for 0% dry, while 1.0f stands for 100% dry. + /// \aknote Reducing the dry level does not mean increasing the wet level. \endaknote + /// \sa + /// - \ref soundengine_environments + /// - \ref soundengine_environments_setting_dry_environment + /// - \ref soundengine_environments_id_vs_string + /// \return AK_Success if succeeded, or AK_IDNotFound if the game object was not registered + AKRESULT GetGameObjectDryLevelValue( + AkGameObjectID in_EmitterID, ///< Associated emitter game object ID + AkGameObjectID in_ListenerID, ///< Associated listener game object ID + AkReal32& out_rfControlValue ///< Dry level control value, ranging from 0.0f to 1.0f + ///< (0.0f stands for 0% dry, while 1.0f stands for 100% dry) + ) override; + + /// Get a game object's obstruction and occlusion levels. + /// \sa + /// - \ref soundengine_obsocc + /// - \ref soundengine_environments + /// \return AK_Success if succeeded, AK_IDNotFound if the game object was not registered + AKRESULT GetObjectObstructionAndOcclusion( + AkGameObjectID in_EmitterID, ///< Associated game object ID + AkGameObjectID in_ListenerID, ///< Listener object ID + AkReal32& out_rfObstructionLevel, ///< ObstructionLevel: [0.0f..1.0f] + AkReal32& out_rfOcclusionLevel ///< OcclusionLevel: [0.0f..1.0f] + ) override; + + //@} + + /// Get the list of audio object IDs associated to an event. + /// \aknote It is possible to call QueryAudioObjectIDs with io_ruNumItems = 0 to get the total size of the + /// structure that should be allocated for out_aObjectInfos. \endaknote + /// \return AK_Success if succeeded, AK_IDNotFound if the eventID cannot be found, AK_InvalidParameter if out_aObjectInfos is NULL while io_ruNumItems > 0 + /// or AK_PartialSuccess if io_ruNumItems was set to 0 to query the number of available items. + AKRESULT QueryAudioObjectIDs( + AkUniqueID in_eventID, ///< Event ID + AkUInt32& io_ruNumItems, ///< Number of items in array provided / Number of items filled in array + AkObjectInfo* out_aObjectInfos ///< Array of AkObjectInfo items to fill + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Get the list of audio object IDs associated to a event name. + /// \aknote It is possible to call QueryAudioObjectIDs with io_ruNumItems = 0 to get the total size of the + /// structure that should be allocated for out_aObjectInfos. \endaknote + /// \return AK_Success if succeeded, AK_IDNotFound if the event name cannot be found, AK_InvalidParameter if out_aObjectInfos is NULL while io_ruNumItems > 0 + /// or AK_PartialSuccess if io_ruNumItems was set to 0 to query the number of available items. + AKRESULT QueryAudioObjectIDs( + const wchar_t* in_pszEventName, ///< Event name + AkUInt32& io_ruNumItems, ///< Number of items in array provided / Number of items filled in array + AkObjectInfo* out_aObjectInfos ///< Array of AkObjectInfo items to fill + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Get the list of audio object IDs associated to an event name. + /// \aknote It is possible to call QueryAudioObjectIDs with io_ruNumItems = 0 to get the total size of the + /// structure that should be allocated for out_aObjectInfos. \endaknote + /// \return AK_Success if succeeded, AK_IDNotFound if the event name cannot be found, AK_InvalidParameter if out_aObjectInfos is NULL while io_ruNumItems > 0 + /// or AK_PartialSuccess if io_ruNumItems was set to 0 to query the number of available items. + AKRESULT QueryAudioObjectIDs( + const char* in_pszEventName, ///< Event name + AkUInt32& io_ruNumItems, ///< Number of items in array provided / Number of items filled in array + AkObjectInfo* out_aObjectInfos ///< Array of AkObjectInfo items to fill + ) override; + + /// Get positioning information associated to an audio object. + /// \return AK_Success if succeeded, AK_IDNotFound if the object ID cannot be found, AK_NotCompatible if the audio object cannot expose positioning + AKRESULT GetPositioningInfo( + AkUniqueID in_ObjectID, ///< Audio object ID + AkPositioningInfo& out_rPositioningInfo ///< Positioning information structure to be filled + ) override; + + /// Fill the provided list with all the game object IDs that are currently active in the sound engine. + /// The function may be used to avoid updating game objects positions that are not required at the moment. + /// After calling this function, the list will contain the list of all game objects that are currently active in the sound engine. + /// Being active means that either a sound is playing or pending to be played using this game object. + /// \sa + /// - AkGameObjectsList + AKRESULT GetActiveGameObjects( + FAkGameObjectsList& io_GameObjectList ///< returned list of active game objects. + ) override; + + /// Query if the specified game object is currently active. + /// Being active means that either a sound is playing or pending to be played using this game object. + bool GetIsGameObjectActive( + AkGameObjectID in_GameObjId ///< Game object ID + ) override; + + /// Returns the maximum distance used in attenuations associated to all sounds currently playing. + /// This may be used for example by the game to know if some processing need to be performed on the game side, that would not be required + /// if the object is out of reach anyway. + /// + /// Example usage: + /// \code + /// /*******************************************************/ + /// AkRadiusList RadLst; //creating the list( array ). + /// // Do not reserve any size for the array, + /// // the system will reserve the correct size. + /// + /// GetMaxRadius( RadLst ) override; + /// // Use the content of the list + /// (...) + /// + /// RadLst.Term() override;// the user is responsible to free the memory allocated + /// /*******************************************************/ + /// \endcode + /// + /// \aknote The returned value is NOT the distance from a listener to an object but + /// the maximum attenuation distance of all sounds playing on this object. This is + /// not related in any way to the curent 3D position of the object. \endaknote + /// + /// \return + /// - AK_Success if succeeded + /// - AK_InsuficientMemory if there was not enough memory + /// + /// \aknote + /// The Scaling factor (if one was specified on the game object) is included in the return value. + /// The Scaling factor is not updated once a sound starts playing since it + /// is computed only when the playback starts with the initial scaling factor of this game object. Scaling factor will + /// be re-computed for every playback instance, always using the scaling factor available at this time. + /// \endaknote + /// + /// \sa + /// - AkRadiusList + AKRESULT GetMaxRadius( + FAkRadiusList& io_RadiusList ///< List that will be filled with AK::SoundEngine::Query::GameObjDst objects. + ) override; + + /// Returns the maximum distance used in attenuations associated to sounds playing using the specified game object. + /// This may be used for example by the game to know if some processing need to be performed on the game side, that would not be required + /// if the object is out of reach anyway. + /// + /// \aknote The returned value is NOT the distance from a listener to an object but the maximum attenuation distance of all sounds playing on this object. \endaknote + /// + /// \return + /// - A negative number if the game object specified is not playing. + /// - 0, if the game object was only associated to sounds playing using no distance attenuation. + /// - A positive number represents the maximum of all the distance attenuations playing on this game object. + /// + /// \aknote + /// The Scaling factor (if one was specified on the game object) is included in the return value. + /// The Scaling factor is not updated once a sound starts playing since it + /// is computed only when the playback starts with the initial scaling factor of this game object. Scaling factor will + /// be re-computed for every playback instance, always using the scaling factor available at this time. + /// \endaknote + /// + /// \sa + /// - \ref AK::SoundEngine::SetScalingFactor + AkReal32 GetMaxRadius( + AkGameObjectID in_GameObjId ///< Game object ID + ) override; + + /// Get the Event ID associated to the specified PlayingID. + /// This function does not acquire the main audio lock. + /// + /// \return AK_INVALID_UNIQUE_ID on failure. + AkUniqueID GetEventIDFromPlayingID( + AkPlayingID in_playingID ///< Associated PlayingID + ) override; + + /// Get the ObjectID associated to the specified PlayingID. + /// This function does not acquire the main audio lock. + /// + /// \return AK_INVALID_GAME_OBJECT on failure. + AkGameObjectID GetGameObjectFromPlayingID( + AkPlayingID in_playingID ///< Associated PlayingID + ) override; + + /// Get the list PlayingIDs associated with the given game object. + /// This function does not acquire the main audio lock. + /// + /// \aknote It is possible to call GetPlayingIDsFromGameObject with io_ruNumItems = 0 to get the total size of the + /// structure that should be allocated for out_aPlayingIDs. \endaknote + /// \return AK_Success if succeeded, AK_InvalidParameter if out_aPlayingIDs is NULL while io_ruNumItems > 0 + AKRESULT GetPlayingIDsFromGameObject( + AkGameObjectID in_GameObjId, ///< Game object ID + AkUInt32& io_ruNumIDs, ///< Number of items in array provided / Number of items filled in array + AkPlayingID* out_aPlayingIDs ///< Array of AkPlayingID items to fill + ) override; + + /// Get the value of a custom property of integer or boolean type. + /// \return AK_PartialSuccess if the object was found but no matching custom property was found on this object. Note that it could mean this value is the default value. + AKRESULT GetCustomPropertyValue( + AkUniqueID in_ObjectID, ///< Object ID, this is the 32bit ShortID of the AudioFileSource or Sound object found in the .wwu XML file. At runtime it can only be retrieved by the AK_Duration callback when registered with PostEvent(), or by calling Query::QueryAudioObjectIDs() to get all the shortIDs associated with an event. + AkUInt32 in_uPropID, ///< Property ID of your custom property found under the Custom Properties tab of the Wwise project settings. + AkInt32& out_iValue ///< Property Value + ) override; + + /// Get the value of a custom property of real type. + /// \return AK_PartialSuccess if the object was found but no matching custom property was found on this object. Note that it could mean this value is the default value. + AKRESULT GetCustomPropertyValue( + AkUniqueID in_ObjectID, ///< Object ID, this is the 32bit ShortID of the AudioFileSource or Sound object found in the .wwu XML file. At runtime it can only be retrieved by the AK_Duration callback when registered with PostEvent(), or by calling Query::QueryAudioObjectIDs() to get all the shortIDs associated with an event. + AkUInt32 in_uPropID, ///< Property ID of your custom property found under the Custom Properties tab of the Wwise project settings. + AkReal32& out_fValue ///< Property Value + ) override; + }; + + class WWISESOUNDENGINE_API FAudioInputPlugin : public IAudioInputPlugin + { + public: + UE_NONCOPYABLE(FAudioInputPlugin); + FAudioInputPlugin() = default; + + void SetAudioInputCallbacks( + AkAudioInputPluginExecuteCallbackFunc in_pfnExecCallback, + AkAudioInputPluginGetFormatCallbackFunc in_pfnGetFormatCallback = nullptr, // Optional + AkAudioInputPluginGetGainCallbackFunc in_pfnGetGainCallback = nullptr // Optional + ) override; + }; + +#if WITH_EDITORONLY_DATA + class WWISESOUNDENGINE_API FErrorTranslator : public AkErrorMessageTranslator + { + FGetInfoErrorMessageTranslatorFunction GetInfoErrorMessageTranslatorFunction; + + public: + + FErrorTranslator(FGetInfoErrorMessageTranslatorFunction InMessageTranslatorFunction); + virtual void Term() override {}; + virtual bool GetInfo(TagInformation* in_pTagList, AkUInt32 in_uCount, AkUInt32& out_uTranslated) override; + }; +#endif + + AkErrorMessageTranslator* NewErrorMessageTranslator(FGetInfoErrorMessageTranslatorFunction InMessageTranslatorFunction) override; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwiseSpatialAudioAPI_2022_1.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwiseSpatialAudioAPI_2022_1.h new file mode 100644 index 0000000..fecd706 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwiseSpatialAudioAPI_2022_1.h @@ -0,0 +1,427 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/WwiseSpatialAudioAPI.h" + +class WWISESOUNDENGINE_API FWwiseSpatialAudioAPI_2022_1 : public IWwiseSpatialAudioAPI +{ +public: + UE_NONCOPYABLE(FWwiseSpatialAudioAPI_2022_1); + FWwiseSpatialAudioAPI_2022_1(); + + //////////////////////////////////////////////////////////////////////// + /// @name Basic functions. + /// In order to use SpatialAudio, you need to initalize it using Init, and register the listeners that you plan on using with any of the services offered by SpatialAudio, using + /// RegisterListener respectively, _after_ having registered their corresponding game object to the sound engine. + /// \akwarning At the moment, there can be only one Spatial Audio listener registered at any given time. + //@{ + + /// Initialize the SpatialAudio API. + AKRESULT Init(const AkSpatialAudioInitSettings& in_initSettings) override; + + /// Assign a game object as the Spatial Audio listener. There can be only one Spatial Audio listener registered at any given time; in_gameObjectID will replace any previously set Spatial Audio listener. + /// The game object passed in must be registered by the client, at some point, for sound to be heard. It is not necessary to be registered at the time of calling this function. + /// If not listener is explicitly registered to spatial audio, then a default listener (set via \c AK::SoundEngine::SetDefaultListeners()) is selected. If the are no default listeners, or there are more than one + /// default listeners, then it is necessary to call RegisterListener() to specify which listener to use with Spatial Audio. + AKRESULT RegisterListener( + AkGameObjectID in_gameObjectID ///< Game object ID + ) override; + + /// Unregister a game object as a listener in the SpatialAudio API; clean up Spatial Audio listener data associated with in_gameObjectID. + /// If in_gameObjectID is the current registered listener, calling this function will clear the Spatial Audio listener and + /// Spatial Audio features will be disabled until another listener is registered. + /// This function is optional - listener are automatically unregistered when their game object is deleted in the sound engine. + /// \sa + /// - \ref AK::SpatialAudio::RegisterListener + AKRESULT UnregisterListener( + AkGameObjectID in_gameObjectID ///< Game object ID + ) override; + + /// Define a inner and outer radius around each sound position for a specified game object. + /// The radii are used in spread and distance calculations, simulating a radial sound source. + /// When applying attenuation curves, the distance between the listener and the inner sphere (defined by the sound position and \c in_innerRadius) is used. + /// The spread for each sound position is calculated as follows: + /// - If the listener is outside the outer radius, then the spread is defined by the area that the sphere takes in the listener field of view. Specifically, this angle is calculated as 2.0*asinf( \c in_outerRadius / distance ), where distance is the distance between the listener and the sound position. + /// - When the listener intersects the outer radius (the listener is exactly \c in_outerRadius units away from the sound position), the spread is exactly 50%. + /// - When the listener is in between the inner and outer radius, the spread interpolates linearly from 50% to 100% as the listener transitions from the outer radius towards the inner radius. + /// - If the listener is inside the inner radius, the spread is 100%. + /// \aknote Transmission and diffraction calculations in Spatial Audio always use the center of the sphere (the position(s) passed into \c AK::SoundEngine::SetPosition or \c AK::SoundEngine::SetMultiplePositions) for raycasting. + /// To obtain accurate diffraction and transmission calculations for radial sources, where different parts of the volume may take different paths through or around geometry, + /// it is necessary to pass multiple sound positions into \c AK::SoundEngine::SetMultiplePositions to allow the engine to 'sample' the area at different points. + /// - \ref AK::SoundEngine::SetPosition + /// - \ref AK::SoundEngine::SetMultiplePositions + AKRESULT SetGameObjectRadius( + AkGameObjectID in_gameObjectID, ///< Game object ID + AkReal32 in_outerRadius, ///< Outer radius around each sound position, defining 50% spread. Must satisfy \c in_innerRadius <= \c in outerRadius. + AkReal32 in_innerRadius ///< Inner radius around each sound position, defining 100% spread and 0 attenuation distance. Must satisfy \c in_innerRadius <= \c in outerRadius. + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Helper functions for passing game data to the Reflect plug-in. + /// Use this API for detailed placement of reflection image sources. + /// \aknote These functions are low-level and useful when your game engine already implements a geometrical approach to sound propagation such as an image-source or a ray tracing algorithm. + /// Functions of Geometry are preferred and easier to use with the Reflect plug-in. \endaknote + //@{ + + /// Add or update an individual image source for processing via the AkReflect plug-in. Use this API for detailed placement of + /// reflection image sources, whose positions have been determined by the client, such as from the results of a ray cast, computation or by manual placement. One possible + /// use case is generating reflections that originate far enough away that they can be modeled as a static point source, for example, off of a distant mountain. + /// The SpatialAudio API manages image sources added via SetImageSource() and sends them to the AkReflect plug-in that is on the aux bus with ID \c in_AuxBusID. + /// The image source applies all game objects that have a reflections aux send defined in the authoring tool, or only to a specific game object if \c in_gameObjectID is used. + /// \aknote The \c AkImageSourceSettings struct passed in \c in_info must contain a unique image source ID to be able to identify this image source across frames and when updating and/or removing it later. + /// Each instance of AkReflect has its own set of data, so you may reuse ID, if desired, as long as \c in_gameObjectID and \c in_AuxBusID are different. + /// \aknote It is possible for the AkReflect plugin to process reflections from both \c SetImageSource and the geometric reflections API on the same aux bus and game object, but be aware that image source ID collisions are possible. + /// The image source IDs used by the geometric reflections API are generated from hashed data that uniquely identifies the reflecting surfaces. If a collision occurs, one of the reflections will not be heard. + /// While collision are rare, to ensure that it never occurs use an aux bus for \c SetImageSource that is unique from the aux bus(es) defined in the authoring tool, and from those passed to \c SetEarlyReflectionsAuxSend. + /// \endaknote + /// \aknote For proper operation with AkReflect and the SpatialAudio API, any aux bus using AkReflect should have 'Listener Relative Routing' checked and the 3D Spatialization set to None in the Wwise authoring tool. See \ref spatial_audio_wwiseprojectsetup_businstances for more details. \endaknote + /// \sa + /// - \ref AK::SpatialAudio::RemoveImageSource + /// - \ref AK::SpatialAudio::ClearImageSources + /// - \ref AK::SpatialAudio::SetGameObjectInRoom + /// - \ref AK::SpatialAudio::SetEarlyReflectionsAuxSend + AKRESULT SetImageSource( + AkImageSourceID in_srcID, ///< The ID of the image source being added. + const AkImageSourceSettings& in_info, ///< Image source information. + const char* in_name, ///< Name given to image source, can be used to identify the image source in the AK Reflect plugin UI. + AkUniqueID in_AuxBusID = AK_INVALID_AUX_ID, ///< Aux bus that has the AkReflect plug in for early reflection DSP. + ///< Pass AK_INVALID_AUX_ID to use the reflections aux bus defined in the authoring tool. + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT ///< The ID of the emitter game object to which the image source applies. + ///< Pass AK_INVALID_GAME_OBJECT to apply to all game objects that have a reflections aux bus assigned in the authoring tool. + ) override; + + /// Remove an individual reflection image source that was previously added via \c SetImageSource. + /// \sa + /// - \ref AK::SpatialAudio::SetImageSource + /// - \ref AK::SpatialAudio::ClearImageSources + AKRESULT RemoveImageSource( + AkImageSourceID in_srcID, ///< The ID of the image source to remove. + AkUniqueID in_AuxBusID = AK_INVALID_AUX_ID, ///< Aux bus that was passed to SetImageSource. + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT ///< Game object ID that was passed to SetImageSource. + ) override; + + /// Remove all image sources matching \c in_AuxBusID and \c in_gameObjectID that were previously added via \c SetImageSource. + /// Both \c in_AuxBusID and \c in_gameObjectID can be treated as wild cards matching all aux buses and/or all game object, by passing \c AK_INVALID_AUX_ID and/or \c AK_INVALID_GAME_OBJECT, respectively. + /// \sa + /// - \ref AK::SpatialAudio::SetImageSource + /// - \ref AK::SpatialAudio::RemoveImageSource + AKRESULT ClearImageSources( + AkUniqueID in_AuxBusID = AK_INVALID_AUX_ID, ///< Aux bus that was passed to SetImageSource, or AK_INVALID_AUX_ID to match all aux buses. + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT ///< Game object ID that was passed to SetImageSource, or AK_INVALID_GAME_OBJECT to match all game objects. + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Geometry + /// Geometry API for early reflection processing using Reflect. + //@{ + + /// Add or update a set of geometry from the \c SpatialAudio module for geometric reflection and diffraction processing. A geometry set is a logical set of vertices, triangles, and acoustic surfaces, + /// which are referenced by the same \c AkGeometrySetID. The ID (\c in_GeomSetID) must be unique and is also chosen by the client in a manner similar to \c AkGameObjectID's. + /// It is necessary to create at least one geometry instance for each geometry set that is to be used for diffraction and reflection simulation. + /// \sa + /// - \ref AkGeometryParams + /// - \ref AK::SpatialAudio::SetGeometryInstance + /// - \ref AK::SpatialAudio::RemoveGeometry + AKRESULT SetGeometry( + AkGeometrySetID in_GeomSetID, ///< Unique geometry set ID, chosen by client. + const AkGeometryParams& in_params ///< Geometry parameters to set. + ) override; + + /// Remove a set of geometry to the SpatialAudio API. + /// Calling \c AK::SpatialAudio::RemoveGeometry will remove all instances of the geometry from the scene. + /// \sa + /// - \ref AK::SpatialAudio::SetGeometry + AKRESULT RemoveGeometry( + AkGeometrySetID in_SetID ///< ID of geometry set to be removed. + ) override; + + /// Add or update a geometry instance from the \c SpatialAudio module for geometric reflection and diffraction processing. + /// A geometry instance is a unique instance of a geometry set with a specified transform (position, rotation and scale). + /// It is necessary to create at least one geometry instance for each geometry set that is to be used for diffraction and reflection simulation. + /// The ID (\c in_GeomSetInstanceID) must be unique amongst all geometry instances, including geometry instances referencing different geometry sets. The ID is chosen by the client in a manner similar to \c AkGameObjectID's. + /// To update the transform of an existing geometry instance, call SetGeometryInstance again, passing the same \c AkGeometryInstanceID, with the updated transform. + /// \sa + /// - \ref AkGeometryInstanceParams + /// - \ref AK::SpatialAudio::RemoveGeometryInstance + AKRESULT SetGeometryInstance( + AkGeometryInstanceID in_GeometryInstanceID, ///< Unique geometry set instance ID, chosen by client. + const AkGeometryInstanceParams& in_params ///< Geometry instance parameters to set. + ) override; + + /// Remove a geometry instance from the SpatialAudio API. + /// \sa + /// - \ref AK::SpatialAudio::SetGeometryInstance + AKRESULT RemoveGeometryInstance( + AkGeometryInstanceID in_GeometryInstanceID ///< ID of geometry set instance to be removed. + ) override; + + /// Query information about the reflection paths that have been calculated via geometric reflection processing in the SpatialAudio API. This function can be used for debugging purposes. + /// This function must acquire the global sound engine lock and therefore, may block waiting for the lock. + /// \sa + /// - \ref AkReflectionPathInfo + AKRESULT QueryReflectionPaths( + AkGameObjectID in_gameObjectID, ///< The ID of the game object that the client wishes to query. + AkUInt32 in_positionIndex, ///< The index of the associated game object position. + AkVector64& out_listenerPos, ///< Returns the position of the listener game object that is associated with the game object \c in_gameObjectID. + AkVector64& out_emitterPos, ///< Returns the position of the emitter game object \c in_gameObjectID. + AkReflectionPathInfo* out_aPaths, ///< Pointer to an array of \c AkReflectionPathInfo's which will be filled after returning. + AkUInt32& io_uArraySize ///< The number of slots in \c out_aPaths, after returning the number of valid elements written. + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Rooms and Portals + /// Sound Propagation API using rooms and portals. + //@{ + + /// Add or update a room. Rooms are used to connect portals and define an orientation for oriented reverbs. This function may be called multiple times with the same ID to update the parameters of the room. + /// \akwarning The ID (\c in_RoomID) must be chosen in the same manner as \c AkGameObjectID's, as they are in the same ID-space. The spatial audio lib manages the + /// registration/unregistration of internal game objects for rooms that use these IDs and, therefore, must not collide. + /// Also, the room ID must not be in the reserved range (AkUInt64)(-32) to (AkUInt64)(-2) inclusively. You may, however, explicitly add the default room ID AK::SpatialAudio::kOutdoorRoomID (-1) + /// in order to customize its AkRoomParams, to provide a valid auxiliary bus, for example.\endakwarning + /// \sa + /// - \ref AkRoomID + /// - \ref AkRoomParams + /// - \ref AK::SpatialAudio::RemoveRoom + AKRESULT SetRoom( + AkRoomID in_RoomID, ///< Unique room ID, chosen by the client. + const AkRoomParams& in_Params, ///< Parameter for the room. + const char* in_RoomName = nullptr ///< Name used to identify the room (optional) + ) override; + + /// Remove a room. + /// \sa + /// - \ref AkRoomID + /// - \ref AK::SpatialAudio::SetRoom + AKRESULT RemoveRoom( + AkRoomID in_RoomID ///< Room ID that was passed to \c SetRoom. + ) override; + + /// Add or update an acoustic portal. A portal is an opening that connects two or more rooms to simulate the transmission of reverberated (indirect) sound between the rooms. + /// This function may be called multiple times with the same ID to update the parameters of the portal. The ID (\c in_PortalID) must be chosen in the same manner as \c AkGameObjectID's, + /// as they are in the same ID-space. The spatial audio lib manages the registration/unregistration of internal game objects for portals that use these IDs, and therefore must not collide. + /// \sa + /// - \ref AkPortalID + /// - \ref AkPortalParams + /// - \ref AK::SpatialAudio::RemovePortal + AKRESULT SetPortal( + AkPortalID in_PortalID, ///< Unique portal ID, chosen by the client. + const AkPortalParams& in_Params, ///< Parameter for the portal. + const char* in_PortalName = nullptr ///< Name used to identify portal (optional) + ) override; + + /// Remove a portal. + /// \sa + /// - \ref AkPortalID + /// - \ref AK::SpatialAudio::SetPortal + AKRESULT RemovePortal( + AkPortalID in_PortalID ///< ID of portal to be removed, which was originally passed to SetPortal. + ) override; + + /// Set the room that the game object is currently located in - usually the result of a containment test performed by the client. The room must have been registered with \c SetRoom. + /// Setting the room for a game object provides the basis for the sound propagation service, and also sets which room's reverb aux bus to send to. The sound propagation service traces the path + /// of the sound from the emitter to the listener, and calculates the diffraction as the sound passes through each portal. The portals are used to define the spatial location of the diffracted and reverberated + /// audio. + /// \sa + /// - \ref AK::SpatialAudio::SetRoom + /// - \ref AK::SpatialAudio::RemoveRoom + AKRESULT SetGameObjectInRoom( + AkGameObjectID in_gameObjectID, ///< Game object ID + AkRoomID in_CurrentRoomID ///< RoomID that was passed to \c AK::SpatialAudio::SetRoom + ) override; + + /// Set the early reflections order for reflection calculation. The reflections order indicates the number of times sound can bounce off of a surface. + /// A higher number requires more CPU resources but results in denser early reflections. Set to 0 to globally disable reflections processing. + AKRESULT SetReflectionsOrder( + AkUInt32 in_uReflectionsOrder, ///< Number of reflections to calculate. Valid range [0,4] + bool in_bUpdatePaths ///< Set to true to clear existing higher-order paths and to force the re-computation of new paths. If false, existing paths will remain and new paths will be computed when the emitter or listener moves. + ) override; + + /// Set the diffraction order for geometric path calculation. The diffraction order indicates the number of edges a sound can diffract around. + /// A higher number requires more CPU resources but results in paths found around more complex geometry. Set to 0 to globally disable geometric diffraction processing. + /// \sa + /// - \ref AkSpatialAudioInitSettings::uMaxDiffractionOrder + AKRESULT SetDiffractionOrder( + AkUInt32 in_uDiffractionOrder, ///< Number of diffraction edges to consider in path calculations. Valid range [0,8] + bool in_bUpdatePaths ///< Set to true to clear existing diffraction paths and to force the re-computation of new paths. If false, existing paths will remain and new paths will be computed when the emitter or listener moves. + ) override; + + /// Set the number of rays cast from the listener by the stochastic ray casting engine. + /// A higher number requires more CPU resources but provides more accurate results. Default value (100) should be good for most applications. + /// + AKRESULT SetNumberOfPrimaryRays( + AkUInt32 in_uNbPrimaryRays ///< Number of rays cast from the listener + ) override; + + /// Set the number of frames on which the path validation phase will be spread. Value between [1..[ + /// High values delay the validation of paths. A value of 1 indicates no spread at all. + /// + AKRESULT SetLoadBalancingSpread( + AkUInt32 in_uNbFrames ///< Number of spread frames + ) override; + + /// Set an early reflections auxiliary bus for a particular game object. + /// Geometrical reflection calculation inside spatial audio is enabled for a game object if any sound playing on the game object has a valid early reflections aux bus specified in the authoring tool, + /// or if an aux bus is specified via \c SetEarlyReflectionsAuxSend. + /// The \c in_auxBusID parameter of SetEarlyReflectionsAuxSend applies to sounds playing on the game object \c in_gameObjectID which have not specified an early reflection bus in the authoring tool - + /// the parameter specified on individual sounds' reflection bus takes priority over the value passed in to \c SetEarlyReflectionsAuxSend. + /// \aknote + /// Users may apply this function to avoid duplicating sounds in the actor-mixer hierarchy solely for the sake of specifying a unique early reflection bus, or in any situation where the same + /// sound should be played on different game objects with different early reflection aux buses (the early reflection bus must be left blank in the authoring tool if the user intends to specify it through the API). \endaknote + AKRESULT SetEarlyReflectionsAuxSend( + AkGameObjectID in_gameObjectID, ///< Game object ID + AkAuxBusID in_auxBusID ///< Auxiliary bus ID. Applies only to sounds which have not specified an early reflection bus in the authoring tool. Pass \c AK_INVALID_AUX_ID to set only the send volume. + ) override; + + /// Set an early reflections send volume for a particular game object. + /// The \c in_fSendVolume parameter is used to control the volume of the early reflections send. It is combined with the early reflections volume specified in the authoring tool, and is applied to all sounds + /// playing on the game object. + /// Setting \c in_fSendVolume to 0.f will disable all reflection processing for this game object. + AKRESULT SetEarlyReflectionsVolume( + AkGameObjectID in_gameObjectID, ///< Game object ID + AkReal32 in_fSendVolume ///< Send volume (linear) for auxiliary send. Set 0.f to disable reflection processing. Valid range 0.f-1.f. + ) override; + + /// Set the obstruction and occlusion value for a portal that has been registered with Spatial Audio. + /// Portal obstruction is used to simulate objects between the portal and the listener that are obstructing the sound coming from the portal. + /// The obstruction value affects only the portals dry path, and should relate to how much of the opening + /// is obstructed, and must be calculated by the client. It is applied to the room's game object, as well as to all the emitters virtual positions + /// which propagate from that room through this portal. + /// Portal occlusion is applied only on the room game object, and affects both the wet and dry path of the signal emitted from the room's bus. + AKRESULT SetPortalObstructionAndOcclusion( + AkPortalID in_PortalID, ///< Portal ID. + AkReal32 in_fObstruction, ///< Obstruction value. Valid range 0.f-1.f + AkReal32 in_fOcclusion ///< Occlusion value. Valid range 0.f-1.f + ) override; + + /// Set the obstruction value of the path between a game object and a portal that has been created by Spatial Audio. + /// The obstruction value is applied on one of the path segments of the sound between the emitter and the listener. + /// Diffraction must be enabled on the sound for a diffraction path to be created. + /// Also, there should not be any portals between the provided game object and portal ID parameters. + /// The obstruction value is used to simulate objects between the portal and the game object that are obstructing the sound. + /// Send an obstruction value of 0 to ensure the value is removed from the internal data structure. + AKRESULT SetGameObjectToPortalObstruction( + AkGameObjectID in_gameObjectID, ///< Game object ID + AkPortalID in_PortalID, ///< Portal ID + AkReal32 in_fObstruction ///< Obstruction value. Valid range 0.f-1.f + ) override; + + /// Set the obstruction value of the path between two portals that has been created by Spatial Audio. + /// The obstruction value is applied on one of the path segments of the sound between the emitter and the listener. + /// Diffraction must be enabled on the sound for a diffraction path to be created. + /// Also, there should not be any portals between the two provided ID parameters. + /// The obstruction value is used to simulate objects between the portals that are obstructing the sound. + /// Send an obstruction value of 0 to ensure the value is removed from the internal data structure. + AKRESULT SetPortalToPortalObstruction( + AkPortalID in_PortalID0, ///< Portal ID + AkPortalID in_PortalID1, ///< Portal ID + AkReal32 in_fObstruction ///< Obstruction value. Valid range 0.f-1.f + ) override; + + /// Query information about the wet diffraction amount for the portal \c in_portal, returned as a normalized value \c out_wetDiffraction in the range [0,1]. + /// The wet diffraction is calculated from how far into the 'shadow region' the listener is from the portal. Unlike dry diffraction, the + /// wet diffraction does not depend on the incident angle, but only the normal of the portal. + /// This value is applied by spatial audio, to the Diffraction value and built-in game parameter of the room game object that is + /// on the other side of the portal (relative to the listener). + /// This function must acquire the global sound engine lock and therefore, may block waiting for the lock. + /// \sa + /// - \ref AkSpatialAudioInitSettings + AKRESULT QueryWetDiffraction( + AkPortalID in_portal, ///< The ID of the game object that the client wishes to query. + AkReal32& out_wetDiffraction ///< The number of slots in \c out_aPaths, after returning the number of valid elements written. + ) override; + + /// Query information about the diffraction state for a particular listener and emitter, which has been calculated using the data provided via the spatial audio emitter API. This function can be used for debugging purposes. + /// Returned in \c out_aPaths, this array contains the sound paths calculated from diffraction around a geometric edge and/or diffraction through portals connecting rooms. + /// No paths will be returned in any of the following conditions: (1) the emitter game object has a direct line of sight to the listener game object, (2) the emitter and listener are in the same room, and the listener is completely outside the radius of the emitter, + /// or (3) The emitter and listener are in different rooms, but there are no paths found via portals between the emitter and the listener. + /// A single path with zero diffraction nodes is returned when all of the following conditions are met: (1) the emitter and listener are in the same room, (2) there is no direct line of sight, and (3) either the Voice's Attenuation's curve max distance is exceeded or the accumulated diffraction coefficient exceeds 1.0. + /// This function must acquire the global sound engine lock and, therefore, may block waiting for the lock. + /// \sa + /// - \ref AkDiffractionPathInfo + AKRESULT QueryDiffractionPaths( + AkGameObjectID in_gameObjectID, ///< The ID of the game object that the client wishes to query. + AkUInt32 in_positionIndex, ///< The index of the associated game object position. + AkVector64& out_listenerPos, ///< Returns the position of the listener game object that is associated with the game object \c in_gameObjectID. + AkVector64& out_emitterPos, ///< Returns the position of the emitter game object \c in_gameObjectID. + AkDiffractionPathInfo* out_aPaths, ///< Pointer to an array of \c AkDiffractionPathInfo's which will be filled on return. + AkUInt32& io_uArraySize ///< The number of slots in \c out_aPaths, after returning the number of valid elements written. + ) override; + + /// Reset the stochastic engine state by re-initializing the random seeds. + /// + AKRESULT ResetStochasticEngine() override; + + //@} + + class WWISESOUNDENGINE_API FReverbEstimation : public IReverbEstimation + { + public: + UE_NONCOPYABLE(FReverbEstimation); + FReverbEstimation() = default; + + //////////////////////////////////////////////////////////////////////// + /// @name Reverb estimation. + /// These functions can be used to estimate the reverb parameters of a physical environment, using its volume and surface area + //@{ + + /// This is used to estimate the line of best fit through the absorption values of an Acoustic Texture. + /// This value is what's known as the HFDamping. + /// return Gradient of line of best fit through y = mx + c. + float CalculateSlope(const AkAcousticTexture& texture) override; + + /// Calculate average absorption values using each of the textures in in_textures, weighted by their corresponding surface area. + void GetAverageAbsorptionValues(AkAcousticTexture* in_textures, float* in_surfaceAreas, int in_numTextures, AkAcousticTexture& out_average) override; + + /// Estimate the time taken (in seconds) for the sound reverberation in a physical environment to decay by 60 dB. + /// This is estimated using the Sabine equation. The T60 decay time can be used to drive the decay parameter of a reverb effect. + AKRESULT EstimateT60Decay( + AkReal32 in_volumeCubicMeters, ///< The volume (in cubic meters) of the physical environment. 0 volume or negative volume will give a decay estimate of 0. + AkReal32 in_surfaceAreaSquaredMeters, ///< The surface area (in squared meters) of the physical environment. Must be >= AK_SA_MIN_ENVIRONMENT_SURFACE_AREA + AkReal32 in_environmentAverageAbsorption, ///< The average absorption amount of the surfaces in the environment. Must be between AK_SA_MIN_ENVIRONMENT_ABSORPTION and 1. + AkReal32& out_decayEstimate ///< Returns the time taken (in seconds) for the reverberation to decay bu 60 dB. + ) override; + + /// Estimate the time taken (in milliseconds) for the first reflection to reach the listener. + /// This assumes the emitter and listener are both positioned in the centre of the environment. + AKRESULT EstimateTimeToFirstReflection( + AkVector in_environmentExtentMeters, ///< Defines the dimensions of the environment (in meters) relative to the center; all components must be positive numbers. + AkReal32& out_timeToFirstReflectionMs, ///< Returns the time taken (in milliseconds) for the first reflection to reach the listener. + AkReal32 in_speedOfSound = 343.0f ///< Defaults to 343.0 - the speed of sound in dry air. Must be > 0. + ) override; + + /// Estimate the high frequency damping from a collection of AkAcousticTextures and corresponding surface areas. + /// The high frequency damping is a measure of how much high frequencies are dampened compared to low frequencies. > 0 indicates more high frequency damping than low frequency damping. < 0 indicates more low frequency damping than high frequency damping. 0 indicates uniform damping. + /// The average absorption values are calculated using each of the textures in the collection, weighted by their corresponding surface area. + /// The HFDamping is then calculated as the line-of-best-fit through the average absorption values. + AKRESULT EstimateHFDamping( + AkAcousticTexture* in_textures, ///< A collection of AkAcousticTexture structs from which to calculate the average high frequency damping. + float* in_surfaceAreas, ///< Surface area values for each of the textures in in_textures. + int in_numTextures, ///< The number of textures in in_textures (and the number of surface area values in in_surfaceAreas). + AkReal32& out_hfDamping ///< Returns the high frequency damping value. > 0 indicates more high frequency damping than low frequency damping. < 0 indicates more low frequency damping than high frequency damping. 0 indicates uniform damping. + ) override; + + //@} + }; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwiseStreamMgrAPI_2022_1.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwiseStreamMgrAPI_2022_1.h new file mode 100644 index 0000000..4599798 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/API_2022_1/WwiseStreamMgrAPI_2022_1.h @@ -0,0 +1,180 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/WwiseStreamMgrAPI.h" + +class WWISESOUNDENGINE_API FWwiseStreamMgrAPI_2022_1 : public IWwiseStreamMgrAPI +{ +public: + UE_NONCOPYABLE(FWwiseStreamMgrAPI_2022_1); + FWwiseStreamMgrAPI_2022_1() = default; + + AK::IAkStreamMgr* GetAkStreamMgrInstance() override; + + /// \name Audiokinetic implementation-specific Stream Manager factory. + //@{ + /// Stream Manager factory. + /// \remarks + /// - In order for the Stream Manager to work properly, you also need to create + /// at least one streaming device (and implement its I/O hook), and register the + /// File Location Resolver with AK::StreamMgr::SetFileLocationResolver(). + /// - Use AK::StreamMgr::GetDefaultSettings(), then modify the settings you want, + /// then feed this function with them. + /// \sa + /// - AK::IAkStreamMgr + /// - AK::StreamMgr::SetFileLocationResolver() + /// - AK::StreamMgr::GetDefaultSettings() + AK::IAkStreamMgr* Create( + const AkStreamMgrSettings& in_settings ///< Stream manager initialization settings. + ) override; + + /// Get the default values for the Stream Manager's settings. + /// \sa + /// - AK::StreamMgr::Create() + /// - AkStreamMgrSettings + /// - \ref streamingmanager_settings + void GetDefaultSettings( + AkStreamMgrSettings& out_settings ///< Returned AkStreamMgrSettings structure with default values. + ) override; + + /// Get the one and only File Location Resolver registered to the Stream Manager. + /// \sa + /// - AK::StreamMgr::IAkFileLocationResolver + /// - AK::StreamMgr::SetFileLocationResolver() + AK::StreamMgr::IAkFileLocationResolver* GetFileLocationResolver() override; + + /// Register the one and only File Location Resolver to the Stream Manager. + /// \sa + /// - AK::StreamMgr::IAkFileLocationResolver + void SetFileLocationResolver( + AK::StreamMgr::IAkFileLocationResolver* in_pFileLocationResolver ///< Interface to your File Location Resolver + ) override; + + //@} + + /// \name Stream Manager: High-level I/O devices management. + //@{ + /// Streaming device creation. + /// Creates a high-level device, with specific settings. + /// You need to provide the associated low-level I/O hook, implemented on your side. + /// \return The device ID. AK_INVALID_DEVICE_ID if there was an error and it could not be created. + /// \warning + /// - This function is not thread-safe. + /// - Use a blocking hook (IAkIOHookBlocking) with SCHEDULER_BLOCKING devices, and a + /// deferred hook (IAkIOHookDeferredBatch) with SCHEDULER_DEFERRED_LINED_UP devices (these flags are + /// specified in the device settings (AkDeviceSettings). The pointer to IAkLowLevelIOHook is + /// statically cast internally into one of these hooks. Implementing the wrong (or no) interface + /// will result into a crash. + /// \remarks + /// - You may use AK::StreamMgr::GetDefaultDeviceSettings() first to get default values for the + /// settings, change those you want, then feed the structure to this function. + /// - The returned device ID should be kept by the Low-Level IO, to assign it to file descriptors + /// in AK::StreamMgr::IAkFileLocationResolver::Open(). + /// \sa + /// - AK::StreamMgr::IAkLowLevelIOHook + /// - AK::StreamMgr::GetDefaultDeviceSettings() + /// - \ref streamingmanager_settings + AkDeviceID CreateDevice( + const AkDeviceSettings& in_settings, ///< Device settings. + AK::StreamMgr::IAkLowLevelIOHook* in_pLowLevelHook ///< Associated low-level I/O hook. Pass either a IAkIOHookBlocking or a IAkIOHookDeferredBatch interface, consistent with the type of the scheduler. + ) override; + /// Streaming device destruction. + /// \return AK_Success if the device was successfully destroyed. + /// \warning This function is not thread-safe. No stream should exist for that device when it is destroyed. + AKRESULT DestroyDevice( + AkDeviceID in_deviceID ///< Device ID of the device to destroy. + ) override; + + /// Execute pending I/O operations on all created I/O devices. + /// This should only be called in single-threaded environments where an I/O device cannot spawn a thread. + /// \return AK_Success when called from an appropriate environment, AK_NotCompatible otherwise. + /// \sa + /// - AK::StreamMgr::CreateDevice() + AKRESULT PerformIO() override; + + /// Get the default values for the streaming device's settings. Recommended usage + /// is to call this function first, then pass the settings to AK::StreamMgr::CreateDevice(). + /// \sa + /// - AK::StreamMgr::CreateDevice() + /// - AkDeviceSettings + /// - \ref streamingmanager_settings + void GetDefaultDeviceSettings( + AkDeviceSettings& out_settings ///< Returned AkDeviceSettings structure with default values. + ) override; + //@} + + /// \name Language management. + //@{ + /// Set the current language once and only once, here. The language name is stored in a static buffer + /// inside the Stream Manager. In order to resolve localized (language-specific) file location, + /// AK::StreamMgr::IAkFileLocationResolver implementations query this string. They may use it to + /// construct a file path (for e.g. SDK/samples/SoundEngine/Common/AkFileLocationBase.cpp), or to + /// find a language-specific file within a look-up table (for e.g. SDK/samples/SoundEngine/Common/AkFilePackageLUT.cpp). + /// Pass a valid null-terminated string, without a trailing slash or backslash. Empty strings are accepted. + /// You may register for language changes (see RegisterToLanguageChangeNotification()). + /// After changing the current language, all observers are notified. + /// \return AK_Success if successful (if language string has less than AK_MAX_LANGUAGE_NAME_SIZE characters). AK_Fail otherwise. + /// \warning Not multithread safe. + /// \sa + /// - AK::StreamMgr::GetCurrentLanguage() + /// - AK::StreamMgr::AddLanguageChangeObserver() + AKRESULT SetCurrentLanguage( + const AkOSChar* in_pszLanguageName ///< Language name. + ) override; + + /// Get the current language. The language name is stored in a static buffer inside the Stream Manager, + /// with AK::StreamMgr::SetCurrentLanguage(). In order to resolve localized (language-specific) file location, + /// AK::StreamMgr::IAkFileLocationResolver implementations query this string. They may use it to + /// construct a file path (for e.g. SDK/samples/SoundEngine/Common/AkFileLocationBase.cpp), or to + /// find a language-specific file within a look-up table (for e.g. SDK/samples/SoundEngine/Common/AkFilePackageLUT.cpp). + /// \return Current language. + /// \sa AK::StreamMgr::SetCurrentLanguage() + const AkOSChar* GetCurrentLanguage() override; + + /// Register to language change notifications. + /// \return AK_Success if successful, AK_Fail otherwise (no memory or no cookie). + /// \warning Not multithread safe. + /// \sa + /// - AK::StreamMgr::SetCurrentLanguage() + /// - AK::StreamMgr::RemoveLanguageChangeObserver() + AKRESULT AddLanguageChangeObserver( + AK::StreamMgr::AkLanguageChangeHandler in_handler, ///< Callback function. + void* in_pCookie ///< Cookie, passed back to AkLanguageChangeHandler. Must set. + ) override; + + /// Unregister to language change notifications. Use the cookie you have passed to + /// AddLanguageChangeObserver() to identify the observer. + /// \warning Not multithread safe. + /// \sa + /// - AK::StreamMgr::SetCurrentLanguage() + /// - AK::StreamMgr::AddLanguageChangeObserver() + void RemoveLanguageChangeObserver( + void* in_pCookie ///< Cookie that was passed to AddLanguageChangeObserver(). + ) override; + + /// \name Stream Manager: Cache management. + //@{ + /// Flush cache of all devices. This function has no effect for devices where + /// AkDeviceSettings::bUseStreamCache was set to false (no caching). + /// \sa + /// - \ref streamingmanager_settings + void FlushAllCaches() override; + + //@} +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/AkInclude_2022_1.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/AkInclude_2022_1.h new file mode 100644 index 0000000..054a206 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/AkInclude_2022_1.h @@ -0,0 +1,64 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/PreSoundEngineInclude.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if AK_SUPPORT_WAAPI +#include +#include + +#if WITH_EDITORONLY_DATA +#include +#else +class AkErrorMessageTranslator; +#endif + +#else // No WAAPI +#if WITH_EDITORONLY_DATA +#include +#else +class AkErrorMessageTranslator; +#endif +#endif + +#include "Wwise/Compat_2022_1/Compat.h" + +#include "Wwise/PostSoundEngineInclude.h" diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/Compat_2022_1/AkAsyncFileOpenData.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/Compat_2022_1/AkAsyncFileOpenData.h new file mode 100644 index 0000000..b69cab4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/Compat_2022_1/AkAsyncFileOpenData.h @@ -0,0 +1,34 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include + +struct AkAsyncFileOpenData; +using AkFileOpenCallback = void(*)(AkAsyncFileOpenData* in_pOpenInfo, AKRESULT in_eResult); +struct AkAsyncFileOpenData +{ + const AkOSChar* pszFileName; ///< File name. Only one of pszFileName or fileID should be valid (pszFileName null while fileID is not AK_INVALID_FILE_ID, or vice versa) + AkFileID fileID; ///< File ID. Only one of pszFileName or fileID should be valid (pszFileName null while fileID is not AK_INVALID_FILE_ID, or vice versa) + AkFileSystemFlags* pFlags; ///< Flags for opening, null when unused + AkOpenMode eOpenMode; ///< Open mode. + AkFileOpenCallback pCallback; ///< Callback function used to notify the high-level device when Open is done + void* pCookie; ///< Reserved. Pass this unchanged to the callback function. The I/O device uses this cookie to retrieve the owner of the transfer. + AkFileDesc* pFileDesc; ///< File Descriptor to fill once the Open operation is complete. + void* pCustomData; ///< Convienience pointer for the IO hook implementer. Useful for additional data used in asynchronous implementations, for example. +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/Compat_2022_1/Compat.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/Compat_2022_1/Compat.h new file mode 100644 index 0000000..2558dd0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/Compat_2022_1/Compat.h @@ -0,0 +1,20 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Compat_2022_1/AkAsyncFileOpenData.h" diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/Stats/SoundEngine_2022_1.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/Stats/SoundEngine_2022_1.h new file mode 100644 index 0000000..7b37fde --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/Stats/SoundEngine_2022_1.h @@ -0,0 +1,24 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Stats/SoundEngine.h" +#include "Stats/Stats.h" +#include "Logging/LogMacros.h" + +DECLARE_CYCLE_STAT_EXTERN(TEXT("SoundEngine 2022.1 API Calls"), STAT_WwiseSoundEngineAPI_2022_1, STATGROUP_WwiseSoundEngine, WWISESOUNDENGINE_API); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/WwiseFileLocationResolver_2022_1.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/WwiseFileLocationResolver_2022_1.h new file mode 100644 index 0000000..05fac92 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/WwiseFileLocationResolver_2022_1.h @@ -0,0 +1,61 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkInclude.h" + +#include "Containers/Map.h" +#include "HAL/CriticalSection.h" +#include "HAL/Event.h" + +class WWISESOUNDENGINE_API FWwiseFileLocationResolver : public AK::StreamMgr::IAkFileLocationResolver +{ +private: + struct FileOpenDataBridge : public AkAsyncFileOpenData + { + AKRESULT Result; + FEventRef Done; + + static void Callback(AkAsyncFileOpenData* in_pOpenInfo, AKRESULT in_eResult); + }; + +public: + virtual AKRESULT Open( + AkAsyncFileOpenData* io_pOpenData + ) = 0; + +private: + FCriticalSection MapLock; + TMultiMap OpenFileIDMap; + virtual AKRESULT Open( + AkFileID in_fileID, + AkOpenMode in_eOpenMode, + AkFileSystemFlags* in_pFlags, + bool& io_bSyncOpen, + AkFileDesc& io_fileDesc + ) override final; + + TMultiMap OpenFileNameMap; + virtual AKRESULT Open( + const AkOSChar* in_pszFileName, + AkOpenMode in_eOpenMode, + AkFileSystemFlags* in_pFlags, + bool& io_bSyncOpen, + AkFileDesc& io_fileDesc + ) override final; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/WwiseLowLevelIOHook_2022_1.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/WwiseLowLevelIOHook_2022_1.h new file mode 100644 index 0000000..dfdf64d --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/WwiseLowLevelIOHook_2022_1.h @@ -0,0 +1,55 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "AkInclude.h" + +class WWISESOUNDENGINE_API FWwiseLowLevelIOHook : public AK::StreamMgr::IAkIOHookDeferredBatch +{ +public: + virtual AKRESULT Close(AkFileDesc* in_pFileDesc) = 0; + + virtual void BatchRead( + AkUInt32 in_uNumTransfers, + BatchIoTransferItem* in_pTransferItems + ) = 0; + + virtual void BatchWrite( + AkUInt32 in_uNumTransfers, + BatchIoTransferItem* in_pTransferItems + ) = 0; + +private: + virtual AKRESULT Close( + AkFileDesc& in_fileDesc + ) override final; + + virtual AKRESULT BatchRead( + AkUInt32 in_uNumTransfers, + BatchIoTransferItem* in_pTransferItems, + AkBatchIOCallback in_pBatchIoCallback, + AKRESULT* io_pDispatchResults + ) override final; + + virtual AKRESULT BatchWrite( + AkUInt32 in_uNumTransfers, ///< Number of transfers to process + BatchIoTransferItem* in_pTransferItems, ///< List of transfer items to process + AkBatchIOCallback in_pBatchIoCallback, ///< Callback to execute to handle completion of multiple items simultaneously + AKRESULT* io_pDispatchResults ///< Output result codes to indicate if a transfer was successfully dispatched + ) override final; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/WwiseSoundEngine_2022_1.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/WwiseSoundEngine_2022_1.h new file mode 100644 index 0000000..59cc76f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Public/Wwise/WwiseSoundEngine_2022_1.h @@ -0,0 +1,34 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/WwiseSoundEngineVersionModule.h" + +class FWwiseSoundEngine_2022_1 : public IWwiseSoundEngineVersionModule +{ +public: + virtual IWwiseCommAPI* GetComm() override; + virtual IWwiseMemoryMgrAPI* GetMemoryMgr() override; + virtual IWwiseMonitorAPI* GetMonitor() override; + virtual IWwiseMusicEngineAPI* GetMusicEngine() override; + virtual IWwiseSoundEngineAPI* GetSoundEngine() override; + virtual IWwiseSpatialAudioAPI* GetSpatialAudio() override; + virtual IWwiseStreamMgrAPI* GetStreamMgr() override; + virtual IWwisePlatformAPI* GetPlatform() override; + virtual IWAAPI* GetWAAPI() override; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseSoundEngine_2022_1_OptionalModule.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseSoundEngine_2022_1_OptionalModule.Build.cs new file mode 100644 index 0000000..b425dbe --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseSoundEngine_2022_1_OptionalModule.Build.cs @@ -0,0 +1,174 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; +using System.IO; +using System.Linq; +using System.Collections.Generic; + +#if UE_5_0_OR_LATER +using EpicGames.Core; +#else +using Tools.DotNETCommon; +#endif + +public struct WwiseSoundEngine_2022_1 +{ + private static List AkLibs = new List + { + "AkSoundEngine", + "AkMemoryMgr", + "AkStreamMgr", + "AkMusicEngine", + "AkSpatialAudio", + "AkAudioInputSource", + "AkVorbisDecoder", + "AkMeterFX", // AkMeter does not have a dedicated DLL + }; + + public static void Apply(WwiseSoundEngine SE, ReadOnlyTargetRules Target) + { + var VersionNumber = "2022_1"; + var ModuleName = "WwiseSoundEngine_" + VersionNumber; + var ModuleDirectory = Path.Combine(SE.ModuleDirectory, "../" + ModuleName); + + if (!WwiseSoundEngineVersion.IsSoundEngineVersionSupported(SE.PluginDirectory, ModuleName)) + { + // We are skipping this version since this Wwise Sound Engine is for a particular version only. + return; + } + + Log.TraceInformation("Using Wwise SoundEngine {0} interface", VersionNumber); + SE.PublicDefinitions.AddRange(WwiseSoundEngineVersion.GetVersionDefinesFromClassName(ModuleName)); + + // If packaging as an Engine plugin, the UBT expects to already have a precompiled plugin available + // This can be set to true so long as plugin was already precompiled + SE.bUsePrecompiled = false; + SE.bPrecompile = false; + + string ThirdPartyFolder = Path.Combine(SE.ModuleDirectory, "../../ThirdParty"); + var WwiseUEPlatformInstance = WwiseUEPlatform.GetWwiseUEPlatformInstance(Target, VersionNumber, ThirdPartyFolder); + SE.PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + SE.bAllowConfidentialPlatformDefines = true; + + SE.AddSoundEngineDirectory("WwiseSoundEngine_" + VersionNumber, WwiseUEPlatformInstance.IsWwiseTargetSupported()); + SE.AddVersionHeaders("WwiseSoundEngine_" + VersionNumber, WwiseUEPlatformInstance.IsWwiseTargetSupported()); + + foreach (var Platform in GetAvailablePlatforms(ModuleDirectory)) + { + SE.ExternalDependencies.Add(string.Format("{0}/WwiseUEPlatform_{1}_{2}.Build.cs", ModuleDirectory, VersionNumber, Platform)); + } + + if (Target.bBuildEditor) + { + foreach (var Platform in GetAvailablePlatforms(ModuleDirectory)) + { + SE.PublicDefinitions.Add("AK_PLATFORM_" + Platform.ToUpper()); + } + } + + SE.PublicIncludePaths.Add(Path.Combine(ThirdPartyFolder, "include")); + + SE.PublicDefinitions.Add("AK_UNREAL_MAX_CONCURRENT_IO=32"); + SE.PublicDefinitions.Add("AK_UNREAL_IO_GRANULARITY=32768"); + if (Target.Configuration == UnrealTargetConfiguration.Shipping) + { + SE.PublicDefinitions.Add("AK_OPTIMIZED"); + } + + if (Target.Configuration != UnrealTargetConfiguration.Shipping && WwiseUEPlatformInstance.SupportsCommunication) + { + AkLibs.Add("CommunicationCentral"); + SE.PublicDefinitions.Add("AK_ENABLE_COMMUNICATION=1"); + } + else + { + SE.PublicDefinitions.Add("AK_ENABLE_COMMUNICATION=0"); + } + + if (WwiseUEPlatformInstance.SupportsAkAutobahn) + { + AkLibs.Add("AkAutobahn"); + SE.PublicDefinitions.Add("AK_SUPPORT_WAAPI=1"); + } + else + { + SE.PublicDefinitions.Add("AK_SUPPORT_WAAPI=0"); + } + + if (WwiseUEPlatformInstance.SupportsOpus) + { + AkLibs.Add("AkOpusDecoder"); + SE.PublicDefinitions.Add("AK_SUPPORT_OPUS=1"); + } + else + { + SE.PublicDefinitions.Add("AK_SUPPORT_OPUS=0"); + } + + if (WwiseUEPlatformInstance.SupportsDeviceMemory) + { + SE.PublicDefinitions.Add("AK_SUPPORT_DEVICE_MEMORY=1"); + } + else + { + SE.PublicDefinitions.Add("AK_SUPPORT_DEVICE_MEMORY=0"); + } + + // Platform-specific dependencies + SE.PublicDefinitions.AddRange(WwiseUEPlatformInstance.GetPublicDefinitions()); + SE.PublicDefinitions.Add(string.Format("AK_CONFIGURATION=\"{0}\"", WwiseUEPlatformInstance.AkConfigurationDir)); + + if (WwiseUEPlatformInstance.IsWwiseTargetSupported()) + { + SE.PublicSystemLibraries.AddRange(WwiseUEPlatformInstance.GetPublicSystemLibraries()); + AkLibs.AddRange(WwiseUEPlatformInstance.GetAdditionalWwiseLibs()); + var AdditionalProperty = WwiseUEPlatformInstance.GetAdditionalPropertyForReceipt(ModuleDirectory); + if (AdditionalProperty != null) + { + SE.AdditionalPropertiesForReceipt.Add(AdditionalProperty.Item1, AdditionalProperty.Item2); + } + + SE.PublicFrameworks.AddRange(WwiseUEPlatformInstance.GetPublicFrameworks()); + + SE.PublicDelayLoadDLLs.AddRange(WwiseUEPlatformInstance.GetPublicDelayLoadDLLs()); + foreach (var RuntimeDependency in WwiseUEPlatformInstance.GetRuntimeDependencies()) + { + SE.RuntimeDependencies.Add(RuntimeDependency); + } + + SE.PublicAdditionalLibraries.AddRange(WwiseUEPlatformInstance.GetSanitizedAkLibList(AkLibs)); + } + } + + private static List GetAvailablePlatforms(string ModuleDir) + { + var FoundPlatforms = new List(); + const string StartPattern = "WwiseUEPlatform_"; + const string EndPattern = ".Build.cs"; + foreach (var BuildCsFile in System.IO.Directory.GetFiles(ModuleDir, "*" + EndPattern)) + { + if (BuildCsFile.Contains(StartPattern) && BuildCsFile.EndsWith(EndPattern)) + { + var Platform = BuildCsFile.Remove(BuildCsFile.Length - EndPattern.Length).Split('_').Last(); + FoundPlatforms.Add(Platform); + } + } + + return FoundPlatforms; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_Android.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_Android.Build.cs new file mode 100644 index 0000000..3fe2118 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_Android.Build.cs @@ -0,0 +1,108 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; +using System; +using System.IO; +using System.Collections.Generic; + +public class WwiseUEPlatform_2022_1_Android : WwiseUEPlatform +{ + private List AndroidSDKFolders; + public WwiseUEPlatform_2022_1_Android(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) + { + AndroidSDKFolders = new List + { +#if UE_5_0_OR_LATER + Path.Combine(ThirdPartyFolder, "Android", "arm64-v8a", AkConfigurationDir), + Path.Combine(ThirdPartyFolder, "Android", "x86_64", AkConfigurationDir), +#else + Path.Combine(ThirdPartyFolder, "Android", "armeabi-v7a", AkConfigurationDir), + Path.Combine(ThirdPartyFolder, "Android", "x86", AkConfigurationDir), + Path.Combine(ThirdPartyFolder, "Android", "arm64-v8a", AkConfigurationDir), + Path.Combine(ThirdPartyFolder, "Android", "x86_64", AkConfigurationDir), +#endif + }; + } + + public override string GetLibraryFullPath(string LibName, string LibPath) + { + return Path.Combine(LibPath, "lib" + LibName + ".a"); + } + + public override bool SupportsAkAutobahn { get { return false; } } + + public override bool SupportsCommunication { get { return true; } } + + public override bool SupportsDeviceMemory { get { return false; } } + + public override string AkPlatformLibDir { get { return "Android"; } } + + public override string DynamicLibExtension { get { return "so"; } } + + public override List GetPublicLibraryPaths() + { + var LibPaths = new List(); + foreach (var folder in AndroidSDKFolders) + { + LibPaths.Add(Path.Combine(folder, "lib")); + } + + return LibPaths; + } + + public override List GetAdditionalWwiseLibs() + { + return new List(); + } + + public override List GetRuntimeDependencies() + { + var Dependencies = new List(); + foreach (var folder in AndroidSDKFolders) + { + Dependencies.AddRange(GetAllLibrariesInFolder(Path.Combine(folder, "bin"), DynamicLibExtension, false, true)); + } + + return Dependencies; + } + + public override List GetPublicSystemLibraries() + { + return new List(); + } + + public override List GetPublicDelayLoadDLLs() + { + return new List(); + } + + public override List GetPublicDefinitions() + { + return new List {"__ANDROID__"}; + } + + public override Tuple GetAdditionalPropertyForReceipt(string ModuleDirectory) + { + return new Tuple("AndroidPlugin", Path.Combine(ModuleDirectory, "Wwise_APL.xml")); + } + + public override List GetPublicFrameworks() + { + return new List(); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_Hololens.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_Hololens.Build.cs new file mode 100644 index 0000000..21de7a9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_Hololens.Build.cs @@ -0,0 +1,79 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; +using System; +using System.IO; +using System.Collections.Generic; + +public class WwiseUEPlatform_2022_1_HoloLens : WwiseUEPlatform +{ + public WwiseUEPlatform_2022_1_HoloLens(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) {} + + public override string GetLibraryFullPath(string LibName, string LibPath) + { + return Path.Combine(LibPath, LibName + ".lib"); + } + + public override bool SupportsAkAutobahn { get { return false; } } + + public override bool SupportsCommunication { get { return true; } } + + public override bool SupportsDeviceMemory { get { return false; } } + + public override string AkPlatformLibDir { get { return "UWP_ARM64_" + GetVisualStudioVersion(); } } + + public override string DynamicLibExtension { get { return "dll"; } } + + public override List GetAdditionalWwiseLibs() + { + return new List(); + } + + public override List GetPublicSystemLibraries() + { + return new List(); + } + + public override List GetPublicDelayLoadDLLs() + { + return new List(); + } + + public override List GetPublicDefinitions() + { + return new List + { + "AK_HOLOLENS_VS_VERSION=\"" + GetVisualStudioVersion() + "\"" + }; + } + + public override Tuple GetAdditionalPropertyForReceipt(string ModuleDirectory) + { + return null; + } + + public override List GetPublicFrameworks() + { + return new List(); + } + + private string GetVisualStudioVersion() + { + return "vc160"; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_Linux.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_Linux.Build.cs new file mode 100644 index 0000000..aef0ad9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_Linux.Build.cs @@ -0,0 +1,83 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; +using System; +using System.IO; +using System.Collections.Generic; + +public class WwiseUEPlatform_2022_1_Linux : WwiseUEPlatform +{ + public WwiseUEPlatform_2022_1_Linux(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) {} + + public override string GetLibraryFullPath(string LibName, string LibPath) + { + return Path.Combine(LibPath, "lib" + LibName + ".a"); + } + + public override string AkConfigurationDir + { + get + { + // Linux cross-compile toolchain does not support communication, force release config. + return "Release"; + } + } + + public override bool SupportsAkAutobahn { get { return false; } } + + public override bool SupportsCommunication { get { return false; } } + + public override bool SupportsDeviceMemory { get { return false; } } + + public override string AkPlatformLibDir { get { return "Linux_x64"; } } + + public override string DynamicLibExtension { get { return "so"; } } + + public override List GetAdditionalWwiseLibs() + { + return new List(); + } + + public override List GetPublicSystemLibraries() + { + return new List(); + } + + public override List GetPublicDelayLoadDLLs() + { + return new List(); + } + + public override List GetPublicDefinitions() + { + return new List + { + "AK_OPTIMIZED" // Always using release, force Optimized. + }; + } + + public override Tuple GetAdditionalPropertyForReceipt(string ModuleDirectory) + { + return null; + } + + public override List GetPublicFrameworks() + { + return new List(); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_Mac.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_Mac.Build.cs new file mode 100644 index 0000000..6c8ef37 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_Mac.Build.cs @@ -0,0 +1,78 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; +using System; +using System.IO; +using System.Collections.Generic; + +public class WwiseUEPlatform_2022_1_Mac : WwiseUEPlatform +{ + public WwiseUEPlatform_2022_1_Mac(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) + { + } + + public override string GetLibraryFullPath(string LibName, string LibPath) + { + return Path.Combine(LibPath, "lib" + LibName + ".a"); + } + + public override bool SupportsAkAutobahn { get { return Target.Configuration != UnrealTargetConfiguration.Shipping; } } + + public override bool SupportsCommunication { get { return true; } } + + public override bool SupportsDeviceMemory { get { return false; } } + + public override string AkPlatformLibDir { get { return "Mac"; } } + + public override string DynamicLibExtension { get { return "dylib"; } } + + public override List GetAdditionalWwiseLibs() + { + return new List(); + } + + public override List GetPublicSystemLibraries() + { + return new List(); + } + + public override List GetPublicDelayLoadDLLs() + { + return new List(); + } + + public override List GetPublicDefinitions() + { + return new List(); + } + + public override Tuple GetAdditionalPropertyForReceipt(string ModuleDirectory) + { + return null; + } + + public override List GetPublicFrameworks() + { + return new List + { + "AudioUnit", + "AudioToolbox", + "CoreAudio" + }; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_PS4.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_PS4.Build.cs new file mode 100644 index 0000000..1e6ab61 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_PS4.Build.cs @@ -0,0 +1,89 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; +using System; +using System.IO; +using System.Collections.Generic; + +public class WwiseUEPlatform_2022_1_PS4 : WwiseUEPlatform +{ + public WwiseUEPlatform_2022_1_PS4(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) {} + + public override string GetLibraryFullPath(string LibName, string LibPath) + { + return Path.Combine(LibPath, "lib" + LibName + ".a"); + } + + public override bool SupportsAkAutobahn { get { return false; } } + + public override bool SupportsCommunication { get { return true; } } + + public override bool SupportsDeviceMemory { get { return false; } } + + public override string AkPlatformLibDir + { + get + { + string ExpectedSdkVersion = Environment.ExpandEnvironmentVariables("%SCE_ORBIS_SDK_DIR%"); + return "PS4_SDK" + System.IO.Path.GetFileName(ExpectedSdkVersion); + } + } + + public override string DynamicLibExtension { get { return "prx"; } } + + public override List GetAdditionalWwiseLibs() + { + return new List + { + "SceAudio3dEngine" + }; + } + + public override List GetPublicSystemLibraries() + { + return new List + { + "SceAjm_stub_weak", + "SceAudio3d_stub_weak", + "SceMove_stub_weak" + }; + } + + public override List GetPublicDelayLoadDLLs() + { + return new List(); + } + + public override List GetPublicDefinitions() + { + return new List + { + "__ORBIS__" + }; + } + + public override Tuple GetAdditionalPropertyForReceipt(string ModuleDirectory) + { + return null; + } + + public override List GetPublicFrameworks() + { + return new List(); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_PS5.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_PS5.Build.cs new file mode 100644 index 0000000..bdff674 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_PS5.Build.cs @@ -0,0 +1,87 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; +using System; +using System.IO; +using System.Collections.Generic; + +public class WwiseUEPlatform_2022_1_PS5 : WwiseUEPlatform +{ + public WwiseUEPlatform_2022_1_PS5(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) {} + + public override string GetLibraryFullPath(string LibName, string LibPath) + { + return Path.Combine(LibPath, "lib" + LibName + ".a"); + } + + public override bool SupportsAkAutobahn { get { return false; } } + + public override bool SupportsCommunication { get { return true; } } + + public override bool SupportsDeviceMemory { get { return true; } } + + public override string AkPlatformLibDir + { + get + { + string ExpectedSdkVersion = Environment.ExpandEnvironmentVariables("%SCE_PROSPERO_SDK_DIR%"); + return "PS5_SDK" + System.IO.Path.GetFileName(ExpectedSdkVersion); + } + } + + public override string DynamicLibExtension { get { return "prx"; } } + + public override List GetAdditionalWwiseLibs() + { + return new List(); + } + + public override List GetPublicSystemLibraries() + { + return new List + { + "SceAcm_stub_weak", + "SceAudioOut2_stub_weak" + }; + } + + public override List GetPublicDelayLoadDLLs() + { + return new List(); + } + + public override List GetPublicDefinitions() + { + return new List(); + } + + public override Tuple GetAdditionalPropertyForReceipt(string ModuleDirectory) + { + return null; + } + + public override List GetPublicFrameworks() + { + return new List(); + } +} + +public class WwiseUEPlatform_2022_1_DPX : WwiseUEPlatform_2022_1_PS5 +{ + public WwiseUEPlatform_2022_1_DPX(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) { } +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_Stadia.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_Stadia.Build.cs new file mode 100644 index 0000000..c719b07 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_Stadia.Build.cs @@ -0,0 +1,80 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; +using System; +using System.IO; +using System.Collections.Generic; + +public class WwiseUEPlatform_2022_1_Stadia : WwiseUEPlatform +{ + public WwiseUEPlatform_2022_1_Stadia(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) {} + + public override string GetLibraryFullPath(string LibName, string LibPath) + { + return Path.Combine(LibPath, "lib" + LibName + ".a"); + } + + public override bool SupportsAkAutobahn { get { return false; } } + + public override bool SupportsCommunication { get { return true; } } + + public override bool SupportsDeviceMemory { get { return false; } } + + public override string AkPlatformLibDir { get { return "GGP"; } } + + public override string DynamicLibExtension { get { return "so"; } } + + public override List GetAdditionalWwiseLibs() + { + return new List (); + } + + public override List GetPublicSystemLibraries() + { + return new List(); + } + + public override List GetPublicDelayLoadDLLs() + { + return new List(); + } + + public override List GetPublicDefinitions() + { + return new List + { + "__ggp__" + }; + + } + + public override Tuple GetAdditionalPropertyForReceipt(string ModuleDirectory) + { + return null; + } + + public override List GetPublicFrameworks() + { + return new List(); + } +} + +public class WwiseUEPlatform_2022_1_Quail : WwiseUEPlatform_2022_1_Stadia +{ + public WwiseUEPlatform_2022_1_Quail(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) {} +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_Switch.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_Switch.Build.cs new file mode 100644 index 0000000..97a7b83 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_Switch.Build.cs @@ -0,0 +1,81 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; +using System; +using System.IO; +using System.Collections.Generic; + +public class WwiseUEPlatform_2022_1_Switch : WwiseUEPlatform +{ + public WwiseUEPlatform_2022_1_Switch(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) {} + + public override string GetLibraryFullPath(string LibName, string LibPath) + { + return Path.Combine(LibPath, "lib" + LibName + ".a"); + } + + public override bool SupportsAkAutobahn { get { return false; } } + + public override bool SupportsCommunication { get { return true; } } + + public override bool SupportsDeviceMemory { get { return false; } } + + public override string AkPlatformLibDir { get { return "NX64"; } } + + public override string DynamicLibExtension { get { return string.Empty; } } + + public override List GetAdditionalWwiseLibs() + { + var libs = GetAllLibrariesInFolder(Path.Combine(ThirdPartyFolder, AkPlatformLibDir, AkConfigurationDir, "lib"), "a", true); + libs.Add("AkOpusNXDecoder"); + return libs; + } + + public override List GetRuntimeDependencies() + { + return new List(); + } + + public override List GetPublicDelayLoadDLLs() + { + return new List(); + } + + public override List GetPublicSystemLibraries() + { + return new List(); + } + + public override List GetPublicDefinitions() + { + return new List + { + "NN_NINTENDO_SDK" + }; + } + + public override Tuple GetAdditionalPropertyForReceipt(string ModuleDirectory) + { + return null; + } + + public override List GetPublicFrameworks() + { + return new List(); + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_WinGDK.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_WinGDK.Build.cs new file mode 100644 index 0000000..ebd98dc --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_WinGDK.Build.cs @@ -0,0 +1,103 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; +using System; +using System.IO; +using System.Collections.Generic; + +public class WwiseUEPlatform_2022_1_WinGDK : WwiseUEPlatform +{ + bool bIsDebugBuild = false; + + public WwiseUEPlatform_2022_1_WinGDK(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) {} + + public override string GetLibraryFullPath(string LibName, string LibPath) + { + return Path.Combine(LibPath, LibName + ".lib"); + } + + public override bool SupportsAkAutobahn { get { return false; } } + + public override bool SupportsCommunication { get { return true; } } + + public override bool SupportsDeviceMemory { get { return false; } } + + public override bool SupportsOpus { get { return !bIsDebugBuild; } } + + public override string AkPlatformLibDir { get { return "WinGC_" + GetVisualStudioVersion(); } } + + public override string DynamicLibExtension { get { return "dll"; } } + + public override List GetPublicLibraryPaths() + { + var confDir = bIsDebugBuild && Target.Configuration == UnrealTargetConfiguration.Debug ? "Debug" : AkConfigurationDir; + return new List { Path.Combine(ThirdPartyFolder, AkPlatformLibDir, confDir, "lib") }; + } + + public override List GetAdditionalWwiseLibs() + { + return new List(); + } + + public override List GetPublicSystemLibraries() + { + return new List + { + "dsound.lib", + "dxguid.lib", + "Msacm32.lib", + "XInput.lib", + "dinput8.lib" + }; + } + + public override List GetPublicDelayLoadDLLs() + { + return new List(); + } + + public override List GetPublicDefinitions() + { + return new List + { + "AK_WINDOWSGC", + "AK_WINGC_VS_VERSION=\"" + GetVisualStudioVersion() + "\"" + }; + } + + public override Tuple GetAdditionalPropertyForReceipt(string ModuleDirectory) + { + return null; + } + + public override List GetPublicFrameworks() + { + return new List(); + } + + private string GetVisualStudioVersion() + { + // TODO: WinAnvilPlatform does not have a Compiler property + return "vc160"; + } +} + +public class WwiseUEPlatform_2022_1_WinAnvil : WwiseUEPlatform_2022_1_WinGDK +{ + public WwiseUEPlatform_2022_1_WinAnvil(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) {} +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_Windows.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_Windows.Build.cs new file mode 100644 index 0000000..cca11c4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_Windows.Build.cs @@ -0,0 +1,129 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; +using System; +using System.IO; +using System.Collections.Generic; + +public abstract class WwiseUEPlatform_2022_1_Windows : WwiseUEPlatform +{ + bool bIsDebugBuild = false; + + public WwiseUEPlatform_2022_1_Windows(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) {} + + public abstract string PlatformPrefix { get; } + + public override string GetLibraryFullPath(string LibName, string LibPath) + { + return Path.Combine(LibPath, LibName + ".lib"); + } + + public override bool SupportsAkAutobahn { get { return Target.Configuration != UnrealTargetConfiguration.Shipping && !bIsDebugBuild ; } } + + public override bool SupportsCommunication { get { return true; } } + + public override bool SupportsDeviceMemory { get { return false; } } + + public override bool SupportsOpus { get { return !bIsDebugBuild; } } + + public override string AkPlatformLibDir { get { return PlatformPrefix + "_" + GetVisualStudioVersion(); } } + + public override string DynamicLibExtension { get { return "dll"; } } + + public override List GetPublicLibraryPaths() + { + var confDir = bIsDebugBuild && Target.Configuration == UnrealTargetConfiguration.Debug ? "Debug" : AkConfigurationDir; + return new List { Path.Combine(ThirdPartyFolder, AkPlatformLibDir, confDir, "lib") }; + } + + public override List GetAdditionalWwiseLibs() + { + return new List(); + } + + public override List GetPublicSystemLibraries() + { + return new List + { + "dsound.lib", + "dxguid.lib", + "Msacm32.lib", + "XInput.lib", + "dinput8.lib" + }; + } + + public override List GetPublicDelayLoadDLLs() + { + return new List(); + } + + public override List GetPublicDefinitions() + { + return new List + { + "AK_WINDOWS_VS_VERSION=\"" + GetVisualStudioVersion() + "\"" + }; + } + + public override Tuple GetAdditionalPropertyForReceipt(string ModuleDirectory) + { + return null; + } + + public override List GetPublicFrameworks() + { + return new List(); + } + + private static void CheckCompilerVersion(ref string Version, WindowsCompiler Compiler, string LongVersionName, string ShortVersionName) + { + try + { + if (Compiler == (WindowsCompiler)Enum.Parse(typeof(WindowsCompiler), LongVersionName)) + Version = ShortVersionName; + } + catch + { + } + } + + private string GetVisualStudioVersion() + { + string VSVersion = "vc160"; + var Compiler = Target.WindowsPlatform.Compiler; + CheckCompilerVersion(ref VSVersion, Compiler, "VisualStudio2022", "vc170"); + CheckCompilerVersion(ref VSVersion, Compiler, "VisualStudio2019", "vc160"); + CheckCompilerVersion(ref VSVersion, Compiler, "VisualStudio2017", "vc150"); + CheckCompilerVersion(ref VSVersion, Compiler, "VisualStudio2015", "vc140"); + CheckCompilerVersion(ref VSVersion, Compiler, "VisualStudio2013", "vc120"); + return VSVersion; + } +} + +public class WwiseUEPlatform_2022_1_Win32 : WwiseUEPlatform_2022_1_Windows +{ + public WwiseUEPlatform_2022_1_Win32(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) {} + public override string PlatformPrefix { get { return "Win32"; } } +} + +public class WwiseUEPlatform_2022_1_Win64 : WwiseUEPlatform_2022_1_Windows +{ + public WwiseUEPlatform_2022_1_Win64(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) {} + public override string PlatformPrefix { get { return "x64"; } } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_XSX.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_XSX.Build.cs new file mode 100644 index 0000000..425cd4f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_XSX.Build.cs @@ -0,0 +1,92 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; +using System; +using System.IO; +using System.Collections.Generic; + +public class WwiseUEPlatform_2022_1_XSX : WwiseUEPlatform +{ + public WwiseUEPlatform_2022_1_XSX(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) {} + + public override string GetLibraryFullPath(string LibName, string LibPath) + { + return Path.Combine(LibPath, LibName + ".lib"); + } + + public override bool SupportsAkAutobahn { get { return false; } } + + public override bool SupportsCommunication { get { return true; } } + + public override bool SupportsDeviceMemory { get { return true; } } + + public override string AkPlatformLibDir { get { return "XboxSeriesX_" + GetVisualStudioVersion(); } } + + public override string DynamicLibExtension { get { return "dll"; } } + + public override List GetAdditionalWwiseLibs() + { + return new List(); + } + + public override List GetPublicSystemLibraries() + { + return new List + { + "AcpHal.lib", + "xapu.lib", + "MMDevApi.lib" + }; + } + + public override List GetPublicDelayLoadDLLs() + { + return new List(); + } + + public override List GetPublicDefinitions() + { + return new List + { + "_GAMING_XBOX_SCARLETT", + "AK_XBOXSERIESX_VS_VERSION=\"" + GetVisualStudioVersion() + "\"", + "AK_XBOXONE_COMMON" + }; + } + + public override Tuple GetAdditionalPropertyForReceipt(string ModuleDirectory) + { + return null; + } + + public override List GetPublicFrameworks() + { + return new List(); + } + + private string GetVisualStudioVersion() + { + // TODO: MPXPlatform does not have a Compiler property + return "vc160"; + } +} + +public class WwiseUEPlatform_2022_1_MPX : WwiseUEPlatform_2022_1_XSX +{ + public WwiseUEPlatform_2022_1_MPX(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) {} +} \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_XboxOne.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_XboxOne.Build.cs new file mode 100644 index 0000000..b98f485 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_XboxOne.Build.cs @@ -0,0 +1,89 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; +using System; +using System.IO; +using System.Collections.Generic; + +public class WwiseUEPlatform_2022_1_XboxOne : WwiseUEPlatform +{ + public WwiseUEPlatform_2022_1_XboxOne(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) {} + + public override string GetLibraryFullPath(string LibName, string LibPath) + { + return Path.Combine(LibPath, LibName + ".lib"); + } + + public override bool SupportsAkAutobahn { get { return false; } } + + public override bool SupportsCommunication { get { return true; } } + + public override bool SupportsDeviceMemory { get { return true; } } + + public override string AkPlatformLibDir { get { return "XboxOne_" + GetVisualStudioVersion(); } } + + public override string DynamicLibExtension { get { return "dll"; } } + + public override List GetAdditionalWwiseLibs() + { + return new List(); + } + + public override List GetPublicSystemLibraries() + { + return new List + { + "AcpHal.lib", + "MMDevApi.lib" + }; + } + + public override List GetPublicDelayLoadDLLs() + { + return new List(); + } + + public override List GetPublicDefinitions() + { + var Defines = new List + { + "_XBOX_ONE", + "AK_XBOXONE_INIT_COMMS_MANIFEST=1", + "AK_XBOXONE_VS_VERSION=\"" + GetVisualStudioVersion() + "\"", + "AK_XBOXONE_NEED_APU_ALLOC", + "AK_XBOXONE_COMMON" + }; + + return Defines; + } + + public override Tuple GetAdditionalPropertyForReceipt(string ModuleDirectory) + { + return null; + } + + public override List GetPublicFrameworks() + { + return new List(); + } + + private string GetVisualStudioVersion() + { + return "vc150"; // The only supported version is now Visual Studio 2017. 2019 is not supported by XDK and 2015 is not supported by Unreal. + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_XboxOneGDK.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_XboxOneGDK.Build.cs new file mode 100644 index 0000000..d4e72fb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_XboxOneGDK.Build.cs @@ -0,0 +1,98 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; +using System; +using System.IO; +using System.Collections.Generic; + +public class WwiseUEPlatform_2022_1_XboxOneGDK : WwiseUEPlatform +{ + public WwiseUEPlatform_2022_1_XboxOneGDK(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) {} + + public override string GetLibraryFullPath(string LibName, string LibPath) + { + return Path.Combine(LibPath, LibName + ".lib"); + } + + public override bool SupportsAkAutobahn { get { return false; } } + + public override bool SupportsCommunication { get { return true; } } + + public override bool SupportsDeviceMemory { get { return true; } } + + public override string AkPlatformLibDir { get { return "XboxOneGC_" + GetVisualStudioVersion(); } } + + public override string DynamicLibExtension { get { return "dll"; } } + + public override List GetAdditionalWwiseLibs() + { + return new List(); + } + + public override List GetPublicSystemLibraries() + { + return new List + { + "AcpHal.lib", + "MMDevApi.lib" + }; + } + + public override List GetPublicDelayLoadDLLs() + { + return new List(); + } + + public override List GetPublicDefinitions() + { + return new List + { + "_GAMING_XBOX", + "AK_XBOXONE_INIT_COMMS_MANIFEST=0", + "AK_XBOXONEGC_VS_VERSION=\"" + GetVisualStudioVersion() + "\"", + "AK_XBOXONE_NEED_APU_ALLOC", + "AK_XBOXONE_COMMON" + }; + } + + public override Tuple GetAdditionalPropertyForReceipt(string ModuleDirectory) + { + return null; + } + + public override List GetPublicFrameworks() + { + return new List(); + } + + private string GetVisualStudioVersion() + { + // TODO: XboxOneAnvilPlatform does not have a Compiler property + return "vc160"; + } +} + +public class WwiseUEPlatform_2022_1_XB1 : WwiseUEPlatform_2022_1_XboxOneGDK +{ + public WwiseUEPlatform_2022_1_XB1(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) { } +} + +public class WwiseUEPlatform_2022_1_XboxOneAnvil : WwiseUEPlatform_2022_1_XboxOneGDK +{ + public WwiseUEPlatform_2022_1_XboxOneAnvil(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) {} +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_iOS.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_iOS.Build.cs new file mode 100644 index 0000000..73342c9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_iOS.Build.cs @@ -0,0 +1,88 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; +using System; +using System.IO; +using System.Collections.Generic; + +public class WwiseUEPlatform_2022_1_IOS : WwiseUEPlatform +{ + public WwiseUEPlatform_2022_1_IOS(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) {} + + public override string GetLibraryFullPath(string LibName, string LibPath) + { + return Path.Combine(LibPath, "lib" + LibName + ".a"); + } + + public override bool SupportsAkAutobahn { get { return false; } } + + public override bool SupportsCommunication { get { return true; } } + + public override bool SupportsDeviceMemory { get { return false; } } + + public override string AkPlatformLibDir { get { return "iOS"; } } + + public override string DynamicLibExtension { get { return string.Empty; } } + + public override List GetPublicLibraryPaths() + { + return new List + { + Path.Combine(ThirdPartyFolder, AkPlatformLibDir, AkConfigurationDir + "-iphoneos", "lib") + }; + } + + public override List GetAdditionalWwiseLibs() + { + return GetAllLibrariesInFolder(Path.Combine(ThirdPartyFolder, AkPlatformLibDir, AkConfigurationDir + "-iphoneos", "lib"), "a", true); + } + + public override List GetRuntimeDependencies() + { + return new List(); + } + + public override List GetPublicDelayLoadDLLs() + { + return new List(); + } + + public override List GetPublicSystemLibraries() + { + return new List(); + } + + public override List GetPublicDefinitions() + { + return new List(); + } + + public override Tuple GetAdditionalPropertyForReceipt(string ModuleDirectory) + { + return null; + } + + public override List GetPublicFrameworks() + { + return new List + { + "AudioToolbox", + "CoreAudio" + }; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_tvOS.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_tvOS.Build.cs new file mode 100644 index 0000000..51d91f0 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/WwiseUEPlatform_2022_1_tvOS.Build.cs @@ -0,0 +1,88 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; +using System; +using System.IO; +using System.Collections.Generic; + +public class WwiseUEPlatform_2022_1_TVOS : WwiseUEPlatform +{ + public WwiseUEPlatform_2022_1_TVOS(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) {} + + public override string GetLibraryFullPath(string LibName, string LibPath) + { + return Path.Combine(LibPath, "lib" + LibName + ".a"); + } + + public override bool SupportsAkAutobahn { get { return false; } } + + public override bool SupportsCommunication { get { return true; } } + + public override bool SupportsDeviceMemory { get { return false; } } + + public override string AkPlatformLibDir { get { return "tvOS"; } } + + public override string DynamicLibExtension { get { return string.Empty; } } + + public override List GetPublicLibraryPaths() + { + return new List + { + Path.Combine(ThirdPartyFolder, AkPlatformLibDir, AkConfigurationDir + "-appletvos", "lib") + }; + } + + public override List GetAdditionalWwiseLibs() + { + return GetAllLibrariesInFolder(Path.Combine(ThirdPartyFolder, AkPlatformLibDir, AkConfigurationDir + "-appletvos", "lib"), "a", true); + } + + public override List GetRuntimeDependencies() + { + return new List(); + } + + public override List GetPublicDelayLoadDLLs() + { + return new List(); + } + + public override List GetPublicSystemLibraries() + { + return new List(); + } + + public override List GetPublicDefinitions() + { + return new List(); + } + + public override Tuple GetAdditionalPropertyForReceipt(string ModuleDirectory) + { + return null; + } + + public override List GetPublicFrameworks() + { + return new List + { + "AudioToolbox", + "CoreAudio" + }; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Wwise_APL.xml b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Wwise_APL.xml new file mode 100644 index 0000000..0a3b515 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_2022_1/Wwise_APL.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WAAPI_Null.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WAAPI_Null.cpp new file mode 100644 index 0000000..96dcb98 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WAAPI_Null.cpp @@ -0,0 +1,33 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_Null/WAAPI_Null.h" +#include "Wwise/Stats/SoundEngine_Null.h" + +#if AK_SUPPORT_WAAPI + +IWAAPI::Client* FWAAPI_Null::NewClient() +{ + return nullptr; +} + +std::string FWAAPI_Null::GetJsonString(const AK::WwiseAuthoringAPI::JsonProvider& InJsonProvider) +{ + return {}; +} + +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WwiseCommAPI_Null.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WwiseCommAPI_Null.cpp new file mode 100644 index 0000000..f37210f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WwiseCommAPI_Null.cpp @@ -0,0 +1,60 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_Null/WwiseCommAPI_Null.h" +#include "Wwise/Stats/SoundEngine_Null.h" + +AKRESULT FWwiseCommAPI_Null::Init( + const AkCommSettings& in_settings +) +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseCommAPI_Null::Init")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AkInt32 FWwiseCommAPI_Null::GetLastError() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return 0; +} + +void FWwiseCommAPI_Null::GetDefaultInitSettings( + AkCommSettings& out_settings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void FWwiseCommAPI_Null::Term() +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseCommAPI_Null::Term")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +AKRESULT FWwiseCommAPI_Null::Reset() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +const AkCommSettings& FWwiseCommAPI_Null::GetCurrentSettings() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + static const AkCommSettings StaticSettings; + return StaticSettings; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WwiseMemoryMgrAPI_Null.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WwiseMemoryMgrAPI_Null.cpp new file mode 100644 index 0000000..42b50a5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WwiseMemoryMgrAPI_Null.cpp @@ -0,0 +1,194 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_Null/WwiseMemoryMgrAPI_Null.h" +#include "Wwise/Stats/SoundEngine_Null.h" + +AKRESULT FWwiseMemoryMgrAPI_Null::Init( + AkMemSettings* in_pSettings +) +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseMemoryMgrAPI_Null::Init")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +void FWwiseMemoryMgrAPI_Null::GetDefaultSettings( + AkMemSettings& out_pMemSettings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +bool FWwiseMemoryMgrAPI_Null::IsInitialized() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return false; +} + +void FWwiseMemoryMgrAPI_Null::Term() +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseMemoryMgrAPI_Null::Term")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void FWwiseMemoryMgrAPI_Null::InitForThread() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void FWwiseMemoryMgrAPI_Null::TermForThread() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void FWwiseMemoryMgrAPI_Null::TrimForThread() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void* FWwiseMemoryMgrAPI_Null::dMalloc( + AkMemPoolId in_poolId, + size_t in_uSize, + const char* in_pszFile, + AkUInt32 in_uLine +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return nullptr; +} + +void* FWwiseMemoryMgrAPI_Null::Malloc( + AkMemPoolId in_poolId, + size_t in_uSize +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return nullptr; +} + +void* FWwiseMemoryMgrAPI_Null::dRealloc( + AkMemPoolId in_poolId, + void* in_pAlloc, + size_t in_uSize, + const char* in_pszFile, + AkUInt32 in_uLine +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return nullptr; +} + +void* FWwiseMemoryMgrAPI_Null::Realloc( + AkMemPoolId in_poolId, + void* in_pAlloc, + size_t in_uSize +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return nullptr; +} + +void* FWwiseMemoryMgrAPI_Null::dReallocAligned( + AkMemPoolId in_poolId, + void* in_pAlloc, + size_t in_uSize, + AkUInt32 in_uAlignment, + const char* in_pszFile, + AkUInt32 in_uLine +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return nullptr; +} + +void* FWwiseMemoryMgrAPI_Null::ReallocAligned( + AkMemPoolId in_poolId, + void* in_pAlloc, + size_t in_uSize, + AkUInt32 in_uAlignment +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return nullptr; +} + +void FWwiseMemoryMgrAPI_Null::Free( + AkMemPoolId in_poolId, + void* in_pMemAddress +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void* FWwiseMemoryMgrAPI_Null::dMalign( + AkMemPoolId in_poolId, + size_t in_uSize, + AkUInt32 in_uAlignment, + const char* in_pszFile, + AkUInt32 in_uLine +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return nullptr; +} + +void* FWwiseMemoryMgrAPI_Null::Malign( + AkMemPoolId in_poolId, + size_t in_uSize, + AkUInt32 in_uAlignment +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return nullptr; +} + +void FWwiseMemoryMgrAPI_Null::GetCategoryStats( + AkMemPoolId in_poolId, + AK::MemoryMgr::CategoryStats& out_poolStats +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void FWwiseMemoryMgrAPI_Null::GetGlobalStats( + AK::MemoryMgr::GlobalStats& out_stats +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void FWwiseMemoryMgrAPI_Null::StartProfileThreadUsage( +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +AkUInt64 FWwiseMemoryMgrAPI_Null::StopProfileThreadUsage( +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return 0; +} + +void FWwiseMemoryMgrAPI_Null::DumpToFile( + const AkOSChar* pszFilename +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WwiseMonitorAPI_Null.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WwiseMonitorAPI_Null.cpp new file mode 100644 index 0000000..6eedcbb --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WwiseMonitorAPI_Null.cpp @@ -0,0 +1,154 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_Null/WwiseMonitorAPI_Null.h" +#include "Wwise/Stats/SoundEngine_Null.h" + +AKRESULT FWwiseMonitorAPI_Null::PostCode( + AK::Monitor::ErrorCode in_eError, + AK::Monitor::ErrorLevel in_eErrorLevel, + AkPlayingID in_playingID, + AkGameObjectID in_gameObjID, + AkUniqueID in_audioNodeID, + bool in_bIsBus +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseMonitorAPI_Null::PostCodeVarArg( + AK::Monitor::ErrorCode in_eError, + AK::Monitor::ErrorLevel in_eErrorLevel, + AK::Monitor::MsgContext msgContext, + ... +) +{ + va_list Args; + va_start(Args, msgContext); + auto Result = PostCodeVaList(in_eError, in_eErrorLevel, msgContext, Args); + va_end(Args); + return Result; +} + +AKRESULT FWwiseMonitorAPI_Null::PostCodeVaList( + AK::Monitor::ErrorCode in_eError, + AK::Monitor::ErrorLevel in_eErrorLevel, + AK::Monitor::MsgContext msgContext, + ::va_list args +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseMonitorAPI_Null::PostString( + const wchar_t* in_pszError, + AK::Monitor::ErrorLevel in_eErrorLevel, + AkPlayingID in_playingID, + AkGameObjectID in_gameObjID, + AkUniqueID in_audioNodeID, + bool in_bIsBus +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif // #ifdef AK_SUPPORT_WCHAR + +AKRESULT FWwiseMonitorAPI_Null::PostString( + const char* in_pszError, + AK::Monitor::ErrorLevel in_eErrorLevel, + AkPlayingID in_playingID, + AkGameObjectID in_gameObjID, + AkUniqueID in_audioNodeID, + bool in_bIsBus +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseMonitorAPI_Null::SetLocalOutput( + AkUInt32 in_uErrorLevel, + AK::Monitor::LocalOutputFunc in_pMonitorFunc +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseMonitorAPI_Null::AddTranslator( + AkErrorMessageTranslator* translator, + bool overridePreviousTranslators +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseMonitorAPI_Null::ResetTranslator( +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AkTimeMs FWwiseMonitorAPI_Null::GetTimeStamp() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return 0; +} + +void FWwiseMonitorAPI_Null::MonitorStreamMgrInit( + const AkStreamMgrSettings& in_streamMgrSettings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void FWwiseMonitorAPI_Null::MonitorStreamingDeviceInit( + AkDeviceID in_deviceID, + const AkDeviceSettings& in_deviceSettings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void FWwiseMonitorAPI_Null::MonitorStreamingDeviceDestroyed( + AkDeviceID in_deviceID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void FWwiseMonitorAPI_Null::MonitorStreamMgrTerm() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void FWwiseMonitorAPI_Null::SetupDefaultWAAPIErrorTranslator(const FString& WaapiIP, AkUInt32 WaapiPort, AkUInt32 Timeout) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void FWwiseMonitorAPI_Null::TerminateDefaultWAAPIErrorTranslator() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WwiseMusicEngineAPI_Null.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WwiseMusicEngineAPI_Null.cpp new file mode 100644 index 0000000..48002da --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WwiseMusicEngineAPI_Null.cpp @@ -0,0 +1,52 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_Null/WwiseMusicEngineAPI_Null.h" +#include "Wwise/Stats/SoundEngine_Null.h" + +AKRESULT FWwiseMusicEngineAPI_Null::Init( + AkMusicSettings* in_pSettings +) +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseMusicEngineAPI_Null::Init")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +void FWwiseMusicEngineAPI_Null::GetDefaultInitSettings( + AkMusicSettings& out_settings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void FWwiseMusicEngineAPI_Null::Term( +) +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseMusicEngineAPI_Null::Term")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +AKRESULT FWwiseMusicEngineAPI_Null::GetPlayingSegmentInfo( + AkPlayingID in_PlayingID, + AkSegmentInfo& out_segmentInfo, + bool in_bExtrapolate +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WwiseSoundEngineAPI_Null.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WwiseSoundEngineAPI_Null.cpp new file mode 100644 index 0000000..0b4ae7f --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WwiseSoundEngineAPI_Null.cpp @@ -0,0 +1,2268 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_Null/WwiseSoundEngineAPI_Null.h" +#include "Wwise/Stats/SoundEngine_Null.h" + +#include "AkInclude.h" + +FWwiseSoundEngineAPI_Null::FWwiseSoundEngineAPI_Null(): + IWwiseSoundEngineAPI(new FQuery, new FAudioInputPlugin) +{} + +bool FWwiseSoundEngineAPI_Null::IsInitialized() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return false; +} + +AKRESULT FWwiseSoundEngineAPI_Null::Init( + AkInitSettings* in_pSettings, + AkPlatformInitSettings* in_pPlatformSettings +) +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseSoundEngineAPI_Null::Init")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +void FWwiseSoundEngineAPI_Null::GetDefaultInitSettings( + AkInitSettings& out_settings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void FWwiseSoundEngineAPI_Null::GetDefaultPlatformInitSettings( + AkPlatformInitSettings& out_platformSettings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void FWwiseSoundEngineAPI_Null::Term() +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseSoundEngineAPI_Null::Term")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +AKRESULT FWwiseSoundEngineAPI_Null::GetAudioSettings( + AkAudioSettings& out_audioSettings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AkChannelConfig FWwiseSoundEngineAPI_Null::GetSpeakerConfiguration( + AkOutputDeviceID in_idOutput +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return {}; +} + +AKRESULT FWwiseSoundEngineAPI_Null::GetOutputDeviceConfiguration( + AkOutputDeviceID in_idOutput, + AkChannelConfig& io_channelConfig, + Ak3DAudioSinkCapabilities& io_capabilities +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::GetPanningRule( + AkPanningRule& out_ePanningRule, + AkOutputDeviceID in_idOutput +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetPanningRule( + AkPanningRule in_ePanningRule, + AkOutputDeviceID in_idOutput +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::GetSpeakerAngles( + AkReal32* io_pfSpeakerAngles, + AkUInt32& io_uNumAngles, + AkReal32& out_fHeightAngle, + AkOutputDeviceID in_idOutput +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetSpeakerAngles( + const AkReal32* in_pfSpeakerAngles, + AkUInt32 in_uNumAngles, + AkReal32 in_fHeightAngle, + AkOutputDeviceID in_idOutput +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetVolumeThreshold( + AkReal32 in_fVolumeThresholdDB +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetMaxNumVoicesLimit( + AkUInt16 in_maxNumberVoices +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::RenderAudio( + bool in_bAllowSyncRender +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AK::IAkGlobalPluginContext* FWwiseSoundEngineAPI_Null::GetGlobalPluginContext() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return nullptr; +} + +AKRESULT FWwiseSoundEngineAPI_Null::RegisterPlugin( + AkPluginType in_eType, + AkUInt32 in_ulCompanyID, + AkUInt32 in_ulPluginID, + AkCreatePluginCallback in_pCreateFunc, + AkCreateParamCallback in_pCreateParamFunc, + AkGetDeviceListCallback in_pGetDeviceList +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::RegisterPluginDLL( + const AkOSChar* in_DllName, + const AkOSChar* in_DllPath +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::RegisterCodec( + AkUInt32 in_ulCompanyID, + AkUInt32 in_ulCodecID, + AkCreateFileSourceCallback in_pFileCreateFunc, + AkCreateBankSourceCallback in_pBankCreateFunc +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::RegisterGlobalCallback( + AkGlobalCallbackFunc in_pCallback, + AkUInt32 in_eLocation, + void* in_pCookie, + AkPluginType in_eType, + AkUInt32 in_ulCompanyID, + AkUInt32 in_ulPluginID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::UnregisterGlobalCallback( + AkGlobalCallbackFunc in_pCallback, + AkUInt32 in_eLocation +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::RegisterResourceMonitorCallback( + AkResourceMonitorCallbackFunc in_pCallback +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::UnregisterResourceMonitorCallback( + AkResourceMonitorCallbackFunc in_pCallback +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::RegisterAudioDeviceStatusCallback( + AK::AkDeviceStatusCallbackFunc in_pCallback +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::UnregisterAudioDeviceStatusCallback() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AkUInt32 FWwiseSoundEngineAPI_Null::GetIDFromString(const wchar_t* in_pszString) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return 0; +} +#endif //AK_SUPPORT_WCHAR + +AkUInt32 FWwiseSoundEngineAPI_Null::GetIDFromString(const char* in_pszString) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return 0; +} + +AkPlayingID FWwiseSoundEngineAPI_Null::PostEvent( + AkUniqueID in_eventID, + AkGameObjectID in_gameObjectID, + AkUInt32 in_uFlags, + AkCallbackFunc in_pfnCallback, + void* in_pCookie, + AkUInt32 in_cExternals, + AkExternalSourceInfo* in_pExternalSources, + AkPlayingID in_PlayingID +) +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseSoundEngineAPI_Null::PostEvent")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_INVALID_PLAYING_ID; +} + +#ifdef AK_SUPPORT_WCHAR +AkPlayingID FWwiseSoundEngineAPI_Null::PostEvent( + const wchar_t* in_pszEventName, + AkGameObjectID in_gameObjectID, + AkUInt32 in_uFlags, + AkCallbackFunc in_pfnCallback, + void* in_pCookie, + AkUInt32 in_cExternals, + AkExternalSourceInfo* in_pExternalSources, + AkPlayingID in_PlayingID +) +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseSoundEngineAPI_Null::PostEvent")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_INVALID_PLAYING_ID; +} +#endif //AK_SUPPORT_WCHAR + +AkPlayingID FWwiseSoundEngineAPI_Null::PostEvent( + const char* in_pszEventName, + AkGameObjectID in_gameObjectID, + AkUInt32 in_uFlags, + AkCallbackFunc in_pfnCallback, + void* in_pCookie, + AkUInt32 in_cExternals, + AkExternalSourceInfo* in_pExternalSources, + AkPlayingID in_PlayingID +) +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseSoundEngineAPI_Null::PostEvent")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_INVALID_PLAYING_ID; +} + +AKRESULT FWwiseSoundEngineAPI_Null::ExecuteActionOnEvent( + AkUniqueID in_eventID, + AK::SoundEngine::AkActionOnEventType in_ActionType, + AkGameObjectID in_gameObjectID, + AkTimeMs in_uTransitionDuration, + AkCurveInterpolation in_eFadeCurve, + AkPlayingID in_PlayingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::ExecuteActionOnEvent( + const wchar_t* in_pszEventName, + AK::SoundEngine::AkActionOnEventType in_ActionType, + AkGameObjectID in_gameObjectID, + AkTimeMs in_uTransitionDuration, + AkCurveInterpolation in_eFadeCurve, + AkPlayingID in_PlayingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::ExecuteActionOnEvent( + const char* in_pszEventName, + AK::SoundEngine::AkActionOnEventType in_ActionType, + AkGameObjectID in_gameObjectID, + AkTimeMs in_uTransitionDuration, + AkCurveInterpolation in_eFadeCurve, + AkPlayingID in_PlayingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AkPlayingID FWwiseSoundEngineAPI_Null::PostMIDIOnEvent( + AkUniqueID in_eventID, + AkGameObjectID in_gameObjectID, + AkMIDIPost* in_pPosts, + AkUInt16 in_uNumPosts, + bool in_bAbsoluteOffsets, + AkUInt32 in_uFlags, + AkCallbackFunc in_pfnCallback, + void* in_pCookie, + AkPlayingID in_playingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::StopMIDIOnEvent( + AkUniqueID in_eventID, + AkGameObjectID in_gameObjectID, + AkPlayingID in_playingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::PinEventInStreamCache( + AkUniqueID in_eventID, + AkPriority in_uActivePriority, + AkPriority in_uInactivePriority +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::PinEventInStreamCache( + const wchar_t* in_pszEventName, + AkPriority in_uActivePriority, + AkPriority in_uInactivePriority +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::PinEventInStreamCache( + const char* in_pszEventName, + AkPriority in_uActivePriority, + AkPriority in_uInactivePriority +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::UnpinEventInStreamCache( + AkUniqueID in_eventID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::UnpinEventInStreamCache( + const wchar_t* in_pszEventName +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::UnpinEventInStreamCache( + const char* in_pszEventName +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::GetBufferStatusForPinnedEvent( + AkUniqueID in_eventID, + AkReal32& out_fPercentBuffered, + bool& out_bCachePinnedMemoryFull +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::GetBufferStatusForPinnedEvent( + const char* in_pszEventName, + AkReal32& out_fPercentBuffered, + bool& out_bCachePinnedMemoryFull +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::GetBufferStatusForPinnedEvent( + const wchar_t* in_pszEventName, + AkReal32& out_fPercentBuffered, + bool& out_bCachePinnedMemoryFull +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif + +AKRESULT FWwiseSoundEngineAPI_Null::SeekOnEvent( + AkUniqueID in_eventID, + AkGameObjectID in_gameObjectID, + AkTimeMs in_iPosition, + bool in_bSeekToNearestMarker, + AkPlayingID in_PlayingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::SeekOnEvent( + const wchar_t* in_pszEventName, + AkGameObjectID in_gameObjectID, + AkTimeMs in_iPosition, + bool in_bSeekToNearestMarker, + AkPlayingID in_PlayingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::SeekOnEvent( + const char* in_pszEventName, + AkGameObjectID in_gameObjectID, + AkTimeMs in_iPosition, + bool in_bSeekToNearestMarker, + AkPlayingID in_PlayingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SeekOnEvent( + AkUniqueID in_eventID, + AkGameObjectID in_gameObjectID, + AkReal32 in_fPercent, + bool in_bSeekToNearestMarker, + AkPlayingID in_PlayingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::SeekOnEvent( + const wchar_t* in_pszEventName, + AkGameObjectID in_gameObjectID, + AkReal32 in_fPercent, + bool in_bSeekToNearestMarker, + AkPlayingID in_PlayingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::SeekOnEvent( + const char* in_pszEventName, + AkGameObjectID in_gameObjectID, + AkReal32 in_fPercent, + bool in_bSeekToNearestMarker, + AkPlayingID in_PlayingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +void FWwiseSoundEngineAPI_Null::CancelEventCallbackCookie( + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void FWwiseSoundEngineAPI_Null::CancelEventCallbackGameObject( + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void FWwiseSoundEngineAPI_Null::CancelEventCallback( + AkPlayingID in_playingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +AKRESULT FWwiseSoundEngineAPI_Null::GetSourcePlayPosition( + AkPlayingID in_PlayingID, + AkTimeMs* out_puPosition, + bool in_bExtrapolate +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::GetSourcePlayPositions( + AkPlayingID in_PlayingID, + AkSourcePosition* out_puPositions, + AkUInt32* io_pcPositions, + bool in_bExtrapolate +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::GetSourceStreamBuffering( + AkPlayingID in_PlayingID, + AkTimeMs& out_buffering, + bool& out_bIsBuffering +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +void FWwiseSoundEngineAPI_Null::StopAll( + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void FWwiseSoundEngineAPI_Null::StopPlayingID( + AkPlayingID in_playingID, + AkTimeMs in_uTransitionDuration, + AkCurveInterpolation in_eFadeCurve +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void FWwiseSoundEngineAPI_Null::ExecuteActionOnPlayingID( + AK::SoundEngine::AkActionOnEventType in_ActionType, + AkPlayingID in_playingID, + AkTimeMs in_uTransitionDuration, + AkCurveInterpolation in_eFadeCurve +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void FWwiseSoundEngineAPI_Null::SetRandomSeed( + AkUInt32 in_uSeed +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void FWwiseSoundEngineAPI_Null::MuteBackgroundMusic( + bool in_bMute +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +bool FWwiseSoundEngineAPI_Null::GetBackgroundMusicMute() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return false; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SendPluginCustomGameData( + AkUniqueID in_busID, + AkGameObjectID in_busObjectID, + AkPluginType in_eType, + AkUInt32 in_uCompanyID, + AkUInt32 in_uPluginID, + const void* in_pData, + AkUInt32 in_uSizeInBytes +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::RegisterGameObj( + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::RegisterGameObj( + AkGameObjectID in_gameObjectID, + const char* in_pszObjName +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::UnregisterGameObj( + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::UnregisterAllGameObj( +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetPosition( + AkGameObjectID in_GameObjectID, + const AkSoundPosition& in_Position, + AkSetPositionFlags in_eFlags +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetMultiplePositions( + AkGameObjectID in_GameObjectID, + const AkSoundPosition* in_pPositions, + AkUInt16 in_NumPositions, + AK::SoundEngine::MultiPositionType in_eMultiPositionType, + AkSetPositionFlags in_eFlags +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetMultiplePositions( + AkGameObjectID in_GameObjectID, + const AkChannelEmitter* in_pPositions, + AkUInt16 in_NumPositions, + AK::SoundEngine::MultiPositionType in_eMultiPositionType, + AkSetPositionFlags in_eFlags +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetScalingFactor( + AkGameObjectID in_GameObjectID, + AkReal32 in_fAttenuationScalingFactor +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetDistanceProbe( + AkGameObjectID in_listenerGameObjectID, + AkGameObjectID in_distanceProbeGameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::ClearBanks() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetBankLoadIOSettings( + AkReal32 in_fThroughput, + AkPriority in_priority +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::LoadBank( + const wchar_t* in_pszString, + AkBankID& out_bankID, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::LoadBank( + const char* in_pszString, + AkBankID& out_bankID, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::LoadBank( + AkBankID in_bankID, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::LoadBankMemoryView( + const void* in_pInMemoryBankPtr, + AkUInt32 in_uInMemoryBankSize, + AkBankID& out_bankID) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::LoadBankMemoryView( + const void* in_pInMemoryBankPtr, + AkUInt32 in_uInMemoryBankSize, + AkBankID& out_bankID, + AkBankType& out_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::LoadBankMemoryCopy( + const void* in_pInMemoryBankPtr, + AkUInt32 in_uInMemoryBankSize, + AkBankID& out_bankID) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::LoadBankMemoryCopy( + const void* in_pInMemoryBankPtr, + AkUInt32 in_uInMemoryBankSize, + AkBankID& out_bankID, + AkBankType& out_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::DecodeBank( + const void* in_pInMemoryBankPtr, + AkUInt32 in_uInMemoryBankSize, + AkMemPoolId in_uPoolForDecodedBank, + void*& out_pDecodedBankPtr, + AkUInt32& out_uDecodedBankSize +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::LoadBank( + const wchar_t* in_pszString, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + AkBankID& out_bankID, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::LoadBank( + const char* in_pszString, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + AkBankID& out_bankID, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::LoadBank( + AkBankID in_bankID, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::LoadBankMemoryView( + const void* in_pInMemoryBankPtr, + AkUInt32 in_uInMemoryBankSize, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + AkBankID& out_bankID, + AkBankType& out_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::LoadBankMemoryCopy( + const void* in_pInMemoryBankPtr, + AkUInt32 in_uInMemoryBankSize, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + AkBankID& out_bankID, + AkBankType& out_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::UnloadBank( + const wchar_t* in_pszString, + const void* in_pInMemoryBankPtr, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::UnloadBank( + const char* in_pszString, + const void* in_pInMemoryBankPtr, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::UnloadBank( + AkBankID in_bankID, + const void* in_pInMemoryBankPtr, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::UnloadBank( + const wchar_t* in_pszString, + const void* in_pInMemoryBankPtr, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::UnloadBank( + const char* in_pszString, + const void* in_pInMemoryBankPtr, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::UnloadBank( + AkBankID in_bankID, + const void* in_pInMemoryBankPtr, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +void FWwiseSoundEngineAPI_Null::CancelBankCallbackCookie( + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, + const wchar_t* in_pszString, + AK::SoundEngine::AkBankContent in_uFlags, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, + const char* in_pszString, + AK::SoundEngine::AkBankContent in_uFlags, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, + AkBankID in_bankID, + AK::SoundEngine::AkBankContent in_uFlags, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, + const wchar_t* in_pszString, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + AK::SoundEngine::AkBankContent in_uFlags, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, + const char* in_pszString, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + AK::SoundEngine::AkBankContent in_uFlags, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, + AkBankID in_bankID, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie, + AK::SoundEngine::AkBankContent in_uFlags, + AkBankType in_bankType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::ClearPreparedEvents() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, + const wchar_t** in_ppszString, + AkUInt32 in_uNumEvent +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, + const char** in_ppszString, + AkUInt32 in_uNumEvent +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, + AkUniqueID* in_pEventID, + AkUInt32 in_uNumEvent +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, + const wchar_t** in_ppszString, + AkUInt32 in_uNumEvent, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, + const char** in_ppszString, + AkUInt32 in_uNumEvent, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, + AkUniqueID* in_pEventID, + AkUInt32 in_uNumEvent, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetMedia( + AkSourceSettings* in_pSourceSettings, + AkUInt32 in_uNumSourceSettings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::UnsetMedia( + AkSourceSettings* in_pSourceSettings, + AkUInt32 in_uNumSourceSettings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::TryUnsetMedia( + AkSourceSettings* in_pSourceSettings, + AkUInt32 in_uNumSourceSettings, + AKRESULT* out_pUnsetResults +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, + AkGroupType in_eGameSyncType, + const wchar_t* in_pszGroupName, + const wchar_t** in_ppszGameSyncName, + AkUInt32 in_uNumGameSyncs +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, + AkGroupType in_eGameSyncType, + const char* in_pszGroupName, + const char** in_ppszGameSyncName, + AkUInt32 in_uNumGameSyncs +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, + AkGroupType in_eGameSyncType, + AkUInt32 in_GroupID, + AkUInt32* in_paGameSyncID, + AkUInt32 in_uNumGameSyncs +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, + AkGroupType in_eGameSyncType, + const wchar_t* in_pszGroupName, + const wchar_t** in_ppszGameSyncName, + AkUInt32 in_uNumGameSyncs, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, + AkGroupType in_eGameSyncType, + const char* in_pszGroupName, + const char** in_ppszGameSyncName, + AkUInt32 in_uNumGameSyncs, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, + AkGroupType in_eGameSyncType, + AkUInt32 in_GroupID, + AkUInt32* in_paGameSyncID, + AkUInt32 in_uNumGameSyncs, + AkBankCallbackFunc in_pfnBankCallback, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetListeners( + AkGameObjectID in_emitterGameObj, + const AkGameObjectID* in_pListenerGameObjs, + AkUInt32 in_uNumListeners +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::AddListener( + AkGameObjectID in_emitterGameObj, + AkGameObjectID in_listenerGameObj +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::RemoveListener( + AkGameObjectID in_emitterGameObj, + AkGameObjectID in_listenerGameObj +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetDefaultListeners( + const AkGameObjectID* in_pListenerObjs, + AkUInt32 in_uNumListeners +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::AddDefaultListener( + AkGameObjectID in_listenerGameObj +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::RemoveDefaultListener( + AkGameObjectID in_listenerGameObj +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::ResetListenersToDefault( + AkGameObjectID in_emitterGameObj +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetListenerSpatialization( + AkGameObjectID in_uListenerID, + bool in_bSpatialized, + AkChannelConfig in_channelConfig, + AK::SpeakerVolumes::VectorPtr in_pVolumeOffsets +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetRTPCValue( + AkRtpcID in_rtpcID, + AkRtpcValue in_value, + AkGameObjectID in_gameObjectID, + AkTimeMs in_uValueChangeDuration, + AkCurveInterpolation in_eFadeCurve, + bool in_bBypassInternalValueInterpolation +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::SetRTPCValue( + const wchar_t* in_pszRtpcName, + AkRtpcValue in_value, + AkGameObjectID in_gameObjectID, + AkTimeMs in_uValueChangeDuration, + AkCurveInterpolation in_eFadeCurve, + bool in_bBypassInternalValueInterpolation +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::SetRTPCValue( + const char* in_pszRtpcName, + AkRtpcValue in_value, + AkGameObjectID in_gameObjectID, + AkTimeMs in_uValueChangeDuration, + AkCurveInterpolation in_eFadeCurve, + bool in_bBypassInternalValueInterpolation +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetRTPCValueByPlayingID( + AkRtpcID in_rtpcID, + AkRtpcValue in_value, + AkPlayingID in_playingID, + AkTimeMs in_uValueChangeDuration, + AkCurveInterpolation in_eFadeCurve, + bool in_bBypassInternalValueInterpolation +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::SetRTPCValueByPlayingID( + const wchar_t* in_pszRtpcName, + AkRtpcValue in_value, + AkPlayingID in_playingID, + AkTimeMs in_uValueChangeDuration, + AkCurveInterpolation in_eFadeCurve, + bool in_bBypassInternalValueInterpolation +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::SetRTPCValueByPlayingID( + const char* in_pszRtpcName, + AkRtpcValue in_value, + AkPlayingID in_playingID, + AkTimeMs in_uValueChangeDuration, + AkCurveInterpolation in_eFadeCurve, + bool in_bBypassInternalValueInterpolation +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::ResetRTPCValue( + AkRtpcID in_rtpcID, + AkGameObjectID in_gameObjectID, + AkTimeMs in_uValueChangeDuration, + AkCurveInterpolation in_eFadeCurve, + bool in_bBypassInternalValueInterpolation +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::ResetRTPCValue( + const wchar_t* in_pszRtpcName, + AkGameObjectID in_gameObjectID, + AkTimeMs in_uValueChangeDuration, + AkCurveInterpolation in_eFadeCurve, + bool in_bBypassInternalValueInterpolation +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::ResetRTPCValue( + const char* in_pszRtpcName, + AkGameObjectID in_gameObjectID, + AkTimeMs in_uValueChangeDuration, + AkCurveInterpolation in_eFadeCurve, + bool in_bBypassInternalValueInterpolation +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetSwitch( + AkSwitchGroupID in_switchGroup, + AkSwitchStateID in_switchState, + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::SetSwitch( + const wchar_t* in_pszSwitchGroup, + const wchar_t* in_pszSwitchState, + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::SetSwitch( + const char* in_pszSwitchGroup, + const char* in_pszSwitchState, + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::PostTrigger( + AkTriggerID in_triggerID, + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::PostTrigger( + const wchar_t* in_pszTrigger, + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::PostTrigger( + const char* in_pszTrigger, + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetState( + AkStateGroupID in_stateGroup, + AkStateID in_state +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::SetState( + const wchar_t* in_pszStateGroup, + const wchar_t* in_pszState +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::SetState( + const char* in_pszStateGroup, + const char* in_pszState +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetGameObjectAuxSendValues( + AkGameObjectID in_gameObjectID, + AkAuxSendValue* in_aAuxSendValues, + AkUInt32 in_uNumSendValues +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::RegisterBusVolumeCallback( + AkUniqueID in_busID, + AkBusCallbackFunc in_pfnCallback, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::RegisterBusMeteringCallback( + AkUniqueID in_busID, + AkBusMeteringCallbackFunc in_pfnCallback, + AkMeteringFlags in_eMeteringFlags, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::RegisterOutputDeviceMeteringCallback( + AkOutputDeviceID in_idOutput, + AkOutputDeviceMeteringCallbackFunc in_pfnCallback, + AkMeteringFlags in_eMeteringFlags, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetGameObjectOutputBusVolume( + AkGameObjectID in_emitterObjID, + AkGameObjectID in_listenerObjID, + AkReal32 in_fControlValue +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetActorMixerEffect( + AkUniqueID in_audioNodeID, + AkUInt32 in_uFXIndex, + AkUniqueID in_shareSetID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetBusEffect( + AkUniqueID in_audioNodeID, + AkUInt32 in_uFXIndex, + AkUniqueID in_shareSetID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::SetBusEffect( + const wchar_t* in_pszBusName, + AkUInt32 in_uFXIndex, + AkUniqueID in_shareSetID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::SetBusEffect( + const char* in_pszBusName, + AkUInt32 in_uFXIndex, + AkUniqueID in_shareSetID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetOutputDeviceEffect( + AkOutputDeviceID in_outputDeviceID, + AkUInt32 in_uFXIndex, + AkUniqueID in_FXShareSetID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetMixer( + AkUniqueID in_audioNodeID, + AkUniqueID in_shareSetID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::SetMixer( + const wchar_t* in_pszBusName, + AkUniqueID in_shareSetID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::SetMixer( + const char* in_pszBusName, + AkUniqueID in_shareSetID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetBusConfig( + AkUniqueID in_audioNodeID, + AkChannelConfig in_channelConfig +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::SetBusConfig( + const wchar_t* in_pszBusName, + AkChannelConfig in_channelConfig +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::SetBusConfig( + const char* in_pszBusName, + AkChannelConfig in_channelConfig +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetObjectObstructionAndOcclusion( + AkGameObjectID in_EmitterID, + AkGameObjectID in_ListenerID, + AkReal32 in_fObstructionLevel, + AkReal32 in_fOcclusionLevel +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetMultipleObstructionAndOcclusion( + AkGameObjectID in_EmitterID, + AkGameObjectID in_uListenerID, + AkObstructionOcclusionValues* in_fObstructionAndOcclusionValues, + AkUInt32 in_uNumObstructionAndOcclusion +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::GetContainerHistory( + AK::IWriteBytes* in_pBytes +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetContainerHistory( + AK::IReadBytes* in_pBytes +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::StartOutputCapture( + const AkOSChar* in_CaptureFileName +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::StopOutputCapture() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::AddOutputCaptureMarker( + const char* in_MarkerText +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AkUInt32 FWwiseSoundEngineAPI_Null::GetSampleRate() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::RegisterCaptureCallback( + AkCaptureCallbackFunc in_pfnCallback, + AkOutputDeviceID in_idOutput, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::UnregisterCaptureCallback( + AkCaptureCallbackFunc in_pfnCallback, + AkOutputDeviceID in_idOutput, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::StartProfilerCapture( + const AkOSChar* in_CaptureFileName +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::StopProfilerCapture() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetOfflineRenderingFrameTime( + AkReal32 in_fFrameTimeInSeconds +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetOfflineRendering( + bool in_bEnableOfflineRendering +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::AddOutput( + const AkOutputSettings& in_Settings, + AkOutputDeviceID* out_pDeviceID, + const AkGameObjectID* in_pListenerIDs, + AkUInt32 in_uNumListeners +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::RemoveOutput( + AkOutputDeviceID in_idOutput +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::ReplaceOutput( + const AkOutputSettings& in_Settings, + AkOutputDeviceID in_outputDeviceId, + AkOutputDeviceID* out_pOutputDeviceId +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AkOutputDeviceID FWwiseSoundEngineAPI_Null::GetOutputID( + AkUniqueID in_idShareSet, + AkUInt32 in_idDevice +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_INVALID_DEVICE_ID; +} + +AkOutputDeviceID FWwiseSoundEngineAPI_Null::GetOutputID( + const char* in_szShareSet, + AkUInt32 in_idDevice +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_INVALID_DEVICE_ID; +} + +#ifdef AK_SUPPORT_WCHAR +AkOutputDeviceID FWwiseSoundEngineAPI_Null::GetOutputID( + const wchar_t* in_szShareSet, + AkUInt32 in_idDevice +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_INVALID_DEVICE_ID; +} +#endif + +AKRESULT FWwiseSoundEngineAPI_Null::SetBusDevice( + AkUniqueID in_idBus, + AkUniqueID in_idNewDevice +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetBusDevice( + const char* in_BusName, + const char* in_DeviceName +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::SetBusDevice( + const wchar_t* in_BusName, + const wchar_t* in_DeviceName +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif + +AKRESULT FWwiseSoundEngineAPI_Null::GetDeviceList( + AkUInt32 in_ulCompanyID, + AkUInt32 in_ulPluginID, + AkUInt32& io_maxNumDevices, + AkDeviceDescription* out_deviceDescriptions +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::GetDeviceList( + AkUniqueID in_audioDeviceShareSetID, + AkUInt32& io_maxNumDevices, + AkDeviceDescription* out_deviceDescriptions +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::SetOutputVolume( + AkOutputDeviceID in_idOutput, + AkReal32 in_fVolume +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::GetDeviceSpatialAudioSupport( + AkUInt32 in_idDevice) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::Suspend( + bool in_bRenderAnyway, + bool in_bFadeOut +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::WakeupFromSuspend( + AkUInt32 in_uDelayMs +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AkUInt32 FWwiseSoundEngineAPI_Null::GetBufferTick() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return 0; +} + +AkUInt64 FWwiseSoundEngineAPI_Null::GetSampleTick() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return 0; +} + +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::GetPosition( + AkGameObjectID in_GameObjectID, + AkSoundPosition& out_rPosition +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::GetListeners( + AkGameObjectID in_GameObjectID, + AkGameObjectID* out_ListenerObjectIDs, + AkUInt32& oi_uNumListeners +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::GetListenerPosition( + AkGameObjectID in_uIndex, + AkListenerPosition& out_rPosition +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::GetListenerSpatialization( + AkUInt32 in_uIndex, + bool& out_rbSpatialized, + AK::SpeakerVolumes::VectorPtr& out_pVolumeOffsets, + AkChannelConfig& out_channelConfig +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::GetRTPCValue( + AkRtpcID in_rtpcID, + AkGameObjectID in_gameObjectID, + AkPlayingID in_playingID, + AkRtpcValue& out_rValue, + AK::SoundEngine::Query::RTPCValue_type& io_rValueType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::GetRTPCValue( + const wchar_t* in_pszRtpcName, + AkGameObjectID in_gameObjectID, + AkPlayingID in_playingID, + AkRtpcValue& out_rValue, + AK::SoundEngine::Query::RTPCValue_type& io_rValueType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::GetRTPCValue( + const char* in_pszRtpcName, + AkGameObjectID in_gameObjectID, + AkPlayingID in_playingID, + AkRtpcValue& out_rValue, + AK::SoundEngine::Query::RTPCValue_type& io_rValueType +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::GetSwitch( + AkSwitchGroupID in_switchGroup, + AkGameObjectID in_gameObjectID, + AkSwitchStateID& out_rSwitchState +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::GetSwitch( + const wchar_t* in_pstrSwitchGroupName, + AkGameObjectID in_GameObj, + AkSwitchStateID& out_rSwitchState +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::GetSwitch( + const char* in_pstrSwitchGroupName, + AkGameObjectID in_GameObj, + AkSwitchStateID& out_rSwitchState +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::GetState( + AkStateGroupID in_stateGroup, + AkStateID& out_rState +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::GetState( + const wchar_t* in_pstrStateGroupName, + AkStateID& out_rState +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::GetState( + const char* in_pstrStateGroupName, + AkStateID& out_rState +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::GetGameObjectAuxSendValues( + AkGameObjectID in_gameObjectID, + AkAuxSendValue* out_paAuxSendValues, + AkUInt32& io_ruNumSendValues +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::GetGameObjectDryLevelValue( + AkGameObjectID in_EmitterID, + AkGameObjectID in_ListenerID, + AkReal32& out_rfControlValue +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::GetObjectObstructionAndOcclusion( + AkGameObjectID in_EmitterID, + AkGameObjectID in_ListenerID, + AkReal32& out_rfObstructionLevel, + AkReal32& out_rfOcclusionLevel +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::QueryAudioObjectIDs( + AkUniqueID in_eventID, + AkUInt32& io_ruNumItems, + AkObjectInfo* out_aObjectInfos +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +#ifdef AK_SUPPORT_WCHAR +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::QueryAudioObjectIDs( + const wchar_t* in_pszEventName, + AkUInt32& io_ruNumItems, + AkObjectInfo* out_aObjectInfos +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} +#endif //AK_SUPPORT_WCHAR + +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::QueryAudioObjectIDs( + const char* in_pszEventName, + AkUInt32& io_ruNumItems, + AkObjectInfo* out_aObjectInfos +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::GetPositioningInfo( + AkUniqueID in_ObjectID, + AkPositioningInfo& out_rPositioningInfo +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::GetActiveGameObjects( + FAkGameObjectsList& io_GameObjectList +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +bool FWwiseSoundEngineAPI_Null::FQuery::GetIsGameObjectActive( + AkGameObjectID in_GameObjId +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return false; +} + +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::GetMaxRadius( + FAkRadiusList& io_RadiusList +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AkReal32 FWwiseSoundEngineAPI_Null::FQuery::GetMaxRadius( + AkGameObjectID in_GameObjId +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return .0f; +} + +AkUniqueID FWwiseSoundEngineAPI_Null::FQuery::GetEventIDFromPlayingID( + AkPlayingID in_playingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_INVALID_UNIQUE_ID; +} + +AkGameObjectID FWwiseSoundEngineAPI_Null::FQuery::GetGameObjectFromPlayingID( + AkPlayingID in_playingID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_INVALID_GAME_OBJECT; +} + +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::GetPlayingIDsFromGameObject( + AkGameObjectID in_GameObjId, + AkUInt32& io_ruNumIDs, + AkPlayingID* out_aPlayingIDs +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::GetCustomPropertyValue( + AkUniqueID in_ObjectID, + AkUInt32 in_uPropID, + AkInt32& out_iValue +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSoundEngineAPI_Null::FQuery::GetCustomPropertyValue( + AkUniqueID in_ObjectID, + AkUInt32 in_uPropID, + AkReal32& out_fValue +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +void FWwiseSoundEngineAPI_Null::FAudioInputPlugin::SetAudioInputCallbacks( + AkAudioInputPluginExecuteCallbackFunc in_pfnExecCallback, + AkAudioInputPluginGetFormatCallbackFunc in_pfnGetFormatCallback /*= nullptr */, + AkAudioInputPluginGetGainCallbackFunc in_pfnGetGainCallback /*= nullptr*/ +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + + +#if WITH_EDITORONLY_DATA +FWwiseSoundEngineAPI_Null::FErrorTranslator::FErrorTranslator(FGetInfoErrorMessageTranslatorFunction InMessageTranslatorFunction) : + GetInfoErrorMessageTranslatorFunction(InMessageTranslatorFunction) +{ +} + +bool FWwiseSoundEngineAPI_Null::FErrorTranslator::GetInfo(TagInformation* in_pTagList, AkUInt32 in_uCount, + AkUInt32& out_uTranslated) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return false; +} +#endif + +AkErrorMessageTranslator* FWwiseSoundEngineAPI_Null::NewErrorMessageTranslator(FGetInfoErrorMessageTranslatorFunction InMessageTranslatorFunction) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return nullptr; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WwiseSpatialAudioAPI_Null.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WwiseSpatialAudioAPI_Null.cpp new file mode 100644 index 0000000..a7c64b9 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WwiseSpatialAudioAPI_Null.cpp @@ -0,0 +1,333 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_Null/WwiseSpatialAudioAPI_Null.h" +#include "Wwise/Stats/SoundEngine_Null.h" + +FWwiseSpatialAudioAPI_Null::FWwiseSpatialAudioAPI_Null() : + IWwiseSpatialAudioAPI(new FReverbEstimation) +{} + +AKRESULT FWwiseSpatialAudioAPI_Null::Init(const AkSpatialAudioInitSettings& in_initSettings) +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseSpatialAudioAPI_Null::Init")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::RegisterListener( + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::UnregisterListener( + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::SetGameObjectRadius( + AkGameObjectID in_gameObjectID, + AkReal32 in_outerRadius, + AkReal32 in_innerRadius +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::SetImageSource( + AkImageSourceID in_srcID, + const AkImageSourceSettings& in_info, + const char* in_name, + AkUniqueID in_AuxBusID, + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::RemoveImageSource( + AkImageSourceID in_srcID, + AkUniqueID in_AuxBusID, + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::ClearImageSources( + AkUniqueID in_AuxBusID, + AkGameObjectID in_gameObjectID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::SetGeometry( + AkGeometrySetID in_GeomSetID, + const AkGeometryParams& in_params +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::RemoveGeometry( + AkGeometrySetID in_SetID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::SetGeometryInstance( + AkGeometryInstanceID in_GeometryInstanceID, + const AkGeometryInstanceParams& in_params +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::RemoveGeometryInstance( + AkGeometryInstanceID in_GeometryInstanceID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::QueryReflectionPaths( + AkGameObjectID in_gameObjectID, + AkUInt32 in_positionIndex, + AkVector64& out_listenerPos, + AkVector64& out_emitterPos, + AkReflectionPathInfo* out_aPaths, + AkUInt32& io_uArraySize +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::SetRoom( + AkRoomID in_RoomID, + const AkRoomParams& in_Params, + const char* in_RoomName +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::RemoveRoom( + AkRoomID in_RoomID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::SetPortal( + AkPortalID in_PortalID, + const AkPortalParams& in_Params, + const char* in_PortalName +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::RemovePortal( + AkPortalID in_PortalID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::SetGameObjectInRoom( + AkGameObjectID in_gameObjectID, + AkRoomID in_CurrentRoomID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::SetReflectionsOrder( + AkUInt32 in_uReflectionsOrder, + bool in_bUpdatePaths +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::SetDiffractionOrder( + AkUInt32 in_uDiffractionOrder, + bool in_bUpdatePaths +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::SetNumberOfPrimaryRays( + AkUInt32 in_uNbPrimaryRays +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::SetLoadBalancingSpread( + AkUInt32 in_uNbFrames +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::SetEarlyReflectionsAuxSend( + AkGameObjectID in_gameObjectID, + AkAuxBusID in_auxBusID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::SetEarlyReflectionsVolume( + AkGameObjectID in_gameObjectID, + AkReal32 in_fSendVolume +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::SetPortalObstructionAndOcclusion( + AkPortalID in_PortalID, + AkReal32 in_fObstruction, + AkReal32 in_fOcclusion +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::SetGameObjectToPortalObstruction( + AkGameObjectID in_gameObjectID, + AkPortalID in_PortalID, + AkReal32 in_fObstruction +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::SetPortalToPortalObstruction( + AkPortalID in_PortalID0, + AkPortalID in_PortalID1, + AkReal32 in_fObstruction +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::QueryWetDiffraction( + AkPortalID in_portal, + AkReal32& out_wetDiffraction +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::QueryDiffractionPaths( + AkGameObjectID in_gameObjectID, + AkUInt32 in_positionIndex, + AkVector64& out_listenerPos, + AkVector64& out_emitterPos, + AkDiffractionPathInfo* out_aPaths, + AkUInt32& io_uArraySize +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::ResetStochasticEngine() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +float FWwiseSpatialAudioAPI_Null::FReverbEstimation::CalculateSlope(const AkAcousticTexture& texture) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return .0f; +} + +void FWwiseSpatialAudioAPI_Null::FReverbEstimation::GetAverageAbsorptionValues(AkAcousticTexture* in_textures, float* in_surfaceAreas, int in_numTextures, AkAcousticTexture& out_average) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +AKRESULT FWwiseSpatialAudioAPI_Null::FReverbEstimation::EstimateT60Decay( + AkReal32 in_volumeCubicMeters, + AkReal32 in_surfaceAreaSquaredMeters, + AkReal32 in_environmentAverageAbsorption, + AkReal32& out_decayEstimate +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::FReverbEstimation::EstimateTimeToFirstReflection( + AkVector in_environmentExtentMeters, + AkReal32& out_timeToFirstReflectionMs, + AkReal32 in_speedOfSound +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseSpatialAudioAPI_Null::FReverbEstimation::EstimateHFDamping( + AkAcousticTexture* in_textures, + float* in_surfaceAreas, + int in_numTextures, + AkReal32& out_hfDamping + +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WwiseStreamMgrAPI_Null.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WwiseStreamMgrAPI_Null.cpp new file mode 100644 index 0000000..beaf5bf --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/API_Null/WwiseStreamMgrAPI_Null.cpp @@ -0,0 +1,120 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/API_Null/WwiseStreamMgrAPI_Null.h" +#include "Wwise/Stats/SoundEngine_Null.h" + +AK::IAkStreamMgr* FWwiseStreamMgrAPI_Null::GetAkStreamMgrInstance() +{ + IWwiseSoundEngineModule::ForceLoadModule(); + return nullptr; +} + +AK::IAkStreamMgr* FWwiseStreamMgrAPI_Null::Create( + const AkStreamMgrSettings& in_settings +) +{ + SCOPED_WWISESOUNDENGINE_EVENT(TEXT("FWwiseStreamMgrAPI_Null::Create")); + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return nullptr; +} + +void FWwiseStreamMgrAPI_Null::GetDefaultSettings( + AkStreamMgrSettings& out_settings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +AK::StreamMgr::IAkFileLocationResolver* FWwiseStreamMgrAPI_Null::GetFileLocationResolver() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return nullptr; +} + +void FWwiseStreamMgrAPI_Null::SetFileLocationResolver( + AK::StreamMgr::IAkFileLocationResolver* in_pFileLocationResolver +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +AkDeviceID FWwiseStreamMgrAPI_Null::CreateDevice( + const AkDeviceSettings& in_settings, + AK::StreamMgr::IAkLowLevelIOHook* in_pLowLevelHook +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_INVALID_OUTPUT_DEVICE_ID; +} + +AKRESULT FWwiseStreamMgrAPI_Null::DestroyDevice( + AkDeviceID in_deviceID +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +AKRESULT FWwiseStreamMgrAPI_Null::PerformIO() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +void FWwiseStreamMgrAPI_Null::GetDefaultDeviceSettings( + AkDeviceSettings& out_settings +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +AKRESULT FWwiseStreamMgrAPI_Null::SetCurrentLanguage( + const AkOSChar* in_pszLanguageName +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +const AkOSChar* FWwiseStreamMgrAPI_Null::GetCurrentLanguage() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return nullptr; +} + +AKRESULT FWwiseStreamMgrAPI_Null::AddLanguageChangeObserver( + AK::StreamMgr::AkLanguageChangeHandler in_handler, + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); + return AK_NotImplemented; +} + +void FWwiseStreamMgrAPI_Null::RemoveLanguageChangeObserver( + void* in_pCookie +) +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + +void FWwiseStreamMgrAPI_Null::FlushAllCaches() +{ + SCOPE_CYCLE_COUNTER(STAT_WwiseSoundEngineAPI_Null); +} + diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/DefineNullBridges.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/DefineNullBridges.cpp new file mode 100644 index 0000000..0b7b024 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/DefineNullBridges.cpp @@ -0,0 +1,21 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +// This actually includes .cpp files +#if AK_USE_NULL_SOUNDENGINE +#include "Wwise/DefineNullBridges.inl" +#endif \ No newline at end of file diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/Stats/SoundEngine_Null.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/Stats/SoundEngine_Null.cpp new file mode 100644 index 0000000..0ccb964 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/Stats/SoundEngine_Null.cpp @@ -0,0 +1,20 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/Stats/SoundEngine_Null.h" + +DEFINE_STAT(STAT_WwiseSoundEngineAPI_Null); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/WwiseSoundEngine_Null.cpp b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/WwiseSoundEngine_Null.cpp new file mode 100644 index 0000000..36922c7 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Private/Wwise/WwiseSoundEngine_Null.cpp @@ -0,0 +1,79 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#include "Wwise/WwiseSoundEngine_Null.h" +#include "Wwise/WwiseSoundEngineModule.h" +#include "Wwise/API_Null/WwiseCommAPI_Null.h" +#include "Wwise/API_Null/WwiseMemoryMgrAPI_Null.h" +#include "Wwise/API_Null/WwiseMonitorAPI_Null.h" +#include "Wwise/API_Null/WwiseMusicEngineAPI_Null.h" +#include "Wwise/API_Null/WwiseSoundEngineAPI_Null.h" +#include "Wwise/API_Null/WwiseSpatialAudioAPI_Null.h" +#include "Wwise/API_Null/WwiseStreamMgrAPI_Null.h" +#include "Wwise/API_Null/WwisePlatformAPI_Null.h" +#include "Wwise/API_Null/WAAPI_Null.h" + +IWwiseCommAPI* FWwiseSoundEngine_Null::GetComm() +{ + return new FWwiseCommAPI_Null; +} + +IWwiseMemoryMgrAPI* FWwiseSoundEngine_Null::GetMemoryMgr() +{ + return new FWwiseMemoryMgrAPI_Null; +} + +IWwiseMonitorAPI* FWwiseSoundEngine_Null::GetMonitor() +{ + return new FWwiseMonitorAPI_Null; +} + +IWwiseMusicEngineAPI* FWwiseSoundEngine_Null::GetMusicEngine() +{ + return new FWwiseMusicEngineAPI_Null; +} + +IWwiseSoundEngineAPI* FWwiseSoundEngine_Null::GetSoundEngine() +{ + return new FWwiseSoundEngineAPI_Null; +} + +IWwiseSpatialAudioAPI* FWwiseSoundEngine_Null::GetSpatialAudio() +{ + return new FWwiseSpatialAudioAPI_Null; +} + +IWwiseStreamMgrAPI* FWwiseSoundEngine_Null::GetStreamMgr() +{ + return new FWwiseStreamMgrAPI_Null; +} + +IWwisePlatformAPI* FWwiseSoundEngine_Null::GetPlatform() +{ + // Nulls are platform-independent. + // return new FWwisePlatformAPI; + return nullptr; +} + +IWAAPI* FWwiseSoundEngine_Null::GetWAAPI() +{ +#if AK_SUPPORT_WAAPI + return new FWAAPI_Null; +#else + return nullptr; +#endif +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WAAPI_Null.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WAAPI_Null.h new file mode 100644 index 0000000..c497b8e --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WAAPI_Null.h @@ -0,0 +1,34 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/WAAPI.h" + +#if AK_SUPPORT_WAAPI +class WWISESOUNDENGINE_API FWAAPI_Null : public IWAAPI +{ +public: + UE_NONCOPYABLE(FWAAPI_Null); + FWAAPI_Null() = default; + ~FWAAPI_Null() override {} + + Client* NewClient() override; + + std::string GetJsonString(const AK::WwiseAuthoringAPI::JsonProvider&) override; +}; +#endif diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwiseCommAPI_Null.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwiseCommAPI_Null.h new file mode 100644 index 0000000..72b370b --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwiseCommAPI_Null.h @@ -0,0 +1,95 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/WwiseCommAPI.h" + +class WWISESOUNDENGINE_API FWwiseCommAPI_Null : public IWwiseCommAPI +{ +public: + UE_NONCOPYABLE(FWwiseCommAPI_Null); + FWwiseCommAPI_Null() = default; + + /////////////////////////////////////////////////////////////////////// + /// @name Initialization + //@{ + + /// Initializes the communication module. When this is called, and AK::SoundEngine::RenderAudio() + /// is called periodically, you may use the authoring tool to connect to the sound engine. + /// + /// \warning This function must be called after the sound engine and memory manager have + /// been properly initialized. + /// + /// + /// \remark The AkCommSettings structure should be initialized with + /// AK::Comm::GetDefaultInitSettings(). You can then change some of the parameters + /// before calling this function. + /// + /// \return + /// - AK_Success if initialization was successful. + /// - AK_InvalidParameter if one of the settings is invalid. + /// - AK_InsufficientMemory if the specified pool size is too small for initialization. + /// - AK_Fail for other errors. + /// + /// \sa + /// - \ref initialization_comm + /// - AK::Comm::GetDefaultInitSettings() + /// - AkCommSettings::Ports + AKRESULT Init( + const AkCommSettings& in_settings///< Initialization settings. + ) override; + + /// Gets the last error from the OS-specific communication library. + /// \return The system error code. Check the code in the platform manufacturer documentation for details about the error. + AkInt32 GetLastError() override; + + /// Gets the communication module's default initialization settings values. + /// \sa + /// - \ref initialization_comm + /// - AK::Comm::Init() + void GetDefaultInitSettings( + AkCommSettings& out_settings ///< Returned default initialization settings. + ) override; + + /// Terminates the communication module. + /// \warning This function must be called before the memory manager is terminated. + /// \sa + /// - \ref termination_comm + void Term() override; + + /// Terminates and reinitialize the communication module using current settings. + /// + /// \return + /// - AK_Success if initialization was successful. + /// - AK_InvalidParameter if one of the settings is invalid. + /// - AK_InsufficientMemory if the specified pool size is too small for initialization. + /// - AK_Fail for other errors. + /// + /// \sa + /// - \ref AK::SoundEngine::iOS::WakeupFromSuspend() + AKRESULT Reset() override; + + + /// Get the initialization settings currently in use by the CommunicationSystem + /// + /// \return + /// - AK_Success if initialization was successful. + const AkCommSettings& GetCurrentSettings() override; + + //@} +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwiseMemoryMgrAPI_Null.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwiseMemoryMgrAPI_Null.h new file mode 100644 index 0000000..dcbae75 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwiseMemoryMgrAPI_Null.h @@ -0,0 +1,226 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/WwiseMemoryMgrAPI.h" + +class WWISESOUNDENGINE_API FWwiseMemoryMgrAPI_Null : public IWwiseMemoryMgrAPI +{ +public: + UE_NONCOPYABLE(FWwiseMemoryMgrAPI_Null); + FWwiseMemoryMgrAPI_Null() = default; + + /// Initialize the default implementation of the Memory Manager. + /// \sa AK::MemoryMgr + AKRESULT Init( + AkMemSettings* in_pSettings ///< Memory manager initialization settings. + ) override; + + /// Obtain the default initialization settings for the default implementation of the Memory Manager. + void GetDefaultSettings( + AkMemSettings& out_pMemSettings ///< Memory manager default initialization settings. + ) override; + + //////////////////////////////////////////////////////////////////////// + /// @name Initialization + //@{ + + /// Query whether the Memory Manager has been sucessfully initialized. + /// \warning This function is not thread-safe. It should not be called at the same time as MemoryMgr::Init or MemoryMgr::Term. + /// \return True if the Memory Manager is initialized, False otherwise + /// \sa + /// - AK::MemoryMgr::Init() + /// - \ref memorymanager + bool IsInitialized() override; + + /// Terminate the Memory Manager. + /// \warning This function is not thread-safe. It is not valid to allocate memory or otherwise interact with the memory manager during or after this call. + /// \sa + /// - \ref memorymanager + void Term() override; + + /// Performs whatever steps are required to initialize a thread for use with the memory manager. + /// For example initializing thread local storage that the allocator requires to work. + /// The default implementation of the memory manager performs thread initialization automatically and therefore this call is optional. + /// For implementations where the cost of automatically initializing a thread for use with an allocator would be prohibitively expensive + /// this call allows you to perform the initialization once during, for example, thread creation. + /// \sa + /// - AkMemInitForThread + void InitForThread() override; + + /// Allows you to manually terminate a thread for use with the memory manager. + /// The default implementation of the memory manager requires that all threads that interact with the memory manager call this function prior + /// to either their termination or the termination of the memory manager. Threads not created by the sound engine itself will not have this + /// function called for them automatically. + /// Take care to call this function for any thread, not owned by wwise, that may have interacted with the memory manager. For example job system workers. + /// \sa + /// - AkMemTermForThread + void TermForThread() override; + + /// Allows you to "trim" a thread being used with the memory manager. + /// This is a function that will be called periodically by some Wwise-owned threads, + /// so that any thread-local state can be cleaned up in order to return memory for other systems to use. + /// For example, this can be used to return thread-local heaps to global stores or to finalize other deferred operations. + /// This function is only required for optimization purposes and does not have to be defined. + /// Therefore, unlike TermForThread, this is not expected to be called in all scenarios by Wwise. + /// It is also recommended to be called by game engine integrations in any worker threads that run Wwise jobs. + /// Refer to \ref eventmgrthread_jobmgr_best_practices for more information. + /// \sa + /// - AkMemTrimForThread + void TrimForThread() override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Memory Allocation + //@{ + + /// Allocate memory: debug version. + /// \return A pointer to the start of the allocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + void* dMalloc( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + size_t in_uSize, ///< Number of bytes to allocate + const char* in_pszFile, ///< Debug file name + AkUInt32 in_uLine ///< Debug line number + ) override; + + /// Allocate memory. + /// \return A pointer to the start of the allocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + void* Malloc( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + size_t in_uSize ///< Number of bytes to allocate + ) override; + + /// Reallocate memory: debug version. + /// \return A pointer to the start of the reallocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + void* dRealloc( + AkMemPoolId in_poolId, + void* in_pAlloc, + size_t in_uSize, + const char* in_pszFile, + AkUInt32 in_uLine + ) override; + + /// Reallocate memory. + /// \return A pointer to the start of the reallocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + void* Realloc( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + void* in_pAlloc, ///< Pointer to the start of the allocated memory + size_t in_uSize ///< Number of bytes to allocate + ) override; + + /// Reallocate memory: debug version. + /// \return A pointer to the start of the reallocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + void* dReallocAligned( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + void* in_pAlloc, ///< Pointer to the start of the allocated memory + size_t in_uSize, ///< Number of bytes to allocate + AkUInt32 in_uAlignment, ///< Alignment (in bytes) + const char* in_pszFile, ///< Debug file name + AkUInt32 in_uLine ///< Debug line number + ) override; + + /// Reallocate memory. + /// \return A pointer to the start of the reallocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + void* ReallocAligned( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + void* in_pAlloc, ///< Pointer to the start of the allocated memory + size_t in_uSize, ///< Number of bytes to allocate + AkUInt32 in_uAlignment ///< Alignment (in bytes) + ) override; + + /// Free memory allocated with the memory manager. + /// \sa + /// - \ref memorymanager + void Free( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + void* in_pMemAddress ///< Pointer to the start of memory + ) override; + + /// Allocate memory with a specific alignment. debug version. + /// \return A pointer to the start of the allocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + void* dMalign( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + size_t in_uSize, ///< Number of bytes to allocate + AkUInt32 in_uAlignment, ///< Alignment (in bytes) + const char* in_pszFile, ///< Debug file name + AkUInt32 in_uLine ///< Debug line number + ) override; + + /// Allocate memory with a specific alignment. + /// \return A pointer to the start of the allocated memory (NULL if the allocation could not be completed) + /// \sa + /// - \ref memorymanager + void* Malign( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + size_t in_uSize, ///< Number of bytes to allocate + AkUInt32 in_uAlignment ///< Alignment (in bytes) + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Memory Profiling + //@{ + + /// Get statistics for a given memory category. + /// \note Be aware of the potentially incoherent nature of reporting such information during concurrent modification by multiple threads. + void GetCategoryStats( + AkMemPoolId in_poolId, ///< ID of the memory category (AkMemID) + AK::MemoryMgr::CategoryStats& out_poolStats ///< Returned statistics. + ) override; + + /// Get statistics for overall memory manager usage. + /// \note Be aware of the potentially incoherent nature of reporting such information during concurrent modification by multiple threads. + void GetGlobalStats( + AK::MemoryMgr::GlobalStats& out_stats ///< Returned statistics. + ) override; + + /// Called to start profiling memory usage for one thread (the calling thread). + /// \note Not implementing this will result in the Soundbank tab of the Wwise Profiler to show 0 bytes for memory usage. + void StartProfileThreadUsage( + ) override; + + /// Called to stop profiling memory usage for the current thread. + /// \return The amount of memory allocated by this thread since StartProfileThreadUsage was called. + /// \note Not implementing this will result in the Soundbank tab of the Wwise Profiler to show 0 bytes for memory usage. + AkUInt64 StopProfileThreadUsage( + ) override; + + /// Dumps the currently tracked allocations to a file + /// \note AkMemSettings::uMemoryDebugLevel must be enabled and the build must define AK_MEMDEBUG for this to work + void DumpToFile( + const AkOSChar* pszFilename ///< Filename. + ) override; + + //@} +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwiseMonitorAPI_Null.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwiseMonitorAPI_Null.h new file mode 100644 index 0000000..e335fa4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwiseMonitorAPI_Null.h @@ -0,0 +1,166 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/WwiseMonitorAPI.h" + +class WWISESOUNDENGINE_API FWwiseMonitorAPI_Null : public IWwiseMonitorAPI +{ +public: + UE_NONCOPYABLE(FWwiseMonitorAPI_Null); + FWwiseMonitorAPI_Null() = default; + + /// Post a monitoring message or error code. This will be displayed in the Wwise capture + /// log. Since this function doesn't send variable arguments, be sure that the error code you're posting doesn't contain any tag. + /// Otherwise, there'll be an undefined behavior + /// \return AK_Success if successful, AK_Fail if there was a problem posting the message. + /// In optimized mode, this function returns AK_NotCompatible. + /// \remark This function is provided as a tracking tool only. It does nothing if it is + /// called in the optimized/release configuration and return AK_NotCompatible. + AKRESULT PostCode( + AK::Monitor::ErrorCode in_eError, ///< Message or error code to be displayed + AK::Monitor::ErrorLevel in_eErrorLevel, ///< Specifies whether it should be displayed as a message or an error + AkPlayingID in_playingID = AK_INVALID_PLAYING_ID, ///< Related Playing ID if applicable + AkGameObjectID in_gameObjID = AK_INVALID_GAME_OBJECT, ///< Related Game Object ID if applicable, AK_INVALID_GAME_OBJECT otherwise + AkUniqueID in_audioNodeID = AK_INVALID_UNIQUE_ID, ///< Related Audio Node ID if applicable, AK_INVALID_UNIQUE_ID otherwise + bool in_bIsBus = false ///< true if in_audioNodeID is a bus + ) override; + + AKRESULT PostCodeVarArg( + AK::Monitor::ErrorCode in_eError, ///< Error code to be displayed. This code corresponds to a predefined message, that may have parameters that can be passed in the variable arguments. Check the message format at the end of AkMonitorError.h. + AK::Monitor::ErrorLevel in_eErrorLevel, ///< Specifies whether it should be displayed as a message or an error + AK::Monitor::MsgContext msgContext, ///< The message context containing the following information : Related Playing ID if applicable, Related Game Object ID if applicable, AK_INVALID_GAME_OBJECT otherwise, Related Audio Node ID if applicable, AK_INVALID_UNIQUE_ID otherwise and whether if in_audioNodeID is a bus + ... ///< The variable arguments, depending on the ErrorCode posted. + ) override; + + /// Post a monitoring message. This will be displayed in the Wwise capture log. + /// \return AK_Success if successful, AK_Fail if there was a problem posting the message. + /// In optimized mode, this function returns AK_NotCompatible. + /// \remark This function is provided as a tracking tool only. It does nothing if it is + /// called in the optimized/release configuration and return AK_NotCompatible. + AKRESULT PostCodeVaList( + AK::Monitor::ErrorCode in_eError, ///< Error code to be displayed. This code corresponds to a predefined message, that may have parameters that can be passed in the variable arguments. Check the message format at the end of AkMonitorError.h. + AK::Monitor::ErrorLevel in_eErrorLevel, ///< Specifies whether it should be displayed as a message or an error + AK::Monitor::MsgContext msgContext, ///< The message context containing the following information : Related Playing ID if applicable, Related Game Object ID if applicable, AK_INVALID_GAME_OBJECT otherwise, Related Audio Node ID if applicable, AK_INVALID_UNIQUE_ID otherwise and whether if in_audioNodeID is a bus + ::va_list args ///< The variable arguments, depending on the ErrorCode posted. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Post a unicode monitoring message or error string. This will be displayed in the Wwise capture + /// log. + /// \return AK_Success if successful, AK_Fail if there was a problem posting the message. + /// In optimized mode, this function returns AK_NotCompatible. + /// \remark This function is provided as a tracking tool only. It does nothing if it is + /// called in the optimized/release configuration and return AK_NotCompatible. + AKRESULT PostString( + const wchar_t* in_pszError, ///< Message or error string to be displayed + AK::Monitor::ErrorLevel in_eErrorLevel, ///< Specifies whether it should be displayed as a message or an error + AkPlayingID in_playingID = AK_INVALID_PLAYING_ID, ///< Related Playing ID if applicable + AkGameObjectID in_gameObjID = AK_INVALID_GAME_OBJECT, ///< Related Game Object ID if applicable, AK_INVALID_GAME_OBJECT otherwise + AkUniqueID in_audioNodeID = AK_INVALID_UNIQUE_ID, ///< Related Audio Node ID if applicable, AK_INVALID_UNIQUE_ID otherwise + bool in_bIsBus = false ///< true if in_audioNodeID is a bus + ) override; + +#endif // #ifdef AK_SUPPORT_WCHAR + + /// Post a monitoring message or error string. This will be displayed in the Wwise capture + /// log. + /// \return AK_Success if successful, AK_Fail if there was a problem posting the message. + /// In optimized mode, this function returns AK_NotCompatible. + /// \remark This function is provided as a tracking tool only. It does nothing if it is + /// called in the optimized/release configuration and return AK_NotCompatible. + AKRESULT PostString( + const char* in_pszError, ///< Message or error string to be displayed + AK::Monitor::ErrorLevel in_eErrorLevel, ///< Specifies whether it should be displayed as a message or an error + AkPlayingID in_playingID = AK_INVALID_PLAYING_ID, ///< Related Playing ID if applicable + AkGameObjectID in_gameObjID = AK_INVALID_GAME_OBJECT, ///< Related Game Object ID if applicable, AK_INVALID_GAME_OBJECT otherwise + AkUniqueID in_audioNodeID = AK_INVALID_UNIQUE_ID, ///< Related Audio Node ID if applicable, AK_INVALID_UNIQUE_ID otherwise + bool in_bIsBus = false ///< true if in_audioNodeID is a bus + ) override; + + /// Enable/Disable local output of monitoring messages or errors. Pass 0 to disable, + /// or any combination of ErrorLevel_Message and ErrorLevel_Error to enable. + /// \return AK_Success. + /// In optimized/release configuration, this function returns AK_NotCompatible. + AKRESULT SetLocalOutput( + AkUInt32 in_uErrorLevel = AK::Monitor::ErrorLevel_All, ///< ErrorLevel(s) to enable in output. Default parameters enable all. + AK::Monitor::LocalOutputFunc in_pMonitorFunc = 0 ///< Handler for local output. If NULL, the standard platform debug output method is used. + ) override; + + /// Add a translator to the wwiseErrorHandler + /// The additional translators increase the chance of a monitoring messages or errors + /// to be succeffully translated. + /// \return AK_Success. + /// In optimized/release configuration, this function returns AK_NotCompatible. + AKRESULT AddTranslator( + AkErrorMessageTranslator* translator, ///< The AkErrorMessageTranslator to add to the WwiseErrorHandler + bool overridePreviousTranslators = false ///< Whether or not the newly added translator should override all the previous translators. + ///< In both cases, the default translator will remain + ) override; + + /// Reset the wwiseErrorHandler to only using the default translator + /// \return AK_Success. + /// In optimized/release configuration, this function returns AK_NotCompatible. + AKRESULT ResetTranslator( + ) override; + + /// Get the time stamp shown in the capture log along with monitoring messages. + /// \return Time stamp in milliseconds. + /// In optimized/release configuration, this function returns 0. + AkTimeMs GetTimeStamp() override; + + /// Add the streaming manager settings to the profiler capture. + void MonitorStreamMgrInit( + const AkStreamMgrSettings& in_streamMgrSettings + ) override; + + /// Add device settings to the list of active streaming devices. + /// The list of streaming devices and their settings will be + /// sent to the profiler capture when remote connecting from Wwise. + /// + /// \remark \c AK::Monitor::MonitorStreamMgrTerm must be called to + /// clean-up memory used to keep track of active streaming devices. + void MonitorStreamingDeviceInit( + AkDeviceID in_deviceID, + const AkDeviceSettings& in_deviceSettings + ) override; + + /// Remove streaming device entry from the list of devices + /// to send when remote connecting from Wwise. + void MonitorStreamingDeviceDestroyed( + AkDeviceID in_deviceID + ) override; + + /// Monitor streaming manager destruction as part of the + /// profiler capture. + /// + /// \remark This function must be called to clean-up memory used by + /// \c AK::Monitor::MonitorStreamingDeviceInit and \c AK::Monitor::MonitorStreamingDeviceTerm + /// to keep track of active streaming devices. + void MonitorStreamMgrTerm() override; + + /// Add the default, WwiseSDK-provided WAAPI error translator. + void SetupDefaultWAAPIErrorTranslator( + const FString& WaapiIP, ///< IP Address of the WAAPI server + AkUInt32 WaapiPort, ///< Port of the WAAPI server + AkUInt32 Timeout ///< Maximum time that can be spent resolving the error parameters. Set to INT_MAX to wait infinitely or 0 to disable XML translation entirely. + ) override; + + /// Terminate the default, WwiseSDK-provided WAAPI error translator. + void TerminateDefaultWAAPIErrorTranslator() override; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwiseMusicEngineAPI_Null.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwiseMusicEngineAPI_Null.h new file mode 100644 index 0000000..950dde5 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwiseMusicEngineAPI_Null.h @@ -0,0 +1,85 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/WwiseMusicEngineAPI.h" + +class WWISESOUNDENGINE_API FWwiseMusicEngineAPI_Null : public IWwiseMusicEngineAPI +{ +public: + UE_NONCOPYABLE(FWwiseMusicEngineAPI_Null); + FWwiseMusicEngineAPI_Null() = default; + + /////////////////////////////////////////////////////////////////////// + /// @name Initialization + //@{ + + /// Initialize the music engine. + /// \warning This function must be called after the base sound engine has been properly initialized. + /// There should be no AK API call between AK::SoundEngine::Init() and this call. Any call done in between is potentially unsafe. + /// \return AK_Success if the Init was successful, AK_Fail otherwise. + /// \sa + /// - \ref workingwithsdks_initialization + AKRESULT Init( + AkMusicSettings* in_pSettings ///< Initialization settings (can be NULL, to use the default values) + ) override; + + /// Get the music engine's default initialization settings values + /// \sa + /// - \ref soundengine_integration_init_advanced + /// - AK::MusicEngine::Init() + void GetDefaultInitSettings( + AkMusicSettings& out_settings ///< Returned default platform-independent music engine settings + ) override; + + /// Terminate the music engine. + /// \warning This function must be called before calling Term() on the base sound engine. + /// \sa + /// - \ref workingwithsdks_termination + void Term( + ) override; + + /// Query information on the active segment of a music object that is playing. Use the playing ID + /// that was returned from AK::SoundEngine::PostEvent(), provided that the event contained a play + /// action that was targetting a music object. For any configuration of interactive music hierarchy, + /// there is only one segment that is active at a time. + /// To be able to query segment information, you must pass the AK_EnableGetMusicPlayPosition flag + /// to the AK::SoundEngine::PostEvent() method. This informs the sound engine that the source associated + /// with this event should be given special consideration because GetPlayingSegmentInfo() can be called + /// at any time for this AkPlayingID. + /// Notes: + /// - If the music object is a single segment, you will get negative values for AkSegmentInfo::iCurrentPosition + /// during the pre-entry. This will never occur with other types of music objects because the + /// pre-entry of a segment always overlaps another active segment. + /// - The active segment during the pre-entry of the first segment of a Playlist Container or a Music Switch + /// Container is "nothing", as well as during the post-exit of the last segment of a Playlist (and beyond). + /// - When the active segment is "nothing", out_uSegmentInfo is filled with zeros. + /// - If in_bExtrapolate is true (default), AkSegmentInfo::iCurrentPosition is corrected by the amount of time elapsed + /// since the beginning of the audio frame. It is thus possible that it slightly overshoots the total segment length. + /// \return AK_Success if there is a playing music structure associated with the specified playing ID. + /// \sa + /// - AK::SoundEngine::PostEvent + /// - AkSegmentInfo + AKRESULT GetPlayingSegmentInfo( + AkPlayingID in_PlayingID, ///< Playing ID returned by AK::SoundEngine::PostEvent(). + AkSegmentInfo& out_segmentInfo, ///< Structure containing information about the active segment of the music structure that is playing. + bool in_bExtrapolate = true ///< Position is extrapolated based on time elapsed since last sound engine update. + ) override; + + //@} +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwisePlatformAPI_Null.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwisePlatformAPI_Null.h new file mode 100644 index 0000000..0b3c977 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwisePlatformAPI_Null.h @@ -0,0 +1,23 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/WwisePlatformAPI.h" + +// Nulls are platform-independent. +// #include COMPILED_PLATFORM_HEADER_WITH_PREFIX(Wwise/API_Null/Platforms, API_Null.h) diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwiseSoundEngineAPI_Null.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwiseSoundEngineAPI_Null.h new file mode 100644 index 0000000..84b5695 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwiseSoundEngineAPI_Null.h @@ -0,0 +1,4517 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/WwiseSoundEngineAPI.h" + +class WWISESOUNDENGINE_API FWwiseSoundEngineAPI_Null : public IWwiseSoundEngineAPI +{ +public: + UE_NONCOPYABLE(FWwiseSoundEngineAPI_Null); + FWwiseSoundEngineAPI_Null(); + + /////////////////////////////////////////////////////////////////////// + /// @name Initialization + //@{ + + /// Query whether or not the sound engine has been successfully initialized. + /// \warning This function is not thread-safe. It should not be called at the same time as \c SoundEngine::Init() or \c SoundEngine::Term(). + /// \return \c True if the sound engine has been initialized, \c False otherwise. + /// \sa + /// - \ref soundengine_integration_init_advanced + /// - AK::SoundEngine::Init() + /// - AK::SoundEngine::Term() + bool IsInitialized() override; + + /// Initialize the sound engine. + /// \warning This function is not thread-safe. + /// \remark The initial settings should be initialized using AK::SoundEngine::GetDefaultInitSettings() + /// and AK::SoundEngine::GetDefaultPlatformInitSettings() to fill the structures with their + /// default settings. This is not mandatory, but it helps avoid backward compatibility problems. + /// + /// \return + /// - \c AK_Success if the initialization was successful + /// - \c AK_MemManagerNotInitialized if the memory manager is not available or not properly initialized + /// - \c AK_StreamMgrNotInitialized if the stream manager is not available or not properly initialized + /// - \c AK_SSEInstructionsNotSupported if the machine does not support SSE instruction (only on the PC) + /// - \c AK_InsufficientMemory if there is not enough memory available to initialize the sound engine properly + /// - \c AK_InvalidParameter if some parameters are invalid + /// - \c AK_AlreadyInitialized if the sound engine is already initialized, or if the provided settings result in insufficient + /// - \c AK_Fail for unknown errors, check with AK Support. + /// resources for the initialization. + /// \sa + /// - \ref soundengine_integration_init_advanced + /// - \ref workingwithsdks_initialization + /// - AK::SoundEngine::Term() + /// - AK::SoundEngine::GetDefaultInitSettings() + /// - AK::SoundEngine::GetDefaultPlatformInitSettings() + AKRESULT Init( + AkInitSettings* in_pSettings, ///< Initialization settings (can be NULL, to use the default values) + AkPlatformInitSettings* in_pPlatformSettings ///< Platform-specific settings (can be NULL, to use the default values) + ) override; + + /// Gets the default values of the platform-independent initialization settings. + /// \warning This function is not thread-safe. + /// \sa + /// - \ref soundengine_integration_init_advanced + /// - AK::SoundEngine::Init() + /// - AK::SoundEngine::GetDefaultPlatformInitSettings() + void GetDefaultInitSettings( + AkInitSettings& out_settings ///< Returned default platform-independent sound engine settings + ) override; + + /// Gets the default values of the platform-specific initialization settings. + /// + /// Windows Specific: + /// When initializing for Windows platform, the HWND value returned in the + /// AkPlatformInitSettings structure is the foreground HWND at the moment of the + /// initialization of the sound engine and may not be the correct one for your need. + /// Each game must specify the HWND that will be passed to DirectSound initialization. + /// It is required that each game provides the correct HWND to be used or it could cause + /// one of the following problem: + /// - Random Sound engine initialization failure. + /// - Audio focus to be located on the wrong window. + /// + /// \warning This function is not thread-safe. + /// \sa + /// - \ref soundengine_integration_init_advanced + /// - AK::SoundEngine::Init() + /// - AK::SoundEngine::GetDefaultInitSettings() + void GetDefaultPlatformInitSettings( + AkPlatformInitSettings& out_platformSettings ///< Returned default platform-specific sound engine settings + ) override; + + /// Terminates the sound engine. + /// If some sounds are still playing or events are still being processed when this function is + /// called, they will be stopped. + /// \warning This function is not thread-safe. + /// \warning Before calling Term, you must ensure that no other thread is accessing the sound engine. + /// \sa + /// - \ref soundengine_integration_init_advanced + /// - AK::SoundEngine::Init() + void Term() override; + + /// Gets the configured audio settings. + /// Call this function to get the configured audio settings. + /// + /// \warning This function is not thread-safe. + /// \warning Call this function only after the sound engine has been properly initialized. + /// \return + /// - \c AK_NotInitialized if AK::SoundEngine::Init() was not called + /// - \c AK_Success otherwise. + AKRESULT GetAudioSettings( + AkAudioSettings& out_audioSettings ///< Returned audio settings + ) override; + + /// Gets the output speaker configuration of the specified output. + /// Call this function to get the speaker configuration of the output (which may not correspond + /// to the physical output format of the platform, in the case of downmixing provided by the platform itself). + /// You may initialize the sound engine with a user-specified configuration, but the resulting + /// configuration is determined by the sound engine, based on the platform, output type and + /// platform settings (for e.g. system menu or control panel option). + /// If the speaker configuration of the output is object-based, the speaker configuration of the + /// main mix is returned. To query more information on object-based output devices, see AK::SoundEngine::GetOutputDeviceConfiguration. + /// + /// It is recommended to call GetSpeakerConfiguration anytime after receiving a callback from RegisterAudioDeviceStatusCallback to know if the channel configuration has changed. + /// + /// \warning Call this function only after the sound engine has been properly initialized. + /// If you are initializing the sound engine with AkInitSettings::bUseLEngineThread to false, it is required to call RenderAudio() at least once before calling this function to complete the sound engine initialization. + /// The Init.bnk must be loaded prior to this call. + /// \return The output configuration. An empty AkChannelConfig not AkChannelConfig::IsValid() if device does not exist or if the Init.bnk was not loaded yet. + /// \sa + /// - AkSpeakerConfig.h + /// - AkOutputSettings + /// - AK::SoundEngine::GetOutputDeviceConfiguration() + AkChannelConfig GetSpeakerConfiguration( + AkOutputDeviceID in_idOutput = 0 ///< Output ID to set the bus on. As returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + ) override; + + /// Gets the configuration of the specified output device. + /// Call this function to get the channel configuration of the output device as well as its 3D audio capabilities. + /// If the configuration of the output device is object-based (io_channelConfig.eConfigType == AK_ChannelConfigType_Objects), + /// io_capabilities can be inspected to determine the channel configuration of the main mix (Ak3DAudioSinkCapabilities::channelConfig), + /// whether or not the output device uses a passthrough mix (Ak3DAudioSinkCapabilities::bPassthrough) and the maximum number of objects + /// that can play simultaneously on this output device (Ak3DAudioSinkCapabilities::uMax3DAudioObjects). Note that if + /// Ak3DAudioSinkCapabilities::bMultiChannelObjects is false, multi-channel objects will be split into multiple mono objects + /// before being sent to the output device. + /// + /// \warning Call this function only after the sound engine has been properly initialized. If you are initializing the sound engine with AkInitSettings::bUseLEngineThread to false, it is required to call RenderAudio() at least once before calling this function to complete the sound engine initialization. + /// \return + /// - \c AK_Success if successful + /// - \c AK_IDNotFound is the output was not found in the system. + /// - \c AK_NotInitialized if the sound engine is not initialized + /// \sa + /// - AkSpeakerConfig.h + /// - AkOutputSettings + /// - AK::SoundEngine::GetSpeakerConfiguration() + AKRESULT GetOutputDeviceConfiguration( + AkOutputDeviceID in_idOutput, + AkChannelConfig& io_channelConfig, + Ak3DAudioSinkCapabilities& io_capabilities + ) override; + + /// Gets the panning rule of the specified output. + /// \warning Call this function only after the sound engine has been properly initialized. + /// Returns the supported configuration in out_ePanningRule: + /// - AkPanningRule_Speakers + /// - AkPanningRule_Headphone + /// \return + /// - \c AK_Success if successful + /// - \c AK_IDNotFound is the output was not found in the system. + /// - \c AK_NotInitialized if the sound engine is not initialized + /// \sa + /// - AkSpeakerConfig.h + AKRESULT GetPanningRule( + AkPanningRule& out_ePanningRule, ///< Returned panning rule (AkPanningRule_Speakers or AkPanningRule_Headphone) for given output. + AkOutputDeviceID in_idOutput = 0 ///< Output ID to set the bus on. As returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + ) override; + + /// Sets the panning rule of the specified output. + /// This may be changed anytime once the sound engine is initialized. + /// \warning This function posts a message through the sound engine's internal message queue, whereas GetPanningRule() queries the current panning rule directly. + /// \aknote + /// The specified panning rule will only impact the sound if the processing format is downmixing to Stereo in the mixing process. It + /// will not impact the output if the audio stays in 5.1 until the end, for example. + /// \endaknote + AKRESULT SetPanningRule( + AkPanningRule in_ePanningRule, ///< Panning rule. + AkOutputDeviceID in_idOutput = 0 ///< Output ID to set the bus on. As returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + ) override; + + /// Gets speaker angles of the specified device. Speaker angles are used for 3D positioning of sounds over standard configurations. + /// Note that the current version of Wwise only supports positioning on the plane. + /// The speaker angles are expressed as an array of loudspeaker pairs, in degrees, relative to azimuth ]0,180]. + /// Supported loudspeaker setups are always symmetric; the center speaker is always in the middle and thus not specified by angles. + /// Angles must be set in ascending order. + /// You may call this function with io_pfSpeakerAngles set to NULL to get the expected number of angle values in io_uNumAngles, + /// in order to allocate your array correctly. You may also obtain this number by calling + /// AK::GetNumberOfAnglesForConfig( AK_SPEAKER_SETUP_DEFAULT_PLANE ). + /// If io_pfSpeakerAngles is not NULL, the array is filled with up to io_uNumAngles. + /// Typical usage: + /// - AkUInt32 uNumAngles; + /// - GetSpeakerAngles( NULL, uNumAngles, AkOutput_Main ) override; + /// - AkReal32 * pfSpeakerAngles = AkAlloca( uNumAngles * sizeof(AkReal32) ) override; + /// - GetSpeakerAngles( pfSpeakerAngles, uNumAngles, AkOutput_Main ) override; + /// \aknote + /// On most platforms, the angle set on the plane consists of 3 angles, to account for 7.1. + /// - When panning to stereo (speaker mode, see AK::SoundEngine::SetPanningRule()), only angle[0] is used, and 3D sounds in the back of the listener are mirrored to the front. + /// - When panning to 5.1, the front speakers use angle[0], and the surround speakers use (angle[2] - angle[1]) / 2. + /// \endaknote + /// \warning Call this function only after the sound engine has been properly initialized. + /// \return AK_Success if device exists. + /// \sa SetSpeakerAngles() + AKRESULT GetSpeakerAngles( + AkReal32* io_pfSpeakerAngles, ///< Returned array of loudspeaker pair angles, in degrees relative to azimuth [0,180]. Pass NULL to get the required size of the array. + AkUInt32& io_uNumAngles, ///< Returned number of angles in io_pfSpeakerAngles, which is the minimum between the value that you pass in, and the number of angles corresponding to AK::GetNumberOfAnglesForConfig( AK_SPEAKER_SETUP_DEFAULT_PLANE ), or just the latter if io_pfSpeakerAngles is NULL. + AkReal32& out_fHeightAngle, ///< Elevation of the height layer, in degrees relative to the plane [-90,90]. + AkOutputDeviceID in_idOutput = 0 ///< Output ID to set the bus on. As returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + ) override; + + /// Sets speaker angles of the specified device. Speaker angles are used for 3D positioning of sounds over standard configurations. + /// Note that the current version of Wwise only supports positioning on the plane. + /// The speaker angles are expressed as an array of loudspeaker pairs, in degrees, relative to azimuth ]0,180]. + /// Supported loudspeaker setups are always symmetric; the center speaker is always in the middle and thus not specified by angles. + /// Angles must be set in ascending order. + /// Note: + /// - This function requires that the minimum speaker angle is at least 5 degrees; as well as the subsequent speaker pairs are at least 5 degrees apart. + /// Typical usage: + /// - Initialize the sound engine and/or add secondary output(s). + /// - Get number of speaker angles and their value into an array using GetSpeakerAngles(). + /// - Modify the angles and call SetSpeakerAngles(). + /// This function posts a message to the audio thread through the command queue, so it is thread safe. However the result may not be immediately read with GetSpeakerAngles(). + /// \warning This function only applies to configurations (or subset of these configurations) that are standard and whose speakers are on the plane (2D). + /// \return + /// - \c AK_Success if successful. + /// - \c AK_InvalidFloatValue if the value specified was NaN or Inf + /// - \c AK_InsufficientMemory if there wasn't enough memory in the message queue + /// - \c AK_InvalidParameter one of the parameter is invalid, check the debug log. + /// \sa GetSpeakerAngles() + AKRESULT SetSpeakerAngles( + const AkReal32* in_pfSpeakerAngles, ///< Array of loudspeaker pair angles, in degrees relative to azimuth [0,180]. + AkUInt32 in_uNumAngles, ///< Number of elements in in_pfSpeakerAngles. It must correspond to AK::GetNumberOfAnglesForConfig( AK_SPEAKER_SETUP_DEFAULT_PLANE ) (the value returned by GetSpeakerAngles()). + AkReal32 in_fHeightAngle, ///< Elevation of the height layer, in degrees relative to the plane [-90,90]. + AkOutputDeviceID in_idOutput = 0 ///< Output ID to set the bus on. As returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + ) override; + + /// Allows the game to set the volume threshold to be used by the sound engine to determine if a voice must go virtual. + /// This may be changed anytime once the sound engine was initialized. + /// If this function is not called, the used value will be the value specified in the platform specific project settings. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if the threshold was not between 0 and -96.3 dB. + /// - \c AK_InvalidFloatValue if the value specified was NaN or Inf + AKRESULT SetVolumeThreshold( + AkReal32 in_fVolumeThresholdDB ///< Volume Threshold, must be a value between 0 and -96.3 dB + ) override; + + /// Allows the game to set the maximum number of non virtual voices to be played simultaneously. + /// This may be changed anytime once the sound engine was initialized. + /// If this function is not called, the used value will be the value specified in the platform specific project settings. + /// \return + /// - \c AK_InvalidParameter if the threshold was not between 1 and MaxUInt16. + /// - \c AK_Success if successful + AKRESULT SetMaxNumVoicesLimit( + AkUInt16 in_maxNumberVoices ///< Maximum number of non-virtual voices. + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Rendering Audio + //@{ + + /// Processes all commands in the sound engine's command queue. + /// This method has to be called periodically (usually once per game frame). + /// \sa + /// - \ref concept_events + /// - \ref soundengine_events + /// - AK::SoundEngine::PostEvent() + /// \return Always returns AK_Success + AKRESULT RenderAudio( + bool in_bAllowSyncRender = true ///< When AkInitSettings::bUseLEngineThread is false, RenderAudio may generate an audio buffer -- unless in_bAllowSyncRender is set to false. Use in_bAllowSyncRender=false when calling RenderAudio from a Sound Engine callback. + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Component Registration + //@{ + + /// Query interface to global plug-in context used for plug-in registration/initialization. + /// \return Global plug-in context. + AK::IAkGlobalPluginContext* GetGlobalPluginContext() override; + + /// Registers a plug-in with the sound engine and sets the callback functions to create the + /// plug-in and its parameter node. + /// \aknote + /// This function is deprecated. Registration is now automatic if you link plug-ins statically. If plug-ins are dynamic libraries (such as DLLs or SOs), use \c RegisterPluginDLL. + /// \endaknote + /// \sa + /// - \ref register_effects + /// - \ref plugin_xml + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if invalid parameters were provided + /// - \c AK_InsufficientMemory if there isn't enough memory to register the plug-in + /// \remarks + /// Codecs and plug-ins must be registered before loading banks that use them.\n + /// Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + AKRESULT RegisterPlugin( + AkPluginType in_eType, ///< Plug-in type (for example, source or effect) + AkUInt32 in_ulCompanyID, ///< Company identifier (as declared in the plug-in description XML file) + AkUInt32 in_ulPluginID, ///< Plug-in identifier (as declared in the plug-in description XML file) + AkCreatePluginCallback in_pCreateFunc, ///< Pointer to the plug-in's creation function + AkCreateParamCallback in_pCreateParamFunc, ///< Pointer to the plug-in's parameter node creation function + AkGetDeviceListCallback in_pGetDeviceList = NULL ///< Optional pointer to the plug-in's device enumeration function. Specify for a sink plug-in to support \ref AK::SoundEngine::GetDeviceList. + ) override; + + /// Loads a plug-in dynamic library and registers it with the sound engine. + /// With dynamic linking, all plugins are automatically registered. + /// The plug-in DLL must be in the OS-specific library path or in the same location as the executable. If not, set AkInitSettings.szPluginDLLPath. + /// \return + /// - \c AK_Success if successful. + /// - \c AK_FileNotFound if the DLL is not found in the OS path or if it has extraneous dependencies not found. + /// - \c AK_InsufficientMemory if the system ran out of resources while loading the dynamic library + /// - \c AK_NotCompatible if the file was found but is not binary-compatible with the system's expected executable format + /// - \c AK_InvalidFile if the symbol g_pAKPluginList is not exported by the dynamic library + /// - \c AK_Fail if an unexpected system error was encountered + AKRESULT RegisterPluginDLL( + const AkOSChar* in_DllName, ///< Name of the DLL to load, without "lib" prefix or extension. + const AkOSChar* in_DllPath = NULL ///< Optional path to the DLL. Will override szPLuginDLLPath that was set in AkInitSettings. + ) override; + + /// Registers a codec type with the sound engine and set the callback functions to create the + /// codec's file source and bank source nodes. + /// \aknote + /// This function is deprecated. Registration is now automatic if you link plugins statically. If plugins are dynamic libraries (such as DLLs or SOs), use RegisterPluginDLL. + /// \endaknote + /// \sa + /// - \ref register_effects + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if invalid parameters were provided + /// - \c AK_InsufficientMemory if there isn't enough memory to register the plug-in + /// \remarks + /// Codecs and plug-ins must be registered before loading banks that use them.\n + /// Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the Event will fail. + AKRESULT RegisterCodec( + AkUInt32 in_ulCompanyID, ///< Company identifier (as declared in the plug-in description XML file) + AkUInt32 in_ulCodecID, ///< Codec identifier (as declared in the plug-in description XML file) + AkCreateFileSourceCallback in_pFileCreateFunc, ///< Pointer to the codec's file source node creation function + AkCreateBankSourceCallback in_pBankCreateFunc ///< Pointer to the codec's bank source node creation function + ) override; + + /// Registers a global callback function. This function will be called from the audio rendering thread, at the + /// location specified by in_eLocation. This function will also be called from the thread calling + /// AK::SoundEngine::Term with in_eLocation set to AkGlobalCallbackLocation_Term. + /// For example, in order to be called at every audio rendering pass, and once during teardown for releasing resources, you would call + /// RegisterGlobalCallback(myCallback, AkGlobalCallbackLocation_BeginRender | AkGlobalCallbackLocation_Term, myCookie, AkPluginTypeNone, 0, 0) override; + /// \remarks + /// A Plugin Type, Company ID and Plugin ID can be provided to this function to enable timing in the performance monitor. + /// If the callback is being timed, it will contribute to the Total Plug-in CPU measurement, and also appear in the Plug-ins tab of the Advanced Profiler by plug-in type and ID. + /// It is illegal to call this function while already inside of a global callback. + /// This function might stall for several milliseconds before returning. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if parameters are out of range (check debug console or Wwise Profiler) + /// \sa + /// - AK::SoundEngine::UnregisterGlobalCallback() + /// - AkGlobalCallbackFunc + /// - AkGlobalCallbackLocation + AKRESULT RegisterGlobalCallback( + AkGlobalCallbackFunc in_pCallback, ///< Function to register as a global callback. + AkUInt32 in_eLocation = AkGlobalCallbackLocation_BeginRender, ///< Callback location defined in AkGlobalCallbackLocation. Bitwise OR multiple locations if needed. + void* in_pCookie = NULL, ///< User cookie. + AkPluginType in_eType = AkPluginTypeNone, ///< Plug-in type (for example, source or effect). AkPluginTypeNone for no timing. + AkUInt32 in_ulCompanyID = 0, ///< Company identifier (as declared in the plug-in description XML file). 0 for no timing. + AkUInt32 in_ulPluginID = 0 ///< Plug-in identifier (as declared in the plug-in description XML file). 0 for no timing. + ) override; + + /// Unregisters a global callback function, previously registered using RegisterGlobalCallback. + /// \remarks + /// It is legal to call this function while already inside of a global callback, If it is unregistering itself and not + /// another callback. + /// This function might stall for several milliseconds before returning. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if parameters are out of range (check debug console or Wwise Profiler) + /// \sa + /// - AK::SoundEngine::RegisterGlobalCallback() + /// - AkGlobalCallbackFunc + /// - AkGlobalCallbackLocation + AKRESULT UnregisterGlobalCallback( + AkGlobalCallbackFunc in_pCallback, ///< Function to unregister as a global callback. + AkUInt32 in_eLocation = AkGlobalCallbackLocation_BeginRender ///< Must match in_eLocation as passed to RegisterGlobalCallback for this callback. + ) override; + + /// Registers a resource monitor callback function that gets all of the resource usage data contained in the + /// AkResourceMonitorDataSummary structure. This includes general information about the system, such as CPU usage, + /// active Voices, and Events. This function will be called from the audio rendering thread at the end of each frame. + /// \remarks + /// If the callback is being timed, it will contribute to the Total Plug-in CPU measurement, and also appear in the Plug-ins tab of the Advanced Profiler by plug-in type and ID. + /// It is illegal to call this function while already inside of a resource callback. + /// This function might stall for several milliseconds before returning. + /// This function will return AK_Fail in Release + /// \sa + /// - AK::SoundEngine::UnregisterResourceMonitorCallback() + /// - AkResourceMonitorCallbackFunc + AKRESULT RegisterResourceMonitorCallback( + AkResourceMonitorCallbackFunc in_pCallback ///< Function to register as a resource monitor callback. + ) override; + + /// Unregisters a resource monitor callback function, previously registered using RegisterResourceMonitorCallback. + /// \remarks + /// It is legal to call this function while already inside of a resource monitor callback, If it is unregistering itself and not + /// another callback. + /// This function might stall for several milliseconds before returning. + /// \sa + /// - AK::SoundEngine::RegisterResourceMonitorCallback() + /// - AkResourceMonitorCallbackFunc + AKRESULT UnregisterResourceMonitorCallback( + AkResourceMonitorCallbackFunc in_pCallback ///< Function to unregister as a resource monitor callback. + ) override; + + /// Registers a callback for the Audio Device status changes. + /// The callback will be called from the audio thread + /// Can be called prior to AK::SoundEngine::Init + /// \sa AK::SoundEngine::AddOutput + AKRESULT RegisterAudioDeviceStatusCallback( + AK::AkDeviceStatusCallbackFunc in_pCallback ///< Function to register as a status callback. + ) override; + + /// Unregisters the callback for the Audio Device status changes, registered by RegisterAudioDeviceStatusCallback + AKRESULT UnregisterAudioDeviceStatusCallback() override; + //@} + +#ifdef AK_SUPPORT_WCHAR + //////////////////////////////////////////////////////////////////////// + /// @name Getting ID from strings + //@{ + + /// Universal converter from Unicode string to ID for the sound engine. + /// This function will hash the name based on a algorithm ( provided at : /AK/Tools/Common/AkFNVHash.h ) + /// Note: + /// This function does return a AkUInt32, which is totally compatible with: + /// AkUniqueID, AkStateGroupID, AkStateID, AkSwitchGroupID, AkSwitchStateID, AkRtpcID, and so on... + /// \sa + /// - AK::SoundEngine::PostEvent + /// - AK::SoundEngine::SetRTPCValue + /// - AK::SoundEngine::SetSwitch + /// - AK::SoundEngine::SetState + /// - AK::SoundEngine::PostTrigger + /// - AK::SoundEngine::SetGameObjectAuxSendValues + /// - AK::SoundEngine::LoadBank + /// - AK::SoundEngine::UnloadBank + /// - AK::SoundEngine::PrepareEvent + /// - AK::SoundEngine::PrepareGameSyncs + AkUInt32 GetIDFromString(const wchar_t* in_pszString) override; +#endif //AK_SUPPORT_WCHAR + + /// Universal converter from string to ID for the sound engine. + /// This function will hash the name based on a algorithm ( provided at : /AK/Tools/Common/AkFNVHash.h ) + /// Note: + /// This function does return a AkUInt32, which is totally compatible with: + /// AkUniqueID, AkStateGroupID, AkStateID, AkSwitchGroupID, AkSwitchStateID, AkRtpcID, and so on... + /// \sa + /// - AK::SoundEngine::PostEvent + /// - AK::SoundEngine::SetRTPCValue + /// - AK::SoundEngine::SetSwitch + /// - AK::SoundEngine::SetState + /// - AK::SoundEngine::PostTrigger + /// - AK::SoundEngine::SetGameObjectAuxSendValues + /// - AK::SoundEngine::LoadBank + /// - AK::SoundEngine::UnloadBank + /// - AK::SoundEngine::PrepareEvent + /// - AK::SoundEngine::PrepareGameSyncs + AkUInt32 GetIDFromString(const char* in_pszString) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Event Management + //@{ + + /// Asynchronously posts an Event to the sound engine (by event ID). + /// The callback function can be used to be noticed when markers are reached or when the event is finished. + /// An array of wave file sources can be provided to resolve External Sources triggered by the event. + /// \return The playing ID of the event launched, or AK_INVALID_PLAYING_ID if posting the event failed and an error will be displayed in the debug console and the Wwise Profiler. + /// \remarks + /// If used, the array of external sources should contain the information for each external source triggered by the + /// event. When triggering an event with multiple external sources, you need to differentiate each source + /// by using the cookie property in the External Source in the Wwise project and in AkExternalSourceInfo. + /// \aknote If an event triggers the playback of more than one external source, they must be named uniquely in the project + /// (therefore have a unique cookie) in order to tell them apart when filling the AkExternalSourceInfo structures. + /// \endaknote + /// \sa + /// - \ref concept_events + /// - \ref integrating_external_sources + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::GetSourcePlayPosition() + AkPlayingID PostEvent( + AkUniqueID in_eventID, ///< Unique ID of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID + AkUInt32 in_uFlags = 0, ///< Bitmask: see \ref AkCallbackType + AkCallbackFunc in_pfnCallback = NULL, ///< Callback function + void* in_pCookie = NULL, ///< Callback cookie that will be sent to the callback function along with additional information + AkUInt32 in_cExternals = 0, ///< Optional count of external source structures + AkExternalSourceInfo* in_pExternalSources = NULL,///< Optional array of external source resolution information + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID///< Optional (advanced users only) Specify the playing ID to target with the event. Will Cause active actions in this event to target an existing Playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any for normal playback. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Posts an Event to the sound engine (by Event name), using callbacks. + /// The callback function can be used to be noticed when markers are reached or when the event is finished. + /// An array of wave file sources can be provided to resolve External Sources triggered by the event. + /// \return The playing ID of the event launched, or AK_INVALID_PLAYING_ID if posting the event failed and an error will be displayed in the debug console and the Wwise Profiler. + /// \remarks + /// If used, the array of external sources should contain the information for each external source triggered by the + /// event. When triggering an event with multiple external sources, you need to differentiate each source + /// by using the cookie property in the External Source in the Wwise project and in AkExternalSourceInfo. + /// \aknote If an event triggers the playback of more than one external source, they must be named uniquely in the project + /// (therefore have a unique cookie) in order to tell them appart when filling the AkExternalSourceInfo structures. + /// \endaknote + /// \sa + /// - \ref concept_events + /// - \ref integrating_external_sources + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::GetSourcePlayPosition() + AkPlayingID PostEvent( + const wchar_t* in_pszEventName, ///< Name of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID + AkUInt32 in_uFlags = 0, ///< Bitmask: see \ref AkCallbackType + AkCallbackFunc in_pfnCallback = NULL, ///< Callback function + void* in_pCookie = NULL, ///< Callback cookie that will be sent to the callback function along with additional information. + AkUInt32 in_cExternals = 0, ///< Optional count of external source structures + AkExternalSourceInfo* in_pExternalSources = NULL,///< Optional array of external source resolution information + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID///< Optional (advanced users only) Specify the playing ID to target with the event. Will Cause active actions in this event to target an existing Playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any for normal playback. + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Posts an Event to the sound engine (by Event name), using callbacks. + /// The callback function can be used to be noticed when markers are reached or when the event is finished. + /// An array of Wave file sources can be provided to resolve External Sources triggered by the event. P + /// \return The playing ID of the event launched, or AK_INVALID_PLAYING_ID if posting the event failed and an error will be displayed in the debug console and the Wwise Profiler. + /// \remarks + /// If used, the array of external sources should contain the information for each external source triggered by the + /// event. When triggering an Event with multiple external sources, you need to differentiate each source + /// by using the cookie property in the External Source in the Wwise project and in AkExternalSourceInfo. + /// \aknote If an event triggers the playback of more than one external source, they must be named uniquely in the project + /// (therefore have a unique cookie) in order to tell them apart when filling the AkExternalSourceInfo structures. + /// \endaknote + /// \sa + /// - \ref concept_events + /// - \ref integrating_external_sources + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::GetSourcePlayPosition() + AkPlayingID PostEvent( + const char* in_pszEventName, ///< Name of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID + AkUInt32 in_uFlags = 0, ///< Bitmask: see \ref AkCallbackType + AkCallbackFunc in_pfnCallback = NULL, ///< Callback function + void* in_pCookie = NULL, ///< Callback cookie that will be sent to the callback function along with additional information. + AkUInt32 in_cExternals = 0, ///< Optional count of external source structures + AkExternalSourceInfo* in_pExternalSources = NULL,///< Optional array of external source resolution information + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID///< Optional (advanced users only) Specify the playing ID to target with the event. Will Cause active actions in this event to target an existing Playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any for normal playback. + ) override; + + /// Executes an action on all nodes that are referenced in the specified event in an action of type play. + /// \return + /// - \c AK_Success if the action was successfully queued. + /// - \c AK_IDNotFound if the Event was not found (not loaded or there is a typo in the ID) + /// \sa + /// - AK::SoundEngine::AkActionOnEventType + AKRESULT ExecuteActionOnEvent( + AkUniqueID in_eventID, ///< Unique ID of the event + AK::SoundEngine::AkActionOnEventType in_ActionType, ///< Action to execute on all the elements that were played using the specified event. + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT, ///< Associated game object ID + AkTimeMs in_uTransitionDuration = 0, ///< Fade duration + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the transition + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Associated PlayingID + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Executes an action on all nodes that are referenced in the specified event in an action of type play. + /// \return + /// - \c AK_Success if the action was successfully queued. + /// - \c AK_IDNotFound if the Event was not found (not loaded or there is a typo in the ID) + /// \sa + /// - AK::SoundEngine::AkActionOnEventType + AKRESULT ExecuteActionOnEvent( + const wchar_t* in_pszEventName, ///< Name of the event + AK::SoundEngine::AkActionOnEventType in_ActionType, ///< Action to execute on all the elements that were played using the specified event. + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT, ///< Associated game object ID + AkTimeMs in_uTransitionDuration = 0, ///< Fade duration + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the transition + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Associated PlayingID + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Executes an Action on all nodes that are referenced in the specified Event in an Action of type Play. + /// \return + /// - \c AK_Success if the action was successfully queued. + /// - \c AK_IDNotFound if the Event was not found (not loaded or there is a typo in the ID) + /// \sa + /// - AK::SoundEngine::AkActionOnEventType + AKRESULT ExecuteActionOnEvent( + const char* in_pszEventName, ///< Name of the event + AK::SoundEngine::AkActionOnEventType in_ActionType, ///< Action to execute on all the elements that were played using the specified event. + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT, ///< Associated game object ID + AkTimeMs in_uTransitionDuration = 0, ///< Fade duration + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the transition + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Associated PlayingID + ) override; + + + /// Executes a number of MIDI Events on all nodes that are referenced in the specified Event in an Action of type Play. + /// The time at which a MIDI Event is posted is determined by in_bAbsoluteOffsets. If false, each MIDI event will be + /// posted in AkMIDIPost::uOffset samples from the start of the current frame. If true, each MIDI event will be posted + /// at the absolute time AkMIDIPost::uOffset samples. + /// To obtain the current absolute time, see AK::SoundEngine::GetSampleTick. + /// The duration of a sample can be determined from the sound engine's audio settings, via a call to AK::SoundEngine::GetAudioSettings. + /// If a playing ID is specified then that playing ID must be active. Otherwise a new playing ID will be assigned. + /// \return The playing ID of the event launched, or AK_INVALID_PLAYING_ID if posting the event failed and an error will be displayed in the debug console and the Wwise Profiler. + /// \sa + /// - AK::SoundEngine::GetAudioSettings + /// - AK::SoundEngine::GetSampleTick + /// - AK::SoundEngine::StopMIDIOnEvent + /// - \ref soundengine_midi_event_playing_id + AkPlayingID PostMIDIOnEvent( + AkUniqueID in_eventID, ///< Unique ID of the Event + AkGameObjectID in_gameObjectID, ///< Associated game object ID + AkMIDIPost* in_pPosts, ///< MIDI Events to post + AkUInt16 in_uNumPosts, ///< Number of MIDI Events to post + bool in_bAbsoluteOffsets = false, ///< Set to true when AkMIDIPost::uOffset are absolute, false when relative to current frame + AkUInt32 in_uFlags = 0, ///< Bitmask: see \ref AkCallbackType + AkCallbackFunc in_pfnCallback = NULL, ///< Callback function + void* in_pCookie = NULL, ///< Callback cookie that will be sent to the callback function along with additional information + AkPlayingID in_playingID = AK_INVALID_PLAYING_ID ///< Target playing ID + ) override; + + /// Stops MIDI notes on all nodes that are referenced in the specified event in an action of type play, + /// with the specified Game Object. Invalid parameters are interpreted as wildcards. For example, calling + /// this function with in_eventID set to AK_INVALID_UNIQUE_ID will stop all MIDI notes for Game Object + /// in_gameObjectID. + /// \return + /// - \c AK_Success if the stop command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::PostMIDIOnEvent + /// - \ref soundengine_midi_event_playing_id + AKRESULT StopMIDIOnEvent( + AkUniqueID in_eventID = AK_INVALID_UNIQUE_ID, ///< Unique ID of the Event + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT, ///< Associated game object ID + AkPlayingID in_playingID = AK_INVALID_PLAYING_ID ///< Target playing ID + ) override; + + + /// Starts streaming the first part of all streamed files referenced by an Event into a cache buffer. Caching streams are serviced when no other streams require the + /// available bandwidth. The files will remain cached until UnpinEventInStreamCache is called, or a higher priority pinned file needs the space and the limit set by + /// uMaxCachePinnedBytes is exceeded. + /// \remarks The amount of data from the start of the file that will be pinned to cache is determined by the prefetch size. The prefetch size is set via the authoring tool and stored in the sound banks. + /// \remarks It is possible to override the prefetch size stored in the sound bank via the low level IO. For more information see AK::StreamMgr::IAkFileLocationResolver::Open() and AkFileSystemFlags. + /// \remarks If this function is called additional times with the same event, then the priority of the caching streams are updated. Note however that priority is passed down to the stream manager + /// on a file-by-file basis, and if another event is pinned to cache that references the same file but with a different priority, then the first priority will be updated with the most recent value. + /// \remarks If the event references files that are chosen based on a State Group (via a switch container), all files in all states will be cached. Those in the current active state + /// will get cached with active priority, while all other files will get cached with inactive priority. + /// \remarks in_uInactivePriority is only relevant for events that reference switch containers that are assigned to State Groups. This parameter is ignored for all other events, including events that only reference + /// switch containers that are assigned to Switch Groups. Files that are chosen based on a Switch Group have a different switch value per game object, and are all effectively considered active by the pin-to-cache system. + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::GetBufferStatusForPinnedEvent + /// - AK::SoundEngine::UnpinEventInStreamCache + /// - AK::StreamMgr::IAkFileLocationResolver::Open + /// - AkFileSystemFlags + AKRESULT PinEventInStreamCache( + AkUniqueID in_eventID, ///< Unique ID of the event + AkPriority in_uActivePriority, ///< Priority of active stream caching I/O + AkPriority in_uInactivePriority ///< Priority of inactive stream caching I/O + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Starts streaming the first part of all streamed files referenced by an event into a cache buffer. Caching streams are serviced when no other streams require the + /// available bandwidth. The files will remain cached until UnpinEventInStreamCache is called, or a higher priority pinned file needs the space and the limit set by + /// uMaxCachePinnedBytes is exceeded. + /// \remarks The amount of data from the start of the file that will be pinned to cache is determined by the prefetch size. The prefetch size is set via the authoring tool and stored in the sound banks. + /// \remarks It is possible to override the prefetch size stored in the sound bank via the low level IO. For more information see AK::StreamMgr::IAkFileLocationResolver::Open() and AkFileSystemFlags. + /// \remarks If this function is called additional times with the same event, then the priority of the caching streams are updated. Note however that priority is passed down to the stream manager + /// on a file-by-file basis, and if another event is pinned to cache that references the same file but with a different priority, then the first priority will be updated with the most recent value. + /// \remarks If the event references files that are chosen based on a State Group (via a Switch Container), all files in all states will be cached. Those in the current active state + /// will get cached with active priority, while all other files will get cached with inactive priority. + /// \remarks in_uInactivePriority is only relevant for events that reference switch containers that are assigned to State Groups. This parameter is ignored for all other events, including events that only reference + /// switch containers that are assigned to Switch Groups. Files that are chosen based on a Switch Group have a different switch value per game object, and are all effectively considered active by the pin-to-cache system. + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::GetBufferStatusForPinnedEvent + /// - AK::SoundEngine::UnpinEventInStreamCache + /// - AK::StreamMgr::IAkFileLocationResolver::Open + /// - AkFileSystemFlags + AKRESULT PinEventInStreamCache( + const wchar_t* in_pszEventName, ///< Name of the event + AkPriority in_uActivePriority, ///< Priority of active stream caching I/O + AkPriority in_uInactivePriority ///< Priority of inactive stream caching I/O + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Starts streaming the first part of all streamed files referenced by an event into a cache buffer. Caching streams are serviced when no other streams require the + /// available bandwidth. The files will remain cached until UnpinEventInStreamCache is called, or a higher priority pinned file needs the space and the limit set by + /// uMaxCachePinnedBytes is exceeded. + /// \remarks The amount of data from the start of the file that will be pinned to cache is determined by the prefetch size. The prefetch size is set via the authoring tool and stored in the sound banks. + /// \remarks It is possible to override the prefetch size stored in the sound bank via the low level IO. For more information see AK::StreamMgr::IAkFileLocationResolver::Open() and AkFileSystemFlags. + /// \remarks If this function is called additional times with the same event, then the priority of the caching streams are updated. Note however that priority is passed down to the stream manager + /// on a file-by-file basis, and if another event is pinned to cache that references the same file but with a different priority, then the first priority will be updated with the most recent value. + /// \remarks If the event references files that are chosen based on a State Group (via a switch container), all files in all states will be cached. Those in the current active state + /// will get cached with active priority, while all other files will get cached with inactive priority. + /// \remarks in_uInactivePriority is only relevant for events that reference switch containers that are assigned to State Groups. This parameter is ignored for all other events, including events that only reference + /// switch containers that are assigned to Switch Groups. Files that are chosen based on a Switch Group have a different switch value per game object, and are all effectively considered active by the pin-to-cache system. + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::GetBufferStatusForPinnedEvent + /// - AK::SoundEngine::UnpinEventInStreamCache + /// - AK::StreamMgr::IAkFileLocationResolver::Open + /// - AkFileSystemFlags + AKRESULT PinEventInStreamCache( + const char* in_pszEventName, ///< Name of the event + AkPriority in_uActivePriority, ///< Priority of active stream caching I/O + AkPriority in_uInactivePriority ///< Priority of inactive stream caching I/O + ) override; + + /// Releases the set of files that were previously requested to be pinned into cache via AK::SoundEngine::PinEventInStreamCache(). The file may still remain in stream cache + /// after AK::SoundEngine::UnpinEventInStreamCache() is called, until the memory is reused by the streaming memory manager in accordance with to its cache management algorithm. + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::PinEventInStreamCache + /// - AK::SoundEngine::GetBufferStatusForPinnedEvent + AKRESULT UnpinEventInStreamCache( + AkUniqueID in_eventID ///< Unique ID of the event + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Releases the set of files that were previously requested to be pinned into cache via AK::SoundEngine::PinEventInStreamCache(). The file may still remain in stream cache + /// after AK::SoundEngine::UnpinEventInStreamCache() is called, until the memory is reused by the streaming memory manager in accordance with to its cache management algorithm. + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::PinEventInStreamCache + /// - AK::SoundEngine::GetBufferStatusForPinnedEvent + AKRESULT UnpinEventInStreamCache( + const wchar_t* in_pszEventName ///< Name of the event + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Releases the set of files that were previously requested to be pinned into cache via AK::SoundEngine::PinEventInStreamCache(). The file may still remain in stream cache + /// after AK::SoundEngine::UnpinEventInStreamCache() is called, until the memory is reused by the streaming memory manager in accordance with to its cache management algorithm. + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::PinEventInStreamCache + /// - AK::SoundEngine::GetBufferStatusForPinnedEvent + AKRESULT UnpinEventInStreamCache( + const char* in_pszEventName ///< Name of the event + ) override; + + /// Returns information about an Event that was requested to be pinned into cache via AK::SoundEngine::PinEventInStreamCache(). + /// Retrieves the smallest buffer fill-percentage for each file referenced by the event, and whether + /// the cache-pinned memory limit is preventing any of the files from filling up their buffer. + /// \remarks To set the limit for the maximum number of bytes that can be pinned to cache, see \c AkDeviceSettings + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::PinEventInStreamCache + /// - AK::SoundEngine::UnpinEventInStreamCache + /// - AkDeviceSettings + AKRESULT GetBufferStatusForPinnedEvent( + AkUniqueID in_eventID, ///< Unique ID of the event + AkReal32& out_fPercentBuffered, ///< Fill-percentage (out of 100) of requested buffer size for least buffered file in the event. + bool& out_bCachePinnedMemoryFull ///< True if at least one file can not complete buffering because the cache-pinned memory limit would be exceeded. + ) override; + + /// Returns information about an Event that was requested to be pinned into cache via \c AK::SoundEngine::PinEventInStreamCache(). + /// Retrieves the smallest buffer fill-percentage for each file referenced by the event, and whether + /// the cache-pinned memory limit is preventing any of the files from filling up their buffer. + /// \remarks To set the limit for the maximum number of bytes that can be pinned to cache, see AkDeviceSettings + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::PinEventInStreamCache + /// - AK::SoundEngine::UnpinEventInStreamCache + /// - AkDeviceSettings + AKRESULT GetBufferStatusForPinnedEvent( + const char* in_pszEventName, ///< Name of the event + AkReal32& out_fPercentBuffered, ///< Fill-percentage (out of 100) of requested buffer size for least buffered file in the event. + bool& out_bCachePinnedMemoryFull ///< True if at least one file can not complete buffering because the cache-pinned memory limit would be exceeded. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Returns information about an Event that was requested to be pinned into cache via \c AK::SoundEngine::PinEventInStreamCache(). + /// Retrieves the smallest buffer fill-percentage for each file referenced by the event, and whether + /// the cache-pinned memory limit is preventing any of the files from filling up their buffer. + /// \remarks To set the limit for the maximum number of bytes that can be pinned to cache, see AkDeviceSettings + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::PinEventInStreamCache + /// - AK::SoundEngine::UnpinEventInStreamCache + /// - AkDeviceSettings + AKRESULT GetBufferStatusForPinnedEvent( + const wchar_t* in_pszEventName, ///< Name of the event + AkReal32& out_fPercentBuffered, ///< Fill-percentage (out of 100) of requested buffer size for least buffered file in the event. + bool& out_bCachePinnedMemoryFull ///< True if at least one file can not complete buffering because the cache-pinned memory limit would be exceeded. + ) override; +#endif + + /// Seeks inside all playing objects that are referenced in Play Actions of the specified Event. + /// + /// Notes: + /// - This works with all objects of the actor-mixer hierarchy, and also with Music Segments and Music Switch Containers. + /// - There is a restriction with sounds that play within a continuous sequence. Seeking is ignored + /// if one of their ancestors is a continuous (random or sequence) container with crossfade or + /// trigger rate transitions. Seeking is also ignored with sample-accurate transitions, unless + /// the sound that is currently playing is the first sound of the sequence. + /// - Seeking is also ignored with voices that can go virtual with "From Beginning" behavior. + /// - Sounds/segments are stopped if in_iPosition is greater than their duration. + /// - in_iPosition is clamped internally to the beginning of the sound/segment. + /// - If the option "Seek to nearest marker" is used, the seeking position snaps to the nearest marker. + /// With objects of the actor-mixer hierarchy, markers are embedded in wave files by an external wave editor. + /// Note that looping regions ("sampler loop") are not considered as markers. Also, the "add file name marker" of the + /// conversion settings dialog adds a marker at the beginning of the file, which is considered when seeking + /// to nearest marker. In the case of objects of the interactive music hierarchy, user (wave) markers are ignored: + /// seeking occurs to the nearest segment cue (authored in the segment editor), including the Entry Cue, but excluding the Exit Cue. + /// - This method posts a command in the sound engine queue, thus seeking will not occur before + /// the audio thread consumes it (after a call to RenderAudio()). + /// + /// Notes specific to Music Segments: + /// - With Music Segments, in_iPosition is relative to the Entry Cue, in milliseconds. Use a negative + /// value to seek within the Pre-Entry. + /// - Music segments cannot be looped. You may want to listen to the AK_EndOfEvent notification + /// in order to restart them if required. + /// - In order to restart at the correct location, with all their tracks synchronized, Music Segments + /// take the "look-ahead time" property of their streamed tracks into account for seeking. + /// Consequently, the resulting position after a call to SeekOnEvent() might be earlier than the + /// value that was passed to the method. Use AK::MusicEngine::GetPlayingSegmentInfo() to query + /// the exact position of a segment. Also, the segment will be silent during that time + /// (so that it restarts precisely at the position that you specified). AK::MusicEngine::GetPlayingSegmentInfo() + /// also informs you about the remaining look-ahead time. + /// + /// Notes specific to Music Switch Containers: + /// - Seeking triggers a music transition towards the current (or target) segment. + /// This transition is subject to the container's transition rule that matches the current and defined in the container, + /// so the moment when seeking occurs depends on the rule's "Exit At" property. On the other hand, the starting position + /// in the target segment depends on both the desired seeking position and the rule's "Sync To" property. + /// - If the specified time is greater than the destination segment's length, the modulo is taken. + /// + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::PostEvent() + /// - AK::SoundEngine::GetSourcePlayPosition() + /// - AK::MusicEngine::GetPlayingSegmentInfo() + AKRESULT SeekOnEvent( + AkUniqueID in_eventID, ///< Unique ID of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID; use AK_INVALID_GAME_OBJECT to affect all game objects + AkTimeMs in_iPosition, ///< Desired position where playback should restart, in milliseconds + bool in_bSeekToNearestMarker = false, ///< If true, the final seeking position will be made equal to the nearest marker (see note above) + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Specify the playing ID for the seek to be applied to. Will result in the seek happening only on active actions of the playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any, to seek on all active actions of this event ID. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Seeks inside all playing objects that are referenced in Play Actions of the specified Event. + /// + /// Notes: + /// - This works with all objects of the actor-mixer hierarchy, and also with Music Segments and Music Switch Containers. + /// - There is a restriction with sounds that play within a continuous sequence. Seeking is ignored + /// if one of their ancestors is a continuous (random or sequence) container with crossfade or + /// trigger rate transitions. Seeking is also ignored with sample-accurate transitions, unless + /// the sound that is currently playing is the first sound of the sequence. + /// - Seeking is also ignored with voices that can go virtual with "From Beginning" behavior. + /// - With Music Segments, in_iPosition is relative to the Entry Cue, in milliseconds. Use a negative + /// value to seek within the Pre-Entry. + /// - Sounds/segments are stopped if in_iPosition is greater than their duration. + /// - in_iPosition is clamped internally to the beginning of the sound/segment. + /// - If the option "Seek to nearest marker" is used, the seeking position snaps to the nearest marker. + /// With objects of the actor-mixer hierarchy, markers are embedded in wave files by an external wave editor. + /// Note that looping regions ("sampler loop") are not considered as markers. Also, the "add file name marker" of the + /// conversion settings dialog adds a marker at the beginning of the file, which is considered when seeking + /// to nearest marker. In the case of objects of the interactive music hierarchy, user (wave) markers are ignored: + /// seeking occurs to the nearest segment cue (authored in the segment editor), including the Entry Cue, but excluding the Exit Cue. + /// - This method posts a command in the sound engine queue, thus seeking will not occur before + /// the audio thread consumes it (after a call to RenderAudio()). + /// + /// Notes specific to Music Segments: + /// - With Music Segments, in_iPosition is relative to the Entry Cue, in milliseconds. Use a negative + /// value to seek within the Pre-Entry. + /// - Music segments cannot be looped. You may want to listen to the AK_EndOfEvent notification + /// in order to restart them if required. + /// - In order to restart at the correct location, with all their tracks synchronized, Music Segments + /// take the "look-ahead time" property of their streamed tracks into account for seeking. + /// Consequently, the resulting position after a call to SeekOnEvent() might be earlier than the + /// value that was passed to the method. Use AK::MusicEngine::GetPlayingSegmentInfo() to query + /// the exact position of a segment. Also, the segment will be silent during that time + /// (so that it restarts precisely at the position that you specified). AK::MusicEngine::GetPlayingSegmentInfo() + /// also informs you about the remaining look-ahead time. + /// + /// Notes specific to Music Switch Containers: + /// - Seeking triggers a music transition towards the current (or target) segment. + /// This transition is subject to the container's transition rule that matches the current and defined in the container, + /// so the moment when seeking occurs depends on the rule's "Exit At" property. On the other hand, the starting position + /// in the target segment depends on both the desired seeking position and the rule's "Sync To" property. + /// - If the specified time is greater than the destination segment's length, the modulo is taken. + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::PostEvent() + /// - AK::SoundEngine::GetSourcePlayPosition() + /// - AK::MusicEngine::GetPlayingSegmentInfo() + AKRESULT SeekOnEvent( + const wchar_t* in_pszEventName, ///< Name of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID; use AK_INVALID_GAME_OBJECT to affect all game objects + AkTimeMs in_iPosition, ///< Desired position where playback should restart, in milliseconds + bool in_bSeekToNearestMarker = false, ///< If true, the final seeking position will be made equal to the nearest marker (see note above) + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Specify the playing ID for the seek to be applied to. Will result in the seek happening only on active actions of the playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any, to seek on all active actions of this event ID. + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Seeks inside all playing objects that are referenced in Play Actions of the specified Event. + /// + /// Notes: + /// - This works with all objects of the actor-mixer hierarchy, and also with Music Segments and Music Switch Containers. + /// - There is a restriction with sounds that play within a continuous sequence. Seeking is ignored + /// if one of their ancestors is a continuous (random or sequence) container with crossfade or + /// trigger rate transitions. Seeking is also ignored with sample-accurate transitions, unless + /// the sound that is currently playing is the first sound of the sequence. + /// - Seeking is also ignored with voices that can go virtual with "From Beginning" behavior. + /// - With Music Segments, in_iPosition is relative to the Entry Cue, in milliseconds. Use a negative + /// value to seek within the Pre-Entry. + /// - Sounds/segments are stopped if in_iPosition is greater than their duration. + /// - in_iPosition is clamped internally to the beginning of the sound/segment. + /// - If the option "Seek to nearest marker" is used, the seeking position snaps to the nearest marker. + /// With objects of the actor-mixer hierarchy, markers are embedded in wave files by an external wave editor. + /// Note that looping regions ("sampler loop") are not considered as markers. Also, the "add file name marker" of the + /// conversion settings dialog adds a marker at the beginning of the file, which is considered when seeking + /// to nearest marker. In the case of objects of the interactive music hierarchy, user (wave) markers are ignored: + /// seeking occurs to the nearest segment cue (authored in the segment editor), including the Entry Cue, but excluding the Exit Cue. + /// - This method posts a command in the sound engine queue, thus seeking will not occur before + /// the audio thread consumes it (after a call to RenderAudio()). + /// + /// Notes specific to Music Segments: + /// - With Music Segments, in_iPosition is relative to the Entry Cue, in milliseconds. Use a negative + /// value to seek within the Pre-Entry. + /// - Music segments cannot be looped. You may want to listen to the AK_EndOfEvent notification + /// in order to restart them if required. + /// - In order to restart at the correct location, with all their tracks synchronized, Music Segments + /// take the "look-ahead time" property of their streamed tracks into account for seeking. + /// Consequently, the resulting position after a call to SeekOnEvent() might be earlier than the + /// value that was passed to the method. Use AK::MusicEngine::GetPlayingSegmentInfo() to query + /// the exact position of a segment. Also, the segment will be silent during that time + /// (so that it restarts precisely at the position that you specified). AK::MusicEngine::GetPlayingSegmentInfo() + /// also informs you about the remaining look-ahead time. + /// + /// Notes specific to Music Switch Containers: + /// - Seeking triggers a music transition towards the current (or target) segment. + /// This transition is subject to the container's transition rule that matches the current and defined in the container, + /// so the moment when seeking occurs depends on the rule's "Exit At" property. On the other hand, the starting position + /// in the target segment depends on both the desired seeking position and the rule's "Sync To" property. + /// - If the specified time is greater than the destination segment's length, the modulo is taken. + /// + /// \return + /// - \c AK_Success if command was queued + /// - \c AK_IDNotFound if the Event ID is unknown (not loaded or typo in the ID) + /// \sa + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::PostEvent() + /// - AK::SoundEngine::GetSourcePlayPosition() + /// - AK::MusicEngine::GetPlayingSegmentInfo() + AKRESULT SeekOnEvent( + const char* in_pszEventName, ///< Name of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID; use AK_INVALID_GAME_OBJECT to affect all game objects + AkTimeMs in_iPosition, ///< Desired position where playback should restart, in milliseconds + bool in_bSeekToNearestMarker = false, ///< If true, the final seeking position will be made equal to the nearest marker (see note above) + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Specify the playing ID for the seek to be applied to. Will result in the seek happening only on active actions of the playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any, to seek on all active actions of this event ID. + ) override; + + /// Seeks inside all playing objects that are referenced in Play Actions of the specified Event. + /// Seek position is specified as a percentage of the sound's total duration, and takes looping into account. + /// + /// Notes: + /// - This works with all objects of the actor-mixer hierarchy, and also with Music Segments and Music Switch Containers. + /// - There is a restriction with sounds that play within a continuous sequence. Seeking is ignored + /// if one of their ancestors is a continuous (random or sequence) container with crossfade or + /// trigger rate transitions. Seeking is also ignored with sample-accurate transitions, unless + /// the sound that is currently playing is the first sound of the sequence. + /// - Seeking is also ignored with voices that can go virtual with "From Beginning" behavior. + /// - in_iPosition is clamped internally to the beginning of the sound/segment. + /// - If the option "Seek to nearest marker" is used, the seeking position snaps to the nearest marker. + /// With objects of the actor-mixer hierarchy, markers are embedded in wave files by an external wave editor. + /// Note that looping regions ("sampler loop") are not considered as markers. Also, the "add file name marker" of the + /// conversion settings dialog adds a marker at the beginning of the file, which is considered when seeking + /// to nearest marker. In the case of objects of the interactive music hierarchy, user (wave) markers are ignored: + /// seeking occurs to the nearest segment cue (authored in the segment editor), including the Entry Cue, but excluding the Exit Cue. + /// - This method posts a command in the sound engine queue, thus seeking will not occur before + /// the audio thread consumes it (after a call to RenderAudio()). + /// + /// Notes specific to Music Segments: + /// - With Music Segments, \c in_fPercent is relative to the Entry Cue, and the segment's duration is the + /// duration between its entry and exit cues. Consequently, you cannot seek within the pre-entry or + /// post-exit of a segment using this method. Use absolute values instead. + /// - Music Segments cannot be looped. You may want to listen to the \c AK_EndOfEvent notification + /// in order to restart them if required. + /// - In order to restart at the correct location, with all their tracks synchronized, Music Segments + /// take the "look-ahead time" property of their streamed tracks into account for seeking. + /// Consequently, the resulting position after a call to SeekOnEvent() might be earlier than the + /// value that was passed to the method. Use AK::MusicEngine::GetPlayingSegmentInfo() to query + /// the exact position of a segment. Also, the segment will be silent during the time that period + /// (so that it restarts precisely at the position that you specified). AK::MusicEngine::GetPlayingSegmentInfo() + /// also informs you about the remaining look-ahead time. + /// + /// Notes specific to Music Switch Containers: + /// - Seeking triggers a music transition towards the current (or target) segment. + /// This transition is subject to the container's transition rule that matches the current and defined in the container, + /// so the moment when seeking occurs depends on the rule's "Exit At" property. On the other hand, the starting position + /// in the target segment depends on both the desired seeking position and the rule's "Sync To" property. + /// - If the specified time is greater than the destination segment's length, the modulo is taken. + /// + /// \sa + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::PostEvent() + /// - AK::SoundEngine::GetSourcePlayPosition() + /// - AK::MusicEngine::GetPlayingSegmentInfo() + AKRESULT SeekOnEvent( + AkUniqueID in_eventID, ///< Unique ID of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID; use AK_INVALID_GAME_OBJECT to affect all game objects + AkReal32 in_fPercent, ///< Desired position where playback should restart, expressed in a percentage of the file's total duration, between 0 and 1.f (see note above about infinite looping sounds) + bool in_bSeekToNearestMarker = false, ///< If true, the final seeking position will be made equal to the nearest marker (see note above) + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Specify the playing ID for the seek to be applied to. Will result in the seek happening only on active actions of the playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any, to seek on all active actions of this event ID. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Seeks inside all playing objects that are referenced in Play Actions of the specified Event. + /// Seek position is specified as a percentage of the sound's total duration, and takes looping into account. + /// + /// Notes: + /// - This works with all objects of the actor-mixer hierarchy, and also with Music Segments and Music Switch Containers. + /// - There is a restriction with sounds that play within a continuous sequence. Seeking is ignored + /// if one of their ancestors is a continuous (random or sequence) container with crossfade or + /// trigger rate transitions. Seeking is also ignored with sample-accurate transitions, unless + /// the sound that is currently playing is the first sound of the sequence. + /// - Seeking is also ignored with voices that can go virtual with "From Beginning" behavior. + /// - If the option "Seek to nearest marker" is used, the seeking position snaps to the nearest marker. + /// With objects of the actor-mixer hierarchy, markers are embedded in wave files by an external wave editor. + /// Note that looping regions ("sampler loop") are not considered as markers. Also, the "add file name marker" of the + /// conversion settings dialog adds a marker at the beginning of the file, which is considered when seeking + /// to nearest marker. In the case of objects of the interactive music hierarchy, user (wave) markers are ignored: + /// seeking occurs to the nearest segment cue (authored in the segment editor), including the Entry Cue, but excluding the Exit Cue. + /// - This method posts a command in the sound engine queue, thus seeking will not occur before + /// the audio thread consumes it (after a call to RenderAudio()). + /// + /// Notes specific to Music Segments: + /// - With Music Segments, \c in_fPercent is relative to the Entry Cue, and the segment's duration is the + /// duration between its entry and exit cues. Consequently, you cannot seek within the pre-entry or + /// post-exit of a segment using this method. Use absolute values instead. + /// - Music Segments cannot be looped. You may want to listen to the \c AK_EndOfEvent notification + /// in order to restart them if required. + /// - In order to restart at the correct location, with all their tracks synchronized, Music Segments + /// take the "look-ahead time" property of their streamed tracks into account for seeking. + /// Consequently, the resulting position after a call to SeekOnEvent() might be earlier than the + /// value that was passed to the method. Use AK::MusicEngine::GetPlayingSegmentInfo() to query + /// the exact position of a segment. Also, the segment will be silent during the time that period + /// (so that it restarts precisely at the position that you specified). AK::MusicEngine::GetPlayingSegmentInfo() + /// also informs you about the remaining look-ahead time. + /// + /// Notes specific to Music Switch Containers: + /// - Seeking triggers a music transition towards the current (or target) segment. + /// This transition is subject to the container's transition rule that matches the current and defined in the container, + /// so the moment when seeking occurs depends on the rule's "Exit At" property. On the other hand, the starting position + /// in the target segment depends on both the desired seeking position and the rule's "Sync To" property. + /// - If the specified time is greater than the destination segment's length, the modulo is taken. + /// + /// \sa + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::PostEvent() + /// - AK::SoundEngine::GetSourcePlayPosition() + /// - AK::MusicEngine::GetPlayingSegmentInfo() + AKRESULT SeekOnEvent( + const wchar_t* in_pszEventName, ///< Name of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID; use AK_INVALID_GAME_OBJECT to affect all game objects + AkReal32 in_fPercent, ///< Desired position where playback should restart, expressed in a percentage of the file's total duration, between 0 and 1.f (see note above about infinite looping sounds) + bool in_bSeekToNearestMarker = false, ///< If true, the final seeking position will be made equal to the nearest marker (see note above) + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Specify the playing ID for the seek to be applied to. Will result in the seek happening only on active actions of the playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any, to seek on all active actions of this event ID. + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Seeks inside all playing objects that are referenced in Play Actions of the specified Event. + /// Seek position is specified as a percentage of the sound's total duration, and takes looping into account. + /// + /// Notes: + /// - This works with all objects of the actor-mixer hierarchy, and also with Music Segments and Music Switch Containers. + /// - There is a restriction with sounds that play within a continuous sequence. Seeking is ignored + /// if one of their ancestors is a continuous (random or sequence) container with crossfade or + /// trigger rate transitions. Seeking is also ignored with sample-accurate transitions, unless + /// the sound that is currently playing is the first sound of the sequence. + /// - Seeking is also ignored with voices that can go virtual with "From Beginning" behavior. + /// - If the option "Seek to nearest marker" is used, the seeking position snaps to the nearest marker. + /// With objects of the actor-mixer hierarchy, markers are embedded in wave files by an external wave editor. + /// Note that looping regions ("sampler loop") are not considered as markers. Also, the "add file name marker" of the + /// conversion settings dialog adds a marker at the beginning of the file, which is considered when seeking + /// to nearest marker. In the case of objects of the interactive music hierarchy, user (wave) markers are ignored: + /// seeking occurs to the nearest segment cue (authored in the segment editor), including the Entry Cue, but excluding the Exit Cue. + /// - This method posts a command in the sound engine queue, thus seeking will not occur before + /// the audio thread consumes it (after a call to RenderAudio()). + /// + /// Notes specific to Music Segments: + /// - With Music Segments, in_fPercent is relative to the Entry Cue, and the segment's duration is the + /// duration between its entry and exit cues. Consequently, you cannot seek within the pre-entry or + /// post-exit of a segment using this method. Use absolute values instead. + /// - Music segments cannot be looped. You may want to listen to the AK_EndOfEvent notification + /// in order to restart them if required. + /// - In order to restart at the correct location, with all their tracks synchronized, Music Segments + /// take the "look-ahead time" property of their streamed tracks into account for seeking. + /// Consequently, the resulting position after a call to SeekOnEvent() might be earlier than the + /// value that was passed to the method. Use AK::MusicEngine::GetPlayingSegmentInfo() to query + /// the exact position of a segment. Also, the segment will be silent during the time that period + /// (so that it restarts precisely at the position that you specified). AK::MusicEngine::GetPlayingSegmentInfo() + /// also informs you about the remaining look-ahead time. + /// + /// Notes specific to Music Switch Containers: + /// - Seeking triggers a music transition towards the current (or target) segment. + /// This transition is subject to the container's transition rule that matches the current and defined in the container, + /// so the moment when seeking occurs depends on the rule's "Exit At" property. On the other hand, the starting position + /// in the target segment depends on both the desired seeking position and the rule's "Sync To" property. + /// - If the specified time is greater than the destination segment's length, the modulo is taken. + /// + /// \sa + /// - AK::SoundEngine::RenderAudio() + /// - AK::SoundEngine::PostEvent() + /// - AK::SoundEngine::GetSourcePlayPosition() + /// - AK::MusicEngine::GetPlayingSegmentInfo() + AKRESULT SeekOnEvent( + const char* in_pszEventName, ///< Name of the event + AkGameObjectID in_gameObjectID, ///< Associated game object ID; use AK_INVALID_GAME_OBJECT to affect all game objects + AkReal32 in_fPercent, ///< Desired position where playback should restart, expressed in a percentage of the file's total duration, between 0 and 1.f (see note above about infinite looping sounds) + bool in_bSeekToNearestMarker = false, ///< If true, the final seeking position will be made equal to the nearest marker (see notes above). + AkPlayingID in_PlayingID = AK_INVALID_PLAYING_ID ///< Specify the playing ID for the seek to be applied to. Will result in the seek happening only on active actions of the playing ID. Let it be AK_INVALID_PLAYING_ID or do not specify any, to seek on all active actions of this event ID. + ) override; + + /// Cancels all Event callbacks associated with a specific callback cookie.\n + /// \sa + /// - \c AK::SoundEngine::PostEvent() + void CancelEventCallbackCookie( + void* in_pCookie ///< Callback cookie to be cancelled + ) override; + + /// Cancels all Event callbacks associated with a specific game object.\n + /// \sa + /// - \c AK::SoundEngine::PostEvent() + void CancelEventCallbackGameObject( + AkGameObjectID in_gameObjectID ///< ID of the game object to be cancelled + ) override; + + /// Cancels all Event callbacks for a specific playing ID. + /// \sa + /// - \c AK::SoundEngine::PostEvent() + void CancelEventCallback( + AkPlayingID in_playingID ///< Playing ID of the event that must not use callbacks + ) override; + + /// Gets the current position of the source associated with this playing ID, obtained from PostEvent(). If more than one source is playing, + /// the first to play is returned. + /// Notes: + /// - You need to pass AK_EnableGetSourcePlayPosition to PostEvent() in order to use this function, otherwise + /// it returns AK_Fail, even if the playing ID is valid. + /// - The source's position is updated at every audio frame, and the time at which this occurs is stored. + /// When you call this function from your thread, you therefore query the position that was updated in the previous audio frame. + /// If in_bExtrapolate is true (default), the returned position is extrapolated using the elapsed time since last + /// sound engine update and the source's playback rate. + /// \return + /// - \c AK_Success if successful. + /// - \c AK_InvalidParameter if the provided pointer is not valid. + /// - \c AK_PlayingIDNotFound if the playing ID is invalid (not playing yet, or finished playing). + /// \sa + /// - \ref soundengine_query_pos + /// - \ref concept_events + AKRESULT GetSourcePlayPosition( + AkPlayingID in_PlayingID, ///< Playing ID returned by AK::SoundEngine::PostEvent() + AkTimeMs* out_puPosition, ///< Position of the source (in ms) associated with the specified playing ID + bool in_bExtrapolate = true ///< Position is extrapolated based on time elapsed since last sound engine update. + ) override; + + /// Gets the current position of the sources associated with this playing ID, obtained from PostEvent(). + /// Notes: + /// - You need to pass AK_EnableGetSourcePlayPosition to PostEvent() in order to use this function, otherwise + /// it returns AK_Fail, even if the playing ID is valid. + /// - The source's position is updated at every audio frame, and the time at which this occurs is stored. + /// When you call this function from your thread, you therefore query the position that was updated in the previous audio frame. + /// If in_bExtrapolate is true (default), the returned position is extrapolated using the elapsed time since last + /// sound engine update and the source's playback rate. + /// - If 0 is passed in for the number of entries (*in_pcPositions == 0) then only the number of positions will be returned and the + /// position array (out_puPositions) will not be updated. + /// - The io_pcPositions pointer must be non-NULL. + /// out_puPositions may be NULL if *io_pcPositions == 0, otherwise it must be non-NULL. + /// \return + /// - \c AK_Success if successful. + /// - \c AK_InvalidParameter if the provided pointer is not valid. + /// - \c AK_PlayingIDNotFound if the playing ID is invalid (not playing yet, or finished playing). + /// \sa + /// - \ref soundengine_query_pos + /// - \ref concept_events + AKRESULT GetSourcePlayPositions( + AkPlayingID in_PlayingID, ///< Playing ID returned by AK::SoundEngine::PostEvent() + AkSourcePosition* out_puPositions, ///< Audio Node IDs and positions of sources associated with the specified playing ID + AkUInt32* io_pcPositions, ///< Number of entries in out_puPositions. Needs to be set to the size of the array: it is adjusted to the actual number of returned entries + bool in_bExtrapolate = true ///< Position is extrapolated based on time elapsed since last sound engine update + ) override; + + /// Gets the stream buffering of the sources associated with this playing ID, obtained from PostEvent(). + /// Notes: + /// - You need to pass AK_EnableGetSourceStreamBuffering to PostEvent() in order to use this function, otherwise + /// it returns AK_Fail, even if the playing ID is valid. + /// - The sources stream buffering is updated at every audio frame. If there are multiple sources associated with this playing ID, + /// the value returned corresponds to the least buffered source. + /// - The returned buffering status out_bIsBuffering will be true If any of the sources associated with the playing ID are actively being buffered. + /// It will be false if all of them have reached the end of file, or have reached a state where they are buffered enough and streaming is temporarily idle. + /// - Purely in-memory sources are excluded from this database. If all sources are in-memory, GetSourceStreamBuffering() will return AK_Fail. + /// - The returned buffering amount and state is not completely accurate with some hardware-accelerated codecs. In such cases, the amount of stream buffering is generally underestimated. + /// On the other hand, it is not guaranteed that the source will be ready to produce data at the next audio frame even if out_bIsBuffering has turned to false. + /// \return + /// - \c AK_Success if successful. + /// - \c AK_PlayingIDNotFound if the source data associated with this playing ID is not found, for example if PostEvent() was not called with AK_EnableGetSourceStreamBuffering, or if the header was not parsed. + /// \sa + /// - \ref concept_events + AKRESULT GetSourceStreamBuffering( + AkPlayingID in_PlayingID, ///< Playing ID returned by AK::SoundEngine::PostEvent() + AkTimeMs& out_buffering, ///< Returned amount of buffering (in ms) of the source (or one of the sources) associated with that playing ID + bool& out_bIsBuffering ///< Returned buffering status of the source(s) associated with that playing ID + ) override; + + /// Stops the current content playing associated to the specified game object ID. + /// If no game object is specified, all sounds will be stopped. + void StopAll( + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT ///< (Optional)Specify a game object to stop only playback associated to the provided game object ID. + ) override; + + /// Stop the current content playing associated to the specified playing ID. + /// \aknote + /// This function is deprecated. Please use ExecuteActionOnPlayingID() in its place. + /// \endaknote + /// \sa + /// - AK::SoundEngine::ExecuteActionOnPlayingID() + void StopPlayingID( + AkPlayingID in_playingID, ///< Playing ID to be stopped. + AkTimeMs in_uTransitionDuration = 0, ///< Fade duration + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear ///< Curve type to be used for the transition + ) override; + + /// Executes an Action on the content associated to the specified playing ID. + /// \sa + /// - AK::SoundEngine::AkActionOnEventType + void ExecuteActionOnPlayingID( + AK::SoundEngine::AkActionOnEventType in_ActionType, ///< Action to execute on the specified playing ID. + AkPlayingID in_playingID, ///< Playing ID on which to execute the action. + AkTimeMs in_uTransitionDuration = 0, ///< Fade duration + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear ///< Curve type to be used for the transition + ) override; + + /// Sets the random seed value. Can be used to synchronize randomness + /// across instances of the Sound Engine. + /// \remark This seeds the number generator used for all container randomizations + /// and the plug-in RNG; since it acts globally, this should be called right + /// before any PostEvent call where randomness synchronization is required, + /// and cannot guarantee similar results for continuous containers. + /// \sa + /// - AK::IAkPluginServiceRNG + void SetRandomSeed( + AkUInt32 in_uSeed ///< Random seed. + ) override; + + /// Mutes/Unmutes the busses tagged as background music. + /// This is automatically called for platforms that have user-music support. + /// This function is provided to give the same behavior on platforms that don't have user-music support. + void MuteBackgroundMusic( + bool in_bMute ///< Sets true to mute, false to unmute. + ) override; + //@} + + /// Gets the state of the Background Music busses. This state is either set directly + /// with \c AK::SoundEngine::MuteBackgroundMusic or by the OS, if it has User Music services. + /// \return true if the background music busses are muted, false if not. + bool GetBackgroundMusicMute() override; + //@} + + + /// Sends custom game data to a plug-in that resides on a bus (insert Effect or mixer plug-in). + /// Data will be copied and stored into a separate list. + /// Previous entry is deleted when a new one is sent. + /// Sets the data pointer to NULL to clear item from the list. + /// \aknote The plug-in type and ID is passed and matched with plugins set on the desired bus. + /// This means that you cannot send different data to various instances of the plug-in on a same bus.\endaknote + /// \return AK_Success if data was sent successfully. + AKRESULT SendPluginCustomGameData( + AkUniqueID in_busID, ///< Bus ID + AkGameObjectID in_busObjectID, ///< Bus Object ID. Pass AK_INVALID_GAME_OBJECT to send custom data with global scope. Game object scope supersedes global scope, as with RTPCs. + AkPluginType in_eType, ///< Plug-in type (for example, source or effect) + AkUInt32 in_uCompanyID, ///< Company identifier (as declared in the plug-in description XML file) + AkUInt32 in_uPluginID, ///< Plug-in identifier (as declared in the plug-in description XML file) + const void* in_pData, ///< The data blob + AkUInt32 in_uSizeInBytes ///< Size of data + ) override; + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Game Objects + //@{ + + /// Registers a game object. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if the specified AkGameObjectID is invalid (0 and -1 are invalid) + /// \remark Registering a game object twice does nothing. Unregistering it once unregisters it no + /// matter how many times it has been registered. + /// \sa + /// - AK::SoundEngine::UnregisterGameObj() + /// - AK::SoundEngine::UnregisterAllGameObj() + /// - \ref concept_gameobjects + AKRESULT RegisterGameObj( + AkGameObjectID in_gameObjectID ///< ID of the game object to be registered + ) override; + + /// Registers a game object. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if the specified AkGameObjectID is invalid (0 and -1 are invalid) + /// \remark Registering a game object twice does nothing. Unregistering it once unregisters it no + /// matter how many times it has been registered. + /// \sa + /// - AK::SoundEngine::UnregisterGameObj() + /// - AK::SoundEngine::UnregisterAllGameObj() + /// - \ref concept_gameobjects + AKRESULT RegisterGameObj( + AkGameObjectID in_gameObjectID, ///< ID of the game object to be registered + const char* in_pszObjName ///< Name of the game object (for monitoring purpose) + ) override; + + /// Unregisters a game object. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if the specified AkGameObjectID is invalid (0 is an invalid ID) + /// \remark Registering a game object twice does nothing. Unregistering it once unregisters it no + /// matter how many times it has been registered. Unregistering a game object while it is + /// in use is allowed, but the control over the parameters of this game object is lost. + /// For example, say a sound associated with this game object is a 3D moving sound. This sound will + /// stop moving when the game object is unregistered, and there will be no way to regain control over the game object. + /// \sa + /// - AK::SoundEngine::RegisterGameObj() + /// - AK::SoundEngine::UnregisterAllGameObj() + /// - \ref concept_gameobjects + AKRESULT UnregisterGameObj( + AkGameObjectID in_gameObjectID ///< ID of the game object to be unregistered. Use + /// AK_INVALID_GAME_OBJECT to unregister all game objects. + ) override; + + /// Unregister all game objects, or all game objects with a particular matching set of property flags. + /// This function to can be used to unregister all game objects. + /// \return + /// - \c AK_Success if successful + /// \remark Registering a game object twice does nothing. Unregistering it once unregisters it no + /// matter how many times it has been registered. Unregistering a game object while it is + /// in use is allowed, but the control over the parameters of this game object is lost. + /// For example, if a sound associated with this game object is a 3D moving sound, it will + /// stop moving once the game object is unregistered, and there will be no way to recover + /// the control over this game object. + /// \sa + /// - AK::SoundEngine::RegisterGameObj() + /// - AK::SoundEngine::UnregisterGameObj() + /// - \ref concept_gameobjects + AKRESULT UnregisterAllGameObj( + ) override; + + /// Sets the position of a game object. + /// \warning The object's orientation vector (in_Position.Orientation) must be normalized. + /// \return + /// - \c AK_Success when successful + /// - \c AK_InvalidParameter if parameters are not valid, for example: + /// + in_Position makes an invalid transform + /// + in_eFlags is not one of the valid enum values + /// + the game object ID is in the reserved ID range. + /// \sa + /// - \ref soundengine_3dpositions + AKRESULT SetPosition( + AkGameObjectID in_GameObjectID, ///< Game Object identifier + const AkSoundPosition& in_Position,///< Position to set; in_Position.Orientation must be normalized. + AkSetPositionFlags in_eFlags = AkSetPositionFlags_Default ///< Optional flags to independently set the position of the emitter or listener component. + ) override; + + /// Sets multiple positions to a single game object. + /// Setting multiple positions on a single game object is a way to simulate multiple emission sources while using the resources of only one voice. + /// This can be used to simulate wall openings, area sounds, or multiple objects emitting the same sound in the same area. + /// \aknote Calling AK::SoundEngine::SetMultiplePositions() with only one position is the same as calling AK::SoundEngine::SetPosition() \endaknote + /// \return + /// - \c AK_Success when successful + /// - \c AK_CommandTooLarge if the number of positions is too large for the command queue. Reduce the number of positions. + /// - \c AK_InvalidParameter if parameters are not valid, for example: + /// + in_Position makes an invalid transform + /// + in_eFlags is not one of the valid enum values + /// + the game object ID is in the reserved ID range. + /// \sa + /// - \ref soundengine_3dpositions + /// - \ref soundengine_3dpositions_multiplepos + /// - \ref AK::SoundEngine::MultiPositionType + AKRESULT SetMultiplePositions( + AkGameObjectID in_GameObjectID, ///< Game Object identifier. + const AkSoundPosition* in_pPositions, ///< Array of positions to apply. + AkUInt16 in_NumPositions, ///< Number of positions specified in the provided array. + AK::SoundEngine::MultiPositionType in_eMultiPositionType = AK::SoundEngine::MultiPositionType_MultiDirections, ///< \ref AK::SoundEngine::MultiPositionType + AkSetPositionFlags in_eFlags = AkSetPositionFlags_Default ///< Optional flags to independently set the position of the emitter or listener component. + ) override; + + /// Sets multiple positions to a single game object, with flexible assignment of input channels. + /// Setting multiple positions on a single game object is a way to simulate multiple emission sources while using the resources of only one voice. + /// This can be used to simulate wall openings, area sounds, or multiple objects emitting the same sound in the same area. + /// \aknote Calling AK::SoundEngine::SetMultiplePositions() with only one position is the same as calling AK::SoundEngine::SetPosition() \endaknote + /// \return + /// - \c AK_Success when successful + /// - \c AK_CommandTooLarge if the number of positions is too large for the command queue. Reduce the number of positions. + /// - \c AK_InvalidParameter if parameters are not valid. + /// \sa + /// - \ref soundengine_3dpositions + /// - \ref soundengine_3dpositions_multiplepos + /// - \ref AK::SoundEngine::MultiPositionType + AKRESULT SetMultiplePositions( + AkGameObjectID in_GameObjectID, ///< Game Object identifier. + const AkChannelEmitter* in_pPositions, ///< Array of positions to apply, each using its own channel mask. + AkUInt16 in_NumPositions, ///< Number of positions specified in the provided array. + AK::SoundEngine::MultiPositionType in_eMultiPositionType = AK::SoundEngine::MultiPositionType_MultiDirections, ///< \ref AK::SoundEngine::MultiPositionType + AkSetPositionFlags in_eFlags = AkSetPositionFlags_Default ///< Optional flags to independently set the position of the emitter or listener component. + ) override; + + /// Sets the scaling factor of a Game Object. + /// Modify the attenuation computations on this Game Object to simulate sounds with a larger or smaller area of effect. + /// \return + /// - \c AK_Success when successful + /// - \c AK_InvalidParameter if the scaling factor specified was 0 or negative. + /// - \c AK_InvalidFloatValue if the value specified was NaN or Inf + AKRESULT SetScalingFactor( + AkGameObjectID in_GameObjectID, ///< Game object identifier + AkReal32 in_fAttenuationScalingFactor ///< Scaling Factor, 1 means 100%, 0.5 means 50%, 2 means 200%, and so on. + ) override; + + /// Use the position of a separate game object for distance calculations for a specified listener. + /// When AK::SoundEngine::SetDistanceProbe() is called, Wwise calculates distance attenuation and filtering + /// based on the distance between the distance probe Game Object (\c in_distanceProbeGameObjectID) and the emitter Game Object's position. + /// In third-person perspective applications, the distance probe Game Object may be set to the player character's position, + /// and the listener Game Object's position to that of the camera. In this scenario, attenuation is based on + /// the distance between the character and the sound, whereas panning, spatialization, and spread and focus calculations are base on the camera. + /// Both Game Objects, \c in_listenerGameObjectID and \c in_distanceProbeGameObjectID must have been previously registered using AK::SoundEngine::RegisterGameObj. + /// This funciton is optional. if AK::SoundEngine::SetDistanceProbe() is never called, distance calculations are based on the listener Game Object position. + /// To clear the distance probe, and revert to using the listener position for distance calculations, pass \c AK_INVALID_GAME_OBJECT to \c in_distanceProbeGameObjectID. + /// \aknote If the distance probe Game Object is assigned multiple positions, then the first position is used for distance calculations by the listener. \endaknote + /// \return + /// - \c AK_Success when successful + /// \sa + /// - AK::SoundEngine::SetPosition() + AKRESULT SetDistanceProbe( + AkGameObjectID in_listenerGameObjectID, ///< Game object identifier for the listener. Must have been previously registered via RegisterGameObj. + AkGameObjectID in_distanceProbeGameObjectID ///< Game object identifier for the distance probe, or \c AK_INVALID_GAME_OBJECT to reset distance probe. If valid, must have been previously registered via RegisterGameObj. + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Bank Management + //@{ + + /// Unload all currently loaded banks. + /// It also internally calls ClearPreparedEvents() since at least one bank must have been loaded to allow preparing events. + /// \return + /// - \c AK_Success if successful + /// - \c AK_NotInitialized if the sound engine was not correctly initialized or if there is not enough memory to handle the command + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::LoadBank() + /// - \ref soundengine_banks + AKRESULT ClearBanks() override; + + /// Sets the I/O settings of the bank load and prepare event processes. + /// The sound engine uses default values unless explicitly set by calling this method. + /// \warning This function must be called before loading banks. + /// \return + /// - \c AK_Success if successful + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console + /// \sa + /// - \ref soundengine_banks + /// - \ref streamingdevicemanager + AKRESULT SetBankLoadIOSettings( + AkReal32 in_fThroughput, ///< Average throughput of bank data streaming (bytes/ms) (the default value is AK_DEFAULT_BANK_THROUGHPUT) + AkPriority in_priority ///< Priority of bank streaming (the default value is AK_DEFAULT_PRIORITY) + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Load a bank synchronously (by Unicode string).\n + /// The bank name and type are passed to the Stream Manager. + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// A bank load request will be posted, and consumed by the Bank Manager thread. + /// The function returns when the request has been completely processed. + /// \return + /// The bank ID, which is obtained by hashing the bank name (see GetIDFromString()). + /// You may use this ID with UnloadBank(). + /// - \c AK_Success: Load or unload successful. + /// - \c AK_BankAlreadyLoaded: This bank is already loaded, nothing done. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_InvalidParameter: Invalid parameter, invalid memory alignment. + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console + /// - \c AK_InvalidBankType if the bank type parameter is out of range. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure, check the debug console) + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// - The sound engine internally calls GetIDFromString(in_pszString) to return the correct bank ID. + /// Therefore, in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. The path should be resolved in + /// your implementation of the Stream Manager, or in the Low-Level I/O module if you use the default Stream Manager's implementation. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AK::SoundEngine::GetIDFromString() + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref streamingdevicemanager + /// - \ref streamingmanager_lowlevel + /// - \ref sdk_bank_training + AKRESULT LoadBank( + const wchar_t* in_pszString, ///< Name of the bank to load + AkBankID& out_bankID, ///< Returned bank ID + AkBankType in_bankType = AkBankType_User ///< Type of the bank to load + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Loads a bank synchronously.\n + /// The bank name and type are passed to the Stream Manager. + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// A bank load request will be posted, and consumed by the Bank Manager thread. + /// The function returns when the request has been completely processed. + /// \return + /// The bank ID, which is obtained by hashing the bank name (see GetIDFromString()). + /// You may use this ID with UnloadBank(). + /// - \c AK_Success: Load or unload successful. + /// - \c AK_BankAlreadyLoaded: This bank is already loaded, nothing done. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console + /// - \c AK_InvalidBankType if the bank type parameter is out of range. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// - The sound engine internally calls GetIDFromString(in_pszString) to return the correct bank ID. + /// Therefore, in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. The path should be resolved in + /// your implementation of the Stream Manager, or in the Low-Level I/O module if you use the default Stream Manager's implementation. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AK::SoundEngine::GetIDFromString + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref streamingdevicemanager + /// - \ref streamingmanager_lowlevel + /// - \ref sdk_bank_training + AKRESULT LoadBank( + const char* in_pszString, ///< Name of the bank to load + AkBankID& out_bankID, ///< Returned bank ID + AkBankType in_bankType = AkBankType_User ///< Type of the bank to load + ) override; + + /// Loads a bank synchronously (by ID).\n + /// \aknote Requires that the "Use SoundBank names" option be unchecked in the Wwise Project Settings. \endaknote + /// The bank ID is passed to the Stream Manager. + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// A bank load request will be posted, and consumed by the Bank Manager thread. + /// The function returns when the request has been completely processed. + /// \return + /// - \c AK_Success: Load or unload successful. + /// - \c AK_BankAlreadyLoaded: This bank is already loaded, nothing done. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. The bank is either shorter than expected or its data corrupted. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console or Wwise Profiler + /// - \c AK_InvalidBankType if the bank type parameter is out of range. + /// - \c AK_FileNotFound if the bank file was not found on disk. + /// - \c AK_FilePermissionError if the file permissions are wrong for the file + /// - \c AK_Fail: Load or unload failed for any other reason. , check the debug console or Wwise Profiler + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + AKRESULT LoadBank( + AkBankID in_bankID, ///< Bank ID of the bank to load + AkBankType in_bankType = AkBankType_User ///< Type of the bank to load + ) override; + + /// Loads a bank synchronously (from in-memory data, in-place, user bank only).\n + /// + /// IMPORTANT: Banks loaded from memory with in-place data MUST be unloaded using the UnloadBank function + /// providing the same memory pointer. Make sure you are using the correct UnloadBank(...) overload + /// + /// Use LoadBankMemoryView when you want to manage I/O on your side. Load the bank file + /// in a buffer and pass its address to the sound engine. + /// In-memory loading is in-place: *** the memory must be valid until the bank is unloaded. *** + /// A bank load request will be posted, and consumed by the Bank Manager thread. + /// The function returns when the request has been completely processed. + /// \return + /// The bank ID, which is stored in the first few bytes of the bank file. You may use this + /// ID with UnloadBank(). + /// - \c AK_Success: Load or unload successful. + /// - \c AK_BankAlreadyLoaded: This bank is already loaded, nothing done. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console + /// - \c AK_InvalidBankType if the bank is not a user-defined bank. + /// - \c AK_DataAlignmentError if the data pointer is not aligned properly + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// - The memory must be aligned on platform-specific AK_BANK_PLATFORM_DATA_ALIGNMENT bytes (see AkTypes.h). + /// - (XboxOne only): If the bank may contain XMA in memory data, the memory must be allocated using the Device memory allocator. + /// - Avoid using this function for banks containing a lot of events or structure data: this data will be unpacked into the sound engine heap, + /// making the supplied bank memory redundant. For event/structure-only banks, prefer LoadBankMemoryCopy(). + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + AKRESULT LoadBankMemoryView( + const void * in_pInMemoryBankPtr, ///< Pointer to the in-memory bank to load (pointer is stored in sound engine, memory must remain valid) + AkUInt32 in_uInMemoryBankSize, ///< Size of the in-memory bank to load + AkBankID & out_bankID ///< Returned bank ID + ) override; + + /// Loads a bank synchronously (from in-memory data, in-place, any bank type).\n + /// + /// IMPORTANT: Banks loaded from memory with in-place data MUST be unloaded using the UnloadBank function + /// providing the same memory pointer. Make sure you are using the correct UnloadBank(...) overload + /// + /// Use LoadBankMemoryView when you want to manage I/O on your side. Load the bank file + /// in a buffer and pass its address to the sound engine. + /// In-memory loading is in-place: *** the memory must be valid until the bank is unloaded. *** + /// A bank load request will be posted, and consumed by the Bank Manager thread. + /// The function returns when the request has been completely processed. + /// \return + /// The bank ID, which is stored in the first few bytes of the bank file. You may use this + /// ID with UnloadBank(). + /// - \c AK_Success: Load or unload successful. + /// - \c AK_BankAlreadyLoaded: This bank is already loaded, nothing done. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console + /// - \c AK_InvalidBankType if the bank type parameter is out of range. + /// - \c AK_DataAlignmentError if the data pointer is not aligned properly + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// - The memory must be aligned on platform-specific AK_BANK_PLATFORM_DATA_ALIGNMENT bytes (see AkTypes.h). + /// - (XboxOne only): If the bank may contain XMA in memory data, the memory must be allocated using the Device memory allocator. + /// - Avoid using this function for banks containing a lot of events or structure data: this data will be unpacked into the sound engine heap, + /// making the supplied bank memory redundant. For event/structure-only banks, prefer LoadBankMemoryCopy(). + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + AKRESULT LoadBankMemoryView( + const void* in_pInMemoryBankPtr, ///< Pointer to the in-memory bank to load (pointer is stored in sound engine, memory must remain valid) + AkUInt32 in_uInMemoryBankSize, ///< Size of the in-memory bank to load + AkBankID& out_bankID, ///< Returned bank ID + AkBankType& out_bankType ///< Returned bank type + ) override; + + /// Loads a bank synchronously (from in-memory data, out-of-place).\n + /// + /// NOTE: Banks loaded from in-memory with out-of-place data must be unloaded using the standard UnloadBank function + /// (with no memory pointer). Make sure you are using the correct UnloadBank(...) overload + /// + /// Use LoadBankMemoryCopy when you want to manage I/O on your side. Load the bank file + /// in a buffer and pass its address to the sound engine, the media section of the bank will be copied into newly + /// allocated memory. + /// In-memory loading is out-of-place: the buffer can be release as soon as the function returns. The advantage of using this + /// over the in-place version is that there is no duplication of bank structures. + /// A bank load request will be posted, and consumed by the Bank Manager thread. + /// The function returns when the request has been completely processed. + /// \return + /// The bank ID, which is stored in the first few bytes of the bank file. You may use this + /// ID with UnloadBank(). + /// - \c AK_Success: Load or unload successful. + /// - \c AK_BankAlreadyLoaded: This bank is already loaded, nothing done. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console + /// - \c AK_InvalidBankType if the bank is not a user-defined bank. + /// - \c AK_DataAlignmentError if the data pointer is not aligned properly + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + AKRESULT LoadBankMemoryCopy( + const void * in_pInMemoryBankPtr, ///< Pointer to the in-memory bank to load (pointer is not stored in sound engine, memory can be released after return) + AkUInt32 in_uInMemoryBankSize, ///< Size of the in-memory bank to load + AkBankID & out_bankID ///< Returned bank ID + ) override; + + /// Loads a bank synchronously (from in-memory data, out-of-place, any bank type).\n + /// + /// NOTE: Banks loaded from in-memory with out-of-place data must be unloaded using the standard UnloadBank function + /// (with no memory pointer). Make sure you are using the correct UnloadBank(...) overload + /// + /// Use LoadBankMemoryCopy when you want to manage I/O on your side. Load the bank file + /// in a buffer and pass its address to the sound engine, the media section of the bank will be copied into newly + /// allocated memory. + /// In-memory loading is out-of-place: the buffer can be release as soon as the function returns. The advantage of using this + /// over the in-place version is that there is no duplication of bank structures. + /// A bank load request will be posted, and consumed by the Bank Manager thread. + /// The function returns when the request has been completely processed. + /// \return + /// The bank ID, which is stored in the first few bytes of the bank file. You may use this + /// ID with UnloadBank(). + /// - \c AK_Success: Load or unload successful. + /// - \c AK_BankAlreadyLoaded: This bank is already loaded, nothing done. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_NotInitialized if the sound engine was not correctly initialized + /// - \c AK_InvalidParameter if some parameters are invalid, check the debug console + /// - \c AK_DataAlignmentError if the data pointer is not aligned properly + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + AKRESULT LoadBankMemoryCopy( + const void* in_pInMemoryBankPtr, ///< Pointer to the in-memory bank to load (pointer is not stored in sound engine, memory can be released after return) + AkUInt32 in_uInMemoryBankSize, ///< Size of the in-memory bank to load + AkBankID& out_bankID, ///< Returned bank ID + AkBankType& out_bankType ///< Returned bank type + ) override; + + /// Synchronously decodes Vorbis-encoded and Opus-encoded (Software version) media in a SoundBank. The file should already be read in memory before the decode operation. The out_pDecodedBankPtr can then be used with variants of LoadBank that load from in-memory data. + /// \n + /// CPU usage, RAM size, storage size and Internet bandwidth must be accounted for when developing a game, especially when it is aimed at mobile platforms. The DecodeBank function makes it possible to decode media at load time instead of decoding them every time they are played. + AKRESULT DecodeBank( + const void* in_pInMemoryBankPtr, ///< Pointer to the in-memory bank to decode (pointer is not stored in sound engine, memory can be released after return) + AkUInt32 in_uInMemoryBankSize, ///< Size of the in-memory bank to decode + AkMemPoolId in_uPoolForDecodedBank, ///< Memory pool to allocate decoded bank into. Specify AK_INVALID_POOL_ID and out_pDecodedBankPtr=NULL to obtain decoded bank size without performing the decode operation. Pass AK_INVALID_POOL_ID and out_pDecodedBankPtr!=NULL to decode bank into specified pointer. + void*& out_pDecodedBankPtr, ///< Decoded bank memory location. + AkUInt32& out_uDecodedBankSize ///< Decoded bank memory size. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Loads a bank asynchronously (by Unicode string).\n + /// The bank name is passed to the Stream Manager. + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// A bank load request will be posted to the Bank Manager consumer thread. + /// The function returns immediately. + /// \return + /// AK_Success if the scheduling was successful, AK_Fail otherwise. + /// Use a callback to be notified when completed, and get the status of the request. + /// The bank ID, which is obtained by hashing the bank name (see GetIDFromString()). + /// You may use this ID with UnloadBank(). + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// - The sound engine internally calls GetIDFromString(in_pszString) to return the correct bank ID. + /// Therefore, in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. The path should be resolved in + /// your implementation of the Stream Manager (AK::IAkStreamMgr::CreateStd()), or in the Low-Level I/O module + /// (AK::StreamMgr::IAkFileLocationResolver::Open()) if you use the default Stream Manager's implementation. + /// - The cookie (in_pCookie) is passed to the Low-Level I/O module for your convenience, in AK::StreamMgr::IAkFileLocationResolver::Open() + // as AkFileSystemFlags::pCustomParam. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref streamingdevicemanager + /// - \ref streamingmanager_lowlevel + /// - \ref sdk_bank_training + AKRESULT LoadBank( + const wchar_t* in_pszString, ///< Name/path of the bank to load + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function, and also to AK::StreamMgr::IAkFileLocationResolver::Open() as AkFileSystemFlags::pCustomParam) + AkBankID& out_bankID, ///< Returned bank ID + AkBankType in_bankType = AkBankType_User ///< Type of the bank to load + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Loads a bank asynchronously.\n + /// The bank name is passed to the Stream Manager. + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// A bank load request will be posted to the Bank Manager consumer thread. + /// The function returns immediately. + /// \return + /// - \c AK_Success if the scheduling was successful, + /// - \c AK_InvalidBankType if in_bankType was invalid + /// Use a callback to be notified when completed, and get the status of the request. + /// The bank ID, which is obtained by hashing the bank name (see GetIDFromString()). + /// You may use this ID with UnloadBank(). + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the Event will fail. + /// - The sound engine internally calls GetIDFromString(in_pszString) to return the correct bank ID. + /// Therefore, \c in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. The path should be resolved in + /// your implementation of the Stream Manager (AK::IAkStreamMgr::CreateStd()), or in the Low-Level I/O module + /// (AK::StreamMgr::IAkFileLocationResolver::Open()) if you use the default Stream Manager's implementation. + /// - The cookie (in_pCookie) is passed to the Low-Level I/O module for your convenience, in AK::StreamMgr::IAkFileLocationResolver::Open() + // as AkFileSystemFlags::pCustomParam. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref streamingdevicemanager + /// - \ref streamingmanager_lowlevel + /// - \ref sdk_bank_training + AKRESULT LoadBank( + const char* in_pszString, ///< Name/path of the bank to load + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function, and also to AK::StreamMgr::IAkFileLocationResolver::Open() as AkFileSystemFlags::pCustomParam) + AkBankID& out_bankID, ///< Returned bank ID + AkBankType in_bankType = AkBankType_User ///< Type of the bank to load + ) override; + + /// Loads a bank asynchronously (by ID).\n + /// \aknote Requires that the "Use SoundBank names" option be unchecked in the Wwise Project Settings. \endaknote + /// The bank ID is passed to the Stream Manager. + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// A bank load request will be posted to the Bank Manager consumer thread. + /// The function returns immediately. + /// \return + /// - \c AK_Success if the scheduling was successful, + /// - \c AK_InvalidBankType if in_bankType was invalid + /// Use a callback to be notified when completed, and get the status of the request. + /// The bank ID, which is obtained by hashing the bank name (see GetIDFromString()). + /// You may use this ID with \c UnloadBank(). + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// - The file path should be resolved in your implementation of the Stream Manager, or in the Low-Level I/O module if + /// you use the default Stream Manager's implementation. The ID overload of AK::IAkStreamMgr::CreateStd() and AK::StreamMgr::IAkFileLocationResolver::Open() are called. + /// - The cookie (in_pCookie) is passed to the Low-Level I/O module for your convenience, in AK::StreamMgr::IAkFileLocationResolver::Open() + // as AkFileSystemFlags::pCustomParam. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + AKRESULT LoadBank( + AkBankID in_bankID, ///< Bank ID of the bank to load + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function, and also to AK::StreamMgr::IAkFileLocationResolver::Open() as AkFileSystemFlags::pCustomParam) + AkBankType in_bankType = AkBankType_User ///< Type of the bank to load + ) override; + + /// Loads a bank asynchronously (from in-memory data, in-place).\n + /// + /// IMPORTANT: Banks loaded from memory with in-place data MUST be unloaded using the UnloadBank function + /// providing the same memory pointer. Make sure you are using the correct UnloadBank(...) overload + /// + /// Use LoadBankMemoryView when you want to manage I/O on your side. Load the bank file + /// in a buffer and pass its address to the sound engine. + /// In-memory loading is in-place: *** the memory must be valid until the bank is unloaded. *** + /// A bank load request will be posted to the Bank Manager consumer thread. + /// The function returns immediately. + /// \return + /// - \c AK_Success if the scheduling was successful, + /// - \c AK_InvalidBankType if in_bankType was invalid + /// - \c AK_DataAlignmentError if the data pointer is not aligned properly + /// Use a callback to be notified when completed, and get the status of the request. + /// The bank ID, which is obtained by hashing the bank name (see GetIDFromString()). + /// You may use this ID with UnloadBank(). + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// - The memory must be aligned on platform-specific AK_BANK_PLATFORM_DATA_ALIGNMENT bytes (see AkTypes.h). + /// - (XboxOne only): If the bank may contain XMA in memory data, the memory must be allocated using the Device memory allocator. + /// - Avoid using this function for banks containing a lot of events or structure data: this data will be unpacked into the sound engine heap, + /// making the supplied bank memory redundant. For event/structure-only banks, prefer LoadBankMemoryCopy(). + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + AKRESULT LoadBankMemoryView( + const void* in_pInMemoryBankPtr, ///< Pointer to the in-memory bank to load (pointer is stored in sound engine, memory must remain valid) + AkUInt32 in_uInMemoryBankSize, ///< Size of the in-memory bank to load + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie + AkBankID& out_bankID, ///< Returned bank ID + AkBankType& out_bankType ///< Returned bank type + ) override; + + /// Loads a bank asynchronously (from in-memory data, out-of-place).\n + /// + /// NOTE: Banks loaded from in-memory with out-of-place data must be unloaded using the standard UnloadBank function + /// (with no memory pointer). Make sure you are using the correct UnloadBank(...) overload + /// + /// Use LoadBankMemoryCopy when you want to manage I/O on your side. Load the bank file + /// in a buffer and pass its address to the sound engine, the media section of the bank will be copied into newly allocated + /// memory. + /// In-memory loading is out-of-place: the buffer can be released after the callback function is called. The advantage of using this + /// over the in-place version is that there is no duplication of bank structures. + /// A bank load request will be posted to the Bank Manager consumer thread. + /// The function returns immediately. + /// \return + /// - \c AK_Success if the scheduling was successful, + /// - \c AK_InvalidBankType if in_bankType was invalid + /// - \c AK_DataAlignmentError if the data pointer is not aligned properly + /// Use a callback to be notified when completed, and get the status of the request. + /// The bank ID, which is obtained by hashing the bank name (see GetIDFromString()). + /// You may use this ID with UnloadBank(). + /// \remarks + /// - The initialization bank must be loaded first. + /// - All SoundBanks subsequently loaded must come from the same Wwise project as the + /// initialization bank. If you need to load SoundBanks from a different project, you + /// must first unload ALL banks, including the initialization bank, then load the + /// initialization bank from the other project, and finally load banks from that project. + /// - Codecs and plug-ins must be registered before loading banks that use them. + /// - Loading a bank referencing an unregistered plug-in or codec will result in a load bank success, + /// but the plug-ins will not be used. More specifically, playing a sound that uses an unregistered effect plug-in + /// will result in audio playback without applying the said effect. If an unregistered source plug-in is used by an event's audio objects, + /// posting the event will fail. + /// \sa + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref integrating_elements_plugins + /// - \ref sdk_bank_training + AKRESULT LoadBankMemoryCopy( + const void* in_pInMemoryBankPtr, ///< Pointer to the in-memory bank to load (pointer is not stored in sound engine, memory can be released after callback) + AkUInt32 in_uInMemoryBankSize, ///< Size of the in-memory bank to load + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie + AkBankID& out_bankID, ///< Returned bank ID + AkBankType& out_bankType ///< Returned bank type + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Unloads a bank synchronously (by Unicode string).\n + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// \return AK_Success if successful, AK_Fail otherwise. AK_Success is returned when the bank was not loaded. + /// \remarks + /// - The sound engine internally calls GetIDFromString(in_pszString) to retrieve the bank ID, + /// then it calls the synchronous version of UnloadBank() by ID. + /// Therefore, in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. + /// - In order to force the memory deallocation of the bank, sounds that use media from this bank will be stopped. + /// This means that streamed sounds or generated sounds will not be stopped. + /// \sa + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + AKRESULT UnloadBank( + const wchar_t* in_pszString, ///< Name of the bank to unload + const void* in_pInMemoryBankPtr, ///< Memory pointer from where the bank was initially loaded from. (REQUIRED to determine which bank associated to a memory pointer must be unloaded). Pass NULL if NULL was passed when loading the bank or if LoadBankMemoryCopy was used to load the bank. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to unload + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Unloads a bank synchronously.\n + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// \return AK_Success if successful, AK_Fail otherwise. AK_Success is returned when the bank was not loaded. + /// \remarks + /// - The sound engine internally calls GetIDFromString(in_pszString) to retrieve the bank ID, + /// then it calls the synchronous version of UnloadBank() by ID. + /// Therefore, in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. + /// - In order to force the memory deallocation of the bank, sounds that use media from this bank will be stopped. + /// This means that streamed sounds or generated sounds will not be stopped. + /// \sa + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + AKRESULT UnloadBank( + const char* in_pszString, ///< Name of the bank to unload + const void* in_pInMemoryBankPtr, ///< Memory pointer from where the bank was initially loaded from. (REQUIRED to determine which bank associated to a memory pointer must be unloaded). Pass NULL if NULL was passed when loading the bank or if LoadBankMemoryCopy was used to load the bank. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to unload + ) override; + + /// Unloads a bank synchronously (by ID and memory pointer).\n + /// \return AK_Success if successful, AK_Fail otherwise. AK_Success is returned when the bank was not loaded. + /// \remarks + /// - In order to force the memory deallocation of the bank, sounds that use media from this bank will be stopped. + /// This means that streamed sounds or generated sounds will not be stopped. + /// \sa + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::ClearBanks() + /// - \ref soundengine_banks + AKRESULT UnloadBank( + AkBankID in_bankID, ///< ID of the bank to unload + const void* in_pInMemoryBankPtr, ///< Memory pointer from where the bank was initially loaded from. (REQUIRED to determine which bank associated to a memory pointer must be unloaded). Pass NULL if NULL was passed when loading the bank or if LoadBankMemoryCopy was used to load the bank. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to unload + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Unloads a bank asynchronously (by Unicode string).\n + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// \return AK_Success if scheduling successful (use a callback to be notified when completed) + /// \remarks + /// The sound engine internally calls GetIDFromString(in_pszString) to retrieve the bank ID, + /// then it calls the synchronous version of UnloadBank() by ID. + /// Therefore, in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. + /// - In order to force the memory deallocation of the bank, sounds that use media from this bank will be stopped. + /// This means that streamed sounds or generated sounds will not be stopped. + /// \sa + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + AKRESULT UnloadBank( + const wchar_t* in_pszString, ///< Name of the bank to unload + const void* in_pInMemoryBankPtr, ///< Memory pointer from where the bank was initially loaded from. (REQUIRED to determine which bank associated to a memory pointer must be unloaded). Pass NULL if NULL was passed when loading the bank or if LoadBankMemoryCopy was used to load the bank. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function) + AkBankType in_bankType = AkBankType_User ///< Type of the bank to unload + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Unloads a bank asynchronously.\n + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// \return AK_Success if scheduling successful (use a callback to be notified when completed) + /// \remarks + /// The sound engine internally calls GetIDFromString(in_pszString) to retrieve the bank ID, + /// then it calls the synchronous version of UnloadBank() by ID. + /// Therefore, in_pszString should be the real name of the SoundBank (with or without the BNK extension - it is trimmed internally), + /// not the name of the file (if you changed it), nor the full path of the file. + /// - In order to force the memory deallocation of the bank, sounds that use media from this bank will be stopped. + /// This means that streamed sounds or generated sounds will not be stopped. + /// \sa + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + AKRESULT UnloadBank( + const char* in_pszString, ///< Name of the bank to unload + const void* in_pInMemoryBankPtr, ///< Memory pointer from where the bank was initially loaded from. (REQUIRED to determine which bank associated to a memory pointer must be unloaded). Pass NULL if NULL was passed when loading the bank or if LoadBankMemoryCopy was used to load the bank. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function) + AkBankType in_bankType = AkBankType_User ///< Type of the bank to unload + ) override; + + /// Unloads a bank asynchronously (by ID and memory pointer).\n + /// Refer to \ref soundengine_banks_general for a discussion on using strings and IDs. + /// \return AK_Success if scheduling successful (use a callback to be notified when completed) + /// \remarks + /// - In order to force the memory deallocation of the bank, sounds that use media from this bank will be stopped. + /// This means that streamed sounds or generated sounds will not be stopped. + /// \sa + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + AKRESULT UnloadBank( + AkBankID in_bankID, ///< ID of the bank to unload + const void* in_pInMemoryBankPtr, ///< Memory pointer from where the bank was initially loaded from. (REQUIRED to determine which bank associated to a memory pointer must be unloaded). Pass NULL if NULL was passed when loading the bank or if LoadBankMemoryCopy was used to load the bank. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function) + AkBankType in_bankType = AkBankType_User ///< Type of the bank to unload + ) override; + + /// Cancels all Event callbacks associated with a specific callback cookie specified while loading Banks of preparing Events.\n + /// \sa + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::UnloadBank() + /// - AK::SoundEngine::ClearBanks() + /// - AkBankCallbackFunc + void CancelBankCallbackCookie( + void* in_pCookie ///< Callback cookie to be canceled + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// This function will load the Events, structural content, and optionally, the media content from the SoundBank. If the flag AkBankContent_All is specified, PrepareBank() will load the media content from + /// the bank; but, as opposed to LoadBank(), the media will be loaded one media item at a time instead of in one contiguous memory block. Using PrepareBank(), alone or in combination with PrepareEvent(), + /// will prevent in-memory duplication of media at the cost of some memory fragmentation. + /// Calling this function specifying the flag AkBankContent_StructureOnly will load only the structural part (including events) of this bank, + /// allowing using PrepareEvent() to load media on demand. + /// \sa + /// - \ref soundengine_banks_preparingbanks + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::PreparationType + /// \remarks + /// PrepareBank(), when called with the flag AkBankContent_StructureOnly, requires additional calls to PrepareEvent() to load the media for each event. PrepareEvent(), however, is unable to + /// access media content contained within SoundBanks and requires that the media be available as loose files in the file system. This flag may be useful to implement multiple loading configurations; + /// for example, a game may have a tool mode that uses PrepareEvent() to load loose files on-demand and, also, a game mode that uses LoadBank() to load the bank in entirety. + AKRESULT PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const wchar_t* in_pszString, ///< Name of the bank to Prepare/Unprepare. + AK::SoundEngine::AkBankContent in_uFlags = AK::SoundEngine::AkBankContent_All, ///< Structures only (including events) or all content. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to Prepare/Unprepare. + ) override; +#endif //AK_SUPPORT_WCHAR + + /// This function will load the Events, structural content, and optionally, the media content from the SoundBank. If the flag AkBankContent_All is specified, PrepareBank() will load the media content from + /// the bank; but, as opposed to LoadBank(), the media will be loaded one media item at a time instead of in one contiguous memory block. Using PrepareBank(), alone or in combination with PrepareEvent(), + /// will prevent in-memory duplication of media at the cost of some memory fragmentation. + /// Calling this function specifying the flag AkBankContent_StructureOnly will load only the structural part (including events) of this bank, + /// allowing using PrepareEvent() to load media on demand. + /// \sa + /// - \ref soundengine_banks_preparingbanks + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::PreparationType + /// \remarks + /// \c PrepareBank(), when called with the flag \c AkBankContent_StructureOnly, requires additional calls to \c PrepareEvent() to load the media for each event. \c PrepareEvent(), however, is unable to + /// access media content contained within SoundBanks and requires that the media be available as loose files in the file system. This flag may be useful to implement multiple loading configurations; + /// for example, a game may have a tool mode that uses PrepareEvent() to load loose files on-demand and, also, a game mode that uses \c LoadBank() to load the bank in entirety. + AKRESULT PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const char* in_pszString, ///< Name of the bank to Prepare/Unprepare. + AK::SoundEngine::AkBankContent in_uFlags = AK::SoundEngine::AkBankContent_All, ///< Structures only (including events) or all content. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to Prepare/Unprepare. + ) override; + + /// \n\aknote Requires that the "Use SoundBank names" option be unchecked in the Wwise Project Settings. \endaknote + /// This function will load the events, structural content, and optionally, the media content from the SoundBank. If the flag AkBankContent_All is specified, PrepareBank() will load the media content from + /// the bank, but as opposed to LoadBank(), the media will be loaded one media item at a time instead of in one contiguous memory block. Using PrepareBank(), alone or in combination with PrepareEvent(), + /// will prevent in-memory duplication of media at the cost of some memory fragmentation. + /// Calling this function specifying the flag AkBankContent_StructureOnly will load only the structural part (including events) of this bank, + /// allowing using PrepareEvent() to load media on demand. + /// \sa + /// - \ref soundengine_banks_preparingbanks + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::PreparationType + /// \remarks + /// \c PrepareBank(), when called with the flag AkBankContent_StructureOnly, requires additional calls to PrepareEvent() to load the media for each event. PrepareEvent(), however, is unable to + /// access media content contained within SoundBanks and requires that the media be available as loose files in the file system. This flag may be useful to implement multiple loading configurations; + /// for example, a game may have a tool mode that uses PrepareEvent() to load loose files on-demand and, also, a game mode that uses LoadBank() to load the bank in entirety. + AKRESULT PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkBankID in_bankID, ///< ID of the bank to Prepare/Unprepare. + AK::SoundEngine::AkBankContent in_uFlags = AK::SoundEngine::AkBankContent_All, ///< Structures only (including events) or all content. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to Prepare/Unprepare. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// This function will load the Events, structural content, and optionally, the media content from the SoundBank. If the flag AkBankContent_All is specified, PrepareBank() will load the media content from + /// the bank, but as opposed to LoadBank(), the media will be loaded one media item at a time instead of in one contiguous memory block. Using PrepareBank(), alone or in combination with PrepareEvent(), + /// will prevent in-memory duplication of media at the cost of some memory fragmentation. + /// Calling this function specifying the flag AkBankContent_StructureOnly will load only the structural part (including events) of this bank, + /// allowing using PrepareEvent() to load media on demand. + /// \sa + /// - \ref soundengine_banks_preparingbanks + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::PreparationType + /// \remarks + /// PrepareBank(), when called with the flag AkBankContent_StructureOnly, requires additional calls to PrepareEvent() to load the media for each event. PrepareEvent(), however, is unable to + /// access media content contained within SoundBanks and requires that the media be available as loose files in the file system. This flag may be useful to implement multiple loading configurations; + /// for example, a game may have a tool mode that uses PrepareEvent() to load loose files on-demand and, also, a game mode that uses LoadBank() to load the bank in entirety. + AKRESULT PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const wchar_t* in_pszString, ///< Name of the bank to Prepare/Unprepare. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function) + AK::SoundEngine::AkBankContent in_uFlags = AK::SoundEngine::AkBankContent_All, ///< Structures only (including events) or all content. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to Prepare/Unprepare. + ) override; +#endif //AK_SUPPORT_WCHAR + + /// This function will load the events, structural content, and optionally, the media content from the SoundBank. If the flag \c AkBankContent_All is specified, \c PrepareBank() will load the media content from + /// the bank, but as opposed to \c LoadBank(), the media will be loaded one media item at a time instead of in one contiguous memory block. Using \c PrepareBank(), alone or in combination with PrepareEvent(), + /// will prevent in-memory duplication of media at the cost of some memory fragmentation. + /// Calling this function specifying the flag AkBankContent_StructureOnly will load only the structural part (including events) of this bank, + /// allowing using PrepareEvent() to load media on demand. + /// \sa + /// - \ref soundengine_banks_preparingbanks + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::PreparationType() + /// \remarks + /// PrepareBank(), when called with the flag AkBankContent_StructureOnly, requires additional calls to PrepareEvent() to load the media for each event. PrepareEvent(), however, is unable to + /// access media content contained within SoundBanks and requires that the media be available as loose files in the file system. This flag may be useful to implement multiple loading configurations; + /// for example, a game may have a tool mode that uses PrepareEvent() to load loose files on-demand and, also, a game mode that uses LoadBank() to load the bank in entirety. + AKRESULT PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const char* in_pszString, ///< Name of the bank to Prepare/Unprepare. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function) + AK::SoundEngine::AkBankContent in_uFlags = AK::SoundEngine::AkBankContent_All, ///< Structures only (including events) or all content. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to Prepare/Unprepare. + ) override; + + /// \n\aknote Requires that the "Use SoundBank names" option be unchecked in the Wwise Project Settings. \endaknote + /// This function will load the events, structural content, and optionally, the media content from the SoundBank. If the flag AkBankContent_All is specified, \c PrepareBank() will load the media content from + /// the bank, but as opposed to \c LoadBank(), the media will be loaded one media item at a time instead of in one contiguous memory block. Using \c PrepareBank(), alone or in combination with \c PrepareEvent(), + /// will prevent in-memory duplication of media at the cost of some memory fragmentation. + /// Calling this function specifying the flag AkBankContent_StructureOnly will load only the structural part (including events) of this bank, + /// allowing using PrepareEvent() to load media on demand. + /// \sa + /// - \ref soundengine_banks_preparingbanks + /// - AK::SoundEngine::LoadBank() + /// - AK::SoundEngine::PreparationType() + /// \remarks + /// \c PrepareBank(), when called with the flag AkBankContent_StructureOnly, requires additional calls to PrepareEvent() to load the media for each event. \c PrepareEvent(), however, is unable to + /// access media content contained within SoundBanks and requires that the media be available as loose files in the file system. This flag may be useful to implement multiple loading configurations; + /// for example, a game may have a tool mode that uses \c PrepareEvent() to load loose files on-demand and, also, a game mode that uses \c LoadBank() to load the bank in entirety. + AKRESULT PrepareBank( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkBankID in_bankID, ///< ID of the bank to Prepare/Unprepare. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie, ///< Callback cookie (reserved to user, passed to the callback function) + AK::SoundEngine::AkBankContent in_uFlags = AK::SoundEngine::AkBankContent_All, ///< Structures only (including events) or all content. + AkBankType in_bankType = AkBankType_User ///< Type of the bank to Prepare/Unprepare. + ) override; + + /// Clear all previously prepared events.\n + /// \return + /// - \c AK_Success if successful. + /// - \c AK_Fail if the sound engine was not correctly initialized or if there is not enough memory to handle the command. + /// \remarks + /// The function \c ClearBanks() also clears all prepared events. + /// \sa + /// - \c AK::SoundEngine::PrepareEvent() + /// - \c AK::SoundEngine::ClearBanks() + AKRESULT ClearPreparedEvents() override; + +#ifdef AK_SUPPORT_WCHAR + /// Prepares or unprepares Events synchronously (by Unicode string).\n + /// The Events are identified by strings, and converted to IDs internally + /// (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// Before invoking \c PrepareEvent(), use \c LoadBank() to explicitly load the SoundBank(s) + /// that contain the Events and structures. When a request is posted to the + /// Bank Manager consumer thread, it will resolve all dependencies needed to + /// successfully post the specified Events and load the required loose media files. + /// \aknote Before version 2015.1, the required media files could be included + /// in a separate media SoundBank. As described in \ref whatsnew_2015_1_migration, + /// however,\c PrepareEvent() now only looks for loose media files. + /// \endaknote + /// The function returns when the request is completely processed. + /// \return + /// - \c AK_Success: Prepare/un-prepare successful. + /// - \c AK_IDNotFound: At least one of the event/game sync identifiers passed to PrepareEvent() does not exist. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_InvalidParameter: Invalid parameter, invalid memory alignment. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// Whenever at least one event fails to be resolved, the actions performed for all + /// other events are cancelled. + /// \sa + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::ClearPreparedEvents() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::LoadBank() + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const wchar_t** in_ppszString, ///< Array of event names + AkUInt32 in_uNumEvent ///< Number of events in the array + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Prepares or unprepares events synchronously.\n + /// The Events are identified by strings and converted to IDs internally + /// (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// Before invoking PrepareEvent(), use LoadBank() to explicitly load the SoundBank(s) + /// that contain the Events and structures. When a request is posted to the + /// Bank Manager consumer thread, it will resolve all dependencies needed to + /// successfully post the specified Events and load the required loose media files. + /// \aknote Before version 2015.1, the required media files could be included + /// in a separate media SoundBank. As described in \ref whatsnew_2015_1_migration, + /// however, PrepareEvent() now only looks for loose media files. + /// \endaknote + /// The function returns when the request is completely processed. + /// \return + /// - \c AK_Success: Prepare/un-prepare successful. + /// - \c AK_IDNotFound: At least one of the event/game sync identifiers passed to PrepareEvent() does not exist. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_InvalidParameter: Invalid parameter, invalid memory alignment. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// Whenever at least one event fails to be resolved, the actions performed for all + /// other events are cancelled. + /// \aknote The use of PrepareEvent is incompatible with LoadBank, using in-memory data. + /// \endaknote + /// \sa + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::ClearPreparedEvents() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::LoadBank() + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const char** in_ppszString, ///< Array of event names + AkUInt32 in_uNumEvent ///< Number of events in the array + ) override; + + /// Prepares or unprepares events synchronously (by ID). + /// The Events are identified by their ID (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// Before invoking PrepareEvent(), use LoadBank() to explicitly load the SoundBank(s) + /// that contain the Events and structures. When a request is posted to the + /// Bank Manager consumer thread, it will resolve all dependencies needed to + /// successfully post the specified Events and load the required loose media files. + /// \aknote Before version 2015.1, the required media files could be included + /// in a separate media SoundBank. As described in \ref whatsnew_2015_1_migration, + /// however, PrepareEvent() now only looks for loose media files. + /// \endaknote + /// The function returns when the request is completely processed. + /// \return + /// - \c AK_Success: Prepare/un-prepare successful. + /// - \c AK_IDNotFound: At least one of the event/game sync identifiers passed to PrepareEvent() does not exist. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_InvalidParameter: Invalid parameter, invalid memory alignment. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// Whenever at least one event fails to be resolved, the actions performed for all + /// other events are cancelled. + /// \sa + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::ClearPreparedEvents() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::LoadBank() + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkUniqueID* in_pEventID, ///< Array of event IDs + AkUInt32 in_uNumEvent ///< Number of event IDs in the array + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Prepares or unprepares an event asynchronously (by Unicode string). + /// The Events are identified by string (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// Before invoking PrepareEvent(), use LoadBank() to explicitly load the SoundBank(s) + /// that contain the Events and structures. When a request is posted to the + /// Bank Manager consumer thread, it will resolve all dependencies needed to + /// successfully post the specified Events and load the required loose media files. + /// \aknote Before version 2015.1, the required media files could be included + /// in a separate media SoundBank. As described in \ref whatsnew_2015_1_migration, + /// however, \c PrepareEvent() now only looks for loose media files. + /// \endaknote + /// The function returns immediately. Use a callback to be notified when the request has finished being processed. + /// \return AK_Success if scheduling is was successful, AK_Fail otherwise. + /// \remarks + /// Whenever at least one Event fails to be resolved, the actions performed for all + /// other Events are cancelled. + /// \sa + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::ClearPreparedEvents() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::LoadBank() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const wchar_t** in_ppszString, ///< Array of event names + AkUInt32 in_uNumEvent, ///< Number of events in the array + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie ///< Callback cookie (reserved to user, passed to the callback function) + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Prepares or unprepares an event asynchronously. + /// The Events are identified by string (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// Before invoking PrepareEvent(), use LoadBank() to explicitly load the SoundBank(s) + /// that contain the Events and structures. When a request is posted to the + /// Bank Manager consumer thread, it will resolve all dependencies needed to + /// successfully post the specified Events and load the required loose media files. + /// \aknote Before version 2015.1, the required media files could be included + /// in a separate media SoundBank. As described in \ref whatsnew_2015_1_migration, + /// however, PrepareEvent() now only looks for loose media files. + /// \endaknote + /// The function returns immediately. Use a callback to be notified when the request has finished being processed. + /// \return AK_Success if scheduling is was successful, AK_Fail otherwise. + /// \remarks + /// Whenever at least one event fails to be resolved, the actions performed for all + /// other events are cancelled. + /// \sa + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::ClearPreparedEvents() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::LoadBank() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + const char** in_ppszString, ///< Array of event names + AkUInt32 in_uNumEvent, ///< Number of events in the array + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie ///< Callback cookie (reserved to user, passed to the callback function) + ) override; + + /// Prepares or unprepares events asynchronously (by ID).\n + /// The Events are identified by their ID (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// Before invoking PrepareEvent(), use LoadBank() to explicitly load the SoundBank(s) + /// that contain the Events and structures. When a request is posted to the + /// Bank Manager consumer thread, it will resolve all dependencies needed to + /// successfully post the specified Events and load the required loose media files. + /// \aknote Before version 2015.1, the required media files could be included + /// in a separate media SoundBank. As described in \ref whatsnew_2015_1_migration, + /// however, PrepareEvent() now only looks for loose media files. + /// \endaknote + /// The function returns immediately. Use a callback to be notified when the request has finished being processed. + /// \return AK_Success if scheduling is was successful, AK_Fail otherwise. + /// \remarks + /// Whenever at least one event fails to be resolved, the actions performed for all + /// other events are cancelled. + /// \sa + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::ClearPreparedEvents() + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::LoadBank() + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareEvent( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkUniqueID* in_pEventID, ///< Array of event IDs + AkUInt32 in_uNumEvent, ///< Number of event IDs in the array + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie ///< Callback cookie (reserved to user, passed to the callback function) + ) override; + + /// Indicates the location of a specific Media ID in memory + /// The sources are identified by their ID (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// \return AK_Success if operation was successful, AK_InvalidParameter if in_pSourceSettings is invalid or media sizes are 0. + AKRESULT SetMedia( + AkSourceSettings* in_pSourceSettings, ///< Array of Source Settings + AkUInt32 in_uNumSourceSettings ///< Number of Source Settings in the array + ) override; + + /// Removes the specified source from the list of loaded media, even if this media is already in use. + /// The sources are identified by their ID (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// \aknote This function is unsafe and deprecated. Use TryUnsetMedia() in its place. + /// Media that is still in use by the sound engine should not be unset by this function. + /// If the media is still in use, this function will attempt to forcibly kill all sounds and effects referencing this media, + /// and then return AK_ResourceInUse. The client should NOT presume that the memory can be safely released at this point. + /// The moment at which the memory can be safely released is unknown, and the only safe course of action is to keep the memory + /// alive until the sound engine is terminated. + /// \endaknote + /// \return + /// - \c AK_Success: Operation was successful, and the memory can be released on the client side. + /// - \c AK_ResourceInUse: Specified media is still in use by the sound engine, the client should not release the memory. + /// - \c AK_InvalidParameter: in_pSourceSettings is invalid + AKRESULT UnsetMedia( + AkSourceSettings* in_pSourceSettings, ///< Array of Source Settings + AkUInt32 in_uNumSourceSettings ///< Number of Source Settings in the array + ) override; + + /// Removes the specified source from the list of loaded media, only if this media is not already in use. + /// The sources are identified by their ID (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// \aknote Media that is still in use by the sound engine should not be unset. It is marked for removal to prevent additional use. + /// If this function returns AK_ResourceInUse, then the client must not release memory for this media. + /// Instead, the client should retry the TryUnsetMedia operation later with the same parameters and check for AK_Success. + /// \endaknote + /// If out_pUnsetResults is not null, then it is assumed to point to an array of result codes of the same length as in_pSourceSettings. + /// out_pUnsetResults will be filled with either AK_Success or AK_ResourceInUse, indicating which media was still in use and not unset. + /// \return + /// - \c AK_Success: Operation was successful, and the memory can be released on the client side. + /// - \c AK_ResourceInUse: Specified media is still in use by the sound engine, and the media was not unset. Do not release memory, and try again later. + /// - \c AK_InvalidParameter: in_pSourceSettings is invalid + AKRESULT TryUnsetMedia( + AkSourceSettings* in_pSourceSettings, ///< Array of Source Settings + AkUInt32 in_uNumSourceSettings, ///< Number of Source Settings in the array + AKRESULT* out_pUnsetResults ///< (optional, can be null) Array of result codes + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Prepares or unprepares game syncs synchronously (by Unicode string).\n + /// The group and game syncs are specified by string (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// The game syncs definitions must already exist in the sound engine by having + /// explicitly loaded the bank(s) that contain them (with LoadBank()). + /// A request is posted to the Bank Manager consumer thread. It will resolve all + /// dependencies needed to successfully set this game sync group to one of the + /// game sync values specified, and load the required banks, if applicable. + /// The function returns when the request has been completely processed. + /// \return + /// - \c AK_Success: Prepare/un-prepare successful. + /// - \c AK_IDNotFound: At least one of the event/game sync identifiers passed to PrepareGameSyncs() does not exist. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_InvalidParameter: Invalid parameter, invalid memory alignment. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// You need to call PrepareGameSyncs() if the sound engine was initialized with AkInitSettings::bEnableGameSyncPreparation + /// set to true. When set to false, the sound engine automatically prepares all game syncs when preparing events, + /// so you never need to call this function. + /// \sa + /// - \c AK::SoundEngine::GetIDFromString() + /// - \c AK::SoundEngine::PrepareEvent() + /// - \c AK::SoundEngine::LoadBank() + /// - \c AkInitSettings + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkGroupType in_eGameSyncType, ///< The type of game sync. + const wchar_t* in_pszGroupName, ///< The State Group Name or the Switch Group Name. + const wchar_t** in_ppszGameSyncName, ///< The specific ID of the state to either support or not support. + AkUInt32 in_uNumGameSyncs ///< The number of game sync in the string array. + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Prepares or unprepares game syncs synchronously.\n + /// The group and game syncs are specified by string (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// The game syncs definitions must already exist in the sound engine by having + /// explicitly loaded the bank(s) that contain them (with LoadBank()). + /// A request is posted to the Bank Manager consumer thread. It will resolve all + /// dependencies needed to successfully set this game sync group to one of the + /// game sync values specified, and load the required banks, if applicable. + /// The function returns when the request has been completely processed. + /// \return + /// - \c AK_Success: Prepare/un-prepare successful. + /// - \c AK_IDNotFound: At least one of the event/game sync identifiers passed to PrepareGameSyncs() does not exist. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_InvalidParameter: Invalid parameter, invalid memory alignment. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// You need to call PrepareGameSyncs() if the sound engine was initialized with AkInitSettings::bEnableGameSyncPreparation + /// set to true. When set to false, the sound engine automatically prepares all game syncs when preparing events, + /// so you never need to call this function. + /// \sa + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::LoadBank() + /// - AkInitSettings + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkGroupType in_eGameSyncType, ///< The type of game sync. + const char* in_pszGroupName, ///< The State Group Name or the Switch Group Name. + const char** in_ppszGameSyncName, ///< The specific ID of the state to either support or not support. + AkUInt32 in_uNumGameSyncs ///< The number of game sync in the string array. + ) override; + + /// Prepares or unprepares game syncs synchronously (by ID).\n + /// The group and game syncs are specified by ID (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// The game syncs definitions must already exist in the sound engine by having + /// explicitly loaded the bank(s) that contain them (with LoadBank()). + /// A request is posted to the Bank Manager consumer thread. It will resolve all + /// dependencies needed to successfully set this game sync group to one of the + /// game sync values specified, and load the required banks, if applicable. + /// The function returns when the request has been completely processed. + /// \return + /// - \c AK_Success: Prepare/un-prepare successful. + /// - \c AK_IDNotFound: At least one of the event/game sync identifiers passed to PrepareGameSyncs() does not exist. + /// - \c AK_InsufficientMemory: Insufficient memory to store bank data. + /// - \c AK_BankReadError: I/O error. + /// - \c AK_WrongBankVersion: Invalid bank version: make sure the version of Wwise that + /// you used to generate the SoundBanks matches that of the SDK you are currently using. + /// - \c AK_InvalidFile: File specified could not be opened. + /// - \c AK_InvalidParameter: Invalid parameter, invalid memory alignment. + /// - \c AK_Fail: Load or unload failed for any other reason. (Most likely small allocation failure) + /// \remarks + /// You need to call \c PrepareGameSyncs() if the sound engine was initialized with \c AkInitSettings::bEnableGameSyncPreparation + /// set to \c true. When set to \c false, the sound engine automatically prepares all game syncs when preparing Events, + /// so you never need to call this function. + /// \sa + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::LoadBank() + /// - AkInitSettings + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkGroupType in_eGameSyncType, ///< The type of game sync. + AkUInt32 in_GroupID, ///< The State Group ID or the Switch Group ID. + AkUInt32* in_paGameSyncID, ///< Array of ID of the game syncs to either support or not support. + AkUInt32 in_uNumGameSyncs ///< The number of game sync ID in the array. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Prepares or unprepares game syncs asynchronously (by Unicode string).\n + /// The group and game syncs are specified by string (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// The game syncs definitions must already exist in the sound engine by having + /// explicitly loaded the bank(s) that contain them (with LoadBank()). + /// A request is posted to the Bank Manager consumer thread. It will resolve all + /// dependencies needed to successfully set this game sync group to one of the + /// game sync values specified, and load the required banks, if applicable. + /// The function returns immediately. Use a callback to be notified when the request has finished being processed. + /// \return AK_Success if scheduling is was successful, AK_Fail otherwise. + /// \remarks + /// You need to call \c PrepareGameSyncs() if the sound engine was initialized with \c AkInitSettings::bEnableGameSyncPreparation + /// set to \c true. When set to \c false, the sound engine automatically prepares all game syncs when preparing Events, + /// so you never need to call this function. + /// \sa + /// - \c AK::SoundEngine::GetIDFromString() + /// - \c AK::SoundEngine::PrepareEvent() + /// - \c AK::SoundEngine::LoadBank() + /// - \c AkInitSettings + /// - \c AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkGroupType in_eGameSyncType, ///< The type of game sync. + const wchar_t* in_pszGroupName, ///< The State Group Name or the Switch Group Name. + const wchar_t** in_ppszGameSyncName, ///< The specific ID of the state to either support or not support. + AkUInt32 in_uNumGameSyncs, ///< The number of game sync in the string array. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie ///< Callback cookie (reserved to user, passed to the callback function) + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Prepares or unprepares game syncs asynchronously.\n + /// The group and game syncs are specified by string (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// The game syncs definitions must already exist in the sound engine by having + /// explicitly loaded the bank(s) that contain them (with LoadBank()). + /// A request is posted to the Bank Manager consumer thread. It will resolve all + /// dependencies needed to successfully set this game sync group to one of the + /// game sync values specified, and load the required banks, if applicable. + /// The function returns immediately. Use a callback to be notified when the request has finished being processed. + /// \return AK_Success if scheduling is was successful, AK_Fail otherwise. + /// \remarks + /// You need to call PrepareGameSyncs() if the sound engine was initialized with AkInitSettings::bEnableGameSyncPreparation + /// set to true. When set to false, the sound engine automatically prepares all game syncs when preparing events, + /// so you never need to call this function. + /// \sa + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::LoadBank() + /// - AkInitSettings + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkGroupType in_eGameSyncType, ///< The type of game sync. + const char* in_pszGroupName, ///< The State Group Name or the Switch Group Name. + const char** in_ppszGameSyncName, ///< The specific ID of the state to either support or not support. + AkUInt32 in_uNumGameSyncs, ///< The number of game sync in the string array. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie ///< Callback cookie (reserved to user, passed to the callback function) + ) override; + + /// Prepares or un-prepare game syncs asynchronously (by ID).\n + /// The group and game syncs are specified by ID (refer to \ref soundengine_banks_general for a discussion on using strings and IDs). + /// The game syncs definitions must already exist in the sound engine by having + /// explicitly loaded the bank(s) that contain them (with LoadBank()). + /// A request is posted to the Bank Manager consumer thread. It will resolve all + /// dependencies needed to successfully set this game sync group to one of the + /// game sync values specified, and load the required banks, if applicable. + /// The function returns immediately. Use a callback to be notified when the request has finished being processed. + /// \return AK_Success if scheduling is was successful, AK_Fail otherwise. + /// \remarks + /// You need to call PrepareGameSyncs() if the sound engine was initialized with AkInitSettings::bEnableGameSyncPreparation + /// set to true. When set to false, the sound engine automatically prepares all Game Syncs when preparing Events, + /// so you never need to call this function. + /// \sa + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::PrepareEvent() + /// - AK::SoundEngine::LoadBank() + /// - AkInitSettings + /// - AkBankCallbackFunc + /// - \ref soundengine_banks + /// - \ref sdk_bank_training + AKRESULT PrepareGameSyncs( + AK::SoundEngine::PreparationType in_PreparationType, ///< Preparation type ( Preparation_Load or Preparation_Unload ) + AkGroupType in_eGameSyncType, ///< The type of game sync. + AkUInt32 in_GroupID, ///< The State Group ID or the Switch Group ID. + AkUInt32* in_paGameSyncID, ///< Array of ID of the Game Syncs to either support or not support. + AkUInt32 in_uNumGameSyncs, ///< The number of game sync ID in the array. + AkBankCallbackFunc in_pfnBankCallback, ///< Callback function + void* in_pCookie ///< Callback cookie (reserved to user, passed to the callback function) + ) override; + + //@} + + + //////////////////////////////////////////////////////////////////////// + /// @name Listeners + //@{ + + /// Sets a game object's associated listeners. + /// All listeners that have previously been added via AddListener or set via SetListeners will be removed and replaced with the listeners in the array in_pListenerGameObjs. + /// Calling this function will override the default set of listeners and in_emitterGameObj will now reference its own, unique set of listeners. + /// \return + /// - \c AK_Success if successful + /// - \c AK_CommandTooLarge if the number of positions is too large for the command queue. Reduce the number of positions. + /// \sa + /// - AK::SoundEngine::AddListener + /// - AK::SoundEngine::RemoveListener + /// - AK::SoundEngine::SetDefaultListeners + /// - \ref soundengine_listeners + AKRESULT SetListeners( + AkGameObjectID in_emitterGameObj, ///< Emitter game object. Must have been previously registered via RegisterGameObj. + const AkGameObjectID* in_pListenerGameObjs, ///< Array of listener game object IDs that will be activated for in_emitterGameObj. + AkUInt32 in_uNumListeners ///< Length of array + ) override; + + /// Add a single listener to a game object's set of associated listeners. + /// Any listeners that have previously been added or set via AddListener or SetListeners will remain as listeners and in_listenerGameObj will be added as an additional listener. + /// Calling this function will override the default set of listeners and in_emitterGameObj will now reference its own, unique set of listeners. + /// \sa + /// - AK::SoundEngine::SetListeners + /// - AK::SoundEngine::RemoveListener + /// - AK::SoundEngine::SetDefaultListeners + /// - \ref soundengine_listeners + AKRESULT AddListener( + AkGameObjectID in_emitterGameObj, ///< Emitter game object. Must have been previously registered via RegisterGameObj. + AkGameObjectID in_listenerGameObj ///< Listener game object IDs that will be activated for in_emitterGameObj. + ) override; + + /// Remove a single listener from a game object's set of active listeners. + /// Calling this function will override the default set of listeners and in_emitterGameObj will now reference its own, unique set of listeners. + /// \sa + /// - AK::SoundEngine::SetListeners + /// - AK::SoundEngine::AddListener + /// - AK::SoundEngine::SetDefaultListeners + /// - \ref soundengine_listeners + AKRESULT RemoveListener( + AkGameObjectID in_emitterGameObj, ///< Emitter game object. + AkGameObjectID in_listenerGameObj ///< Listener game object IDs that will be deactivated for in_emitterGameObj. Game objects must have been previously registered. + ) override; + + /// Sets the default set of associated listeners for game objects that have not explicitly overridden their listener sets. Upon registration, all game objects reference the default listener set, until + /// a call to AddListener, RemoveListener, SetListeners or SetGameObjectOutputBusVolume is made on that game object. + /// All default listeners that have previously been added via AddDefaultListener or set via SetDefaultListeners will be removed and replaced with the listeners in the array in_pListenerGameObjs. + /// \return Always returns AK_Success + /// \sa + /// - \ref soundengine_listeners + AKRESULT SetDefaultListeners( + const AkGameObjectID* in_pListenerObjs, ///< Array of listener game object IDs that will be activated for subsequent registrations. Game objects must have been previously registered. + AkUInt32 in_uNumListeners ///< Length of array + ) override; + + /// Add a single listener to the default set of listeners. Upon registration, all game objects reference the default listener set, until + /// a call to AddListener, RemoveListener, SetListeners or SetGameObjectOutputBusVolume is made on that game object. + /// \sa + /// - AK::SoundEngine::SetDefaultListeners + /// - AK::SoundEngine::RemoveDefaultListener + /// - \ref soundengine_listeners + AKRESULT AddDefaultListener( + AkGameObjectID in_listenerGameObj ///< Listener game object IDs that will be added to the default set of listeners. + ) override; + + /// Remove a single listener from the default set of listeners. Upon registration, all game objects reference the default listener set, until + /// a call to AddListener, RemoveListener, SetListeners or SetGameObjectOutputBusVolume is made on that game object. + /// \sa + /// - AK::SoundEngine::SetDefaultListeners + /// - AK::SoundEngine::AddDefaultListener + /// - \ref soundengine_listeners + AKRESULT RemoveDefaultListener( + AkGameObjectID in_listenerGameObj ///< Listener game object IDs that will be removed from the default set of listeners. + ) override; + + /// Resets the listener associations to the default listener(s), as set by SetDefaultListeners. This will also reset per-listener gains that have been set using SetGameObjectOutputBusVolume. + /// \return Always returns AK_Success + /// \sa + /// - AK::SoundEngine::SetListeners + /// - AK::SoundEngine::SetDefaultListeners + /// - AK::SoundEngine::SetGameObjectOutputBusVolume + /// - \ref soundengine_listeners + AKRESULT ResetListenersToDefault( + AkGameObjectID in_emitterGameObj ///< Emitter game object. + ) override; + + /// Sets a listener's spatialization parameters. This lets you define listener-specific + /// volume offsets for each audio channel. + /// If \c in_bSpatialized is false, only \c in_pVolumeOffsets is used for this listener (3D positions + /// have no effect on the speaker distribution). Otherwise, \c in_pVolumeOffsets is added to the speaker + /// distribution computed for this listener. + /// Use helper functions of \c AK::SpeakerVolumes to manipulate the vector of volume offsets in_pVolumeOffsets. + /// + /// \remarks + /// - If a sound is mixed into a bus that has a different speaker configuration than in_channelConfig, + /// standard up/downmix rules apply. + /// - Sounds with 3D Spatialization set to None will not be affected by these parameters. + /// \return + /// - \c AK_Success if message was successfully posted to sound engine queue + /// - \c AK_InvalidFloatValue if the value specified was NaN or Inf + /// - \c AK_InsufficientMemory if there wasn't enough memory in the message queue + /// \sa + /// - \ref soundengine_listeners_spatial + AKRESULT SetListenerSpatialization( + AkGameObjectID in_uListenerID, ///< Listener game object ID + bool in_bSpatialized, ///< Spatialization toggle (True : enable spatialization, False : disable spatialization) + AkChannelConfig in_channelConfig, ///< Channel configuration associated with volumes in_pVolumeOffsets. Ignored if in_pVolumeOffsets is NULL. + AK::SpeakerVolumes::VectorPtr in_pVolumeOffsets = NULL ///< Per-speaker volume offset, in dB. See AkSpeakerVolumes.h for how to manipulate this vector. + ) override; + + //@} + + + //////////////////////////////////////////////////////////////////////// + /// @name Game Syncs + //@{ + + /// Sets the value of a real-time parameter control (by ID). + /// With this function, you may set a game parameter value with global scope or with game object scope. + /// Game object scope supersedes global scope. (Once a value is set for the game object scope, it will not be affected by changes to the global scope value.) Game parameter values set with a global scope are applied to all + /// game objects that not yet registered, or already registered but not overridden with a value with game object scope. + /// To set a game parameter value with global scope, pass \c AK_INVALID_GAME_OBJECT as the game object. + /// With this function, you may also change the value of a game parameter over time. To do so, specify a non-zero + /// value for \c in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call \c SetRTPCValue() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. Thus, if you call this + /// function at every game frame, you should not use \c in_uValueChangeDuration, as it would have no effect and it is less efficient. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// \return + /// - \c AK_Success if the value was successfully set + /// - \c AK_InvalidFloatValue if the value specified was NaN, Inf or FLT_MAX (3.402823e+38) + /// - \c AK_InvalidID if in_rtpcID is AK_INVALID_UNIQUE_ID (0) + /// \sa + /// - \ref soundengine_rtpc + /// - AK::SoundEngine::GetIDFromString() + AKRESULT SetRTPCValue( + AkRtpcID in_rtpcID, ///< ID of the game parameter + AkRtpcValue in_value, ///< Value to set + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT,///< Associated game object ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards in_value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when for example loading a level and you dont want the values to interpolate. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Sets the value of a real-time parameter control (by Unicode string name). + /// With this function, you may set a game parameter value to global scope or to game object scope. + /// Game object scope supersedes global scope. (Once a value is set for the game object scope, it will not be affected by changes to the global scope value.) Game parameter values set with global scope are applied to all + /// game objects that not yet registered, or already registered but not overridden with a value with game object scope. + /// To set a game parameter value with global scope, pass AK_INVALID_GAME_OBJECT as the game object. + /// With this function, you may also change the value of a game parameter over time. To do so, specify a non-zero + /// value for in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValue() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. Thus, if you call this + /// function at every game frame, you should not use in_uValueChangeDuration, as it would have no effect and it is less efficient. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// \return + /// - \c AK_Success if the value was successfully set + /// - \c AK_InvalidFloatValue if the value specified was NaN, Inf or FLT_MAX (3.402823e+38) + /// - \c AK_InvalidID if in_pszRtpcName is NULL. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_rtpc + AKRESULT SetRTPCValue( + const wchar_t* in_pszRtpcName, ///< Name of the game parameter + AkRtpcValue in_value, ///< Value to set + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT,///< Associated game object ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards in_value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when for example loading a level and you dont want the values to interpolate. + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Sets the value of a real-time parameter control. + /// With this function, you may set a game parameter value with global scope or with game object scope. + /// Game object scope supersedes global scope. (Once a value is set for the game object scope, it will not be affected by changes to the global scope value.) Game parameter values set with global scope are applied to all + /// game objects that not yet registered, or already registered but not overridden with a value with game object scope. + /// To set a game parameter value with global scope, pass AK_INVALID_GAME_OBJECT as the game object. + /// With this function, you may also change the value of a game parameter over time. To do so, specify a non-zero + /// value for \c in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValue() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. Thus, if you call this + /// function at every game frame, you should not use in_uValueChangeDuration, as it would have no effect and it is less efficient. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// \return + /// - \c AK_Success if the value was successfully set + /// - \c AK_InvalidFloatValue if the value specified was NaN, Inf or FLT_MAX (3.402823e+38) + /// - \c AK_InvalidID if in_pszRtpcName is NULL. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_rtpc + AKRESULT SetRTPCValue( + const char* in_pszRtpcName, ///< Name of the game parameter + AkRtpcValue in_value, ///< Value to set + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT,///< Associated game object ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards in_value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when for example loading a level and you dont want the values to interpolate. + ) override; + + /// Sets the value of a real-time parameter control (by ID). + /// With this function, you may set a game parameter value on playing id scope. + /// Playing id scope supersedes both game object scope and global scope. + /// With this function, you may also change the value of a game parameter over time. To do so, specify a non-zero + /// value for in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValueByPlayingID() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. Thus, if you call this + /// function at every game frame, you should not use in_uValueChangeDuration, as it would have no effect and it is less efficient. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// - \c AK_Success if successful + /// - \c AK_PlayingIDNotFound if in_playingID is not found. + /// - \c AK_InvalidID if in_pszRtpcName is NULL. + /// - \c AK_InvalidFloatValue if the value specified was NaN, Inf or FLT_MAX (3.402823e+38) + /// \sa + /// - \ref soundengine_rtpc + /// - AK::SoundEngine::GetIDFromString() + AKRESULT SetRTPCValueByPlayingID( + AkRtpcID in_rtpcID, ///< ID of the game parameter + AkRtpcValue in_value, ///< Value to set + AkPlayingID in_playingID, ///< Associated playing ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards in_value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when, for example, loading a level and you don't want the values to interpolate. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Sets the value of a real-time parameter control (by Unicode string name). + /// With this function, you may set a game parameter value on playing ID scope. + /// Playing id scope supersedes both game object scope and global scope. + /// With this function, you may also change the value of a game parameter over time. To do so, specify a non-zero + /// value for in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValueByPlayingID() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. Thus, if you call this + /// function at every game frame, you should not use in_uValueChangeDuration, as it would have no effect and it is less efficient. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// - \c AK_Success if successful + /// - \c AK_PlayingIDNotFound if in_playingID is not found. + /// - \c AK_InvalidID if in_pszRtpcName is NULL. + /// - \c AK_InvalidFloatValue if the value specified was NaN, Inf or FLT_MAX (3.402823e+38) + /// \sa + /// - \ref soundengine_rtpc + /// - AK::SoundEngine::GetIDFromString() + AKRESULT SetRTPCValueByPlayingID( + const wchar_t* in_pszRtpcName, ///< Name of the game parameter + AkRtpcValue in_value, ///< Value to set + AkPlayingID in_playingID, ///< Associated playing ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards in_value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when, for example, loading a level and you don't want the values to interpolate. + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Sets the value of a real-time parameter control (by string name). + /// With this function, you may set a game parameter value on playing id scope. + /// Playing id scope supersedes both game object scope and global scope. + /// With this function, you may also change the value of a game parameter over time. To do so, specify a non-zero + /// value for in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValueByPlayingID() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. Thus, if you call this + /// function at every game frame, you should not use in_uValueChangeDuration, as it would have no effect and it is less efficient. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// - \c AK_Success if successful + /// - \c AK_PlayingIDNotFound if in_playingID is not found. + /// - \c AK_InvalidID if in_pszRtpcName is NULL. + /// - \c AK_InvalidFloatValue if the value specified was NaN, Inf or FLT_MAX (3.402823e+38) + /// \sa + /// - \ref soundengine_rtpc + /// - AK::SoundEngine::GetIDFromString() + AKRESULT SetRTPCValueByPlayingID( + const char* in_pszRtpcName, ///< Name of the game parameter + AkRtpcValue in_value, ///< Value to set + AkPlayingID in_playingID, ///< Associated playing ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards in_value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when for example loading a level and you dont want the values to interpolate. + ) override; + + /// Resets the value of the game parameter to its default value, as specified in the Wwise project. + /// With this function, you may reset a game parameter to its default value with global scope or with game object scope. + /// Game object scope supersedes global scope. Game parameter values reset with global scope are applied to all + /// game objects that were not overridden with a value with game object scope. + /// To reset a game parameter value with global scope, pass AK_INVALID_GAME_OBJECT as the game object. + /// With this function, you may also reset the value of a game parameter over time. To do so, specify a non-zero + /// value for in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValue() or ResetRTPCValue() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// \return + /// - \c AK_Success when successful + /// - \c AK_InvalidID if in_rtpcID is AK_INVALID_UNIQUE_ID (0) + /// \sa + /// - \ref soundengine_rtpc + /// - AK::SoundEngine::GetIDFromString() + /// - AK::SoundEngine::SetRTPCValue() + AKRESULT ResetRTPCValue( + AkRtpcID in_rtpcID, ///< ID of the game parameter + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT,///< Associated game object ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards its default value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when for example loading a level and you dont want the values to interpolate. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Resets the value of the game parameter to its default value, as specified in the Wwise project. + /// With this function, you may reset a game parameter to its default value with global scope or with game object scope. + /// Game object scope supersedes global scope. Game parameter values reset with global scope are applied to all + /// game objects that were not overridden with a value with game object scope. + /// To reset a game parameter value with global scope, pass AK_INVALID_GAME_OBJECT as the game object. + /// With this function, you may also reset the value of a game parameter over time. To do so, specify a non-zero + /// value for in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValue() or ResetRTPCValue() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if in_pszParamName is NULL. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_rtpc + /// - AK::SoundEngine::SetRTPCValue() + AKRESULT ResetRTPCValue( + const wchar_t* in_pszRtpcName, ///< Name of the game parameter + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT,///< Associated game object ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards its default value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when for example loading a level and you dont want the values to interpolate. + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Resets the value of the game parameter to its default value, as specified in the Wwise project. + /// With this function, you may reset a game parameter to its default value with global scope or with game object scope. + /// Game object scope supersedes global scope. Game parameter values reset with global scope are applied to all + /// game objects that were not overridden with a value with game object scope. + /// To reset a game parameter value with global scope, pass AK_INVALID_GAME_OBJECT as the game object. + /// With this function, you may also reset the value of a game parameter over time. To do so, specify a non-zero + /// value for in_uValueChangeDuration. At each audio frame, the game parameter value will be updated internally + /// according to the interpolation curve. If you call SetRTPCValue() or ResetRTPCValue() with in_uValueChangeDuration = 0 in the + /// middle of an interpolation, the interpolation stops and the new value is set directly. + /// Refer to \ref soundengine_rtpc_pergameobject, \ref soundengine_rtpc_buses and + /// \ref soundengine_rtpc_effects for more details on RTPC scope. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if in_pszParamName is NULL. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_rtpc + /// - AK::SoundEngine::SetRTPCValue() + AKRESULT ResetRTPCValue( + const char* in_pszRtpcName, ///< Name of the game parameter + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT,///< Associated game object ID + AkTimeMs in_uValueChangeDuration = 0, ///< Duration during which the game parameter is interpolated towards its default value + AkCurveInterpolation in_eFadeCurve = AkCurveInterpolation_Linear, ///< Curve type to be used for the game parameter interpolation + bool in_bBypassInternalValueInterpolation = false ///< True if you want to bypass the internal "slew rate" or "over time filtering" specified by the sound designer. This is meant to be used when for example loading a level and you dont want the values to interpolate. + ) override; + + /// Sets the State of a Switch Group (by IDs). + /// \return Always returns AK_Success + /// \sa + /// - \ref soundengine_switch + /// - AK::SoundEngine::GetIDFromString() + AKRESULT SetSwitch( + AkSwitchGroupID in_switchGroup, ///< ID of the Switch Group + AkSwitchStateID in_switchState, ///< ID of the Switch + AkGameObjectID in_gameObjectID ///< Associated game object ID + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Sets the State of a Switch Group (by Unicode string names). + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if the switch or Switch Group name was not resolved to an existing ID\n + /// Make sure that the banks were generated with the "include string" option. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_switch + AKRESULT SetSwitch( + const wchar_t* in_pszSwitchGroup, ///< Name of the Switch Group + const wchar_t* in_pszSwitchState, ///< Name of the Switch + AkGameObjectID in_gameObjectID ///< Associated game object ID + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Sets the state of a Switch Group. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if the switch or Switch Group name was not resolved to an existing ID\n + /// Make sure that the banks were generated with the "include string" option. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_switch + AKRESULT SetSwitch( + const char* in_pszSwitchGroup, ///< Name of the Switch Group + const char* in_pszSwitchState, ///< Name of the Switch + AkGameObjectID in_gameObjectID ///< Associated game object ID + ) override; + + /// Post the specified trigger (by IDs). + /// \return Always returns AK_Success + /// \sa + /// - \ref soundengine_triggers + /// - AK::SoundEngine::GetIDFromString() + AKRESULT PostTrigger( + AkTriggerID in_triggerID, ///< ID of the trigger + AkGameObjectID in_gameObjectID ///< Associated game object ID + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Posts the specified trigger (by Unicode string name). + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if the trigger name was null + /// Make sure that the banks were generated with the "include string" option. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_triggers + AKRESULT PostTrigger( + const wchar_t* in_pszTrigger, ///< Name of the trigger + AkGameObjectID in_gameObjectID ///< Associated game object ID + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Posts the specified trigger. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if the trigger name was null + /// Make sure that the banks were generated with the "include string" option. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_triggers + AKRESULT PostTrigger( + const char* in_pszTrigger, ///< Name of the trigger + AkGameObjectID in_gameObjectID ///< Associated game object ID + ) override; + + /// Sets the state of a State Group (by IDs). + /// \return Always returns AK_Success + /// \sa + /// - \ref soundengine_states + /// - AK::SoundEngine::GetIDFromString() + AKRESULT SetState( + AkStateGroupID in_stateGroup, ///< ID of the State Group + AkStateID in_state ///< ID of the state + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Sets the state of a State Group (by Unicode string names). + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if the state or State Group name was null + /// Make sure that the banks were generated with the "include string" option. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_states + /// - AK::SoundEngine::GetIDFromString() + AKRESULT SetState( + const wchar_t* in_pszStateGroup, ///< Name of the State Group + const wchar_t* in_pszState ///< Name of the state + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Sets the state of a State Group. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidID if the state or State Group name was null + /// Make sure that the banks were generated with the "include string" option. + /// \aknote Strings are case-insensitive. \endaknote + /// \sa + /// - \ref soundengine_states + /// - AK::SoundEngine::GetIDFromString() + AKRESULT SetState( + const char* in_pszStateGroup, ///< Name of the State Group + const char* in_pszState ///< Name of the state + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Environments + //@{ + + /// Sets the Auxiliary Busses to route the specified game object + /// To clear the game object's auxiliary sends, \c in_uNumSendValues must be 0. + /// \sa + /// - \ref soundengine_environments + /// - \ref soundengine_environments_dynamic_aux_bus_routing + /// - \ref soundengine_environments_id_vs_string + /// - AK::SoundEngine::GetIDFromString() + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidParameter if a duplicated environment is found in the array + /// - \c AK_InvalidFloatValue if the value specified was NaN or Inf + AKRESULT SetGameObjectAuxSendValues( + AkGameObjectID in_gameObjectID, ///< Associated game object ID + AkAuxSendValue* in_aAuxSendValues, ///< Variable-size array of AkAuxSendValue structures + ///< (it may be NULL if no environment must be set) + AkUInt32 in_uNumSendValues ///< The number of auxiliary busses at the pointer's address + ///< (it must be 0 if no environment is set) + ) override; + + /// Registers a callback to allow the game to modify or override the volume to be applied at the output of an audio bus. + /// The callback must be registered once per bus ID. + /// Call with in_pfnCallback = NULL to unregister. + /// \aknote The bus in_busID needs to be a mixing bus.\endaknote + /// \aknote The callback function will not be called for the Master Audio Bus, since the output of this bus is not a bus, but is instead an Audio Device.\endaknote + /// \sa + /// - \ref goingfurther_speakermatrixcallback + /// - \ref soundengine_environments + /// - AkSpeakerVolumeMatrixCallbackInfo + /// - AK::IAkMixerInputContext + /// - AK::IAkMixerPluginContext + /// \return + /// - \c AK_Success if successful + /// - \c AK_IDNotFound if the bus is not found + /// - \c AK_NotInitialized if the sound engine is not initialized + /// - \c AK_InsufficientMemory if there is not enough memory to complete the operation + AKRESULT RegisterBusVolumeCallback( + AkUniqueID in_busID, ///< Bus ID, as obtained by GetIDFromString( bus_name ). + AkBusCallbackFunc in_pfnCallback, ///< Callback function. + void* in_pCookie = NULL ///< User cookie. + ) override; + + /// Registers a callback to be called to allow the game to access metering data from any mixing bus. You may use this to monitor loudness at any point of the mixing hierarchy + /// by accessing the peak, RMS, True Peak and K-weighted power (according to loudness standard ITU BS.1770). See \ref goingfurther_speakermatrixcallback for an example. + /// The callback must be registered once per bus ID. + /// Call with in_pfnCallback = NULL to unregister. + /// \aknote The bus in_busID needs to be a mixing bus.\endaknote + /// \sa + /// - \ref goingfurther_speakermatrixcallback + /// - AkBusMeteringCallbackFunc + /// - AK::AkMetering + /// \return + /// - \c AK_Success if successful + /// - \c AK_IDNotFound if the bus is not found + /// - \c AK_NotInitialized if the sound engine is not initialized + /// - \c AK_InsufficientMemory if there is not enough memory to complete the operation + AKRESULT RegisterBusMeteringCallback( + AkUniqueID in_busID, ///< Bus ID, as obtained by GetIDFromString( bus_name ). + AkBusMeteringCallbackFunc in_pfnCallback, ///< Callback function. + AkMeteringFlags in_eMeteringFlags, ///< Metering flags. + void* in_pCookie = NULL ///< User cookie. + ) override; + + /// Registers a callback to be called to allow the game to access metering data from any output device. You may use this to monitor loudness as sound leaves the Wwise sound engine + /// by accessing the peak, RMS, True Peak and K-weighted power (according to loudness standard ITU BS.1770). See \ref goingfurther_speakermatrixcallback for an example. + /// The callback must be registered once per device ShareSet ID. + /// Call with in_pfnCallback = NULL to unregister. + /// \sa + /// - \ref goingfurther_speakermatrixcallback + /// - AkOutputDeviceMeteringCallbackFunc + /// - AK::AkMetering + /// \return + /// - \c AK_Success if successful + /// - \c AK_DeviceNotFound if the device is not found + /// - \c AK_NotInitialized if the sound engine is not initialized + /// - \c AK_InsufficientMemory if there is not enough memory to complete the operation + AKRESULT RegisterOutputDeviceMeteringCallback( + AkOutputDeviceID in_idOutput, ///< Output ID, as returned from AddOutput or GetOutputID. You can pass 0 for the main (default) output + AkOutputDeviceMeteringCallbackFunc in_pfnCallback, ///< Callback function. + AkMeteringFlags in_eMeteringFlags, ///< Metering flags. + void* in_pCookie = NULL ///< User cookie. + ) override; + + /// Sets the Output Bus Volume (direct) to be used for the specified game object. + /// The control value is a number ranging from 0.0f to 1.0f. + /// Output Bus Volumes are stored per listener association, so calling this function will override the default set of listeners. The game object in_emitterObjID will now reference its own set of listeners which will + /// be the same as the old set of listeners, but with the new associated gain. Future changes to the default listener set will not be picked up by this game object unless ResetListenersToDefault() is called. + /// \sa + /// - \ref AK::SoundEngine::ResetListenersToDefault + /// - \ref soundengine_environments + /// - \ref soundengine_environments_setting_dry_environment + /// - \ref soundengine_environments_id_vs_string + /// \return + /// - \c AK_Success when successful + /// - \c AK_InvalidFloatValue if the value specified was NaN or Inf + AKRESULT SetGameObjectOutputBusVolume( + AkGameObjectID in_emitterObjID, ///< Associated emitter game object ID + AkGameObjectID in_listenerObjID, ///< Associated listener game object ID. Pass AK_INVALID_GAME_OBJECT to set the Output Bus Volume for all connected listeners. + AkReal32 in_fControlValue ///< A multiplier in the range [0.0f:16.0f] ( -inf dB to +24 dB). + ///< A value greater than 1.0f will amplify the sound. + ) override; + + /// Sets an Effect ShareSet at the specified audio node and Effect slot index. + /// \aknote + /// Replacing effects is preferably done through a Set Effect Event Action. + /// \endaknote + /// The target node cannot be a Bus, to set effects on a bus, use SetBusEffect() instead. + /// \aknote The option "Override Parent" in + /// the Effect section in Wwise must be enabled for this node, otherwise the parent's effect will + /// still be the one in use and the call to SetActorMixerEffect will have no impact. + /// \endaknote + /// \return Always returns AK_Success + AKRESULT SetActorMixerEffect( + AkUniqueID in_audioNodeID, ///< Can be a member of the Actor-Mixer or Interactive Music Hierarchy (not a bus). + AkUInt32 in_uFXIndex, ///< Effect slot index (0-3) + AkUniqueID in_ShareSetID ///< ShareSets ID; pass AK_INVALID_UNIQUE_ID to clear the effect slot + ) override; + + /// Sets an Effect ShareSet at the specified bus and Effect slot index. + /// \aknote + /// Replacing effects is preferably done through a Set Effect Event Action. + /// \endaknote + /// The Bus can either be an Audio Bus or an Auxiliary Bus. + /// This adds a reference on the audio node to an existing ShareSet. + /// \aknote This function has unspecified behavior when adding an Effect to a currently playing + /// Bus which does not have any Effects, or removing the last Effect on a currently playing bus. + /// \endaknote + /// \aknote This function will replace existing Effects on the node. If the target node is not at + /// the top of the hierarchy and is in the actor-mixer hierarchy, the option "Override Parent" in + /// the Effect section in Wwise must be enabled for this node, otherwise the parent's Effect will + /// still be the one in use and the call to SetBusEffect will have no impact. + /// \endaknote + /// \return + /// - \c AK_Success when successfully posted. + /// - \c AK_IDNotFound if the Bus isn't found by in_audioNodeID + /// - \c AK_InvalidParameter if in_uFXIndex isn't in range + AKRESULT SetBusEffect( + AkUniqueID in_audioNodeID, ///< Bus Short ID. + AkUInt32 in_uFXIndex, ///< Effect slot index (0-3) + AkUniqueID in_ShareSetID ///< ShareSets ID; pass AK_INVALID_UNIQUE_ID to clear the Effect slot + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Sets an Effect ShareSet at the specified Bus and Effect slot index. + /// \aknote + /// Replacing effects is preferably done through a Set Effect Event Action. + /// \endaknote + /// The Bus can either be an Audio Bus or an Auxiliary Bus. + /// This adds a reference on the audio node to an existing ShareSet. + /// \aknote This function has unspecified behavior when adding an Effect to a currently playing + /// bus which does not have any Effects, or removing the last Effect on a currently playing Bus. + /// \endaknote + /// \aknote This function will replace existing Effects on the node. If the target node is not at + /// the top of the hierarchy and is in the Actor-Mixer Hierarchy, the option "Override Parent" in + /// the Effect section in Wwise must be enabled for this node, otherwise the parent's Effect will + /// still be the one in use and the call to \c SetBusEffect will have no impact. + /// \endaknote + /// \returns + /// - \c AK_Success when successfully posted. + /// - \c AK_IDNotFound if the Bus name doesn't point to a valid bus. + /// - \c AK_InvalidID if in_pszBusName is null + /// - \c AK_InvalidParameter if in_uFXIndex isn't in range or in_pszBusName is null + AKRESULT SetBusEffect( + const wchar_t* in_pszBusName, ///< Bus name + AkUInt32 in_uFXIndex, ///< Effect slot index (0-3) + AkUniqueID in_ShareSetID ///< ShareSets ID; pass AK_INVALID_UNIQUE_ID to clear the effect slot + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Sets an Effect ShareSet at the specified Bus and Effect slot index. + /// \aknote + /// Replacing effects is preferably done through a Set Effect Event Action. + /// \endaknote + /// The Bus can either be an Audio Bus or an Auxiliary Bus. + /// This adds a reference on the audio node to an existing ShareSet. + /// \aknote This function has unspecified behavior when adding an Effect to a currently playing + /// Bus which does not have any effects, or removing the last Effect on a currently playing bus. + /// \endaknote + /// \aknote Make sure the new effect ShareSet is included in a soundbank, and that sound bank is loaded. Otherwise you will see errors in the Capture Log.\endaknote + /// \aknote This function will replace existing Effects on the node. If the target node is not at + /// the top of the hierarchy and is in the Actor-Mixer Hierarchy, the option "Override Parent" in + /// the Effect section in Wwise must be enabled for this node, otherwise the parent's Effect will + /// still be the one in use and the call to SetBusEffect will have no impact. + /// \endaknote + /// \returns + /// - \c AK_Success when successfully posted. + /// - \c AK_IDNotFound if the Bus name doesn't point to a valid bus. + /// - \c AK_InvalidParameter if in_uFXIndex isn't in range + /// - \c AK_InvalidID if in_pszBusName is null + AKRESULT SetBusEffect( + const char* in_pszBusName, ///< Bus name + AkUInt32 in_uFXIndex, ///< Effect slot index (0-3) + AkUniqueID in_ShareSetID ///< ShareSets ID; pass AK_INVALID_UNIQUE_ID to clear the effect slot + ) override; + + /// Sets an audio device effect ShareSet on the specified output device and effect slot index. + /// \aknote + /// Replacing effects is preferably done through a Set Effect Event Action. + /// \endaknote + /// \aknote Make sure the new effect ShareSet is included in a soundbank, and that sound bank is loaded. Otherwise you will see errors in the Capture Log.\endaknote + /// \aknote This function will replace existing effects of the audio device ShareSet. \endaknote + /// \aknote Audio device effects support is limited to one ShareSet per plug-in type at any time. \endaknote + /// \aknote Errors are reported in the Wwise Capture Log if the effect cannot be set on the output device. \endaknote + + /// \returns Always returns AK_Success + AKRESULT SetOutputDeviceEffect( + AkOutputDeviceID in_outputDeviceID, ///< Output ID, as returned from AddOutput or GetOutputID. Most of the time this should be 0 to designate the main (default) output + AkUInt32 in_uFXIndex, ///< Effect slot index (0-3) + AkUniqueID in_FXShareSetID ///< Effect ShareSet ID + ) override; + + /// Sets a Mixer ShareSet at the specified bus. + /// \aknote This function has unspecified behavior when adding a mixer to a currently playing + /// Bus which does not have any Effects or mixer, or removing the last mixer on a currently playing Bus. + /// \endaknote + /// \aknote Make sure the new mixer ShareSet is included in a soundbank, and that sound bank is loaded. Otherwise you will see errors in the Capture Log.\endaknote + /// \aknote This function will replace existing mixers on the node. + /// \endaknote + /// \return Always returns AK_Success + AKRESULT SetMixer( + AkUniqueID in_audioNodeID, ///< Bus Short ID. + AkUniqueID in_ShareSetID ///< ShareSets ID; pass AK_INVALID_UNIQUE_ID to remove. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Sets a Mixer ShareSet at the specified bus. + /// \aknote This function has unspecified behavior when adding a mixer to a currently playing + /// bus which does not have any effects nor mixer, or removing the last mixer on a currently playing bus. + /// \endaknote + /// \aknote Make sure the new mixer ShareSet is included in a soundbank, and that sound bank is loaded. Otherwise you will see errors in the Capture Log.\endaknote + /// \aknote This function will replace existing mixers on the node. + /// \endaknote + /// \returns + /// - \c AK_Success when successful + /// - \c AK_InvalidID if in_pszBusName is null + AKRESULT SetMixer( + const wchar_t* in_pszBusName, ///< Bus name + AkUniqueID in_ShareSetID ///< ShareSets ID; pass AK_INVALID_UNIQUE_ID to remove. + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Sets a Mixer ShareSet at the specified bus. + /// \aknote This function has unspecified behavior when adding a mixer to a currently playing + /// bus which does not have any effects nor mixer, or removing the last mixer on a currently playing bus. + /// \endaknote + /// \aknote Make sure the new mixer ShareSet is included in a soundbank, and that sound bank is loaded. Otherwise you will see errors in the Capture Log.\endaknote + /// \aknote This function will replace existing mixers on the node. + /// \endaknote + /// \returns + /// - \c AK_Success when successful + /// - \c AK_InvalidID if in_pszBusName is null + AKRESULT SetMixer( + const char* in_pszBusName, ///< Bus name + AkUniqueID in_ShareSetID ///< ShareSets ID; pass AK_INVALID_UNIQUE_ID to remove. + ) override; + + /// Forces channel configuration for the specified bus. + /// \aknote You cannot change the configuration of the master bus.\endaknote + /// + /// \return Always returns AK_Success + AKRESULT SetBusConfig( + AkUniqueID in_audioNodeID, ///< Bus Short ID. + AkChannelConfig in_channelConfig ///< Desired channel configuration. An invalid configuration (from default constructor) means "as parent". + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Forces channel configuration for the specified bus. + /// \aknote You cannot change the configuration of the master bus.\endaknote + /// + /// \returns + /// - \c AK_Success when successful + /// - \c AK_InvalidID if in_pszBusName is null + AKRESULT SetBusConfig( + const wchar_t* in_pszBusName, ///< Bus name + AkChannelConfig in_channelConfig ///< Desired channel configuration. An invalid configuration (from default constructor) means "as parent". + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Forces channel configuration for the specified bus. + /// \aknote You cannot change the configuration of the master bus.\endaknote + /// + /// \returns + /// - \c AK_Success when successful + /// - \c AK_InvalidID if in_pszBusName is null + AKRESULT SetBusConfig( + const char* in_pszBusName, ///< Bus name + AkChannelConfig in_channelConfig ///< Desired channel configuration. An invalid configuration (from default constructor) means "as parent". + ) override; + + /// Sets a game object's obstruction and occlusion levels. If SetMultiplePositions were used, values are set for all positions. + /// This function is used to affect how an object should be heard by a specific listener. + /// \sa + /// - \ref soundengine_obsocc + /// - \ref soundengine_environments + /// \return Always returns AK_Success + AKRESULT SetObjectObstructionAndOcclusion( + AkGameObjectID in_EmitterID, ///< Emitter game object ID + AkGameObjectID in_ListenerID, ///< Listener game object ID + AkReal32 in_fObstructionLevel, ///< ObstructionLevel: [0.0f..1.0f] + AkReal32 in_fOcclusionLevel ///< OcclusionLevel: [0.0f..1.0f] + ) override; + + /// Sets a game object's obstruction and occlusion level for each positions defined by SetMultiplePositions. + /// This function differs from SetObjectObstructionAndOcclusion as a list of obstruction/occlusion pair is provided + /// and each obstruction/occlusion pair will affect the corresponding position defined at the same index. + /// \aknote In the case the number of obstruction/occlusion pairs is smaller than the number of positions, remaining positions' + /// obstrucion/occlusion values are set to 0.0. \endaknote + /// \return + /// - \c AK_Success if successful + /// - \c AK_CommandTooLarge if the number of obstruction values is too large for the command queue. + /// - \c AK_InvalidParameter if one of the parameter is out of range (check the debug console) + /// - \c AK_InvalidFloatValue if one of the occlusion/obstruction values is NaN or Inf. + /// \sa + /// - \ref soundengine_obsocc + /// - \ref soundengine_environments + /// \return AK_Success if occlusion/obstruction values are successfully stored for this emitter + AKRESULT SetMultipleObstructionAndOcclusion( + AkGameObjectID in_EmitterID, ///< Emitter game object ID + AkGameObjectID in_uListenerID, ///< Listener game object ID + AkObstructionOcclusionValues* in_fObstructionAndOcclusionValues, ///< Array of obstruction/occlusion pairs to apply + ///< ObstructionLevel: [0.0f..1.0f] + ///< OcclusionLevel: [0.0f..1.0f] + AkUInt32 in_uNumObstructionAndOcclusion ///< Number of obstruction/occlusion pairs specified in the provided array + ) override; + + /// Saves the playback history of container structures. + /// This function will write history data for all currently loaded containers and instantiated game + /// objects (for example, current position in Sequence Containers and previously played elements in + /// Random Containers). + /// \remarks + /// This function acquires the main audio lock, and may block the caller for several milliseconds. + /// \return + /// - \c AK_Success when successful + /// - \c AK_Fail is in_pBytes could not be parsed (corruption or data is truncated) + /// \sa + /// - AK::SoundEngine::SetContainerHistory() + AKRESULT GetContainerHistory( + AK::IWriteBytes* in_pBytes ///< Pointer to IWriteBytes interface used to save the history. + ) override; + + /// Restores the playback history of container structures. + /// This function will read history data from the passed-in stream reader interface, and apply it to all + /// currently loaded containers and instantiated game objects. Game objects are matched by + /// ID. History for unloaded structures and unknown game objects will be skipped. + /// \remarks + /// This function acquires the main audio lock, and may block the caller for several milliseconds. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InsufficientMemory if not enough memory is available for IReadBytes operation + /// \sa + /// - AK::SoundEngine::GetContainerHistory() + AKRESULT SetContainerHistory( + AK::IReadBytes* in_pBytes ///< Pointer to IReadBytes interface used to load the history. + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Capture + //@{ + + /// Starts recording the sound engine audio output. + /// StartOutputCapture outputs a wav file per current output device of the sound engine. + /// If more than one device is active, the system will create multiple files in the same output + /// directory and will append numbers at the end of the provided filename. + /// + /// If no device is running yet, the system will return success AK_Success despite doing nothing. + /// Use RegisterAudioDeviceStatusCallback to get notified when devices are created/destructed. + /// + /// \return AK_Success if successful, AK_Fail if there was a problem starting the output capture. + /// \remark + /// - The sound engine opens a stream for writing using AK::IAkStreamMgr::CreateStd(). If you are using the + /// default implementation of the Stream Manager, file opening is executed in your implementation of + /// the Low-Level IO interface AK::StreamMgr::IAkFileLocationResolver::Open(). The following + /// AkFileSystemFlags are passed: uCompanyID = AKCOMPANYID_AUDIOKINETIC and uCodecID = AKCODECID_PCM, + /// and the AkOpenMode is AK_OpenModeWriteOvrwr. Refer to \ref streamingmanager_lowlevel_location for + /// more details on managing the deployment of your Wwise generated data. + /// \return + /// - \c AK_Success when successful + /// - \c AK_InvalidParameter if in_CaptureFileName is null. + /// - \c AK_InsufficientMemory if not enough memory is available. + /// \sa + /// - AK::SoundEngine::StopOutputCapture() + /// - AK::StreamMgr::SetFileLocationResolver() + /// - \ref streamingdevicemanager + /// - \ref streamingmanager_lowlevel_location + /// - RegisterAudioDeviceStatusCallback + AKRESULT StartOutputCapture( + const AkOSChar* in_CaptureFileName ///< Name of the output capture file + ) override; + + /// Stops recording the sound engine audio output. + /// \return AK_Success if successful, AK_Fail if there was a problem stopping the output capture. + /// \sa + /// - AK::SoundEngine::StartOutputCapture() + AKRESULT StopOutputCapture() override; + + /// Adds text marker in audio output file. + /// \return + /// - \c AK_Success when successful + /// - \c AK_InvalidParameter if in_MarkerText is null. + /// - \c AK_InsufficientMemory if not enough memory is available. + /// \sa + /// - AK::SoundEngine::StartOutputCapture() + AKRESULT AddOutputCaptureMarker( + const char* in_MarkerText ///< Text of the marker + ) override; + + /// Gets the system sample rate. + /// \return The sample rate. + AkUInt32 GetSampleRate() override; + + /// Registers a callback used for retrieving audio samples. + /// The callback will be called from the audio thread during real-time rendering and from the main thread during offline rendering. + /// \return + /// - \c AK_Success when successful + /// - \c AK_DeviceNotFound if the audio device ID doesn't match to a device in use. + /// - \c AK_InvalidParameter when in_pfnCallback is null + /// - \c AK_NotInitialized if the sound engine is not initialized at this time + /// \sa + /// - AK::SoundEngine::AddOutput() + /// - AK::SoundEngine::GetOutputID() + /// - AK::SoundEngine::UnregisterCaptureCallback() + AKRESULT RegisterCaptureCallback( + AkCaptureCallbackFunc in_pfnCallback, ///< Capture callback function to register. + AkOutputDeviceID in_idOutput = AK_INVALID_OUTPUT_DEVICE_ID, ///< The audio device specific id, return by AK::SoundEngine::AddOutput or AK::SoundEngine::GetOutputID + void* in_pCookie = NULL ///< Callback cookie that will be sent to the callback function along with additional information + ) override; + + /// Unregisters a callback used for retrieving audio samples. + /// \return + /// - \c AK_Success when successful + /// - \c AK_DeviceNotFound if the audio device ID doesn't match to a device in use. + /// - \c AK_InvalidParameter when in_pfnCallback is null + /// - \c AK_NotInitialized if the sound engine is not initialized at this time + /// \sa + /// - AK::SoundEngine::AddOutput() + /// - AK::SoundEngine::GetOutputID() + /// - AK::SoundEngine::RegisterCaptureCallback() + AKRESULT UnregisterCaptureCallback( + AkCaptureCallbackFunc in_pfnCallback, ///< Capture callback function to unregister. + AkOutputDeviceID in_idOutput = AK_INVALID_OUTPUT_DEVICE_ID, ///< The audio device specific id, return by AK::SoundEngine::AddOutput or AK::SoundEngine::GetOutputID + void* in_pCookie = NULL ///< Callback cookie that will be sent to the callback function along with additional information + ) override; + + /// Starts recording the sound engine profiling information into a file. This file can be read + /// by Wwise Authoring. The file is created at the base path. If you have integrated Wwise I/O, + /// you can use CAkDefaultIOHookBlocking::SetBasePath() (or CAkDefaultIOHookBlocking::AddBasePath()) + /// to change the location where the file is saved. The profiling session records all data types possible. + /// Note that this call captures peak metering for all the busses loaded and mixing + /// while this call is invoked. + /// \remark This function is provided as a utility tool only. It does nothing if it is + /// called in the release configuration and returns AK_NotCompatible. + AKRESULT StartProfilerCapture( + const AkOSChar* in_CaptureFileName ///< Name of the output profiler file (.prof extension recommended) + ) override; + + /// Stops recording the sound engine profiling information. + /// \remark This function is provided as a utility tool only. It does nothing if it is + /// called in the release configuration and returns AK_NotCompatible. + AKRESULT StopProfilerCapture() override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Offline Rendering + //@{ + + /// Sets the offline rendering frame time in seconds. + /// When offline rendering is enabled, every call to \ref RenderAudio() will generate sample data as if this much time has elapsed. If the frame time argument is less than or equal to zero, every call to RenderAudio() will generate one audio buffer. + /// \return Always returns AK_Success + AKRESULT SetOfflineRenderingFrameTime( + AkReal32 in_fFrameTimeInSeconds ///< frame time in seconds used during offline rendering + ) override; + + /// Enables/disables offline rendering. + /// \return Always returns AK_Success + AKRESULT SetOfflineRendering( + bool in_bEnableOfflineRendering ///< enables/disables offline rendering + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Secondary Outputs + //@{ + + /// Adds an output to the sound engine. Use this to add controller-attached headphones, controller speakers, DVR output, etc. + /// The in_Settings parameter contains an Audio Device ShareSet to specify the output plugin to use and a device ID to specify the instance, if necessary (e.g. which game controller). + /// + /// Like most functions of AK::SoundEngine, AddOutput is asynchronous. A successful return code merely indicates that the request is properly queued. + /// Error codes returned by this function indicate various invalid parameters. To know if this function succeeds or not, and the failure code, + /// register an AkDeviceStatusCallbackFunc callback with RegisterAudioDeviceStatusCallback. + /// + /// \sa AkOutputSettings for more details. + /// \sa \ref integrating_secondary_outputs + /// \sa \ref default_audio_devices + /// \sa AK::SoundEngine::RegisterAudioDeviceStatusCallback + /// \sa AK::AkDeviceStatusCallbackFunc + /// \return + /// The following codes are returned directly from the function, as opposed to the AkDeviceStatusCallback + /// - \c AK_NotImplemented: Feature not supported, some platforms don't have other outputs. + /// - \c AK_InvalidParameter: Out of range parameters or unsupported parameter combinations (see parameter list below). + /// - \c AK_IDNotFound: The audioDeviceShareSet on in_settings doesn't exist. Possibly, the Init bank isn't loaded yet or was not updated with latest changes. + /// - \c AK_DeviceNotReady: The idDevice on in_settings doesn't match with a valid hardware device. Either the device doesn't exist or is disabled. Disconnected devices (headphones) are not considered "not ready" therefore won't cause this error. + /// - \c AK_NotInitialized: If AK::SoundEngine::Init was not called or if the Init.bnk was not loaded before the call. + /// - \c AK_Success: Parameters are valid. + /// + /// The following codes are returned from the callback. + /// - \c AK_InsufficientMemory : Not enough memory to complete the operation. + /// - \c AK_IDNotFound: The audioDeviceShareSet on in_settings doesn't exist. Possibly, the Init bank isn't loaded yet or was not updated with latest changes. + /// - \c AK_PluginNotRegistered: The audioDeviceShareSet exists but the plug-in it refers to is not installed or statically linked with the game. + /// - \c AK_NotCompatible: The hardware does not support this type of output. Wwise will try to use the System output instead, and a separate callback will fire when that completes. + /// - \c AK_DeviceNotCompatible: The hardware does not support this type of output. Wwise will NOT fallback to any other type of output. + /// - \c AK_Fail: Generic code for any non-permanent conditions (e.g. disconnection) that prevent the use of the output. Wwise has created the output and sounds will be routed to it, but this output is currently silent until the temporary condition resolves. + /// - \c AK_NoDistinctListener: Outputs of the same type (same ShareSet, like controller speakers) must have distinct Listeners to make a proper routing. This doesn't happen if there is only one output of that type. + AKRESULT AddOutput( + const AkOutputSettings& in_Settings, ///< Creation parameters for this output. \ref AkOutputSettings + AkOutputDeviceID* out_pDeviceID = NULL, ///< (Optional) Output ID to use with all other Output management functions. Leave to NULL if not required. \ref AK::SoundEngine::GetOutputID + const AkGameObjectID* in_pListenerIDs = NULL, ///< Specific listener(s) to attach to this device. + ///< If specified, only the sounds routed to game objects linked to those listeners will play in this device. + ///< It is necessary to have separate listeners if multiple devices of the same type can coexist (e.g. controller speakers) + ///< If not specified, sound routing simply obey the associations between Master Busses and Audio Devices setup in the Wwise Project. + AkUInt32 in_uNumListeners = 0 ///< The number of elements in the in_pListenerIDs array. + ) override; + + /// Removes one output added through AK::SoundEngine::AddOutput + /// If a listener was associated with the device, you should consider unregistering the listener prior to call RemoveOutput + /// so that Game Object/Listener routing is properly updated according to your game scenario. + /// \sa \ref integrating_secondary_outputs + /// \sa AK::SoundEngine::AddOutput + /// \return AK_Success: Parameters are valid. + AKRESULT RemoveOutput( + AkOutputDeviceID in_idOutput ///< ID of the output to remove. Use the returned ID from AddOutput, GetOutputID, or ReplaceOutput + ) override; + + /// Replaces an output device previously created during engine initialization or from AddOutput, with a new output device. + /// In addition to simply removing one output device and adding a new one, the new output device will also be used on all of the master buses + /// that the old output device was associated with, and preserve all listeners that were attached to the old output device. + /// + /// Like most functions of AK::SoundEngine, AddOutput is asynchronous. A successful return code merely indicates that the request is properly queued. + /// Error codes returned by this function indicate various invalid parameters. To know if this function succeeds or not, and the failure code, + /// register an AkDeviceStatusCallbackFunc callback with RegisterAudioDeviceStatusCallback. + /// + /// \sa AK::SoundEngine::AddOutput + /// \sa AK::SoundEngine::RegisterAudioDeviceStatusCallback + /// \sa AK::AkDeviceStatusCallbackFunc + /// \return + /// - \c AK_InvalidID: The audioDeviceShareSet on in_settings was not valid. + /// - \c AK_IDNotFound: The audioDeviceShareSet on in_settings doesn't exist. Possibly, the Init bank isn't loaded yet or was not updated with latest changes. + /// - \c AK_DeviceNotReady: The idDevice on in_settings doesn't match with a valid hardware device. Either the device doesn't exist or is disabled. Disconnected devices (headphones) are not considered "not ready" therefore won't cause this error. + /// - \c AK_DeviceNotFound: The in_outputDeviceId provided does not match with any of the output devices that the sound engine is currently using. + /// - \c AK_InvalidParameter: Out of range parameters or unsupported parameter combinations on in_settings + /// - \c AK_Success: parameters were valid, and the remove and add will occur. + AKRESULT ReplaceOutput( + const AkOutputSettings& in_Settings, ///< Creation parameters for this output. \ref AkOutputSettings + AkOutputDeviceID in_outputDeviceId, ///< AkOutputDeviceID of the output to replace. Use 0 to target the current main output, regardless of its id. Otherwise, use the AkOuptutDeviceID returned from AddOutput() or ReplaceOutput(), or generated by GetOutputID() + AkOutputDeviceID* out_pOutputDeviceId = NULL ///< (Optional) Pointer into which the method writes the AkOutputDeviceID of the new output device. If the call fails, the value pointed to will not be modified. + ) override; + + /// Gets the compounded output ID from ShareSet and device id. + /// Outputs are defined by their type (Audio Device ShareSet) and their specific system ID. A system ID could be reused for other device types on some OS or platforms, hence the compounded ID. + /// Use 0 for in_idShareSet & in_idDevice to get the Main Output ID (the one usually initialized during AK::SoundEngine::Init) + /// \return The id of the output + AkOutputDeviceID GetOutputID( + AkUniqueID in_idShareSet, ///< Audio Device ShareSet ID, as defined in the Wwise Project. If needed, use AK::SoundEngine::GetIDFromString() to convert from a string. Set to AK_INVALID_UNIQUE_ID to use the default. + AkUInt32 in_idDevice ///< Device specific identifier, when multiple devices of the same type are possible. If only one device is possible, leave to 0. + ///< - PS4 Controller-Speakers: UserID as returned from sceUserServiceGetLoginUserIdList + ///< - XBoxOne Controller-Headphones: Use the AK::GetDeviceID function to get the ID from an IMMDevice. Find the player's device with the WASAPI API (IMMDeviceEnumerator, see Microsoft documentation) or use AK::GetDeviceIDFromName. + ///< - Windows: Use AK::GetDeviceID or AK::GetDeviceIDFromName to get the correct ID. + ///< - All others output: use 0 to select the default device for that type. + ) override; + + AkOutputDeviceID GetOutputID( + const char* in_szShareSet, ///< Audio Device ShareSet Name, as defined in the Wwise Project. If Null, will select the Default Output ShareSet (always available) + AkUInt32 in_idDevice ///< Device specific identifier, when multiple devices of the same type are possible. If only one device is possible, leave to 0. + ///< - PS4 Controller-Speakers: UserID as returned from sceUserServiceGetLoginUserIdList + ///< - XBoxOne Controller-Headphones: Use the AK::GetDeviceID function to get the ID from an IMMDevice. Find the player's device with the WASAPI API (IMMDeviceEnumerator, see Microsoft documentation) or use AK::GetDeviceIDFromName. + ///< - Windows: Use AK::GetDeviceID or AK::GetDeviceIDFromName to get the correct ID. + ///< - All others output: use 0 to select the default device for that type. + ) override; + +#ifdef AK_SUPPORT_WCHAR + AkOutputDeviceID GetOutputID( + const wchar_t* in_szShareSet, ///< Audio Device ShareSet Name, as defined in the Wwise Project. If Null, will select the Default Output ShareSet (always available) + AkUInt32 in_idDevice ///< Device specific identifier, when multiple devices of the same type are possible. If only one device is possible, leave to 0. + ///< - PS4 Controller-Speakers: UserID as returned from sceUserServiceGetLoginUserIdList + ///< - XBoxOne Controller-Headphones: Use the AK::GetDeviceID function to get the ID from an IMMDevice. Find the player's device with the WASAPI API (IMMDeviceEnumerator, see Microsoft documentation) or use AK::GetDeviceIDFromName. + ///< - Windows: Use AK::GetDeviceID or AK::GetDeviceIDFromName to get the correct ID. + ///< - All others output: use 0 to select the default device for that type. + ) override; +#endif + + /// Sets the Audio Device to which a master bus outputs. This overrides the setting in the Wwise project. + /// Can only be set on top-level busses. The Init bank should be successfully loaded prior to this call. + /// \aknote This function is useful only if used before the creation of an output, at the beginning of the sound engine setup. + /// Once active outputs using this Bus have been created, it is imperative to use AK::SoundEngine:ReplaceOutput instead to change the type of output. + /// \return + /// AK_IDNotFound when either the Bus ID or the Device ID are not present in the Init bank or the bank was not loaded + /// AK_InvalidParameter when the specified bus is not a Master Bus. This function can be called only on busses that have no parent bus. + AKRESULT SetBusDevice( + AkUniqueID in_idBus, ///< Id of the master bus + AkUniqueID in_idNewDevice ///< New device ShareSet to replace with. + ) override; + + /// Sets the Audio Device to which a master bus outputs. This overrides the setting in the Wwise project. + /// Can only be set on top-level busses. The Init bank should be successfully loaded prior to this call. + /// \aknote This function is useful only if used before the creation of an output, at the beginning of the sound engine setup. + /// Once active outputs using this Bus have been created, it is imperative to use AK::SoundEngine:ReplaceOutput instead to change the type of output. + /// \return + /// AK_IDNotFound when either the Bus ID or the Device ID are not present in the Init bank or the bank was not loaded + /// AK_InvalidParameter when the specified bus is not a Master Bus. This function can be called only on busses that have no parent bus. + AKRESULT SetBusDevice( + const char* in_BusName, ///< Name of the master bus + const char* in_DeviceName ///< New device ShareSet to replace with. + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Sets the Audio Device to which a master bus outputs. This overrides the setting in the Wwise project. + /// Can only be set on top-level busses. The Init bank should be successfully loaded prior to this call. + /// SetBusDevice must be preceded by a call to AddOutput for the new device ShareSet to be registered as an output. + /// \return + /// AK_IDNotFound when either the Bus ID or the Device ID are not present in the Init bank or the bank was not loaded + /// AK_InvalidParameter when the specified bus is not a Master Bus. This function can be called only on busses that have no parent bus. + AKRESULT SetBusDevice( + const wchar_t* in_BusName, ///< Name of the master bus + const wchar_t* in_DeviceName ///< New device ShareSet to replace with. + ) override; +#endif + + /// Returns a listing of the current devices for a given sink plug-in, including Device ID, friendly name, and state. + /// This call is only valid for sink plug-ins that support device enumeration. + /// Prerequisites: the plug-in must have been initialized by loading the init bank or by calling \ref AK::SoundEngine::RegisterPlugin. + /// \return + /// - \c AK_NotImplemented if the sink plug-in does not implement device enumeration + /// - \c AK_PluginNotRegistered if the plug-in has not been registered yet either by loading the init bank or by calling RegisterPluginDLL. + /// - \c AK_NotCompatible if no device of this type are supported on the current platform + /// - \c AK_Fail in case of system device manager failure (OS related) + /// + AKRESULT GetDeviceList( + AkUInt32 in_ulCompanyID, ///< Company identifier (as declared in the plug-in description XML file) + AkUInt32 in_ulPluginID, ///< Plug-in identifier (as declared in the plug-in description XML file) + AkUInt32& io_maxNumDevices, ///< In: The length of the out_deviceDescriptions array. Out: If out_deviceDescriptions is not-null, this will be set to the number of entries in out_deviceDescriptions that was populated. If out_deviceDescriptions is null, this will be set to the number of devices that may be available. + AkDeviceDescription* out_deviceDescriptions ///< The output array of device descriptions. + ) override; + + /// Returns a listing of the current devices for a given sink plug-in, including Device ID, friendly name, and state. + /// This call is only valid for sink plug-ins that support device enumeration. + /// Prerequisites: + /// * The plug-in must have been initialized by loading the init bank or by calling \ref AK::SoundEngine::RegisterPlugin. + /// * The audio device ShareSet must have been loaded from a soundbank and a device with this ShareSet must exist in the pipeline. + /// \return + /// AK_NotImplemented if the sink plug-in does not implement device enumeration + /// AK_PluginNotRegistered if the plug-in has not been registered yet either by loading the init bank or by calling RegisterPluginDLL. + AKRESULT GetDeviceList( + AkUniqueID in_audioDeviceShareSetID, ///< In: The audio device ShareSet ID for which to list the sink plug-in devices. + AkUInt32& io_maxNumDevices, ///< In: The length of the out_deviceDescriptions array. Out: If out_deviceDescriptions is not-null, this will be set to the number of entries in out_deviceDescriptions that was populated. If out_deviceDescriptions is null, this will be set to the number of devices that may be available. + AkDeviceDescription* out_deviceDescriptions ///< The output array of device descriptions. + ) override; + + /// Sets the volume of a output device. + /// \return + /// - \c AK_Success if successful + /// - \c AK_InvalidFloatValue if the value specified was NaN or Inf + AKRESULT SetOutputVolume( + AkOutputDeviceID in_idOutput, ///< Output ID to set the volume on. As returned from AddOutput or GetOutputID + AkReal32 in_fVolume ///< Volume (0.0 = Muted, 1.0 = Volume max) + ) override; + + /// Returns whether or not the audio device matching the device ID provided supports spatial audio (i.e. the functionality is enabled, and more than 0 dynamic objects are supported). + /// If Spatial Audio is supported, then you can call Init, AddOutput, or ReplaceOutput with an Audio Device ShareSet corresponding to the respective platform-specific plug-in that + /// provides spatial audio, such as the Microsoft Spatial Sound Platform for Windows. Note that on Xbox One, you need to call EnableSpatialAudio() before the sound engine is + /// initialized, or initialize the sound engine with AkPlatformInitSettings::bEnableSpatialAudio set to true if you want spatial audio support; otherwise this will always return AK_NotCompatible. + /// \return + /// AK_NotCompatible when the device ID provided does not support spatial audio, or the platform does not support spatial audio + /// AK_Fail when there is some other miscellaneous failure, or the device ID provided does not match a device that the system knows about + /// AK_Success when the device ID provided does support spatial audio + AKRESULT GetDeviceSpatialAudioSupport( + AkUInt32 in_idDevice ///< Device specific identifier, when multiple devices of the same type are possible. If only one device is possible, leave to 0. + ///< - PS4 Controller-Speakers: UserID as returned from sceUserServiceGetLoginUserIdList + ///< - XBoxOne Controller-Headphones: Use the AK::GetDeviceID function to get the ID from an IMMDevice. Find the player's device with the WASAPI API (IMMDeviceEnumerator, see Microsoft documentation) or use AK::GetDeviceIDFromName. + ///< - Windows: Use AK::GetDeviceID or AK::GetDeviceIDFromName to get the correct ID. + ///< - All others output: use 0 to select the default device for that type. + ) override; + + + //@} + + /// This function should be called to put the sound engine in background mode, where audio isn't processed anymore. This needs to be called if the console has a background mode or some suspended state. + /// Call \c WakeupFromSuspend when your application receives the message from the OS that the process is back in foreground. + /// When suspended, the sound engine will process API messages (like PostEvent and SetSwitch) only when \ref RenderAudio() is called. + /// It is recommended to match the in_bRenderAnyway parameter with the behavior of the rest of your game: + /// if your game still runs in background and you must keep some kind of coherent state between the audio engine and game, then allow rendering. + /// If you want to minimize CPU when in background, then don't allow rendering and never call RenderAudio from the game. + /// + /// Consult \ref workingwithsdks_system_calls to learn when it is appropriate to call this function for each platform. + /// \sa WakeupFromSuspend + /// \sa \ref workingwithsdks_system_calls + AKRESULT Suspend( + bool in_bRenderAnyway = false, ///< If set to true, audio processing will still occur, but not outputted. When set to false, no audio will be processed at all, even upon reception of RenderAudio(). + bool in_bFadeOut = true ///< Delay the suspend by one audio frame in order to fade-out. When false, the suspend takes effect immediately but audio may glitch. + ) override; + + /// This function should be called to wake up the sound engine and start processing audio again. This needs to be called if the console has a background mode or some suspended state. + /// + /// Consult \ref workingwithsdks_system_calls to learn when it is appropriate to call this function for each platform. + /// \sa Suspend + /// \sa \ref workingwithsdks_system_calls + AKRESULT WakeupFromSuspend( + AkUInt32 in_uDelayMs = 0 /// Delay (in milliseconds) before the wake up occurs. Rounded up to audio frame granularity. Adding a delay is useful if there is a possibility that another OS event may override the wake-up in the near future. + ) override; + + /// Obtains the current audio output buffer tick. This corresponds to the number of buffers produced by + /// the sound engine since initialization. + /// \return Tick count. + AkUInt32 GetBufferTick() override; + + /// Obtains the current audio output sample tick. This corresponds to the number of sapmles produced by + /// the sound engine since initialization. + /// \return Sample count. + AkUInt64 GetSampleTick() override; + + class WWISESOUNDENGINE_API FQuery : public IQuery + { + public: + UE_NONCOPYABLE(FQuery); + FQuery() = default; + + //////////////////////////////////////////////////////////////////////// + /// @name Game Objects + //@{ + + /// Get the position of a game object. + /// \return AK_Success if succeeded, or AK_IDNotFound if the game object was not registered + /// \sa + /// - \ref soundengine_3dpositions + AKRESULT GetPosition( + AkGameObjectID in_GameObjectID, ///< Game object identifier + AkSoundPosition& out_rPosition ///< Position to get + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Listeners + //@{ + + /// Get a game object's listeners. + /// \return AK_Success if succeeded, or AK_IDNotFound if the game object was not registered + /// \sa + /// - \ref soundengine_listeners + AKRESULT GetListeners( + AkGameObjectID in_GameObjectID, ///< Source game object identifier + AkGameObjectID* out_ListenerObjectIDs, ///< Pointer to an array of AkGameObjectID's. Will be populated with the IDs of the listeners of in_GameObjectID. Pass NULL to querry the size required. + AkUInt32& oi_uNumListeners ///< Pass in the the available number of elements in the array 'out_ListenerObjectIDs'. After return, the number of valid elements filled in the array. + ) override; + + /// Get a listener's position. + /// \return AK_Success if succeeded, or AK_InvalidParameter if the index is out of range + /// \sa + /// - \ref soundengine_listeners_settingpos + AKRESULT GetListenerPosition( + AkGameObjectID in_uIndex, ///< Listener index (0: first listener, 7: 8th listener) + AkListenerPosition& out_rPosition ///< Position set + ) override; + + /// Get a listener's spatialization parameters. + /// \return AK_Success if succeeded, or AK_InvalidParameter if the index is out of range + /// \sa + /// - AK::SoundEngine::SetListenerSpatialization(). + /// - \ref soundengine_listeners_spatial + AKRESULT GetListenerSpatialization( + AkUInt32 in_uIndex, ///< Listener index (0: first listener, 7: 8th listener) + bool& out_rbSpatialized, ///< Spatialization enabled + AK::SpeakerVolumes::VectorPtr& out_pVolumeOffsets, ///< Per-speaker vector of volume offsets, in decibels. Use the functions of AK::SpeakerVolumes::Vector to interpret it. + AkChannelConfig& out_channelConfig ///< Channel configuration associated with out_rpVolumeOffsets. + ) override; + + //@} + + + //////////////////////////////////////////////////////////////////////// + /// @name Game Syncs + //@{ + + /// Get the value of a real-time parameter control (by ID) + /// An RTPC can have a any combination of a global value, a unique value for each game object, or a unique value for each playing ID. + /// The value requested is determined by RTPCValue_type, in_gameObjectID and in_playingID. + /// If a value at the requested scope (determined by RTPCValue_type) is not found, the value that is available at the the next broadest scope will be returned, and io_rValueType will be changed to indicate this. + /// \note + /// When looking up RTPC values via playing ID (ie. io_rValueType is RTPC_PlayingID), in_gameObjectID can be set to a specific game object (if it is available to the caller) to use as a fall back value. + /// If the game object is unknown or unavailable, AK_INVALID_GAME_OBJECT can be passed in in_gameObjectID, and the game object will be looked up via in_playingID. + /// However in this case, it is not possible to retrieve a game object value as a fall back value if the playing id does not exist. It is best to pass in the game object if possible. + /// + /// \return AK_Success if succeeded, AK_IDNotFound if the RTPC does not exist + /// \sa + /// - \ref soundengine_rtpc + /// - RTPCValue_type + AKRESULT GetRTPCValue( + AkRtpcID in_rtpcID, ///< ID of the RTPC + AkGameObjectID in_gameObjectID, ///< Associated game object ID, ignored if io_rValueType is RTPCValue_Global. + AkPlayingID in_playingID, ///< Associated playing ID, ignored if io_rValueType is not RTPC_PlayingID. + AkRtpcValue& out_rValue, ///< Value returned + AK::SoundEngine::Query::RTPCValue_type& io_rValueType ///< In/Out value, the user must specify the requested type. The function will return in this variable the type of the returned value. + ) override; + +#ifdef AK_SUPPORT_WCHAR + + /// Get the value of a real-time parameter control (by ID) + /// An RTPC can have a any combination of a global value, a unique value for each game object, or a unique value for each playing ID. + /// The value requested is determined by RTPCValue_type, in_gameObjectID and in_playingID. + /// If a value at the requested scope (determined by RTPCValue_type) is not found, the value that is available at the the next broadest scope will be returned, and io_rValueType will be changed to indicate this. + /// \note + /// When looking up RTPC values via playing ID (ie. io_rValueType is RTPC_PlayingID), in_gameObjectID can be set to a specific game object (if it is available to the caller) to use as a fall back value. + /// If the game object is unknown or unavailable, AK_INVALID_GAME_OBJECT can be passed in in_gameObjectID, and the game object will be looked up via in_playingID. + /// However in this case, it is not possible to retrieve a game object value as a fall back value if the playing id does not exist. It is best to pass in the game object if possible. + /// + /// \return AK_Success if succeeded, AK_IDNotFound if the RTPC does not exist + /// \sa + /// - \ref soundengine_rtpc + /// - RTPCValue_type + AKRESULT GetRTPCValue( + const wchar_t* in_pszRtpcName, ///< String name of the RTPC + AkGameObjectID in_gameObjectID, ///< Associated game object ID, ignored if io_rValueType is RTPCValue_Global. + AkPlayingID in_playingID, ///< Associated playing ID, ignored if io_rValueType is not RTPC_PlayingID. + AkRtpcValue& out_rValue, ///< Value returned + AK::SoundEngine::Query::RTPCValue_type& io_rValueType ///< In/Out value, the user must specify the requested type. The function will return in this variable the type of the returned value. ) override; + ) override; + +#endif //AK_SUPPORT_WCHAR + + /// Get the value of a real-time parameter control (by ID) + /// An RTPC can have a any combination of a global value, a unique value for each game object, or a unique value for each playing ID. + /// The value requested is determined by RTPCValue_type, in_gameObjectID and in_playingID. + /// If a value at the requested scope (determined by RTPCValue_type) is not found, the value that is available at the the next broadest scope will be returned, and io_rValueType will be changed to indicate this. + /// \note + /// When looking up RTPC values via playing ID (ie. io_rValueType is RTPC_PlayingID), in_gameObjectID can be set to a specific game object (if it is available to the caller) to use as a fall back value. + /// If the game object is unknown or unavailable, AK_INVALID_GAME_OBJECT can be passed in in_gameObjectID, and the game object will be looked up via in_playingID. + /// However in this case, it is not possible to retrieve a game object value as a fall back value if the playing id does not exist. It is best to pass in the game object if possible. + /// + /// \return AK_Success if succeeded, AK_IDNotFound if the RTPC does not exist + /// \sa + /// - \ref soundengine_rtpc + /// - RTPCValue_type + AKRESULT GetRTPCValue( + const char* in_pszRtpcName, ///< String name of the RTPC + AkGameObjectID in_gameObjectID, ///< Associated game object ID, ignored if io_rValueType is RTPCValue_Global. + AkPlayingID in_playingID, ///< Associated playing ID, ignored if io_rValueType is not RTPC_PlayingID. + AkRtpcValue& out_rValue, ///< Value returned + AK::SoundEngine::Query::RTPCValue_type& io_rValueType ///< In/Out value, the user must specify the requested type. The function will return in this variable the type of the returned value. ) override; + ) override; + + /// Get the state of a switch group (by IDs). + /// \return AK_Success if succeeded, or AK_IDNotFound if the game object was not registered + /// \sa + /// - \ref soundengine_switch + AKRESULT GetSwitch( + AkSwitchGroupID in_switchGroup, ///< ID of the switch group + AkGameObjectID in_gameObjectID, ///< Associated game object ID + AkSwitchStateID& out_rSwitchState ///< ID of the switch + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Get the state of a switch group. + /// \return AK_Success if succeeded, or AK_IDNotFound if the game object was not registered or the switch group name can not be found + /// \sa + /// - \ref soundengine_switch + AKRESULT GetSwitch( + const wchar_t* in_pstrSwitchGroupName, ///< String name of the switch group + AkGameObjectID in_GameObj, ///< Associated game object ID + AkSwitchStateID& out_rSwitchState ///< ID of the switch + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Get the state of a switch group. + /// \return AK_Success if succeeded, or AK_IDNotFound if the game object was not registered or the switch group name can not be found + /// \sa + /// - \ref soundengine_switch + AKRESULT GetSwitch( + const char* in_pstrSwitchGroupName, ///< String name of the switch group + AkGameObjectID in_GameObj, ///< Associated game object ID + AkSwitchStateID& out_rSwitchState ///< ID of the switch + ) override; + + /// Get the state of a state group (by IDs). + /// \return AK_Success if succeeded + /// \sa + /// - \ref soundengine_states + AKRESULT GetState( + AkStateGroupID in_stateGroup, ///< ID of the state group + AkStateID& out_rState ///< ID of the state + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Get the state of a state group. + /// \return AK_Success if succeeded, or AK_IDNotFound if the state group name can not be found + /// \sa + /// - \ref soundengine_states + AKRESULT GetState( + const wchar_t* in_pstrStateGroupName, ///< String name of the state group + AkStateID& out_rState ///< ID of the state + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Get the state of a state group. + /// \return AK_Success if succeeded, or AK_IDNotFound if the state group name can not be found + /// \sa + /// - \ref soundengine_states + AKRESULT GetState( + const char* in_pstrStateGroupName, ///< String name of the state group + AkStateID& out_rState ///< ID of the state + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Environments + //@{ + + /// Get the environmental ratios used by the specified game object. + /// To clear the game object's environments, in_uNumEnvValues must be 0. + /// \sa + /// - \ref soundengine_environments + /// - \ref soundengine_environments_dynamic_aux_bus_routing + /// - \ref soundengine_environments_id_vs_string + /// \return AK_Success if succeeded, or AK_InvalidParameter if io_ruNumEnvValues is 0 or out_paEnvironmentValues is NULL, or AK_PartialSuccess if more environments exist than io_ruNumEnvValues + /// AK_InvalidParameter + AKRESULT GetGameObjectAuxSendValues( + AkGameObjectID in_gameObjectID, ///< Associated game object ID + AkAuxSendValue* out_paAuxSendValues, ///< Variable-size array of AkAuxSendValue structures + ///< (it may be NULL if no aux send must be set) + AkUInt32& io_ruNumSendValues ///< The number of Auxilliary busses at the pointer's address + ///< (it must be 0 if no aux bus is set) + ) override; + + /// Get the environmental dry level to be used for the specified game object + /// The control value is a number ranging from 0.0f to 1.0f. + /// 0.0f stands for 0% dry, while 1.0f stands for 100% dry. + /// \aknote Reducing the dry level does not mean increasing the wet level. \endaknote + /// \sa + /// - \ref soundengine_environments + /// - \ref soundengine_environments_setting_dry_environment + /// - \ref soundengine_environments_id_vs_string + /// \return AK_Success if succeeded, or AK_IDNotFound if the game object was not registered + AKRESULT GetGameObjectDryLevelValue( + AkGameObjectID in_EmitterID, ///< Associated emitter game object ID + AkGameObjectID in_ListenerID, ///< Associated listener game object ID + AkReal32& out_rfControlValue ///< Dry level control value, ranging from 0.0f to 1.0f + ///< (0.0f stands for 0% dry, while 1.0f stands for 100% dry) + ) override; + + /// Get a game object's obstruction and occlusion levels. + /// \sa + /// - \ref soundengine_obsocc + /// - \ref soundengine_environments + /// \return AK_Success if succeeded, AK_IDNotFound if the game object was not registered + AKRESULT GetObjectObstructionAndOcclusion( + AkGameObjectID in_EmitterID, ///< Associated game object ID + AkGameObjectID in_ListenerID, ///< Listener object ID + AkReal32& out_rfObstructionLevel, ///< ObstructionLevel: [0.0f..1.0f] + AkReal32& out_rfOcclusionLevel ///< OcclusionLevel: [0.0f..1.0f] + ) override; + + //@} + + /// Get the list of audio object IDs associated to an event. + /// \aknote It is possible to call QueryAudioObjectIDs with io_ruNumItems = 0 to get the total size of the + /// structure that should be allocated for out_aObjectInfos. \endaknote + /// \return AK_Success if succeeded, AK_IDNotFound if the eventID cannot be found, AK_InvalidParameter if out_aObjectInfos is NULL while io_ruNumItems > 0 + /// or AK_PartialSuccess if io_ruNumItems was set to 0 to query the number of available items. + AKRESULT QueryAudioObjectIDs( + AkUniqueID in_eventID, ///< Event ID + AkUInt32& io_ruNumItems, ///< Number of items in array provided / Number of items filled in array + AkObjectInfo* out_aObjectInfos ///< Array of AkObjectInfo items to fill + ) override; + +#ifdef AK_SUPPORT_WCHAR + /// Get the list of audio object IDs associated to a event name. + /// \aknote It is possible to call QueryAudioObjectIDs with io_ruNumItems = 0 to get the total size of the + /// structure that should be allocated for out_aObjectInfos. \endaknote + /// \return AK_Success if succeeded, AK_IDNotFound if the event name cannot be found, AK_InvalidParameter if out_aObjectInfos is NULL while io_ruNumItems > 0 + /// or AK_PartialSuccess if io_ruNumItems was set to 0 to query the number of available items. + AKRESULT QueryAudioObjectIDs( + const wchar_t* in_pszEventName, ///< Event name + AkUInt32& io_ruNumItems, ///< Number of items in array provided / Number of items filled in array + AkObjectInfo* out_aObjectInfos ///< Array of AkObjectInfo items to fill + ) override; +#endif //AK_SUPPORT_WCHAR + + /// Get the list of audio object IDs associated to an event name. + /// \aknote It is possible to call QueryAudioObjectIDs with io_ruNumItems = 0 to get the total size of the + /// structure that should be allocated for out_aObjectInfos. \endaknote + /// \return AK_Success if succeeded, AK_IDNotFound if the event name cannot be found, AK_InvalidParameter if out_aObjectInfos is NULL while io_ruNumItems > 0 + /// or AK_PartialSuccess if io_ruNumItems was set to 0 to query the number of available items. + AKRESULT QueryAudioObjectIDs( + const char* in_pszEventName, ///< Event name + AkUInt32& io_ruNumItems, ///< Number of items in array provided / Number of items filled in array + AkObjectInfo* out_aObjectInfos ///< Array of AkObjectInfo items to fill + ) override; + + /// Get positioning information associated to an audio object. + /// \return AK_Success if succeeded, AK_IDNotFound if the object ID cannot be found, AK_NotCompatible if the audio object cannot expose positioning + AKRESULT GetPositioningInfo( + AkUniqueID in_ObjectID, ///< Audio object ID + AkPositioningInfo& out_rPositioningInfo ///< Positioning information structure to be filled + ) override; + + /// Fill the provided list with all the game object IDs that are currently active in the sound engine. + /// The function may be used to avoid updating game objects positions that are not required at the moment. + /// After calling this function, the list will contain the list of all game objects that are currently active in the sound engine. + /// Being active means that either a sound is playing or pending to be played using this game object. + /// \sa + /// - AkGameObjectsList + AKRESULT GetActiveGameObjects( + FAkGameObjectsList& io_GameObjectList ///< returned list of active game objects. + ) override; + + /// Query if the specified game object is currently active. + /// Being active means that either a sound is playing or pending to be played using this game object. + bool GetIsGameObjectActive( + AkGameObjectID in_GameObjId ///< Game object ID + ) override; + + /// Returns the maximum distance used in attenuations associated to all sounds currently playing. + /// This may be used for example by the game to know if some processing need to be performed on the game side, that would not be required + /// if the object is out of reach anyway. + /// + /// Example usage: + /// \code + /// /*******************************************************/ + /// AkRadiusList RadLst; //creating the list( array ). + /// // Do not reserve any size for the array, + /// // the system will reserve the correct size. + /// + /// GetMaxRadius( RadLst ) override; + /// // Use the content of the list + /// (...) + /// + /// RadLst.Term() override;// the user is responsible to free the memory allocated + /// /*******************************************************/ + /// \endcode + /// + /// \aknote The returned value is NOT the distance from a listener to an object but + /// the maximum attenuation distance of all sounds playing on this object. This is + /// not related in any way to the curent 3D position of the object. \endaknote + /// + /// \return + /// - AK_Success if succeeded + /// - AK_InsuficientMemory if there was not enough memory + /// + /// \aknote + /// The Scaling factor (if one was specified on the game object) is included in the return value. + /// The Scaling factor is not updated once a sound starts playing since it + /// is computed only when the playback starts with the initial scaling factor of this game object. Scaling factor will + /// be re-computed for every playback instance, always using the scaling factor available at this time. + /// \endaknote + /// + /// \sa + /// - AkRadiusList + AKRESULT GetMaxRadius( + FAkRadiusList& io_RadiusList ///< List that will be filled with AK::SoundEngine::Query::GameObjDst objects. + ) override; + + /// Returns the maximum distance used in attenuations associated to sounds playing using the specified game object. + /// This may be used for example by the game to know if some processing need to be performed on the game side, that would not be required + /// if the object is out of reach anyway. + /// + /// \aknote The returned value is NOT the distance from a listener to an object but the maximum attenuation distance of all sounds playing on this object. \endaknote + /// + /// \return + /// - A negative number if the game object specified is not playing. + /// - 0, if the game object was only associated to sounds playing using no distance attenuation. + /// - A positive number represents the maximum of all the distance attenuations playing on this game object. + /// + /// \aknote + /// The Scaling factor (if one was specified on the game object) is included in the return value. + /// The Scaling factor is not updated once a sound starts playing since it + /// is computed only when the playback starts with the initial scaling factor of this game object. Scaling factor will + /// be re-computed for every playback instance, always using the scaling factor available at this time. + /// \endaknote + /// + /// \sa + /// - \ref AK::SoundEngine::SetScalingFactor + AkReal32 GetMaxRadius( + AkGameObjectID in_GameObjId ///< Game object ID + ) override; + + /// Get the Event ID associated to the specified PlayingID. + /// This function does not acquire the main audio lock. + /// + /// \return AK_INVALID_UNIQUE_ID on failure. + AkUniqueID GetEventIDFromPlayingID( + AkPlayingID in_playingID ///< Associated PlayingID + ) override; + + /// Get the ObjectID associated to the specified PlayingID. + /// This function does not acquire the main audio lock. + /// + /// \return AK_INVALID_GAME_OBJECT on failure. + AkGameObjectID GetGameObjectFromPlayingID( + AkPlayingID in_playingID ///< Associated PlayingID + ) override; + + /// Get the list PlayingIDs associated with the given game object. + /// This function does not acquire the main audio lock. + /// + /// \aknote It is possible to call GetPlayingIDsFromGameObject with io_ruNumItems = 0 to get the total size of the + /// structure that should be allocated for out_aPlayingIDs. \endaknote + /// \return AK_Success if succeeded, AK_InvalidParameter if out_aPlayingIDs is NULL while io_ruNumItems > 0 + AKRESULT GetPlayingIDsFromGameObject( + AkGameObjectID in_GameObjId, ///< Game object ID + AkUInt32& io_ruNumIDs, ///< Number of items in array provided / Number of items filled in array + AkPlayingID* out_aPlayingIDs ///< Array of AkPlayingID items to fill + ) override; + + /// Get the value of a custom property of integer or boolean type. + /// \return AK_PartialSuccess if the object was found but no matching custom property was found on this object. Note that it could mean this value is the default value. + AKRESULT GetCustomPropertyValue( + AkUniqueID in_ObjectID, ///< Object ID, this is the 32bit ShortID of the AudioFileSource or Sound object found in the .wwu XML file. At runtime it can only be retrieved by the AK_Duration callback when registered with PostEvent(), or by calling Query::QueryAudioObjectIDs() to get all the shortIDs associated with an event. + AkUInt32 in_uPropID, ///< Property ID of your custom property found under the Custom Properties tab of the Wwise project settings. + AkInt32& out_iValue ///< Property Value + ) override; + + /// Get the value of a custom property of real type. + /// \return AK_PartialSuccess if the object was found but no matching custom property was found on this object. Note that it could mean this value is the default value. + AKRESULT GetCustomPropertyValue( + AkUniqueID in_ObjectID, ///< Object ID, this is the 32bit ShortID of the AudioFileSource or Sound object found in the .wwu XML file. At runtime it can only be retrieved by the AK_Duration callback when registered with PostEvent(), or by calling Query::QueryAudioObjectIDs() to get all the shortIDs associated with an event. + AkUInt32 in_uPropID, ///< Property ID of your custom property found under the Custom Properties tab of the Wwise project settings. + AkReal32& out_fValue ///< Property Value + ) override; + }; + + class WWISESOUNDENGINE_API FAudioInputPlugin : public IAudioInputPlugin + { + public: + UE_NONCOPYABLE(FAudioInputPlugin); + FAudioInputPlugin() = default; + + void SetAudioInputCallbacks( + AkAudioInputPluginExecuteCallbackFunc in_pfnExecCallback, + AkAudioInputPluginGetFormatCallbackFunc in_pfnGetFormatCallback = nullptr, // Optional + AkAudioInputPluginGetGainCallbackFunc in_pfnGetGainCallback = nullptr // Optional + ) override; + }; + +#if WITH_EDITORONLY_DATA + class WWISESOUNDENGINE_API FErrorTranslator + { + FGetInfoErrorMessageTranslatorFunction GetInfoErrorMessageTranslatorFunction; + + public: + struct TagInformation + { + const AkOSChar* m_pTag = nullptr; + const AkOSChar* m_pStartBlock = nullptr; + const char* m_args = nullptr; + AkOSChar m_parsedInfo[AK_TRANSLATOR_MAX_NAME_SIZE] = { 0 }; + AkUInt32 m_argSize = 0; + AkUInt16 m_len; + bool m_infoIsParsed = false; + }; + + FErrorTranslator(FGetInfoErrorMessageTranslatorFunction InMessageTranslatorFunction); + virtual ~FErrorTranslator() = default; + virtual void Term() {}; + virtual bool GetInfo(TagInformation* in_pTagList, AkUInt32 in_uCount, AkUInt32& out_uTranslated); + }; +#endif + + AkErrorMessageTranslator* NewErrorMessageTranslator(FGetInfoErrorMessageTranslatorFunction InMessageTranslatorFunction) override; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwiseSpatialAudioAPI_Null.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwiseSpatialAudioAPI_Null.h new file mode 100644 index 0000000..4279dff --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwiseSpatialAudioAPI_Null.h @@ -0,0 +1,427 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/WwiseSpatialAudioAPI.h" + +class WWISESOUNDENGINE_API FWwiseSpatialAudioAPI_Null : public IWwiseSpatialAudioAPI +{ +public: + UE_NONCOPYABLE(FWwiseSpatialAudioAPI_Null); + FWwiseSpatialAudioAPI_Null(); + + //////////////////////////////////////////////////////////////////////// + /// @name Basic functions. + /// In order to use SpatialAudio, you need to initalize it using Init, and register the listeners that you plan on using with any of the services offered by SpatialAudio, using + /// RegisterListener respectively, _after_ having registered their corresponding game object to the sound engine. + /// \akwarning At the moment, there can be only one Spatial Audio listener registered at any given time. + //@{ + + /// Initialize the SpatialAudio API. + AKRESULT Init(const AkSpatialAudioInitSettings& in_initSettings) override; + + /// Assign a game object as the Spatial Audio listener. There can be only one Spatial Audio listener registered at any given time; in_gameObjectID will replace any previously set Spatial Audio listener. + /// The game object passed in must be registered by the client, at some point, for sound to be heard. It is not necessary to be registered at the time of calling this function. + /// If not listener is explicitly registered to spatial audio, then a default listener (set via \c AK::SoundEngine::SetDefaultListeners()) is selected. If the are no default listeners, or there are more than one + /// default listeners, then it is necessary to call RegisterListener() to specify which listener to use with Spatial Audio. + AKRESULT RegisterListener( + AkGameObjectID in_gameObjectID ///< Game object ID + ) override; + + /// Unregister a game object as a listener in the SpatialAudio API; clean up Spatial Audio listener data associated with in_gameObjectID. + /// If in_gameObjectID is the current registered listener, calling this function will clear the Spatial Audio listener and + /// Spatial Audio features will be disabled until another listener is registered. + /// This function is optional - listener are automatically unregistered when their game object is deleted in the sound engine. + /// \sa + /// - \ref AK::SpatialAudio::RegisterListener + AKRESULT UnregisterListener( + AkGameObjectID in_gameObjectID ///< Game object ID + ) override; + + /// Define a inner and outer radius around each sound position for a specified game object. + /// The radii are used in spread and distance calculations, simulating a radial sound source. + /// When applying attenuation curves, the distance between the listener and the inner sphere (defined by the sound position and \c in_innerRadius) is used. + /// The spread for each sound position is calculated as follows: + /// - If the listener is outside the outer radius, then the spread is defined by the area that the sphere takes in the listener field of view. Specifically, this angle is calculated as 2.0*asinf( \c in_outerRadius / distance ), where distance is the distance between the listener and the sound position. + /// - When the listener intersects the outer radius (the listener is exactly \c in_outerRadius units away from the sound position), the spread is exactly 50%. + /// - When the listener is in between the inner and outer radius, the spread interpolates linearly from 50% to 100% as the listener transitions from the outer radius towards the inner radius. + /// - If the listener is inside the inner radius, the spread is 100%. + /// \aknote Transmission and diffraction calculations in Spatial Audio always use the center of the sphere (the position(s) passed into \c AK::SoundEngine::SetPosition or \c AK::SoundEngine::SetMultiplePositions) for raycasting. + /// To obtain accurate diffraction and transmission calculations for radial sources, where different parts of the volume may take different paths through or around geometry, + /// it is necessary to pass multiple sound positions into \c AK::SoundEngine::SetMultiplePositions to allow the engine to 'sample' the area at different points. + /// - \ref AK::SoundEngine::SetPosition + /// - \ref AK::SoundEngine::SetMultiplePositions + AKRESULT SetGameObjectRadius( + AkGameObjectID in_gameObjectID, ///< Game object ID + AkReal32 in_outerRadius, ///< Outer radius around each sound position, defining 50% spread. Must satisfy \c in_innerRadius <= \c in outerRadius. + AkReal32 in_innerRadius ///< Inner radius around each sound position, defining 100% spread and 0 attenuation distance. Must satisfy \c in_innerRadius <= \c in outerRadius. + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Helper functions for passing game data to the Reflect plug-in. + /// Use this API for detailed placement of reflection image sources. + /// \aknote These functions are low-level and useful when your game engine already implements a geometrical approach to sound propagation such as an image-source or a ray tracing algorithm. + /// Functions of Geometry are preferred and easier to use with the Reflect plug-in. \endaknote + //@{ + + /// Add or update an individual image source for processing via the AkReflect plug-in. Use this API for detailed placement of + /// reflection image sources, whose positions have been determined by the client, such as from the results of a ray cast, computation or by manual placement. One possible + /// use case is generating reflections that originate far enough away that they can be modeled as a static point source, for example, off of a distant mountain. + /// The SpatialAudio API manages image sources added via SetImageSource() and sends them to the AkReflect plug-in that is on the aux bus with ID \c in_AuxBusID. + /// The image source applies all game objects that have a reflections aux send defined in the authoring tool, or only to a specific game object if \c in_gameObjectID is used. + /// \aknote The \c AkImageSourceSettings struct passed in \c in_info must contain a unique image source ID to be able to identify this image source across frames and when updating and/or removing it later. + /// Each instance of AkReflect has its own set of data, so you may reuse ID, if desired, as long as \c in_gameObjectID and \c in_AuxBusID are different. + /// \aknote It is possible for the AkReflect plugin to process reflections from both \c SetImageSource and the geometric reflections API on the same aux bus and game object, but be aware that image source ID collisions are possible. + /// The image source IDs used by the geometric reflections API are generated from hashed data that uniquely identifies the reflecting surfaces. If a collision occurs, one of the reflections will not be heard. + /// While collision are rare, to ensure that it never occurs use an aux bus for \c SetImageSource that is unique from the aux bus(es) defined in the authoring tool, and from those passed to \c SetEarlyReflectionsAuxSend. + /// \endaknote + /// \aknote For proper operation with AkReflect and the SpatialAudio API, any aux bus using AkReflect should have 'Listener Relative Routing' checked and the 3D Spatialization set to None in the Wwise authoring tool. See \ref spatial_audio_wwiseprojectsetup_businstances for more details. \endaknote + /// \sa + /// - \ref AK::SpatialAudio::RemoveImageSource + /// - \ref AK::SpatialAudio::ClearImageSources + /// - \ref AK::SpatialAudio::SetGameObjectInRoom + /// - \ref AK::SpatialAudio::SetEarlyReflectionsAuxSend + AKRESULT SetImageSource( + AkImageSourceID in_srcID, ///< The ID of the image source being added. + const AkImageSourceSettings& in_info, ///< Image source information. + const char* in_name, ///< Name given to image source, can be used to identify the image source in the AK Reflect plugin UI. + AkUniqueID in_AuxBusID = AK_INVALID_AUX_ID, ///< Aux bus that has the AkReflect plug in for early reflection DSP. + ///< Pass AK_INVALID_AUX_ID to use the reflections aux bus defined in the authoring tool. + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT ///< The ID of the emitter game object to which the image source applies. + ///< Pass AK_INVALID_GAME_OBJECT to apply to all game objects that have a reflections aux bus assigned in the authoring tool. + ) override; + + /// Remove an individual reflection image source that was previously added via \c SetImageSource. + /// \sa + /// - \ref AK::SpatialAudio::SetImageSource + /// - \ref AK::SpatialAudio::ClearImageSources + AKRESULT RemoveImageSource( + AkImageSourceID in_srcID, ///< The ID of the image source to remove. + AkUniqueID in_AuxBusID = AK_INVALID_AUX_ID, ///< Aux bus that was passed to SetImageSource. + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT ///< Game object ID that was passed to SetImageSource. + ) override; + + /// Remove all image sources matching \c in_AuxBusID and \c in_gameObjectID that were previously added via \c SetImageSource. + /// Both \c in_AuxBusID and \c in_gameObjectID can be treated as wild cards matching all aux buses and/or all game object, by passing \c AK_INVALID_AUX_ID and/or \c AK_INVALID_GAME_OBJECT, respectively. + /// \sa + /// - \ref AK::SpatialAudio::SetImageSource + /// - \ref AK::SpatialAudio::RemoveImageSource + AKRESULT ClearImageSources( + AkUniqueID in_AuxBusID = AK_INVALID_AUX_ID, ///< Aux bus that was passed to SetImageSource, or AK_INVALID_AUX_ID to match all aux buses. + AkGameObjectID in_gameObjectID = AK_INVALID_GAME_OBJECT ///< Game object ID that was passed to SetImageSource, or AK_INVALID_GAME_OBJECT to match all game objects. + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Geometry + /// Geometry API for early reflection processing using Reflect. + //@{ + + /// Add or update a set of geometry from the \c SpatialAudio module for geometric reflection and diffraction processing. A geometry set is a logical set of vertices, triangles, and acoustic surfaces, + /// which are referenced by the same \c AkGeometrySetID. The ID (\c in_GeomSetID) must be unique and is also chosen by the client in a manner similar to \c AkGameObjectID's. + /// It is necessary to create at least one geometry instance for each geometry set that is to be used for diffraction and reflection simulation. + /// \sa + /// - \ref AkGeometryParams + /// - \ref AK::SpatialAudio::SetGeometryInstance + /// - \ref AK::SpatialAudio::RemoveGeometry + AKRESULT SetGeometry( + AkGeometrySetID in_GeomSetID, ///< Unique geometry set ID, chosen by client. + const AkGeometryParams& in_params ///< Geometry parameters to set. + ) override; + + /// Remove a set of geometry to the SpatialAudio API. + /// Calling \c AK::SpatialAudio::RemoveGeometry will remove all instances of the geometry from the scene. + /// \sa + /// - \ref AK::SpatialAudio::SetGeometry + AKRESULT RemoveGeometry( + AkGeometrySetID in_SetID ///< ID of geometry set to be removed. + ) override; + + /// Add or update a geometry instance from the \c SpatialAudio module for geometric reflection and diffraction processing. + /// A geometry instance is a unique instance of a geometry set with a specified transform (position, rotation and scale). + /// It is necessary to create at least one geometry instance for each geometry set that is to be used for diffraction and reflection simulation. + /// The ID (\c in_GeomSetInstanceID) must be unique amongst all geometry instances, including geometry instances referencing different geometry sets. The ID is chosen by the client in a manner similar to \c AkGameObjectID's. + /// To update the transform of an existing geometry instance, call SetGeometryInstance again, passing the same \c AkGeometryInstanceID, with the updated transform. + /// \sa + /// - \ref AkGeometryInstanceParams + /// - \ref AK::SpatialAudio::RemoveGeometryInstance + AKRESULT SetGeometryInstance( + AkGeometryInstanceID in_GeometryInstanceID, ///< Unique geometry set instance ID, chosen by client. + const AkGeometryInstanceParams& in_params ///< Geometry instance parameters to set. + ) override; + + /// Remove a geometry instance from the SpatialAudio API. + /// \sa + /// - \ref AK::SpatialAudio::SetGeometryInstance + AKRESULT RemoveGeometryInstance( + AkGeometryInstanceID in_GeometryInstanceID ///< ID of geometry set instance to be removed. + ) override; + + /// Query information about the reflection paths that have been calculated via geometric reflection processing in the SpatialAudio API. This function can be used for debugging purposes. + /// This function must acquire the global sound engine lock and therefore, may block waiting for the lock. + /// \sa + /// - \ref AkReflectionPathInfo + AKRESULT QueryReflectionPaths( + AkGameObjectID in_gameObjectID, ///< The ID of the game object that the client wishes to query. + AkUInt32 in_positionIndex, ///< The index of the associated game object position. + AkVector64& out_listenerPos, ///< Returns the position of the listener game object that is associated with the game object \c in_gameObjectID. + AkVector64& out_emitterPos, ///< Returns the position of the emitter game object \c in_gameObjectID. + AkReflectionPathInfo* out_aPaths, ///< Pointer to an array of \c AkReflectionPathInfo's which will be filled after returning. + AkUInt32& io_uArraySize ///< The number of slots in \c out_aPaths, after returning the number of valid elements written. + ) override; + + //@} + + //////////////////////////////////////////////////////////////////////// + /// @name Rooms and Portals + /// Sound Propagation API using rooms and portals. + //@{ + + /// Add or update a room. Rooms are used to connect portals and define an orientation for oriented reverbs. This function may be called multiple times with the same ID to update the parameters of the room. + /// \akwarning The ID (\c in_RoomID) must be chosen in the same manner as \c AkGameObjectID's, as they are in the same ID-space. The spatial audio lib manages the + /// registration/unregistration of internal game objects for rooms that use these IDs and, therefore, must not collide. + /// Also, the room ID must not be in the reserved range (AkUInt64)(-32) to (AkUInt64)(-2) inclusively. You may, however, explicitly add the default room ID AK::SpatialAudio::kOutdoorRoomID (-1) + /// in order to customize its AkRoomParams, to provide a valid auxiliary bus, for example.\endakwarning + /// \sa + /// - \ref AkRoomID + /// - \ref AkRoomParams + /// - \ref AK::SpatialAudio::RemoveRoom + AKRESULT SetRoom( + AkRoomID in_RoomID, ///< Unique room ID, chosen by the client. + const AkRoomParams& in_Params, ///< Parameter for the room. + const char* in_RoomName = nullptr ///< Name used to identify the room (optional) + ) override; + + /// Remove a room. + /// \sa + /// - \ref AkRoomID + /// - \ref AK::SpatialAudio::SetRoom + AKRESULT RemoveRoom( + AkRoomID in_RoomID ///< Room ID that was passed to \c SetRoom. + ) override; + + /// Add or update an acoustic portal. A portal is an opening that connects two or more rooms to simulate the transmission of reverberated (indirect) sound between the rooms. + /// This function may be called multiple times with the same ID to update the parameters of the portal. The ID (\c in_PortalID) must be chosen in the same manner as \c AkGameObjectID's, + /// as they are in the same ID-space. The spatial audio lib manages the registration/unregistration of internal game objects for portals that use these IDs, and therefore must not collide. + /// \sa + /// - \ref AkPortalID + /// - \ref AkPortalParams + /// - \ref AK::SpatialAudio::RemovePortal + AKRESULT SetPortal( + AkPortalID in_PortalID, ///< Unique portal ID, chosen by the client. + const AkPortalParams& in_Params, ///< Parameter for the portal. + const char* in_PortalName = nullptr ///< Name used to identify portal (optional) + ) override; + + /// Remove a portal. + /// \sa + /// - \ref AkPortalID + /// - \ref AK::SpatialAudio::SetPortal + AKRESULT RemovePortal( + AkPortalID in_PortalID ///< ID of portal to be removed, which was originally passed to SetPortal. + ) override; + + /// Set the room that the game object is currently located in - usually the result of a containment test performed by the client. The room must have been registered with \c SetRoom. + /// Setting the room for a game object provides the basis for the sound propagation service, and also sets which room's reverb aux bus to send to. The sound propagation service traces the path + /// of the sound from the emitter to the listener, and calculates the diffraction as the sound passes through each portal. The portals are used to define the spatial location of the diffracted and reverberated + /// audio. + /// \sa + /// - \ref AK::SpatialAudio::SetRoom + /// - \ref AK::SpatialAudio::RemoveRoom + AKRESULT SetGameObjectInRoom( + AkGameObjectID in_gameObjectID, ///< Game object ID + AkRoomID in_CurrentRoomID ///< RoomID that was passed to \c AK::SpatialAudio::SetRoom + ) override; + + /// Set the early reflections order for reflection calculation. The reflections order indicates the number of times sound can bounce off of a surface. + /// A higher number requires more CPU resources but results in denser early reflections. Set to 0 to globally disable reflections processing. + AKRESULT SetReflectionsOrder( + AkUInt32 in_uReflectionsOrder, ///< Number of reflections to calculate. Valid range [0,4] + bool in_bUpdatePaths ///< Set to true to clear existing higher-order paths and to force the re-computation of new paths. If false, existing paths will remain and new paths will be computed when the emitter or listener moves. + ) override; + + /// Set the diffraction order for geometric path calculation. The diffraction order indicates the number of edges a sound can diffract around. + /// A higher number requires more CPU resources but results in paths found around more complex geometry. Set to 0 to globally disable geometric diffraction processing. + /// \sa + /// - \ref AkSpatialAudioInitSettings::uMaxDiffractionOrder + AKRESULT SetDiffractionOrder( + AkUInt32 in_uDiffractionOrder, ///< Number of diffraction edges to consider in path calculations. Valid range [0,8] + bool in_bUpdatePaths ///< Set to true to clear existing diffraction paths and to force the re-computation of new paths. If false, existing paths will remain and new paths will be computed when the emitter or listener moves. + ) override; + + /// Set the number of rays cast from the listener by the stochastic ray casting engine. + /// A higher number requires more CPU resources but provides more accurate results. Default value (100) should be good for most applications. + /// + AKRESULT SetNumberOfPrimaryRays( + AkUInt32 in_uNbPrimaryRays ///< Number of rays cast from the listener + ) override; + + /// Set the number of frames on which the path validation phase will be spread. Value between [1..[ + /// High values delay the validation of paths. A value of 1 indicates no spread at all. + /// + AKRESULT SetLoadBalancingSpread( + AkUInt32 in_uNbFrames ///< Number of spread frames + ) override; + + /// Set an early reflections auxiliary bus for a particular game object. + /// Geometrical reflection calculation inside spatial audio is enabled for a game object if any sound playing on the game object has a valid early reflections aux bus specified in the authoring tool, + /// or if an aux bus is specified via \c SetEarlyReflectionsAuxSend. + /// The \c in_auxBusID parameter of SetEarlyReflectionsAuxSend applies to sounds playing on the game object \c in_gameObjectID which have not specified an early reflection bus in the authoring tool - + /// the parameter specified on individual sounds' reflection bus takes priority over the value passed in to \c SetEarlyReflectionsAuxSend. + /// \aknote + /// Users may apply this function to avoid duplicating sounds in the actor-mixer hierarchy solely for the sake of specifying a unique early reflection bus, or in any situation where the same + /// sound should be played on different game objects with different early reflection aux buses (the early reflection bus must be left blank in the authoring tool if the user intends to specify it through the API). \endaknote + AKRESULT SetEarlyReflectionsAuxSend( + AkGameObjectID in_gameObjectID, ///< Game object ID + AkAuxBusID in_auxBusID ///< Auxiliary bus ID. Applies only to sounds which have not specified an early reflection bus in the authoring tool. Pass \c AK_INVALID_AUX_ID to set only the send volume. + ) override; + + /// Set an early reflections send volume for a particular game object. + /// The \c in_fSendVolume parameter is used to control the volume of the early reflections send. It is combined with the early reflections volume specified in the authoring tool, and is applied to all sounds + /// playing on the game object. + /// Setting \c in_fSendVolume to 0.f will disable all reflection processing for this game object. + AKRESULT SetEarlyReflectionsVolume( + AkGameObjectID in_gameObjectID, ///< Game object ID + AkReal32 in_fSendVolume ///< Send volume (linear) for auxiliary send. Set 0.f to disable reflection processing. Valid range 0.f-1.f. + ) override; + + /// Set the obstruction and occlusion value for a portal that has been registered with Spatial Audio. + /// Portal obstruction is used to simulate objects between the portal and the listener that are obstructing the sound coming from the portal. + /// The obstruction value affects only the portals dry path, and should relate to how much of the opening + /// is obstructed, and must be calculated by the client. It is applied to the room's game object, as well as to all the emitters virtual positions + /// which propagate from that room through this portal. + /// Portal occlusion is applied only on the room game object, and affects both the wet and dry path of the signal emitted from the room's bus. + AKRESULT SetPortalObstructionAndOcclusion( + AkPortalID in_PortalID, ///< Portal ID. + AkReal32 in_fObstruction, ///< Obstruction value. Valid range 0.f-1.f + AkReal32 in_fOcclusion ///< Occlusion value. Valid range 0.f-1.f + ) override; + + /// Set the obstruction value of the path between a game object and a portal that has been created by Spatial Audio. + /// The obstruction value is applied on one of the path segments of the sound between the emitter and the listener. + /// Diffraction must be enabled on the sound for a diffraction path to be created. + /// Also, there should not be any portals between the provided game object and portal ID parameters. + /// The obstruction value is used to simulate objects between the portal and the game object that are obstructing the sound. + /// Send an obstruction value of 0 to ensure the value is removed from the internal data structure. + AKRESULT SetGameObjectToPortalObstruction( + AkGameObjectID in_gameObjectID, ///< Game object ID + AkPortalID in_PortalID, ///< Portal ID + AkReal32 in_fObstruction ///< Obstruction value. Valid range 0.f-1.f + ) override; + + /// Set the obstruction value of the path between two portals that has been created by Spatial Audio. + /// The obstruction value is applied on one of the path segments of the sound between the emitter and the listener. + /// Diffraction must be enabled on the sound for a diffraction path to be created. + /// Also, there should not be any portals between the two provided ID parameters. + /// The obstruction value is used to simulate objects between the portals that are obstructing the sound. + /// Send an obstruction value of 0 to ensure the value is removed from the internal data structure. + AKRESULT SetPortalToPortalObstruction( + AkPortalID in_PortalID0, ///< Portal ID + AkPortalID in_PortalID1, ///< Portal ID + AkReal32 in_fObstruction ///< Obstruction value. Valid range 0.f-1.f + ) override; + + /// Query information about the wet diffraction amount for the portal \c in_portal, returned as a normalized value \c out_wetDiffraction in the range [0,1]. + /// The wet diffraction is calculated from how far into the 'shadow region' the listener is from the portal. Unlike dry diffraction, the + /// wet diffraction does not depend on the incident angle, but only the normal of the portal. + /// This value is applied by spatial audio, to the Diffraction value and built-in game parameter of the room game object that is + /// on the other side of the portal (relative to the listener). + /// This function must acquire the global sound engine lock and therefore, may block waiting for the lock. + /// \sa + /// - \ref AkSpatialAudioInitSettings + AKRESULT QueryWetDiffraction( + AkPortalID in_portal, ///< The ID of the game object that the client wishes to query. + AkReal32& out_wetDiffraction ///< The number of slots in \c out_aPaths, after returning the number of valid elements written. + ) override; + + /// Query information about the diffraction state for a particular listener and emitter, which has been calculated using the data provided via the spatial audio emitter API. This function can be used for debugging purposes. + /// Returned in \c out_aPaths, this array contains the sound paths calculated from diffraction around a geometric edge and/or diffraction through portals connecting rooms. + /// No paths will be returned in any of the following conditions: (1) the emitter game object has a direct line of sight to the listener game object, (2) the emitter and listener are in the same room, and the listener is completely outside the radius of the emitter, + /// or (3) The emitter and listener are in different rooms, but there are no paths found via portals between the emitter and the listener. + /// A single path with zero diffraction nodes is returned when all of the following conditions are met: (1) the emitter and listener are in the same room, (2) there is no direct line of sight, and (3) either the Voice's Attenuation's curve max distance is exceeded or the accumulated diffraction coefficient exceeds 1.0. + /// This function must acquire the global sound engine lock and, therefore, may block waiting for the lock. + /// \sa + /// - \ref AkDiffractionPathInfo + AKRESULT QueryDiffractionPaths( + AkGameObjectID in_gameObjectID, ///< The ID of the game object that the client wishes to query. + AkUInt32 in_positionIndex, ///< The index of the associated game object position. + AkVector64& out_listenerPos, ///< Returns the position of the listener game object that is associated with the game object \c in_gameObjectID. + AkVector64& out_emitterPos, ///< Returns the position of the emitter game object \c in_gameObjectID. + AkDiffractionPathInfo* out_aPaths, ///< Pointer to an array of \c AkDiffractionPathInfo's which will be filled on return. + AkUInt32& io_uArraySize ///< The number of slots in \c out_aPaths, after returning the number of valid elements written. + ) override; + + /// Reset the stochastic engine state by re-initializing the random seeds. + /// + AKRESULT ResetStochasticEngine() override; + + //@} + + class WWISESOUNDENGINE_API FReverbEstimation : public IReverbEstimation + { + public: + UE_NONCOPYABLE(FReverbEstimation); + FReverbEstimation() = default; + + //////////////////////////////////////////////////////////////////////// + /// @name Reverb estimation. + /// These functions can be used to estimate the reverb parameters of a physical environment, using its volume and surface area + //@{ + + /// This is used to estimate the line of best fit through the absorption values of an Acoustic Texture. + /// This value is what's known as the HFDamping. + /// return Gradient of line of best fit through y = mx + c. + float CalculateSlope(const AkAcousticTexture& texture) override; + + /// Calculate average absorption values using each of the textures in in_textures, weighted by their corresponding surface area. + void GetAverageAbsorptionValues(AkAcousticTexture* in_textures, float* in_surfaceAreas, int in_numTextures, AkAcousticTexture& out_average) override; + + /// Estimate the time taken (in seconds) for the sound reverberation in a physical environment to decay by 60 dB. + /// This is estimated using the Sabine equation. The T60 decay time can be used to drive the decay parameter of a reverb effect. + AKRESULT EstimateT60Decay( + AkReal32 in_volumeCubicMeters, ///< The volume (in cubic meters) of the physical environment. 0 volume or negative volume will give a decay estimate of 0. + AkReal32 in_surfaceAreaSquaredMeters, ///< The surface area (in squared meters) of the physical environment. Must be >= AK_SA_MIN_ENVIRONMENT_SURFACE_AREA + AkReal32 in_environmentAverageAbsorption, ///< The average absorption amount of the surfaces in the environment. Must be between AK_SA_MIN_ENVIRONMENT_ABSORPTION and 1. + AkReal32& out_decayEstimate ///< Returns the time taken (in seconds) for the reverberation to decay bu 60 dB. + ) override; + + /// Estimate the time taken (in milliseconds) for the first reflection to reach the listener. + /// This assumes the emitter and listener are both positioned in the centre of the environment. + AKRESULT EstimateTimeToFirstReflection( + AkVector in_environmentExtentMeters, ///< Defines the dimensions of the environment (in meters) relative to the center; all components must be positive numbers. + AkReal32& out_timeToFirstReflectionMs, ///< Returns the time taken (in milliseconds) for the first reflection to reach the listener. + AkReal32 in_speedOfSound = 343.0f ///< Defaults to 343.0 - the speed of sound in dry air. Must be > 0. + ) override; + + /// Estimate the high frequency damping from a collection of AkAcousticTextures and corresponding surface areas. + /// The high frequency damping is a measure of how much high frequencies are dampened compared to low frequencies. > 0 indicates more high frequency damping than low frequency damping. < 0 indicates more low frequency damping than high frequency damping. 0 indicates uniform damping. + /// The average absorption values are calculated using each of the textures in the collection, weighted by their corresponding surface area. + /// The HFDamping is then calculated as the line-of-best-fit through the average absorption values. + AKRESULT EstimateHFDamping( + AkAcousticTexture* in_textures, ///< A collection of AkAcousticTexture structs from which to calculate the average high frequency damping. + float* in_surfaceAreas, ///< Surface area values for each of the textures in in_textures. + int in_numTextures, ///< The number of textures in in_textures (and the number of surface area values in in_surfaceAreas). + AkReal32& out_hfDamping ///< Returns the high frequency damping value. > 0 indicates more high frequency damping than low frequency damping. < 0 indicates more low frequency damping than high frequency damping. 0 indicates uniform damping. + ) override; + + //@} + }; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwiseStreamMgrAPI_Null.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwiseStreamMgrAPI_Null.h new file mode 100644 index 0000000..8df1fca --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/API_Null/WwiseStreamMgrAPI_Null.h @@ -0,0 +1,181 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/API/WwiseStreamMgrAPI.h" + +class WWISESOUNDENGINE_API FWwiseStreamMgrAPI_Null : public IWwiseStreamMgrAPI +{ +public: + UE_NONCOPYABLE(FWwiseStreamMgrAPI_Null); + FWwiseStreamMgrAPI_Null() = default; + + AK::IAkStreamMgr* GetAkStreamMgrInstance() override; + + /// \name Audiokinetic implementation-specific Stream Manager factory. + //@{ + /// Stream Manager factory. + /// \remarks + /// - In order for the Stream Manager to work properly, you also need to create + /// at least one streaming device (and implement its I/O hook), and register the + /// File Location Resolver with AK::StreamMgr::SetFileLocationResolver(). + /// - Use AK::StreamMgr::GetDefaultSettings(), then modify the settings you want, + /// then feed this function with them. + /// \sa + /// - AK::IAkStreamMgr + /// - AK::StreamMgr::SetFileLocationResolver() + /// - AK::StreamMgr::GetDefaultSettings() + AK::IAkStreamMgr* Create( + const AkStreamMgrSettings& in_settings ///< Stream manager initialization settings. + ) override; + + /// Get the default values for the Stream Manager's settings. + /// \sa + /// - AK::StreamMgr::Create() + /// - AkStreamMgrSettings + /// - \ref streamingmanager_settings + void GetDefaultSettings( + AkStreamMgrSettings& out_settings ///< Returned AkStreamMgrSettings structure with default values. + ) override; + + /// Get the one and only File Location Resolver registered to the Stream Manager. + /// \sa + /// - AK::StreamMgr::IAkFileLocationResolver + /// - AK::StreamMgr::SetFileLocationResolver() + AK::StreamMgr::IAkFileLocationResolver* GetFileLocationResolver() override; + + /// Register the one and only File Location Resolver to the Stream Manager. + /// \sa + /// - AK::StreamMgr::IAkFileLocationResolver + void SetFileLocationResolver( + AK::StreamMgr::IAkFileLocationResolver* in_pFileLocationResolver ///< Interface to your File Location Resolver + ) override; + + //@} + + /// \name Stream Manager: High-level I/O devices management. + //@{ + /// Streaming device creation. + /// Creates a high-level device, with specific settings. + /// You need to provide the associated low-level I/O hook, implemented on your side. + /// \return The device ID. AK_INVALID_DEVICE_ID if there was an error and it could not be created. + /// \warning + /// - This function is not thread-safe. + /// - Use a blocking hook (IAkIOHookBlocking) with SCHEDULER_BLOCKING devices, and a + /// deferred hook (IAkIOHookDeferredBatch) with SCHEDULER_DEFERRED_LINED_UP devices (these flags are + /// specified in the device settings (AkDeviceSettings). The pointer to IAkLowLevelIOHook is + /// statically cast internally into one of these hooks. Implementing the wrong (or no) interface + /// will result into a crash. + /// \remarks + /// - You may use AK::StreamMgr::GetDefaultDeviceSettings() first to get default values for the + /// settings, change those you want, then feed the structure to this function. + /// - The returned device ID should be kept by the Low-Level IO, to assign it to file descriptors + /// in AK::StreamMgr::IAkFileLocationResolver::Open(). + /// \sa + /// - AK::StreamMgr::IAkLowLevelIOHook + /// - AK::StreamMgr::GetDefaultDeviceSettings() + /// - \ref streamingmanager_settings + AkDeviceID CreateDevice( + const AkDeviceSettings& in_settings, ///< Device settings. + AK::StreamMgr::IAkLowLevelIOHook* in_pLowLevelHook ///< Associated low-level I/O hook. Pass either a IAkIOHookBlocking or a IAkIOHookDeferredBatch interface, consistent with the type of the scheduler. + ) override; + + /// Streaming device destruction. + /// \return AK_Success if the device was successfully destroyed. + /// \warning This function is not thread-safe. No stream should exist for that device when it is destroyed. + AKRESULT DestroyDevice( + AkDeviceID in_deviceID ///< Device ID of the device to destroy. + ) override; + + /// Execute pending I/O operations on all created I/O devices. + /// This should only be called in single-threaded environments where an I/O device cannot spawn a thread. + /// \return AK_Success when called from an appropriate environment, AK_NotCompatible otherwise. + /// \sa + /// - AK::StreamMgr::CreateDevice() + AKRESULT PerformIO() override; + + /// Get the default values for the streaming device's settings. Recommended usage + /// is to call this function first, then pass the settings to AK::StreamMgr::CreateDevice(). + /// \sa + /// - AK::StreamMgr::CreateDevice() + /// - AkDeviceSettings + /// - \ref streamingmanager_settings + void GetDefaultDeviceSettings( + AkDeviceSettings& out_settings ///< Returned AkDeviceSettings structure with default values. + ) override; + //@} + + /// \name Language management. + //@{ + /// Set the current language once and only once, here. The language name is stored in a static buffer + /// inside the Stream Manager. In order to resolve localized (language-specific) file location, + /// AK::StreamMgr::IAkFileLocationResolver implementations query this string. They may use it to + /// construct a file path (for e.g. SDK/samples/SoundEngine/Common/AkFileLocationBase.cpp), or to + /// find a language-specific file within a look-up table (for e.g. SDK/samples/SoundEngine/Common/AkFilePackageLUT.cpp). + /// Pass a valid null-terminated string, without a trailing slash or backslash. Empty strings are accepted. + /// You may register for language changes (see RegisterToLanguageChangeNotification()). + /// After changing the current language, all observers are notified. + /// \return AK_Success if successful (if language string has less than AK_MAX_LANGUAGE_NAME_SIZE characters). AK_Fail otherwise. + /// \warning Not multithread safe. + /// \sa + /// - AK::StreamMgr::GetCurrentLanguage() + /// - AK::StreamMgr::AddLanguageChangeObserver() + AKRESULT SetCurrentLanguage( + const AkOSChar* in_pszLanguageName ///< Language name. + ) override; + + /// Get the current language. The language name is stored in a static buffer inside the Stream Manager, + /// with AK::StreamMgr::SetCurrentLanguage(). In order to resolve localized (language-specific) file location, + /// AK::StreamMgr::IAkFileLocationResolver implementations query this string. They may use it to + /// construct a file path (for e.g. SDK/samples/SoundEngine/Common/AkFileLocationBase.cpp), or to + /// find a language-specific file within a look-up table (for e.g. SDK/samples/SoundEngine/Common/AkFilePackageLUT.cpp). + /// \return Current language. + /// \sa AK::StreamMgr::SetCurrentLanguage() + const AkOSChar* GetCurrentLanguage() override; + + /// Register to language change notifications. + /// \return AK_Success if successful, AK_Fail otherwise (no memory or no cookie). + /// \warning Not multithread safe. + /// \sa + /// - AK::StreamMgr::SetCurrentLanguage() + /// - AK::StreamMgr::RemoveLanguageChangeObserver() + AKRESULT AddLanguageChangeObserver( + AK::StreamMgr::AkLanguageChangeHandler in_handler, ///< Callback function. + void* in_pCookie ///< Cookie, passed back to AkLanguageChangeHandler. Must set. + ) override; + + /// Unregister to language change notifications. Use the cookie you have passed to + /// AddLanguageChangeObserver() to identify the observer. + /// \warning Not multithread safe. + /// \sa + /// - AK::StreamMgr::SetCurrentLanguage() + /// - AK::StreamMgr::AddLanguageChangeObserver() + void RemoveLanguageChangeObserver( + void* in_pCookie ///< Cookie that was passed to AddLanguageChangeObserver(). + ) override; + + /// \name Stream Manager: Cache management. + //@{ + /// Flush cache of all devices. This function has no effect for devices where + /// AkDeviceSettings::bUseStreamCache was set to false (no caching). + /// \sa + /// - \ref streamingmanager_settings + void FlushAllCaches() override; + + //@} +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/Stats/SoundEngine_Null.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/Stats/SoundEngine_Null.h new file mode 100644 index 0000000..6c61cc4 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/Stats/SoundEngine_Null.h @@ -0,0 +1,24 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "Wwise/Stats/SoundEngine.h" +#include "Stats/Stats.h" +#include "Logging/LogMacros.h" + +DECLARE_CYCLE_STAT_EXTERN(TEXT("SoundEngine Null API Calls"), STAT_WwiseSoundEngineAPI_Null, STATGROUP_WwiseSoundEngine, WWISESOUNDENGINE_API); diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/WwiseSoundEngine_Null.h b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/WwiseSoundEngine_Null.h new file mode 100644 index 0000000..0260cf8 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/Public/Wwise/WwiseSoundEngine_Null.h @@ -0,0 +1,36 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +#pragma once + +#include "CoreTypes.h" + +#include "Wwise/WwiseSoundEngineVersionModule.h" + +class WWISESOUNDENGINE_API FWwiseSoundEngine_Null : public IWwiseSoundEngineVersionModule +{ +public: + virtual IWwiseCommAPI* GetComm() override; + virtual IWwiseMemoryMgrAPI* GetMemoryMgr() override; + virtual IWwiseMonitorAPI* GetMonitor() override; + virtual IWwiseMusicEngineAPI* GetMusicEngine() override; + virtual IWwiseSoundEngineAPI* GetSoundEngine() override; + virtual IWwiseSpatialAudioAPI* GetSpatialAudio() override; + virtual IWwiseStreamMgrAPI* GetStreamMgr() override; + virtual IWwisePlatformAPI* GetPlatform() override; + virtual IWAAPI* GetWAAPI() override; +}; diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/WwiseSoundEngine_Null_OptionalModule.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/WwiseSoundEngine_Null_OptionalModule.Build.cs new file mode 100644 index 0000000..65d6738 --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/WwiseSoundEngine_Null_OptionalModule.Build.cs @@ -0,0 +1,77 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; +using System.IO; +using System.Linq; +using System.Collections.Generic; + +public struct WwiseSoundEngine_Null +{ + private static List AkLibs = new List + { + }; + + public static void Apply(WwiseSoundEngine SE, ReadOnlyTargetRules Target) + { + var VersionNumber = "Null"; + var ModuleName = "WwiseSoundEngine_" + VersionNumber; + var ModuleDirectory = Path.Combine(SE.ModuleDirectory, "../" + ModuleName); + + SE.AddSoundEngineDirectory("WwiseSoundEngine_" + VersionNumber, true); + + // If packaging as an Engine plugin, the UBT expects to already have a precompiled plugin available + // This can be set to true so long as plugin was already precompiled + SE.bUsePrecompiled = false; + SE.bPrecompile = false; + + string ThirdPartyFolder = Path.Combine(SE.ModuleDirectory, "../../ThirdParty"); + var WwiseUEPlatformInstance = WwiseUEPlatform.GetWwiseUEPlatformInstance(Target, VersionNumber, ThirdPartyFolder); + SE.PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + SE.bAllowConfidentialPlatformDefines = true; + + foreach (var Platform in GetAvailablePlatforms(ModuleDirectory)) + { + SE.ExternalDependencies.Add(string.Format("{0}/WwiseUEPlatform_{1}.Build.cs", ModuleDirectory, VersionNumber, Platform)); + } + + if (Target.bBuildEditor) + { + foreach (var Platform in GetAvailablePlatforms(ModuleDirectory)) + { + SE.PublicDefinitions.Add("AK_PLATFORM_" + Platform.ToUpper()); + } + } + } + + private static List GetAvailablePlatforms(string ModuleDir) + { + var FoundPlatforms = new List(); + const string StartPattern = "WwiseUEPlatform_"; + const string EndPattern = ".Build.cs"; + foreach (var BuildCsFile in System.IO.Directory.GetFiles(ModuleDir, "*" + EndPattern)) + { + if (BuildCsFile.Contains(StartPattern) && BuildCsFile.EndsWith(EndPattern)) + { + var Platform = BuildCsFile.Remove(BuildCsFile.Length - EndPattern.Length).Split('_').Last(); + FoundPlatforms.Add(Platform); + } + } + + return FoundPlatforms; + } +} diff --git a/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/WwiseUEPlatform_Null.Build.cs b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/WwiseUEPlatform_Null.Build.cs new file mode 100644 index 0000000..93d2e2a --- /dev/null +++ b/Pawn_Unreal/Plugins/Wwise/Source/WwiseSoundEngine_Null/WwiseUEPlatform_Null.Build.cs @@ -0,0 +1,73 @@ +/******************************************************************************* +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. +*******************************************************************************/ + +using UnrealBuildTool; +using System; +using System.IO; +using System.Collections.Generic; + +public class WwiseUEPlatform_Null : WwiseUEPlatform +{ + public WwiseUEPlatform_Null(ReadOnlyTargetRules in_TargetRules, string in_ThirdPartyFolder) : base(in_TargetRules, in_ThirdPartyFolder) + { + } + + public override string GetLibraryFullPath(string LibName, string LibPath) + { + return ""; + } + + public override bool SupportsAkAutobahn { get { return true; } } + + public override bool SupportsCommunication { get { return true; } } + + public override bool SupportsDeviceMemory { get { return false; } } + + public override string AkPlatformLibDir { get { return "Null"; } } + + public override string DynamicLibExtension { get { return ""; } } + + public override List GetAdditionalWwiseLibs() + { + return new List(); + } + + public override List GetPublicSystemLibraries() + { + return new List(); + } + + public override List GetPublicDelayLoadDLLs() + { + return new List(); + } + + public override List GetPublicDefinitions() + { + return new List(); + } + + public override Tuple GetAdditionalPropertyForReceipt(string ModuleDirectory) + { + return null; + } + + public override List GetPublicFrameworks() + { + return new List(); + } +}